Skip to content

feat(clerk-js): vitest #5716

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all 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
10 changes: 6 additions & 4 deletions packages/clerk-js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,9 @@
"lint": "eslint src",
"lint:attw": "attw --pack . --profile node16 --ignore-rules named-exports",
"lint:publint": "publint || true",
"test": "jest",
"test:cache:clear": "jest --clearCache --useStderr",
"test:ci": "jest --maxWorkers=70%",
"test:coverage": "jest --collectCoverage && open coverage/lcov-report/index.html",
"test": "vitest run",
"test:ci": "vitest run --reporter=verbose --coverage",
"test:coverage": "vitest run --coverage",
"watch": "rspack build --config rspack.config.js --env production --watch"
},
"browserslist": "last 2 years",
Expand Down Expand Up @@ -77,12 +76,15 @@
"regenerator-runtime": "0.13.11"
},
"devDependencies": {
"@emotion/jest": "^11.13.0",
"@rsdoctor/rspack-plugin": "^0.4.13",
"@rspack/cli": "^1.2.8",
"@rspack/core": "^1.2.8",
"@rspack/plugin-react-refresh": "^1.0.1",
"@svgr/webpack": "^6.5.1",
"@types/webpack-env": "^1.18.8",
"jsdom": "^24.1.1",
"vite-plugin-svgr": "^4.2.0",
"webpack-merge": "^5.10.0"
},
"peerDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion packages/clerk-js/src/__tests__/headless.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* @jest-environment node
* @vitest-environment node
*/

describe('clerk/headless', () => {
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;
31 changes: 16 additions & 15 deletions packages/clerk-js/src/core/__tests__/clerk.redirects.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,29 @@ import type { DevBrowser } from '../auth/devBrowser';
import { Clerk } from '../clerk';
import type { DisplayConfig } from '../resources/internal';
import { Client, Environment } from '../resources/internal';
import { describe, it, expect, beforeEach, afterEach, afterAll, vi } from 'vitest';

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 +60,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
13 changes: 7 additions & 6 deletions packages/clerk-js/src/core/__tests__/fapiClient.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { InstanceType } from '@clerk/types';
import { describe, it, expect, beforeAll, beforeEach, afterAll, vi } from 'vitest';

import { SUPPORTED_FAPI_VERSION } from '../constants';
import { createFapiClient } from '../fapiClient';
Expand All @@ -25,10 +26,10 @@ type RecursivePartial<T> = {
};

// @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 }),
}),
Expand All @@ -54,7 +55,7 @@ beforeAll(() => {
});

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

afterAll(() => {
Expand Down Expand Up @@ -184,10 +185,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 +202,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
21 changes: 11 additions & 10 deletions packages/clerk-js/src/core/__tests__/tokenCache.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import type { TokenResource } from '@clerk/types';
import { describe, it, beforeAll, afterAll, expect, 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
16 changes: 9 additions & 7 deletions packages/clerk-js/src/core/auth/__tests__/cookieSuffix.test.ts
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
14 changes: 8 additions & 6 deletions packages/clerk-js/src/core/auth/__tests__/devBrowser.test.ts
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
22 changes: 12 additions & 10 deletions packages/clerk-js/src/core/auth/__tests__/getCookieDomain.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';

import type { getCookieDomain as _getCookieDomain } from '../getCookieDomain';

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

Expand All @@ -20,14 +22,14 @@ describe('getCookieDomain', () => {
// assume that the Public Suffix List is correctly handled by the browser.
const hostname = 'app.fr.hosting.co.uk';
const handler: CookieHandler = {
get: jest
get: vi
.fn()
.mockReturnValueOnce(undefined)
.mockReturnValueOnce(undefined)
.mockReturnValueOnce(undefined)
.mockReturnValueOnce('1'),
set: jest.fn().mockReturnValue(undefined),
remove: jest.fn().mockReturnValue(undefined),
set: vi.fn().mockReturnValue(undefined),
remove: vi.fn().mockReturnValue(undefined),
};
const result = getCookieDomain(hostname, handler);
expect(result).toBe(hostname);
Expand All @@ -53,9 +55,9 @@ describe('getCookieDomain', () => {

it('returns undefined if the domain could not be determined', () => {
const handler: CookieHandler = {
get: jest.fn().mockReturnValue(undefined),
set: jest.fn().mockReturnValue(undefined),
remove: jest.fn().mockReturnValue(undefined),
get: vi.fn().mockReturnValue(undefined),
set: vi.fn().mockReturnValue(undefined),
remove: vi.fn().mockReturnValue(undefined),
};
const hostname = 'app.hello.co.uk';
const result = getCookieDomain(hostname, handler);
Expand All @@ -65,9 +67,9 @@ describe('getCookieDomain', () => {
it('uses cached value if there is one', () => {
const hostname = 'clerk.com';
const handler: CookieHandler = {
get: jest.fn().mockReturnValue('1'),
set: jest.fn().mockReturnValue(undefined),
remove: jest.fn().mockReturnValue(undefined),
get: vi.fn().mockReturnValue('1'),
set: vi.fn().mockReturnValue(undefined),
remove: vi.fn().mockReturnValue(undefined),
};
expect(getCookieDomain(hostname, handler)).toBe(hostname);
expect(getCookieDomain(hostname, handler)).toBe(hostname);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';

import { getSecureAttribute } from '../getSecureAttribute';

describe('getSecureAttribute', () => {
let windowSpy: any;

beforeEach(() => {
windowSpy = jest.spyOn(window, 'window', 'get');
windowSpy = vi.spyOn(window, 'window', 'get');
});

afterEach(() => {
Expand Down
Loading
Loading