Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .changeset/chatty-wombats-rest.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
Comment thread
jacekradko marked this conversation as resolved.
8 changes: 8 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,14 @@ export default tseslint.config([
'custom-rules/no-navigate-useClerk': 'error',
},
},
{
name: 'packages/clerk-js - vitest',
files: ['packages/clerk-js/src/**/*.spec.{ts,tsx}'],
rules: {
'jest/unbound-method': 'off',
'@typescript-eslint/unbound-method': 'off',
},
},
{
name: 'packages/expo-passkeys',
files: ['packages/expo-passkeys/src/**/*'],
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@
"@types/react": "catalog:react",
"@types/react-dom": "catalog:react",
"@vitejs/plugin-react": "^4.5.1",
"@vitest/coverage-v8": "3.0.2",
"@vitest/coverage-v8": "3.0.5",
"chalk": "4.1.2",
"citty": "^0.1.6",
"conventional-changelog-conventionalcommits": "^4.6.3",
Expand Down
4 changes: 4 additions & 0 deletions packages/clerk-js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"test:cache:clear": "jest --clearCache --useStderr",
"test:ci": "jest --maxWorkers=70%",
"test:coverage": "jest --collectCoverage && open coverage/lcov-report/index.html",
"test:jest": "jest",
"test:vitest": "vitest",
Comment thread
jacekradko marked this conversation as resolved.
"watch": "rspack build --config rspack.config.js --env production --watch"
},
Expand Down Expand Up @@ -80,6 +81,7 @@
"swr": "2.3.3"
},
"devDependencies": {
"@emotion/jest": "^11.13.0",
"@rsdoctor/rspack-plugin": "^0.4.13",
"@rspack/cli": "^1.2.8",
"@rspack/core": "^1.2.8",
Expand All @@ -88,6 +90,8 @@
"@swc/jest": "^0.2.38",
"@types/cloudflare-turnstile": "^0.2.2",
"@types/webpack-env": "^1.18.8",
"jsdom": "^24.1.1",
"vite-plugin-svgr": "^4.2.0",
"webpack-merge": "^5.10.0"
},
"peerDependencies": {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
/**
* @jest-environment node
* @vitest-environment node
*/

import { describe, expect, it } from 'vitest';

describe('clerk/headless', () => {
it('JS-689: should not error when loading headless', () => {
expect(() => {
Expand Down
12 changes: 12 additions & 0 deletions packages/clerk-js/src/__tests__/mocks/svgMock.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from 'react';

// A simple span component to mock SVG imports in Vitest
const SvgMock = React.forwardRef<HTMLSpanElement, React.SVGProps<SVGSVGElement>>((props, ref) => (
<span
ref={ref}
{...props}
/>
));

export const ReactComponent = SvgMock;
export default SvgMock;
Original file line number Diff line number Diff line change
@@ -1,29 +1,31 @@
import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from 'vitest';

import type { DevBrowser } from '../auth/devBrowser';
import { Clerk } from '../clerk';
import type { DisplayConfig } from '../resources/internal';
import { Client, Environment } from '../resources/internal';

const mockClientFetch = jest.fn();
const mockEnvironmentFetch = jest.fn();
const mockClientFetch = vi.fn();
const mockEnvironmentFetch = vi.fn();

jest.mock('../resources/Client');
jest.mock('../resources/Environment');
vi.mock('../resources/Client');
vi.mock('../resources/Environment');

// Because Jest, don't ask me why...
jest.mock('../auth/devBrowser', () => ({
vi.mock('../auth/devBrowser', () => ({
createDevBrowser: (): DevBrowser => ({
clear: jest.fn(),
setup: jest.fn(),
getDevBrowserJWT: jest.fn(() => 'deadbeef'),
setDevBrowserJWT: jest.fn(),
removeDevBrowserJWT: jest.fn(),
clear: vi.fn(),
setup: vi.fn(),
getDevBrowserJWT: vi.fn(() => 'deadbeef'),
setDevBrowserJWT: vi.fn(),
removeDevBrowserJWT: vi.fn(),
}),
}));

Client.getOrCreateInstance = jest.fn().mockImplementation(() => {
Client.getOrCreateInstance = vi.fn().mockImplementation(() => {
return { fetch: mockClientFetch };
});
Environment.getInstance = jest.fn().mockImplementation(() => {
Environment.getInstance = vi.fn().mockImplementation(() => {
return { fetch: mockEnvironmentFetch };
});

Expand Down Expand Up @@ -59,14 +61,14 @@ const developmentPublishableKey = 'pk_test_Y2xlcmsuYWJjZWYuMTIzNDUuZGV2LmxjbGNsZ
const productionPublishableKey = 'pk_live_Y2xlcmsuYWJjZWYuMTIzNDUucHJvZC5sY2xjbGVyay5jb20k';

describe('Clerk singleton - Redirects', () => {
const mockNavigate = jest.fn((to: string) => Promise.resolve(to));
const mockNavigate = vi.fn((to: string) => Promise.resolve(to));
const mockedLoadOptions = { routerPush: mockNavigate, routerReplace: mockNavigate };

let mockWindowLocation;
let mockHref: jest.Mock;
let mockHref: vi.Mock;

beforeEach(() => {
mockHref = jest.fn();
mockHref = vi.fn();
mockWindowLocation = {
host: 'test.host',
hostname: 'test.host',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { InstanceType } from '@clerk/types';
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';

import { SUPPORTED_FAPI_VERSION } from '../constants';
import { createFapiClient } from '../fapiClient';
Expand All @@ -24,11 +25,13 @@ type RecursivePartial<T> = {
[P in keyof T]?: RecursivePartial<T[P]>;
};

const originalFetch = global.fetch;

// @ts-ignore -- We don't need to fully satisfy the fetch types for the sake of this mock
global.fetch = jest.fn(() =>
global.fetch = vi.fn(() =>
Promise.resolve<RecursivePartial<Response>>({
headers: {
get: jest.fn(() => 'sess_43'),
get: vi.fn(() => 'sess_43'),
},
json: () => Promise.resolve({ foo: 42 }),
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}),
Expand All @@ -54,12 +57,13 @@ beforeAll(() => {
});

beforeEach(() => {
(global.fetch as jest.Mock).mockClear();
(global.fetch as vi.Mock).mockClear();
});

afterAll(() => {
window.location = oldWindowLocation;
delete window.Clerk;
global.fetch = originalFetch;
});

describe('buildUrl(options)', () => {
Expand Down Expand Up @@ -184,10 +188,10 @@ describe('request', () => {
});

it('returns array response as array', async () => {
(global.fetch as jest.Mock).mockResolvedValueOnce(
(global.fetch as vi.Mock).mockResolvedValueOnce(
Promise.resolve<RecursivePartial<Response>>({
headers: {
get: jest.fn(() => 'sess_43'),
get: vi.fn(() => 'sess_43'),
},
json: () => Promise.resolve([{ foo: 42 }]),
}),
Expand All @@ -201,7 +205,7 @@ describe('request', () => {
});

it('handles the empty body on 204 response, returning null', async () => {
(global.fetch as jest.Mock).mockResolvedValueOnce(
(global.fetch as vi.Mock).mockResolvedValueOnce(
Promise.resolve<RecursivePartial<Response>>({
status: 204,
json: () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import type { TokenResource } from '@clerk/types';
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';

import { Token } from '../resources/internal';
import { SessionTokenCache } from '../tokenCache';

// This is required since abstract TS methods are undefined in Jest
jest.mock('../resources/Base', () => {
vi.mock('../resources/Base', () => {
class BaseResource {}

return {
Expand All @@ -17,11 +18,11 @@ const jwt =

describe('MemoryTokenCache', () => {
beforeAll(() => {
jest.useFakeTimers();
vi.useFakeTimers();
});

afterAll(() => {
jest.useRealTimers();
vi.useRealTimers();
});

describe('clear()', () => {
Expand Down Expand Up @@ -87,7 +88,7 @@ describe('MemoryTokenCache', () => {
expect(isResolved).toBe(false);

// Wait tokenResolver to resolve
jest.advanceTimersByTime(100);
vi.advanceTimersByTime(100);
await tokenResolver;

// Cache is not empty, retrieve the resolved tokenResolver
Expand All @@ -98,7 +99,7 @@ describe('MemoryTokenCache', () => {
});

// Advance the timer to force the JWT expiration
jest.advanceTimersByTime(60 * 1000);
vi.advanceTimersByTime(60 * 1000);

// Cache is empty, tokenResolver has been removed due to JWT expiration
expect(cache.get(key)).toBeUndefined();
Expand All @@ -125,11 +126,11 @@ describe('MemoryTokenCache', () => {
expect(cache.get(key)).toMatchObject(key);

// 44s since token created
jest.advanceTimersByTime(45 * 1000);
vi.advanceTimersByTime(45 * 1000);
expect(cache.get(key)).toMatchObject(key);

// 46s since token created
jest.advanceTimersByTime(1 * 1000);
vi.advanceTimersByTime(1 * 1000);
expect(cache.get(key)).toBeUndefined();
});

Expand All @@ -150,15 +151,15 @@ describe('MemoryTokenCache', () => {
expect(cache.get(key)).toMatchObject(key);

// 45s since token created
jest.advanceTimersByTime(45 * 1000);
vi.advanceTimersByTime(45 * 1000);
expect(cache.get(key, 0)).toMatchObject(key);

// 54s since token created
jest.advanceTimersByTime(9 * 1000);
vi.advanceTimersByTime(9 * 1000);
expect(cache.get(key, 0)).toMatchObject(key);

// 55s since token created
jest.advanceTimersByTime(1 * 1000);
vi.advanceTimersByTime(1 * 1000);
expect(cache.get(key, 0)).toBeUndefined();
});
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
jest.mock('@clerk/shared/keys', () => {
return { getCookieSuffix: jest.fn() };
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';

vi.mock('@clerk/shared/keys', () => {
return { getCookieSuffix: vi.fn() };
});
jest.mock('@clerk/shared/logger', () => {
return { logger: { logOnce: jest.fn() } };
vi.mock('@clerk/shared/logger', () => {
return { logger: { logOnce: vi.fn() } };
});
import { getCookieSuffix as getSharedCookieSuffix } from '@clerk/shared/keys';
import { logger } from '@clerk/shared/logger';
Expand All @@ -11,12 +13,12 @@ import { getCookieSuffix } from '../cookieSuffix';

describe('getCookieSuffix', () => {
beforeEach(() => {
(getSharedCookieSuffix as jest.Mock).mockRejectedValue(new Error('mocked error for insecure context'));
(getSharedCookieSuffix as vi.Mock).mockRejectedValue(new Error('mocked error for insecure context'));
});

afterEach(() => {
(getSharedCookieSuffix as jest.Mock).mockReset();
(logger.logOnce as jest.Mock).mockReset();
(getSharedCookieSuffix as vi.Mock).mockReset();
(logger.logOnce as vi.Mock).mockReset();
});

describe('getCookieSuffix(publishableKey, subtle?)', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';

import type { FapiClient } from '../../fapiClient';
import { createDevBrowser } from '../devBrowser';

Expand All @@ -8,7 +10,7 @@ type RecursivePartial<T> = {
describe('Thrown errors', () => {
beforeEach(() => {
// @ts-ignore
global.fetch = jest.fn(() =>
global.fetch = vi.fn(() =>
Promise.resolve<RecursivePartial<Response>>({
ok: false,
json: () =>
Expand All @@ -29,17 +31,17 @@ describe('Thrown errors', () => {

afterEach(() => {
// @ts-ignore
global.fetch?.mockClear();
vi.mocked(global.fetch)?.mockClear();
});

// Note: The test runs without any initial or mocked values on __clerk_db_jwt cookies.
// It is expected to modify the test accordingly if cookies are mocked for future extra testing.
it('throws any FAPI errors during dev browser creation', async () => {
const mockCreateFapiClient = jest.fn().mockImplementation(() => {
const mockCreateFapiClient = vi.fn().mockImplementation(() => {
return {
buildUrl: jest.fn(() => 'https://white-koala-42.clerk.accounts.dev/dev_browser'),
onAfterResponse: jest.fn(),
onBeforeRequest: jest.fn(),
buildUrl: vi.fn(() => 'https://white-koala-42.clerk.accounts.dev/dev_browser'),
onAfterResponse: vi.fn(),
onBeforeRequest: vi.fn(),
};
});

Expand Down
Loading
Loading