Skip to content

Commit ce9e403

Browse files
feat(clerk-js): Additional vitest specs (#5716)
Co-authored-by: Tom Milewski <[email protected]>
1 parent 4118ed7 commit ce9e403

File tree

67 files changed

+2030
-261
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

67 files changed

+2030
-261
lines changed

.changeset/chatty-wombats-rest.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
---
2+
---

eslint.config.mjs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,14 @@ export default tseslint.config([
369369
'custom-rules/no-navigate-useClerk': 'error',
370370
},
371371
},
372+
{
373+
name: 'packages/clerk-js - vitest',
374+
files: ['packages/clerk-js/src/**/*.spec.{ts,tsx}'],
375+
rules: {
376+
'jest/unbound-method': 'off',
377+
'@typescript-eslint/unbound-method': 'off',
378+
},
379+
},
372380
{
373381
name: 'packages/expo-passkeys',
374382
files: ['packages/expo-passkeys/src/**/*'],

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@
8383
"@types/react": "catalog:react",
8484
"@types/react-dom": "catalog:react",
8585
"@vitejs/plugin-react": "^4.5.1",
86-
"@vitest/coverage-v8": "3.0.2",
86+
"@vitest/coverage-v8": "3.0.5",
8787
"chalk": "4.1.2",
8888
"citty": "^0.1.6",
8989
"conventional-changelog-conventionalcommits": "^4.6.3",

packages/clerk-js/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
"test:cache:clear": "jest --clearCache --useStderr",
5252
"test:ci": "jest --maxWorkers=70%",
5353
"test:coverage": "jest --collectCoverage && open coverage/lcov-report/index.html",
54+
"test:jest": "jest",
5455
"test:vitest": "vitest",
5556
"watch": "rspack build --config rspack.config.js --env production --watch"
5657
},
@@ -80,6 +81,7 @@
8081
"swr": "2.3.3"
8182
},
8283
"devDependencies": {
84+
"@emotion/jest": "^11.13.0",
8385
"@rsdoctor/rspack-plugin": "^0.4.13",
8486
"@rspack/cli": "^1.2.8",
8587
"@rspack/core": "^1.2.8",
@@ -88,6 +90,7 @@
8890
"@swc/jest": "^0.2.38",
8991
"@types/cloudflare-turnstile": "^0.2.2",
9092
"@types/webpack-env": "^1.18.8",
93+
"jsdom": "^24.1.1",
9194
"webpack-merge": "^5.10.0"
9295
},
9396
"peerDependencies": {

packages/clerk-js/src/__tests__/headless.test.ts renamed to packages/clerk-js/src/__tests__/headless.spec.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
/**
2-
* @jest-environment node
2+
* @vitest-environment node
33
*/
44

5+
import { describe, expect, it } from 'vitest';
6+
57
describe('clerk/headless', () => {
68
it('JS-689: should not error when loading headless', () => {
79
expect(() => {

packages/clerk-js/src/core/__tests__/clerk.redirects.test.ts renamed to packages/clerk-js/src/core/__tests__/clerk.redirects.spec.ts

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,31 @@
1+
import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2+
13
import type { DevBrowser } from '../auth/devBrowser';
24
import { Clerk } from '../clerk';
35
import type { DisplayConfig } from '../resources/internal';
46
import { Client, Environment } from '../resources/internal';
57

6-
const mockClientFetch = jest.fn();
7-
const mockEnvironmentFetch = jest.fn();
8+
const mockClientFetch = vi.fn();
9+
const mockEnvironmentFetch = vi.fn();
810

9-
jest.mock('../resources/Client');
10-
jest.mock('../resources/Environment');
11+
vi.mock('../resources/Client');
12+
vi.mock('../resources/Environment');
1113

1214
// Because Jest, don't ask me why...
13-
jest.mock('../auth/devBrowser', () => ({
15+
vi.mock('../auth/devBrowser', () => ({
1416
createDevBrowser: (): DevBrowser => ({
15-
clear: jest.fn(),
16-
setup: jest.fn(),
17-
getDevBrowserJWT: jest.fn(() => 'deadbeef'),
18-
setDevBrowserJWT: jest.fn(),
19-
removeDevBrowserJWT: jest.fn(),
17+
clear: vi.fn(),
18+
setup: vi.fn(),
19+
getDevBrowserJWT: vi.fn(() => 'deadbeef'),
20+
setDevBrowserJWT: vi.fn(),
21+
removeDevBrowserJWT: vi.fn(),
2022
}),
2123
}));
2224

23-
Client.getOrCreateInstance = jest.fn().mockImplementation(() => {
25+
Client.getOrCreateInstance = vi.fn().mockImplementation(() => {
2426
return { fetch: mockClientFetch };
2527
});
26-
Environment.getInstance = jest.fn().mockImplementation(() => {
28+
Environment.getInstance = vi.fn().mockImplementation(() => {
2729
return { fetch: mockEnvironmentFetch };
2830
});
2931

@@ -59,14 +61,14 @@ const developmentPublishableKey = 'pk_test_Y2xlcmsuYWJjZWYuMTIzNDUuZGV2LmxjbGNsZ
5961
const productionPublishableKey = 'pk_live_Y2xlcmsuYWJjZWYuMTIzNDUucHJvZC5sY2xjbGVyay5jb20k';
6062

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

6567
let mockWindowLocation;
66-
let mockHref: jest.Mock;
68+
let mockHref: vi.Mock;
6769

6870
beforeEach(() => {
69-
mockHref = jest.fn();
71+
mockHref = vi.fn();
7072
mockWindowLocation = {
7173
host: 'test.host',
7274
hostname: 'test.host',

packages/clerk-js/src/core/__tests__/fapiClient.test.ts renamed to packages/clerk-js/src/core/__tests__/fapiClient.spec.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { InstanceType } from '@clerk/types';
2+
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
23

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

28+
const originalFetch = global.fetch;
29+
2730
// @ts-ignore -- We don't need to fully satisfy the fetch types for the sake of this mock
28-
global.fetch = jest.fn(() =>
31+
global.fetch = vi.fn(() =>
2932
Promise.resolve<RecursivePartial<Response>>({
3033
headers: {
31-
get: jest.fn(() => 'sess_43'),
34+
get: vi.fn(() => 'sess_43'),
3235
},
3336
json: () => Promise.resolve({ foo: 42 }),
3437
}),
@@ -54,12 +57,13 @@ beforeAll(() => {
5457
});
5558

5659
beforeEach(() => {
57-
(global.fetch as jest.Mock).mockClear();
60+
(global.fetch as vi.Mock).mockClear();
5861
});
5962

6063
afterAll(() => {
6164
window.location = oldWindowLocation;
6265
delete window.Clerk;
66+
global.fetch = originalFetch;
6367
});
6468

6569
describe('buildUrl(options)', () => {
@@ -184,10 +188,10 @@ describe('request', () => {
184188
});
185189

186190
it('returns array response as array', async () => {
187-
(global.fetch as jest.Mock).mockResolvedValueOnce(
191+
(global.fetch as vi.Mock).mockResolvedValueOnce(
188192
Promise.resolve<RecursivePartial<Response>>({
189193
headers: {
190-
get: jest.fn(() => 'sess_43'),
194+
get: vi.fn(() => 'sess_43'),
191195
},
192196
json: () => Promise.resolve([{ foo: 42 }]),
193197
}),
@@ -201,7 +205,7 @@ describe('request', () => {
201205
});
202206

203207
it('handles the empty body on 204 response, returning null', async () => {
204-
(global.fetch as jest.Mock).mockResolvedValueOnce(
208+
(global.fetch as vi.Mock).mockResolvedValueOnce(
205209
Promise.resolve<RecursivePartial<Response>>({
206210
status: 204,
207211
json: () => {

packages/clerk-js/src/core/__tests__/tokenCache.test.ts renamed to packages/clerk-js/src/core/__tests__/tokenCache.spec.ts

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import type { TokenResource } from '@clerk/types';
2+
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';
23

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

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

1011
return {
@@ -17,11 +18,11 @@ const jwt =
1718

1819
describe('MemoryTokenCache', () => {
1920
beforeAll(() => {
20-
jest.useFakeTimers();
21+
vi.useFakeTimers();
2122
});
2223

2324
afterAll(() => {
24-
jest.useRealTimers();
25+
vi.useRealTimers();
2526
});
2627

2728
describe('clear()', () => {
@@ -87,7 +88,7 @@ describe('MemoryTokenCache', () => {
8788
expect(isResolved).toBe(false);
8889

8990
// Wait tokenResolver to resolve
90-
jest.advanceTimersByTime(100);
91+
vi.advanceTimersByTime(100);
9192
await tokenResolver;
9293

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

100101
// Advance the timer to force the JWT expiration
101-
jest.advanceTimersByTime(60 * 1000);
102+
vi.advanceTimersByTime(60 * 1000);
102103

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

127128
// 44s since token created
128-
jest.advanceTimersByTime(45 * 1000);
129+
vi.advanceTimersByTime(45 * 1000);
129130
expect(cache.get(key)).toMatchObject(key);
130131

131132
// 46s since token created
132-
jest.advanceTimersByTime(1 * 1000);
133+
vi.advanceTimersByTime(1 * 1000);
133134
expect(cache.get(key)).toBeUndefined();
134135
});
135136

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

152153
// 45s since token created
153-
jest.advanceTimersByTime(45 * 1000);
154+
vi.advanceTimersByTime(45 * 1000);
154155
expect(cache.get(key, 0)).toMatchObject(key);
155156

156157
// 54s since token created
157-
jest.advanceTimersByTime(9 * 1000);
158+
vi.advanceTimersByTime(9 * 1000);
158159
expect(cache.get(key, 0)).toMatchObject(key);
159160

160161
// 55s since token created
161-
jest.advanceTimersByTime(1 * 1000);
162+
vi.advanceTimersByTime(1 * 1000);
162163
expect(cache.get(key, 0)).toBeUndefined();
163164
});
164165
});

packages/clerk-js/src/core/auth/__tests__/cookieSuffix.test.ts renamed to packages/clerk-js/src/core/auth/__tests__/cookieSuffix.spec.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
jest.mock('@clerk/shared/keys', () => {
2-
return { getCookieSuffix: jest.fn() };
1+
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
2+
3+
vi.mock('@clerk/shared/keys', () => {
4+
return { getCookieSuffix: vi.fn() };
35
});
4-
jest.mock('@clerk/shared/logger', () => {
5-
return { logger: { logOnce: jest.fn() } };
6+
vi.mock('@clerk/shared/logger', () => {
7+
return { logger: { logOnce: vi.fn() } };
68
});
79
import { getCookieSuffix as getSharedCookieSuffix } from '@clerk/shared/keys';
810
import { logger } from '@clerk/shared/logger';
@@ -11,12 +13,12 @@ import { getCookieSuffix } from '../cookieSuffix';
1113

1214
describe('getCookieSuffix', () => {
1315
beforeEach(() => {
14-
(getSharedCookieSuffix as jest.Mock).mockRejectedValue(new Error('mocked error for insecure context'));
16+
(getSharedCookieSuffix as vi.Mock).mockRejectedValue(new Error('mocked error for insecure context'));
1517
});
1618

1719
afterEach(() => {
18-
(getSharedCookieSuffix as jest.Mock).mockReset();
19-
(logger.logOnce as jest.Mock).mockReset();
20+
(getSharedCookieSuffix as vi.Mock).mockReset();
21+
(logger.logOnce as vi.Mock).mockReset();
2022
});
2123

2224
describe('getCookieSuffix(publishableKey, subtle?)', () => {

packages/clerk-js/src/core/auth/__tests__/devBrowser.test.ts renamed to packages/clerk-js/src/core/auth/__tests__/devBrowser.spec.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2+
13
import type { FapiClient } from '../../fapiClient';
24
import { createDevBrowser } from '../devBrowser';
35

@@ -8,7 +10,7 @@ type RecursivePartial<T> = {
810
describe('Thrown errors', () => {
911
beforeEach(() => {
1012
// @ts-ignore
11-
global.fetch = jest.fn(() =>
13+
global.fetch = vi.fn(() =>
1214
Promise.resolve<RecursivePartial<Response>>({
1315
ok: false,
1416
json: () =>
@@ -29,17 +31,17 @@ describe('Thrown errors', () => {
2931

3032
afterEach(() => {
3133
// @ts-ignore
32-
global.fetch?.mockClear();
34+
vi.mocked(global.fetch)?.mockClear();
3335
});
3436

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

packages/clerk-js/src/core/auth/__tests__/getCookieDomain.test.ts renamed to packages/clerk-js/src/core/auth/__tests__/getCookieDomain.spec.ts

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { beforeEach, describe, expect, it, vi } from 'vitest';
2+
13
import type { getCookieDomain as _getCookieDomain } from '../getCookieDomain';
24

35
type CookieHandler = NonNullable<Parameters<typeof _getCookieDomain>[1]>;
@@ -6,7 +8,7 @@ describe('getCookieDomain', () => {
68
let getCookieDomain: typeof _getCookieDomain;
79
beforeEach(async () => {
810
// We're dynamically importing getCookieDomain here to reset the module-level cache
9-
jest.resetModules();
11+
vi.resetModules();
1012
getCookieDomain = await import('../getCookieDomain').then(m => m.getCookieDomain);
1113
});
1214

@@ -20,14 +22,14 @@ describe('getCookieDomain', () => {
2022
// assume that the Public Suffix List is correctly handled by the browser.
2123
const hostname = 'app.fr.hosting.co.uk';
2224
const handler: CookieHandler = {
23-
get: jest
25+
get: vi
2426
.fn()
2527
.mockReturnValueOnce(undefined)
2628
.mockReturnValueOnce(undefined)
2729
.mockReturnValueOnce(undefined)
2830
.mockReturnValueOnce('1'),
29-
set: jest.fn().mockReturnValue(undefined),
30-
remove: jest.fn().mockReturnValue(undefined),
31+
set: vi.fn().mockReturnValue(undefined),
32+
remove: vi.fn().mockReturnValue(undefined),
3133
};
3234
const result = getCookieDomain(hostname, handler);
3335
expect(result).toBe(hostname);
@@ -53,9 +55,9 @@ describe('getCookieDomain', () => {
5355

5456
it('returns undefined if the domain could not be determined', () => {
5557
const handler: CookieHandler = {
56-
get: jest.fn().mockReturnValue(undefined),
57-
set: jest.fn().mockReturnValue(undefined),
58-
remove: jest.fn().mockReturnValue(undefined),
58+
get: vi.fn().mockReturnValue(undefined),
59+
set: vi.fn().mockReturnValue(undefined),
60+
remove: vi.fn().mockReturnValue(undefined),
5961
};
6062
const hostname = 'app.hello.co.uk';
6163
const result = getCookieDomain(hostname, handler);
@@ -65,9 +67,9 @@ describe('getCookieDomain', () => {
6567
it('uses cached value if there is one', () => {
6668
const hostname = 'clerk.com';
6769
const handler: CookieHandler = {
68-
get: jest.fn().mockReturnValue('1'),
69-
set: jest.fn().mockReturnValue(undefined),
70-
remove: jest.fn().mockReturnValue(undefined),
70+
get: vi.fn().mockReturnValue('1'),
71+
set: vi.fn().mockReturnValue(undefined),
72+
remove: vi.fn().mockReturnValue(undefined),
7173
};
7274
expect(getCookieDomain(hostname, handler)).toBe(hostname);
7375
expect(getCookieDomain(hostname, handler)).toBe(hostname);

0 commit comments

Comments
 (0)