diff --git a/.changeset/two-dancers-wait.md b/.changeset/modern-stars-knock.md similarity index 100% rename from .changeset/two-dancers-wait.md rename to .changeset/modern-stars-knock.md diff --git a/eslint.config.mjs b/eslint.config.mjs index ea461759061..40c097bde6d 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -335,7 +335,6 @@ export default tseslint.config([ jest: pluginJest, }, rules: { - '@typescript-eslint/no-non-null-assertion': 'off', '@typescript-eslint/unbound-method': 'off', 'jest/unbound-method': 'error', }, @@ -366,7 +365,7 @@ export default tseslint.config([ }, { name: 'packages/clerk-js - vitest', - files: ['packages/clerk-js/src/**/*.test.{ts,tsx}'], + files: ['packages/clerk-js/src/**/*.spec.{ts,tsx}'], rules: { 'jest/unbound-method': 'off', '@typescript-eslint/unbound-method': 'off', diff --git a/packages/clerk-js/jest.config.js b/packages/clerk-js/jest.config.js new file mode 100644 index 00000000000..b8211320a49 --- /dev/null +++ b/packages/clerk-js/jest.config.js @@ -0,0 +1,62 @@ +const { name } = require('./package.json'); + +/** @type {import('ts-jest').JestConfigWithTsJest} */ +const config = { + displayName: name.replace('@clerk', ''), + injectGlobals: true, + globals: { + __PKG_NAME__: '@clerk/clerk-js', + __PKG_VERSION__: 'test', + __BUILD_VARIANT_CHIPS__: false, + __BUILD_DISABLE_RHC__: false, + }, + + testEnvironment: '/jest.jsdom-with-timezone.ts', + roots: ['/src'], + setupFiles: ['./jest.setup.ts'], + setupFilesAfterEnv: ['./jest.setup-after-env.ts'], + testRegex: [ + '/__tests__/(.+/)*.*.test.[jt]sx?$', + '/ui/.*/__tests__/.*.test.[jt]sx?$', + '/(core|utils)/.*.test.[jt]sx?$', + ], + testPathIgnorePatterns: ['/node_modules/'], + collectCoverage: false, + coverageProvider: 'v8', + coverageDirectory: 'coverage', + coveragePathIgnorePatterns: ['/node_modules/'], + // collectCoverageFrom: [ + // '**/*.{js,jsx,ts,tsx}', + // '!**/*.d.ts', + // '!**/index.ts', + // '!**/index.browser.ts', + // '!**/index.headless.ts', + // '!**/index.headless.browser.ts', + // '!**/coverage/**', + // '!**/dist/**', + // '!**/node_modules/**', + // ], + transformIgnorePatterns: ['[/\\\\]node_modules[/\\\\](?!(@formkit/auto-animate/react)).+\\.(js|jsx|mjs|cjs|ts|tsx)$'], + moduleDirectories: ['node_modules', '/src'], + moduleNameMapper: { + '@/(.*)': '/src/$1', + }, + transform: { + '^.+\\.m?tsx?$': [ + '@swc/jest', + { + jsc: { + transform: { + react: { + runtime: 'automatic', + importSource: '@emotion/react', + }, + }, + }, + }, + ], + '^.+\\.svg$': '/svgTransform.js', + }, +}; + +module.exports = config; diff --git a/packages/clerk-js/jest.jsdom-with-timezone.ts b/packages/clerk-js/jest.jsdom-with-timezone.ts new file mode 100644 index 00000000000..7a6af037b98 --- /dev/null +++ b/packages/clerk-js/jest.jsdom-with-timezone.ts @@ -0,0 +1,21 @@ +import JSDOMEnvironment from 'jest-environment-jsdom'; + +/** + * Timezone-aware jsdom Jest environment. Supports `@timezone` JSDoc + * pragma within test suites to set timezone. + * + * You'd make another copy of this extending the Node environment, + * if needed for Node server environment-based tests. + */ +module.exports = class TimezoneAwareJSDOMEnvironment extends JSDOMEnvironment { + // @ts-ignore + constructor(config, context) { + // Allow test suites to change timezone, even if TZ is passed in a script. + // Falls back to existing TZ environment variable or UTC if no timezone is specified. + // IMPORTANT: This must happen before super(config) is called, otherwise + // it doesn't work. + process.env.TZ = context.docblockPragmas.timezone || process.env.TZ || 'UTC'; + + super(config, context); + } +}; diff --git a/packages/clerk-js/jest.setup-after-env.ts b/packages/clerk-js/jest.setup-after-env.ts new file mode 100644 index 00000000000..7b0828bfa80 --- /dev/null +++ b/packages/clerk-js/jest.setup-after-env.ts @@ -0,0 +1 @@ +import '@testing-library/jest-dom'; diff --git a/packages/clerk-js/jest.setup.ts b/packages/clerk-js/jest.setup.ts new file mode 100644 index 00000000000..800452ccd1b --- /dev/null +++ b/packages/clerk-js/jest.setup.ts @@ -0,0 +1,76 @@ +import crypto from 'node:crypto'; +import { TextDecoder, TextEncoder } from 'node:util'; + +import { jest } from '@jest/globals'; + +class FakeResponse {} + +if (typeof window !== 'undefined') { + Object.defineProperties(globalThis, { + TextDecoder: { value: TextDecoder }, + TextEncoder: { value: TextEncoder }, + Response: { value: FakeResponse }, + crypto: { value: crypto.webcrypto }, + }); + + window.ResizeObserver = + window.ResizeObserver || + jest.fn().mockImplementation(() => ({ + disconnect: jest.fn(), + observe: jest.fn(), + unobserve: jest.fn(), + })); + + Object.defineProperty(window, 'matchMedia', { + writable: true, + value: jest.fn().mockImplementation(query => ({ + matches: false, + media: query, + onchange: null, + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + dispatchEvent: jest.fn(), + })), + }); + + //@ts-expect-error - JSDOM doesn't provide IntersectionObserver, so we mock it for testing + global.IntersectionObserver = class IntersectionObserver { + constructor() {} + + disconnect() { + return null; + } + + observe() { + return null; + } + + takeRecords() { + return null; + } + + unobserve() { + return null; + } + }; + + // Mock HTMLCanvasElement.prototype.getContext to prevent errors + HTMLCanvasElement.prototype.getContext = jest.fn().mockImplementation(((contextType: string) => { + if (contextType === '2d') { + return { + fillRect: jest.fn(), + getImageData: jest.fn(() => ({ data: new Uint8ClampedArray([255, 255, 255, 255]) }) as unknown as ImageData), + } as unknown as CanvasRenderingContext2D; + } + if (contextType === 'webgl' || contextType === 'webgl2') { + return {} as unknown as WebGLRenderingContext; + } + return null; + }) as any) as jest.MockedFunction; + + // Mock document.elementFromPoint for input-otp library + Object.defineProperty(document, 'elementFromPoint', { + value: jest.fn().mockReturnValue(null), + writable: true, + }); +} diff --git a/packages/clerk-js/package.json b/packages/clerk-js/package.json index 18595647597..d7c427cf197 100644 --- a/packages/clerk-js/package.json +++ b/packages/clerk-js/package.json @@ -51,10 +51,15 @@ "lint": "eslint src", "lint:attw": "attw --pack . --profile node16 --ignore-rules named-exports", "lint:publint": "publint || true", - "test": "vitest --watch=false", + "test": "jest && vitest --watch=false", + "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:sandbox:integration": "playwright test", "test:sandbox:integration:ui": "playwright test --ui", "test:sandbox:integration:update-snapshots": "playwright test --update-snapshots", + "test:vitest": "vitest", "watch": "rspack build --config rspack.config.js --env production --watch" }, "browserslist": "last 2 years", @@ -92,6 +97,7 @@ "@rspack/core": "^1.4.11", "@rspack/plugin-react-refresh": "^1.5.0", "@svgr/webpack": "^6.5.1", + "@swc/jest": "0.2.39", "@types/cloudflare-turnstile": "^0.2.2", "@types/node": "^22.18.1", "@types/webpack-env": "^1.18.8", diff --git a/packages/clerk-js/src/__tests__/headless.test.ts b/packages/clerk-js/src/__tests__/headless.spec.ts similarity index 100% rename from packages/clerk-js/src/__tests__/headless.test.ts rename to packages/clerk-js/src/__tests__/headless.spec.ts diff --git a/packages/clerk-js/src/core/__tests__/clerk.redirects.test.ts b/packages/clerk-js/src/core/__tests__/clerk.redirects.spec.ts similarity index 99% rename from packages/clerk-js/src/core/__tests__/clerk.redirects.test.ts rename to packages/clerk-js/src/core/__tests__/clerk.redirects.spec.ts index 78d6553e262..a7d45c04331 100644 --- a/packages/clerk-js/src/core/__tests__/clerk.redirects.test.ts +++ b/packages/clerk-js/src/core/__tests__/clerk.redirects.spec.ts @@ -11,6 +11,7 @@ const mockEnvironmentFetch = vi.fn(); vi.mock('../resources/Client'); vi.mock('../resources/Environment'); +// Because Jest, don't ask me why... vi.mock('../auth/devBrowser', () => ({ createDevBrowser: (): DevBrowser => ({ clear: vi.fn(), diff --git a/packages/clerk-js/src/core/__tests__/clerk.test.ts b/packages/clerk-js/src/core/__tests__/clerk.test.ts index 18c1f234251..0142da8ad08 100644 --- a/packages/clerk-js/src/core/__tests__/clerk.test.ts +++ b/packages/clerk-js/src/core/__tests__/clerk.test.ts @@ -7,9 +7,8 @@ import type { TokenResource, } from '@clerk/types'; import { waitFor } from '@testing-library/dom'; -import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, test, vi } from 'vitest'; -import { mockNativeRuntime } from '../../vitestUtils'; +import { mockNativeRuntime } from '../../testUtils'; import type { DevBrowser } from '../auth/devBrowser'; import { Clerk } from '../clerk'; import { eventBus, events } from '../events'; @@ -17,26 +16,27 @@ import type { DisplayConfig, Organization } from '../resources/internal'; import { BaseResource, Client, EmailLinkErrorCodeStatus, Environment, SignIn, SignUp } from '../resources/internal'; import { mockJwt } from '../test/fixtures'; -const mockClientFetch = vi.fn(); -const mockEnvironmentFetch = vi.fn(() => Promise.resolve({})); +const mockClientFetch = jest.fn(); +const mockEnvironmentFetch = jest.fn(() => Promise.resolve({})); -vi.mock('../resources/Client'); -vi.mock('../resources/Environment'); +jest.mock('../resources/Client'); +jest.mock('../resources/Environment'); -vi.mock('../auth/devBrowser', () => ({ +// Because Jest, don't ask me why... +jest.mock('../auth/devBrowser', () => ({ createDevBrowser: (): DevBrowser => ({ - clear: vi.fn(), - setup: vi.fn(), - getDevBrowserJWT: vi.fn(() => 'deadbeef'), - setDevBrowserJWT: vi.fn(), - removeDevBrowserJWT: vi.fn(), + clear: jest.fn(), + setup: jest.fn(), + getDevBrowserJWT: jest.fn(() => 'deadbeef'), + setDevBrowserJWT: jest.fn(), + removeDevBrowserJWT: jest.fn(), }), })); -Client.getOrCreateInstance = vi.fn().mockImplementation(() => { +Client.getOrCreateInstance = jest.fn().mockImplementation(() => { return { fetch: mockClientFetch }; }); -Environment.getInstance = vi.fn().mockImplementation(() => { +Environment.getInstance = jest.fn().mockImplementation(() => { return { fetch: mockEnvironmentFetch }; }); @@ -54,7 +54,7 @@ describe('Clerk singleton', () => { const developmentPublishableKey = 'pk_test_Y2xlcmsuYWJjZWYuMTIzNDUuZGV2LmxjbGNsZXJrLmNvbSQ'; const productionPublishableKey = 'pk_live_Y2xlcmsuYWJjZWYuMTIzNDUucHJvZC5sY2xjbGVyay5jb20k'; - const mockNavigate = vi.fn((to: string) => Promise.resolve(to)); + const mockNavigate = jest.fn((to: string) => Promise.resolve(to)); const mockedLoadOptions = { routerDebug: true, routerPush: mockNavigate, routerReplace: mockNavigate }; const mockDisplayConfig = { @@ -73,7 +73,7 @@ describe('Clerk singleton', () => { }; let mockWindowLocation; - let mockHref: ReturnType; + let mockHref: jest.Mock; afterAll(() => { Object.defineProperty(global.window, 'location', { @@ -82,7 +82,7 @@ describe('Clerk singleton', () => { }); beforeEach(() => { - mockHref = vi.fn(); + mockHref = jest.fn(); mockWindowLocation = { host: 'test.host', hostname: 'test.host', @@ -159,17 +159,17 @@ describe('Clerk singleton', () => { describe('with `active` session status', () => { const mockSession = { id: '1', - remove: vi.fn(), + remove: jest.fn(), status: 'active', user: {}, - touch: vi.fn(() => Promise.resolve()), - getToken: vi.fn(), + touch: jest.fn(() => Promise.resolve()), + getToken: jest.fn(), lastActiveToken: { getRawString: () => 'mocked-token' }, }; - let eventBusSpy: ReturnType; + let eventBusSpy: jest.SpyInstance; beforeEach(() => { - eventBusSpy = vi.spyOn(eventBus, 'emit'); + eventBusSpy = jest.spyOn(eventBus, 'emit'); }); afterEach(() => { @@ -245,7 +245,7 @@ describe('Clerk singleton', () => { }); it('calls __unstable__onAfterSetActive after beforeEmit and session.touch', async () => { - const beforeEmitMock = vi.fn(); + const beforeEmitMock = jest.fn(); mockSession.touch.mockReturnValueOnce(Promise.resolve()); mockClientFetch.mockReturnValue(Promise.resolve({ signedInSessions: [mockSession] })); @@ -263,11 +263,11 @@ describe('Clerk singleton', () => { it('calls session.touch -> set cookie -> before emit with touched session on session switch', async () => { const mockSession2 = { id: '2', - remove: vi.fn(), + remove: jest.fn(), status: 'active', user: {}, - touch: vi.fn(), - getToken: vi.fn(), + touch: jest.fn(), + getToken: jest.fn(), }; mockClientFetch.mockReturnValue( Promise.resolve({ @@ -288,7 +288,7 @@ describe('Clerk singleton', () => { executionOrder.push('set cookie'); return 'mocked-token-2'; }); - const beforeEmitMock = vi.fn().mockImplementationOnce(() => { + const beforeEmitMock = jest.fn().mockImplementationOnce(() => { executionOrder.push('before emit'); return Promise.resolve(); }); @@ -321,7 +321,7 @@ describe('Clerk singleton', () => { return 'mocked-token'; }); - const beforeEmitMock = vi.fn().mockImplementationOnce(() => { + const beforeEmitMock = jest.fn().mockImplementationOnce(() => { executionOrder.push('before emit'); return Promise.resolve(); }); @@ -353,8 +353,8 @@ describe('Clerk singleton', () => { }, ], }, - touch: vi.fn(), - getToken: vi.fn(), + touch: jest.fn(), + getToken: jest.fn(), }; mockClientFetch.mockReturnValue(Promise.resolve({ signedInSessions: [mockSession2] })); const sut = new Clerk(productionPublishableKey); @@ -389,13 +389,13 @@ describe('Clerk singleton', () => { ); const sut = new Clerk(productionPublishableKey); - sut.navigate = vi.fn(); + sut.navigate = jest.fn(); await sut.load(); await sut.setActive({ session: mockSession as any as ActiveSessionResource, redirectUrl: '/redirect-url-path', }); - const redirectUrl = new URL((sut.navigate as ReturnType).mock.calls[0]); + const redirectUrl = new URL((sut.navigate as jest.Mock).mock.calls[0]); expect(redirectUrl.pathname).toEqual('/v1/client/touch'); expect(redirectUrl.searchParams.get('redirect_url')).toEqual(`${mockWindowLocation.href}/redirect-url-path`); }); @@ -413,7 +413,7 @@ describe('Clerk singleton', () => { ); const sut = new Clerk(productionPublishableKey); - sut.navigate = vi.fn(); + sut.navigate = jest.fn(); await sut.load(); await sut.setActive({ session: mockSession as any as ActiveSessionResource, @@ -435,7 +435,7 @@ describe('Clerk singleton', () => { ); const sut = new Clerk(productionPublishableKey); - sut.navigate = vi.fn(); + sut.navigate = jest.fn(); await sut.load(); await sut.setActive({ session: mockSession as any as ActiveSessionResource, @@ -447,7 +447,7 @@ describe('Clerk singleton', () => { it('calls `navigate`', async () => { mockSession.touch.mockReturnValue(Promise.resolve()); mockClientFetch.mockReturnValue(Promise.resolve({ signedInSessions: [mockSession] })); - const navigate = vi.fn(); + const navigate = jest.fn(); const sut = new Clerk(productionPublishableKey); await sut.load(); @@ -469,7 +469,7 @@ describe('Clerk singleton', () => { executionOrder.push('session.touch'); return Promise.resolve(); }); - const beforeEmitMock = vi.fn().mockImplementationOnce(() => { + const beforeEmitMock = jest.fn().mockImplementationOnce(() => { executionOrder.push('before emit'); return Promise.resolve(); }); @@ -489,15 +489,15 @@ describe('Clerk singleton', () => { describe('with `pending` session status', () => { const mockSession = { id: '1', - remove: vi.fn(), + remove: jest.fn(), status: 'pending', user: {}, - touch: vi.fn(() => Promise.resolve()), - getToken: vi.fn(), + touch: jest.fn(() => Promise.resolve()), + getToken: jest.fn(), lastActiveToken: { getRawString: () => 'mocked-token' }, tasks: [{ key: 'choose-organization' }], currentTask: { key: 'choose-organization' }, - reload: vi.fn(() => + reload: jest.fn(() => Promise.resolve({ id: '1', status: 'pending', @@ -512,7 +512,7 @@ describe('Clerk singleton', () => { let eventBusSpy; beforeEach(() => { - eventBusSpy = vi.spyOn(eventBus, 'emit'); + eventBusSpy = jest.spyOn(eventBus, 'emit'); }); afterEach(() => { @@ -539,7 +539,7 @@ describe('Clerk singleton', () => { mockSession.touch.mockReturnValueOnce(Promise.resolve()); mockClientFetch.mockReturnValue(Promise.resolve({ signedInSessions: [mockSession] })); - const onBeforeSetActive = vi.fn(); + const onBeforeSetActive = jest.fn(); (window as any).__unstable__onBeforeSetActive = onBeforeSetActive; const sut = new Clerk(productionPublishableKey); @@ -552,7 +552,7 @@ describe('Clerk singleton', () => { mockSession.touch.mockReturnValueOnce(Promise.resolve()); mockClientFetch.mockReturnValue(Promise.resolve({ signedInSessions: [mockSession] })); - const onAfterSetActive = vi.fn(); + const onAfterSetActive = jest.fn(); (window as any).__unstable__onAfterSetActive = onAfterSetActive; const sut = new Clerk(productionPublishableKey); @@ -566,7 +566,7 @@ describe('Clerk singleton', () => { mockClientFetch.mockReturnValue(Promise.resolve({ signedInSessions: [mockSession] })); const sut = new Clerk(productionPublishableKey); - sut.navigate = vi.fn(); + sut.navigate = jest.fn(); await sut.load({ taskUrls: { 'choose-organization': '/choose-organization', @@ -580,7 +580,7 @@ describe('Clerk singleton', () => { it('calls `navigate`', async () => { mockSession.touch.mockReturnValue(Promise.resolve()); mockClientFetch.mockReturnValue(Promise.resolve({ signedInSessions: [mockSession] })); - const navigate = vi.fn(); + const navigate = jest.fn(); const sut = new Clerk(productionPublishableKey); await sut.load(); @@ -593,11 +593,11 @@ describe('Clerk singleton', () => { describe('with force organization selection enabled', () => { const mockSession = { id: '1', - remove: vi.fn(), + remove: jest.fn(), status: 'active', user: {}, - touch: vi.fn(() => Promise.resolve()), - getToken: vi.fn(), + touch: jest.fn(() => Promise.resolve()), + getToken: jest.fn(), lastActiveToken: { getRawString: () => 'mocked-token' }, }; @@ -641,8 +641,8 @@ describe('Clerk singleton', () => { }, ], }, - touch: vi.fn(), - getToken: vi.fn(), + touch: jest.fn(), + getToken: jest.fn(), }; mockClientFetch.mockReturnValue(Promise.resolve({ signedInSessions: [mockSessionWithOrganization] })); @@ -680,7 +680,7 @@ describe('Clerk singleton', () => { id: '1', status, user: {}, - getToken: vi.fn(), + getToken: jest.fn(), lastActiveToken: { getRawString: () => mockJwt }, }; @@ -698,7 +698,7 @@ describe('Clerk singleton', () => { ); // any is intentional here. We simulate a runtime value that should not exist - const mockSelectInitialSession = vi.fn(() => undefined) as any; + const mockSelectInitialSession = jest.fn(() => undefined) as any; const sut = new Clerk(productionPublishableKey); await sut.load({ selectInitialSession: mockSelectInitialSession, @@ -738,11 +738,11 @@ describe('Clerk singleton', () => { }); describe('.signOut()', () => { - const mockClientDestroy = vi.fn(); - const mockClientRemoveSessions = vi.fn(); - const mockSession1 = { id: '1', remove: vi.fn(), status: 'active', user: {}, getToken: vi.fn() }; - const mockSession2 = { id: '2', remove: vi.fn(), status: 'active', user: {}, getToken: vi.fn() }; - const mockSession3 = { id: '2', remove: vi.fn(), status: 'pending', user: {}, getToken: vi.fn() }; + const mockClientDestroy = jest.fn(); + const mockClientRemoveSessions = jest.fn(); + const mockSession1 = { id: '1', remove: jest.fn(), status: 'active', user: {}, getToken: jest.fn() }; + const mockSession2 = { id: '2', remove: jest.fn(), status: 'active', user: {}, getToken: jest.fn() }; + const mockSession3 = { id: '2', remove: jest.fn(), status: 'pending', user: {}, getToken: jest.fn() }; beforeEach(() => { mockClientDestroy.mockReset(); @@ -780,7 +780,7 @@ describe('Clerk singleton', () => { ); const sut = new Clerk(productionPublishableKey); - sut.navigate = vi.fn(); + sut.navigate = jest.fn(); await sut.load(); await sut.signOut(); await waitFor(() => { @@ -803,7 +803,7 @@ describe('Clerk singleton', () => { ); const sut = new Clerk(productionPublishableKey); - sut.navigate = vi.fn(); + sut.navigate = jest.fn(); await sut.load(); await sut.signOut(); await waitFor(() => { @@ -825,7 +825,7 @@ describe('Clerk singleton', () => { ); const sut = new Clerk(productionPublishableKey); - sut.navigate = vi.fn(); + sut.navigate = jest.fn(); await sut.load(); await sut.signOut({ sessionId: '2' }); await waitFor(() => { @@ -845,7 +845,7 @@ describe('Clerk singleton', () => { ); const sut = new Clerk(productionPublishableKey); - sut.navigate = vi.fn(); + sut.navigate = jest.fn(); await sut.load(); await sut.signOut({ sessionId: '1' }); await waitFor(() => { @@ -865,7 +865,7 @@ describe('Clerk singleton', () => { ); const sut = new Clerk(productionPublishableKey); - sut.navigate = vi.fn(); + sut.navigate = jest.fn(); await sut.load(); await sut.signOut({ sessionId: '1', redirectUrl: '/after-sign-out' }); await waitFor(() => { @@ -881,7 +881,7 @@ describe('Clerk singleton', () => { let logSpy; beforeEach(() => { - logSpy = vi.spyOn(console, 'log').mockReturnValue(void 0); + logSpy = jest.spyOn(console, 'log').mockReturnValue(void 0); sut = new Clerk(productionPublishableKey); }); @@ -960,7 +960,7 @@ describe('Clerk singleton', () => { // need a return value though, so we just mock a resolved promise. const originalFetch = global.fetch; beforeAll(() => { - global.fetch = vi.fn().mockResolvedValue({ json: vi.fn().mockResolvedValue({}) }); + global.fetch = jest.fn().mockResolvedValue({ json: jest.fn().mockResolvedValue({}) }); }); afterAll(() => { @@ -1001,10 +1001,10 @@ describe('Clerk singleton', () => { const mockResource = { ...mockSession, - remove: vi.fn(), - touch: vi.fn(() => Promise.resolve()), - getToken: vi.fn(), - reload: vi.fn(() => Promise.resolve(mockSession)), + remove: jest.fn(), + touch: jest.fn(() => Promise.resolve()), + getToken: jest.fn(), + reload: jest.fn(() => Promise.resolve(mockSession)), }; mockResource.touch.mockReturnValueOnce(Promise.resolve()); @@ -1019,7 +1019,7 @@ describe('Clerk singleton', () => { }), ); - const mockSignUpCreate = vi + const mockSignUpCreate = jest .fn() .mockReturnValue(Promise.resolve({ status: 'complete', createdSessionId: '123' })); @@ -1076,8 +1076,8 @@ describe('Clerk singleton', () => { }), ); - const mockSetActive = vi.fn(); - const mockSignUpCreate = vi + const mockSetActive = jest.fn(); + const mockSignUpCreate = jest .fn() .mockReturnValue(Promise.resolve({ status: 'complete', createdSessionId: '123' })); @@ -1135,8 +1135,8 @@ describe('Clerk singleton', () => { }), ); - const mockSetActive = vi.fn(); - const mockSignUpCreate = vi + const mockSetActive = jest.fn(); + const mockSignUpCreate = jest .fn() .mockReturnValue(Promise.resolve({ status: 'complete', createdSessionId: '123' })); @@ -1197,7 +1197,7 @@ describe('Clerk singleton', () => { }), ); - const mockSignUpCreate = vi.fn().mockReturnValue( + const mockSignUpCreate = jest.fn().mockReturnValue( Promise.resolve( new SignUp({ status: 'missing_requirements', @@ -1267,8 +1267,8 @@ describe('Clerk singleton', () => { }), ); - const mockSetActive = vi.fn(); - const mockSignInCreate = vi + const mockSetActive = jest.fn(); + const mockSignInCreate = jest .fn() .mockReturnValue(Promise.resolve({ status: 'complete', createdSessionId: '123' })); @@ -1327,7 +1327,7 @@ describe('Clerk singleton', () => { }), ); - const mockSetActive = vi.fn(); + const mockSetActive = jest.fn(); const sut = new Clerk(productionPublishableKey); await sut.load(mockedLoadOptions); sut.setActive = mockSetActive; @@ -1373,8 +1373,8 @@ describe('Clerk singleton', () => { }), ); - const mockSetActive = vi.fn(); - const mockSignUpCreate = vi + const mockSetActive = jest.fn(); + const mockSignUpCreate = jest .fn() .mockReturnValue(Promise.resolve({ status: 'complete', createdSessionId: '123' })); @@ -1468,7 +1468,7 @@ describe('Clerk singleton', () => { const sut = new Clerk(productionPublishableKey); await sut.load(mockedLoadOptions); - await sut.handleRedirectCallback({ + sut.handleRedirectCallback({ secondFactorUrl: '/custom-2fa', }); @@ -1481,11 +1481,11 @@ describe('Clerk singleton', () => { const sessionId = 'sess_1yDceUR8SIKtQ0gIOO8fNsW7nhe'; const mockSession = { id: sessionId, - remove: vi.fn(), + remove: jest.fn(), status: 'active', user: {}, - touch: vi.fn(() => Promise.resolve()), - getToken: vi.fn(), + touch: jest.fn(() => Promise.resolve()), + getToken: jest.fn(), lastActiveToken: { getRawString: () => 'mocked-token' }, }; mockEnvironmentFetch.mockReturnValue( @@ -1542,11 +1542,11 @@ describe('Clerk singleton', () => { const sessionId = 'sess_1yDceUR8SIKtQ0gIOO8fNsW7nhe'; const mockSession = { id: sessionId, - remove: vi.fn(), + remove: jest.fn(), status: 'active', user: {}, - touch: vi.fn(() => Promise.resolve()), - getToken: vi.fn(), + touch: jest.fn(() => Promise.resolve()), + getToken: jest.fn(), lastActiveToken: { getRawString: () => 'mocked-token' }, }; mockEnvironmentFetch.mockReturnValue( @@ -1850,7 +1850,7 @@ describe('Clerk singleton', () => { }), ); - const mockSignInCreate = vi.fn().mockReturnValue(Promise.resolve({ status: 'needs_first_factor' })); + const mockSignInCreate = jest.fn().mockReturnValue(Promise.resolve({ status: 'needs_first_factor' })); const sut = new Clerk(productionPublishableKey); await sut.load(mockedLoadOptions); @@ -1902,7 +1902,7 @@ describe('Clerk singleton', () => { }), ); - const mockSignUpCreate = vi.fn().mockReturnValue( + const mockSignUpCreate = jest.fn().mockReturnValue( Promise.resolve( new SignUp({ status: 'missing_requirements', @@ -1960,7 +1960,7 @@ describe('Clerk singleton', () => { }), ); - const mockSignInCreate = vi.fn().mockReturnValue(Promise.resolve({ status: 'needs_first_factor' })); + const mockSignInCreate = jest.fn().mockReturnValue(Promise.resolve({ status: 'needs_first_factor' })); const sut = new Clerk(productionPublishableKey); await sut.load(mockedLoadOptions); @@ -1998,7 +1998,7 @@ describe('Clerk singleton', () => { }), ); - const mockSignInCreate = vi.fn().mockReturnValue(Promise.resolve({ status: 'needs_new_password' })); + const mockSignInCreate = jest.fn().mockReturnValue(Promise.resolve({ status: 'needs_new_password' })); const sut = new Clerk(productionPublishableKey); await sut.load(mockedLoadOptions); @@ -2046,14 +2046,14 @@ describe('Clerk singleton', () => { signUp: new SignUp(null), }), ); - const mockSetActive = vi.fn(); + const mockSetActive = jest.fn(); const sut = new Clerk(productionPublishableKey); await sut.load(mockedLoadOptions); sut.setActive = mockSetActive; const redirectUrlComplete = '/redirect-to'; - await sut.handleEmailLinkVerification({ redirectUrlComplete }); + sut.handleEmailLinkVerification({ redirectUrlComplete }); await waitFor(() => { expect(mockSetActive).toHaveBeenCalledWith({ @@ -2075,7 +2075,7 @@ describe('Clerk singleton', () => { signUp: new SignUp(null), }), ); - const mockSetActive = vi.fn(); + const mockSetActive = jest.fn(); const sut = new Clerk(productionPublishableKey); await sut.load(mockedLoadOptions); @@ -2106,14 +2106,14 @@ describe('Clerk singleton', () => { signIn: new SignIn(null), }), ); - const mockSetActive = vi.fn(); + const mockSetActive = jest.fn(); const sut = new Clerk(productionPublishableKey); await sut.load(mockedLoadOptions); sut.setActive = mockSetActive; const redirectUrlComplete = '/redirect-to'; - await sut.handleEmailLinkVerification({ redirectUrlComplete }); + sut.handleEmailLinkVerification({ redirectUrlComplete }); await waitFor(() => { expect(mockSetActive).toHaveBeenCalledWith({ @@ -2135,7 +2135,7 @@ describe('Clerk singleton', () => { signIn: new SignIn(null), }), ); - const mockSetActive = vi.fn(); + const mockSetActive = jest.fn(); const sut = new Clerk(productionPublishableKey); await sut.load(mockedLoadOptions); @@ -2160,7 +2160,7 @@ describe('Clerk singleton', () => { signIn: new SignIn(null), }), ); - const mockSetActive = vi.fn(); + const mockSetActive = jest.fn(); const sut = new Clerk(productionPublishableKey); await sut.load(mockedLoadOptions); @@ -2182,7 +2182,7 @@ describe('Clerk singleton', () => { signIn: new SignIn(null), }), ); - const mockSetActive = vi.fn(); + const mockSetActive = jest.fn(); const sut = new Clerk(productionPublishableKey); await sut.load(mockedLoadOptions); @@ -2207,7 +2207,7 @@ describe('Clerk singleton', () => { signIn: new SignIn(null), }), ); - const mockSetActive = vi.fn(); + const mockSetActive = jest.fn(); const sut = new Clerk(productionPublishableKey); await sut.load(mockedLoadOptions); sut.setActive = mockSetActive; @@ -2230,7 +2230,7 @@ describe('Clerk singleton', () => { signIn: new SignIn(null), }), ); - const mockSetActive = vi.fn(); + const mockSetActive = jest.fn(); const sut = new Clerk(productionPublishableKey); await sut.load(mockedLoadOptions); sut.setActive = mockSetActive; @@ -2255,7 +2255,7 @@ describe('Clerk singleton', () => { signUp: new SignUp(null), }), ); - const mockSetActive = vi.fn(); + const mockSetActive = jest.fn(); const sut = new Clerk(productionPublishableKey); await sut.load(mockedLoadOptions); sut.setActive = mockSetActive; @@ -2436,7 +2436,7 @@ describe('Clerk singleton', () => { describe('Organizations', () => { it('getOrganization', async () => { // @ts-expect-error - Mocking a protected method - BaseResource._fetch = vi.fn().mockResolvedValue({}); + BaseResource._fetch = jest.fn().mockResolvedValue({}); const sut = new Clerk(developmentPublishableKey); await sut.getOrganization('org_id'); @@ -2457,8 +2457,8 @@ describe('Clerk singleton', () => { }); it('runs server revalidation hooks when session transitions from `active` to `pending`', async () => { - const mockOnBeforeSetActive = vi.fn().mockReturnValue(Promise.resolve()); - const mockOnAfterSetActive = vi.fn().mockReturnValue(Promise.resolve()); + const mockOnBeforeSetActive = jest.fn().mockReturnValue(Promise.resolve()); + const mockOnAfterSetActive = jest.fn().mockReturnValue(Promise.resolve()); (window as any).__unstable__onBeforeSetActive = mockOnBeforeSetActive; (window as any).__unstable__onAfterSetActive = mockOnAfterSetActive; diff --git a/packages/clerk-js/src/core/__tests__/fapiClient.test.ts b/packages/clerk-js/src/core/__tests__/fapiClient.spec.ts similarity index 96% rename from packages/clerk-js/src/core/__tests__/fapiClient.test.ts rename to packages/clerk-js/src/core/__tests__/fapiClient.spec.ts index a2d3d031e27..8d2541aa830 100644 --- a/packages/clerk-js/src/core/__tests__/fapiClient.test.ts +++ b/packages/clerk-js/src/core/__tests__/fapiClient.spec.ts @@ -60,7 +60,7 @@ beforeAll(() => { value: 'http://test.host', }, }, - ) as any; + ) as Location; }); beforeEach(() => { @@ -68,7 +68,7 @@ beforeEach(() => { }); afterAll(() => { - window.location = oldWindowLocation as any; + window.location = oldWindowLocation; delete window.Clerk; global.fetch = originalFetch; }); @@ -144,25 +144,26 @@ describe('buildUrl(options)', () => { path: '/foo', search: { array: ['item1', 'item2'], - } as any, + }, }).href, ).toBe( `https://clerk.example.com/v1/foo?array=item1&array=item2&__clerk_api_version=${SUPPORTED_FAPI_VERSION}&_clerk_js_version=test`, ); }); - it('parses search params when value is undefined', () => { + // The return value isn't as expected. + // The buildUrl function converts an undefined value to the string 'undefined' + // and includes it in the search parameters. + it.skip('parses search params when value is undefined', () => { expect( fapiClient.buildUrl({ path: '/foo', search: { array: ['item1', 'item2'], test: undefined, - } as any, + }, }).href, - ).toBe( - `https://clerk.example.com/v1/foo?array=item1&array=item2&__clerk_api_version=${SUPPORTED_FAPI_VERSION}&_clerk_js_version=test`, - ); + ).toBe('https://clerk.example.com/v1/foo?array=item1&array=item2&_clerk_js_version=test'); }); const cases = [ diff --git a/packages/clerk-js/src/core/__tests__/tokenCache.test.ts b/packages/clerk-js/src/core/__tests__/tokenCache.spec.ts similarity index 100% rename from packages/clerk-js/src/core/__tests__/tokenCache.test.ts rename to packages/clerk-js/src/core/__tests__/tokenCache.spec.ts diff --git a/packages/clerk-js/src/core/auth/__tests__/cookieSuffix.test.ts b/packages/clerk-js/src/core/auth/__tests__/cookieSuffix.spec.ts similarity index 100% rename from packages/clerk-js/src/core/auth/__tests__/cookieSuffix.test.ts rename to packages/clerk-js/src/core/auth/__tests__/cookieSuffix.spec.ts diff --git a/packages/clerk-js/src/core/auth/__tests__/devBrowser.test.ts b/packages/clerk-js/src/core/auth/__tests__/devBrowser.spec.ts similarity index 100% rename from packages/clerk-js/src/core/auth/__tests__/devBrowser.test.ts rename to packages/clerk-js/src/core/auth/__tests__/devBrowser.spec.ts diff --git a/packages/clerk-js/src/core/auth/__tests__/getCookieDomain.test.ts b/packages/clerk-js/src/core/auth/__tests__/getCookieDomain.spec.ts similarity index 99% rename from packages/clerk-js/src/core/auth/__tests__/getCookieDomain.test.ts rename to packages/clerk-js/src/core/auth/__tests__/getCookieDomain.spec.ts index 2f8bae19ffb..d12f504cfb3 100644 --- a/packages/clerk-js/src/core/auth/__tests__/getCookieDomain.test.ts +++ b/packages/clerk-js/src/core/auth/__tests__/getCookieDomain.spec.ts @@ -12,7 +12,7 @@ describe('getCookieDomain', () => { getCookieDomain = await import('../getCookieDomain').then(m => m.getCookieDomain); }); - it('returns the eTLD+1 domain based on where the cookie can be set', () => { + it('returns the eTLD+1 domain based on where the cookie can be set', async () => { // This unit tests relies on browser APIs that we can't mock without // rendering this test useless. // This logic will be covered by a separate E2E test suite, however, for diff --git a/packages/clerk-js/src/core/auth/__tests__/getSecureAttribute.test.ts b/packages/clerk-js/src/core/auth/__tests__/getSecureAttribute.spec.ts similarity index 100% rename from packages/clerk-js/src/core/auth/__tests__/getSecureAttribute.test.ts rename to packages/clerk-js/src/core/auth/__tests__/getSecureAttribute.spec.ts diff --git a/packages/clerk-js/src/core/auth/cookies/__tests__/clientUat.test.ts b/packages/clerk-js/src/core/auth/cookies/__tests__/clientUat.spec.ts similarity index 100% rename from packages/clerk-js/src/core/auth/cookies/__tests__/clientUat.test.ts rename to packages/clerk-js/src/core/auth/cookies/__tests__/clientUat.spec.ts diff --git a/packages/clerk-js/src/core/auth/cookies/__tests__/session.test.ts b/packages/clerk-js/src/core/auth/cookies/__tests__/session.spec.ts similarity index 100% rename from packages/clerk-js/src/core/auth/cookies/__tests__/session.test.ts rename to packages/clerk-js/src/core/auth/cookies/__tests__/session.spec.ts diff --git a/packages/clerk-js/src/core/fapiClient.ts b/packages/clerk-js/src/core/fapiClient.ts index 75a0bf04e0f..28f2f3ef0c1 100644 --- a/packages/clerk-js/src/core/fapiClient.ts +++ b/packages/clerk-js/src/core/fapiClient.ts @@ -105,20 +105,7 @@ export function createFapiClient(options: FapiClientOptions): FapiClient { } function buildQueryString({ method, path, sessionId, search, rotatingTokenNonce }: FapiRequestInit): string { - const filteredSearch = - search && typeof search === 'object' - ? Object.keys(search as Record).reduce( - (acc, key) => { - const value = (search as Record)[key]; - if (value !== undefined) { - acc[key] = value; - } - return acc; - }, - {} as Record, - ) - : search; - const searchParams = new URLSearchParams(filteredSearch as any); + const searchParams = new URLSearchParams(search as any); // the above will parse {key: ['val1','val2']} as key: 'val1,val2' and we need to recreate the array bellow // Append supported FAPI version to the query string diff --git a/packages/clerk-js/src/core/fraudProtection.test.ts b/packages/clerk-js/src/core/fraudProtection.spec.ts similarity index 100% rename from packages/clerk-js/src/core/fraudProtection.test.ts rename to packages/clerk-js/src/core/fraudProtection.spec.ts diff --git a/packages/clerk-js/src/core/modules/checkout/__tests__/manager.test.ts b/packages/clerk-js/src/core/modules/checkout/__tests__/manager.spec.ts similarity index 94% rename from packages/clerk-js/src/core/modules/checkout/__tests__/manager.test.ts rename to packages/clerk-js/src/core/modules/checkout/__tests__/manager.spec.ts index 29bd7b27015..fe003965e5b 100644 --- a/packages/clerk-js/src/core/modules/checkout/__tests__/manager.test.ts +++ b/packages/clerk-js/src/core/modules/checkout/__tests__/manager.spec.ts @@ -1,8 +1,8 @@ -import type { __experimental_CheckoutCacheState, BillingCheckoutResource, ClerkAPIResponseError } from '@clerk/types'; +import type { BillingCheckoutResource, ClerkAPIResponseError } from '@clerk/types'; import type { MockedFunction } from 'vitest'; import { beforeEach, describe, expect, it, vi } from 'vitest'; -import { type CheckoutKey, createCheckoutManager, FETCH_STATUS } from '../manager'; +import { type CheckoutCacheState, type CheckoutKey, createCheckoutManager, FETCH_STATUS } from '../manager'; // Type-safe mock for BillingCheckoutResource const createMockCheckoutResource = (overrides: Partial = {}): BillingCheckoutResource => ({ @@ -104,7 +104,7 @@ describe('createCheckoutManager', () => { describe('subscribe', () => { it('should add listener and return unsubscribe function', () => { - const listener: MockedFunction<(state: __experimental_CheckoutCacheState) => void> = vi.fn(); + const listener: MockedFunction<(state: CheckoutCacheState) => void> = vi.fn(); const unsubscribe = manager.subscribe(listener); @@ -113,7 +113,7 @@ describe('createCheckoutManager', () => { }); it('should remove listener when unsubscribe is called', async () => { - const listener: MockedFunction<(state: __experimental_CheckoutCacheState) => void> = vi.fn(); + const listener: MockedFunction<(state: CheckoutCacheState) => void> = vi.fn(); const unsubscribe = manager.subscribe(listener); @@ -137,8 +137,8 @@ describe('createCheckoutManager', () => { }); it('should notify all listeners when state changes', async () => { - const listener1: MockedFunction<(state: __experimental_CheckoutCacheState) => void> = vi.fn(); - const listener2: MockedFunction<(state: __experimental_CheckoutCacheState) => void> = vi.fn(); + const listener1: MockedFunction<(state: CheckoutCacheState) => void> = vi.fn(); + const listener2: MockedFunction<(state: CheckoutCacheState) => void> = vi.fn(); const mockCheckout = createMockCheckoutResource(); manager.subscribe(listener1); @@ -164,7 +164,7 @@ describe('createCheckoutManager', () => { }); it('should handle multiple subscribe/unsubscribe cycles', () => { - const listener: MockedFunction<(state: __experimental_CheckoutCacheState) => void> = vi.fn(); + const listener: MockedFunction<(state: CheckoutCacheState) => void> = vi.fn(); // Subscribe and unsubscribe multiple times const unsubscribe1 = manager.subscribe(listener); @@ -210,7 +210,7 @@ describe('createCheckoutManager', () => { }); it('should set isStarting to true during operation', async () => { - let capturedState: __experimental_CheckoutCacheState | null = null; + let capturedState: CheckoutCacheState | null = null; const mockOperation: MockedFunction<() => Promise> = vi .fn() .mockImplementation(async () => { @@ -314,7 +314,7 @@ describe('createCheckoutManager', () => { }); it('should set isConfirming to true during operation', async () => { - let capturedState: __experimental_CheckoutCacheState | null = null; + let capturedState: CheckoutCacheState | null = null; const mockOperation: MockedFunction<() => Promise> = vi .fn() .mockImplementation(async () => { @@ -479,7 +479,7 @@ describe('createCheckoutManager', () => { describe('clearCheckout', () => { it('should clear checkout state when no operations are pending', () => { - const listener: MockedFunction<(state: __experimental_CheckoutCacheState) => void> = vi.fn(); + const listener: MockedFunction<(state: CheckoutCacheState) => void> = vi.fn(); manager.subscribe(listener); manager.clearCheckout(); @@ -543,7 +543,7 @@ describe('createCheckoutManager', () => { expect(manager.getCacheState().fetchStatus).toBe(FETCH_STATUS.IDLE); // During operation - fetching - let capturedState: __experimental_CheckoutCacheState | null = null; + let capturedState: CheckoutCacheState | null = null; const mockOperation: MockedFunction<() => Promise> = vi .fn() .mockImplementation(async () => { @@ -595,8 +595,8 @@ describe('createCheckoutManager', () => { }); it('should handle both operations running simultaneously', async () => { - let startCapturedState: __experimental_CheckoutCacheState | null = null; - let confirmCapturedState: __experimental_CheckoutCacheState | null = null; + let startCapturedState: CheckoutCacheState | null = null; + let confirmCapturedState: CheckoutCacheState | null = null; const startOperation: MockedFunction<() => Promise> = vi .fn() @@ -660,8 +660,8 @@ describe('createCheckoutManager', () => { const manager1 = createCheckoutManager(createCacheKey('key1')); const manager2 = createCheckoutManager(createCacheKey('key2')); - const listener1: MockedFunction<(state: __experimental_CheckoutCacheState) => void> = vi.fn(); - const listener2: MockedFunction<(state: __experimental_CheckoutCacheState) => void> = vi.fn(); + const listener1: MockedFunction<(state: CheckoutCacheState) => void> = vi.fn(); + const listener2: MockedFunction<(state: CheckoutCacheState) => void> = vi.fn(); manager1.subscribe(listener1); manager2.subscribe(listener2); diff --git a/packages/clerk-js/src/core/modules/debug/__tests__/logger.test.ts b/packages/clerk-js/src/core/modules/debug/__tests__/logger.spec.ts similarity index 100% rename from packages/clerk-js/src/core/modules/debug/__tests__/logger.test.ts rename to packages/clerk-js/src/core/modules/debug/__tests__/logger.spec.ts diff --git a/packages/clerk-js/src/core/modules/debug/transports/__tests__/telemetry.test.ts b/packages/clerk-js/src/core/modules/debug/transports/__tests__/telemetry.spec.ts similarity index 100% rename from packages/clerk-js/src/core/modules/debug/transports/__tests__/telemetry.test.ts rename to packages/clerk-js/src/core/modules/debug/transports/__tests__/telemetry.spec.ts diff --git a/packages/clerk-js/src/core/resources/__tests__/AuthConfig.test.ts b/packages/clerk-js/src/core/resources/__tests__/AuthConfig.spec.ts similarity index 100% rename from packages/clerk-js/src/core/resources/__tests__/AuthConfig.test.ts rename to packages/clerk-js/src/core/resources/__tests__/AuthConfig.spec.ts diff --git a/packages/clerk-js/src/core/resources/__tests__/Base.test.ts b/packages/clerk-js/src/core/resources/__tests__/Base.spec.ts similarity index 100% rename from packages/clerk-js/src/core/resources/__tests__/Base.test.ts rename to packages/clerk-js/src/core/resources/__tests__/Base.spec.ts diff --git a/packages/clerk-js/src/core/resources/__tests__/Client.test.ts b/packages/clerk-js/src/core/resources/__tests__/Client.spec.ts similarity index 100% rename from packages/clerk-js/src/core/resources/__tests__/Client.test.ts rename to packages/clerk-js/src/core/resources/__tests__/Client.spec.ts diff --git a/packages/clerk-js/src/core/resources/__tests__/Environment.test.ts b/packages/clerk-js/src/core/resources/__tests__/Environment.spec.ts similarity index 100% rename from packages/clerk-js/src/core/resources/__tests__/Environment.test.ts rename to packages/clerk-js/src/core/resources/__tests__/Environment.spec.ts diff --git a/packages/clerk-js/src/core/resources/__tests__/ExternalAccount.test.ts b/packages/clerk-js/src/core/resources/__tests__/ExternalAccount.spec.ts similarity index 100% rename from packages/clerk-js/src/core/resources/__tests__/ExternalAccount.test.ts rename to packages/clerk-js/src/core/resources/__tests__/ExternalAccount.spec.ts diff --git a/packages/clerk-js/src/core/resources/__tests__/Image.test.ts b/packages/clerk-js/src/core/resources/__tests__/Image.spec.ts similarity index 100% rename from packages/clerk-js/src/core/resources/__tests__/Image.test.ts rename to packages/clerk-js/src/core/resources/__tests__/Image.spec.ts diff --git a/packages/clerk-js/src/core/resources/__tests__/Organization.test.ts b/packages/clerk-js/src/core/resources/__tests__/Organization.spec.ts similarity index 100% rename from packages/clerk-js/src/core/resources/__tests__/Organization.test.ts rename to packages/clerk-js/src/core/resources/__tests__/Organization.spec.ts diff --git a/packages/clerk-js/src/core/resources/__tests__/OrganizationDomain.test.ts b/packages/clerk-js/src/core/resources/__tests__/OrganizationDomain.spec.ts similarity index 100% rename from packages/clerk-js/src/core/resources/__tests__/OrganizationDomain.test.ts rename to packages/clerk-js/src/core/resources/__tests__/OrganizationDomain.spec.ts diff --git a/packages/clerk-js/src/core/resources/__tests__/OrganizationInvitation.test.ts b/packages/clerk-js/src/core/resources/__tests__/OrganizationInvitation.spec.ts similarity index 100% rename from packages/clerk-js/src/core/resources/__tests__/OrganizationInvitation.test.ts rename to packages/clerk-js/src/core/resources/__tests__/OrganizationInvitation.spec.ts diff --git a/packages/clerk-js/src/core/resources/__tests__/OrganizationMembership.test.ts b/packages/clerk-js/src/core/resources/__tests__/OrganizationMembership.spec.ts similarity index 100% rename from packages/clerk-js/src/core/resources/__tests__/OrganizationMembership.test.ts rename to packages/clerk-js/src/core/resources/__tests__/OrganizationMembership.spec.ts diff --git a/packages/clerk-js/src/core/resources/__tests__/OrganizationMembershipRequest.test.ts b/packages/clerk-js/src/core/resources/__tests__/OrganizationMembershipRequest.spec.ts similarity index 100% rename from packages/clerk-js/src/core/resources/__tests__/OrganizationMembershipRequest.test.ts rename to packages/clerk-js/src/core/resources/__tests__/OrganizationMembershipRequest.spec.ts diff --git a/packages/clerk-js/src/core/resources/__tests__/OrganizationSuggestion.test.ts b/packages/clerk-js/src/core/resources/__tests__/OrganizationSuggestion.spec.ts similarity index 100% rename from packages/clerk-js/src/core/resources/__tests__/OrganizationSuggestion.test.ts rename to packages/clerk-js/src/core/resources/__tests__/OrganizationSuggestion.spec.ts diff --git a/packages/clerk-js/src/core/resources/__tests__/PublicUserData.test.ts b/packages/clerk-js/src/core/resources/__tests__/PublicUserData.spec.ts similarity index 100% rename from packages/clerk-js/src/core/resources/__tests__/PublicUserData.test.ts rename to packages/clerk-js/src/core/resources/__tests__/PublicUserData.spec.ts diff --git a/packages/clerk-js/src/core/resources/__tests__/Session.test.ts b/packages/clerk-js/src/core/resources/__tests__/Session.spec.ts similarity index 100% rename from packages/clerk-js/src/core/resources/__tests__/Session.test.ts rename to packages/clerk-js/src/core/resources/__tests__/Session.spec.ts diff --git a/packages/clerk-js/src/core/resources/__tests__/SignIn.test.ts b/packages/clerk-js/src/core/resources/__tests__/SignIn.spec.ts similarity index 100% rename from packages/clerk-js/src/core/resources/__tests__/SignIn.test.ts rename to packages/clerk-js/src/core/resources/__tests__/SignIn.spec.ts diff --git a/packages/clerk-js/src/core/resources/__tests__/Token.test.ts b/packages/clerk-js/src/core/resources/__tests__/Token.spec.ts similarity index 100% rename from packages/clerk-js/src/core/resources/__tests__/Token.test.ts rename to packages/clerk-js/src/core/resources/__tests__/Token.spec.ts diff --git a/packages/clerk-js/src/core/resources/__tests__/User.test.ts b/packages/clerk-js/src/core/resources/__tests__/User.spec.ts similarity index 100% rename from packages/clerk-js/src/core/resources/__tests__/User.test.ts rename to packages/clerk-js/src/core/resources/__tests__/User.spec.ts diff --git a/packages/clerk-js/src/core/resources/__tests__/UserSettings.test.ts b/packages/clerk-js/src/core/resources/__tests__/UserSettings.spec.ts similarity index 100% rename from packages/clerk-js/src/core/resources/__tests__/UserSettings.test.ts rename to packages/clerk-js/src/core/resources/__tests__/UserSettings.spec.ts diff --git a/packages/clerk-js/src/core/resources/__tests__/Waitlist.test.ts b/packages/clerk-js/src/core/resources/__tests__/Waitlist.spec.ts similarity index 100% rename from packages/clerk-js/src/core/resources/__tests__/Waitlist.test.ts rename to packages/clerk-js/src/core/resources/__tests__/Waitlist.spec.ts diff --git a/packages/clerk-js/src/core/resources/__tests__/Web3Wallet.test.ts b/packages/clerk-js/src/core/resources/__tests__/Web3Wallet.spec.ts similarity index 100% rename from packages/clerk-js/src/core/resources/__tests__/Web3Wallet.test.ts rename to packages/clerk-js/src/core/resources/__tests__/Web3Wallet.spec.ts diff --git a/packages/clerk-js/src/testUtils.ts b/packages/clerk-js/src/testUtils.ts new file mode 100644 index 00000000000..57d179a8979 --- /dev/null +++ b/packages/clerk-js/src/testUtils.ts @@ -0,0 +1,77 @@ +// eslint-disable-next-line no-restricted-imports +import { matchers } from '@emotion/jest'; +import type { RenderOptions } from '@testing-library/react'; +import { render as _render } from '@testing-library/react'; +import UserEvent from '@testing-library/user-event'; + +expect.extend(matchers); + +Element.prototype.scrollIntoView = jest.fn(); + +const render = (ui: React.ReactElement, options?: RenderOptions) => { + const userEvent = UserEvent.setup({ delay: null }); + return { ..._render(ui, { ...options }), userEvent }; +}; + +/** + * Helper method to mock a native runtime environment for specific test cases, currently targeted at React Native. + * Makes some assumptions about our runtime detection utilities in `packages/clerk-js/src/utils/runtime.ts`. + * + * Usage: + * + * ```js + * mockNativeRuntime(() => { + * // test cases + * it('simulates native', () => { + * expect(typeof document).toBe('undefined'); + * }); + * }); + * ``` + */ +export const mockNativeRuntime = (fn: () => void) => { + describe('native runtime', () => { + let spyDocument: jest.SpyInstance; + let spyNavigator: jest.SpyInstance; + + beforeAll(() => { + spyDocument = jest.spyOn(globalThis, 'document', 'get'); + spyDocument.mockReturnValue(undefined); + + spyNavigator = jest.spyOn(globalThis.navigator, 'product', 'get'); + spyNavigator.mockReturnValue('ReactNative'); + }); + + afterAll(() => { + spyDocument.mockRestore(); + spyNavigator.mockRestore(); + }); + + fn(); + }); +}; + +export const mockWebAuthn = (fn: () => void) => { + describe('with WebAuthn', () => { + let originalPublicKeyCredential: any; + beforeAll(() => { + originalPublicKeyCredential = global.PublicKeyCredential; + const publicKeyCredential: any = () => {}; + global.PublicKeyCredential = publicKeyCredential; + publicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable = () => Promise.resolve(true); + publicKeyCredential.isConditionalMediationAvailable = () => Promise.resolve(true); + }); + + afterAll(() => { + global.PublicKeyCredential = originalPublicKeyCredential; + }); + + fn(); + }); +}; + +export * from './ui/utils/test/runFakeTimers'; +export * from './ui/utils/test/createFixtures'; +// eslint-disable-next-line import/export +export * from '@testing-library/react'; +// eslint-disable-next-line import/export +export { render }; diff --git a/packages/clerk-js/src/ui/common/__tests__/redirects.test.ts b/packages/clerk-js/src/ui/common/__tests__/redirects.spec.ts similarity index 100% rename from packages/clerk-js/src/ui/common/__tests__/redirects.test.ts rename to packages/clerk-js/src/ui/common/__tests__/redirects.spec.ts diff --git a/packages/clerk-js/src/ui/common/__tests__/verification.test.ts b/packages/clerk-js/src/ui/common/__tests__/verification.spec.ts similarity index 100% rename from packages/clerk-js/src/ui/common/__tests__/verification.test.ts rename to packages/clerk-js/src/ui/common/__tests__/verification.spec.ts diff --git a/packages/clerk-js/src/ui/common/__tests__/withRedirect.test.tsx b/packages/clerk-js/src/ui/common/__tests__/withRedirect.test.tsx index 20a975de8d2..e823455c560 100644 --- a/packages/clerk-js/src/ui/common/__tests__/withRedirect.test.tsx +++ b/packages/clerk-js/src/ui/common/__tests__/withRedirect.test.tsx @@ -1,8 +1,7 @@ import React from 'react'; -import { describe, expect, it } from 'vitest'; -import { render } from '../../../vitestUtils'; -import { bindCreateFixtures } from '../../utils/vitest/createFixtures'; +import { render } from '../../../testUtils'; +import { bindCreateFixtures } from '../../utils/test/createFixtures'; import { withRedirect } from '../withRedirect'; const { createFixtures } = bindCreateFixtures('SignIn'); diff --git a/packages/clerk-js/src/ui/components/Checkout/__tests__/Checkout.test.tsx b/packages/clerk-js/src/ui/components/Checkout/__tests__/Checkout.test.tsx index cfe21708185..ac5cd0a1835 100644 --- a/packages/clerk-js/src/ui/components/Checkout/__tests__/Checkout.test.tsx +++ b/packages/clerk-js/src/ui/components/Checkout/__tests__/Checkout.test.tsx @@ -1,23 +1,21 @@ -import { describe, expect, it, vi } from 'vitest'; - import { Drawer } from '@/ui/elements/Drawer'; -import { render, waitFor } from '../../../../vitestUtils'; -import { bindCreateFixtures } from '../../../utils/vitest/createFixtures'; +import { render, waitFor } from '../../../../testUtils'; +import { bindCreateFixtures } from '../../../utils/test/createFixtures'; import { Checkout } from '..'; const { createFixtures } = bindCreateFixtures('Checkout'); // Mock Payment Element to be ready so the submit button is rendered during tests -vi.mock('@clerk/shared/react', async importOriginal => { - const actual = await importOriginal(); +jest.mock('@clerk/shared/react', () => { + const actual = jest.requireActual('@clerk/shared/react'); return { ...actual, __experimental_PaymentElementProvider: ({ children }: { children: any }) => children, __experimental_PaymentElement: (_: { fallback?: any }) => null, __experimental_usePaymentElement: () => ({ - submit: vi.fn().mockResolvedValue({ data: { gateway: 'stripe', paymentToken: 'tok_test' }, error: null }), - reset: vi.fn().mockResolvedValue(undefined), + submit: jest.fn().mockResolvedValue({ data: { gateway: 'stripe', paymentToken: 'tok_test' }, error: null }), + reset: jest.fn().mockResolvedValue(undefined), isFormReady: true, provider: { name: 'stripe' }, isProviderReady: true, @@ -367,7 +365,7 @@ describe('Checkout', () => { freeTrialEnabled: true, }, paymentSource: undefined, - confirm: vi.fn(), + confirm: jest.fn(), freeTrialEndsAt, } as any); @@ -451,7 +449,7 @@ describe('Checkout', () => { freeTrialEnabled: true, }, paymentSource: undefined, - confirm: vi.fn(), + confirm: jest.fn(), freeTrialEndsAt, } as any); @@ -539,13 +537,13 @@ describe('Checkout', () => { isRemovable: true, status: 'active', walletType: undefined, - remove: vi.fn(), - makeDefault: vi.fn(), + remove: jest.fn(), + makeDefault: jest.fn(), pathRoot: '/', - reload: vi.fn(), + reload: jest.fn(), }, planPeriodStart: new Date('2025-08-19'), - confirm: vi.fn(), + confirm: jest.fn(), freeTrialEndsAt: null, } as any); @@ -631,12 +629,12 @@ describe('Checkout', () => { isRemovable: true, status: 'active', walletType: undefined, - remove: vi.fn(), - makeDefault: vi.fn(), + remove: jest.fn(), + makeDefault: jest.fn(), pathRoot: '/', - reload: vi.fn(), + reload: jest.fn(), }, - confirm: vi.fn(), + confirm: jest.fn(), freeTrialEndsAt: null, } as any); @@ -676,10 +674,10 @@ describe('Checkout', () => { isRemovable: true, status: 'active', walletType: undefined, - remove: vi.fn(), - makeDefault: vi.fn(), + remove: jest.fn(), + makeDefault: jest.fn(), pathRoot: '/', - reload: vi.fn(), + reload: jest.fn(), }, { id: 'pm_test_mastercard', @@ -690,10 +688,10 @@ describe('Checkout', () => { isRemovable: true, status: 'active', walletType: undefined, - remove: vi.fn(), - makeDefault: vi.fn(), + remove: jest.fn(), + makeDefault: jest.fn(), pathRoot: '/', - reload: vi.fn(), + reload: jest.fn(), }, ], total_count: 2, @@ -748,7 +746,7 @@ describe('Checkout', () => { freeTrialEnabled: true, }, paymentSource: undefined, - confirm: vi.fn(), + confirm: jest.fn(), freeTrialEndsAt: new Date('2025-08-19'), } as any); @@ -815,10 +813,10 @@ describe('Checkout', () => { isRemovable: true, status: 'active', walletType: undefined, - remove: vi.fn(), - makeDefault: vi.fn(), + remove: jest.fn(), + makeDefault: jest.fn(), pathRoot: '/', - reload: vi.fn(), + reload: jest.fn(), }, { id: 'pm_test_mastercard', @@ -829,10 +827,10 @@ describe('Checkout', () => { isRemovable: true, status: 'active', walletType: undefined, - remove: vi.fn(), - makeDefault: vi.fn(), + remove: jest.fn(), + makeDefault: jest.fn(), pathRoot: '/', - reload: vi.fn(), + reload: jest.fn(), }, ], total_count: 2, @@ -887,7 +885,7 @@ describe('Checkout', () => { freeTrialEnabled: true, }, paymentSource: undefined, - confirm: vi.fn(), + confirm: jest.fn(), freeTrialEndsAt: null, } as any); @@ -954,10 +952,10 @@ describe('Checkout', () => { isRemovable: true, status: 'active', walletType: undefined, - remove: vi.fn(), - makeDefault: vi.fn(), + remove: jest.fn(), + makeDefault: jest.fn(), pathRoot: '/', - reload: vi.fn(), + reload: jest.fn(), }, { id: 'pm_test_mastercard', @@ -968,10 +966,10 @@ describe('Checkout', () => { isRemovable: true, status: 'active', walletType: undefined, - remove: vi.fn(), - makeDefault: vi.fn(), + remove: jest.fn(), + makeDefault: jest.fn(), pathRoot: '/', - reload: vi.fn(), + reload: jest.fn(), }, ], total_count: 2, @@ -1026,7 +1024,7 @@ describe('Checkout', () => { freeTrialEnabled: true, }, paymentSource: undefined, - confirm: vi.fn(), + confirm: jest.fn(), freeTrialEndsAt: null, } as any); diff --git a/packages/clerk-js/src/ui/components/CreateOrganization/__tests__/CreateOrganization.test.tsx b/packages/clerk-js/src/ui/components/CreateOrganization/__tests__/CreateOrganization.test.tsx index 3393cc838be..66cb609fe2f 100644 --- a/packages/clerk-js/src/ui/components/CreateOrganization/__tests__/CreateOrganization.test.tsx +++ b/packages/clerk-js/src/ui/components/CreateOrganization/__tests__/CreateOrganization.test.tsx @@ -1,9 +1,9 @@ import type { OrganizationResource } from '@clerk/types'; +import { describe, jest } from '@jest/globals'; import { waitFor } from '@testing-library/dom'; -import { describe, expect, it, vi } from 'vitest'; -import { render } from '../../../../vitestUtils'; -import { bindCreateFixtures } from '../../../utils/vitest/createFixtures'; +import { render } from '../../../../testUtils'; +import { bindCreateFixtures } from '../../../utils/test/createFixtures'; import { CreateOrganization } from '../'; const { createFixtures } = bindCreateFixtures('CreateOrganization'); @@ -35,20 +35,20 @@ export const createFakeOrganization = (params: FakeOrganizationParams): Organiza maxAllowedMemberships: params?.maxAllowedMemberships, createdAt: params?.createdAt || new Date(), updatedAt: new Date(), - update: vi.fn() as any, - getMemberships: vi.fn() as any, - addMember: vi.fn() as any, - inviteMember: vi.fn() as any, - inviteMembers: vi.fn() as any, - updateMember: vi.fn() as any, - removeMember: vi.fn() as any, - createDomain: vi.fn() as any, - getDomain: vi.fn() as any, - getDomains: vi.fn() as any, - getMembershipRequests: vi.fn() as any, - destroy: vi.fn() as any, - setLogo: vi.fn() as any, - reload: vi.fn() as any, + update: jest.fn() as any, + getMemberships: jest.fn() as any, + addMember: jest.fn() as any, + inviteMember: jest.fn() as any, + inviteMembers: jest.fn() as any, + updateMember: jest.fn() as any, + removeMember: jest.fn() as any, + createDomain: jest.fn() as any, + getDomain: jest.fn() as any, + getDomains: jest.fn() as any, + getMembershipRequests: jest.fn() as any, + destroy: jest.fn() as any, + setLogo: jest.fn() as any, + reload: jest.fn() as any, }; }; diff --git a/packages/clerk-js/src/ui/components/OrganizationList/__tests__/OrganizationList.test.tsx b/packages/clerk-js/src/ui/components/OrganizationList/__tests__/OrganizationList.test.tsx index df2284938b7..7649f9c5149 100644 --- a/packages/clerk-js/src/ui/components/OrganizationList/__tests__/OrganizationList.test.tsx +++ b/packages/clerk-js/src/ui/components/OrganizationList/__tests__/OrganizationList.test.tsx @@ -1,7 +1,7 @@ -import { describe, expect, it, vi } from 'vitest'; +import { describe } from '@jest/globals'; -import { render, waitFor } from '../../../../vitestUtils'; -import { bindCreateFixtures } from '../../../utils/vitest/createFixtures'; +import { render, waitFor } from '../../../../testUtils'; +import { bindCreateFixtures } from '../../../utils/test/createFixtures'; import { createFakeOrganization } from '../../CreateOrganization/__tests__/CreateOrganization.test'; import { createFakeUserOrganizationInvitation, @@ -143,7 +143,7 @@ describe('OrganizationList', () => { }, }); - invitation.accept = vi.fn().mockResolvedValue( + invitation.accept = jest.fn().mockResolvedValue( createFakeUserOrganizationInvitation({ id: '1', emailAddress: 'one@clerk.com', diff --git a/packages/clerk-js/src/ui/components/OrganizationProfile/__tests__/InviteMembersPage.test.tsx b/packages/clerk-js/src/ui/components/OrganizationProfile/__tests__/InviteMembersPage.test.tsx index 341ac60182c..b32f6ffdc94 100644 --- a/packages/clerk-js/src/ui/components/OrganizationProfile/__tests__/InviteMembersPage.test.tsx +++ b/packages/clerk-js/src/ui/components/OrganizationProfile/__tests__/InviteMembersPage.test.tsx @@ -1,13 +1,13 @@ import type { OrganizationInvitationResource } from '@clerk/types'; +import { describe } from '@jest/globals'; import { waitFor } from '@testing-library/dom'; import React from 'react'; -import { beforeEach, describe, expect, it, vi } from 'vitest'; import { ClerkAPIResponseError } from '../../../../core/resources'; -import { render } from '../../../../vitestUtils'; +import { render } from '../../../../testUtils'; import { Action } from '../../../elements/Action'; import { clearFetchCache } from '../../../hooks'; -import { bindCreateFixtures } from '../../../utils/vitest/createFixtures'; +import { bindCreateFixtures } from '../../../utils/test/createFixtures'; import { InviteMembersScreen } from '../InviteMembersScreen'; const { createFixtures } = bindCreateFixtures('OrganizationProfile'); @@ -60,7 +60,7 @@ describe('InviteMembersPage', () => { data: [ { pathRoot: '', - reload: vi.fn(), + reload: jest.fn(), id: 'member', key: 'member', name: 'member', @@ -71,7 +71,7 @@ describe('InviteMembersPage', () => { }, { pathRoot: '', - reload: vi.fn(), + reload: jest.fn(), id: 'admin', key: 'admin', name: 'Admin', @@ -82,7 +82,7 @@ describe('InviteMembersPage', () => { }, { pathRoot: '', - reload: vi.fn(), + reload: jest.fn(), id: defaultRole, key: defaultRole, name: defaultRole, @@ -119,7 +119,7 @@ describe('InviteMembersPage', () => { data: [ { pathRoot: '', - reload: vi.fn(), + reload: jest.fn(), id: 'member', key: 'member', name: 'member', @@ -156,7 +156,7 @@ describe('InviteMembersPage', () => { data: [ { pathRoot: '', - reload: vi.fn(), + reload: jest.fn(), id: 'member', key: 'member', name: 'member', @@ -167,7 +167,7 @@ describe('InviteMembersPage', () => { }, { pathRoot: '', - reload: vi.fn(), + reload: jest.fn(), id: 'admin', key: 'admin', name: 'admin', @@ -207,7 +207,7 @@ describe('InviteMembersPage', () => { data: [ { pathRoot: '', - reload: vi.fn(), + reload: jest.fn(), id: 'member', key: 'member', name: 'member', @@ -218,7 +218,7 @@ describe('InviteMembersPage', () => { }, { pathRoot: '', - reload: vi.fn(), + reload: jest.fn(), id: 'admin', key: 'admin', name: 'Admin', @@ -229,7 +229,7 @@ describe('InviteMembersPage', () => { }, { pathRoot: '', - reload: vi.fn(), + reload: jest.fn(), id: defaultRole, key: defaultRole, name: defaultRole, @@ -270,7 +270,7 @@ describe('InviteMembersPage', () => { data: [ { pathRoot: '', - reload: vi.fn(), + reload: jest.fn(), id: 'member', key: 'member', name: 'member', @@ -281,7 +281,7 @@ describe('InviteMembersPage', () => { }, { pathRoot: '', - reload: vi.fn(), + reload: jest.fn(), id: 'admin', key: 'admin', name: 'Admin', @@ -324,7 +324,7 @@ describe('InviteMembersPage', () => { data: [ { pathRoot: '', - reload: vi.fn(), + reload: jest.fn(), id: 'member', key: 'member', name: 'member', @@ -335,7 +335,7 @@ describe('InviteMembersPage', () => { }, { pathRoot: '', - reload: vi.fn(), + reload: jest.fn(), id: 'admin', key: 'admin', name: 'Admin', @@ -381,7 +381,7 @@ describe('InviteMembersPage', () => { data: [ { pathRoot: '', - reload: vi.fn(), + reload: jest.fn(), id: 'member', key: 'member', name: 'member', @@ -392,7 +392,7 @@ describe('InviteMembersPage', () => { }, { pathRoot: '', - reload: vi.fn(), + reload: jest.fn(), id: 'admin', key: 'admin', name: 'Admin', @@ -441,7 +441,7 @@ describe('InviteMembersPage', () => { data: [ { pathRoot: '', - reload: vi.fn(), + reload: jest.fn(), id: 'member', key: 'member', name: 'member', @@ -452,7 +452,7 @@ describe('InviteMembersPage', () => { }, { pathRoot: '', - reload: vi.fn(), + reload: jest.fn(), id: 'admin', key: 'admin', name: 'Admin', @@ -497,7 +497,7 @@ describe('InviteMembersPage', () => { data: [ { pathRoot: '', - reload: vi.fn(), + reload: jest.fn(), id: 'member', key: 'member', name: 'member', @@ -508,7 +508,7 @@ describe('InviteMembersPage', () => { }, { pathRoot: '', - reload: vi.fn(), + reload: jest.fn(), id: 'admin', key: 'admin', name: 'Admin', @@ -567,7 +567,7 @@ describe('InviteMembersPage', () => { data: [ { pathRoot: '', - reload: vi.fn(), + reload: jest.fn(), id: 'member', key: 'member', name: 'member', @@ -578,7 +578,7 @@ describe('InviteMembersPage', () => { }, { pathRoot: '', - reload: vi.fn(), + reload: jest.fn(), id: 'admin', key: 'admin', name: 'Admin', @@ -633,7 +633,7 @@ describe('InviteMembersPage', () => { data: [ { pathRoot: '', - reload: vi.fn(), + reload: jest.fn(), id: 'member', key: 'member', name: 'member', @@ -644,7 +644,7 @@ describe('InviteMembersPage', () => { }, { pathRoot: '', - reload: vi.fn(), + reload: jest.fn(), id: 'admin', key: 'admin', name: 'Admin', diff --git a/packages/clerk-js/src/ui/components/OrganizationProfile/__tests__/LeaveOrganizationPage.test.tsx b/packages/clerk-js/src/ui/components/OrganizationProfile/__tests__/LeaveOrganizationPage.test.tsx index c28424b61fd..9669002dc21 100644 --- a/packages/clerk-js/src/ui/components/OrganizationProfile/__tests__/LeaveOrganizationPage.test.tsx +++ b/packages/clerk-js/src/ui/components/OrganizationProfile/__tests__/LeaveOrganizationPage.test.tsx @@ -1,11 +1,11 @@ import type { DeletedObjectResource } from '@clerk/types'; +import { describe, it } from '@jest/globals'; import { waitFor } from '@testing-library/react'; -import { describe, expect, it, vi } from 'vitest'; import { CardStateProvider } from '@/ui/elements/contexts'; -import { render } from '../../../../vitestUtils'; -import { bindCreateFixtures } from '../../../utils/vitest/createFixtures'; +import { render } from '../../../../testUtils'; +import { bindCreateFixtures } from '../../../utils/test/createFixtures'; import { LeaveOrganizationForm } from '../ActionConfirmationPage'; const { createFixtures } = bindCreateFixtures('OrganizationProfile'); @@ -25,8 +25,8 @@ describe('LeaveOrganizationPage', () => { const { getByRole, userEvent, getByLabelText } = render( , { wrapper }, @@ -50,8 +50,8 @@ describe('LeaveOrganizationPage', () => { const { getByRole, userEvent, getByLabelText, findByRole } = render( , { wrapper }, diff --git a/packages/clerk-js/src/ui/components/OrganizationProfile/__tests__/OrganizationGeneralPage.test.tsx b/packages/clerk-js/src/ui/components/OrganizationProfile/__tests__/OrganizationGeneralPage.test.tsx index 050196def76..a7bd80b47ef 100644 --- a/packages/clerk-js/src/ui/components/OrganizationProfile/__tests__/OrganizationGeneralPage.test.tsx +++ b/packages/clerk-js/src/ui/components/OrganizationProfile/__tests__/OrganizationGeneralPage.test.tsx @@ -1,11 +1,11 @@ import type { ClerkPaginatedResponse, OrganizationDomainResource, OrganizationMembershipResource } from '@clerk/types'; +import { describe, it } from '@jest/globals'; import userEvent from '@testing-library/user-event'; -import { describe, expect, it } from 'vitest'; import { CardStateProvider } from '@/ui/elements/contexts'; -import { act, render, waitFor } from '../../../../vitestUtils'; -import { bindCreateFixtures } from '../../../utils/vitest/createFixtures'; +import { act, render, waitFor } from '../../../../testUtils'; +import { bindCreateFixtures } from '../../../utils/test/createFixtures'; import { OrganizationGeneralPage } from '../OrganizationGeneralPage'; import { createFakeDomain, createFakeMember } from './utils'; @@ -58,11 +58,12 @@ describe('OrganizationSettings', () => { total_count: 1, }), ); - const { findByText, findByRole } = render(, { wrapper }); - await findByText('General'); - await findByRole('button', { name: /update profile/i }); - const leaveButton = await findByRole('button', { name: /leave organization/i }); - expect(leaveButton).not.toBeDisabled(); + const { getByText, getByRole } = render(, { wrapper }); + await waitFor(() => { + expect(getByText('General')).toBeDefined(); + getByRole('button', { name: /update profile/i }); + expect(getByRole('button', { name: /leave organization/i })).not.toBeDisabled(); + }); }); it('disabled organization profile button when user does not have permissions', async () => { @@ -84,11 +85,12 @@ describe('OrganizationSettings', () => { total_count: 1, }), ); - const { findByText, queryByRole } = render(, { wrapper }); - await findByText('General'); - expect(queryByRole('button', { name: /update profile/i })).not.toBeInTheDocument(); - const leaveButton = queryByRole('button', { name: /leave organization/i }); - expect(leaveButton).not.toBeDisabled(); + const { getByText, queryByRole } = render(, { wrapper }); + await waitFor(() => { + expect(getByText('General')).toBeDefined(); + expect(queryByRole('button', { name: /update profile/i })).not.toBeInTheDocument(); + expect(queryByRole('button', { name: /leave organization/i })).not.toBeDisabled(); + }); }); it.skip('disables organization profile button and enables leave when user is not admin', async () => { @@ -106,13 +108,13 @@ describe('OrganizationSettings', () => { }); fixtures.clerk.organization?.getMemberships.mockReturnValue(Promise.resolve(adminsList)); - const { findByText, getByText } = render(, { wrapper }); + const { getByText } = render(, { wrapper }); await waitFor(() => { expect(fixtures.clerk.organization?.getMemberships).toHaveBeenCalled(); + expect(getByText('General')).toBeDefined(); + expect(getByText('Org1', { exact: false }).closest('button')).toBeNull(); + expect(getByText(/leave organization/i, { exact: false }).closest('button')).not.toHaveAttribute('disabled'); }); - await findByText('General'); - expect(getByText('Org1', { exact: false }).closest('button')).toBeNull(); - expect(getByText(/leave organization/i, { exact: false }).closest('button')).not.toHaveAttribute('disabled'); }); it('hides domains when `read` permission is missing', async () => { @@ -186,9 +188,11 @@ describe('OrganizationSettings', () => { }); }); - const { findByRole, queryByRole } = await act(() => render(, { wrapper })); - await findByRole('button', { name: /leave organization/i }); - expect(queryByRole('button', { name: /delete organization/i })).not.toBeInTheDocument(); + const { queryByRole } = await act(() => render(, { wrapper })); + await waitFor(() => { + expect(queryByRole('button', { name: /leave organization/i })).toBeInTheDocument(); + expect(queryByRole('button', { name: /delete organization/i })).not.toBeInTheDocument(); + }); }); it('enabled leave organization button with delete organization button', async () => { @@ -200,10 +204,11 @@ describe('OrganizationSettings', () => { }); }); - const { findByRole } = render(, { wrapper }); - const leaveButton = await findByRole('button', { name: /leave organization/i }); - expect(leaveButton).not.toHaveAttribute('disabled'); - await findByRole('button', { name: /delete organization/i }); + const { getByRole } = render(, { wrapper }); + await waitFor(() => { + expect(getByRole('button', { name: /leave organization/i })).not.toHaveAttribute('disabled'); + expect(getByRole('button', { name: /delete organization/i })).toBeInTheDocument(); + }); }); it.skip('disabled leave organization button with delete organization button', async () => { @@ -232,13 +237,12 @@ describe('OrganizationSettings', () => { }); fixtures.clerk.organization?.getMemberships.mockReturnValue(Promise.resolve(adminsList)); - const { findByRole } = render(, { wrapper }); + const { getByRole } = render(, { wrapper }); await waitFor(() => { expect(fixtures.clerk.organization?.getMemberships).toHaveBeenCalled(); + expect(getByRole('button', { name: /leave organization/i })).toHaveAttribute('disabled'); + expect(getByRole('button', { name: /delete organization/i })).toBeInTheDocument(); }); - const leaveButton = await findByRole('button', { name: /leave organization/i }); - expect(leaveButton).toHaveAttribute('disabled'); - await findByRole('button', { name: /delete organization/i }); }); }); @@ -252,7 +256,7 @@ describe('OrganizationSettings', () => { }); }); - const { getByText, findByLabelText, getByRole, userEvent, queryByText, queryByLabelText } = render( + const { getByText, getByLabelText, getByRole, userEvent, queryByText, queryByLabelText } = render( , { wrapper, @@ -260,7 +264,7 @@ describe('OrganizationSettings', () => { ); getByText('Org1'); await userEvent.click(getByRole('button', { name: /update profile/i })); - await findByLabelText(/name/i); + await waitFor(() => getByLabelText(/name/i)); expect(queryByText('Logo')).toBeInTheDocument(); expect(queryByLabelText(/name/i)).toBeInTheDocument(); expect(queryByLabelText(/slug/i)).toBeInTheDocument(); @@ -288,16 +292,19 @@ describe('OrganizationSettings', () => { { wrapper }, ); - await findByRole('button', { name: /leave organization/i }); + await waitFor(async () => + expect(await findByRole('button', { name: /leave organization/i })).toBeInTheDocument(), + ); await userEvent.click(getByRole('button', { name: /leave organization/i })); - await findByRole('heading', { name: /leave organization/i }); + await waitFor(async () => + expect(await findByRole('heading', { name: /leave organization/i })).toBeInTheDocument(), + ); getByText(/Are you sure you want to leave this organization/i); getByText(/This action is permanent and irreversible/i); }); - // TODO: investigate why this test is failing in vitest - it.skip('hides Leave Organization screen when clicking cancel', async () => { + it('hides Leave Organization screen when clicking cancel', async () => { const { wrapper } = await createFixtures(f => { f.withOrganizations(); f.withUser({ @@ -306,29 +313,25 @@ describe('OrganizationSettings', () => { }); }); - const { findByRole, getByRole } = render( + const { findByRole, getByRole, queryByRole } = render( , { wrapper }, ); - // Wait for the leave organization button to be available - await findByRole('button', { name: /leave organization/i }); - - // Click the leave organization button + await waitFor(async () => + expect(await findByRole('button', { name: /leave organization/i })).toBeInTheDocument(), + ); await userEvent.click(getByRole('button', { name: /leave organization/i })); - - // Wait for the modal to appear - await findByRole('heading', { name: /leave organization/i }); - - // Click cancel button + await waitFor(async () => + expect(await findByRole('heading', { name: /leave organization/i })).toBeInTheDocument(), + ); await userEvent.click(getByRole('button', { name: /cancel/i })); - - // Wait for the modal to disappear - check that the original button is back - const originalButton = await findByRole('button', { name: /leave organization/i }); - expect(originalButton).toBeInTheDocument(); - expect(originalButton).not.toBeDisabled(); + await waitFor(async () => + expect(await findByRole('button', { name: /leave organization/i })).toBeInTheDocument(), + ); + expect(queryByRole('heading', { name: /leave organization/i })).not.toBeInTheDocument(); }); }); }); diff --git a/packages/clerk-js/src/ui/components/OrganizationProfile/__tests__/OrganizationMembers.test.tsx b/packages/clerk-js/src/ui/components/OrganizationProfile/__tests__/OrganizationMembers.test.tsx index 9aac84a61ba..e9b18a5cdd2 100644 --- a/packages/clerk-js/src/ui/components/OrganizationProfile/__tests__/OrganizationMembers.test.tsx +++ b/packages/clerk-js/src/ui/components/OrganizationProfile/__tests__/OrganizationMembers.test.tsx @@ -1,11 +1,11 @@ import type { OrganizationInvitationResource, OrganizationMembershipResource } from '@clerk/types'; +import { describe } from '@jest/globals'; import { screen, waitFor, waitForElementToBeRemoved } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { beforeEach, describe, expect, it, vi } from 'vitest'; -import { render } from '../../../../vitestUtils'; +import { render } from '../../../../testUtils'; import { clearFetchCache } from '../../../hooks/useFetch'; -import { bindCreateFixtures } from '../../../utils/vitest/createFixtures'; +import { bindCreateFixtures } from '../../../utils/test/createFixtures'; import { OrganizationMembers } from '../OrganizationMembers'; import { createFakeMember, createFakeOrganizationInvitation, createFakeOrganizationMembershipRequest } from './utils'; @@ -167,7 +167,7 @@ describe('OrganizationMembers', () => { data: [ { pathRoot: '', - reload: vi.fn(), + reload: jest.fn(), id: 'member', key: 'member', name: 'Member', @@ -178,7 +178,7 @@ describe('OrganizationMembers', () => { }, { pathRoot: '', - reload: vi.fn(), + reload: jest.fn(), id: 'admin', key: 'admin', name: 'Admin', @@ -505,7 +505,7 @@ describe('OrganizationMembers', () => { data: [ { pathRoot: '', - reload: vi.fn(), + reload: jest.fn(), id: 'member', key: 'member', name: 'Member', @@ -516,7 +516,7 @@ describe('OrganizationMembers', () => { }, { pathRoot: '', - reload: vi.fn(), + reload: jest.fn(), id: 'admin', key: 'admin', name: 'Admin', @@ -561,7 +561,7 @@ describe('OrganizationMembers', () => { }); }); - it.skip('hides the invite screen when user clicks cancel button', async () => { + it('hides the invite screen when user clicks cancel button', async () => { const { wrapper, fixtures } = await createFixtures(f => { f.withOrganizations(); f.withUser({ @@ -572,22 +572,21 @@ describe('OrganizationMembers', () => { fixtures.clerk.organization?.getRoles.mockRejectedValue(null); - const { container, queryByRole, findByRole } = render(, { wrapper }); + const { container, getByRole, queryByRole, findByRole } = render(, { wrapper }); await waitForLoadingCompleted(container); - const inviteButton = await findByRole('button', { name: 'Invite' }); - await userEvent.click(inviteButton); - - expect(await findByRole('heading', { name: /invite new members/i })).toBeInTheDocument(); + const inviteButton = queryByRole('button', { name: 'Invite' }); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + await userEvent.click(inviteButton!); + await waitFor(async () => + expect(await findByRole('heading', { name: /invite new members/i })).toBeInTheDocument(), + ); expect(inviteButton).toBeInTheDocument(); + await userEvent.click(getByRole('button', { name: 'Cancel' })); - const cancelButton = await findByRole('button', { name: 'Cancel' }); - await userEvent.click(cancelButton); - - await waitForElementToBeRemoved(() => queryByRole('heading', { name: /invite new members/i })); - - expect(await findByRole('button', { name: 'Invite' })).toBeInTheDocument(); + await waitFor(async () => expect(await findByRole('button', { name: 'Invite' })).toBeInTheDocument()); + expect(queryByRole('heading', { name: /invite new members/i })).not.toBeInTheDocument(); }); }); }); diff --git a/packages/clerk-js/src/ui/components/OrganizationProfile/__tests__/OrganizationProfile.test.tsx b/packages/clerk-js/src/ui/components/OrganizationProfile/__tests__/OrganizationProfile.test.tsx index 709f4f15e6c..f682b03655c 100644 --- a/packages/clerk-js/src/ui/components/OrganizationProfile/__tests__/OrganizationProfile.test.tsx +++ b/packages/clerk-js/src/ui/components/OrganizationProfile/__tests__/OrganizationProfile.test.tsx @@ -1,16 +1,16 @@ import type { CustomPage } from '@clerk/types'; +import { describe, it } from '@jest/globals'; import React from 'react'; -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { render } from '../../../../vitestUtils'; -import { bindCreateFixtures } from '../../../utils/vitest/createFixtures'; +import { render } from '../../../../testUtils'; +import { bindCreateFixtures } from '../../../utils/test/createFixtures'; import { OrganizationProfile } from '../'; const { createFixtures } = bindCreateFixtures('OrganizationProfile'); describe('OrganizationProfile', () => { - beforeEach(() => vi.useFakeTimers()); - afterEach(() => vi.useRealTimers()); + beforeEach(() => jest.useFakeTimers()); + afterEach(() => jest.useRealTimers()); it('includes buttons for the bigger sections', async () => { const { wrapper } = await createFixtures(f => { f.withOrganizations(); diff --git a/packages/clerk-js/src/ui/components/OrganizationProfile/__tests__/ProfileSettingsPage.test.tsx b/packages/clerk-js/src/ui/components/OrganizationProfile/__tests__/ProfileSettingsPage.test.tsx index 2e1a97d7874..40d08c2ceca 100644 --- a/packages/clerk-js/src/ui/components/OrganizationProfile/__tests__/ProfileSettingsPage.test.tsx +++ b/packages/clerk-js/src/ui/components/OrganizationProfile/__tests__/ProfileSettingsPage.test.tsx @@ -1,8 +1,8 @@ import type { OrganizationResource } from '@clerk/types'; -import { describe, expect, it, vi } from 'vitest'; +import { describe, it } from '@jest/globals'; -import { render } from '../../../../vitestUtils'; -import { bindCreateFixtures } from '../../../utils/vitest/createFixtures'; +import { render } from '../../../../testUtils'; +import { bindCreateFixtures } from '../../../utils/test/createFixtures'; import { ProfileForm } from '../ProfileForm'; const { createFixtures } = bindCreateFixtures('OrganizationProfile'); @@ -16,8 +16,8 @@ describe('OrganizationProfileScreen', () => { const { getByDisplayValue } = render( , { wrapper }, ); @@ -35,8 +35,8 @@ describe('OrganizationProfileScreen', () => { const { getByLabelText, userEvent, getByRole } = render( , { wrapper }, ); @@ -57,8 +57,8 @@ describe('OrganizationProfileScreen', () => { fixtures.clerk.organization?.update.mockResolvedValue({} as OrganizationResource); const { getByDisplayValue, getByLabelText, userEvent, getByRole } = render( , { wrapper }, ); @@ -80,8 +80,8 @@ describe('OrganizationProfileScreen', () => { fixtures.clerk.organization?.update.mockResolvedValue({} as OrganizationResource); const { getByDisplayValue, getByLabelText, userEvent, getByRole } = render( , { wrapper }, ); diff --git a/packages/clerk-js/src/ui/components/OrganizationProfile/__tests__/utils.ts b/packages/clerk-js/src/ui/components/OrganizationProfile/__tests__/utils.ts index 9c85919ac43..4354199e5dd 100644 --- a/packages/clerk-js/src/ui/components/OrganizationProfile/__tests__/utils.ts +++ b/packages/clerk-js/src/ui/components/OrganizationProfile/__tests__/utils.ts @@ -10,7 +10,7 @@ import type { OrganizationResource, PublicUserData, } from '@clerk/types'; -import { vi } from 'vitest'; +import { jest } from '@jest/globals'; type FakeMemberParams = { id: string; @@ -26,8 +26,8 @@ type FakeMemberParams = { export const createFakeMember = (params: FakeMemberParams): OrganizationMembershipResource => { return { - destroy: vi.fn() as any, - update: vi.fn() as any, + destroy: jest.fn() as any, + update: jest.fn() as any, organization: { id: params.orgId } as any as OrganizationResource, id: params.id, role: params?.role || 'admin', @@ -70,11 +70,11 @@ export const createFakeDomain = (params: FakeDomainParams): OrganizationDomainRe createdAt: params.createdAt || new Date(), updatedAt: new Date(), affiliationEmailAddress: params.affiliationEmailAddress || null, - attemptAffiliationVerification: vi.fn() as any, - delete: vi.fn() as any, - prepareAffiliationVerification: vi.fn() as any, - updateEnrollmentMode: vi.fn() as any, - reload: vi.fn() as any, + attemptAffiliationVerification: jest.fn() as any, + delete: jest.fn() as any, + prepareAffiliationVerification: jest.fn() as any, + updateEnrollmentMode: jest.fn() as any, + reload: jest.fn() as any, }; }; @@ -100,8 +100,8 @@ export const createFakeOrganizationInvitation = (params: FakeInvitationParams): status: params.status || 'pending', createdAt: params?.createdAt || new Date(), updatedAt: new Date(), - revoke: vi.fn as any, - reload: vi.fn as any, + revoke: jest.fn as any, + reload: jest.fn as any, }; }; @@ -131,8 +131,8 @@ export const createFakeOrganizationMembershipRequest = ( status: params.status || 'pending', createdAt: params?.createdAt || new Date(), updatedAt: new Date(), - accept: vi.fn as any, - reject: vi.fn as any, - reload: vi.fn as any, + accept: jest.fn as any, + reject: jest.fn as any, + reload: jest.fn as any, }; }; diff --git a/packages/clerk-js/src/ui/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx b/packages/clerk-js/src/ui/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx index b89bb7268fa..323c2689fc9 100644 --- a/packages/clerk-js/src/ui/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx +++ b/packages/clerk-js/src/ui/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx @@ -1,9 +1,9 @@ import type { MembershipRole } from '@clerk/types'; +import { describe } from '@jest/globals'; import { waitFor } from '@testing-library/react'; -import { describe, expect, it } from 'vitest'; -import { act, render } from '../../../../vitestUtils'; -import { bindCreateFixtures } from '../../../utils/vitest/createFixtures'; +import { act, render } from '../../../../testUtils'; +import { bindCreateFixtures } from '../../../utils/test/createFixtures'; import { OrganizationSwitcher } from '../'; import { createFakeUserOrganizationInvitation, diff --git a/packages/clerk-js/src/ui/components/OrganizationSwitcher/__tests__/utils.test.ts b/packages/clerk-js/src/ui/components/OrganizationSwitcher/__tests__/utils.spec.ts similarity index 100% rename from packages/clerk-js/src/ui/components/OrganizationSwitcher/__tests__/utils.test.ts rename to packages/clerk-js/src/ui/components/OrganizationSwitcher/__tests__/utils.spec.ts diff --git a/packages/clerk-js/src/ui/components/OrganizationSwitcher/__tests__/utlis.ts b/packages/clerk-js/src/ui/components/OrganizationSwitcher/__tests__/utlis.ts index 3c817b50afd..497f54ab386 100644 --- a/packages/clerk-js/src/ui/components/OrganizationSwitcher/__tests__/utlis.ts +++ b/packages/clerk-js/src/ui/components/OrganizationSwitcher/__tests__/utlis.ts @@ -6,7 +6,7 @@ import type { OrganizationSuggestionStatus, UserOrganizationInvitationResource, } from '@clerk/types'; -import { vi } from 'vitest'; +import { jest } from '@jest/globals'; import type { FakeOrganizationParams } from '../../CreateOrganization/__tests__/CreateOrganization.test'; import { createFakeOrganization } from '../../CreateOrganization/__tests__/CreateOrganization.test'; @@ -40,8 +40,8 @@ export const createFakeUserOrganizationInvitation = ( createdAt: params?.createdAt || new Date(), updatedAt: new Date(), publicMetadata: {}, - accept: vi.fn() as any, - reload: vi.fn() as any, + accept: jest.fn() as any, + reload: jest.fn() as any, }; }; @@ -65,9 +65,9 @@ export const createFakeUserOrganizationMembership = ( updatedAt: new Date(), publicMetadata: {}, publicUserData: {} as any, - update: vi.fn() as any, - destroy: vi.fn() as any, - reload: vi.fn() as any, + update: jest.fn() as any, + destroy: jest.fn() as any, + reload: jest.fn() as any, }; }; @@ -97,7 +97,7 @@ export const createFakeUserOrganizationSuggestion = ( id: params.id, createdAt: params?.createdAt || new Date(), updatedAt: new Date(), - accept: vi.fn() as any, - reload: vi.fn() as any, + accept: jest.fn() as any, + reload: jest.fn() as any, }; }; diff --git a/packages/clerk-js/src/ui/components/Plans/__tests__/PlanDetails.test.tsx b/packages/clerk-js/src/ui/components/Plans/__tests__/PlanDetails.test.tsx index 0dfa170c40c..9810e552f1d 100644 --- a/packages/clerk-js/src/ui/components/Plans/__tests__/PlanDetails.test.tsx +++ b/packages/clerk-js/src/ui/components/Plans/__tests__/PlanDetails.test.tsx @@ -1,9 +1,7 @@ -import { describe, expect, it, vi } from 'vitest'; - import { Drawer } from '@/ui/elements/Drawer'; -import { render, waitFor } from '../../../../vitestUtils'; -import { bindCreateFixtures } from '../../../utils/vitest/createFixtures'; +import { render, waitFor } from '../../../../testUtils'; +import { bindCreateFixtures } from '../../../utils/test/createFixtures'; import { PlanDetails } from '../PlanDetails'; const { createFixtures } = bindCreateFixtures('UserProfile'); @@ -15,9 +13,9 @@ describe('PlanDetails', () => { description: 'Feature 1 Description', avatarUrl: 'https://example.com/feature1.png', slug: 'feature-1', - __internal_toSnapshot: vi.fn(), + __internal_toSnapshot: jest.fn(), pathRoot: '', - reload: vi.fn(), + reload: jest.fn(), }; const mockFeature2 = { @@ -26,9 +24,9 @@ describe('PlanDetails', () => { description: 'Feature 2 Description', avatarUrl: 'https://example.com/feature2.png', slug: 'feature-2', - __internal_toSnapshot: vi.fn(), + __internal_toSnapshot: jest.fn(), pathRoot: '', - reload: vi.fn(), + reload: jest.fn(), }; const mockPlan = { @@ -62,9 +60,9 @@ describe('PlanDetails', () => { slug: 'test-plan', avatarUrl: 'https://example.com/avatar.png', features: [mockFeature, mockFeature2], - __internal_toSnapshot: vi.fn(), + __internal_toSnapshot: jest.fn(), pathRoot: '', - reload: vi.fn(), + reload: jest.fn(), }; it('displays spinner when loading with planId', async () => { diff --git a/packages/clerk-js/src/ui/components/PricingTable/__tests__/PricingTable.test.tsx b/packages/clerk-js/src/ui/components/PricingTable/__tests__/PricingTable.test.tsx index 1230b16d10b..1184de42e8c 100644 --- a/packages/clerk-js/src/ui/components/PricingTable/__tests__/PricingTable.test.tsx +++ b/packages/clerk-js/src/ui/components/PricingTable/__tests__/PricingTable.test.tsx @@ -1,7 +1,5 @@ -import { describe, expect, it, vi } from 'vitest'; - -import { render, waitFor } from '../../../../vitestUtils'; -import { bindCreateFixtures } from '../../../utils/vitest/createFixtures'; +import { render, waitFor } from '../../../../testUtils'; +import { bindCreateFixtures } from '../../../utils/test/createFixtures'; import { PricingTable } from '..'; const { createFixtures } = bindCreateFixtures('PricingTable'); @@ -39,9 +37,9 @@ describe('PricingTable - trial info', () => { features: [] as any[], freeTrialEnabled: true, freeTrialDays: 14, - __internal_toSnapshot: vi.fn(), + __internal_toSnapshot: jest.fn(), pathRoot: '', - reload: vi.fn(), + reload: jest.fn(), } as const; it('shows footer notice with trial end date when active subscription is in free trial', async () => { @@ -75,13 +73,13 @@ describe('PricingTable - trial info', () => { planPeriod: 'month' as const, status: 'active' as const, isFreeTrial: true, - cancel: vi.fn(), + cancel: jest.fn(), pathRoot: '', - reload: vi.fn(), + reload: jest.fn(), }, ], pathRoot: '', - reload: vi.fn(), + reload: jest.fn(), }); const { findByRole, getByText, userEvent } = render(, { wrapper }); @@ -121,7 +119,7 @@ describe('PricingTable - trial info', () => { // No subscription items for the trial plan yet subscriptionItems: [], pathRoot: '', - reload: vi.fn(), + reload: jest.fn(), }); const { getByRole, getByText } = render(, { wrapper }); @@ -220,13 +218,13 @@ describe('PricingTable - trial info', () => { planPeriod: 'month' as const, status: 'upcoming' as const, isFreeTrial: false, - cancel: vi.fn(), + cancel: jest.fn(), pathRoot: '', - reload: vi.fn(), + reload: jest.fn(), }, ], pathRoot: '', - reload: vi.fn(), + reload: jest.fn(), }); const { findByRole, getByText, userEvent } = render(, { wrapper }); @@ -278,9 +276,9 @@ describe('PricingTable - plans visibility', () => { features: [] as any[], freeTrialEnabled: false, freeTrialDays: 0, - __internal_toSnapshot: vi.fn(), + __internal_toSnapshot: jest.fn(), pathRoot: '', - reload: vi.fn(), + reload: jest.fn(), } as const; it('shows no plans when user is signed in but has no subscription', async () => { @@ -296,7 +294,7 @@ describe('PricingTable - plans visibility', () => { fixtures.clerk.billing.getSubscription.mockResolvedValue({ subscriptionItems: [], pathRoot: '', - reload: vi.fn(), + reload: jest.fn(), } as any); const { queryByRole } = render(, { wrapper }); @@ -339,13 +337,13 @@ describe('PricingTable - plans visibility', () => { planPeriod: 'month' as const, status: 'active' as const, isFreeTrial: false, - cancel: vi.fn(), + cancel: jest.fn(), pathRoot: '', - reload: vi.fn(), + reload: jest.fn(), }, ], pathRoot: '', - reload: vi.fn(), + reload: jest.fn(), }); const { getByRole } = render(, { wrapper }); @@ -465,13 +463,13 @@ describe('PricingTable - plans visibility', () => { planPeriod: 'month' as const, status: 'active' as const, isFreeTrial: false, - cancel: vi.fn(), + cancel: jest.fn(), pathRoot: '', - reload: vi.fn(), + reload: jest.fn(), }, ], pathRoot: '', - reload: vi.fn(), + reload: jest.fn(), }); // Assert the plan heading appears after subscription resolves diff --git a/packages/clerk-js/src/ui/components/SessionTasks/tasks/TaskChooseOrganization/__tests__/TaskChooseOrganization.test.tsx b/packages/clerk-js/src/ui/components/SessionTasks/tasks/TaskChooseOrganization/__tests__/TaskChooseOrganization.test.tsx index bc9a19ff4c7..92b5ab6852a 100644 --- a/packages/clerk-js/src/ui/components/SessionTasks/tasks/TaskChooseOrganization/__tests__/TaskChooseOrganization.test.tsx +++ b/packages/clerk-js/src/ui/components/SessionTasks/tasks/TaskChooseOrganization/__tests__/TaskChooseOrganization.test.tsx @@ -1,27 +1,16 @@ +import { describe } from '@jest/globals'; import userEvent from '@testing-library/user-event'; -import { describe, expect, it } from 'vitest'; +import { bindCreateFixtures, render, waitFor } from '@/testUtils'; import { createFakeUserOrganizationMembership } from '@/ui/components/OrganizationSwitcher/__tests__/utlis'; -import { TaskChooseOrganizationContext } from '@/ui/contexts/components/SessionTasks'; -import { bindCreateFixtures, render, waitFor } from '@/vitestUtils'; import { TaskChooseOrganization } from '..'; const { createFixtures } = bindCreateFixtures('TaskChooseOrganization'); -const TaskChooseOrganizationWithContext = ({ - redirectUrlComplete = '/dashboard', -}: { - redirectUrlComplete?: string; -}) => ( - - - -); - describe('TaskChooseOrganization', () => { it('does not render component without existing session task', async () => { - const { wrapper, props } = await createFixtures(f => { + const { wrapper } = await createFixtures(f => { f.withOrganizations(); f.withForceOrganizationSelection(); f.withUser({ @@ -30,9 +19,7 @@ describe('TaskChooseOrganization', () => { }); }); - props.setProps({ redirectUrlComplete: '/dashboard' }); - - const { queryByText, queryByRole } = render(, { wrapper }); + const { queryByText, queryByRole } = render(, { wrapper }); await waitFor(() => { expect(queryByText('Setup your organization')).not.toBeInTheDocument(); @@ -42,7 +29,7 @@ describe('TaskChooseOrganization', () => { }); it('renders component when session task exists', async () => { - const { wrapper, props } = await createFixtures(f => { + const { wrapper } = await createFixtures(f => { f.withOrganizations(); f.withForceOrganizationSelection(); f.withUser({ @@ -52,9 +39,7 @@ describe('TaskChooseOrganization', () => { }); }); - props.setProps({ redirectUrlComplete: '/dashboard' }); - - const { getByText, getByRole } = render(, { wrapper }); + const { getByText, getByRole } = render(, { wrapper }); await waitFor(() => { expect(getByText('Setup your organization')).toBeInTheDocument(); @@ -64,7 +49,7 @@ describe('TaskChooseOrganization', () => { }); it('shows create organization screen when user has no existing resources', async () => { - const { wrapper, props } = await createFixtures(f => { + const { wrapper } = await createFixtures(f => { f.withOrganizations(); f.withForceOrganizationSelection(); f.withUser({ @@ -74,9 +59,7 @@ describe('TaskChooseOrganization', () => { }); }); - props.setProps({ redirectUrlComplete: '/dashboard' }); - - const { getByRole, getByText } = render(, { wrapper }); + const { getByRole, getByText } = render(, { wrapper }); await waitFor(() => { expect(getByRole('textbox', { name: /name/i })).toBeInTheDocument(); @@ -85,7 +68,7 @@ describe('TaskChooseOrganization', () => { }); it('shows choose organization screen when user has existing organizations', async () => { - const { wrapper, fixtures, props } = await createFixtures(f => { + const { wrapper, fixtures } = await createFixtures(f => { f.withOrganizations(); f.withForceOrganizationSelection(); f.withUser({ @@ -95,9 +78,7 @@ describe('TaskChooseOrganization', () => { }); }); - props.setProps({ redirectUrlComplete: '/dashboard' }); - - (fixtures.clerk.user?.getOrganizationMemberships as any).mockReturnValue( + fixtures.clerk.user?.getOrganizationMemberships.mockReturnValueOnce( Promise.resolve({ data: [ createFakeUserOrganizationMembership({ @@ -117,7 +98,7 @@ describe('TaskChooseOrganization', () => { }), ); - const { getByText, queryByRole } = render(, { wrapper }); + const { getByText, queryByRole } = render(, { wrapper }); await waitFor(() => { expect(getByText('Existing Org')).toBeInTheDocument(); @@ -127,7 +108,7 @@ describe('TaskChooseOrganization', () => { }); it('allows switching between choose and create organization screens', async () => { - const { wrapper, fixtures, props } = await createFixtures(f => { + const { wrapper, fixtures } = await createFixtures(f => { f.withOrganizations(); f.withForceOrganizationSelection(); f.withUser({ @@ -137,9 +118,7 @@ describe('TaskChooseOrganization', () => { }); }); - props.setProps({ redirectUrlComplete: '/dashboard' }); - - (fixtures.clerk.user?.getOrganizationMemberships as any).mockReturnValue( + fixtures.clerk.user?.getOrganizationMemberships.mockReturnValueOnce( Promise.resolve({ data: [ createFakeUserOrganizationMembership({ @@ -159,9 +138,7 @@ describe('TaskChooseOrganization', () => { }), ); - const { getByText, getByRole, queryByRole, queryByText } = render(, { - wrapper, - }); + const { getByText, getByRole, queryByRole, queryByText } = render(, { wrapper }); await waitFor(() => { expect(getByText('Existing Org')).toBeInTheDocument(); @@ -190,7 +167,7 @@ describe('TaskChooseOrganization', () => { }); it('displays user identifier in sign out section', async () => { - const { wrapper, props } = await createFixtures(f => { + const { wrapper } = await createFixtures(f => { f.withOrganizations(); f.withForceOrganizationSelection(); f.withUser({ @@ -200,9 +177,7 @@ describe('TaskChooseOrganization', () => { }); }); - props.setProps({ redirectUrlComplete: '/dashboard' }); - - const { getByText } = render(, { wrapper }); + const { getByText } = render(, { wrapper }); await waitFor(() => { expect(getByText(/user@test\.com/)).toBeInTheDocument(); @@ -211,7 +186,7 @@ describe('TaskChooseOrganization', () => { }); it('handles sign out correctly', async () => { - const { wrapper, fixtures, props } = await createFixtures(f => { + const { wrapper, fixtures } = await createFixtures(f => { f.withOrganizations(); f.withForceOrganizationSelection(); f.withUser({ @@ -221,9 +196,7 @@ describe('TaskChooseOrganization', () => { }); }); - props.setProps({ redirectUrlComplete: '/dashboard' }); - - const { getByRole } = render(, { wrapper }); + const { getByRole } = render(, { wrapper }); const signOutButton = getByRole('link', { name: /sign out/i }); await userEvent.click(signOutButton); @@ -232,7 +205,7 @@ describe('TaskChooseOrganization', () => { }); it('renders with username when email is not available', async () => { - const { wrapper, props } = await createFixtures(f => { + const { wrapper } = await createFixtures(f => { f.withOrganizations(); f.withForceOrganizationSelection(); f.withUser({ @@ -242,9 +215,7 @@ describe('TaskChooseOrganization', () => { }); }); - props.setProps({ redirectUrlComplete: '/dashboard' }); - - const { getByText } = render(, { wrapper }); + const { getByText } = render(, { wrapper }); expect(getByText(/testuser/)).toBeInTheDocument(); }); diff --git a/packages/clerk-js/src/ui/components/SignIn/SignInFactorTwoCodeForm.tsx b/packages/clerk-js/src/ui/components/SignIn/SignInFactorTwoCodeForm.tsx index 6909c62a40a..4b06c83ed75 100644 --- a/packages/clerk-js/src/ui/components/SignIn/SignInFactorTwoCodeForm.tsx +++ b/packages/clerk-js/src/ui/components/SignIn/SignInFactorTwoCodeForm.tsx @@ -49,11 +49,8 @@ export const SignInFactorTwoCodeForm = (props: SignInFactorTwoCodeFormProps) => const prepare = props.prepare ? () => { - const prepareResult = props.prepare?.(); - if (!prepareResult) { - return Promise.resolve(); - } - return prepareResult + return props + .prepare?.() .then(() => props.onFactorPrepare()) .catch(err => { if (isUserLockedError(err)) { diff --git a/packages/clerk-js/src/ui/components/SignIn/__tests__/ResetPassword.test.tsx b/packages/clerk-js/src/ui/components/SignIn/__tests__/ResetPassword.test.tsx index cc21b4bb0d8..e5c97aa1048 100644 --- a/packages/clerk-js/src/ui/components/SignIn/__tests__/ResetPassword.test.tsx +++ b/packages/clerk-js/src/ui/components/SignIn/__tests__/ResetPassword.test.tsx @@ -1,8 +1,9 @@ import type { SignInResource } from '@clerk/types'; -import { describe, expect, it } from 'vitest'; +import { describe, it } from '@jest/globals'; -import { fireEvent, render, screen, waitFor } from '../../../../vitestUtils'; -import { bindCreateFixtures } from '../../../utils/vitest/createFixtures'; +import { fireEvent, render, screen, waitFor } from '../../../../testUtils'; +import { bindCreateFixtures } from '../../../utils/test/createFixtures'; +import { runFakeTimers } from '../../../utils/test/runFakeTimers'; import { ResetPassword } from '../ResetPassword'; const { createFixtures } = bindCreateFixtures('SignIn'); @@ -31,13 +32,15 @@ describe('ResetPassword', () => { }), ); - render(, { wrapper }); - screen.getByRole('heading', { name: /Set new password/i }); + await runFakeTimers(async () => { + render(, { wrapper }); + screen.getByRole('heading', { name: /Set new password/i }); - const passwordField = screen.getByLabelText(/New password/i); - fireEvent.focus(passwordField); - await waitFor(() => { - screen.getByText(/Your password must contain 8 or more characters/i); + const passwordField = screen.getByLabelText(/New password/i); + fireEvent.focus(passwordField); + await waitFor(() => { + screen.getByText(/Your password must contain 8 or more characters/i); + }); }); }); @@ -111,20 +114,22 @@ describe('ResetPassword', () => { it('results in error if the passwords do not match and persists', async () => { const { wrapper } = await createFixtures(); - const { userEvent } = render(, { wrapper }); + await runFakeTimers(async () => { + const { userEvent } = render(, { wrapper }); - await userEvent.type(screen.getByLabelText(/new password/i), 'testewrewr'); - const confirmField = screen.getByLabelText(/confirm password/i); - await userEvent.type(confirmField, 'testrwerrwqrwe'); - await waitFor(() => { - expect(screen.getByText(`Passwords don't match.`)).toBeInTheDocument(); - }); + await userEvent.type(screen.getByLabelText(/new password/i), 'testewrewr'); + const confirmField = screen.getByLabelText(/confirm password/i); + await userEvent.type(confirmField, 'testrwerrwqrwe'); + await waitFor(() => { + expect(screen.getByText(`Passwords don't match.`)).toBeInTheDocument(); + }); - await userEvent.clear(confirmField); - await waitFor(() => { - expect(screen.getByText(`Passwords don't match.`)).toBeInTheDocument(); + await userEvent.clear(confirmField); + await waitFor(() => { + expect(screen.getByText(`Passwords don't match.`)).toBeInTheDocument(); + }); }); - }); + }, 10000); it('navigates to the root page upon pressing the back link', async () => { const { wrapper, fixtures } = await createFixtures(); diff --git a/packages/clerk-js/src/ui/components/SignIn/__tests__/ResetPasswordSuccess.test.tsx b/packages/clerk-js/src/ui/components/SignIn/__tests__/ResetPasswordSuccess.test.tsx index 9e0e654724d..1836303ab19 100644 --- a/packages/clerk-js/src/ui/components/SignIn/__tests__/ResetPasswordSuccess.test.tsx +++ b/packages/clerk-js/src/ui/components/SignIn/__tests__/ResetPasswordSuccess.test.tsx @@ -1,7 +1,8 @@ -import { describe, expect, it, vi } from 'vitest'; +import { describe, it } from '@jest/globals'; -import { render, screen } from '../../../../vitestUtils'; -import { bindCreateFixtures } from '../../../utils/vitest/createFixtures'; +import { render, screen } from '../../../../testUtils'; +import { bindCreateFixtures } from '../../../utils/test/createFixtures'; +import { runFakeTimers } from '../../../utils/test/runFakeTimers'; import { ResetPasswordSuccess } from '../ResetPasswordSuccess'; const { createFixtures: createFixturesWithQuery } = bindCreateFixtures('SignIn', { @@ -23,27 +24,21 @@ describe('ResetPasswordSuccess', () => { it('sets active session after 2000 ms', async () => { const { wrapper, fixtures } = await createFixturesWithQuery(); - vi.useFakeTimers(); - try { + runFakeTimers(timers => { render(, { wrapper }); - vi.advanceTimersByTime(1000); + timers.advanceTimersByTime(1000); expect(fixtures.clerk.setActive).not.toHaveBeenCalled(); - vi.advanceTimersByTime(1000); + timers.advanceTimersByTime(1000); expect(fixtures.clerk.setActive).toHaveBeenCalled(); - } finally { - vi.useRealTimers(); - } + }); }); it('does not set a session if createdSessionId is missing', async () => { const { wrapper, fixtures } = await createFixtures(); - vi.useFakeTimers(); - try { + runFakeTimers(timers => { render(, { wrapper }); - vi.advanceTimersByTime(2000); + timers.advanceTimersByTime(2000); expect(fixtures.clerk.setActive).not.toHaveBeenCalled(); - } finally { - vi.useRealTimers(); - } + }); }); }); diff --git a/packages/clerk-js/src/ui/components/SignIn/__tests__/SignInAccountSwitcher.test.tsx b/packages/clerk-js/src/ui/components/SignIn/__tests__/SignInAccountSwitcher.test.tsx index f073c2228da..29f4610249b 100644 --- a/packages/clerk-js/src/ui/components/SignIn/__tests__/SignInAccountSwitcher.test.tsx +++ b/packages/clerk-js/src/ui/components/SignIn/__tests__/SignInAccountSwitcher.test.tsx @@ -1,7 +1,7 @@ -import { describe, expect, it } from 'vitest'; +import { describe, it } from '@jest/globals'; -import { render } from '../../../../vitestUtils'; -import { bindCreateFixtures } from '../../../utils/vitest/createFixtures'; +import { render } from '../../../../testUtils'; +import { bindCreateFixtures } from '../../../utils/test/createFixtures'; import { SignInAccountSwitcher } from '../SignInAccountSwitcher'; const { createFixtures } = bindCreateFixtures('SignIn'); diff --git a/packages/clerk-js/src/ui/components/SignIn/__tests__/SignInFactorOne.test.tsx b/packages/clerk-js/src/ui/components/SignIn/__tests__/SignInFactorOne.test.tsx index eafc4e98d1c..ccb475182e4 100644 --- a/packages/clerk-js/src/ui/components/SignIn/__tests__/SignInFactorOne.test.tsx +++ b/packages/clerk-js/src/ui/components/SignIn/__tests__/SignInFactorOne.test.tsx @@ -1,11 +1,12 @@ import { parseError } from '@clerk/shared/error'; import type { SignInResource } from '@clerk/types'; +import { describe, it, jest } from '@jest/globals'; import { waitFor } from '@testing-library/dom'; -import { describe, expect, it, vi } from 'vitest'; import { ClerkAPIResponseError } from '../../../../core/resources'; -import { act, mockWebAuthn, render, screen } from '../../../../vitestUtils'; -import { bindCreateFixtures } from '../../../utils/vitest/createFixtures'; +import { act, mockWebAuthn, render, screen } from '../../../../testUtils'; +import { bindCreateFixtures } from '../../../utils/test/createFixtures'; +import { runFakeTimers } from '../../../utils/test/runFakeTimers'; import { SignInFactorOne } from '../SignInFactorOne'; const { createFixtures } = bindCreateFixtures('SignIn'); @@ -82,10 +83,13 @@ describe('SignInFactorOne', () => { fixtures.signIn.attemptFirstFactor.mockReturnValueOnce( Promise.resolve({ status: 'needs_second_factor' } as SignInResource), ); - const { userEvent } = render(, { wrapper }); + await runFakeTimers(async timers => { + const { userEvent } = render(, { wrapper }); - await userEvent.type(screen.getByLabelText(/Enter verification code/i), '123456'); - await waitFor(() => expect(fixtures.router.navigate).toHaveBeenCalledWith('../factor-two')); + await userEvent.type(screen.getByLabelText(/Enter verification code/i), '123456'); + timers.runOnlyPendingTimers(); + await waitFor(() => expect(fixtures.router.navigate).toHaveBeenCalledWith('../factor-two')); + }); }); it('sets an active session when user submits first factor successfully and second factor does not exist', async () => { @@ -96,11 +100,14 @@ describe('SignInFactorOne', () => { }); fixtures.signIn.prepareFirstFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource)); fixtures.signIn.attemptFirstFactor.mockReturnValueOnce(Promise.resolve({ status: 'complete' } as SignInResource)); - const { userEvent } = render(, { wrapper }); + await runFakeTimers(async timers => { + const { userEvent } = render(, { wrapper }); - await userEvent.type(screen.getByLabelText(/Enter verification code/i), '123456'); - await waitFor(() => { - expect(fixtures.clerk.setActive).toHaveBeenCalled(); + await userEvent.type(screen.getByLabelText(/Enter verification code/i), '123456'); + timers.runOnlyPendingTimers(); + await waitFor(() => { + expect(fixtures.clerk.setActive).toHaveBeenCalled(); + }); }); }); }); @@ -133,12 +140,10 @@ describe('SignInFactorOne', () => { }); const { userEvent } = render(, { wrapper }); await userEvent.click(screen.getByText(/Forgot password/i)); - await waitFor(() => { - screen.getByText('Use another method'); - expect(screen.queryByText('Or, sign in with another method')).not.toBeInTheDocument(); - screen.getByText(`Email code to ${email}`); - expect(screen.queryByText('Sign in with your password')).not.toBeInTheDocument(); - }); + screen.getByText('Use another method'); + expect(screen.queryByText('Or, sign in with another method')).not.toBeInTheDocument(); + screen.getByText(`Email code to ${email}`); + expect(screen.queryByText('Sign in with your password')).not.toBeInTheDocument(); }); it('should render the Forgot Password alternative methods component when clicking on "Forgot password" (email)', async () => { @@ -156,14 +161,10 @@ describe('SignInFactorOne', () => { fixtures.signIn.prepareFirstFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource)); await userEvent.click(screen.getByText(/Forgot password/i)); - await waitFor(() => { - screen.getByText('Forgot Password?'); - screen.getByText('Or, sign in with another method'); - }); + screen.getByText('Forgot Password?'); + screen.getByText('Or, sign in with another method'); await userEvent.click(screen.getByText('Reset your password')); - await waitFor(() => { - screen.getByText('First, enter the code sent to your email address'); - }); + screen.getByText('First, enter the code sent to your email address'); }); it('shows a UI error when submission fails', async () => { @@ -187,10 +188,12 @@ describe('SignInFactorOne', () => { status: 422, }), ); - const { userEvent } = render(, { wrapper }); - await userEvent.type(screen.getByLabelText('Password'), '123456'); - await userEvent.click(screen.getByText('Continue')); - await waitFor(() => expect(screen.getByText('Incorrect Password')).toBeInTheDocument()); + await runFakeTimers(async () => { + const { userEvent } = render(, { wrapper }); + await userEvent.type(screen.getByLabelText('Password'), '123456'); + await userEvent.click(screen.getByText('Continue')); + await waitFor(() => expect(screen.getByText('Incorrect Password')).toBeDefined()); + }); }); it('redirects back to sign-in if the user is locked', async () => { @@ -215,11 +218,13 @@ describe('SignInFactorOne', () => { status: 422, }), ); - const { userEvent } = render(, { wrapper }); - await userEvent.type(screen.getByLabelText('Password'), '123456'); - await userEvent.click(screen.getByText('Continue')); - await waitFor(() => { - expect(fixtures.clerk.__internal_navigateWithError).toHaveBeenCalledWith('..', parseError(errJSON)); + await runFakeTimers(async () => { + const { userEvent } = render(, { wrapper }); + await userEvent.type(screen.getByLabelText('Password'), '123456'); + await userEvent.click(screen.getByText('Continue')); + await waitFor(() => { + expect(fixtures.clerk.__internal_navigateWithError).toHaveBeenCalledWith('..', parseError(errJSON)); + }); }); }); @@ -251,20 +256,20 @@ describe('SignInFactorOne', () => { }), ); - const { userEvent } = render(, { wrapper }); - await userEvent.type(screen.getByLabelText('Password'), '123456'); - await userEvent.click(screen.getByText('Continue')); + await runFakeTimers(async () => { + const { userEvent } = render(, { wrapper }); + await userEvent.type(screen.getByLabelText('Password'), '123456'); + await userEvent.click(screen.getByText('Continue')); - await waitFor(() => { - screen.getByText('Password compromised'); - screen.getByText( - 'This password has been found as part of a breach and can not be used, please reset your password.', - ); - screen.getByText('Or, sign in with another method'); - }); + await waitFor(() => { + screen.getByText('Password compromised'); + screen.getByText( + 'This password has been found as part of a breach and can not be used, please reset your password.', + ); + screen.getByText('Or, sign in with another method'); + }); - await userEvent.click(screen.getByText('Reset your password')); - await waitFor(() => { + await userEvent.click(screen.getByText('Reset your password')); screen.getByText('First, enter the code sent to your email address'); }); }); @@ -297,20 +302,20 @@ describe('SignInFactorOne', () => { }), ); - const { userEvent } = render(, { wrapper }); - await userEvent.type(screen.getByLabelText('Password'), '123456'); - await userEvent.click(screen.getByText('Continue')); + await runFakeTimers(async () => { + const { userEvent } = render(, { wrapper }); + await userEvent.type(screen.getByLabelText('Password'), '123456'); + await userEvent.click(screen.getByText('Continue')); - await waitFor(() => { - screen.getByText('Password compromised'); - screen.getByText( - 'This password has been found as part of a breach and can not be used, please reset your password.', - ); - screen.getByText('Or, sign in with another method'); - }); + await waitFor(() => { + screen.getByText('Password compromised'); + screen.getByText( + 'This password has been found as part of a breach and can not be used, please reset your password.', + ); + screen.getByText('Or, sign in with another method'); + }); - await userEvent.click(screen.getByText('Reset your password')); - await waitFor(() => { + await userEvent.click(screen.getByText('Reset your password')); screen.getByText('First, enter the code sent to your phone'); }); }); @@ -343,24 +348,24 @@ describe('SignInFactorOne', () => { }), ); - const { userEvent } = render(, { wrapper }); - await userEvent.type(screen.getByLabelText('Password'), '123456'); - await userEvent.click(screen.getByText('Continue')); + await runFakeTimers(async () => { + const { userEvent } = render(, { wrapper }); + await userEvent.type(screen.getByLabelText('Password'), '123456'); + await userEvent.click(screen.getByText('Continue')); - await waitFor(() => { - screen.getByText('Password compromised'); - screen.getByText( - 'This password has been found as part of a breach and can not be used, please reset your password.', - ); - screen.getByText('Or, sign in with another method'); - }); + await waitFor(() => { + screen.getByText('Password compromised'); + screen.getByText( + 'This password has been found as part of a breach and can not be used, please reset your password.', + ); + screen.getByText('Or, sign in with another method'); + }); - // Go back - await userEvent.click(screen.getByText('Back')); + // Go back + await userEvent.click(screen.getByText('Back')); - // Choose to reset password via "Forgot password" instead - await userEvent.click(screen.getByText(/Forgot password/i)); - await waitFor(() => { + // Choose to reset password via "Forgot password" instead + await userEvent.click(screen.getByText(/Forgot password/i)); screen.getByText('Forgot Password?'); expect( screen.queryByText( @@ -385,14 +390,10 @@ describe('SignInFactorOne', () => { fixtures.signIn.prepareFirstFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource)); const { userEvent } = render(, { wrapper }); await userEvent.click(screen.getByText(/Forgot password/i)); - await waitFor(() => { - screen.getByText('Forgot Password?'); - }); + screen.getByText('Forgot Password?'); await userEvent.click(screen.getByText('Reset your password')); - await waitFor(() => { - screen.getByText('First, enter the code sent to your phone'); - }); + screen.getByText('First, enter the code sent to your phone'); }); it('redirects to `reset-password` on successful code verification', async () => { @@ -463,8 +464,8 @@ describe('SignInFactorOne', () => { fixtures.signIn.createEmailLinkFlow.mockImplementation( () => ({ - startEmailLinkFlow: vi.fn(() => new Promise(() => ({}))), - cancelEmailLinkFlow: vi.fn(() => new Promise(() => ({}))), + startEmailLinkFlow: jest.fn(() => new Promise(() => ({}))), + cancelEmailLinkFlow: jest.fn(() => new Promise(() => ({}))), }) as any, ); @@ -473,7 +474,7 @@ describe('SignInFactorOne', () => { }); it('enables the "Resend link" button after 60 seconds', async () => { - vi.useFakeTimers(); + jest.useFakeTimers(); const { wrapper, fixtures } = await createFixtures(f => { f.withEmailAddress(); @@ -485,23 +486,23 @@ describe('SignInFactorOne', () => { fixtures.signIn.createEmailLinkFlow.mockImplementation( () => ({ - startEmailLinkFlow: vi.fn(() => new Promise(() => ({}))), - cancelEmailLinkFlow: vi.fn(() => new Promise(() => ({}))), + startEmailLinkFlow: jest.fn(() => new Promise(() => ({}))), + cancelEmailLinkFlow: jest.fn(() => new Promise(() => ({}))), }) as any, ); const { getByText } = render(, { wrapper }); expect(getByText(/Resend/, { exact: false }).closest('button')).toHaveAttribute('disabled'); await act(() => { - vi.advanceTimersByTime(30000); + jest.advanceTimersByTime(30000); }); expect(getByText(/Resend/, { exact: false }).closest('button')).toHaveAttribute('disabled'); await act(() => { - vi.advanceTimersByTime(30000); + jest.advanceTimersByTime(30000); }); expect(getByText(/Resend/, { exact: false }).closest('button')).not.toHaveAttribute('disabled'); - vi.useRealTimers(); + jest.useRealTimers(); }); }); @@ -519,7 +520,7 @@ describe('SignInFactorOne', () => { }); it('enables the "Resend code" button after 30 seconds', async () => { - vi.useFakeTimers(); + jest.useFakeTimers(); const { wrapper, fixtures } = await createFixtures(f => { f.withEmailAddress(); @@ -532,15 +533,15 @@ describe('SignInFactorOne', () => { const { getByText } = render(, { wrapper }); expect(getByText(/Resend/, { exact: false }).closest('button')).toHaveAttribute('disabled'); await act(() => { - vi.advanceTimersByTime(15000); + jest.advanceTimersByTime(15000); }); expect(getByText(/Resend/, { exact: false }).closest('button')).toHaveAttribute('disabled'); await act(() => { - vi.advanceTimersByTime(15000); + jest.advanceTimersByTime(15000); }); expect(getByText(/Resend/, { exact: false }).closest('button')).not.toHaveAttribute('disabled'); - vi.useRealTimers(); + jest.useRealTimers(); }); it('auto submits when typing all the 6 digits of the code', async () => { @@ -578,9 +579,11 @@ describe('SignInFactorOne', () => { status: 422, }), ); - const { userEvent } = render(, { wrapper }); - await userEvent.type(screen.getByLabelText(/Enter verification code/i), '123456'); - await waitFor(() => expect(screen.getByText('Incorrect code')).toBeDefined()); + await runFakeTimers(async () => { + const { userEvent } = render(, { wrapper }); + await userEvent.type(screen.getByLabelText(/Enter verification code/i), '123456'); + await waitFor(() => expect(screen.getByText('Incorrect code')).toBeDefined()); + }); }); it('redirects back to sign-in if the user is locked', async () => { @@ -605,9 +608,11 @@ describe('SignInFactorOne', () => { }), ); - const { userEvent } = render(, { wrapper }); - await userEvent.type(screen.getByLabelText(/Enter verification code/i), '123456'); - expect(fixtures.clerk.__internal_navigateWithError).toHaveBeenCalledWith('..', parseError(errJSON)); + await runFakeTimers(async () => { + const { userEvent } = render(, { wrapper }); + await userEvent.type(screen.getByLabelText(/Enter verification code/i), '123456'); + expect(fixtures.clerk.__internal_navigateWithError).toHaveBeenCalledWith('..', parseError(errJSON)); + }); }); }); @@ -625,7 +630,7 @@ describe('SignInFactorOne', () => { }); it('enables the "Resend" button after 30 seconds', async () => { - vi.useFakeTimers(); + jest.useFakeTimers(); const { wrapper, fixtures } = await createFixtures(f => { f.withPhoneNumber(); @@ -637,15 +642,15 @@ describe('SignInFactorOne', () => { const { getByText } = render(, { wrapper }); expect(getByText(/Resend/, { exact: false }).closest('button')).toHaveAttribute('disabled'); await act(() => { - vi.advanceTimersByTime(15000); + jest.advanceTimersByTime(15000); }); expect(getByText(/Resend/, { exact: false }).closest('button')).toHaveAttribute('disabled'); await act(() => { - vi.advanceTimersByTime(15000); + jest.advanceTimersByTime(15000); }); expect(getByText(/Resend/, { exact: false }).closest('button')).not.toHaveAttribute('disabled'); - vi.useRealTimers(); + jest.useRealTimers(); }); it('auto submits when typing all the 6 digits of the code', async () => { @@ -683,9 +688,11 @@ describe('SignInFactorOne', () => { status: 422, }), ); - const { userEvent } = render(, { wrapper }); - await userEvent.type(screen.getByLabelText(/Enter verification code/i), '123456'); - await waitFor(() => expect(screen.getByText('Incorrect phone code')).toBeDefined()); + await runFakeTimers(async () => { + const { userEvent } = render(, { wrapper }); + await userEvent.type(screen.getByLabelText(/Enter verification code/i), '123456'); + await waitFor(() => expect(screen.getByText('Incorrect phone code')).toBeDefined()); + }); }); it('redirects back to sign-in if the user is locked', async () => { @@ -709,10 +716,12 @@ describe('SignInFactorOne', () => { }), ); - const { userEvent } = render(, { wrapper }); - await userEvent.type(screen.getByLabelText(/Enter verification code/i), '123456'); - await waitFor(() => { - expect(fixtures.clerk.__internal_navigateWithError).toHaveBeenCalledWith('..', parseError(errJSON)); + await runFakeTimers(async () => { + const { userEvent } = render(, { wrapper }); + await userEvent.type(screen.getByLabelText(/Enter verification code/i), '123456'); + await waitFor(() => { + expect(fixtures.clerk.__internal_navigateWithError).toHaveBeenCalledWith('..', parseError(errJSON)); + }); }); }); }); @@ -918,8 +927,8 @@ describe('SignInFactorOne', () => { fixtures.signIn.createEmailLinkFlow.mockImplementation( () => ({ - startEmailLinkFlow: vi.fn(() => new Promise(() => ({}))), - cancelEmailLinkFlow: vi.fn(() => new Promise(() => ({}))), + startEmailLinkFlow: jest.fn(() => new Promise(() => ({}))), + cancelEmailLinkFlow: jest.fn(() => new Promise(() => ({}))), }) as any, ); const { userEvent } = render(, { wrapper }); @@ -940,8 +949,8 @@ describe('SignInFactorOne', () => { }); fixtures.signIn.prepareFirstFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource)); fixtures.signIn.createEmailLinkFlow.mockReturnValue({ - startEmailLinkFlow: vi.fn(() => new Promise(() => ({}))), - cancelEmailLinkFlow: vi.fn(() => new Promise(() => ({}))), + startEmailLinkFlow: jest.fn(() => new Promise(() => ({}))), + cancelEmailLinkFlow: jest.fn(() => new Promise(() => ({}))), } as any); const { userEvent } = render(, { wrapper }); await userEvent.click(screen.getByText('Use another method')); @@ -960,8 +969,8 @@ describe('SignInFactorOne', () => { }); fixtures.signIn.prepareFirstFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource)); fixtures.signIn.createEmailLinkFlow.mockReturnValue({ - startEmailLinkFlow: vi.fn(() => new Promise(() => ({}))), - cancelEmailLinkFlow: vi.fn(() => new Promise(() => ({}))), + startEmailLinkFlow: jest.fn(() => new Promise(() => ({}))), + cancelEmailLinkFlow: jest.fn(() => new Promise(() => ({}))), } as any); const { userEvent } = render(, { wrapper }); await userEvent.click(screen.getByText('Use another method')); @@ -1007,8 +1016,8 @@ describe('SignInFactorOne', () => { await userEvent.click(screen.getByText('Use another method')); await userEvent.click(screen.getByText('Get help')); - const assignMock = vi.fn(); - const mockResponse = vi.fn(); + const assignMock = jest.fn(); + const mockResponse = jest.fn(); Object.defineProperty(window, 'location', { value: { set href(_) { diff --git a/packages/clerk-js/src/ui/components/SignIn/__tests__/SignInFactorOneCodeForm.test.tsx b/packages/clerk-js/src/ui/components/SignIn/__tests__/SignInFactorOneCodeForm.spec.tsx similarity index 100% rename from packages/clerk-js/src/ui/components/SignIn/__tests__/SignInFactorOneCodeForm.test.tsx rename to packages/clerk-js/src/ui/components/SignIn/__tests__/SignInFactorOneCodeForm.spec.tsx diff --git a/packages/clerk-js/src/ui/components/SignIn/__tests__/SignInFactorTwo.test.tsx b/packages/clerk-js/src/ui/components/SignIn/__tests__/SignInFactorTwo.test.tsx index e330b41417c..12dbce2c3a3 100644 --- a/packages/clerk-js/src/ui/components/SignIn/__tests__/SignInFactorTwo.test.tsx +++ b/packages/clerk-js/src/ui/components/SignIn/__tests__/SignInFactorTwo.test.tsx @@ -1,10 +1,11 @@ import { parseError } from '@clerk/shared/error'; import type { SignInResource } from '@clerk/types'; -import { describe, expect, it, vi } from 'vitest'; +import { describe, it, jest } from '@jest/globals'; import { ClerkAPIResponseError } from '../../../../core/resources'; -import { render, screen, waitFor } from '../../../../vitestUtils'; -import { bindCreateFixtures } from '../../../utils/vitest/createFixtures'; +import { render, screen, waitFor } from '../../../../testUtils'; +import { bindCreateFixtures } from '../../../utils/test/createFixtures'; +import { runFakeTimers } from '../../../utils/test/runFakeTimers'; import { SignInFactorTwo } from '../SignInFactorTwo'; const { createFixtures } = bindCreateFixtures('SignIn'); @@ -57,11 +58,14 @@ describe('SignInFactorTwo', () => { fixtures.signIn.attemptSecondFactor.mockReturnValueOnce( Promise.resolve({ status: 'complete' } as SignInResource), ); - const { userEvent } = render(, { wrapper }); + await runFakeTimers(async timers => { + const { userEvent } = render(, { wrapper }); - await userEvent.type(screen.getByLabelText(/Enter verification code/i), '123456'); - await waitFor(() => { - expect(fixtures.clerk.setActive).toHaveBeenCalled(); + await userEvent.type(screen.getByLabelText(/Enter verification code/i), '123456'); + timers.runOnlyPendingTimers(); + await waitFor(() => { + expect(fixtures.clerk.setActive).toHaveBeenCalled(); + }); }); }); @@ -121,11 +125,16 @@ describe('SignInFactorTwo', () => { f.startSignInFactorTwo({ identifier: '+3012345567890', supportPhoneCode: true, supportTotp: false }); }); - fixtures.signIn.prepareSecondFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource)); - const { getByText } = render(, { wrapper }); - expect(getByText(/Resend/, { exact: false }).closest('button')).toHaveAttribute('disabled'); - // Note: Timer functionality is tested in the TimerButton component itself - // This test verifies the initial disabled state is correct + runFakeTimers(timers => { + fixtures.signIn.prepareSecondFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource)); + const { getByText } = render(, { wrapper }); + expect(getByText(/Resend/, { exact: false }).closest('button')).toHaveAttribute('disabled'); + timers.advanceTimersByTime(15000); + expect(getByText(/Resend/, { exact: false }).closest('button')).toHaveAttribute('disabled'); + getByText('(15)', { exact: false }); + timers.advanceTimersByTime(15000); + expect(getByText(/Resend/, { exact: false }).closest('button')).not.toHaveAttribute('disabled'); + }); }); it('disables again the resend code button after clicking it', async () => { @@ -138,10 +147,15 @@ describe('SignInFactorTwo', () => { }); fixtures.signIn.prepareSecondFactor.mockReturnValue(Promise.resolve({} as SignInResource)); - const { getByText } = render(, { wrapper }); - expect(getByText(/Resend/, { exact: false }).closest('button')).toHaveAttribute('disabled'); - // Note: Timer functionality and button state changes are tested in the TimerButton component itself - // This test verifies the initial disabled state is correct + await runFakeTimers(async timers => { + const { getByText, userEvent } = render(, { wrapper }); + expect(getByText(/Resend/, { exact: false }).closest('button')).toHaveAttribute('disabled'); + timers.advanceTimersByTime(30000); + expect(getByText(/Resend/).closest('button')).not.toHaveAttribute('disabled'); + await userEvent.click(getByText(/Resend/)); + timers.advanceTimersByTime(1000); + expect(getByText(/Resend/, { exact: false }).closest('button')).toHaveAttribute('disabled'); + }); }); it('auto submits when typing all the 6 digits of the code', async () => { @@ -183,10 +197,12 @@ describe('SignInFactorTwo', () => { status: 422, }), ); - const { userEvent } = render(, { wrapper }); - await userEvent.type(screen.getByLabelText(/Enter verification code/i), '123456'); - await waitFor(() => expect(screen.getByText('Incorrect phone code')).toBeDefined()); - }); + await runFakeTimers(async () => { + const { userEvent } = render(, { wrapper }); + await userEvent.type(screen.getByLabelText(/Enter verification code/i), '123456'); + await waitFor(() => expect(screen.getByText('Incorrect phone code')).toBeDefined()); + }); + }, 10000); it('redirects back to sign-in if the user is locked', async () => { const { wrapper, fixtures } = await createFixtures(f => { @@ -211,9 +227,9 @@ describe('SignInFactorTwo', () => { }), ); - const { userEvent } = render(, { wrapper }); - await userEvent.type(screen.getByLabelText(/Enter verification code/i), '123456'); - await waitFor(() => { + await runFakeTimers(async () => { + const { userEvent } = render(, { wrapper }); + await userEvent.type(screen.getByLabelText(/Enter verification code/i), '123456'); expect(fixtures.clerk.__internal_navigateWithError).toHaveBeenCalledWith('..', parseError(errJSON)); }); }); @@ -246,9 +262,7 @@ describe('SignInFactorTwo', () => { ); const { userEvent } = render(, { wrapper }); await userEvent.type(screen.getByLabelText(/Enter verification code/i), '123456'); - await waitFor(() => { - expect(fixtures.signIn.attemptSecondFactor).toHaveBeenCalled(); - }); + expect(fixtures.signIn.attemptSecondFactor).toHaveBeenCalled(); }); it('shows a UI error when submission fails', async () => { @@ -272,10 +286,12 @@ describe('SignInFactorTwo', () => { status: 422, }), ); - const { userEvent } = render(, { wrapper }); - await userEvent.type(screen.getByLabelText(/Enter verification code/i), '123456'); - await waitFor(() => expect(screen.getByText('Incorrect authenticator code')).toBeDefined()); - }); + await runFakeTimers(async () => { + const { userEvent } = render(, { wrapper }); + await userEvent.type(screen.getByLabelText(/Enter verification code/i), '123456'); + await waitFor(() => expect(screen.getByText('Incorrect authenticator code')).toBeDefined()); + }); + }, 10000); }); describe('Backup code', () => { @@ -311,9 +327,7 @@ describe('SignInFactorTwo', () => { const { getByText, getByLabelText, userEvent } = render(, { wrapper }); await userEvent.type(getByLabelText('Backup code'), '123456'); await userEvent.click(getByText('Continue')); - await waitFor(() => { - expect(fixtures.signIn.attemptSecondFactor).toHaveBeenCalled(); - }); + expect(fixtures.signIn.attemptSecondFactor).toHaveBeenCalled(); }); it('does not proceed when user clicks the continue button with password field empty', async () => { @@ -335,9 +349,7 @@ describe('SignInFactorTwo', () => { // type nothing in the input field await userEvent.click(getByText('Continue')); - await waitFor(() => { - expect(fixtures.signIn.attemptSecondFactor).not.toHaveBeenCalled(); - }); + expect(fixtures.signIn.attemptSecondFactor).not.toHaveBeenCalled(); }); it('shows a UI error when submission fails', async () => { @@ -364,11 +376,13 @@ describe('SignInFactorTwo', () => { status: 422, }), ); - const { userEvent, getByLabelText, getByText } = render(, { wrapper }); - await userEvent.type(getByLabelText('Backup code'), '123456'); - await userEvent.click(getByText('Continue')); - await waitFor(() => expect(screen.getByText('Incorrect backup code')).toBeDefined()); - }); + await runFakeTimers(async () => { + const { userEvent, getByLabelText, getByText } = render(, { wrapper }); + await userEvent.type(getByLabelText('Backup code'), '123456'); + await userEvent.click(getByText('Continue')); + await waitFor(() => expect(screen.getByText('Incorrect backup code')).toBeDefined()); + }); + }, 10000); it('redirects back to sign-in if the user is locked', async () => { const { wrapper, fixtures } = await createFixtures(f => { @@ -396,11 +410,13 @@ describe('SignInFactorTwo', () => { }), ); - const { userEvent, getByLabelText, getByText } = render(, { wrapper }); - await userEvent.type(getByLabelText('Backup code'), '123456'); - await userEvent.click(getByText('Continue')); - await waitFor(() => { - expect(fixtures.clerk.__internal_navigateWithError).toHaveBeenCalledWith('..', parseError(errJSON)); + await runFakeTimers(async () => { + const { userEvent, getByLabelText, getByText } = render(, { wrapper }); + await userEvent.type(getByLabelText('Backup code'), '123456'); + await userEvent.click(getByText('Continue')); + await waitFor(() => { + expect(fixtures.clerk.__internal_navigateWithError).toHaveBeenCalledWith('..', parseError(errJSON)); + }); }); }); }); @@ -421,10 +437,6 @@ describe('SignInFactorTwo', () => { fixtures.signIn.prepareSecondFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource)); const { userEvent } = render(, { wrapper }); await userEvent.click(screen.getByText('Use another method')); - // Wait for the alternative methods to be rendered - await waitFor(() => { - expect(screen.getByText(/Send SMS code to \+/i)).toBeInTheDocument(); - }); }); it('goes back to the main screen when clicking the "<- Back" button', async () => { @@ -441,13 +453,8 @@ describe('SignInFactorTwo', () => { fixtures.signIn.prepareSecondFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource)); const { userEvent } = render(, { wrapper }); await userEvent.click(screen.getByText('Use another method')); - await waitFor(() => { - expect(screen.getByText('Back')).toBeInTheDocument(); - }); await userEvent.click(screen.getByText('Back')); - await waitFor(() => { - expect(screen.getByText('Check your phone')).toBeInTheDocument(); - }); + screen.getByText('Check your phone'); }); it('lists all the enabled second factor methods', async () => { @@ -464,11 +471,9 @@ describe('SignInFactorTwo', () => { fixtures.signIn.prepareSecondFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource)); const { userEvent } = render(, { wrapper }); await userEvent.click(screen.getByText('Use another method')); - await waitFor(() => { - expect(screen.getByText(/Send SMS code to \+/i)).toBeInTheDocument(); - expect(screen.getByText(/Use a backup code/i)).toBeInTheDocument(); - expect(screen.getByText(/Authenticator/i)).toBeInTheDocument(); - }); + screen.getByText(/Send SMS code to \+/i); + screen.getByText(/Use a backup code/i); + screen.getByText(/Authenticator/i); }); it('shows the SMS code input when clicking the Phone code method', async () => { @@ -485,13 +490,8 @@ describe('SignInFactorTwo', () => { fixtures.signIn.prepareSecondFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource)); const { userEvent } = render(, { wrapper }); await userEvent.click(screen.getByText('Use another method')); - await waitFor(() => { - expect(screen.getByText(/Send SMS code to \+/i)).toBeInTheDocument(); - }); await userEvent.click(screen.getByText(/Send SMS code to \+/i)); - await waitFor(() => { - expect(screen.getByText(/Check your phone/i)).toBeInTheDocument(); - }); + screen.getByText(/Check your phone/i); }); it('shows the Authenticator app screen when clicking the Authenticator app method', async () => { @@ -508,14 +508,9 @@ describe('SignInFactorTwo', () => { fixtures.signIn.prepareSecondFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource)); const { userEvent } = render(, { wrapper }); await userEvent.click(screen.getByText('Use another method')); - await waitFor(() => { - expect(screen.getByText(/authenticator/i)).toBeInTheDocument(); - }); await userEvent.click(screen.getByText(/authenticator/i)); - await waitFor(() => { - expect(screen.getByText(/Enter the verification code/i)).toBeInTheDocument(); - expect(screen.getByText(/authenticator/i)).toBeInTheDocument(); - }); + screen.getByText(/Enter the verification code/i); + screen.getByText(/authenticator/i); }); it('shows the Backup code screen when clicking the Backup code method', async () => { @@ -532,13 +527,8 @@ describe('SignInFactorTwo', () => { fixtures.signIn.prepareSecondFactor.mockReturnValueOnce(Promise.resolve({} as SignInResource)); const { userEvent } = render(, { wrapper }); await userEvent.click(screen.getByText('Use another method')); - await waitFor(() => { - expect(screen.getByText(/backup/i)).toBeInTheDocument(); - }); await userEvent.click(screen.getByText(/backup/i)); - await waitFor(() => { - expect(screen.getByText(/enter a backup code/i)).toBeInTheDocument(); - }); + screen.getByText(/enter a backup code/i); }); describe('Get Help', () => { @@ -555,13 +545,8 @@ describe('SignInFactorTwo', () => { const { userEvent } = render(, { wrapper }); await userEvent.click(screen.getByText('Use another method')); - await waitFor(() => { - expect(screen.getByText('Get help')).toBeInTheDocument(); - }); await userEvent.click(screen.getByText('Get help')); - await waitFor(() => { - expect(screen.getByText('Email support')).toBeInTheDocument(); - }); + screen.getByText('Email support'); }); it('should go back to "Use another method" screen when clicking the "<- Back" button', async () => { @@ -577,17 +562,9 @@ describe('SignInFactorTwo', () => { const { userEvent } = render(, { wrapper }); await userEvent.click(screen.getByText('Use another method')); - await waitFor(() => { - expect(screen.getByText('Get help')).toBeInTheDocument(); - }); await userEvent.click(screen.getByText('Get help')); - await waitFor(() => { - expect(screen.getByText('Back')).toBeInTheDocument(); - }); await userEvent.click(screen.getByText('Back')); - await waitFor(() => { - expect(screen.getByText('Use another method')).toBeInTheDocument(); - }); + screen.getByText('Use another method'); }); it('should open a "mailto:" link when clicking the email support button', async () => { @@ -603,16 +580,10 @@ describe('SignInFactorTwo', () => { const { userEvent } = render(, { wrapper }); await userEvent.click(screen.getByText('Use another method')); - await waitFor(() => { - expect(screen.getByText('Get help')).toBeInTheDocument(); - }); await userEvent.click(screen.getByText('Get help')); - await waitFor(() => { - expect(screen.getByText('Email support')).toBeInTheDocument(); - }); - const assignMock = vi.fn(); - const mockResponse = vi.fn(); + const assignMock = jest.fn(); + const mockResponse = jest.fn(); Object.defineProperty(window, 'location', { value: { set href(_) { diff --git a/packages/clerk-js/src/ui/components/SignIn/__tests__/SignInStart.test.tsx b/packages/clerk-js/src/ui/components/SignIn/__tests__/SignInStart.test.tsx index a3ceb80d83d..3483860d896 100644 --- a/packages/clerk-js/src/ui/components/SignIn/__tests__/SignInStart.test.tsx +++ b/packages/clerk-js/src/ui/components/SignIn/__tests__/SignInStart.test.tsx @@ -1,15 +1,14 @@ import { OAUTH_PROVIDERS } from '@clerk/shared/oauth'; import type { SignInResource } from '@clerk/types'; import { waitFor } from '@testing-library/react'; -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { CardStateProvider } from '@/ui/elements/contexts'; import { ClerkAPIResponseError } from '../../../../core/resources'; -import { fireEvent, mockWebAuthn, render, screen } from '../../../../vitestUtils'; +import { fireEvent, mockWebAuthn, render, screen } from '../../../../testUtils'; import { OptionsProvider } from '../../../contexts'; import { AppearanceProvider } from '../../../customizables'; -import { bindCreateFixtures } from '../../../utils/vitest/createFixtures'; +import { bindCreateFixtures } from '../../../utils/test/createFixtures'; import { SignInStart } from '../SignInStart'; const { createFixtures } = bindCreateFixtures('SignIn'); @@ -18,7 +17,7 @@ describe('SignInStart', () => { const originalGetComputedStyle = window.getComputedStyle; const originalLocation = window.location; const originalHistory = window.history; - const mockGetComputedStyle = vi.fn(); + const mockGetComputedStyle = jest.fn(); beforeEach(() => { // Mock window.getComputedStyle @@ -26,7 +25,7 @@ describe('SignInStart', () => { mockGetComputedStyle.mockReturnValue({ animationName: '', pointerEvents: 'auto', - getPropertyValue: vi.fn().mockReturnValue(''), + getPropertyValue: jest.fn().mockReturnValue(''), }); Object.defineProperty(window, 'getComputedStyle', { value: mockGetComputedStyle, @@ -329,7 +328,7 @@ describe('SignInStart', () => { expect(fixtures.signIn.create).toHaveBeenCalled(); expect(fixtures.signIn.authenticateWithRedirect).toHaveBeenCalledWith({ strategy: 'enterprise_sso', - redirectUrl: 'http://localhost:3000/#/sso-callback', + redirectUrl: 'http://localhost/#/sso-callback', redirectUrlComplete: '/', continueSignIn: true, }); @@ -353,7 +352,7 @@ describe('SignInStart', () => { expect(fixtures.signIn.create).toHaveBeenCalled(); expect(fixtures.signIn.authenticateWithRedirect).toHaveBeenCalledWith({ strategy: 'enterprise_sso', - redirectUrl: 'http://localhost:3000/#/sso-callback', + redirectUrl: 'http://localhost/#/sso-callback', redirectUrlComplete: '/', continueSignIn: true, }); @@ -536,7 +535,7 @@ describe('SignInStart', () => { }); Object.defineProperty(window, 'history', { writable: true, - value: { replaceState: vi.fn() }, + value: { replaceState: jest.fn() }, }); render( @@ -570,7 +569,7 @@ describe('SignInStart', () => { }); Object.defineProperty(window, 'history', { writable: true, - value: { replaceState: vi.fn() }, + value: { replaceState: jest.fn() }, }); render( diff --git a/packages/clerk-js/src/ui/components/SignIn/__tests__/handleCombinedFlowTransfer.test.ts b/packages/clerk-js/src/ui/components/SignIn/__tests__/handleCombinedFlowTransfer.spec.ts similarity index 100% rename from packages/clerk-js/src/ui/components/SignIn/__tests__/handleCombinedFlowTransfer.test.ts rename to packages/clerk-js/src/ui/components/SignIn/__tests__/handleCombinedFlowTransfer.spec.ts diff --git a/packages/clerk-js/src/ui/components/SignIn/__tests__/utils.test.ts b/packages/clerk-js/src/ui/components/SignIn/__tests__/utils.spec.ts similarity index 100% rename from packages/clerk-js/src/ui/components/SignIn/__tests__/utils.test.ts rename to packages/clerk-js/src/ui/components/SignIn/__tests__/utils.spec.ts diff --git a/packages/clerk-js/src/ui/components/SignUp/__tests__/SignUpContinue.test.tsx b/packages/clerk-js/src/ui/components/SignUp/__tests__/SignUpContinue.test.tsx index c56eea1bec0..a2a10c6f187 100644 --- a/packages/clerk-js/src/ui/components/SignUp/__tests__/SignUpContinue.test.tsx +++ b/packages/clerk-js/src/ui/components/SignUp/__tests__/SignUpContinue.test.tsx @@ -2,10 +2,9 @@ import { ClerkAPIResponseError } from '@clerk/shared/error'; import { OAUTH_PROVIDERS } from '@clerk/shared/oauth'; import { waitFor } from '@testing-library/dom'; import React from 'react'; -import { describe, expect, it } from 'vitest'; -import { render, screen } from '../../../../vitestUtils'; -import { bindCreateFixtures } from '../../../utils/vitest/createFixtures'; +import { render, runFakeTimers, screen } from '../../../../testUtils'; +import { bindCreateFixtures } from '../../../utils/test/createFixtures'; import { SignUpContinue } from '../SignUpContinue'; const { createFixtures } = bindCreateFixtures('SignUp'); @@ -161,16 +160,21 @@ describe('SignUpContinue', () => { }), ); - const { userEvent } = render(, { wrapper }); - expect(screen.queryByText(/username/i)).toBeInTheDocument(); - await userEvent.type(screen.getByLabelText(/username/i), 'clerkUser'); - const button = screen.getByText('Continue'); - await userEvent.click(button); - - await waitFor(() => expect(fixtures.signUp.update).toHaveBeenCalled()); - await waitFor(() => - expect(screen.queryByText(/^Your username must be between 4 and 40 characters long./i)).toBeInTheDocument(), - ); + await runFakeTimers(async timers => { + const { userEvent } = render(, { wrapper }); + expect(screen.queryByText(/username/i)).toBeInTheDocument(); + await userEvent.type(screen.getByLabelText(/username/i), 'clerkUser'); + timers.runOnlyPendingTimers(); + const button = screen.getByText('Continue'); + await userEvent.click(button); + timers.runOnlyPendingTimers(); + + await waitFor(() => expect(fixtures.signUp.update).toHaveBeenCalled()); + timers.runOnlyPendingTimers(); + await waitFor(() => + expect(screen.queryByText(/^Your username must be between 4 and 40 characters long./i)).toBeInTheDocument(), + ); + }); }); it('renders error for existing username', async () => { @@ -196,16 +200,21 @@ describe('SignUpContinue', () => { }), ); - const { userEvent } = render(, { wrapper }); - expect(screen.queryByText(/username/i)).toBeInTheDocument(); - await userEvent.type(screen.getByLabelText(/username/i), 'clerkUser'); - const button = screen.getByText('Continue'); - await userEvent.click(button); - - await waitFor(() => expect(fixtures.signUp.update).toHaveBeenCalled()); - await waitFor(() => - expect(screen.queryByText(/^This username is taken. Please try another./i)).toBeInTheDocument(), - ); + await runFakeTimers(async timers => { + const { userEvent } = render(, { wrapper }); + expect(screen.queryByText(/username/i)).toBeInTheDocument(); + await userEvent.type(screen.getByLabelText(/username/i), 'clerkUser'); + timers.runOnlyPendingTimers(); + const button = screen.getByText('Continue'); + await userEvent.click(button); + timers.runOnlyPendingTimers(); + + await waitFor(() => expect(fixtures.signUp.update).toHaveBeenCalled()); + timers.runOnlyPendingTimers(); + await waitFor(() => + expect(screen.queryByText(/^This username is taken. Please try another./i)).toBeInTheDocument(), + ); + }); }); describe('Sign in Link', () => { diff --git a/packages/clerk-js/src/ui/components/SignUp/__tests__/SignUpEmailLinkFlowComplete.test.tsx b/packages/clerk-js/src/ui/components/SignUp/__tests__/SignUpEmailLinkFlowComplete.test.tsx index 8780776db1d..7f4dbcf97ef 100644 --- a/packages/clerk-js/src/ui/components/SignUp/__tests__/SignUpEmailLinkFlowComplete.test.tsx +++ b/packages/clerk-js/src/ui/components/SignUp/__tests__/SignUpEmailLinkFlowComplete.test.tsx @@ -1,10 +1,9 @@ import React from 'react'; -import { describe, expect, it } from 'vitest'; import { EmailLinkError, EmailLinkErrorCodeStatus } from '../../../../core/resources'; -import { render, screen, waitFor } from '../../../../vitestUtils'; +import { render, runFakeTimers, screen, waitFor } from '../../../../testUtils'; import { SignUpEmailLinkFlowComplete } from '../../../common/EmailLinkCompleteFlowCard'; -import { bindCreateFixtures } from '../../../utils/vitest/createFixtures'; +import { bindCreateFixtures } from '../../../utils/test/createFixtures'; const { createFixtures } = bindCreateFixtures('SignUp'); @@ -23,8 +22,11 @@ describe('SignUpEmailLinkFlowComplete', () => { const { wrapper, fixtures } = await createFixtures(f => { f.withEmailAddress({ required: true }); }); - render(, { wrapper }); - await waitFor(() => expect(fixtures.clerk.handleEmailLinkVerification).toHaveBeenCalled()); + await runFakeTimers(async timers => { + render(, { wrapper }); + timers.runOnlyPendingTimers(); + await waitFor(() => expect(fixtures.clerk.handleEmailLinkVerification).toHaveBeenCalled()); + }); }); describe('Success', () => { @@ -32,9 +34,12 @@ describe('SignUpEmailLinkFlowComplete', () => { const { wrapper, fixtures } = await createFixtures(f => { f.withEmailAddress({ required: true }); }); - render(, { wrapper }); - await waitFor(() => expect(fixtures.clerk.handleEmailLinkVerification).toHaveBeenCalled()); - screen.getByText(/success/i); + await runFakeTimers(async timers => { + render(, { wrapper }); + timers.runOnlyPendingTimers(); + await waitFor(() => expect(fixtures.clerk.handleEmailLinkVerification).toHaveBeenCalled()); + screen.getByText(/success/i); + }); }); }); @@ -43,25 +48,35 @@ describe('SignUpEmailLinkFlowComplete', () => { const { wrapper, fixtures } = await createFixtures(f => { f.withEmailAddress({ required: true }); }); - fixtures.clerk.handleEmailLinkVerification.mockImplementationOnce(() => - Promise.reject(new EmailLinkError(EmailLinkErrorCodeStatus.Expired)), + fixtures.clerk.handleEmailLinkVerification.mockImplementationOnce( + await Promise.resolve(() => { + throw new EmailLinkError(EmailLinkErrorCodeStatus.Expired); + }), ); - render(, { wrapper }); - await waitFor(() => expect(fixtures.clerk.handleEmailLinkVerification).toHaveBeenCalled()); - screen.getByText(/expired/i); + await runFakeTimers(async timers => { + render(, { wrapper }); + timers.runOnlyPendingTimers(); + await waitFor(() => expect(fixtures.clerk.handleEmailLinkVerification).toHaveBeenCalled()); + screen.getByText(/expired/i); + }); }); it('shows the failed error message when the appropriate error is thrown', async () => { const { wrapper, fixtures } = await createFixtures(f => { f.withEmailAddress({ required: true }); }); - fixtures.clerk.handleEmailLinkVerification.mockImplementationOnce(() => - Promise.reject(new EmailLinkError(EmailLinkErrorCodeStatus.Failed)), + fixtures.clerk.handleEmailLinkVerification.mockImplementationOnce( + await Promise.resolve(() => { + throw new EmailLinkError(EmailLinkErrorCodeStatus.Failed); + }), ); - render(, { wrapper }); - await waitFor(() => expect(fixtures.clerk.handleEmailLinkVerification).toHaveBeenCalled()); - screen.getByText(/invalid/i); + await runFakeTimers(async timers => { + render(, { wrapper }); + timers.runOnlyPendingTimers(); + await waitFor(() => expect(fixtures.clerk.handleEmailLinkVerification).toHaveBeenCalled()); + screen.getByText(/invalid/i); + }); }); }); }); diff --git a/packages/clerk-js/src/ui/components/SignUp/__tests__/SignUpStart.test.tsx b/packages/clerk-js/src/ui/components/SignUp/__tests__/SignUpStart.spec.tsx similarity index 100% rename from packages/clerk-js/src/ui/components/SignUp/__tests__/SignUpStart.test.tsx rename to packages/clerk-js/src/ui/components/SignUp/__tests__/SignUpStart.spec.tsx diff --git a/packages/clerk-js/src/ui/components/SignUp/__tests__/SignUpVerifyEmail.test.tsx b/packages/clerk-js/src/ui/components/SignUp/__tests__/SignUpVerifyEmail.test.tsx index 6ade6432111..536abb374f9 100644 --- a/packages/clerk-js/src/ui/components/SignUp/__tests__/SignUpVerifyEmail.test.tsx +++ b/packages/clerk-js/src/ui/components/SignUp/__tests__/SignUpVerifyEmail.test.tsx @@ -1,8 +1,7 @@ import { waitFor } from '@testing-library/dom'; -import { describe, expect, it, vi } from 'vitest'; -import { render, screen } from '../../../../vitestUtils'; -import { bindCreateFixtures } from '../../../utils/vitest/createFixtures'; +import { render, screen } from '../../../../testUtils'; +import { bindCreateFixtures } from '../../../utils/test/createFixtures'; import { SignUpVerifyEmail } from '../SignUpVerifyEmail'; const { createFixtures } = bindCreateFixtures('SignUp'); @@ -32,8 +31,8 @@ describe('SignUpVerifyEmail', () => { fixtures.signUp.createEmailLinkFlow.mockImplementation( () => ({ - startEmailLinkFlow: vi.fn(() => new Promise(() => ({}))), - cancelEmailLinkFlow: vi.fn(() => new Promise(() => ({}))), + startEmailLinkFlow: jest.fn(() => new Promise(() => ({}))), + cancelEmailLinkFlow: jest.fn(() => new Promise(() => ({}))), }) as any, ); @@ -80,8 +79,8 @@ describe('SignUpVerifyEmail', () => { fixtures.signUp.createEmailLinkFlow.mockImplementation( () => ({ - startEmailLinkFlow: vi.fn(() => new Promise(() => ({}))), - cancelEmailLinkFlow: vi.fn(() => new Promise(() => ({}))), + startEmailLinkFlow: jest.fn(() => new Promise(() => ({}))), + cancelEmailLinkFlow: jest.fn(() => new Promise(() => ({}))), }) as any, ); const { findByText } = render(, { wrapper }); diff --git a/packages/clerk-js/src/ui/components/SignUp/__tests__/SignUpVerifyPhone.test.tsx b/packages/clerk-js/src/ui/components/SignUp/__tests__/SignUpVerifyPhone.test.tsx index 21b920dc118..b92c5263c85 100644 --- a/packages/clerk-js/src/ui/components/SignUp/__tests__/SignUpVerifyPhone.test.tsx +++ b/packages/clerk-js/src/ui/components/SignUp/__tests__/SignUpVerifyPhone.test.tsx @@ -1,8 +1,7 @@ import { waitFor } from '@testing-library/dom'; -import { describe, expect, it } from 'vitest'; -import { render, screen } from '../../../../vitestUtils'; -import { bindCreateFixtures } from '../../../utils/vitest/createFixtures'; +import { render, screen } from '../../../../testUtils'; +import { bindCreateFixtures } from '../../../utils/test/createFixtures'; import { SignUpVerifyPhone } from '../SignUpVerifyPhone'; const { createFixtures } = bindCreateFixtures('SignUp'); diff --git a/packages/clerk-js/src/ui/components/SignUp/__tests__/signUpFormHelpers.test.ts b/packages/clerk-js/src/ui/components/SignUp/__tests__/signUpFormHelpers.spec.ts similarity index 100% rename from packages/clerk-js/src/ui/components/SignUp/__tests__/signUpFormHelpers.test.ts rename to packages/clerk-js/src/ui/components/SignUp/__tests__/signUpFormHelpers.spec.ts diff --git a/packages/clerk-js/src/ui/components/SubscriptionDetails/__tests__/SubscriptionDetails.test.tsx b/packages/clerk-js/src/ui/components/SubscriptionDetails/__tests__/SubscriptionDetails.test.tsx index e71e8969282..66b01c85222 100644 --- a/packages/clerk-js/src/ui/components/SubscriptionDetails/__tests__/SubscriptionDetails.test.tsx +++ b/packages/clerk-js/src/ui/components/SubscriptionDetails/__tests__/SubscriptionDetails.test.tsx @@ -1,9 +1,7 @@ -import { describe, expect, it, vi } from 'vitest'; - import { Drawer } from '@/ui/elements/Drawer'; -import { render, waitFor } from '../../../../vitestUtils'; -import { bindCreateFixtures } from '../../../utils/vitest/createFixtures'; +import { render, waitFor } from '../../../../testUtils'; +import { bindCreateFixtures } from '../../../utils/test/createFixtures'; import { SubscriptionDetails } from '..'; const { createFixtures } = bindCreateFixtures('SubscriptionDetails'); @@ -625,7 +623,7 @@ describe('SubscriptionDetails', () => { f.withBilling(); }); - const cancelSubscriptionMock = vi.fn().mockResolvedValue({}); + const cancelSubscriptionMock = jest.fn().mockResolvedValue({}); fixtures.clerk.billing.getSubscription.mockResolvedValue({ activeAt: new Date('2021-01-01'), @@ -762,9 +760,9 @@ describe('SubscriptionDetails', () => { slug: 'annual-plan', avatarUrl: '', features: [], - __internal_toSnapshot: vi.fn(), + __internal_toSnapshot: jest.fn(), pathRoot: '', - reload: vi.fn(), + reload: jest.fn(), }; const subscription = { @@ -777,9 +775,9 @@ describe('SubscriptionDetails', () => { paymentSourceId: 'src_annual', planPeriod: 'annual' as const, status: 'active' as const, - cancel: vi.fn(), + cancel: jest.fn(), pathRoot: '', - reload: vi.fn(), + reload: jest.fn(), }; // Mock getSubscriptions to return the canceled subscription @@ -878,9 +876,9 @@ describe('SubscriptionDetails', () => { paymentSourceId: 'src_annual', planPeriod: 'annual' as const, status: 'active' as const, - cancel: vi.fn(), + cancel: jest.fn(), pathRoot: '', - reload: vi.fn(), + reload: jest.fn(), }; // Mock getSubscriptions to return the annual subscription @@ -1138,7 +1136,7 @@ describe('SubscriptionDetails', () => { f.withBilling(); }); - const cancelSubscriptionMock = vi.fn().mockResolvedValue({}); + const cancelSubscriptionMock = jest.fn().mockResolvedValue({}); fixtures.clerk.billing.getSubscription.mockResolvedValue({ activeAt: new Date('2021-01-01'), diff --git a/packages/clerk-js/src/ui/components/Subscriptions/__tests__/SubscriptionsList.test.tsx b/packages/clerk-js/src/ui/components/Subscriptions/__tests__/SubscriptionsList.test.tsx index 3dec2b94450..988b5a3e5bc 100644 --- a/packages/clerk-js/src/ui/components/Subscriptions/__tests__/SubscriptionsList.test.tsx +++ b/packages/clerk-js/src/ui/components/Subscriptions/__tests__/SubscriptionsList.test.tsx @@ -1,9 +1,8 @@ import type { BillingPayerResourceType } from '@clerk/types'; -import { describe, expect, it, vi } from 'vitest'; -import { render, waitFor } from '../../../../vitestUtils'; +import { render, waitFor } from '../../../../testUtils'; import { localizationKeys } from '../../../customizables'; -import { bindCreateFixtures } from '../../../utils/vitest/createFixtures'; +import { bindCreateFixtures } from '../../../utils/test/createFixtures'; import { SubscriptionsList } from '../SubscriptionsList'; const { createFixtures } = bindCreateFixtures('UserProfile'); @@ -36,7 +35,7 @@ describe('SubscriptionsList', () => { updatedAt: null, subscriptionItems: [], pathRoot: '', - reload: vi.fn(), + reload: jest.fn(), }); const { getByText, queryByText } = render(, { wrapper }); @@ -83,7 +82,7 @@ describe('SubscriptionsList', () => { freeTrialDays: null, freeTrialEnabled: false, pathRoot: '', - reload: vi.fn(), + reload: jest.fn(), }, status: 'active', createdAt: new Date('2021-01-01'), @@ -94,13 +93,13 @@ describe('SubscriptionsList', () => { planPeriod: 'month' as const, isFreeTrial: false, pastDueAt: null, - cancel: vi.fn(), + cancel: jest.fn(), pathRoot: '', - reload: vi.fn(), + reload: jest.fn(), }, ], pathRoot: '', - reload: vi.fn(), + reload: jest.fn(), }); const testProps = { @@ -149,7 +148,7 @@ describe('SubscriptionsList', () => { freeTrialDays: 14, freeTrialEnabled: true, pathRoot: '', - reload: vi.fn(), + reload: jest.fn(), }, createdAt: new Date('2021-01-01'), periodStart: new Date('2021-01-01'), @@ -160,9 +159,9 @@ describe('SubscriptionsList', () => { status: 'active' as const, isFreeTrial: true, // This subscription is in a free trial pastDueAt: null, - cancel: vi.fn(), + cancel: jest.fn(), pathRoot: '', - reload: vi.fn(), + reload: jest.fn(), }; fixtures.clerk.billing.getSubscription.mockResolvedValue({ @@ -175,7 +174,7 @@ describe('SubscriptionsList', () => { updatedAt: null, subscriptionItems: [freeTrialSubscription], pathRoot: '', - reload: vi.fn(), + reload: jest.fn(), }); const { getByText } = render(, { wrapper }); @@ -213,7 +212,7 @@ describe('SubscriptionsList', () => { freeTrialDays: null, freeTrialEnabled: false, pathRoot: '', - reload: vi.fn(), + reload: jest.fn(), }, createdAt: new Date('2021-01-01'), periodStart: new Date('2021-01-01'), @@ -224,9 +223,9 @@ describe('SubscriptionsList', () => { status: 'past_due' as const, isFreeTrial: false, pastDueAt: new Date('2021-01-15'), - cancel: vi.fn(), + cancel: jest.fn(), pathRoot: '', - reload: vi.fn(), + reload: jest.fn(), }; fixtures.clerk.billing.getSubscription.mockResolvedValue({ @@ -239,7 +238,7 @@ describe('SubscriptionsList', () => { updatedAt: null, subscriptionItems: [pastDueSubscription], pathRoot: '', - reload: vi.fn(), + reload: jest.fn(), }); const { getByText, queryByText } = render(, { wrapper }); @@ -278,7 +277,7 @@ describe('SubscriptionsList', () => { freeTrialDays: null, freeTrialEnabled: false, pathRoot: '', - reload: vi.fn(), + reload: jest.fn(), }, createdAt: new Date('2021-01-01'), periodStart: new Date('2021-01-01'), @@ -289,9 +288,9 @@ describe('SubscriptionsList', () => { status: 'active' as const, isFreeTrial: false, pastDueAt: null, - cancel: vi.fn(), + cancel: jest.fn(), pathRoot: '', - reload: vi.fn(), + reload: jest.fn(), }; fixtures.clerk.billing.getSubscription.mockResolvedValue({ @@ -304,7 +303,7 @@ describe('SubscriptionsList', () => { updatedAt: null, subscriptionItems: [activeSubscription], pathRoot: '', - reload: vi.fn(), + reload: jest.fn(), }); const { getByText, queryByText } = render(, { wrapper }); @@ -342,7 +341,7 @@ describe('SubscriptionsList', () => { freeTrialDays: null, freeTrialEnabled: false, pathRoot: '', - reload: vi.fn(), + reload: jest.fn(), }, createdAt: new Date('2021-01-01'), periodStart: new Date('2021-02-01'), @@ -353,9 +352,9 @@ describe('SubscriptionsList', () => { status: 'upcoming' as const, isFreeTrial: false, pastDueAt: null, - cancel: vi.fn(), + cancel: jest.fn(), pathRoot: '', - reload: vi.fn(), + reload: jest.fn(), }; const activeCanceledSubscription = { @@ -378,7 +377,7 @@ describe('SubscriptionsList', () => { freeTrialDays: null, freeTrialEnabled: false, pathRoot: '', - reload: vi.fn(), + reload: jest.fn(), }, createdAt: new Date('2021-01-01'), periodStart: new Date('2021-01-01'), @@ -389,9 +388,9 @@ describe('SubscriptionsList', () => { status: 'active' as const, isFreeTrial: false, pastDueAt: null, - cancel: vi.fn(), + cancel: jest.fn(), pathRoot: '', - reload: vi.fn(), + reload: jest.fn(), }; fixtures.clerk.billing.getSubscription.mockResolvedValue({ @@ -404,7 +403,7 @@ describe('SubscriptionsList', () => { updatedAt: null, subscriptionItems: [activeCanceledSubscription, upcomingSubscription], pathRoot: '', - reload: vi.fn(), + reload: jest.fn(), }); const { getByText, queryByText } = render(, { wrapper }); diff --git a/packages/clerk-js/src/ui/components/UserButton/__tests__/UserButton.test.tsx b/packages/clerk-js/src/ui/components/UserButton/__tests__/UserButton.test.tsx index ccc9e17338c..e96492e6f58 100644 --- a/packages/clerk-js/src/ui/components/UserButton/__tests__/UserButton.test.tsx +++ b/packages/clerk-js/src/ui/components/UserButton/__tests__/UserButton.test.tsx @@ -1,7 +1,7 @@ -import { describe, expect, it } from 'vitest'; +import { describe } from '@jest/globals'; -import { render, waitFor } from '../../../../vitestUtils'; -import { bindCreateFixtures } from '../../../utils/vitest/createFixtures'; +import { render, waitFor } from '../../../../testUtils'; +import { bindCreateFixtures } from '../../../utils/test/createFixtures'; import { UserButton } from '../'; const { createFixtures } = bindCreateFixtures('UserButton'); diff --git a/packages/clerk-js/src/ui/components/UserProfile/__tests__/AccountPage.test.tsx b/packages/clerk-js/src/ui/components/UserProfile/__tests__/AccountPage.test.tsx index 04dc643c474..9ee58dd8717 100644 --- a/packages/clerk-js/src/ui/components/UserProfile/__tests__/AccountPage.test.tsx +++ b/packages/clerk-js/src/ui/components/UserProfile/__tests__/AccountPage.test.tsx @@ -1,8 +1,8 @@ import type { EnterpriseAccountJSON } from '@clerk/types'; -import { describe, expect, it } from 'vitest'; +import { describe, it } from '@jest/globals'; -import { render, screen, waitFor } from '../../../../vitestUtils'; -import { bindCreateFixtures } from '../../../utils/vitest/createFixtures'; +import { render, screen, waitFor } from '../../../../testUtils'; +import { bindCreateFixtures } from '../../../utils/test/createFixtures'; import { AccountPage } from '../AccountPage'; const { createFixtures } = bindCreateFixtures('UserProfile'); diff --git a/packages/clerk-js/src/ui/components/UserProfile/__tests__/ConnectedAccountsSection.test.tsx b/packages/clerk-js/src/ui/components/UserProfile/__tests__/ConnectedAccountsSection.test.tsx index 2daf0108b29..9bb3a36df29 100644 --- a/packages/clerk-js/src/ui/components/UserProfile/__tests__/ConnectedAccountsSection.test.tsx +++ b/packages/clerk-js/src/ui/components/UserProfile/__tests__/ConnectedAccountsSection.test.tsx @@ -1,9 +1,9 @@ import type { ExternalAccountResource } from '@clerk/types'; +import { describe, it } from '@jest/globals'; import { act, waitFor } from '@testing-library/react'; -import { describe, expect, it } from 'vitest'; -import { render, screen } from '../../../../vitestUtils'; -import { bindCreateFixtures } from '../../../utils/vitest/createFixtures'; +import { render, screen } from '../../../../testUtils'; +import { bindCreateFixtures } from '../../../utils/test/createFixtures'; import { ConnectedAccountsSection } from '../ConnectedAccountsSection'; const { createFixtures } = bindCreateFixtures('UserProfile'); @@ -150,6 +150,7 @@ describe('ConnectedAccountsSection ', () => { // Still displays a remove button const menuButton = item.parentElement?.parentElement?.parentElement?.parentElement?.children?.[1]; await act(async () => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await userEvent.click(menuButton!); }); @@ -184,6 +185,7 @@ describe('ConnectedAccountsSection ', () => { // Still displays a remove button const menuButton = item.parentElement?.parentElement?.parentElement?.parentElement?.children?.[1]; await act(async () => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await userEvent.click(menuButton!); }); @@ -206,6 +208,7 @@ describe('ConnectedAccountsSection ', () => { // Still displays a remove button const menuButton = item.parentElement?.parentElement?.parentElement?.parentElement?.children?.[1]; await act(async () => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await userEvent.click(menuButton!); }); @@ -226,6 +229,7 @@ describe('ConnectedAccountsSection ', () => { const item = getByText(/github/i); const menuButton = item.parentElement?.parentElement?.parentElement?.parentElement?.children?.[1]; await act(async () => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await userEvent.click(menuButton!); }); getByRole('menuitem', { name: /remove/i }); @@ -241,38 +245,41 @@ describe('ConnectedAccountsSection ', () => { it('removes a connection', async () => { const { wrapper, fixtures } = await createFixtures(withConnections); fixtures.clerk.user?.externalAccounts[1].destroy.mockResolvedValue(); - const { userEvent, getByText, getByRole } = render(, { wrapper }); + const { userEvent, getByText, getByRole, queryByRole } = render(, { wrapper }); const item = getByText(/github/i); const menuButton = item.parentElement?.parentElement?.parentElement?.parentElement?.children?.[1]; await act(async () => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await userEvent.click(menuButton!); }); getByRole('menuitem', { name: /remove/i }); await userEvent.click(getByRole('menuitem', { name: /remove/i })); - await waitFor(() => getByRole('heading', { name: /Remove connected account/i }), { timeout: 500 }); + await waitFor(() => getByRole('heading', { name: /Remove connected account/i })); await userEvent.click(getByRole('button', { name: /remove/i })); expect(fixtures.clerk.user?.externalAccounts[1].destroy).toHaveBeenCalled(); + + await waitFor(() => + expect(queryByRole('heading', { name: /Remove connected account/i })).not.toBeInTheDocument(), + ); }); it('hides screen when when pressing cancel', async () => { const { wrapper } = await createFixtures(withConnections); - const { userEvent, getByText, getByRole } = render(, { wrapper }); + const { userEvent, getByText, getByRole, queryByRole } = render(, { wrapper }); const item = getByText(/github/i); const menuButton = item.parentElement?.parentElement?.parentElement?.parentElement?.children?.[1]; await act(async () => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await userEvent.click(menuButton!); }); getByRole('menuitem', { name: /remove/i }); await userEvent.click(getByRole('menuitem', { name: /remove/i })); await waitFor(() => getByRole('heading', { name: /Remove connected account/i })); - - await userEvent.click(screen.getByRole('button', { name: /cancel/i })); - - // The cancel button should be clickable - expect(screen.getByRole('button', { name: /cancel/i })).toBeInTheDocument(); + await userEvent.click(screen.getByRole('button', { name: /cancel$/i })); + expect(queryByRole('heading', { name: /Remove connected account/i })).not.toBeInTheDocument(); }); }); @@ -284,6 +291,7 @@ describe('ConnectedAccountsSection ', () => { const item = getByText(/google/i); const menuButton = item.parentElement?.parentElement?.parentElement?.parentElement?.children?.[1]; await act(async () => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await userEvent.click(menuButton!); }); getByRole('menuitem', { name: /remove/i }); diff --git a/packages/clerk-js/src/ui/components/UserProfile/__tests__/EmailsSection.test.tsx b/packages/clerk-js/src/ui/components/UserProfile/__tests__/EmailsSection.test.tsx index 1a5ff97203e..9937864c8ac 100644 --- a/packages/clerk-js/src/ui/components/UserProfile/__tests__/EmailsSection.test.tsx +++ b/packages/clerk-js/src/ui/components/UserProfile/__tests__/EmailsSection.test.tsx @@ -1,10 +1,10 @@ +import { describe, it } from '@jest/globals'; import { act } from '@testing-library/react'; -import { describe, expect, it, vi } from 'vitest'; import { CardStateProvider } from '@/ui/elements/contexts'; -import { render, waitFor } from '../../../../vitestUtils'; -import { bindCreateFixtures } from '../../../utils/vitest/createFixtures'; +import { render, waitFor } from '../../../../testUtils'; +import { bindCreateFixtures } from '../../../utils/test/createFixtures'; import { EmailsSection } from '../EmailsSection'; const { createFixtures } = bindCreateFixtures('UserProfile'); @@ -28,7 +28,7 @@ const getMenuItemFromText = (element: HTMLElement) => { describe('EmailSection', () => { it('renders the section', async () => { const { wrapper, fixtures } = await createFixtures(withEmails); - + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion fixtures.clerk.user!.getSessions.mockReturnValue(Promise.resolve([])); const { getByText } = render( @@ -62,7 +62,7 @@ describe('EmailSection', () => { fixtures.clerk.user?.createEmailAddress.mockReturnValueOnce( Promise.resolve({ - prepareVerification: vi.fn().mockReturnValueOnce(Promise.resolve({} as any)), + prepareVerification: jest.fn().mockReturnValueOnce(Promise.resolve({} as any)), } as any), ); @@ -109,6 +109,7 @@ describe('EmailSection', () => { const item = getByText(emails[0]); const menuButton = getMenuItemFromText(item); await act(async () => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await userEvent.click(menuButton!); }); @@ -119,7 +120,7 @@ describe('EmailSection', () => { it('removes an email address', async () => { const { wrapper, fixtures } = await createFixtures(withEmails); - const { getByText, userEvent, getByRole } = render( + const { getByText, userEvent, getByRole, queryByRole } = render( , @@ -131,15 +132,18 @@ describe('EmailSection', () => { const item = getByText(emails[0]); const menuButton = getMenuItemFromText(item); await act(async () => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await userEvent.click(menuButton!); }); getByRole('menuitem', { name: /remove email/i }); await userEvent.click(getByRole('menuitem', { name: /remove email/i })); - await waitFor(() => getByRole('heading', { name: /Remove email address/i }), { timeout: 500 }); + await waitFor(() => getByRole('heading', { name: /Remove email address/i })); await userEvent.click(getByRole('button', { name: /remove/i })); expect(fixtures.clerk.user?.emailAddresses[0].destroy).toHaveBeenCalled(); + + await waitFor(() => expect(queryByRole('heading', { name: /Remove email address/i })).not.toBeInTheDocument()); }); describe('Form buttons', () => { @@ -155,6 +159,7 @@ describe('EmailSection', () => { const item = getByText(emails[0]); const menuButton = getMenuItemFromText(item); await act(async () => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await userEvent.click(menuButton!); }); @@ -166,7 +171,7 @@ describe('EmailSection', () => { it('hides screen when when pressing cancel', async () => { const { wrapper } = await createFixtures(withEmails); - const { getByRole, userEvent, getByText } = render( + const { getByRole, userEvent, getByText, queryByRole } = render( , @@ -176,6 +181,7 @@ describe('EmailSection', () => { const item = getByText(emails[0]); const menuButton = getMenuItemFromText(item); await act(async () => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await userEvent.click(menuButton!); }); @@ -183,11 +189,7 @@ describe('EmailSection', () => { await userEvent.click(getByRole('menuitem', { name: /remove email/i })); await waitFor(() => getByRole('heading', { name: /Remove email address/i })); await userEvent.click(getByRole('button', { name: /cancel$/i })); - - // Wait for the form to close and the "Add email address" button to reappear - await waitFor(() => { - expect(getByRole('button', { name: /Add email address/i })).toBeInTheDocument(); - }); + expect(queryByRole('heading', { name: /Remove email address/i })).not.toBeInTheDocument(); }); }); }); @@ -195,7 +197,7 @@ describe('EmailSection', () => { describe('Handles opening/closing actions', () => { it('closes add email form when remove an email address action is clicked', async () => { const { wrapper, fixtures } = await createFixtures(withEmails); - const { getByText, userEvent, getByRole } = render( + const { getByText, userEvent, getByRole, queryByRole } = render( , @@ -205,25 +207,26 @@ describe('EmailSection', () => { fixtures.clerk.user?.emailAddresses[0].destroy.mockResolvedValue(); await userEvent.click(getByRole('button', { name: /add email address/i })); - await waitFor(() => getByRole('heading', { name: /add email address/i }), { timeout: 500 }); + await waitFor(() => getByRole('heading', { name: /add email address/i })); const item = getByText(emails[0]); const menuButton = getMenuItemFromText(item); await act(async () => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await userEvent.click(menuButton!); }); getByRole('menuitem', { name: /remove email/i }); await userEvent.click(getByRole('menuitem', { name: /remove email/i })); - await waitFor(() => getByRole('heading', { name: /remove email address/i }), { timeout: 500 }); + await waitFor(() => getByRole('heading', { name: /remove email address/i })); - // Verify that the remove email form is now visible - expect(getByRole('heading', { name: /remove email address/i })).toBeInTheDocument(); + await waitFor(() => expect(queryByRole('heading', { name: /remove email address/i })).toBeInTheDocument()); + await waitFor(() => expect(queryByRole('heading', { name: /add email address/i })).not.toBeInTheDocument()); }); it('closes remove email address form when add email address action is clicked', async () => { const { wrapper, fixtures } = await createFixtures(withEmails); - const { getByText, userEvent, getByRole } = render( + const { getByText, userEvent, getByRole, queryByRole } = render( , @@ -235,18 +238,19 @@ describe('EmailSection', () => { const item = getByText(emails[0]); const menuButton = getMenuItemFromText(item); await act(async () => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await userEvent.click(menuButton!); }); getByRole('menuitem', { name: /remove email/i }); await userEvent.click(getByRole('menuitem', { name: /remove email/i })); - await waitFor(() => getByRole('heading', { name: /remove email address/i }), { timeout: 500 }); + await waitFor(() => getByRole('heading', { name: /remove email address/i })); await userEvent.click(getByRole('button', { name: /add email address/i })); - await waitFor(() => getByRole('heading', { name: /add email address/i }), { timeout: 500 }); + await waitFor(() => getByRole('heading', { name: /add email address/i })); - // Verify that the add email form is now visible - expect(getByRole('heading', { name: /add email address/i })).toBeInTheDocument(); + await waitFor(() => expect(queryByRole('heading', { name: /remove email address/i })).not.toBeInTheDocument()); + await waitFor(() => expect(queryByRole('heading', { name: /add email address/i })).toBeInTheDocument()); }); }); }); diff --git a/packages/clerk-js/src/ui/components/UserProfile/__tests__/EnterpriseAccountsSection.test.tsx b/packages/clerk-js/src/ui/components/UserProfile/__tests__/EnterpriseAccountsSection.test.tsx index edf402d529d..67ba687f4b3 100644 --- a/packages/clerk-js/src/ui/components/UserProfile/__tests__/EnterpriseAccountsSection.test.tsx +++ b/packages/clerk-js/src/ui/components/UserProfile/__tests__/EnterpriseAccountsSection.test.tsx @@ -1,8 +1,8 @@ +import { describe, it } from '@jest/globals'; import React from 'react'; -import { describe, expect, it } from 'vitest'; -import { render } from '../../../../vitestUtils'; -import { bindCreateFixtures } from '../../../utils/vitest/createFixtures'; +import { render } from '../../../../testUtils'; +import { bindCreateFixtures } from '../../../utils/test/createFixtures'; import { EnterpriseAccountsSection } from '../EnterpriseAccountsSection'; const { createFixtures } = bindCreateFixtures('UserProfile'); diff --git a/packages/clerk-js/src/ui/components/UserProfile/__tests__/MfaPage.test.tsx b/packages/clerk-js/src/ui/components/UserProfile/__tests__/MfaPage.test.tsx index 494ad23bafd..caa8ac01aa4 100644 --- a/packages/clerk-js/src/ui/components/UserProfile/__tests__/MfaPage.test.tsx +++ b/packages/clerk-js/src/ui/components/UserProfile/__tests__/MfaPage.test.tsx @@ -5,13 +5,13 @@ import type { TOTPResource, VerificationJSON, } from '@clerk/types'; +import { describe, it } from '@jest/globals'; import { act, waitFor } from '@testing-library/react'; -import { describe, expect, it, vi } from 'vitest'; import { CardStateProvider } from '@/ui/elements/contexts'; -import { render, screen } from '../../../../vitestUtils'; -import { bindCreateFixtures } from '../../../utils/vitest/createFixtures'; +import { render, runFakeTimers, screen } from '../../../../testUtils'; +import { bindCreateFixtures } from '../../../utils/test/createFixtures'; import { MfaSection } from '../MfaSection'; const { createFixtures } = bindCreateFixtures('UserProfile'); @@ -169,7 +169,7 @@ describe('MfaPage', () => { await waitFor(() => expect(queryByRole(/Add SMS code verification/i)).not.toBeInTheDocument()); }); - it.skip('Complete verification with authenticator app', async () => { + it('Complete verification with authenticator app', async () => { const { wrapper, fixtures } = await createFixtures(f => { f.withUser({ two_factor_enabled: true }); f.withAuthenticatorApp(); @@ -178,50 +178,42 @@ describe('MfaPage', () => { fixtures.clerk.user?.createTOTP.mockResolvedValue({} as TOTPResource); fixtures.clerk.user?.verifyTOTP.mockResolvedValue({} as TOTPResource); - vi.useFakeTimers(); - try { + await runFakeTimers(async timers => { const { getByText, userEvent, getByRole } = render(, { wrapper }); - await waitFor(() => getByText('Two-step verification'), { timeout: 200 }); + await waitFor(() => getByText('Two-step verification')); await act(async () => { await userEvent.click(getByRole('button', { name: /Add two-step verification/i })); }); - await waitFor(() => getByText(/authenticator app/i), { timeout: 200 }); + await waitFor(() => getByText(/authenticator app/i)); await userEvent.click(getByRole('menuitem', { name: /authenticator app/i })); - await waitFor(() => expect(getByText(/Add authenticator application/i)).toBeInTheDocument(), { timeout: 200 }); + await waitFor(() => expect(getByText(/Add authenticator application/i)).toBeInTheDocument()); - await waitFor(() => expect(getByRole('button', { name: /continue/i })).toBeInTheDocument(), { timeout: 200 }); + await waitFor(() => expect(getByRole('button', { name: /continue/i })).toBeInTheDocument()); await userEvent.click(getByRole('button', { name: /continue/i })); await userEvent.type(screen.getByRole('textbox', { name: /Enter verification code/i }), '123456'); - vi.runAllTimers(); - await waitFor( - () => { - expect(fixtures.clerk.user?.verifyTOTP).toHaveBeenCalled(); - }, - { timeout: 200 }, - ); - vi.runAllTimers(); - await waitFor( - () => - expect( - getByText( - /Two-step verification is now enabled. When signing in, you will need to enter a verification code from this authenticator as an additional step./i, - ), - ).toBeInTheDocument(), - { timeout: 200 }, + timers.runOnlyPendingTimers(); + await waitFor(() => { + expect(fixtures.clerk.user?.verifyTOTP).toHaveBeenCalled(); + }); + timers.runOnlyPendingTimers(); + await waitFor(() => + expect( + getByText( + /Two-step verification is now enabled. When signing in, you will need to enter a verification code from this authenticator as an additional step./i, + ), + ).toBeInTheDocument(), ); await userEvent.click(getByRole('button', { name: /finish/i })); - } finally { - vi.useRealTimers(); - } - }, 5000); + }); + }); }); describe('Regenerates', () => { - it.skip('Regenerates backup codes', async () => { + it('Regenerates backup codes', async () => { const { wrapper, fixtures } = await createFixtures(f => { f.withBackupCode(); f.withPhoneNumber({ second_factors: ['phone_code'], used_for_second_factor: true }); @@ -247,34 +239,33 @@ describe('MfaPage', () => { , { wrapper }, ); - await waitFor(() => getByText('Two-step verification'), { timeout: 500 }); + await waitFor(() => getByText('Two-step verification')); const itemButton = getByText(/backup codes/i)?.parentElement?.parentElement?.children[1]; expect(itemButton).toBeDefined(); await act(async () => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await userEvent.click(itemButton!); }); - await waitFor(() => getByText(/^regenerate$/i), { timeout: 500 }); + await waitFor(() => getByText(/^regenerate$/i)); await userEvent.click(getByText(/^regenerate$/i)); getByText('Add backup code verification'); - await waitFor( - () => - getByText( - 'Backup codes are now enabled. You can use one of these to sign in to your account, if you lose access to your authentication device. Each code can only be used once.', - ), - { timeout: 500 }, + await waitFor(() => + getByText( + 'Backup codes are now enabled. You can use one of these to sign in to your account, if you lose access to your authentication device. Each code can only be used once.', + ), ); expect(fixtures.clerk.user?.createBackupCode).toHaveBeenCalled(); await userEvent.click(getByRole('button', { name: /^finish$/i })); - }, 5000); + }); it.todo('Test the copy all/download/print buttons'); }); describe('Removes a verification', () => { - it.skip('Removes a phone verification', async () => { + it('Removes a phone verification', async () => { const { wrapper, fixtures } = await createFixtures(f => { f.withPhoneNumber({ second_factors: ['phone_code'], used_for_second_factor: true }); f.withUser({ @@ -298,16 +289,17 @@ describe('MfaPage', () => { , { wrapper }, ); - await waitFor(() => getByText('Two-step verification'), { timeout: 500 }); + await waitFor(() => getByText('Two-step verification')); const itemButton = getByText(/\+30 691 1111111/i)?.parentElement?.parentElement?.parentElement?.children[1]; expect(itemButton).toBeDefined(); await act(async () => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await userEvent.click(itemButton!); }); - await waitFor(() => getByText(/^remove$/i), { timeout: 500 }); + await waitFor(() => getByText(/^remove$/i)); await userEvent.click(getByText(/^remove$/i)); getByText(/remove two-step verification/i); getByText('Your account may not be as secure. Are you sure you want to continue?'); @@ -315,9 +307,9 @@ describe('MfaPage', () => { await userEvent.click(getByRole('button', { name: /^remove$/i })); expect(fixtures.clerk.user?.phoneNumbers[0].setReservedForSecondFactor).toHaveBeenCalledWith({ reserved: false }); - }, 5000); + }); - it.skip('Removes a authenticator app verification', async () => { + it('Removes a authenticator app verification', async () => { const { wrapper, fixtures } = await createFixtures(f => { f.withUser({ two_factor_enabled: true, totp_enabled: true }); f.withAuthenticatorApp(); @@ -331,16 +323,17 @@ describe('MfaPage', () => { , { wrapper }, ); - await waitFor(() => getByText('Two-step verification'), { timeout: 500 }); + await waitFor(() => getByText('Two-step verification')); const itemButton = getByText(/Authenticator application/i)?.parentElement?.parentElement?.children[1]; expect(itemButton).toBeDefined(); await act(async () => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await userEvent.click(itemButton!); }); - await waitFor(() => getByText(/^remove$/i), { timeout: 500 }); + await waitFor(() => getByText(/^remove$/i)); await userEvent.click(getByText(/^remove$/i)); getByText(/remove two-step verification/i); getByText('Your account may not be as secure. Are you sure you want to continue?'); @@ -349,11 +342,11 @@ describe('MfaPage', () => { await userEvent.click(getByRole('button', { name: /^remove$/i })); expect(fixtures.clerk.user?.disableTOTP).toHaveBeenCalled(); - }, 5000); + }); }); describe('Handles opening/closing actions', () => { - it.skip('closes remove sms code form when add two-step verification action is clicked', async () => { + it('closes remove sms code form when add two-step verification action is clicked', async () => { const { wrapper } = await createFixtures(f => { f.withPhoneNumber({ second_factors: ['phone_code'], used_for_second_factor: true }); f.withUser({ @@ -375,16 +368,17 @@ describe('MfaPage', () => { , { wrapper }, ); - await waitFor(() => getByText('Two-step verification'), { timeout: 500 }); + await waitFor(() => getByText('Two-step verification')); const itemButton = getByText(/\+30 691 1111111/i)?.parentElement?.parentElement?.parentElement?.children[1]; expect(itemButton).toBeDefined(); await act(async () => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await userEvent.click(itemButton!); }); - await waitFor(() => getByText(/^remove$/i), { timeout: 500 }); + await waitFor(() => getByText(/^remove$/i)); await userEvent.click(getByText(/^remove$/i)); await expect(queryByRole('heading', { name: /remove two-step verification/i })).toBeInTheDocument(); @@ -393,10 +387,9 @@ describe('MfaPage', () => { await userEvent.click(getByRole('button', { name: /Add two-step verification/i })); }); - await waitFor( - () => expect(queryByRole('heading', { name: /remove two-step verification/i })).not.toBeInTheDocument(), - { timeout: 500 }, + await waitFor(() => + expect(queryByRole('heading', { name: /remove two-step verification/i })).not.toBeInTheDocument(), ); - }, 5000); + }); }); }); diff --git a/packages/clerk-js/src/ui/components/UserProfile/__tests__/PasskeysSection.test.tsx b/packages/clerk-js/src/ui/components/UserProfile/__tests__/PasskeysSection.test.tsx index a4a5b8ae6b5..5dd5b22101d 100644 --- a/packages/clerk-js/src/ui/components/UserProfile/__tests__/PasskeysSection.test.tsx +++ b/packages/clerk-js/src/ui/components/UserProfile/__tests__/PasskeysSection.test.tsx @@ -1,11 +1,11 @@ import type { PasskeyJSON, PasskeyResource } from '@clerk/types'; +import { describe, it } from '@jest/globals'; import { act } from '@testing-library/react'; -import { describe, expect, it, vi } from 'vitest'; import { CardStateProvider } from '@/ui/elements/contexts'; -import { render, waitFor } from '../../../../vitestUtils'; -import { bindCreateFixtures } from '../../../utils/vitest/createFixtures'; +import { render, waitFor } from '../../../../testUtils'; +import { bindCreateFixtures } from '../../../utils/test/createFixtures'; import { PasskeySection } from '../PasskeySection'; const { createFixtures } = bindCreateFixtures('UserProfile'); @@ -36,7 +36,7 @@ const getMenuItemFromText = (element: HTMLElement) => { describe('PasskeySection', () => { it('renders the section', async () => { const { wrapper, fixtures } = await createFixtures(withPasskeys); - + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion fixtures.clerk.user!.getSessions.mockReturnValue(Promise.resolve([])); const { getByText } = render( @@ -92,6 +92,7 @@ describe('PasskeySection', () => { const item = getByText(passkeys[0].name); const menuButton = getMenuItemFromText(item); await act(async () => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await userEvent.click(menuButton!); }); @@ -114,6 +115,7 @@ describe('PasskeySection', () => { const item = getByText(passkeys[0].name); const menuButton = getMenuItemFromText(item); await act(async () => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await userEvent.click(menuButton!); }); @@ -141,6 +143,7 @@ describe('PasskeySection', () => { const item = getByText(passkeys[0].name); const menuButton = getMenuItemFromText(item); await act(async () => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await userEvent.click(menuButton!); }); @@ -152,24 +155,22 @@ describe('PasskeySection', () => { it('removes a passkey', async () => { const { wrapper, fixtures } = await createFixtures(withPasskeys); - const { getByText, userEvent, getByRole } = render( + const { getByText, userEvent, getByRole, queryByRole } = render( , { wrapper }, ); - const deleteMock = fixtures.clerk.user?.passkeys?.[0]?.delete; - if (deleteMock) { - vi.mocked(deleteMock).mockResolvedValue({ - object: 'passkey', - deleted: true, - }); - } + fixtures.clerk.user?.passkeys[0].delete.mockResolvedValue({ + object: 'passkey', + deleted: true, + }); const item = getByText(passkeys[0].name); const menuButton = getMenuItemFromText(item); await act(async () => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await userEvent.click(menuButton!); }); @@ -179,12 +180,14 @@ describe('PasskeySection', () => { await userEvent.click(getByRole('button', { name: /remove/i })); expect(fixtures.clerk.user?.passkeys[0].delete).toHaveBeenCalled(); + + await waitFor(() => expect(queryByRole('heading', { name: /remove passkey/i })).not.toBeInTheDocument()); }); describe('Form buttons', () => { it('hides screen when when pressing cancel', async () => { const { wrapper } = await createFixtures(withPasskeys); - const { getByRole, userEvent, getByText } = render( + const { getByRole, userEvent, getByText, queryByRole } = render( , @@ -194,6 +197,7 @@ describe('PasskeySection', () => { const item = getByText(passkeys[0].name); const menuButton = getMenuItemFromText(item); await act(async () => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await userEvent.click(menuButton!); }); @@ -202,10 +206,7 @@ describe('PasskeySection', () => { await waitFor(() => getByRole('heading', { name: /remove passkey/i })); await userEvent.click(getByRole('button', { name: /cancel$/i })); - // The form should close when cancel is clicked - await waitFor(() => { - expect(getByRole('button', { name: /add a passkey/i })).toBeInTheDocument(); - }); + await waitFor(() => expect(queryByRole('heading', { name: /remove passkey/i })).not.toBeInTheDocument()); }); }); }); @@ -213,7 +214,7 @@ describe('PasskeySection', () => { describe('Handles opening/closing actions', () => { it('closes remove passkey form when add a passkey action is clicked', async () => { const { wrapper } = await createFixtures(withPasskeys); - const { getByRole, userEvent, getByText } = render( + const { getByRole, userEvent, getByText, queryByRole } = render( , @@ -223,6 +224,7 @@ describe('PasskeySection', () => { const item = getByText(passkeys[0].name); const menuButton = getMenuItemFromText(item); await act(async () => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await userEvent.click(menuButton!); }); @@ -230,12 +232,11 @@ describe('PasskeySection', () => { await userEvent.click(getByRole('menuitem', { name: /remove/i })); await waitFor(() => getByRole('heading', { name: /remove passkey/i })); + await waitFor(() => expect(queryByRole('heading', { name: /remove passkey/i })).toBeInTheDocument()); + await userEvent.click(getByRole('button', { name: /add a passkey/i })); - // The form should close when add passkey is clicked - await waitFor(() => { - expect(getByRole('button', { name: /add a passkey/i })).toBeInTheDocument(); - }); + await waitFor(() => expect(queryByRole('heading', { name: /remove passkey/i })).not.toBeInTheDocument()); }); }); }); diff --git a/packages/clerk-js/src/ui/components/UserProfile/__tests__/PasswordSection.test.tsx b/packages/clerk-js/src/ui/components/UserProfile/__tests__/PasswordSection.test.tsx index ce7c0039c72..b30b6d95e4b 100644 --- a/packages/clerk-js/src/ui/components/UserProfile/__tests__/PasswordSection.test.tsx +++ b/packages/clerk-js/src/ui/components/UserProfile/__tests__/PasswordSection.test.tsx @@ -1,9 +1,10 @@ -import { describe, expect, it } from 'vitest'; +import { describe, it } from '@jest/globals'; import { CardStateProvider } from '@/ui/elements/contexts'; -import { fireEvent, render, screen, waitFor } from '../../../../vitestUtils'; -import { bindCreateFixtures } from '../../../utils/vitest/createFixtures'; +import { fireEvent, render, screen, waitFor } from '../../../../testUtils'; +import { bindCreateFixtures } from '../../../utils/test/createFixtures'; +import { runFakeTimers } from '../../../utils/test/runFakeTimers'; import { PasswordSection } from '../PasswordSection'; const { createFixtures } = bindCreateFixtures('UserProfile'); @@ -58,11 +59,11 @@ describe('PasswordSection', () => { getByLabelText(/It is recommended to sign out of all other devices which may have used your old password./i); }); - it('sets a new password and calls the appropriate function', async () => { + it('sets a new password and calls the appropriate function and closes', async () => { const { wrapper, fixtures } = await createFixtures(initConfig); fixtures.clerk.user?.updatePassword.mockResolvedValue({}); - const { getByRole, userEvent, getByLabelText } = render(, { wrapper }); + const { getByRole, userEvent, getByLabelText, queryByRole } = render(, { wrapper }); await userEvent.click(getByRole('button', { name: /set password/i })); await waitFor(() => getByRole('heading', { name: /set password/i })); @@ -73,6 +74,8 @@ describe('PasswordSection', () => { newPassword: 'testtest', signOutOfOtherSessions: true, }); + await waitFor(() => getByRole('button', { name: /set password/i })); + expect(queryByRole('heading', { name: /set password/i })).not.toBeInTheDocument(); }); it('renders a hidden identifier field', async () => { @@ -270,11 +273,8 @@ describe('PasswordSection', () => { expect(queryByRole('button', { name: /set password/i })).not.toBeInTheDocument(); await userEvent.click(getByRole('button', { name: /cancel$/i })); - - // Wait for the form to close and the button to reappear - await waitFor(() => { - expect(getByRole('button', { name: /set password/i })).toBeInTheDocument(); - }); + await waitFor(() => getByRole('button', { name: /set password/i })); + expect(queryByRole('heading', { name: /set password/i })).not.toBeInTheDocument(); }); }); }); @@ -295,11 +295,11 @@ describe('PasswordSection', () => { getByLabelText(/It is recommended to sign out of all other devices which may have used your old password./i); }); - it('changes a new password and calls the appropriate function', async () => { + it('changes a new password and calls the appropriate function and closes', async () => { const { wrapper, fixtures } = await createFixtures(updatePasswordConfig); fixtures.clerk.user?.updatePassword.mockResolvedValue({}); - const { getByRole, userEvent, getByLabelText } = render(, { wrapper }); + const { getByRole, userEvent, getByLabelText, queryByRole } = render(, { wrapper }); await userEvent.click(getByRole('button', { name: /update password/i })); await waitFor(() => getByRole('heading', { name: /update password/i })); @@ -318,6 +318,9 @@ describe('PasswordSection', () => { signOutOfOtherSessions: true, }); }); + + await waitFor(() => getByRole('button', { name: /update password/i })); + expect(queryByRole('heading', { name: /update password/i })).not.toBeInTheDocument(); }, 10_000); it('current password is not required when Reverification enabled', async () => { @@ -329,7 +332,7 @@ describe('PasswordSection', () => { const { wrapper, fixtures } = await createFixtures(config); fixtures.clerk.user?.updatePassword.mockResolvedValue({}); - const { getByRole, userEvent, getByLabelText } = render(, { wrapper }); + const { getByRole, userEvent, getByLabelText, queryByRole } = render(, { wrapper }); await userEvent.click(getByRole('button', { name: /update password/i })); await waitFor(() => getByRole('heading', { name: /update password/i })); @@ -340,6 +343,8 @@ describe('PasswordSection', () => { newPassword: 'testtest', signOutOfOtherSessions: true, }); + await waitFor(() => getByRole('button', { name: /update password/i })); + expect(queryByRole('heading', { name: /update password/i })).not.toBeInTheDocument(); }); describe('with Enterprise SSO', () => { @@ -529,69 +534,93 @@ describe('PasswordSection', () => { it('results in error if the password is too small', async () => { const { wrapper } = await createFixtures(initConfig); - const { userEvent, getByRole } = render(, { wrapper }); - await userEvent.click(getByRole('button', { name: /set password/i })); - await waitFor(() => getByRole('heading', { name: /set password/i })); + await runFakeTimers(async () => { + const { userEvent, getByRole } = render(, { wrapper }); + await userEvent.click(getByRole('button', { name: /set password/i })); + await waitFor(() => getByRole('heading', { name: /set password/i })); - await userEvent.type(screen.getByLabelText(/new password/i), 'test'); - const confirmField = screen.getByLabelText(/confirm password/i); - await userEvent.type(confirmField, 'test'); - fireEvent.blur(confirmField); - await waitFor(() => { - screen.getByText(/or more/i); + await userEvent.type(screen.getByLabelText(/new password/i), 'test'); + const confirmField = screen.getByLabelText(/confirm password/i); + await userEvent.type(confirmField, 'test'); + fireEvent.blur(confirmField); + await waitFor(() => { + screen.getByText(/or more/i); + }); }); }); - it('results in error if the passwords do not match and persists', async () => { + it('results in error if the password is too small', async () => { const { wrapper } = await createFixtures(initConfig); - const { userEvent, getByRole } = render(, { wrapper }); - await userEvent.click(getByRole('button', { name: /set password/i })); - await waitFor(() => getByRole('heading', { name: /set password/i })); + await runFakeTimers(async () => { + const { userEvent, getByRole } = render(, { wrapper }); + await userEvent.click(getByRole('button', { name: /set password/i })); + await waitFor(() => getByRole('heading', { name: /set password/i })); - await userEvent.type(screen.getByLabelText(/new password/i), 'testewrewr'); - const confirmField = screen.getByLabelText(/confirm password/i); - await userEvent.type(confirmField, 'testrwerrwqrwe'); - fireEvent.blur(confirmField); - await waitFor(() => { - screen.getByText(`Passwords don't match.`); + await userEvent.type(screen.getByLabelText(/new password/i), 'test'); + const confirmField = screen.getByLabelText(/confirm password/i); + await userEvent.type(confirmField, 'test'); + fireEvent.blur(confirmField); + await waitFor(() => { + screen.getByText(/or more/i); + }); }); + }); - await userEvent.clear(confirmField); - await waitFor(() => { - screen.getByText(`Passwords don't match.`); + it('results in error if the passwords do not match and persists', async () => { + const { wrapper } = await createFixtures(initConfig); + + await runFakeTimers(async () => { + const { userEvent, getByRole } = render(, { wrapper }); + await userEvent.click(getByRole('button', { name: /set password/i })); + await waitFor(() => getByRole('heading', { name: /set password/i })); + + await userEvent.type(screen.getByLabelText(/new password/i), 'testewrewr'); + const confirmField = screen.getByLabelText(/confirm password/i); + await userEvent.type(confirmField, 'testrwerrwqrwe'); + fireEvent.blur(confirmField); + await waitFor(() => { + screen.getByText(`Passwords don't match.`); + }); + + await userEvent.clear(confirmField); + await waitFor(() => { + screen.getByText(`Passwords don't match.`); + }); }); - }); + }, 10000); it(`Displays "Password match" when password match and removes it if they stop`, async () => { const { wrapper } = await createFixtures(initConfig); - const { userEvent, getByRole, getByLabelText, queryByText } = render(, { wrapper }); - await userEvent.click(getByRole('button', { name: /set password/i })); - await waitFor(() => getByRole('heading', { name: /set password/i })); - const passwordField = getByLabelText(/new password/i); + await runFakeTimers(async () => { + const { userEvent, getByRole, getByLabelText, queryByText } = render(, { wrapper }); + await userEvent.click(getByRole('button', { name: /set password/i })); + await waitFor(() => getByRole('heading', { name: /set password/i })); + const passwordField = getByLabelText(/new password/i); - await userEvent.type(passwordField, 'testewrewr'); - const confirmField = getByLabelText(/confirm password/i); - await waitFor(() => { - expect(queryByText(`Passwords match.`)).not.toBeInTheDocument(); - }); + await userEvent.type(passwordField, 'testewrewr'); + const confirmField = getByLabelText(/confirm password/i); + await waitFor(() => { + expect(queryByText(`Passwords match.`)).not.toBeInTheDocument(); + }); - await userEvent.type(confirmField, 'testewrewr'); - await waitFor(() => { - expect(queryByText(`Passwords match.`)).toBeInTheDocument(); - }); + await userEvent.type(confirmField, 'testewrewr'); + await waitFor(() => { + expect(queryByText(`Passwords match.`)).toBeInTheDocument(); + }); - await userEvent.type(confirmField, 'testrwerrwqrwe'); - await waitFor(() => { - expect(queryByText(`Passwords match.`)).not.toBeVisible(); - }); + await userEvent.type(confirmField, 'testrwerrwqrwe'); + await waitFor(() => { + expect(queryByText(`Passwords match.`)).not.toBeVisible(); + }); - await userEvent.type(passwordField, 'testrwerrwqrwe'); - fireEvent.blur(confirmField); - await waitFor(() => { - screen.getByText(`Passwords match.`); + await userEvent.type(passwordField, 'testrwerrwqrwe'); + fireEvent.blur(confirmField); + await waitFor(() => { + screen.getByText(`Passwords match.`); + }); }); - }); + }, 10000); }); }); diff --git a/packages/clerk-js/src/ui/components/UserProfile/__tests__/PhoneSection.test.tsx b/packages/clerk-js/src/ui/components/UserProfile/__tests__/PhoneSection.test.tsx index c5bd43d41c9..43e88570926 100644 --- a/packages/clerk-js/src/ui/components/UserProfile/__tests__/PhoneSection.test.tsx +++ b/packages/clerk-js/src/ui/components/UserProfile/__tests__/PhoneSection.test.tsx @@ -1,10 +1,10 @@ -import { act } from '@testing-library/react'; -import { describe, expect, it } from 'vitest'; +import { describe, it } from '@jest/globals'; +import { act, waitFor } from '@testing-library/react'; import { CardStateProvider } from '@/ui/elements/contexts'; -import { render, screen } from '../../../../vitestUtils'; -import { bindCreateFixtures } from '../../../utils/vitest/createFixtures'; +import { render, screen } from '../../../../testUtils'; +import { bindCreateFixtures } from '../../../utils/test/createFixtures'; import { PhoneSection } from '../PhoneSection'; const { createFixtures } = bindCreateFixtures('UserProfile'); @@ -54,9 +54,9 @@ describe('PhoneSection', () => { it('renders add phone screen', async () => { const { wrapper } = await createFixtures(initConfig); - const { findByRole, getByRole, userEvent, getByLabelText, getByText } = render(, { wrapper }); + const { getByRole, userEvent, getByLabelText, getByText } = render(, { wrapper }); await userEvent.click(getByRole('button', { name: 'Add phone number' })); - await findByRole('heading', { name: /Add phone number/i }); + await waitFor(() => getByRole('heading', { name: /Add phone number/i })); getByLabelText(/phone number/i); getByText(/A text message containing a verification code will be sent to this phone number./i); @@ -66,9 +66,9 @@ describe('PhoneSection', () => { it('create a new phone number', async () => { const { wrapper, fixtures } = await createFixtures(initConfig); - const { findByRole, getByRole, userEvent, getByLabelText } = render(, { wrapper }); + const { getByRole, userEvent, getByLabelText } = render(, { wrapper }); await userEvent.click(getByRole('button', { name: 'Add phone number' })); - await findByRole('heading', { name: /Add phone number/i }); + await waitFor(() => getByRole('heading', { name: /Add phone number/i })); fixtures.clerk.user?.createPhoneNumber.mockReturnValueOnce(Promise.resolve({} as any)); @@ -80,9 +80,9 @@ describe('PhoneSection', () => { describe('Form buttons', () => { it('save button is disabled by default', async () => { const { wrapper } = await createFixtures(initConfig); - const { findByRole, getByRole, userEvent } = render(, { wrapper }); + const { getByRole, userEvent } = render(, { wrapper }); await userEvent.click(getByRole('button', { name: 'Add phone number' })); - await findByRole('heading', { name: /Add phone number/i }); + await waitFor(() => getByRole('heading', { name: /Add phone number/i })); expect(screen.getByText(/add$/i, { exact: false }).closest('button')).toHaveAttribute('disabled'); }); @@ -90,13 +90,14 @@ describe('PhoneSection', () => { it('hides screen when when pressing cancel', async () => { const { wrapper } = await createFixtures(initConfig); - const { userEvent, findByRole, getByRole, queryByRole } = render(, { wrapper }); + const { userEvent, getByRole, queryByRole } = render(, { wrapper }); await userEvent.click(getByRole('button', { name: /Add phone number/i })); - await findByRole('heading', { name: /Add phone number/i }); + await waitFor(() => getByRole('heading', { name: /Add phone number/i })); expect(queryByRole('button', { name: /Add phone number/i })).not.toBeInTheDocument(); await userEvent.click(screen.getByRole('button', { name: /cancel$/i })); - await findByRole('button', { name: /Add phone number/i }); + await waitFor(() => getByRole('button', { name: /Add phone number/i })); + expect(queryByRole('heading', { name: /Add phone number/i })).not.toBeInTheDocument(); }); }); }); @@ -113,7 +114,7 @@ describe('PhoneSection', () => { }); }); - const { findByRole, getByText, userEvent, getByRole } = render( + const { getByText, userEvent, getByRole } = render( , @@ -123,17 +124,18 @@ describe('PhoneSection', () => { const item = getByText(number); const menuButton = getMenuItemFromText(item); await act(async () => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await userEvent.click(menuButton!); }); getByRole('menuitem', { name: /remove phone number/i }); await userEvent.click(getByRole('menuitem', { name: /remove phone number/i })); - await findByRole('heading', { name: /Remove phone number/i }); + await waitFor(() => getByRole('heading', { name: /Remove phone number/i })); }); it('removes a phone number', async () => { const { wrapper, fixtures } = await createFixtures(withNumberCofig); - const { findByRole, getByText, userEvent, getByRole } = render( + const { getByText, userEvent, getByRole, queryByRole } = render( , @@ -145,21 +147,24 @@ describe('PhoneSection', () => { const item = getByText(numbers[0]); const menuButton = getMenuItemFromText(item); await act(async () => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await userEvent.click(menuButton!); }); getByRole('menuitem', { name: /remove phone number/i }); await userEvent.click(getByRole('menuitem', { name: /remove phone number/i })); - await findByRole('heading', { name: /Remove phone number/i }); + await waitFor(() => getByRole('heading', { name: /Remove phone number/i })); await userEvent.click(getByRole('button', { name: /remove/i })); expect(fixtures.clerk.user?.phoneNumbers[0].destroy).toHaveBeenCalled(); + + await waitFor(() => expect(queryByRole('heading', { name: /Remove phone number/i })).not.toBeInTheDocument()); }); describe('Form buttons', () => { it('save button is not disabled by default', async () => { const { wrapper } = await createFixtures(withNumberCofig); - const { findByRole, getByRole, userEvent, getByText } = render( + const { getByRole, userEvent, getByText } = render( , @@ -169,18 +174,19 @@ describe('PhoneSection', () => { const item = getByText(numbers[0]); const menuButton = getMenuItemFromText(item); await act(async () => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await userEvent.click(menuButton!); }); getByRole('menuitem', { name: /remove phone number/i }); await userEvent.click(getByRole('menuitem', { name: /remove phone number/i })); - await findByRole('heading', { name: /Remove phone number/i }); + await waitFor(() => getByRole('heading', { name: /Remove phone number/i })); expect(getByRole('button', { name: /remove$/i })).not.toHaveAttribute('disabled'); }); it('hides screen when when pressing cancel', async () => { const { wrapper } = await createFixtures(withNumberCofig); - const { findByRole, findByText, getByRole, userEvent, getByText } = render( + const { getByRole, userEvent, getByText, queryByRole } = render( , @@ -190,14 +196,15 @@ describe('PhoneSection', () => { const item = getByText(numbers[0]); const menuButton = getMenuItemFromText(item); await act(async () => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await userEvent.click(menuButton!); }); getByRole('menuitem', { name: /remove phone number/i }); await userEvent.click(getByRole('menuitem', { name: /remove phone number/i })); - await findByRole('heading', { name: /Remove phone number/i }); + await waitFor(() => getByRole('heading', { name: /Remove phone number/i })); await userEvent.click(screen.getByRole('button', { name: /cancel$/i })); - await findByText(/Phone numbers/i); + expect(queryByRole('heading', { name: /Remove phone number/i })).not.toBeInTheDocument(); }); }); }); @@ -205,7 +212,7 @@ describe('PhoneSection', () => { describe('Handles opening/closing actions', () => { it('closes add phone number form when remove an phone number action is clicked', async () => { const { wrapper, fixtures } = await createFixtures(withNumberCofig); - const { findByRole, getByText, userEvent, getByRole, queryByRole } = render( + const { getByText, userEvent, getByRole, queryByRole } = render( , @@ -215,24 +222,26 @@ describe('PhoneSection', () => { fixtures.clerk.user?.phoneNumbers[0].destroy.mockResolvedValue(); await userEvent.click(getByRole('button', { name: /add phone number/i })); - await findByRole('heading', { name: /add phone number/i }); + await waitFor(() => getByRole('heading', { name: /add phone number/i })); const item = getByText(numbers[0]); const menuButton = getMenuItemFromText(item); await act(async () => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await userEvent.click(menuButton!); }); getByRole('menuitem', { name: /remove phone number/i }); await userEvent.click(getByRole('menuitem', { name: /remove phone number/i })); - await findByRole('heading', { name: /remove phone number/i }); + await waitFor(() => getByRole('heading', { name: /remove phone number/i })); - expect(queryByRole('heading', { name: /remove phone number/i })).toBeInTheDocument(); + await waitFor(() => expect(queryByRole('heading', { name: /remove phone number/i })).toBeInTheDocument()); + await waitFor(() => expect(queryByRole('heading', { name: /add phone number/i })).not.toBeInTheDocument()); }); it('closes remove phone number form when add phone number action is clicked', async () => { const { wrapper, fixtures } = await createFixtures(withNumberCofig); - const { findByRole, getByText, userEvent, getByRole, queryByRole } = render( + const { getByText, userEvent, getByRole, queryByRole } = render( , @@ -244,17 +253,19 @@ describe('PhoneSection', () => { const item = getByText(numbers[0]); const menuButton = getMenuItemFromText(item); await act(async () => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await userEvent.click(menuButton!); }); getByRole('menuitem', { name: /remove phone number/i }); await userEvent.click(getByRole('menuitem', { name: /remove phone number/i })); - await findByRole('heading', { name: /remove phone number/i }); + await waitFor(() => getByRole('heading', { name: /remove phone number/i })); await userEvent.click(getByRole('button', { name: /add phone number/i })); - await findByRole('heading', { name: /add phone number/i }); + await waitFor(() => getByRole('heading', { name: /add phone number/i })); - expect(queryByRole('heading', { name: /add phone number/i })).toBeInTheDocument(); + await waitFor(() => expect(queryByRole('heading', { name: /remove phone number/i })).not.toBeInTheDocument()); + await waitFor(() => expect(queryByRole('heading', { name: /add phone number/i })).toBeInTheDocument()); }); }); diff --git a/packages/clerk-js/src/ui/components/UserProfile/__tests__/SecurityPage.test.tsx b/packages/clerk-js/src/ui/components/UserProfile/__tests__/SecurityPage.test.tsx index 55901023af3..a06a8a738c1 100644 --- a/packages/clerk-js/src/ui/components/UserProfile/__tests__/SecurityPage.test.tsx +++ b/packages/clerk-js/src/ui/components/UserProfile/__tests__/SecurityPage.test.tsx @@ -1,10 +1,10 @@ import type { SessionWithActivitiesResource } from '@clerk/types'; +import { describe, it } from '@jest/globals'; import { within } from '@testing-library/dom'; -import { beforeEach, describe, expect, it, vi } from 'vitest'; -import { render, screen, waitFor } from '../../../../vitestUtils'; +import { render, screen, waitFor } from '../../../../testUtils'; import { clearFetchCache } from '../../../hooks'; -import { bindCreateFixtures } from '../../../utils/vitest/createFixtures'; +import { bindCreateFixtures } from '../../../utils/test/createFixtures'; import { SecurityPage } from '../SecurityPage'; const { createFixtures } = bindCreateFixtures('UserProfile'); @@ -282,7 +282,7 @@ describe('SecurityPage', () => { last_name: 'Clerk', }); }); - + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion fixtures.clerk.user!.getSessions.mockReturnValue( Promise.resolve([ { @@ -302,7 +302,7 @@ describe('SecurityPage', () => { isMobile: false, }, actor: null, - revoke: vi.fn().mockResolvedValue({}), + revoke: jest.fn().mockResolvedValue({}), } as any as SessionWithActivitiesResource, { pathRoot: '/me/sessions', @@ -321,7 +321,7 @@ describe('SecurityPage', () => { isMobile: false, }, actor: null, - revoke: vi.fn().mockResolvedValue({}), + revoke: jest.fn().mockResolvedValue({}), } as any as SessionWithActivitiesResource, { pathRoot: '/me/sessions', @@ -340,7 +340,7 @@ describe('SecurityPage', () => { isMobile: false, }, actor: null, - revoke: vi.fn().mockResolvedValue({}), + revoke: jest.fn().mockResolvedValue({}), } as any as SessionWithActivitiesResource, ]), ); @@ -357,7 +357,7 @@ describe('SecurityPage', () => { devices.forEach(d => { const elem = d.parentElement?.parentElement?.parentElement?.parentElement; expect(elem).toBeDefined(); - + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion, jest/unbound-method const { getByText } = within(elem!); getByText(/107.0.0.0/i); getByText(/Athens/i); diff --git a/packages/clerk-js/src/ui/components/UserProfile/__tests__/UserProfile.test.tsx b/packages/clerk-js/src/ui/components/UserProfile/__tests__/UserProfile.test.tsx index dd4d5881813..1fd599ffdf0 100644 --- a/packages/clerk-js/src/ui/components/UserProfile/__tests__/UserProfile.test.tsx +++ b/packages/clerk-js/src/ui/components/UserProfile/__tests__/UserProfile.test.tsx @@ -1,8 +1,8 @@ import type { CustomPage } from '@clerk/types'; -import { describe, expect, it } from 'vitest'; +import { describe, it } from '@jest/globals'; -import { render, screen, waitFor } from '../../../../vitestUtils'; -import { bindCreateFixtures } from '../../../utils/vitest/createFixtures'; +import { render, screen, waitFor } from '../../../../testUtils'; +import { bindCreateFixtures } from '../../../utils/test/createFixtures'; import { UserProfile } from '../'; const { createFixtures } = bindCreateFixtures('UserProfile'); diff --git a/packages/clerk-js/src/ui/components/UserProfile/__tests__/UserProfileSection.test.tsx b/packages/clerk-js/src/ui/components/UserProfile/__tests__/UserProfileSection.test.tsx index 0c56c1e79f6..1c28747a32d 100644 --- a/packages/clerk-js/src/ui/components/UserProfile/__tests__/UserProfileSection.test.tsx +++ b/packages/clerk-js/src/ui/components/UserProfile/__tests__/UserProfileSection.test.tsx @@ -1,8 +1,8 @@ import type { ImageResource } from '@clerk/types'; -import { describe, expect, it } from 'vitest'; +import { describe, it } from '@jest/globals'; -import { render, screen, waitFor } from '../../../../vitestUtils'; -import { bindCreateFixtures } from '../../../utils/vitest/createFixtures'; +import { render, screen, waitFor } from '../../../../testUtils'; +import { bindCreateFixtures } from '../../../utils/test/createFixtures'; import { UserProfileSection } from '../UserProfileSection'; const { createFixtures } = bindCreateFixtures('UserProfileSection'); diff --git a/packages/clerk-js/src/ui/components/UserProfile/__tests__/UsernameSection.test.tsx b/packages/clerk-js/src/ui/components/UserProfile/__tests__/UsernameSection.test.tsx index 9e75d350eeb..47f16d055ef 100644 --- a/packages/clerk-js/src/ui/components/UserProfile/__tests__/UsernameSection.test.tsx +++ b/packages/clerk-js/src/ui/components/UserProfile/__tests__/UsernameSection.test.tsx @@ -1,9 +1,9 @@ import type { UserResource } from '@clerk/types'; +import { describe, it } from '@jest/globals'; import React from 'react'; -import { describe, expect, it } from 'vitest'; -import { render, screen, waitFor } from '../../../../vitestUtils'; -import { bindCreateFixtures } from '../../../utils/vitest/createFixtures'; +import { render, screen, waitFor } from '../../../../testUtils'; +import { bindCreateFixtures } from '../../../utils/test/createFixtures'; import { UsernameSection } from '../UsernameSection'; const { createFixtures } = bindCreateFixtures('UsernameSection'); diff --git a/packages/clerk-js/src/ui/components/UserProfile/__tests__/utils.test.ts b/packages/clerk-js/src/ui/components/UserProfile/__tests__/utils.spec.ts similarity index 100% rename from packages/clerk-js/src/ui/components/UserProfile/__tests__/utils.test.ts rename to packages/clerk-js/src/ui/components/UserProfile/__tests__/utils.spec.ts diff --git a/packages/clerk-js/src/ui/components/UserVerification/__tests__/UVFactorOne.test.tsx b/packages/clerk-js/src/ui/components/UserVerification/__tests__/UVFactorOne.test.tsx index 373856b7e90..c93e5d97f80 100644 --- a/packages/clerk-js/src/ui/components/UserVerification/__tests__/UVFactorOne.test.tsx +++ b/packages/clerk-js/src/ui/components/UserVerification/__tests__/UVFactorOne.test.tsx @@ -1,9 +1,9 @@ +import { describe, it } from '@jest/globals'; import { waitFor } from '@testing-library/react'; -import { beforeEach, describe, expect, it } from 'vitest'; -import { render, screen } from '../../../../vitestUtils'; +import { render, screen } from '../../../../testUtils'; import { clearFetchCache } from '../../../hooks'; -import { bindCreateFixtures } from '../../../utils/vitest/createFixtures'; +import { bindCreateFixtures } from '../../../utils/test/createFixtures'; import { UserVerificationFactorOne } from '../UserVerificationFactorOne'; const { createFixtures } = bindCreateFixtures('UserVerification'); @@ -50,7 +50,7 @@ describe('UserVerificationFactorOne', () => { getByLabelText(/Enter verification code/i); }); - expect(fixtures.session?.prepareFirstFactorVerification).toHaveBeenCalled(); + expect(fixtures.session?.prepareFirstFactorVerification).toHaveBeenCalledTimes(1); }); it('renders the component for with strategy:phone_code', async () => { @@ -70,7 +70,7 @@ describe('UserVerificationFactorOne', () => { getByLabelText(/Enter verification code/i); }); - expect(fixtures.session?.prepareFirstFactorVerification).toHaveBeenCalled(); + expect(fixtures.session?.prepareFirstFactorVerification).toHaveBeenCalledTimes(1); }); describe('Submitting', () => { diff --git a/packages/clerk-js/src/ui/components/UserVerification/__tests__/UVFactorTwo.test.tsx b/packages/clerk-js/src/ui/components/UserVerification/__tests__/UVFactorTwo.test.tsx index b719c6719ae..b84f61c7053 100644 --- a/packages/clerk-js/src/ui/components/UserVerification/__tests__/UVFactorTwo.test.tsx +++ b/packages/clerk-js/src/ui/components/UserVerification/__tests__/UVFactorTwo.test.tsx @@ -1,9 +1,9 @@ +import { describe, it } from '@jest/globals'; import { waitFor } from '@testing-library/react'; -import { beforeEach, describe, expect, it, vi } from 'vitest'; -import { render } from '../../../../vitestUtils'; +import { render, runFakeTimers } from '../../../../testUtils'; import { clearFetchCache } from '../../../hooks'; -import { bindCreateFixtures } from '../../../utils/vitest/createFixtures'; +import { bindCreateFixtures } from '../../../utils/test/createFixtures'; import { UserVerificationFactorTwo } from '../UserVerificationFactorTwo'; const { createFixtures } = bindCreateFixtures('UserVerification'); @@ -20,15 +20,15 @@ describe('UserVerificationFactorTwo', () => { const { wrapper, fixtures } = await createFixtures(f => { f.withUser({ username: 'clerkuser' }); }); - vi.spyOn(fixtures.session!, 'startVerification').mockResolvedValue({ + fixtures.session?.startVerification.mockResolvedValue({ status: 'needs_second_factor', supportedSecondFactors: [{ strategy: 'phone_code' }], - } as any); + }); - vi.spyOn(fixtures.session!, 'prepareSecondFactorVerification').mockResolvedValue({ + fixtures.session?.prepareSecondFactorVerification.mockResolvedValue({ status: 'needs_second_factor', supportedSecondFactors: [{ strategy: 'phone_code' }], - } as any); + }); const { getByText, getAllByTestId } = render(, { wrapper }); @@ -43,15 +43,15 @@ describe('UserVerificationFactorTwo', () => { const { wrapper, fixtures } = await createFixtures(f => { f.withUser({ username: 'clerkuser' }); }); - vi.spyOn(fixtures.session!, 'startVerification').mockResolvedValue({ + fixtures.session?.startVerification.mockResolvedValue({ status: 'needs_second_factor', supportedSecondFactors: [{ strategy: 'totp' }], - } as any); + }); - vi.spyOn(fixtures.session!, 'prepareSecondFactorVerification').mockResolvedValue({ + fixtures.session?.prepareSecondFactorVerification.mockResolvedValue({ status: 'needs_second_factor', supportedSecondFactors: [{ strategy: 'totp' }], - } as any); + }); const { getByLabelText, getByText } = render(, { wrapper }); await waitFor(() => { @@ -65,15 +65,15 @@ describe('UserVerificationFactorTwo', () => { const { wrapper, fixtures } = await createFixtures(f => { f.withUser({ username: 'clerkuser' }); }); - vi.spyOn(fixtures.session!, 'startVerification').mockResolvedValue({ + fixtures.session?.startVerification.mockResolvedValue({ status: 'needs_second_factor', supportedSecondFactors: [{ strategy: 'backup_code' }], - } as any); + }); - vi.spyOn(fixtures.session!, 'prepareSecondFactorVerification').mockResolvedValue({ + fixtures.session?.prepareSecondFactorVerification.mockResolvedValue({ status: 'needs_second_factor', supportedSecondFactors: [{ strategy: 'backup_code' }], - } as any); + }); const { getByLabelText, getByText } = render(, { wrapper }); await waitFor(() => { @@ -88,9 +88,9 @@ describe('UserVerificationFactorTwo', () => { const { wrapper, fixtures } = await createFixtures(f => { f.withUser({ username: 'clerkuser' }); }); - vi.spyOn(fixtures.session!, 'startVerification').mockResolvedValue({ + fixtures.session?.startVerification.mockResolvedValue({ status: 'needs_first_factor', - } as any); + }); render(, { wrapper }); await waitFor(() => expect(fixtures.router.navigate).toHaveBeenCalledWith('../')); @@ -102,31 +102,34 @@ describe('UserVerificationFactorTwo', () => { const { wrapper, fixtures } = await createFixtures(f => { f.withUser({ username: 'clerkuser' }); }); - vi.spyOn(fixtures.session!, 'startVerification').mockResolvedValue({ + fixtures.session?.startVerification.mockResolvedValue({ status: 'needs_second_factor', supportedSecondFactors: [{ strategy: 'phone_code' }], - } as any); + }); - vi.spyOn(fixtures.session!, 'prepareSecondFactorVerification').mockResolvedValue({ + fixtures.session?.prepareSecondFactorVerification.mockResolvedValue({ status: 'needs_second_factor', supportedSecondFactors: [{ strategy: 'phone_code' }], - } as any); + }); - vi.spyOn(fixtures.session!, 'attemptSecondFactorVerification').mockResolvedValue({ + fixtures.session?.attemptSecondFactorVerification.mockResolvedValue({ status: 'complete', supportedSecondFactors: [], session: { id: '123', }, - } as any); + }); - const { userEvent, getByLabelText, getByText } = render(, { wrapper }); + await runFakeTimers(async timers => { + const { userEvent, getByLabelText, getByText } = render(, { wrapper }); - await waitFor(() => getByText('Verification required')); + await waitFor(() => getByText('Verification required')); - await userEvent.type(getByLabelText(/Enter verification code/i), '123456'); - await waitFor(() => { - expect(fixtures.clerk.setActive).toHaveBeenCalled(); + await userEvent.type(getByLabelText(/Enter verification code/i), '123456'); + timers.runOnlyPendingTimers(); + await waitFor(() => { + expect(fixtures.clerk.setActive).toHaveBeenCalled(); + }); }); }); }); @@ -136,7 +139,7 @@ describe('UserVerificationFactorTwo', () => { const { wrapper, fixtures } = await createFixtures(f => { f.withUser({ username: 'clerkuser' }); }); - vi.spyOn(fixtures.session!, 'startVerification').mockResolvedValue({ + fixtures.session?.startVerification.mockResolvedValue({ status: 'needs_second_factor', supportedSecondFactors: [ { @@ -150,18 +153,18 @@ describe('UserVerificationFactorTwo', () => { safeIdentifier: '+3069XXXXXXX2', }, ], - } as any); - vi.spyOn(fixtures.session!, 'prepareSecondFactorVerification').mockResolvedValue({} as any); + }); + fixtures.session?.prepareSecondFactorVerification.mockResolvedValue({}); - const { userEvent, getByText, getByRole } = render(, { wrapper }); + const { getByText, getByRole } = render(, { wrapper }); await waitFor(() => { getByText('Verification required'); getByText('Use another method'); }); - await userEvent.click(getByText('Use another method')); await waitFor(() => { + getByText('Use another method').click(); expect(getByRole('button')).toHaveTextContent('Send SMS code to +3069XXXXXXX2'); expect(getByRole('button')).not.toHaveTextContent('Send SMS code to +3069XXXXXXX1'); }); @@ -171,7 +174,7 @@ describe('UserVerificationFactorTwo', () => { const { wrapper, fixtures } = await createFixtures(f => { f.withUser({ username: 'clerkuser' }); }); - vi.spyOn(fixtures.session!, 'startVerification').mockResolvedValue({ + fixtures.session?.startVerification.mockResolvedValue({ status: 'needs_second_factor', supportedSecondFactors: [ { @@ -185,10 +188,10 @@ describe('UserVerificationFactorTwo', () => { safeIdentifier: '+3069XXXXXXX2', }, ], - } as any); - vi.spyOn(fixtures.session!, 'prepareSecondFactorVerification').mockResolvedValue({} as any); + }); + fixtures.session?.prepareSecondFactorVerification.mockResolvedValue({}); - const { userEvent, getByText, container } = render(, { wrapper }); + const { getByText, container } = render(, { wrapper }); await waitFor(() => { getByText('Verification required'); @@ -197,8 +200,10 @@ describe('UserVerificationFactorTwo', () => { getByText('Use another method'); }); - await userEvent.click(getByText('Use another method')); - await userEvent.click(getByText('Send SMS code to +3069XXXXXXX2')); + await waitFor(() => { + getByText('Use another method').click(); + getByText('Send SMS code to +3069XXXXXXX2').click(); + }); await waitFor(() => { getByText('Verification required'); @@ -206,8 +211,8 @@ describe('UserVerificationFactorTwo', () => { getByText('Use another method'); }); - await userEvent.click(getByText('Use another method')); await waitFor(() => { + getByText('Use another method').click(); expect(container).toHaveTextContent('+3069XXXXXXX1'); }); }); diff --git a/packages/clerk-js/src/ui/components/Waitlist/__tests__/Waitlist.test.tsx b/packages/clerk-js/src/ui/components/Waitlist/__tests__/Waitlist.test.tsx index f3465eb90fd..6d2bd98f2b6 100644 --- a/packages/clerk-js/src/ui/components/Waitlist/__tests__/Waitlist.test.tsx +++ b/packages/clerk-js/src/ui/components/Waitlist/__tests__/Waitlist.test.tsx @@ -1,8 +1,7 @@ import type { WaitlistResource } from '@clerk/types'; -import { describe, expect, it } from 'vitest'; -import { render, screen } from '../../../../vitestUtils'; -import { bindCreateFixtures } from '../../../utils/vitest/createFixtures'; +import { render, screen } from '../../../../testUtils'; +import { bindCreateFixtures } from '../../../utils/test/createFixtures'; import { Waitlist } from '../'; const { createFixtures } = bindCreateFixtures('Waitlist'); diff --git a/packages/clerk-js/src/ui/contexts/ClerkUIComponentsContext.tsx b/packages/clerk-js/src/ui/contexts/ClerkUIComponentsContext.tsx index 383a58e5ec2..ccde1a6e924 100644 --- a/packages/clerk-js/src/ui/contexts/ClerkUIComponentsContext.tsx +++ b/packages/clerk-js/src/ui/contexts/ClerkUIComponentsContext.tsx @@ -11,7 +11,6 @@ import type { ReactNode } from 'react'; import type { AvailableComponentName, AvailableComponentProps } from '../types'; import { ApiKeysContext, - CheckoutContext, CreateOrganizationContext, GoogleOneTapContext, OAuthConsentContext, @@ -22,7 +21,6 @@ import { SignInContext, SignUpContext, SubscriberTypeContext, - SubscriptionDetailsContext, UserButtonContext, UserProfileContext, UserVerificationContext, @@ -120,14 +118,6 @@ export function ComponentContextProvider({ {children} ); - case 'SubscriptionDetails': - return ( - - {children} - - ); - case 'Checkout': - return {children}; default: throw new Error(`Unknown component context: ${componentName}`); } diff --git a/packages/clerk-js/src/ui/contexts/components/index.ts b/packages/clerk-js/src/ui/contexts/components/index.ts index 92554780aae..408688463e8 100644 --- a/packages/clerk-js/src/ui/contexts/components/index.ts +++ b/packages/clerk-js/src/ui/contexts/components/index.ts @@ -2,7 +2,6 @@ export * from './SignIn'; export * from './SignUp'; export * from './SignOut'; export * from './SubscriberType'; -export * from './SubscriptionDetails'; export * from './UserProfile'; export * from './UserVerification'; export * from './UserButton'; diff --git a/packages/clerk-js/src/ui/customizables/__tests__/elementDescriptors.test.tsx b/packages/clerk-js/src/ui/customizables/__tests__/elementDescriptors.test.tsx index 65b03e6bd43..44d42122732 100644 --- a/packages/clerk-js/src/ui/customizables/__tests__/elementDescriptors.test.tsx +++ b/packages/clerk-js/src/ui/customizables/__tests__/elementDescriptors.test.tsx @@ -1,7 +1,6 @@ // eslint-disable-next-line simple-import-sort/imports -import { render, screen } from '../../../vitestUtils'; +import { render, screen } from '../../../testUtils'; import React from 'react'; -import { describe, it, expect } from 'vitest'; import { Box, descriptors } from '..'; import { AppearanceProvider } from '../AppearanceContext'; diff --git a/packages/clerk-js/src/ui/customizables/__tests__/makeCustomizable.test.tsx b/packages/clerk-js/src/ui/customizables/__tests__/makeCustomizable.test.tsx index 12fa509607e..95ca7788fea 100644 --- a/packages/clerk-js/src/ui/customizables/__tests__/makeCustomizable.test.tsx +++ b/packages/clerk-js/src/ui/customizables/__tests__/makeCustomizable.test.tsx @@ -1,11 +1,10 @@ // eslint-disable-next-line simple-import-sort/imports -import { render, screen } from '../../../vitestUtils'; +import { render, screen } from '../../../testUtils'; import React from 'react'; -import { describe, it, expect } from 'vitest'; import { Box, descriptors } from '..'; import { AppearanceProvider } from '../AppearanceContext'; -import { computedColors } from './vitestUtils'; +import { knownColors } from './testUtils'; import { InternalThemeProvider } from '../../styledSystem'; describe('Theme used in sx callback', () => { @@ -24,7 +23,7 @@ describe('Theme used in sx callback', () => { , ); - expect(getComputedStyle(screen.getByTestId('test')).backgroundColor).toBe(computedColors.blue); + expect(screen.getByTestId('test')).toHaveStyleRule('background-color', knownColors.blue); }); it('styles match the theme/appearance', () => { @@ -42,7 +41,7 @@ describe('Theme used in sx callback', () => { , ); - expect(getComputedStyle(screen.getByTestId('test')).backgroundColor).toBe(computedColors.red); + expect(screen.getByTestId('test')).toHaveStyleRule('background-color', knownColors.red); }); it('styles match the merged result from globalAppearance and appearance', () => { @@ -61,7 +60,7 @@ describe('Theme used in sx callback', () => { , ); - expect(getComputedStyle(screen.getByTestId('test')).backgroundColor).toBe(computedColors.red); + expect(screen.getByTestId('test')).toHaveStyleRule('background-color', knownColors.red); }); }); @@ -84,7 +83,7 @@ describe('Styles for specific elements', () => { /> , ); - expect(getComputedStyle(screen.getByTestId('test')).backgroundColor).toBe('rgb(255, 255, 0)'); + expect(screen.getByTestId('test')).toHaveStyleRule('background-color', 'yellow'); }); it('styles propagate to the correct element specified, including overriding styles when loading state is applied', () => { @@ -115,7 +114,7 @@ describe('Styles for specific elements', () => { wrapper, }, ); - expect(getComputedStyle(screen.getByTestId('test')).backgroundColor).toBe('rgb(255, 255, 0)'); + expect(getComputedStyle(screen.getByTestId('test')).backgroundColor).toBe('yellow'); rerender( { isLoading />, ); - expect(getComputedStyle(screen.getByTestId('test')).backgroundColor).toBe('rgb(255, 0, 0)'); + expect(getComputedStyle(screen.getByTestId('test')).backgroundColor).toBe('red'); }); it('overrides styles when active state is applied', () => { @@ -153,7 +152,7 @@ describe('Styles for specific elements', () => { />, { wrapper }, ); - expect(getComputedStyle(screen.getByTestId('test')).backgroundColor).toBe('rgb(255, 255, 0)'); + expect(getComputedStyle(screen.getByTestId('test')).backgroundColor).toBe('yellow'); rerender( { isActive />, ); - expect(getComputedStyle(screen.getByTestId('test')).backgroundColor).toBe('rgb(255, 0, 0)'); + expect(getComputedStyle(screen.getByTestId('test')).backgroundColor).toBe('red'); }); it('overrides styles when error state is applied', () => { @@ -193,7 +192,7 @@ describe('Styles for specific elements', () => { wrapper, }, ); - expect(getComputedStyle(screen.getByTestId('test')).backgroundColor).toBe('rgb(255, 255, 0)'); + expect(getComputedStyle(screen.getByTestId('test')).backgroundColor).toBe('yellow'); rerender( { hasError />, ); - expect(getComputedStyle(screen.getByTestId('test')).backgroundColor).toBe('rgb(255, 0, 0)'); + expect(getComputedStyle(screen.getByTestId('test')).backgroundColor).toBe('red'); }); it('overrides styles when open state is applied', () => { @@ -236,7 +235,7 @@ describe('Styles for specific elements', () => { wrapper, }, ); - expect(getComputedStyle(screen.getByTestId('test')).backgroundColor).toBe('rgb(255, 255, 0)'); + expect(getComputedStyle(screen.getByTestId('test')).backgroundColor).toBe('yellow'); rerender( { isOpen />, ); - expect(getComputedStyle(screen.getByTestId('test')).backgroundColor).toBe('rgb(255, 0, 0)'); + expect(getComputedStyle(screen.getByTestId('test')).backgroundColor).toBe('red'); }); it('overrides &:disabled styles when loading state is applied', () => { @@ -278,7 +277,7 @@ describe('Styles for specific elements', () => { { wrapper }, ); - expect(getComputedStyle(screen.getByTestId('test')).backgroundColor).toBe('rgb(255, 255, 0)'); + expect(getComputedStyle(screen.getByTestId('test')).backgroundColor).toBe('yellow'); rerender( { isLoading />, ); - expect(getComputedStyle(screen.getByTestId('test')).backgroundColor).toBe('rgb(255, 0, 0)'); + expect(getComputedStyle(screen.getByTestId('test')).backgroundColor).toBe('red'); }); it('if a class is provided to the element via appearance, it adds the class to the element', () => { diff --git a/packages/clerk-js/src/ui/customizables/__tests__/parseAppearance.test.tsx b/packages/clerk-js/src/ui/customizables/__tests__/parseAppearance.test.tsx index f4405ef26f0..022df997c8b 100644 --- a/packages/clerk-js/src/ui/customizables/__tests__/parseAppearance.test.tsx +++ b/packages/clerk-js/src/ui/customizables/__tests__/parseAppearance.test.tsx @@ -1,11 +1,11 @@ -import { renderHook } from '@testing-library/react'; -import { describe, expect, it } from 'vitest'; - -import { render } from '@/vitestUtils'; +// eslint-disable-next-line simple-import-sort/imports +import { render } from '../../../testUtils'; +import React from 'react'; import { Box, useAppearance } from '..'; import { AppearanceProvider } from '../AppearanceContext'; -import { knownColors } from './vitestUtils'; +import { renderHook } from '@testing-library/react'; +import { knownColors } from './testUtils'; const themeAColor = 'blue'; const themeA = { diff --git a/packages/clerk-js/src/ui/customizables/__tests__/parseVariables.test.ts b/packages/clerk-js/src/ui/customizables/__tests__/parseVariables.spec.ts similarity index 100% rename from packages/clerk-js/src/ui/customizables/__tests__/parseVariables.test.ts rename to packages/clerk-js/src/ui/customizables/__tests__/parseVariables.spec.ts diff --git a/packages/clerk-js/src/ui/customizables/__tests__/testUtils.ts b/packages/clerk-js/src/ui/customizables/__tests__/testUtils.ts new file mode 100644 index 00000000000..3d5dbc28fdb --- /dev/null +++ b/packages/clerk-js/src/ui/customizables/__tests__/testUtils.ts @@ -0,0 +1,4 @@ +export const knownColors = { + blue: 'hsla(240, 100%, 50%, 1)', + red: 'hsla(0, 100%, 50%, 1)', +}; diff --git a/packages/clerk-js/src/ui/customizables/__tests__/vitestUtils.ts b/packages/clerk-js/src/ui/customizables/__tests__/vitestUtils.ts deleted file mode 100644 index 6047cd1c912..00000000000 --- a/packages/clerk-js/src/ui/customizables/__tests__/vitestUtils.ts +++ /dev/null @@ -1,10 +0,0 @@ -export const knownColors = { - blue: 'hsla(240, 100%, 50%, 1)', - red: 'hsla(0, 100%, 50%, 1)', -}; - -// Browser computed styles return RGB values, so we need these for DOM testing -export const computedColors = { - blue: 'rgb(0, 0, 255)', - red: 'rgb(255, 0, 0)', -}; diff --git a/packages/clerk-js/src/ui/elements/PhoneInput/__tests__/useFormattedPhoneNumber.test.ts b/packages/clerk-js/src/ui/elements/PhoneInput/__tests__/useFormattedPhoneNumber.spec.ts similarity index 100% rename from packages/clerk-js/src/ui/elements/PhoneInput/__tests__/useFormattedPhoneNumber.test.ts rename to packages/clerk-js/src/ui/elements/PhoneInput/__tests__/useFormattedPhoneNumber.spec.ts diff --git a/packages/clerk-js/src/ui/elements/__tests__/CodeControl.test.tsx b/packages/clerk-js/src/ui/elements/__tests__/CodeControl.spec.tsx similarity index 100% rename from packages/clerk-js/src/ui/elements/__tests__/CodeControl.test.tsx rename to packages/clerk-js/src/ui/elements/__tests__/CodeControl.spec.tsx diff --git a/packages/clerk-js/src/ui/elements/__tests__/LinkRenderer.test.tsx b/packages/clerk-js/src/ui/elements/__tests__/LinkRenderer.test.tsx index 55902843b04..3c6ebab89ec 100644 --- a/packages/clerk-js/src/ui/elements/__tests__/LinkRenderer.test.tsx +++ b/packages/clerk-js/src/ui/elements/__tests__/LinkRenderer.test.tsx @@ -1,8 +1,8 @@ +import { describe, it } from '@jest/globals'; import { render } from '@testing-library/react'; import React from 'react'; -import { describe, expect, it } from 'vitest'; -import { bindCreateFixtures } from '../../utils/vitest/createFixtures'; +import { bindCreateFixtures } from '../../utils/test/createFixtures'; import { LinkRenderer } from '../LinkRenderer'; const { createFixtures } = bindCreateFixtures('SignUp'); diff --git a/packages/clerk-js/src/ui/elements/__tests__/PlainInput.test.tsx b/packages/clerk-js/src/ui/elements/__tests__/PlainInput.spec.tsx similarity index 100% rename from packages/clerk-js/src/ui/elements/__tests__/PlainInput.test.tsx rename to packages/clerk-js/src/ui/elements/__tests__/PlainInput.spec.tsx diff --git a/packages/clerk-js/src/ui/elements/__tests__/RadioGroup.test.tsx b/packages/clerk-js/src/ui/elements/__tests__/RadioGroup.spec.tsx similarity index 100% rename from packages/clerk-js/src/ui/elements/__tests__/RadioGroup.test.tsx rename to packages/clerk-js/src/ui/elements/__tests__/RadioGroup.spec.tsx diff --git a/packages/clerk-js/src/ui/foundations/__tests__/createInternalTheme.test.ts b/packages/clerk-js/src/ui/foundations/__tests__/createInternalTheme.spec.ts similarity index 100% rename from packages/clerk-js/src/ui/foundations/__tests__/createInternalTheme.test.ts rename to packages/clerk-js/src/ui/foundations/__tests__/createInternalTheme.spec.ts diff --git a/packages/clerk-js/src/ui/hooks/__tests__/useCoreOrganization.test.tsx b/packages/clerk-js/src/ui/hooks/__tests__/useCoreOrganization.test.tsx index b002b78834a..c5027a3a251 100644 --- a/packages/clerk-js/src/ui/hooks/__tests__/useCoreOrganization.test.tsx +++ b/packages/clerk-js/src/ui/hooks/__tests__/useCoreOrganization.test.tsx @@ -1,14 +1,14 @@ import { useOrganization } from '@clerk/shared/react'; -import { describe, expect, it } from 'vitest'; +import { describe } from '@jest/globals'; -import { act, renderHook, waitFor } from '../../../vitestUtils'; +import { act, renderHook, waitFor } from '../../../testUtils'; import { createFakeDomain, createFakeOrganizationInvitation, createFakeOrganizationMembershipRequest, } from '../../components/OrganizationProfile/__tests__/utils'; import { createFakeUserOrganizationMembership } from '../../components/OrganizationSwitcher/__tests__/utlis'; -import { bindCreateFixtures } from '../../utils/vitest/createFixtures'; +import { bindCreateFixtures } from '../../utils/test/createFixtures'; const { createFixtures } = bindCreateFixtures('OrganizationProfile'); diff --git a/packages/clerk-js/src/ui/hooks/__tests__/useCoreOrganizationList.test.tsx b/packages/clerk-js/src/ui/hooks/__tests__/useCoreOrganizationList.spec.tsx similarity index 100% rename from packages/clerk-js/src/ui/hooks/__tests__/useCoreOrganizationList.test.tsx rename to packages/clerk-js/src/ui/hooks/__tests__/useCoreOrganizationList.spec.tsx diff --git a/packages/clerk-js/src/ui/hooks/__tests__/useDevMode.test.tsx b/packages/clerk-js/src/ui/hooks/__tests__/useDevMode.spec.tsx similarity index 100% rename from packages/clerk-js/src/ui/hooks/__tests__/useDevMode.test.tsx rename to packages/clerk-js/src/ui/hooks/__tests__/useDevMode.spec.tsx diff --git a/packages/clerk-js/src/ui/hooks/__tests__/useDirection.test.ts b/packages/clerk-js/src/ui/hooks/__tests__/useDirection.spec.ts similarity index 100% rename from packages/clerk-js/src/ui/hooks/__tests__/useDirection.test.ts rename to packages/clerk-js/src/ui/hooks/__tests__/useDirection.spec.ts diff --git a/packages/clerk-js/src/ui/hooks/__tests__/useEnabledThirdPartyProviders.test.tsx b/packages/clerk-js/src/ui/hooks/__tests__/useEnabledThirdPartyProviders.test.tsx index ceba870f8c6..fcd1de3c6da 100644 --- a/packages/clerk-js/src/ui/hooks/__tests__/useEnabledThirdPartyProviders.test.tsx +++ b/packages/clerk-js/src/ui/hooks/__tests__/useEnabledThirdPartyProviders.test.tsx @@ -1,8 +1,7 @@ import { OAUTH_PROVIDERS } from '@clerk/shared/oauth'; import { renderHook } from '@testing-library/react'; -import { describe, expect, it } from 'vitest'; -import { bindCreateFixtures } from '../../../vitestUtils'; +import { bindCreateFixtures } from '../../../testUtils'; import { useEnabledThirdPartyProviders } from '../useEnabledThirdPartyProviders'; const { createFixtures } = bindCreateFixtures('SignUp'); diff --git a/packages/clerk-js/src/ui/hooks/__tests__/usePasswordComplexity.test.tsx b/packages/clerk-js/src/ui/hooks/__tests__/usePasswordComplexity.test.tsx index d0280e835df..bb67dd585ea 100644 --- a/packages/clerk-js/src/ui/hooks/__tests__/usePasswordComplexity.test.tsx +++ b/packages/clerk-js/src/ui/hooks/__tests__/usePasswordComplexity.test.tsx @@ -1,7 +1,5 @@ -import { describe, expect, it } from 'vitest'; - -import { act, renderHook } from '../../../vitestUtils'; -import { bindCreateFixtures } from '../../utils/vitest/createFixtures'; +import { act, renderHook } from '../../../testUtils'; +import { bindCreateFixtures } from '../../utils/test/createFixtures'; import { usePasswordComplexity } from '../usePasswordComplexity'; const { createFixtures } = bindCreateFixtures('SignIn'); diff --git a/packages/clerk-js/src/ui/hooks/__tests__/useSupportEmail.test.tsx b/packages/clerk-js/src/ui/hooks/__tests__/useSupportEmail.spec.tsx similarity index 100% rename from packages/clerk-js/src/ui/hooks/__tests__/useSupportEmail.test.tsx rename to packages/clerk-js/src/ui/hooks/__tests__/useSupportEmail.spec.tsx diff --git a/packages/clerk-js/src/ui/localization/__tests__/applyTokensToString.test.ts b/packages/clerk-js/src/ui/localization/__tests__/applyTokensToString.spec.ts similarity index 100% rename from packages/clerk-js/src/ui/localization/__tests__/applyTokensToString.test.ts rename to packages/clerk-js/src/ui/localization/__tests__/applyTokensToString.spec.ts diff --git a/packages/clerk-js/src/ui/localization/__tests__/makeLocalizable.test.tsx b/packages/clerk-js/src/ui/localization/__tests__/makeLocalizable.test.tsx index ee4ef481c5f..9df02c4bf7a 100644 --- a/packages/clerk-js/src/ui/localization/__tests__/makeLocalizable.test.tsx +++ b/packages/clerk-js/src/ui/localization/__tests__/makeLocalizable.test.tsx @@ -1,6 +1,4 @@ -import { describe, expect, it } from 'vitest'; - -import { render, renderHook, screen } from '../../../vitestUtils'; +import { render, renderHook, screen } from '../../../testUtils'; import { Badge, Button, @@ -13,7 +11,7 @@ import { Text, useLocalizations, } from '../../customizables'; -import { bindCreateFixtures } from '../../utils/vitest/createFixtures'; +import { bindCreateFixtures } from '../../utils/test/createFixtures'; const { createFixtures } = bindCreateFixtures('SignIn'); diff --git a/packages/clerk-js/src/ui/localization/__tests__/parseLocalization.test.tsx b/packages/clerk-js/src/ui/localization/__tests__/parseLocalization.test.tsx index e7bf4cfaaa1..6fb7385fd7c 100644 --- a/packages/clerk-js/src/ui/localization/__tests__/parseLocalization.test.tsx +++ b/packages/clerk-js/src/ui/localization/__tests__/parseLocalization.test.tsx @@ -1,10 +1,9 @@ import React from 'react'; -import { describe, expect, it } from 'vitest'; -import { renderHook } from '../../../vitestUtils'; +import { renderHook } from '../../../testUtils'; import { OptionsProvider } from '../../contexts'; import { localizationKeys, useLocalizations } from '../../customizables'; -import { bindCreateFixtures } from '../../utils/vitest/createFixtures'; +import { bindCreateFixtures } from '../../utils/test/createFixtures'; import { defaultResource } from '../defaultEnglishResource'; const { createFixtures } = bindCreateFixtures('SignIn'); diff --git a/packages/clerk-js/src/ui/router/__tests__/HashRouter.test.tsx b/packages/clerk-js/src/ui/router/__tests__/HashRouter.spec.tsx similarity index 100% rename from packages/clerk-js/src/ui/router/__tests__/HashRouter.test.tsx rename to packages/clerk-js/src/ui/router/__tests__/HashRouter.spec.tsx diff --git a/packages/clerk-js/src/ui/router/__tests__/PathRouter.test.tsx b/packages/clerk-js/src/ui/router/__tests__/PathRouter.spec.tsx similarity index 100% rename from packages/clerk-js/src/ui/router/__tests__/PathRouter.test.tsx rename to packages/clerk-js/src/ui/router/__tests__/PathRouter.spec.tsx diff --git a/packages/clerk-js/src/ui/router/__tests__/Switch.test.tsx b/packages/clerk-js/src/ui/router/__tests__/Switch.spec.tsx similarity index 100% rename from packages/clerk-js/src/ui/router/__tests__/Switch.test.tsx rename to packages/clerk-js/src/ui/router/__tests__/Switch.spec.tsx diff --git a/packages/clerk-js/src/ui/router/__tests__/VirtualRouter.test.tsx b/packages/clerk-js/src/ui/router/__tests__/VirtualRouter.spec.tsx similarity index 100% rename from packages/clerk-js/src/ui/router/__tests__/VirtualRouter.test.tsx rename to packages/clerk-js/src/ui/router/__tests__/VirtualRouter.spec.tsx diff --git a/packages/clerk-js/src/ui/styledSystem/__tests__/createVariants.test.ts b/packages/clerk-js/src/ui/styledSystem/__tests__/createVariants.spec.ts similarity index 100% rename from packages/clerk-js/src/ui/styledSystem/__tests__/createVariants.test.ts rename to packages/clerk-js/src/ui/styledSystem/__tests__/createVariants.spec.ts diff --git a/packages/clerk-js/src/ui/utils/__tests__/createCustomMenuItems.test.ts b/packages/clerk-js/src/ui/utils/__tests__/createCustomMenuItems.spec.ts similarity index 100% rename from packages/clerk-js/src/ui/utils/__tests__/createCustomMenuItems.test.ts rename to packages/clerk-js/src/ui/utils/__tests__/createCustomMenuItems.spec.ts diff --git a/packages/clerk-js/src/ui/utils/__tests__/createCustomPages.test.ts b/packages/clerk-js/src/ui/utils/__tests__/createCustomPages.spec.ts similarity index 100% rename from packages/clerk-js/src/ui/utils/__tests__/createCustomPages.test.ts rename to packages/clerk-js/src/ui/utils/__tests__/createCustomPages.spec.ts diff --git a/packages/clerk-js/src/ui/utils/__tests__/cssSupports.test.ts b/packages/clerk-js/src/ui/utils/__tests__/cssSupports.spec.ts similarity index 100% rename from packages/clerk-js/src/ui/utils/__tests__/cssSupports.test.ts rename to packages/clerk-js/src/ui/utils/__tests__/cssSupports.spec.ts diff --git a/packages/clerk-js/src/ui/utils/__tests__/cssVariables.test.ts b/packages/clerk-js/src/ui/utils/__tests__/cssVariables.spec.ts similarity index 100% rename from packages/clerk-js/src/ui/utils/__tests__/cssVariables.test.ts rename to packages/clerk-js/src/ui/utils/__tests__/cssVariables.spec.ts diff --git a/packages/clerk-js/src/ui/utils/__tests__/factorSorting.test.ts b/packages/clerk-js/src/ui/utils/__tests__/factorSorting.spec.ts similarity index 100% rename from packages/clerk-js/src/ui/utils/__tests__/factorSorting.test.ts rename to packages/clerk-js/src/ui/utils/__tests__/factorSorting.spec.ts diff --git a/packages/clerk-js/src/ui/utils/__tests__/formatSafeIdentifier.test.ts b/packages/clerk-js/src/ui/utils/__tests__/formatSafeIdentifier.spec.ts similarity index 100% rename from packages/clerk-js/src/ui/utils/__tests__/formatSafeIdentifier.test.ts rename to packages/clerk-js/src/ui/utils/__tests__/formatSafeIdentifier.spec.ts diff --git a/packages/clerk-js/src/ui/utils/__tests__/intl.test.ts b/packages/clerk-js/src/ui/utils/__tests__/intl.spec.ts similarity index 100% rename from packages/clerk-js/src/ui/utils/__tests__/intl.test.ts rename to packages/clerk-js/src/ui/utils/__tests__/intl.spec.ts diff --git a/packages/clerk-js/src/ui/utils/__tests__/originPrefersPopup.test.ts b/packages/clerk-js/src/ui/utils/__tests__/originPrefersPopup.spec.ts similarity index 100% rename from packages/clerk-js/src/ui/utils/__tests__/originPrefersPopup.test.ts rename to packages/clerk-js/src/ui/utils/__tests__/originPrefersPopup.spec.ts diff --git a/packages/clerk-js/src/ui/utils/__tests__/passwordUtils.test.tsx b/packages/clerk-js/src/ui/utils/__tests__/passwordUtils.test.tsx index 805c7a88079..be962402001 100644 --- a/packages/clerk-js/src/ui/utils/__tests__/passwordUtils.test.tsx +++ b/packages/clerk-js/src/ui/utils/__tests__/passwordUtils.test.tsx @@ -1,10 +1,8 @@ -import { describe, expect, it } from 'vitest'; - -import { renderHook } from '../../../vitestUtils'; +import { renderHook } from '../../../testUtils'; import { OptionsProvider } from '../../contexts'; import { useLocalizations } from '../../customizables'; import { createPasswordError } from '../passwordUtils'; -import { bindCreateFixtures } from '../vitest/createFixtures'; +import { bindCreateFixtures } from '../test/createFixtures'; const { createFixtures } = bindCreateFixtures('SignIn'); diff --git a/packages/clerk-js/src/ui/utils/__tests__/phoneUtils.test.ts b/packages/clerk-js/src/ui/utils/__tests__/phoneUtils.spec.ts similarity index 100% rename from packages/clerk-js/src/ui/utils/__tests__/phoneUtils.test.ts rename to packages/clerk-js/src/ui/utils/__tests__/phoneUtils.spec.ts diff --git a/packages/clerk-js/src/ui/utils/__tests__/truncateTextWithEndVisible.test.ts b/packages/clerk-js/src/ui/utils/__tests__/truncateTextWithEndVisible.spec.ts similarity index 100% rename from packages/clerk-js/src/ui/utils/__tests__/truncateTextWithEndVisible.test.ts rename to packages/clerk-js/src/ui/utils/__tests__/truncateTextWithEndVisible.spec.ts diff --git a/packages/clerk-js/src/ui/utils/colors/__tests__/constants.test.ts b/packages/clerk-js/src/ui/utils/colors/__tests__/constants.spec.ts similarity index 100% rename from packages/clerk-js/src/ui/utils/colors/__tests__/constants.test.ts rename to packages/clerk-js/src/ui/utils/colors/__tests__/constants.spec.ts diff --git a/packages/clerk-js/src/ui/utils/colors/__tests__/index.test.ts b/packages/clerk-js/src/ui/utils/colors/__tests__/index.spec.ts similarity index 100% rename from packages/clerk-js/src/ui/utils/colors/__tests__/index.test.ts rename to packages/clerk-js/src/ui/utils/colors/__tests__/index.spec.ts diff --git a/packages/clerk-js/src/ui/utils/colors/__tests__/legacy.test.ts b/packages/clerk-js/src/ui/utils/colors/__tests__/legacy.spec.ts similarity index 100% rename from packages/clerk-js/src/ui/utils/colors/__tests__/legacy.test.ts rename to packages/clerk-js/src/ui/utils/colors/__tests__/legacy.spec.ts diff --git a/packages/clerk-js/src/ui/utils/colors/__tests__/modern.test.ts b/packages/clerk-js/src/ui/utils/colors/__tests__/modern.spec.ts similarity index 100% rename from packages/clerk-js/src/ui/utils/colors/__tests__/modern.test.ts rename to packages/clerk-js/src/ui/utils/colors/__tests__/modern.spec.ts diff --git a/packages/clerk-js/src/ui/utils/colors/__tests__/scales.test.ts b/packages/clerk-js/src/ui/utils/colors/__tests__/scales.spec.ts similarity index 100% rename from packages/clerk-js/src/ui/utils/colors/__tests__/scales.test.ts rename to packages/clerk-js/src/ui/utils/colors/__tests__/scales.spec.ts diff --git a/packages/clerk-js/src/ui/utils/colors/__tests__/utils.test.ts b/packages/clerk-js/src/ui/utils/colors/__tests__/utils.spec.ts similarity index 100% rename from packages/clerk-js/src/ui/utils/colors/__tests__/utils.test.ts rename to packages/clerk-js/src/ui/utils/colors/__tests__/utils.spec.ts diff --git a/packages/clerk-js/src/ui/utils/test/createFixtures.tsx b/packages/clerk-js/src/ui/utils/test/createFixtures.tsx index 42f92aa629b..bcafd820363 100644 --- a/packages/clerk-js/src/ui/utils/test/createFixtures.tsx +++ b/packages/clerk-js/src/ui/utils/test/createFixtures.tsx @@ -1,5 +1,5 @@ import type { ClerkOptions, ClientJSON, EnvironmentJSON, LoadedClerk } from '@clerk/types'; -import { vi } from 'vitest'; +import { jest } from '@jest/globals'; import { FlowMetadataProvider } from '@/ui/elements/contexts'; @@ -55,11 +55,11 @@ const unboundCreateFixtures = ( } const environmentMock = new Environment(baseEnvironment); - Environment.getInstance().fetch = vi.fn(() => Promise.resolve(environmentMock)); + Environment.getInstance().fetch = jest.fn(() => Promise.resolve(environmentMock)); // @ts-expect-error We cannot mess with the singleton when tests are running in parallel const clientMock = new Client(baseClient); - Client.getOrCreateInstance().fetch = vi.fn(() => Promise.resolve(clientMock)); + Client.getOrCreateInstance().fetch = jest.fn(() => Promise.resolve(clientMock)); // Use a FAPI value for local production instances to avoid triggering the devInit flow during testing const productionPublishableKey = 'pk_live_Y2xlcmsuYWJjZWYuMTIzNDUucHJvZC5sY2xjbGVyay5jb20k'; diff --git a/packages/clerk-js/src/ui/utils/test/mockHelpers.ts b/packages/clerk-js/src/ui/utils/test/mockHelpers.ts index cf47ec35a0a..82eee90827d 100644 --- a/packages/clerk-js/src/ui/utils/test/mockHelpers.ts +++ b/packages/clerk-js/src/ui/utils/test/mockHelpers.ts @@ -1,12 +1,12 @@ import type { LoadedClerk } from '@clerk/types'; -import { vi } from 'vitest'; +import { jest } from '@jest/globals'; import type { RouteContextValue } from '../../router'; type FunctionLike = (...args: any) => any; type DeepJestMocked = T extends FunctionLike - ? vi.Mocked + ? jest.Mocked : T extends object ? { [k in keyof T]: DeepJestMocked; @@ -14,7 +14,7 @@ type DeepJestMocked = T extends FunctionLike : T; type MockMap = { - [K in { [K in keyof T]: T[K] extends (...args: any[]) => any ? K : never }[keyof T]]?: vi.Mock< + [K in { [K in keyof T]: T[K] extends (...args: any[]) => any ? K : never }[keyof T]]?: jest.Mock< // @ts-expect-error -- the typing seems to be working in practice T[K] >; @@ -23,7 +23,7 @@ type MockMap = { const mockProp = (obj: T, k: keyof T, mocks?: MockMap) => { if (typeof obj[k] === 'function') { // @ts-ignore - obj[k] = mocks?.[k] ?? vi.fn(); + obj[k] = mocks?.[k] ?? jest.fn(); } }; @@ -51,7 +51,7 @@ export const mockClerkMethods = (clerk: LoadedClerk): DeepJestMocked Promise.resolve(session)), + touch: jest.fn(() => Promise.resolve(session)), }, }); mockMethodsOf(session.user); @@ -81,12 +81,12 @@ export const mockRouteContextValue = ({ queryString = '' }: Partial Promise.resolve(true)), - resolve: vi.fn((to: string) => new URL(to, 'https://clerk.com')), - refresh: vi.fn(), + getMatchData: jest.fn(), + matches: jest.fn(), + baseNavigate: jest.fn(), + navigate: jest.fn(() => Promise.resolve(true)), + resolve: jest.fn((to: string) => new URL(to, 'https://clerk.com')), + refresh: jest.fn(), params: {}, } as RouteContextValue; }; diff --git a/packages/clerk-js/src/ui/utils/test/runFakeTimers.ts b/packages/clerk-js/src/ui/utils/test/runFakeTimers.ts new file mode 100644 index 00000000000..5fda44aeca3 --- /dev/null +++ b/packages/clerk-js/src/ui/utils/test/runFakeTimers.ts @@ -0,0 +1,35 @@ +import { jest } from '@jest/globals'; +import { act } from '@testing-library/react'; + +type WithAct = (fn: T) => T; +const withAct = ((fn: any) => + (...args: any) => { + act(() => { + fn(...args); + }); + }) as WithAct; + +const advanceTimersByTime = withAct(jest.advanceTimersByTime.bind(jest)); +const runAllTimers = withAct(jest.runAllTimers.bind(jest)); +const runOnlyPendingTimers = withAct(jest.runOnlyPendingTimers.bind(jest)); + +const createFakeTimersHelpers = () => { + return { advanceTimersByTime, runAllTimers, runOnlyPendingTimers }; +}; + +type FakeTimersHelpers = ReturnType; +type RunFakeTimersCallback = (timers: FakeTimersHelpers) => void | Promise; + +export const runFakeTimers = >( + cb: T, +): R extends Promise ? Promise : void => { + jest.useFakeTimers(); + const res = cb(createFakeTimersHelpers()); + if (res && 'then' in res) { + // @ts-expect-error + return res.finally(() => jest.useRealTimers()); + } + jest.useRealTimers(); + // @ts-ignore + return; +}; diff --git a/packages/clerk-js/src/ui/utils/vitest/createFixtures.tsx b/packages/clerk-js/src/ui/utils/vitest/createFixtures.tsx index cd26c06617d..897f65ca603 100644 --- a/packages/clerk-js/src/ui/utils/vitest/createFixtures.tsx +++ b/packages/clerk-js/src/ui/utils/vitest/createFixtures.tsx @@ -1,4 +1,6 @@ +// import { jest } from '@jest/globals'; import type { ClerkOptions, ClientJSON, EnvironmentJSON, LoadedClerk } from '@clerk/types'; +import React from 'react'; import { vi } from 'vitest'; import { FlowMetadataProvider } from '@/ui/elements/contexts'; diff --git a/packages/clerk-js/src/ui/utils/vitest/fixtureHelpers.ts b/packages/clerk-js/src/ui/utils/vitest/fixtureHelpers.ts index a9bf9f78f41..567bf8ada6e 100644 --- a/packages/clerk-js/src/ui/utils/vitest/fixtureHelpers.ts +++ b/packages/clerk-js/src/ui/utils/vitest/fixtureHelpers.ts @@ -27,7 +27,6 @@ export const createEnvironmentFixtureHelpers = (baseEnvironment: EnvironmentJSON ...createAuthConfigFixtureHelpers(baseEnvironment), ...createDisplayConfigFixtureHelpers(baseEnvironment), ...createOrganizationSettingsFixtureHelpers(baseEnvironment), - ...createBillingSettingsFixtureHelpers(baseEnvironment), ...createUserSettingsFixtureHelpers(baseEnvironment), }; }; @@ -50,7 +49,6 @@ const createUserFixtureHelpers = (baseClient: ClientJSON) => { external_accounts?: Array>; saml_accounts?: Array>; organization_memberships?: Array; - tasks?: Array<{ key: 'choose-organization' }>; }; const createPublicUserData = (params: WithUserParams) => { @@ -77,7 +75,7 @@ const createUserFixtureHelpers = (baseClient: ClientJSON) => { } const session = { - status: params.tasks?.length ? 'pending' : 'active', + status: 'active', id: baseClient.sessions.length.toString(), object: 'session', last_active_organization_id: activeOrganization, @@ -89,8 +87,6 @@ const createUserFixtureHelpers = (baseClient: ClientJSON) => { last_active_token: { jwt: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NzU4NzY3OTAsImRhdGEiOiJmb29iYXIiLCJpYXQiOjE2NzU4NzY3MzB9.Z1BC47lImYvaAtluJlY-kBo0qOoAk42Xb-gNrB2SxJg', }, - tasks: params.tasks || null, - current_task: params.tasks?.[0] || null, } as SessionJSON; baseClient.sessions.push(session); }; @@ -350,21 +346,6 @@ const createOrganizationSettingsFixtureHelpers = (environment: EnvironmentJSON) return { withOrganizations, withMaxAllowedMemberships, withOrganizationDomains, withForceOrganizationSelection }; }; -const createBillingSettingsFixtureHelpers = (environment: EnvironmentJSON) => { - const os = environment.commerce_settings.billing; - const withBilling = () => { - os.enabled = true; - os.user.enabled = true; - os.user.has_paid_plans = true; - os.organization.enabled = true; - os.organization.has_paid_plans = true; - os.has_paid_org_plans = true; - os.has_paid_user_plans = true; - }; - - return { withBilling }; -}; - const createUserSettingsFixtureHelpers = (environment: EnvironmentJSON) => { const us = environment.user_settings; us.password_settings = { diff --git a/packages/clerk-js/src/ui/utils/vitest/fixtures.ts b/packages/clerk-js/src/ui/utils/vitest/fixtures.ts index bea7992329c..40858b78c88 100644 --- a/packages/clerk-js/src/ui/utils/vitest/fixtures.ts +++ b/packages/clerk-js/src/ui/utils/vitest/fixtures.ts @@ -1,7 +1,6 @@ import type { AuthConfigJSON, ClientJSON, - CommerceSettingsJSON, DisplayConfigJSON, EnvironmentJSON, OrganizationSettingsJSON, @@ -19,7 +18,6 @@ export const createBaseEnvironmentJSON = (): EnvironmentJSON => { display_config: createBaseDisplayConfig(), organization_settings: createBaseOrganizationSettings(), user_settings: createBaseUserSettings(), - commerce_settings: createBaseCommerceSettings(), meta: { responseHeaders: { country: 'us' } }, }; }; @@ -220,27 +218,6 @@ export const createBaseClientJSON = (): ClientJSON => { return {} as ClientJSON; }; -const createBaseCommerceSettings = (): CommerceSettingsJSON => { - return { - object: 'commerce_settings', - id: 'commerce_settings_1', - billing: { - enabled: false, - user: { - enabled: false, - has_paid_plans: false, - }, - organization: { - enabled: false, - has_paid_plans: false, - }, - has_paid_org_plans: false, - has_paid_user_plans: false, - stripe_publishable_key: '', - }, - }; -}; - export const createUserFixture = (): UserJSON => { return { first_name: 'Firstname', diff --git a/packages/clerk-js/src/ui/utils/vitest/mockHelpers.ts b/packages/clerk-js/src/ui/utils/vitest/mockHelpers.ts index 7e47e9426cb..8be7b9ae20d 100644 --- a/packages/clerk-js/src/ui/utils/vitest/mockHelpers.ts +++ b/packages/clerk-js/src/ui/utils/vitest/mockHelpers.ts @@ -1,5 +1,6 @@ -// import { vi } from 'vitest'; -import type { ActiveSessionResource, LoadedClerk } from '@clerk/types'; +// import { jest } from '@jest/globals'; +import type { LoadedClerk } from '@clerk/types'; +import type { ActiveSessionResource } from '@clerk/types'; import { type Mocked, vi } from 'vitest'; import type { RouteContextValue } from '../../router'; @@ -14,7 +15,7 @@ type DeepVitestMocked = T extends FunctionLike } : T; -// Removing vi.Mock type for now, relying on inference +// Removing jest.Mock type for now, relying on inference type MockMap = { [K in { [K in keyof T]: T[K] extends (...args: any[]) => any ? K : never }[keyof T]]?: ReturnType; }; @@ -74,9 +75,6 @@ export const mockClerkMethods = (clerk: LoadedClerk): DeepVitestMocked(fn: T) => T; +const withAct = ((fn: any) => + (...args: any) => { + act(() => { + fn(...args); + }); + }) as WithAct; + +const advanceTimersByTime = withAct(vi.advanceTimersByTime.bind(vi)); +const runAllTimers = withAct(vi.runAllTimers.bind(vi)); +const runOnlyPendingTimers = withAct(vi.runOnlyPendingTimers.bind(vi)); + +const createFakeTimersHelpers = () => { + return { advanceTimersByTime, runAllTimers, runOnlyPendingTimers }; +}; + +type FakeTimersHelpers = ReturnType; +type RunFakeTimersCallback = (timers: FakeTimersHelpers) => void | Promise; + +export async function runFakeTimers(cb: (timers: FakeTimersHelpers) => void): Promise; +export async function runFakeTimers(cb: (timers: FakeTimersHelpers) => Promise): Promise; +export async function runFakeTimers(cb: RunFakeTimersCallback): Promise { + vi.useFakeTimers(); + try { + const result = cb(createFakeTimersHelpers()); + if (result instanceof Promise) { + await result; + } + } finally { + vi.useRealTimers(); + } +} diff --git a/packages/clerk-js/src/utils/__tests__/appearance.test.ts b/packages/clerk-js/src/utils/__tests__/appearance.spec.ts similarity index 100% rename from packages/clerk-js/src/utils/__tests__/appearance.test.ts rename to packages/clerk-js/src/utils/__tests__/appearance.spec.ts diff --git a/packages/clerk-js/src/utils/__tests__/captcha.test.ts b/packages/clerk-js/src/utils/__tests__/captcha.spec.ts similarity index 99% rename from packages/clerk-js/src/utils/__tests__/captcha.test.ts rename to packages/clerk-js/src/utils/__tests__/captcha.spec.ts index 97b916e7802..97665749f7d 100644 --- a/packages/clerk-js/src/utils/__tests__/captcha.test.ts +++ b/packages/clerk-js/src/utils/__tests__/captcha.spec.ts @@ -149,7 +149,7 @@ describe('Nonce support', () => { describe('CaptchaChallenge nonce integration', () => { let mockClerk: any; - beforeEach(() => { + beforeEach(async () => { // Mock clerk instance mockClerk = { __unstable__environment: { diff --git a/packages/clerk-js/src/utils/__tests__/completeSignUpFlow.test.ts b/packages/clerk-js/src/utils/__tests__/completeSignUpFlow.spec.ts similarity index 100% rename from packages/clerk-js/src/utils/__tests__/completeSignUpFlow.test.ts rename to packages/clerk-js/src/utils/__tests__/completeSignUpFlow.spec.ts diff --git a/packages/clerk-js/src/utils/__tests__/date.test.ts b/packages/clerk-js/src/utils/__tests__/date.spec.ts similarity index 100% rename from packages/clerk-js/src/utils/__tests__/date.test.ts rename to packages/clerk-js/src/utils/__tests__/date.spec.ts diff --git a/packages/clerk-js/src/utils/__tests__/dynamicParamParser.test.ts b/packages/clerk-js/src/utils/__tests__/dynamicParamParser.spec.ts similarity index 100% rename from packages/clerk-js/src/utils/__tests__/dynamicParamParser.test.ts rename to packages/clerk-js/src/utils/__tests__/dynamicParamParser.spec.ts diff --git a/packages/clerk-js/src/utils/__tests__/email.test.ts b/packages/clerk-js/src/utils/__tests__/email.spec.ts similarity index 100% rename from packages/clerk-js/src/utils/__tests__/email.test.ts rename to packages/clerk-js/src/utils/__tests__/email.spec.ts diff --git a/packages/clerk-js/src/utils/__tests__/encoders.test.ts b/packages/clerk-js/src/utils/__tests__/encoders.spec.ts similarity index 100% rename from packages/clerk-js/src/utils/__tests__/encoders.test.ts rename to packages/clerk-js/src/utils/__tests__/encoders.spec.ts diff --git a/packages/clerk-js/src/utils/__tests__/errors.test.ts b/packages/clerk-js/src/utils/__tests__/errors.spec.ts similarity index 100% rename from packages/clerk-js/src/utils/__tests__/errors.test.ts rename to packages/clerk-js/src/utils/__tests__/errors.spec.ts diff --git a/packages/clerk-js/src/utils/__tests__/getClerkQueryParam.test.ts b/packages/clerk-js/src/utils/__tests__/getClerkQueryParam.spec.ts similarity index 100% rename from packages/clerk-js/src/utils/__tests__/getClerkQueryParam.test.ts rename to packages/clerk-js/src/utils/__tests__/getClerkQueryParam.spec.ts diff --git a/packages/clerk-js/src/utils/__tests__/ignoreEventValue.test.ts b/packages/clerk-js/src/utils/__tests__/ignoreEventValue.spec.ts similarity index 100% rename from packages/clerk-js/src/utils/__tests__/ignoreEventValue.test.ts rename to packages/clerk-js/src/utils/__tests__/ignoreEventValue.spec.ts diff --git a/packages/clerk-js/src/utils/__tests__/instance.test.ts b/packages/clerk-js/src/utils/__tests__/instance.spec.ts similarity index 100% rename from packages/clerk-js/src/utils/__tests__/instance.test.ts rename to packages/clerk-js/src/utils/__tests__/instance.spec.ts diff --git a/packages/clerk-js/src/utils/__tests__/jwt.test.ts b/packages/clerk-js/src/utils/__tests__/jwt.spec.ts similarity index 100% rename from packages/clerk-js/src/utils/__tests__/jwt.test.ts rename to packages/clerk-js/src/utils/__tests__/jwt.spec.ts diff --git a/packages/clerk-js/src/utils/__tests__/localStorage.test.ts b/packages/clerk-js/src/utils/__tests__/localStorage.spec.ts similarity index 100% rename from packages/clerk-js/src/utils/__tests__/localStorage.test.ts rename to packages/clerk-js/src/utils/__tests__/localStorage.spec.ts diff --git a/packages/clerk-js/src/utils/__tests__/memoizeStateListenerCallback.test.ts b/packages/clerk-js/src/utils/__tests__/memoizeStateListenerCallback.spec.ts similarity index 100% rename from packages/clerk-js/src/utils/__tests__/memoizeStateListenerCallback.test.ts rename to packages/clerk-js/src/utils/__tests__/memoizeStateListenerCallback.spec.ts diff --git a/packages/clerk-js/src/utils/__tests__/organization.test.ts b/packages/clerk-js/src/utils/__tests__/organization.spec.ts similarity index 100% rename from packages/clerk-js/src/utils/__tests__/organization.test.ts rename to packages/clerk-js/src/utils/__tests__/organization.spec.ts diff --git a/packages/clerk-js/src/utils/__tests__/passkeys.test.ts b/packages/clerk-js/src/utils/__tests__/passkeys.spec.ts similarity index 100% rename from packages/clerk-js/src/utils/__tests__/passkeys.test.ts rename to packages/clerk-js/src/utils/__tests__/passkeys.spec.ts diff --git a/packages/clerk-js/src/utils/__tests__/path.test.ts b/packages/clerk-js/src/utils/__tests__/path.spec.ts similarity index 100% rename from packages/clerk-js/src/utils/__tests__/path.test.ts rename to packages/clerk-js/src/utils/__tests__/path.spec.ts diff --git a/packages/clerk-js/src/utils/__tests__/queryStateParams.test.ts b/packages/clerk-js/src/utils/__tests__/queryStateParams.spec.ts similarity index 100% rename from packages/clerk-js/src/utils/__tests__/queryStateParams.test.ts rename to packages/clerk-js/src/utils/__tests__/queryStateParams.spec.ts diff --git a/packages/clerk-js/src/utils/__tests__/querystring.test.ts b/packages/clerk-js/src/utils/__tests__/querystring.spec.ts similarity index 100% rename from packages/clerk-js/src/utils/__tests__/querystring.test.ts rename to packages/clerk-js/src/utils/__tests__/querystring.spec.ts diff --git a/packages/clerk-js/src/utils/__tests__/redirectUrls.test.ts b/packages/clerk-js/src/utils/__tests__/redirectUrls.spec.ts similarity index 100% rename from packages/clerk-js/src/utils/__tests__/redirectUrls.test.ts rename to packages/clerk-js/src/utils/__tests__/redirectUrls.spec.ts diff --git a/packages/clerk-js/src/utils/__tests__/resourceParams.test.ts b/packages/clerk-js/src/utils/__tests__/resourceParams.spec.ts similarity index 100% rename from packages/clerk-js/src/utils/__tests__/resourceParams.test.ts rename to packages/clerk-js/src/utils/__tests__/resourceParams.spec.ts diff --git a/packages/clerk-js/src/utils/__tests__/runAsyncResourceTask.test.ts b/packages/clerk-js/src/utils/__tests__/runAsyncResourceTask.spec.ts similarity index 100% rename from packages/clerk-js/src/utils/__tests__/runAsyncResourceTask.test.ts rename to packages/clerk-js/src/utils/__tests__/runAsyncResourceTask.spec.ts diff --git a/packages/clerk-js/src/utils/__tests__/url.test.ts b/packages/clerk-js/src/utils/__tests__/url.spec.ts similarity index 100% rename from packages/clerk-js/src/utils/__tests__/url.test.ts rename to packages/clerk-js/src/utils/__tests__/url.spec.ts diff --git a/packages/clerk-js/src/vitestUtils.ts b/packages/clerk-js/src/vitestUtils.ts index 39626b3249c..58500f47371 100644 --- a/packages/clerk-js/src/vitestUtils.ts +++ b/packages/clerk-js/src/vitestUtils.ts @@ -1,13 +1,17 @@ +// eslint-disable-next-line no-restricted-imports +import { matchers } from '@emotion/jest'; import type { RenderOptions } from '@testing-library/react'; import { render as _render } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import { afterAll, beforeAll, describe, vi } from 'vitest'; +import UserEvent from '@testing-library/user-event'; +import { afterAll, beforeAll, describe, expect, type SpyInstance, vi } from 'vitest'; + +expect.extend(matchers); Element.prototype.scrollIntoView = vi.fn(); const render = (ui: React.ReactElement, options?: RenderOptions) => { - const user = userEvent.setup({ delay: null }); - return { ..._render(ui, { ...options }), userEvent: user }; + const userEvent = UserEvent.setup({ delay: null }); + return { ..._render(ui, { ...options }), userEvent }; }; /** @@ -27,8 +31,8 @@ const render = (ui: React.ReactElement, options?: RenderOptions) => { */ export const mockNativeRuntime = (fn: () => void) => { describe('native runtime', () => { - let spyDocument: ReturnType; - let spyNavigator: ReturnType; + let spyDocument: SpyInstance; + let spyNavigator: SpyInstance; beforeAll(() => { spyDocument = vi.spyOn(globalThis, 'document', 'get'); @@ -66,19 +70,9 @@ export const mockWebAuthn = (fn: () => void) => { }); }; +export * from './ui/utils/vitest/runFakeTimers'; export * from './ui/utils/vitest/createFixtures'; -// Export everything from @testing-library/react except render, then export our custom render -export { - screen, - waitFor, - fireEvent, - act, - cleanup, - renderHook, - type RenderOptions, - type RenderHookOptions, - type RenderHookResult, - type RenderResult, -} from '@testing-library/react'; -// Export our custom render function that includes userEvent +// eslint-disable-next-line import/export +export * from '@testing-library/react'; +// eslint-disable-next-line import/export export { render }; diff --git a/packages/clerk-js/tsconfig.json b/packages/clerk-js/tsconfig.json index b0d2e5d9126..87a894bfaef 100644 --- a/packages/clerk-js/tsconfig.json +++ b/packages/clerk-js/tsconfig.json @@ -24,5 +24,5 @@ "@/*": ["./src/*"] } }, - "include": ["src", "vitest.setup.mts", "vitest.config.mts"] + "include": ["src", "jest.setup.ts", "jest.setup-after-env.ts", "jest.jsdom-with-timezone.ts"] } diff --git a/packages/clerk-js/tsconfig.test.json b/packages/clerk-js/tsconfig.test.json new file mode 100644 index 00000000000..dc05a8af5c1 --- /dev/null +++ b/packages/clerk-js/tsconfig.test.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "sourceMap": true + }, + "include": ["src/**/*", "./jest.setup-after-env.ts"], + "exclude": ["node_modules"] +} diff --git a/packages/clerk-js/turbo.json b/packages/clerk-js/turbo.json index a6e53939738..2e9d62c5c8c 100644 --- a/packages/clerk-js/turbo.json +++ b/packages/clerk-js/turbo.json @@ -29,6 +29,7 @@ "test": { "inputs": [ "*.d.ts", + "jest.*", "src/**", "svgTransform.js", "tests/**", diff --git a/packages/clerk-js/vitest.config.mts b/packages/clerk-js/vitest.config.mts index cbc700d0ed3..fa7da0d033b 100644 --- a/packages/clerk-js/vitest.config.mts +++ b/packages/clerk-js/vitest.config.mts @@ -5,7 +5,7 @@ import { defineConfig } from 'vitest/config'; function viteSvgMockPlugin() { return { name: 'svg-mock', - transform(_code: string, id: string) { + transform(code: string, id: string) { if (id.endsWith('.svg') && process.env.NODE_ENV === 'test') { return { code: ` @@ -17,7 +17,6 @@ function viteSvgMockPlugin() { map: null, }; } - return undefined; }, }; } @@ -53,15 +52,9 @@ export default defineConfig({ }, environment: 'jsdom', globals: false, - include: ['**/*.test.?(c|m)[jt]s?(x)'], - exclude: ['sandbox/**/*.spec.?(c|m)[jt]s?(x)', 'node_modules/**', 'dist/**'], + include: ['**/*.spec.?(c|m)[jt]s?(x)'], + exclude: ['sandbox/**/*.spec.?(c|m)[jt]s?(x)'], setupFiles: './vitest.setup.mts', - testTimeout: 5000, - environmentOptions: { - jsdom: { - resources: 'usable', - }, - }, }, resolve: { alias: [{ find: /^@\//, replacement: `${resolve(__dirname, 'src')}/` }], diff --git a/packages/clerk-js/vitest.setup.mts b/packages/clerk-js/vitest.setup.mts index 75edf3ff66e..9fe3bc83ea8 100644 --- a/packages/clerk-js/vitest.setup.mts +++ b/packages/clerk-js/vitest.setup.mts @@ -3,11 +3,9 @@ import '@testing-library/jest-dom/vitest'; import * as crypto from 'node:crypto'; import { TextDecoder, TextEncoder } from 'node:util'; -import { cleanup, configure } from '@testing-library/react'; +import { cleanup } from '@testing-library/react'; import { afterAll, afterEach, beforeAll, vi } from 'vitest'; -configure({}); - afterEach(cleanup); // Store the original method @@ -90,42 +88,6 @@ if (typeof window !== 'undefined') { return null; } }; - - // Mock HTMLCanvasElement.prototype.getContext to prevent errors - HTMLCanvasElement.prototype.getContext = vi.fn().mockImplementation((contextType: string) => { - if (contextType === '2d') { - return { - fillRect: vi.fn(), - getImageData: vi.fn(() => ({ data: new Uint8ClampedArray([255, 255, 255, 255]) }) as unknown as ImageData), - } as unknown as CanvasRenderingContext2D; - } - if (contextType === 'webgl' || contextType === 'webgl2') { - return {} as unknown as WebGLRenderingContext; - } - return null; - }); - - // Mock Element.prototype.animate for auto-animate library - Element.prototype.animate = vi.fn().mockImplementation(() => ({ - cancel: vi.fn(), - finish: vi.fn(), - pause: vi.fn(), - play: vi.fn(), - reverse: vi.fn(), - updatePlaybackRate: vi.fn(), - addEventListener: vi.fn(), - removeEventListener: vi.fn(), - dispatchEvent: vi.fn(), - })); - - // Mock requestAnimationFrame for auto-animate library - global.requestAnimationFrame = vi.fn().mockImplementation((callback: FrameRequestCallback) => { - return setTimeout(callback, 16); - }); - - global.cancelAnimationFrame = vi.fn().mockImplementation((id: number) => { - clearTimeout(id); - }); } // Mock browser-tabs-lock to prevent window access errors in tests diff --git a/packages/nextjs/src/server/__tests__/clerkMiddleware.test.ts b/packages/nextjs/src/server/__tests__/clerkMiddleware.test.ts index 69419e2d504..64a3913f80f 100644 --- a/packages/nextjs/src/server/__tests__/clerkMiddleware.test.ts +++ b/packages/nextjs/src/server/__tests__/clerkMiddleware.test.ts @@ -350,6 +350,7 @@ describe('clerkMiddleware(params)', () => { })(req, {} as NextFetchEvent); expect(resp?.status).toEqual(307); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion expect(new URL(resp!.headers.get('location')!).searchParams.get('redirect_url')).toContain('/protected'); expect((await clerkClient()).authenticateRequest).toBeCalled(); }); @@ -367,6 +368,7 @@ describe('clerkMiddleware(params)', () => { expect(resp?.status).toEqual(307); expect(resp?.headers.get('location')).toContain(locationHeader); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion expect(new URL(resp!.headers.get('location')!).searchParams.get('redirect_url')).toEqual( 'https://www.clerk.com/hello', ); @@ -386,6 +388,7 @@ describe('clerkMiddleware(params)', () => { expect(resp?.status).toEqual(307); expect(resp?.headers.get('location')).toContain(locationHeader); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion expect(new URL(resp!.headers.get('location')!).searchParams.get('redirect_url')).toBeNull(); expect((await clerkClient()).authenticateRequest).toBeCalled(); }); diff --git a/packages/vue/src/utils/__tests__/useCustomElementPortal.test.ts b/packages/vue/src/utils/__tests__/useCustomElementPortal.test.ts index 996107f634c..46e491db086 100644 --- a/packages/vue/src/utils/__tests__/useCustomElementPortal.test.ts +++ b/packages/vue/src/utils/__tests__/useCustomElementPortal.test.ts @@ -20,7 +20,7 @@ describe('useCustomElementPortal', () => { expect(portals.value).toHaveLength(1); expect(portals.value[0].type).toBe(Teleport); - + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion expect(portals.value[0].props!.to).toBe(el); unmount(el); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 933c3039f20..8a0ed5ee8b0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -534,6 +534,9 @@ importers: '@svgr/webpack': specifier: ^6.5.1 version: 6.5.1 + '@swc/jest': + specifier: 0.2.39 + version: 0.2.39(@swc/core@1.11.29(@swc/helpers@0.5.17)) '@types/cloudflare-turnstile': specifier: ^0.2.2 version: 0.2.2 @@ -3160,6 +3163,10 @@ packages: resolution: {integrity: sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jest/create-cache-key-function@30.0.2': + resolution: {integrity: sha512-AwlDAHwEHDi+etw9vKWx9HeIApVos8GD/sSTpHtDkqhm9OWuEUPKKPP6EaS17yv0GSzBB3TeeJFLyJ5LPjRqWg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + '@jest/environment@29.7.0': resolution: {integrity: sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -3180,6 +3187,10 @@ packages: resolution: {integrity: sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jest/pattern@30.0.1': + resolution: {integrity: sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + '@jest/reporters@29.7.0': resolution: {integrity: sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -3193,6 +3204,10 @@ packages: resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jest/schemas@30.0.1': + resolution: {integrity: sha512-+g/1TKjFuGrf1Hh0QPCv0gISwBxJ+MQSNXmG9zjHy7BmFhtoJ9fdNhWJp3qUKRi93AOZHXtdxZgJ1vAtz6z65w==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + '@jest/source-map@29.6.3': resolution: {integrity: sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -3217,6 +3232,10 @@ packages: resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jest/types@30.0.1': + resolution: {integrity: sha512-HGwoYRVF0QSKJu1ZQX0o5ZrUrrhj0aOOFA8hXrumD7SIzjouevhawbTjmXdwOmURdGluU9DM/XvGm3NyFoiQjw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -4658,6 +4677,9 @@ packages: '@sinclair/typebox@0.27.8': resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + '@sinclair/typebox@0.34.38': + resolution: {integrity: sha512-HpkxMmc2XmZKhvaKIZZThlHmx1L0I/V1hWK1NubtlFnr6ZqdiOpV72TKudZUNQjZNsyDBay72qFEhEvb+bcwcA==} + '@sindresorhus/is@4.6.0': resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} engines: {node: '>=10'} @@ -4865,6 +4887,12 @@ packages: '@swc/helpers@0.5.5': resolution: {integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==} + '@swc/jest@0.2.39': + resolution: {integrity: sha512-eyokjOwYd0Q8RnMHri+8/FS1HIrIUKK/sRrFp8c1dThUOfNeCWbLmBP1P5VsKdvmkd25JaH+OKYwEYiAYg9YAA==} + engines: {npm: '>= 7.0.0'} + peerDependencies: + '@swc/core': '*' + '@swc/types@0.1.24': resolution: {integrity: sha512-tjTMh3V4vAORHtdTprLlfoMptu1WfTZG9Rsca6yOKyNYsRr+MUXutKmliB17orgSZk5DpnDxs8GUdd/qwYxOng==} @@ -9850,6 +9878,10 @@ packages: resolution: {integrity: sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-regex-util@30.0.1: + resolution: {integrity: sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + jest-resolve-dependencies@29.7.0: resolution: {integrity: sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -10040,6 +10072,9 @@ packages: engines: {node: '>=6'} hasBin: true + jsonc-parser@3.3.1: + resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} + jsondiffpatch@0.6.0: resolution: {integrity: sha512-3QItJOXp2AP1uv7waBkao5nCvhEv+QmJAd38Ybq7wNI74Q+BBmnLn4EDKz6yI9xGAIQoUF87qHt+kc1IVxB4zQ==} engines: {node: ^18.0.0 || >=20.0.0} @@ -17597,6 +17632,10 @@ snapshots: dependencies: '@jest/types': 29.6.3 + '@jest/create-cache-key-function@30.0.2': + dependencies: + '@jest/types': 30.0.1 + '@jest/environment@29.7.0': dependencies: '@jest/fake-timers': 29.7.0 @@ -17633,6 +17672,11 @@ snapshots: transitivePeerDependencies: - supports-color + '@jest/pattern@30.0.1': + dependencies: + '@types/node': 22.18.1 + jest-regex-util: 30.0.1 + '@jest/reporters@29.7.0': dependencies: '@bcoe/v8-coverage': 0.2.3 @@ -17666,6 +17710,10 @@ snapshots: dependencies: '@sinclair/typebox': 0.27.8 + '@jest/schemas@30.0.1': + dependencies: + '@sinclair/typebox': 0.34.38 + '@jest/source-map@29.6.3': dependencies: '@jridgewell/trace-mapping': 0.3.30 @@ -17724,6 +17772,16 @@ snapshots: '@types/yargs': 17.0.33 chalk: 4.1.2 + '@jest/types@30.0.1': + dependencies: + '@jest/pattern': 30.0.1 + '@jest/schemas': 30.0.1 + '@types/istanbul-lib-coverage': 2.0.6 + '@types/istanbul-reports': 3.0.4 + '@types/node': 22.18.1 + '@types/yargs': 17.0.33 + chalk: 4.1.2 + '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -19472,6 +19530,8 @@ snapshots: '@sinclair/typebox@0.27.8': {} + '@sinclair/typebox@0.34.38': {} + '@sindresorhus/is@4.6.0': {} '@sindresorhus/is@7.1.0': {} @@ -19650,7 +19710,6 @@ snapshots: '@swc/core-win32-ia32-msvc': 1.11.29 '@swc/core-win32-x64-msvc': 1.11.29 '@swc/helpers': 0.5.17 - optional: true '@swc/counter@0.1.3': {} @@ -19663,10 +19722,16 @@ snapshots: '@swc/counter': 0.1.3 tslib: 2.8.1 + '@swc/jest@0.2.39(@swc/core@1.11.29(@swc/helpers@0.5.17))': + dependencies: + '@jest/create-cache-key-function': 30.0.2 + '@swc/core': 1.11.29(@swc/helpers@0.5.17) + '@swc/counter': 0.1.3 + jsonc-parser: 3.3.1 + '@swc/types@0.1.24': dependencies: '@swc/counter': 0.1.3 - optional: true '@tanstack/directive-functions-plugin@1.131.2(vite@7.1.5(@types/node@24.3.1)(jiti@2.5.1)(lightningcss@1.27.0)(terser@5.44.0)(tsx@4.19.2)(yaml@2.8.1))': dependencies: @@ -26018,6 +26083,8 @@ snapshots: jest-regex-util@29.6.3: {} + jest-regex-util@30.0.1: {} + jest-resolve-dependencies@29.7.0: dependencies: jest-regex-util: 29.6.3 @@ -26364,6 +26431,8 @@ snapshots: json5@2.2.3: {} + jsonc-parser@3.3.1: {} + jsondiffpatch@0.6.0: dependencies: '@types/diff-match-patch': 1.0.36