From ef039aed13eb42a54aa52323b7113633e5b15611 Mon Sep 17 00:00:00 2001 From: Jacek Date: Mon, 9 Jun 2025 14:51:49 -0500 Subject: [PATCH 01/12] feat: vitest conv --- packages/clerk-js/package.json | 4 + .../{headless.test.ts => headless.spec.ts} | 4 +- .../clerk-js/src/__tests__/mocks/svgMock.tsx | 12 + ...irects.test.ts => clerk.redirects.spec.ts} | 32 +- ...{fapiClient.test.ts => fapiClient.spec.ts} | 13 +- ...{tokenCache.test.ts => tokenCache.spec.ts} | 21 +- ...kieSuffix.test.ts => cookieSuffix.spec.ts} | 16 +- ...{devBrowser.test.ts => devBrowser.spec.ts} | 14 +- ...Domain.test.ts => getCookieDomain.spec.ts} | 22 +- ...ute.test.ts => getSecureAttribute.spec.ts} | 4 +- ...ection.test.ts => fraudProtection.spec.ts} | 34 +- ...{AuthConfig.test.ts => AuthConfig.spec.ts} | 6 +- .../__tests__/{Base.test.ts => Base.spec.ts} | 10 +- .../{Client.test.ts => Client.spec.ts} | 27 +- ...nvironment.test.ts => Environment.spec.ts} | 1 + ...ccount.test.ts => ExternalAccount.spec.ts} | 5 +- .../{Image.test.ts => Image.spec.ts} | 4 +- .../{Session.test.ts => Session.spec.ts} | 9 +- .../{Token.test.ts => Token.spec.ts} | 7 +- .../__tests__/{User.test.ts => User.spec.ts} | 17 +- ...rSettings.test.ts => UserSettings.spec.ts} | 1 + ...{Web3Wallet.test.ts => Web3Wallet.spec.ts} | 9 +- ...lient.test.ts.snap => Client.spec.ts.snap} | 13 +- ....test.ts.snap => Environment.spec.ts.snap} | 22 +- ...sion.test.ts.snap => Session.spec.ts.snap} | 12 +- ...test.ts.snap => UserSettings.spec.ts.snap} | 4 +- packages/clerk-js/src/core/vitest/fixtures.ts | 285 +++++++++ .../{redirects.test.ts => redirects.spec.ts} | 47 +- .../__tests__/vitestUtils.ts | 145 +++++ ...nUpStart.test.tsx => SignUpStart.spec.tsx} | 11 +- ...lainInput.test.tsx => PlainInput.spec.tsx} | 6 +- ...adioGroup.test.tsx => RadioGroup.spec.tsx} | 6 +- ...t.tsx => useCoreOrganizationList.spec.tsx} | 8 +- ...seDevMode.test.tsx => useDevMode.spec.tsx} | 11 +- ...mail.test.tsx => useSupportEmail.spec.tsx} | 9 +- ...ng.test.ts => applyTokensToString.spec.ts} | 4 +- .../src/ui/router/__mocks__/RouteContext.tsx | 7 +- ...athRouter.test.tsx => PathRouter.spec.tsx} | 5 +- .../{Switch.test.tsx => Switch.spec.tsx} | 9 +- ...Router.test.tsx => VirtualRouter.spec.tsx} | 7 +- ...g.test.ts => normalizeColorString.spec.ts} | 14 +- .../src/ui/utils/vitest/createFixtures.tsx | 131 ++++ .../src/ui/utils/vitest/fixtureHelpers.ts | 566 ++++++++++++++++++ .../clerk-js/src/ui/utils/vitest/fixtures.ts | 220 +++++++ .../src/ui/utils/vitest/mockHelpers.ts | 103 ++++ .../src/ui/utils/vitest/runFakeTimers.ts | 36 ++ .../{captcha.test.ts => captcha.spec.ts} | 2 + ...low.test.ts => completeSignUpFlow.spec.ts} | 7 +- .../__tests__/{date.test.ts => date.spec.ts} | 7 +- ...ser.test.ts => dynamicParamParser.spec.ts} | 2 +- .../{errors.test.ts => errors.spec.ts} | 2 + ...Value.test.ts => ignoreEventValue.spec.ts} | 2 + .../{instance.test.ts => instance.spec.ts} | 2 + .../__tests__/{jwt.test.ts => jwt.spec.ts} | 2 + ...alStorage.test.ts => localStorage.spec.ts} | 13 +- ...s => memoizeStateListenerCallback.spec.ts} | 2 + ...anization.test.ts => organization.spec.ts} | 2 + .../{passkeys.test.ts => passkeys.spec.ts} | 1 + .../__tests__/{path.test.ts => path.spec.ts} | 2 + ...arams.test.ts => queryStateParams.spec.ts} | 2 + ...uerystring.test.ts => querystring.spec.ts} | 1 + ...irectUrls.test.ts => redirectUrls.spec.ts} | 1 + ...eParams.test.ts => resourceParams.spec.ts} | 1 + .../__tests__/{url.test.ts => url.spec.ts} | 21 +- packages/clerk-js/src/vitestUtils.ts | 78 +++ packages/clerk-js/vitest.config.mts | 50 +- packages/clerk-js/vitest.setup.mts | 97 ++- pnpm-lock.yaml | 195 +++++- 68 files changed, 2171 insertions(+), 274 deletions(-) rename packages/clerk-js/src/__tests__/{headless.test.ts => headless.spec.ts} (78%) create mode 100644 packages/clerk-js/src/__tests__/mocks/svgMock.tsx rename packages/clerk-js/src/core/__tests__/{clerk.redirects.test.ts => clerk.redirects.spec.ts} (94%) rename packages/clerk-js/src/core/__tests__/{fapiClient.test.ts => fapiClient.spec.ts} (95%) rename packages/clerk-js/src/core/__tests__/{tokenCache.test.ts => tokenCache.spec.ts} (90%) rename packages/clerk-js/src/core/auth/__tests__/{cookieSuffix.test.ts => cookieSuffix.spec.ts} (72%) rename packages/clerk-js/src/core/auth/__tests__/{devBrowser.test.ts => devBrowser.spec.ts} (83%) rename packages/clerk-js/src/core/auth/__tests__/{getCookieDomain.test.ts => getCookieDomain.spec.ts} (83%) rename packages/clerk-js/src/core/auth/__tests__/{getSecureAttribute.test.ts => getSecureAttribute.spec.ts} (95%) rename packages/clerk-js/src/core/{fraudProtection.test.ts => fraudProtection.spec.ts} (82%) rename packages/clerk-js/src/core/resources/__tests__/{AuthConfig.test.ts => AuthConfig.spec.ts} (89%) rename packages/clerk-js/src/core/resources/__tests__/{Base.test.ts => Base.spec.ts} (86%) rename packages/clerk-js/src/core/resources/__tests__/{Client.test.ts => Client.spec.ts} (95%) rename packages/clerk-js/src/core/resources/__tests__/{Environment.test.ts => Environment.spec.ts} (99%) rename packages/clerk-js/src/core/resources/__tests__/{ExternalAccount.test.ts => ExternalAccount.spec.ts} (83%) rename packages/clerk-js/src/core/resources/__tests__/{Image.test.ts => Image.spec.ts} (86%) rename packages/clerk-js/src/core/resources/__tests__/{Session.test.ts => Session.spec.ts} (98%) rename packages/clerk-js/src/core/resources/__tests__/{Token.test.ts => Token.spec.ts} (93%) rename packages/clerk-js/src/core/resources/__tests__/{User.test.ts => User.spec.ts} (90%) rename packages/clerk-js/src/core/resources/__tests__/{UserSettings.test.ts => UserSettings.spec.ts} (99%) rename packages/clerk-js/src/core/resources/__tests__/{Web3Wallet.test.ts => Web3Wallet.spec.ts} (84%) rename packages/clerk-js/src/core/resources/__tests__/__snapshots__/{Client.test.ts.snap => Client.spec.ts.snap} (97%) rename packages/clerk-js/src/core/resources/__tests__/__snapshots__/{Environment.test.ts.snap => Environment.spec.ts.snap} (96%) rename packages/clerk-js/src/core/resources/__tests__/__snapshots__/{Session.test.ts.snap => Session.spec.ts.snap} (90%) rename packages/clerk-js/src/core/resources/__tests__/__snapshots__/{UserSettings.test.ts.snap => UserSettings.spec.ts.snap} (96%) create mode 100644 packages/clerk-js/src/core/vitest/fixtures.ts rename packages/clerk-js/src/ui/common/__tests__/{redirects.test.ts => redirects.spec.ts} (81%) create mode 100644 packages/clerk-js/src/ui/components/OrganizationSwitcher/__tests__/vitestUtils.ts rename packages/clerk-js/src/ui/components/SignUp/__tests__/{SignUpStart.test.tsx => SignUpStart.spec.tsx} (97%) rename packages/clerk-js/src/ui/elements/__tests__/{PlainInput.test.tsx => PlainInput.spec.tsx} (96%) rename packages/clerk-js/src/ui/elements/__tests__/{RadioGroup.test.tsx => RadioGroup.spec.tsx} (96%) rename packages/clerk-js/src/ui/hooks/__tests__/{useCoreOrganizationList.test.tsx => useCoreOrganizationList.spec.tsx} (98%) rename packages/clerk-js/src/ui/hooks/__tests__/{useDevMode.test.tsx => useDevMode.spec.tsx} (93%) rename packages/clerk-js/src/ui/hooks/__tests__/{useSupportEmail.test.tsx => useSupportEmail.spec.tsx} (88%) rename packages/clerk-js/src/ui/localization/__tests__/{applyTokensToString.test.ts => applyTokensToString.spec.ts} (97%) rename packages/clerk-js/src/ui/router/__tests__/{PathRouter.test.tsx => PathRouter.spec.tsx} (94%) rename packages/clerk-js/src/ui/router/__tests__/{Switch.test.tsx => Switch.spec.tsx} (94%) rename packages/clerk-js/src/ui/router/__tests__/{VirtualRouter.test.tsx => VirtualRouter.spec.tsx} (93%) rename packages/clerk-js/src/ui/utils/__tests__/{normalizeColorString.test.ts => normalizeColorString.spec.ts} (91%) create mode 100644 packages/clerk-js/src/ui/utils/vitest/createFixtures.tsx create mode 100644 packages/clerk-js/src/ui/utils/vitest/fixtureHelpers.ts create mode 100644 packages/clerk-js/src/ui/utils/vitest/fixtures.ts create mode 100644 packages/clerk-js/src/ui/utils/vitest/mockHelpers.ts create mode 100644 packages/clerk-js/src/ui/utils/vitest/runFakeTimers.ts rename packages/clerk-js/src/utils/__tests__/{captcha.test.ts => captcha.spec.ts} (92%) rename packages/clerk-js/src/utils/__tests__/{completeSignUpFlow.test.ts => completeSignUpFlow.spec.ts} (98%) rename packages/clerk-js/src/utils/__tests__/{date.test.ts => date.spec.ts} (82%) rename packages/clerk-js/src/utils/__tests__/{dynamicParamParser.test.ts => dynamicParamParser.spec.ts} (94%) rename packages/clerk-js/src/utils/__tests__/{errors.test.ts => errors.spec.ts} (91%) rename packages/clerk-js/src/utils/__tests__/{ignoreEventValue.test.ts => ignoreEventValue.spec.ts} (97%) rename packages/clerk-js/src/utils/__tests__/{instance.test.ts => instance.spec.ts} (95%) rename packages/clerk-js/src/utils/__tests__/{jwt.test.ts => jwt.spec.ts} (94%) rename packages/clerk-js/src/utils/__tests__/{localStorage.test.ts => localStorage.spec.ts} (95%) rename packages/clerk-js/src/utils/__tests__/{memoizeStateListenerCallback.test.ts => memoizeStateListenerCallback.spec.ts} (96%) rename packages/clerk-js/src/utils/__tests__/{organization.test.ts => organization.spec.ts} (93%) rename packages/clerk-js/src/utils/__tests__/{passkeys.test.ts => passkeys.spec.ts} (99%) rename packages/clerk-js/src/utils/__tests__/{path.test.ts => path.spec.ts} (91%) rename packages/clerk-js/src/utils/__tests__/{queryStateParams.test.ts => queryStateParams.spec.ts} (95%) rename packages/clerk-js/src/utils/__tests__/{querystring.test.ts => querystring.spec.ts} (98%) rename packages/clerk-js/src/utils/__tests__/{redirectUrls.test.ts => redirectUrls.spec.ts} (99%) rename packages/clerk-js/src/utils/__tests__/{resourceParams.test.ts => resourceParams.spec.ts} (96%) rename packages/clerk-js/src/utils/__tests__/{url.test.ts => url.spec.ts} (97%) create mode 100644 packages/clerk-js/src/vitestUtils.ts diff --git a/packages/clerk-js/package.json b/packages/clerk-js/package.json index 40a680bd5ff..43e862ed049 100644 --- a/packages/clerk-js/package.json +++ b/packages/clerk-js/package.json @@ -51,6 +51,7 @@ "test:cache:clear": "jest --clearCache --useStderr", "test:ci": "jest --maxWorkers=70%", "test:coverage": "jest --collectCoverage && open coverage/lcov-report/index.html", + "test:jest": "jest", "test:vitest": "vitest", "watch": "rspack build --config rspack.config.js --env production --watch" }, @@ -80,6 +81,7 @@ "swr": "2.3.3" }, "devDependencies": { + "@emotion/jest": "^11.13.0", "@rsdoctor/rspack-plugin": "^0.4.13", "@rspack/cli": "^1.2.8", "@rspack/core": "^1.2.8", @@ -88,6 +90,8 @@ "@swc/jest": "^0.2.38", "@types/cloudflare-turnstile": "^0.2.2", "@types/webpack-env": "^1.18.8", + "jsdom": "^24.1.1", + "vite-plugin-svgr": "^4.2.0", "webpack-merge": "^5.10.0" }, "peerDependencies": { diff --git a/packages/clerk-js/src/__tests__/headless.test.ts b/packages/clerk-js/src/__tests__/headless.spec.ts similarity index 78% rename from packages/clerk-js/src/__tests__/headless.test.ts rename to packages/clerk-js/src/__tests__/headless.spec.ts index 949b6e30805..8ea5196a2a9 100644 --- a/packages/clerk-js/src/__tests__/headless.test.ts +++ b/packages/clerk-js/src/__tests__/headless.spec.ts @@ -1,7 +1,9 @@ /** - * @jest-environment node + * @vitest-environment node */ +import { describe, expect, it } from 'vitest'; + describe('clerk/headless', () => { it('JS-689: should not error when loading headless', () => { expect(() => { diff --git a/packages/clerk-js/src/__tests__/mocks/svgMock.tsx b/packages/clerk-js/src/__tests__/mocks/svgMock.tsx new file mode 100644 index 00000000000..87ea82c61fd --- /dev/null +++ b/packages/clerk-js/src/__tests__/mocks/svgMock.tsx @@ -0,0 +1,12 @@ +import React from 'react'; + +// A simple span component to mock SVG imports in Vitest +const SvgMock = React.forwardRef>((props, ref) => ( + +)); + +export const ReactComponent = SvgMock; +export default SvgMock; 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 94% 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 c744fffd27f..a7d45c04331 100644 --- a/packages/clerk-js/src/core/__tests__/clerk.redirects.test.ts +++ b/packages/clerk-js/src/core/__tests__/clerk.redirects.spec.ts @@ -1,29 +1,31 @@ +import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + import type { DevBrowser } from '../auth/devBrowser'; import { Clerk } from '../clerk'; import type { DisplayConfig } from '../resources/internal'; import { Client, Environment } from '../resources/internal'; -const mockClientFetch = jest.fn(); -const mockEnvironmentFetch = jest.fn(); +const mockClientFetch = vi.fn(); +const mockEnvironmentFetch = vi.fn(); -jest.mock('../resources/Client'); -jest.mock('../resources/Environment'); +vi.mock('../resources/Client'); +vi.mock('../resources/Environment'); // Because Jest, don't ask me why... -jest.mock('../auth/devBrowser', () => ({ +vi.mock('../auth/devBrowser', () => ({ createDevBrowser: (): DevBrowser => ({ - clear: jest.fn(), - setup: jest.fn(), - getDevBrowserJWT: jest.fn(() => 'deadbeef'), - setDevBrowserJWT: jest.fn(), - removeDevBrowserJWT: jest.fn(), + clear: vi.fn(), + setup: vi.fn(), + getDevBrowserJWT: vi.fn(() => 'deadbeef'), + setDevBrowserJWT: vi.fn(), + removeDevBrowserJWT: vi.fn(), }), })); -Client.getOrCreateInstance = jest.fn().mockImplementation(() => { +Client.getOrCreateInstance = vi.fn().mockImplementation(() => { return { fetch: mockClientFetch }; }); -Environment.getInstance = jest.fn().mockImplementation(() => { +Environment.getInstance = vi.fn().mockImplementation(() => { return { fetch: mockEnvironmentFetch }; }); @@ -59,14 +61,14 @@ const developmentPublishableKey = 'pk_test_Y2xlcmsuYWJjZWYuMTIzNDUuZGV2LmxjbGNsZ const productionPublishableKey = 'pk_live_Y2xlcmsuYWJjZWYuMTIzNDUucHJvZC5sY2xjbGVyay5jb20k'; describe('Clerk singleton - Redirects', () => { - const mockNavigate = jest.fn((to: string) => Promise.resolve(to)); + const mockNavigate = vi.fn((to: string) => Promise.resolve(to)); const mockedLoadOptions = { routerPush: mockNavigate, routerReplace: mockNavigate }; let mockWindowLocation; - let mockHref: jest.Mock; + let mockHref: vi.Mock; beforeEach(() => { - mockHref = jest.fn(); + mockHref = vi.fn(); mockWindowLocation = { host: 'test.host', hostname: 'test.host', diff --git a/packages/clerk-js/src/core/__tests__/fapiClient.test.ts b/packages/clerk-js/src/core/__tests__/fapiClient.spec.ts similarity index 95% rename from packages/clerk-js/src/core/__tests__/fapiClient.test.ts rename to packages/clerk-js/src/core/__tests__/fapiClient.spec.ts index 5e4ef4723f4..585917f4533 100644 --- a/packages/clerk-js/src/core/__tests__/fapiClient.test.ts +++ b/packages/clerk-js/src/core/__tests__/fapiClient.spec.ts @@ -1,4 +1,5 @@ import type { InstanceType } from '@clerk/types'; +import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; import { SUPPORTED_FAPI_VERSION } from '../constants'; import { createFapiClient } from '../fapiClient'; @@ -25,10 +26,10 @@ type RecursivePartial = { }; // @ts-ignore -- We don't need to fully satisfy the fetch types for the sake of this mock -global.fetch = jest.fn(() => +global.fetch = vi.fn(() => Promise.resolve>({ headers: { - get: jest.fn(() => 'sess_43'), + get: vi.fn(() => 'sess_43'), }, json: () => Promise.resolve({ foo: 42 }), }), @@ -54,7 +55,7 @@ beforeAll(() => { }); beforeEach(() => { - (global.fetch as jest.Mock).mockClear(); + (global.fetch as vi.Mock).mockClear(); }); afterAll(() => { @@ -184,10 +185,10 @@ describe('request', () => { }); it('returns array response as array', async () => { - (global.fetch as jest.Mock).mockResolvedValueOnce( + (global.fetch as vi.Mock).mockResolvedValueOnce( Promise.resolve>({ headers: { - get: jest.fn(() => 'sess_43'), + get: vi.fn(() => 'sess_43'), }, json: () => Promise.resolve([{ foo: 42 }]), }), @@ -201,7 +202,7 @@ describe('request', () => { }); it('handles the empty body on 204 response, returning null', async () => { - (global.fetch as jest.Mock).mockResolvedValueOnce( + (global.fetch as vi.Mock).mockResolvedValueOnce( Promise.resolve>({ status: 204, json: () => { diff --git a/packages/clerk-js/src/core/__tests__/tokenCache.test.ts b/packages/clerk-js/src/core/__tests__/tokenCache.spec.ts similarity index 90% rename from packages/clerk-js/src/core/__tests__/tokenCache.test.ts rename to packages/clerk-js/src/core/__tests__/tokenCache.spec.ts index 6dcf3833ab4..cca0d8e84a1 100644 --- a/packages/clerk-js/src/core/__tests__/tokenCache.test.ts +++ b/packages/clerk-js/src/core/__tests__/tokenCache.spec.ts @@ -1,10 +1,11 @@ import type { TokenResource } from '@clerk/types'; +import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'; import { Token } from '../resources/internal'; import { SessionTokenCache } from '../tokenCache'; // This is required since abstract TS methods are undefined in Jest -jest.mock('../resources/Base', () => { +vi.mock('../resources/Base', () => { class BaseResource {} return { @@ -17,11 +18,11 @@ const jwt = describe('MemoryTokenCache', () => { beforeAll(() => { - jest.useFakeTimers(); + vi.useFakeTimers(); }); afterAll(() => { - jest.useRealTimers(); + vi.useRealTimers(); }); describe('clear()', () => { @@ -87,7 +88,7 @@ describe('MemoryTokenCache', () => { expect(isResolved).toBe(false); // Wait tokenResolver to resolve - jest.advanceTimersByTime(100); + vi.advanceTimersByTime(100); await tokenResolver; // Cache is not empty, retrieve the resolved tokenResolver @@ -98,7 +99,7 @@ describe('MemoryTokenCache', () => { }); // Advance the timer to force the JWT expiration - jest.advanceTimersByTime(60 * 1000); + vi.advanceTimersByTime(60 * 1000); // Cache is empty, tokenResolver has been removed due to JWT expiration expect(cache.get(key)).toBeUndefined(); @@ -125,11 +126,11 @@ describe('MemoryTokenCache', () => { expect(cache.get(key)).toMatchObject(key); // 44s since token created - jest.advanceTimersByTime(45 * 1000); + vi.advanceTimersByTime(45 * 1000); expect(cache.get(key)).toMatchObject(key); // 46s since token created - jest.advanceTimersByTime(1 * 1000); + vi.advanceTimersByTime(1 * 1000); expect(cache.get(key)).toBeUndefined(); }); @@ -150,15 +151,15 @@ describe('MemoryTokenCache', () => { expect(cache.get(key)).toMatchObject(key); // 45s since token created - jest.advanceTimersByTime(45 * 1000); + vi.advanceTimersByTime(45 * 1000); expect(cache.get(key, 0)).toMatchObject(key); // 54s since token created - jest.advanceTimersByTime(9 * 1000); + vi.advanceTimersByTime(9 * 1000); expect(cache.get(key, 0)).toMatchObject(key); // 55s since token created - jest.advanceTimersByTime(1 * 1000); + vi.advanceTimersByTime(1 * 1000); expect(cache.get(key, 0)).toBeUndefined(); }); }); 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 72% rename from packages/clerk-js/src/core/auth/__tests__/cookieSuffix.test.ts rename to packages/clerk-js/src/core/auth/__tests__/cookieSuffix.spec.ts index 1adb90dfbbb..c50eda73114 100644 --- a/packages/clerk-js/src/core/auth/__tests__/cookieSuffix.test.ts +++ b/packages/clerk-js/src/core/auth/__tests__/cookieSuffix.spec.ts @@ -1,8 +1,10 @@ -jest.mock('@clerk/shared/keys', () => { - return { getCookieSuffix: jest.fn() }; +import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; + +vi.mock('@clerk/shared/keys', () => { + return { getCookieSuffix: vi.fn() }; }); -jest.mock('@clerk/shared/logger', () => { - return { logger: { logOnce: jest.fn() } }; +vi.mock('@clerk/shared/logger', () => { + return { logger: { logOnce: vi.fn() } }; }); import { getCookieSuffix as getSharedCookieSuffix } from '@clerk/shared/keys'; import { logger } from '@clerk/shared/logger'; @@ -11,12 +13,12 @@ import { getCookieSuffix } from '../cookieSuffix'; describe('getCookieSuffix', () => { beforeEach(() => { - (getSharedCookieSuffix as jest.Mock).mockRejectedValue(new Error('mocked error for insecure context')); + (getSharedCookieSuffix as vi.Mock).mockRejectedValue(new Error('mocked error for insecure context')); }); afterEach(() => { - (getSharedCookieSuffix as jest.Mock).mockReset(); - (logger.logOnce as jest.Mock).mockReset(); + (getSharedCookieSuffix as vi.Mock).mockReset(); + (logger.logOnce as vi.Mock).mockReset(); }); describe('getCookieSuffix(publishableKey, subtle?)', () => { 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 83% rename from packages/clerk-js/src/core/auth/__tests__/devBrowser.test.ts rename to packages/clerk-js/src/core/auth/__tests__/devBrowser.spec.ts index 787824c65e2..26fbe011864 100644 --- a/packages/clerk-js/src/core/auth/__tests__/devBrowser.test.ts +++ b/packages/clerk-js/src/core/auth/__tests__/devBrowser.spec.ts @@ -1,3 +1,5 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + import type { FapiClient } from '../../fapiClient'; import { createDevBrowser } from '../devBrowser'; @@ -8,7 +10,7 @@ type RecursivePartial = { describe('Thrown errors', () => { beforeEach(() => { // @ts-ignore - global.fetch = jest.fn(() => + global.fetch = vi.fn(() => Promise.resolve>({ ok: false, json: () => @@ -29,17 +31,17 @@ describe('Thrown errors', () => { afterEach(() => { // @ts-ignore - global.fetch?.mockClear(); + vi.mocked(global.fetch)?.mockClear(); }); // Note: The test runs without any initial or mocked values on __clerk_db_jwt cookies. // It is expected to modify the test accordingly if cookies are mocked for future extra testing. it('throws any FAPI errors during dev browser creation', async () => { - const mockCreateFapiClient = jest.fn().mockImplementation(() => { + const mockCreateFapiClient = vi.fn().mockImplementation(() => { return { - buildUrl: jest.fn(() => 'https://white-koala-42.clerk.accounts.dev/dev_browser'), - onAfterResponse: jest.fn(), - onBeforeRequest: jest.fn(), + buildUrl: vi.fn(() => 'https://white-koala-42.clerk.accounts.dev/dev_browser'), + onAfterResponse: vi.fn(), + onBeforeRequest: vi.fn(), }; }); 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 83% 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 5f2b335f22e..d12f504cfb3 100644 --- a/packages/clerk-js/src/core/auth/__tests__/getCookieDomain.test.ts +++ b/packages/clerk-js/src/core/auth/__tests__/getCookieDomain.spec.ts @@ -1,3 +1,5 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; + import type { getCookieDomain as _getCookieDomain } from '../getCookieDomain'; type CookieHandler = NonNullable[1]>; @@ -6,7 +8,7 @@ describe('getCookieDomain', () => { let getCookieDomain: typeof _getCookieDomain; beforeEach(async () => { // We're dynamically importing getCookieDomain here to reset the module-level cache - jest.resetModules(); + vi.resetModules(); getCookieDomain = await import('../getCookieDomain').then(m => m.getCookieDomain); }); @@ -20,14 +22,14 @@ describe('getCookieDomain', () => { // assume that the Public Suffix List is correctly handled by the browser. const hostname = 'app.fr.hosting.co.uk'; const handler: CookieHandler = { - get: jest + get: vi .fn() .mockReturnValueOnce(undefined) .mockReturnValueOnce(undefined) .mockReturnValueOnce(undefined) .mockReturnValueOnce('1'), - set: jest.fn().mockReturnValue(undefined), - remove: jest.fn().mockReturnValue(undefined), + set: vi.fn().mockReturnValue(undefined), + remove: vi.fn().mockReturnValue(undefined), }; const result = getCookieDomain(hostname, handler); expect(result).toBe(hostname); @@ -53,9 +55,9 @@ describe('getCookieDomain', () => { it('returns undefined if the domain could not be determined', () => { const handler: CookieHandler = { - get: jest.fn().mockReturnValue(undefined), - set: jest.fn().mockReturnValue(undefined), - remove: jest.fn().mockReturnValue(undefined), + get: vi.fn().mockReturnValue(undefined), + set: vi.fn().mockReturnValue(undefined), + remove: vi.fn().mockReturnValue(undefined), }; const hostname = 'app.hello.co.uk'; const result = getCookieDomain(hostname, handler); @@ -65,9 +67,9 @@ describe('getCookieDomain', () => { it('uses cached value if there is one', () => { const hostname = 'clerk.com'; const handler: CookieHandler = { - get: jest.fn().mockReturnValue('1'), - set: jest.fn().mockReturnValue(undefined), - remove: jest.fn().mockReturnValue(undefined), + get: vi.fn().mockReturnValue('1'), + set: vi.fn().mockReturnValue(undefined), + remove: vi.fn().mockReturnValue(undefined), }; expect(getCookieDomain(hostname, handler)).toBe(hostname); expect(getCookieDomain(hostname, handler)).toBe(hostname); 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 95% rename from packages/clerk-js/src/core/auth/__tests__/getSecureAttribute.test.ts rename to packages/clerk-js/src/core/auth/__tests__/getSecureAttribute.spec.ts index 36b2b699de6..97f9b1eff0c 100644 --- a/packages/clerk-js/src/core/auth/__tests__/getSecureAttribute.test.ts +++ b/packages/clerk-js/src/core/auth/__tests__/getSecureAttribute.spec.ts @@ -1,10 +1,12 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + import { getSecureAttribute } from '../getSecureAttribute'; describe('getSecureAttribute', () => { let windowSpy: any; beforeEach(() => { - windowSpy = jest.spyOn(window, 'window', 'get'); + windowSpy = vi.spyOn(window, 'window', 'get'); }); afterEach(() => { diff --git a/packages/clerk-js/src/core/fraudProtection.test.ts b/packages/clerk-js/src/core/fraudProtection.spec.ts similarity index 82% rename from packages/clerk-js/src/core/fraudProtection.test.ts rename to packages/clerk-js/src/core/fraudProtection.spec.ts index 98118289571..16139bdfccb 100644 --- a/packages/clerk-js/src/core/fraudProtection.test.ts +++ b/packages/clerk-js/src/core/fraudProtection.spec.ts @@ -1,3 +1,5 @@ +import { beforeEach, describe, expect, it, type Mock, vi } from 'vitest'; + import { FraudProtection } from './fraudProtection'; import type { Clerk, Client } from './resources/internal'; import { ClerkAPIResponseError } from './resources/internal'; @@ -7,7 +9,7 @@ describe('FraudProtectionService', () => { let mockClerk: Clerk; let mockClient: typeof Client; let solveCaptcha: any; - let mockManagedInModal: jest.Mock; + let mockManagedInModal: Mock; function MockCaptchaChallenge() { // @ts-ignore - we don't need to implement the entire class @@ -22,14 +24,14 @@ describe('FraudProtectionService', () => { }; beforeEach(() => { - mockManagedInModal = jest.fn().mockResolvedValue( + mockManagedInModal = vi.fn().mockResolvedValue( new Promise(r => { solveCaptcha = r; }), ); const mockClientInstance = { - __internal_sendCaptchaToken: jest.fn().mockResolvedValue({}), + __internal_sendCaptchaToken: vi.fn().mockResolvedValue({}), }; mockClient = { @@ -45,7 +47,7 @@ describe('FraudProtectionService', () => { }); it('does not handle requests that did not throw', async () => { - const fn1 = jest.fn().mockResolvedValue('result'); + const fn1 = vi.fn().mockResolvedValue('result'); const fn1res = sut.execute(mockClerk, fn1); @@ -64,17 +66,17 @@ describe('FraudProtectionService', () => { data: [{ code: 'no-idea' } as any], status: 401, }); - const fn1 = jest.fn().mockRejectedValueOnce(unrelatedError); + const fn1 = vi.fn().mockRejectedValueOnce(unrelatedError); const fn1res = sut.execute(mockClerk, fn1); - expect(fn1res).rejects.toEqual(unrelatedError); + await expect(fn1res).rejects.toEqual(unrelatedError); expect(mockManagedInModal).toHaveBeenCalledTimes(0); expect(mockClient.getOrCreateInstance().__internal_sendCaptchaToken).toHaveBeenCalledTimes(0); expect(fn1).toHaveBeenCalledTimes(1); }); it('handles parallel requests that began at the same time by handling any requests that returned requires_captcha', async () => { - const fn1 = jest.fn().mockRejectedValueOnce(createCaptchaError()).mockResolvedValueOnce('result1'); - const fn2 = jest.fn().mockResolvedValue('result2'); + const fn1 = vi.fn().mockRejectedValueOnce(createCaptchaError()).mockResolvedValueOnce('result1'); + const fn2 = vi.fn().mockResolvedValue('result2'); const fn1res = sut.execute(mockClerk, fn1); const fn2res = sut.execute(mockClerk, fn2); @@ -93,8 +95,8 @@ describe('FraudProtectionService', () => { }); it('handles parallel requests that returned 401 requires_captcha', async () => { - const fn1 = jest.fn().mockRejectedValueOnce(createCaptchaError()).mockResolvedValueOnce('result1'); - const fn2 = jest.fn().mockRejectedValueOnce(createCaptchaError()).mockResolvedValueOnce('result2'); + const fn1 = vi.fn().mockRejectedValueOnce(createCaptchaError()).mockResolvedValueOnce('result1'); + const fn2 = vi.fn().mockRejectedValueOnce(createCaptchaError()).mockResolvedValueOnce('result2'); const fn1res = sut.execute(mockClerk, fn1); const fn2res = sut.execute(mockClerk, fn2); @@ -115,8 +117,8 @@ describe('FraudProtectionService', () => { }); it('handles requests that were made in close succession by blocking all other requests if the first returns requires_captcha', async () => { - const fn1 = jest.fn().mockRejectedValueOnce(createCaptchaError()).mockResolvedValueOnce('result1'); - const fn2 = jest.fn().mockResolvedValue('result2'); + const fn1 = vi.fn().mockRejectedValueOnce(createCaptchaError()).mockResolvedValueOnce('result1'); + const fn2 = vi.fn().mockResolvedValue('result2'); // Start the first request const fn1res = sut.execute(mockClerk, fn1); @@ -147,10 +149,10 @@ describe('FraudProtectionService', () => { }); // both with fail in parallel but fn2 will temporarily be blocked from retrying - const fn1 = jest.fn().mockRejectedValueOnce(createCaptchaError()).mockRejectedValueOnce(unrelatedError); - const fn2 = jest.fn().mockRejectedValueOnce(createCaptchaError()).mockResolvedValue('result2'); + const fn1 = vi.fn().mockRejectedValueOnce(createCaptchaError()).mockRejectedValueOnce(unrelatedError); + const fn2 = vi.fn().mockRejectedValueOnce(createCaptchaError()).mockResolvedValue('result2'); // fn3 will be blocked until the captcha is solved - const fn3 = jest.fn().mockResolvedValue('result3'); + const fn3 = vi.fn().mockResolvedValue('result3'); const fn1res = sut.execute(mockClerk, fn1); const fn2res = sut.execute(mockClerk, fn2); @@ -163,7 +165,7 @@ describe('FraudProtectionService', () => { solveCaptcha(); // fn1 rejects - expect(fn1res).rejects.toEqual(unrelatedError); + await expect(fn1res).rejects.toEqual(unrelatedError); // but the other requests will be unblocked and retried await Promise.all([fn2res, fn3res]); 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 89% rename from packages/clerk-js/src/core/resources/__tests__/AuthConfig.test.ts rename to packages/clerk-js/src/core/resources/__tests__/AuthConfig.spec.ts index 95a50f77110..bfce1d5c021 100644 --- a/packages/clerk-js/src/core/resources/__tests__/AuthConfig.test.ts +++ b/packages/clerk-js/src/core/resources/__tests__/AuthConfig.spec.ts @@ -1,8 +1,10 @@ +import { describe, expect, it, vi } from 'vitest'; + import { unixEpochToDate } from '../../../utils/date'; import { AuthConfig } from '../AuthConfig'; -jest.mock('../../../utils/date', () => ({ - unixEpochToDate: jest.fn(timestamp => new Date(timestamp)), +vi.mock('../../../utils/date', () => ({ + unixEpochToDate: vi.fn(timestamp => new Date(timestamp)), })); describe('AuthConfig', () => { 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 86% rename from packages/clerk-js/src/core/resources/__tests__/Base.test.ts rename to packages/clerk-js/src/core/resources/__tests__/Base.spec.ts index bf7f8d6c57f..0eb8022b2a2 100644 --- a/packages/clerk-js/src/core/resources/__tests__/Base.test.ts +++ b/packages/clerk-js/src/core/resources/__tests__/Base.spec.ts @@ -1,3 +1,5 @@ +import { describe, expect, it, vi } from 'vitest'; + import { BaseResource } from '../internal'; class TestResource extends BaseResource { @@ -20,7 +22,7 @@ describe('BaseResource', () => { // @ts-expect-error - We're not about to mock the entire FapiClient getFapiClient: () => { return { - request: jest.fn().mockResolvedValue({ + request: vi.fn().mockResolvedValue({ payload: {}, status: 429, statusText: 'Too Many Requests', @@ -28,7 +30,7 @@ describe('BaseResource', () => { }), }; }, - __internal_setCountry: jest.fn(), + __internal_setCountry: vi.fn(), }; const resource = new TestResource(); const errResponse = await resource.fetch().catch(err => err); @@ -41,7 +43,7 @@ describe('BaseResource', () => { // @ts-expect-error - We're not about to mock the entire FapiClient getFapiClient: () => { return { - request: jest.fn().mockResolvedValue({ + request: vi.fn().mockResolvedValue({ payload: {}, status: 429, statusText: 'Too Many Requests', @@ -49,7 +51,7 @@ describe('BaseResource', () => { }), }; }, - __internal_setCountry: jest.fn(), + __internal_setCountry: vi.fn(), }; const resource = new TestResource(); const errResponse = await resource.fetch().catch(err => err); 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 95% rename from packages/clerk-js/src/core/resources/__tests__/Client.test.ts rename to packages/clerk-js/src/core/resources/__tests__/Client.spec.ts index 434f09c24c4..a43096aed4c 100644 --- a/packages/clerk-js/src/core/resources/__tests__/Client.test.ts +++ b/packages/clerk-js/src/core/resources/__tests__/Client.spec.ts @@ -1,6 +1,7 @@ import type { ClientJSON, ClientJSONSnapshot } from '@clerk/types'; +import { describe, expect, it, vi } from 'vitest'; -import { createSession, createSignIn, createSignUp, createUser } from '../../test/fixtures'; +import { createSession, createSignIn, createSignUp, createUser } from '../../vitest/fixtures'; import { BaseResource, Client } from '../internal'; describe('Client Singleton', () => { @@ -16,12 +17,12 @@ describe('Client Singleton', () => { sign_in: createSignIn({ id: 'test_sign_in_id' }, user), sign_up: createSignUp({ id: 'test_sign_up_id' }), // This is only for testing purposes, this will never happen sessions: [session], - created_at: jest.now() - 1000, - updated_at: jest.now(), + created_at: Date.now() - 1000, + updated_at: Date.now(), } as any; // @ts-expect-error This is a private method that we are mocking - BaseResource._baseFetch = jest.fn(); + BaseResource._baseFetch = vi.fn(); const client = Client.getOrCreateInstance().fromJSON(clientObjectJSON); await client.__internal_sendCaptchaToken({ captcha_token: 'test_captcha_token' }); @@ -46,12 +47,12 @@ describe('Client Singleton', () => { sign_in: createSignIn({ id: 'test_sign_in_id' }, user), sign_up: createSignUp({ id: 'test_sign_up_id' }), // This is only for testing purposes, this will never happen sessions: [session], - created_at: jest.now() - 1000, - updated_at: jest.now(), + created_at: Date.now() - 1000, + updated_at: Date.now(), } as any; // @ts-expect-error This is a private method that we are mocking - BaseResource._baseFetch = jest.fn().mockResolvedValueOnce(Promise.resolve(null)); + BaseResource._baseFetch = vi.fn().mockResolvedValueOnce(Promise.resolve(null)); const client = Client.getOrCreateInstance().fromJSON(clientObjectJSON); await client.__internal_sendCaptchaToken({ captcha_token: 'test_captcha_token' }); @@ -78,14 +79,14 @@ describe('Client Singleton', () => { sign_in: createSignIn({ id: 'test_sign_in_id' }, user), sign_up: createSignUp({ id: 'test_sign_up_id' }), // This is only for testing purposes, this will never happen sessions: [session], - created_at: jest.now() - 1000, - updated_at: jest.now(), + created_at: Date.now() - 1000, + updated_at: Date.now(), }; const destroyedSession = createSession( { id: 'test_session_id', - abandon_at: jest.now(), + abandon_at: Date.now(), status: 'ended', last_active_token: undefined, }, @@ -99,12 +100,12 @@ describe('Client Singleton', () => { sign_in: null, sign_up: null, sessions: [destroyedSession], - created_at: jest.now() - 1000, - updated_at: jest.now(), + created_at: Date.now() - 1000, + updated_at: Date.now(), }; // @ts-expect-error This is a private method that we are mocking - BaseResource._fetch = jest.fn().mockReturnValue( + BaseResource._fetch = vi.fn().mockReturnValue( Promise.resolve({ client: null, response: clientObjectDeletedJSON, 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 99% rename from packages/clerk-js/src/core/resources/__tests__/Environment.test.ts rename to packages/clerk-js/src/core/resources/__tests__/Environment.spec.ts index 4fcb37f622d..7bf469c9e6d 100644 --- a/packages/clerk-js/src/core/resources/__tests__/Environment.test.ts +++ b/packages/clerk-js/src/core/resources/__tests__/Environment.spec.ts @@ -1,4 +1,5 @@ import type { EnvironmentJSONSnapshot } from '@clerk/types'; +import { describe, expect, it } from 'vitest'; import { Environment } from '../internal'; 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 83% rename from packages/clerk-js/src/core/resources/__tests__/ExternalAccount.test.ts rename to packages/clerk-js/src/core/resources/__tests__/ExternalAccount.spec.ts index c61986913bb..d19bd96cf33 100644 --- a/packages/clerk-js/src/core/resources/__tests__/ExternalAccount.test.ts +++ b/packages/clerk-js/src/core/resources/__tests__/ExternalAccount.spec.ts @@ -1,4 +1,5 @@ import { BaseResource, ExternalAccount } from '../internal'; +import { describe, expect, it, vi } from 'vitest'; describe('External account', () => { it('reauthorize', async () => { @@ -10,7 +11,7 @@ describe('External account', () => { }; // @ts-ignore - BaseResource._fetch = jest.fn().mockReturnValue(Promise.resolve({ response: externalAccountJSON })); + BaseResource._fetch = vi.fn().mockReturnValue(Promise.resolve({ response: externalAccountJSON })); const externalAccount = new ExternalAccount({ id: targetId }, '/me/external_accounts'); await externalAccount.reauthorize({ additionalScopes: ['read', 'write'], redirectUrl: 'https://test.com' }); @@ -36,7 +37,7 @@ describe('External account', () => { }; // @ts-ignore - BaseResource._fetch = jest.fn().mockReturnValue(Promise.resolve({ response: deletedObjectJSON })); + BaseResource._fetch = vi.fn().mockReturnValue(Promise.resolve({ response: deletedObjectJSON })); const externalAccount = new ExternalAccount({ id: targetId }, '/me/external_accounts'); await externalAccount.destroy(); 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 86% rename from packages/clerk-js/src/core/resources/__tests__/Image.test.ts rename to packages/clerk-js/src/core/resources/__tests__/Image.spec.ts index c5118f68309..89f7888081b 100644 --- a/packages/clerk-js/src/core/resources/__tests__/Image.test.ts +++ b/packages/clerk-js/src/core/resources/__tests__/Image.spec.ts @@ -1,3 +1,5 @@ +import { describe, expect, it, vi } from 'vitest'; + import { Image } from '../Image'; import { BaseResource } from '../internal'; @@ -10,7 +12,7 @@ describe('Image', () => { }; // @ts-ignore - BaseResource._fetch = jest.fn().mockReturnValue( + BaseResource._fetch = vi.fn().mockReturnValue( Promise.resolve({ client: {}, response: mockResponse, 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 98% rename from packages/clerk-js/src/core/resources/__tests__/Session.test.ts rename to packages/clerk-js/src/core/resources/__tests__/Session.spec.ts index d7b42229651..eb065923c14 100644 --- a/packages/clerk-js/src/core/resources/__tests__/Session.test.ts +++ b/packages/clerk-js/src/core/resources/__tests__/Session.spec.ts @@ -1,8 +1,9 @@ import type { InstanceType, OrganizationJSON, SessionJSON } from '@clerk/types'; +import { afterEach, beforeEach, describe, expect, it, type Mock, vi } from 'vitest'; import { eventBus } from '../../events'; import { createFapiClient } from '../../fapiClient'; -import { clerkMock, createUser, mockJwt, mockNetworkFailedFetch } from '../../test/fixtures'; +import { clerkMock, createUser, mockJwt, mockNetworkFailedFetch } from '../../vitest/fixtures'; import { SessionTokenCache } from '../../tokenCache'; import { BaseResource, Organization, Session } from '../internal'; @@ -23,7 +24,7 @@ describe('Session', () => { let dispatchSpy; beforeEach(() => { - dispatchSpy = jest.spyOn(eventBus, 'emit'); + dispatchSpy = vi.spyOn(eventBus, 'emit'); BaseResource.clerk = clerkMock() as any; }); @@ -169,7 +170,7 @@ describe('Session', () => { writable: true, value: false, }); - warnSpy = jest.spyOn(console, 'warn').mockReturnValue(); + warnSpy = vi.spyOn(console, 'warn').mockReturnValue(); }); afterEach(() => { @@ -221,7 +222,7 @@ describe('Session', () => { await session.getToken(); - expect((BaseResource.fapiClient.request as jest.Mock).mock.calls[0][0]).toMatchObject({ + expect((BaseResource.fapiClient.request as Mock).mock.calls[0][0]).toMatchObject({ body: { organizationId: 'newActiveOrganization' }, }); }); 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 93% rename from packages/clerk-js/src/core/resources/__tests__/Token.test.ts rename to packages/clerk-js/src/core/resources/__tests__/Token.spec.ts index 94e5e473354..fd19452f5e0 100644 --- a/packages/clerk-js/src/core/resources/__tests__/Token.test.ts +++ b/packages/clerk-js/src/core/resources/__tests__/Token.spec.ts @@ -1,8 +1,9 @@ import type { InstanceType } from '@clerk/types'; +import { afterEach, beforeEach, describe, expect, it, type Mock, vi } from 'vitest'; import { SUPPORTED_FAPI_VERSION } from '../../constants'; import { createFapiClient } from '../../fapiClient'; -import { mockFetch, mockNetworkFailedFetch } from '../../test/fixtures'; +import { mockFetch, mockNetworkFailedFetch } from '../../vitest/fixtures'; import { BaseResource } from '../internal'; import { Token } from '../Token'; @@ -17,7 +18,7 @@ const baseFapiClientOptions = { describe('Token', () => { describe('create', () => { afterEach(() => { - (global.fetch as jest.Mock)?.mockClear(); + (global.fetch as Mock)?.mockClear(); BaseResource.clerk = null as any; }); @@ -49,7 +50,7 @@ describe('Token', () => { writable: true, value: false, }); - warnSpy = jest.spyOn(console, 'warn').mockReturnValue(); + warnSpy = vi.spyOn(console, 'warn').mockReturnValue(); }); afterEach(() => { 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 90% rename from packages/clerk-js/src/core/resources/__tests__/User.test.ts rename to packages/clerk-js/src/core/resources/__tests__/User.spec.ts index bfec0f83123..04eda1aa6d4 100644 --- a/packages/clerk-js/src/core/resources/__tests__/User.test.ts +++ b/packages/clerk-js/src/core/resources/__tests__/User.spec.ts @@ -1,4 +1,5 @@ import type { UserJSON } from '@clerk/types'; +import { describe, expect, it, vi } from 'vitest'; import { BaseResource } from '../internal'; import { User } from '../User'; @@ -14,7 +15,7 @@ describe('User', () => { }; // @ts-ignore - BaseResource._fetch = jest.fn().mockReturnValue(Promise.resolve({ response: externalAccountJSON })); + BaseResource._fetch = vi.fn().mockReturnValue(Promise.resolve({ response: externalAccountJSON })); const user = new User({ email_addresses: [], @@ -49,7 +50,7 @@ describe('User', () => { }; // @ts-ignore - BaseResource._fetch = jest.fn().mockReturnValue(Promise.resolve({ response: web3WalletJSON })); + BaseResource._fetch = vi.fn().mockReturnValue(Promise.resolve({ response: web3WalletJSON })); const user = new User({ email_addresses: [], @@ -148,7 +149,7 @@ describe('User', () => { }; // @ts-ignore - BaseResource._fetch = jest.fn().mockReturnValue(Promise.resolve({ response: totpJSON })); + BaseResource._fetch = vi.fn().mockReturnValue(Promise.resolve({ response: totpJSON })); const user = new User({ email_addresses: [], @@ -176,7 +177,7 @@ describe('User', () => { }; // @ts-ignore - BaseResource._fetch = jest.fn().mockReturnValue(Promise.resolve({ response: totpJSON })); + BaseResource._fetch = vi.fn().mockReturnValue(Promise.resolve({ response: totpJSON })); const user = new User({ email_addresses: [], @@ -203,7 +204,7 @@ describe('User', () => { }; // @ts-ignore - BaseResource._fetch = jest.fn().mockReturnValue(Promise.resolve({ response: deletedObjectJSON })); + BaseResource._fetch = vi.fn().mockReturnValue(Promise.resolve({ response: deletedObjectJSON })); const user = new User({ email_addresses: [], @@ -229,7 +230,7 @@ describe('User', () => { }; // @ts-ignore - BaseResource._fetch = jest.fn().mockReturnValue(Promise.resolve({ response: backupCodeJSON })); + BaseResource._fetch = vi.fn().mockReturnValue(Promise.resolve({ response: backupCodeJSON })); const user = new User({ email_addresses: [], @@ -270,7 +271,7 @@ describe('User', () => { it('.updatePassword triggers a request to change password', async () => { // @ts-ignore - BaseResource._fetch = jest.fn().mockReturnValue(Promise.resolve({ response: {} })); + BaseResource._fetch = vi.fn().mockReturnValue(Promise.resolve({ response: {} })); const user = new User({} as unknown as UserJSON); const params = { @@ -289,7 +290,7 @@ describe('User', () => { it('.removePassword triggers a request to remove password', async () => { // @ts-ignore - BaseResource._fetch = jest.fn().mockReturnValue(Promise.resolve({ response: {} })); + BaseResource._fetch = vi.fn().mockReturnValue(Promise.resolve({ response: {} })); const user = new User({} as unknown as UserJSON); const params = { 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 99% rename from packages/clerk-js/src/core/resources/__tests__/UserSettings.test.ts rename to packages/clerk-js/src/core/resources/__tests__/UserSettings.spec.ts index 94ae59e76bf..3a623a1bebb 100644 --- a/packages/clerk-js/src/core/resources/__tests__/UserSettings.test.ts +++ b/packages/clerk-js/src/core/resources/__tests__/UserSettings.spec.ts @@ -1,4 +1,5 @@ import type { UserSettingsJSON } from '@clerk/types'; +import { describe, expect, it } from 'vitest'; import { UserSettings } from '../internal'; 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 84% rename from packages/clerk-js/src/core/resources/__tests__/Web3Wallet.test.ts rename to packages/clerk-js/src/core/resources/__tests__/Web3Wallet.spec.ts index 0bb32228eb5..9848dbe4561 100644 --- a/packages/clerk-js/src/core/resources/__tests__/Web3Wallet.test.ts +++ b/packages/clerk-js/src/core/resources/__tests__/Web3Wallet.spec.ts @@ -1,4 +1,5 @@ import type { Web3WalletJSON } from '@clerk/types'; +import { describe, expect, it, vi } from 'vitest'; import { BaseResource, Web3Wallet } from '../internal'; @@ -10,7 +11,7 @@ describe('Web3 wallet', () => { } as Web3WalletJSON; // @ts-ignore - BaseResource._fetch = jest.fn().mockReturnValue(Promise.resolve({ response: web3WalletJSON })); + BaseResource._fetch = vi.fn().mockReturnValue(Promise.resolve({ response: web3WalletJSON })); const web3Wallet = new Web3Wallet(web3WalletJSON, '/me/web3_wallets'); await web3Wallet.create(); @@ -33,7 +34,7 @@ describe('Web3 wallet', () => { } as Web3WalletJSON; // @ts-ignore - BaseResource._fetch = jest.fn().mockReturnValue(Promise.resolve({ response: web3WalletJSON })); + BaseResource._fetch = vi.fn().mockReturnValue(Promise.resolve({ response: web3WalletJSON })); const web3Wallet = new Web3Wallet(web3WalletJSON, '/me/web3_wallets'); await web3Wallet.prepareVerification({ strategy: 'web3_metamask_signature' }); @@ -56,7 +57,7 @@ describe('Web3 wallet', () => { } as Web3WalletJSON; // @ts-ignore - BaseResource._fetch = jest.fn().mockReturnValue(Promise.resolve({ response: web3WalletJSON })); + BaseResource._fetch = vi.fn().mockReturnValue(Promise.resolve({ response: web3WalletJSON })); const web3Wallet = new Web3Wallet(web3WalletJSON, '/me/web3_wallets'); await web3Wallet.attemptVerification({ signature: 'mock-signature' }); @@ -81,7 +82,7 @@ describe('Web3 wallet', () => { }; // @ts-ignore - BaseResource._fetch = jest.fn().mockReturnValue(Promise.resolve({ response: deletedObjectJSON })); + BaseResource._fetch = vi.fn().mockReturnValue(Promise.resolve({ response: deletedObjectJSON })); const web3Wallet = new Web3Wallet({ id: targetId }, '/me/web3_wallets'); await web3Wallet.destroy(); diff --git a/packages/clerk-js/src/core/resources/__tests__/__snapshots__/Client.test.ts.snap b/packages/clerk-js/src/core/resources/__tests__/__snapshots__/Client.spec.ts.snap similarity index 97% rename from packages/clerk-js/src/core/resources/__tests__/__snapshots__/Client.test.ts.snap rename to packages/clerk-js/src/core/resources/__tests__/__snapshots__/Client.spec.ts.snap index 85afbcf660e..683a9bbe554 100644 --- a/packages/clerk-js/src/core/resources/__tests__/__snapshots__/Client.test.ts.snap +++ b/packages/clerk-js/src/core/resources/__tests__/__snapshots__/Client.spec.ts.snap @@ -1,6 +1,6 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`Client Singleton __internal_toSnapshot() 1`] = ` +exports[`Client Singleton > __internal_toSnapshot() 1`] = ` { "captcha_bypass": false, "cookie_expires_at": null, @@ -204,7 +204,7 @@ exports[`Client Singleton __internal_toSnapshot() 1`] = ` } `; -exports[`Client Singleton has the same initial properties 1`] = ` +exports[`Client Singleton > has the same initial properties 1`] = ` Client { "captchaBypass": false, "cookieExpiresAt": null, @@ -244,7 +244,6 @@ Client { }, "expireAt": 2024-12-11T13:42:26.849Z, "externalVerificationRedirectURL": null, - "id": undefined, "message": null, "nonce": null, "pathRoot": "", @@ -276,7 +275,6 @@ Client { }, "expireAt": 2024-12-11T13:42:26.849Z, "externalVerificationRedirectURL": null, - "id": undefined, "message": null, "nonce": null, "pathRoot": "", @@ -315,7 +313,6 @@ Client { "createdSessionId": null, "createdUserId": null, "emailAddress": null, - "externalAccount": undefined, "firstName": null, "hasPassword": false, "id": "", @@ -355,7 +352,6 @@ Client { }, "expireAt": 2024-12-11T13:42:26.849Z, "externalVerificationRedirectURL": null, - "id": undefined, "message": null, "nextAction": "", "nonce": null, @@ -383,7 +379,6 @@ Client { }, "expireAt": 2024-12-11T13:42:26.849Z, "externalVerificationRedirectURL": null, - "id": undefined, "message": null, "nonce": null, "pathRoot": "", @@ -409,7 +404,6 @@ Client { }, "expireAt": 2024-12-11T13:42:26.849Z, "externalVerificationRedirectURL": null, - "id": undefined, "message": null, "nextAction": "", "nonce": null, @@ -437,7 +431,6 @@ Client { }, "expireAt": 2024-12-11T13:42:26.849Z, "externalVerificationRedirectURL": null, - "id": undefined, "message": null, "nextAction": "", "nonce": null, diff --git a/packages/clerk-js/src/core/resources/__tests__/__snapshots__/Environment.test.ts.snap b/packages/clerk-js/src/core/resources/__tests__/__snapshots__/Environment.spec.ts.snap similarity index 96% rename from packages/clerk-js/src/core/resources/__tests__/__snapshots__/Environment.test.ts.snap rename to packages/clerk-js/src/core/resources/__tests__/__snapshots__/Environment.spec.ts.snap index eed8966b4d4..02c88013910 100644 --- a/packages/clerk-js/src/core/resources/__tests__/__snapshots__/Environment.test.ts.snap +++ b/packages/clerk-js/src/core/resources/__tests__/__snapshots__/Environment.spec.ts.snap @@ -1,6 +1,6 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`Environment __internal_toSnapshot() 1`] = ` +exports[`Environment > __internal_toSnapshot() 1`] = ` { "auth_config": { "claimed_at": null, @@ -273,11 +273,10 @@ exports[`Environment __internal_toSnapshot() 1`] = ` } `; -exports[`Environment defaults values when instantiated without arguments 1`] = ` +exports[`Environment > defaults values when instantiated without arguments 1`] = ` Environment { "authConfig": AuthConfig { "claimedAt": null, - "id": undefined, "pathRoot": "", "preferredChannels": null, "reverification": false, @@ -290,7 +289,6 @@ Environment { "hasPaidUserPlans": false, "stripePublishableKey": "", }, - "id": undefined, "pathRoot": "", }, "displayConfig": DisplayConfig { @@ -307,7 +305,6 @@ Environment { "backendHost": "", "branded": false, "captchaHeartbeat": false, - "captchaHeartbeatIntervalMs": undefined, "captchaOauthBypass": [ "oauth_google", "oauth_microsoft", @@ -317,11 +314,8 @@ Environment { "captchaPublicKey": null, "captchaPublicKeyInvisible": null, "captchaWidgetType": null, - "clerkJSVersion": undefined, "createOrganizationUrl": "", - "experimental__forceOauthFirst": undefined, "faviconImageUrl": "", - "googleOneTapClientId": undefined, "homeUrl": "", "id": "", "instanceEnvironmentType": "", @@ -339,7 +333,6 @@ Environment { "userProfileUrl": "", "waitlistUrl": "", }, - "id": undefined, "isDevelopmentOrStaging": [Function], "isProduction": [Function], "isSingleSession": [Function], @@ -355,8 +348,6 @@ Environment { "enrollmentModes": [], }, "enabled": false, - "forceOrganizationSelection": undefined, - "id": undefined, "maxAllowedMemberships": 1, "pathRoot": "", }, @@ -514,11 +505,10 @@ Environment { } `; -exports[`Environment has the same initial properties 1`] = ` +exports[`Environment > has the same initial properties 1`] = ` Environment { "authConfig": AuthConfig { "claimedAt": null, - "id": undefined, "pathRoot": "", "preferredChannels": null, "reverification": true, @@ -531,7 +521,6 @@ Environment { "hasPaidUserPlans": false, "stripePublishableKey": "", }, - "id": undefined, "pathRoot": "", }, "displayConfig": DisplayConfig { @@ -556,7 +545,6 @@ Environment { "captchaWidgetType": null, "clerkJSVersion": "5", "createOrganizationUrl": "", - "experimental__forceOauthFirst": undefined, "faviconImageUrl": "", "googleOneTapClientId": undefined, "homeUrl": "", @@ -595,7 +583,6 @@ Environment { "userProfileUrl": "", "waitlistUrl": "", }, - "id": undefined, "isDevelopmentOrStaging": [Function], "isProduction": [Function], "isSingleSession": [Function], @@ -612,7 +599,6 @@ Environment { }, "enabled": false, "forceOrganizationSelection": false, - "id": undefined, "maxAllowedMemberships": 5, "pathRoot": "", }, diff --git a/packages/clerk-js/src/core/resources/__tests__/__snapshots__/Session.test.ts.snap b/packages/clerk-js/src/core/resources/__tests__/__snapshots__/Session.spec.ts.snap similarity index 90% rename from packages/clerk-js/src/core/resources/__tests__/__snapshots__/Session.test.ts.snap rename to packages/clerk-js/src/core/resources/__tests__/__snapshots__/Session.spec.ts.snap index 001f91d1a78..2fea28d442a 100644 --- a/packages/clerk-js/src/core/resources/__tests__/__snapshots__/Session.test.ts.snap +++ b/packages/clerk-js/src/core/resources/__tests__/__snapshots__/Session.spec.ts.snap @@ -1,12 +1,11 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`Session getToken() dispatches token:update event on getToken with active organization 1`] = ` +exports[`Session > getToken() > dispatches token:update event on getToken with active organization 1`] = ` [ "token:update", { "token": Token { "getRawString": [Function], - "id": undefined, "jwt": { "claims": { "__raw": "eyJhbGciOiJSUzI1NiIsImtpZCI6Imluc18yR0lvUWhiVXB5MGhYN0IyY1ZrdVRNaW5Yb0QiLCJ0eXAiOiJKV1QifQ.eyJhenAiOiJodHRwczovL2FjY291bnRzLmluc3BpcmVkLnB1bWEtNzQubGNsLmRldiIsImV4cCI6MTY2NjY0ODMxMCwiaWF0IjoxNjY2NjQ4MjUwLCJpc3MiOiJodHRwczovL2NsZXJrLmluc3BpcmVkLnB1bWEtNzQubGNsLmRldiIsIm5iZiI6MTY2NjY0ODI0MCwic2lkIjoic2Vzc18yR2JEQjRlbk5kQ2E1dlMxenBDM1h6Zzl0SzkiLCJzdWIiOiJ1c2VyXzJHSXBYT0VwVnlKdzUxcmtabjlLbW5jNlN4ciJ9.n1Usc-DLDftqA0Xb-_2w8IGs4yjCmwc5RngwbSRvwevuZOIuRoeHmE2sgCdEvjfJEa7ewL6EVGVcM557TWPW--g_J1XQPwBy8tXfz7-S73CEuyRFiR97L2AHRdvRtvGtwR-o6l8aHaFxtlmfWbQXfg4kFJz2UGe9afmh3U9-f_4JOZ5fa3mI98UMy1-bo20vjXeWQ9aGrqaxHQxjnzzC-1Kpi5LdPvhQ16H0dPB8MHRTSM5TAuLKTpPV7wqixmbtcc2-0k6b9FKYZNqRVTaIyV-lifZloBvdzlfOF8nW1VVH_fx-iW5Q3hovHFcJIULHEC1kcAYTubbxzpgeVQepGg", @@ -35,20 +34,19 @@ exports[`Session getToken() dispatches token:update event on getToken with activ ] `; -exports[`Session getToken() dispatches token:update event on getToken with active organization 2`] = ` +exports[`Session > getToken() > dispatches token:update event on getToken with active organization 2`] = ` [ "session:tokenResolved", null, ] `; -exports[`Session getToken() dispatches token:update event on getToken without active organization 1`] = ` +exports[`Session > getToken() > dispatches token:update event on getToken without active organization 1`] = ` [ "token:update", { "token": Token { "getRawString": [Function], - "id": undefined, "jwt": { "claims": { "__raw": "eyJhbGciOiJSUzI1NiIsImtpZCI6Imluc18yR0lvUWhiVXB5MGhYN0IyY1ZrdVRNaW5Yb0QiLCJ0eXAiOiJKV1QifQ.eyJhenAiOiJodHRwczovL2FjY291bnRzLmluc3BpcmVkLnB1bWEtNzQubGNsLmRldiIsImV4cCI6MTY2NjY0ODMxMCwiaWF0IjoxNjY2NjQ4MjUwLCJpc3MiOiJodHRwczovL2NsZXJrLmluc3BpcmVkLnB1bWEtNzQubGNsLmRldiIsIm5iZiI6MTY2NjY0ODI0MCwic2lkIjoic2Vzc18yR2JEQjRlbk5kQ2E1dlMxenBDM1h6Zzl0SzkiLCJzdWIiOiJ1c2VyXzJHSXBYT0VwVnlKdzUxcmtabjlLbW5jNlN4ciJ9.n1Usc-DLDftqA0Xb-_2w8IGs4yjCmwc5RngwbSRvwevuZOIuRoeHmE2sgCdEvjfJEa7ewL6EVGVcM557TWPW--g_J1XQPwBy8tXfz7-S73CEuyRFiR97L2AHRdvRtvGtwR-o6l8aHaFxtlmfWbQXfg4kFJz2UGe9afmh3U9-f_4JOZ5fa3mI98UMy1-bo20vjXeWQ9aGrqaxHQxjnzzC-1Kpi5LdPvhQ16H0dPB8MHRTSM5TAuLKTpPV7wqixmbtcc2-0k6b9FKYZNqRVTaIyV-lifZloBvdzlfOF8nW1VVH_fx-iW5Q3hovHFcJIULHEC1kcAYTubbxzpgeVQepGg", @@ -77,7 +75,7 @@ exports[`Session getToken() dispatches token:update event on getToken without ac ] `; -exports[`Session getToken() dispatches token:update event on getToken without active organization 2`] = ` +exports[`Session > getToken() > dispatches token:update event on getToken without active organization 2`] = ` [ "session:tokenResolved", null, diff --git a/packages/clerk-js/src/core/resources/__tests__/__snapshots__/UserSettings.test.ts.snap b/packages/clerk-js/src/core/resources/__tests__/__snapshots__/UserSettings.spec.ts.snap similarity index 96% rename from packages/clerk-js/src/core/resources/__tests__/__snapshots__/UserSettings.test.ts.snap rename to packages/clerk-js/src/core/resources/__tests__/__snapshots__/UserSettings.spec.ts.snap index 5ed6282b2f3..194316a0b62 100644 --- a/packages/clerk-js/src/core/resources/__tests__/__snapshots__/UserSettings.test.ts.snap +++ b/packages/clerk-js/src/core/resources/__tests__/__snapshots__/UserSettings.spec.ts.snap @@ -1,6 +1,6 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`UserSettings defaults values when instantiated with no arguments 1`] = ` +exports[`UserSettings > defaults values when instantiated with no arguments 1`] = ` UserSettings { "actions": { "create_organization": false, diff --git a/packages/clerk-js/src/core/vitest/fixtures.ts b/packages/clerk-js/src/core/vitest/fixtures.ts new file mode 100644 index 00000000000..f7d1587fc97 --- /dev/null +++ b/packages/clerk-js/src/core/vitest/fixtures.ts @@ -0,0 +1,285 @@ +import type { + Clerk, + EmailAddressJSON, + ExternalAccountJSON, + OAuthProvider, + OrganizationCustomRoleKey, + OrganizationJSON, + OrganizationMembershipJSON, + OrganizationPermissionKey, + PhoneNumberJSON, + SessionJSON, + SignInJSON, + SignUpJSON, + UserJSON, +} from '@clerk/types'; +import { vi } from 'vitest'; + +export const mockJwt = + 'eyJhbGciOiJSUzI1NiIsImtpZCI6Imluc18yR0lvUWhiVXB5MGhYN0IyY1ZrdVRNaW5Yb0QiLCJ0eXAiOiJKV1QifQ.eyJhenAiOiJodHRwczovL2FjY291bnRzLmluc3BpcmVkLnB1bWEtNzQubGNsLmRldiIsImV4cCI6MTY2NjY0ODMxMCwiaWF0IjoxNjY2NjQ4MjUwLCJpc3MiOiJodHRwczovL2NsZXJrLmluc3BpcmVkLnB1bWEtNzQubGNsLmRldiIsIm5iZiI6MTY2NjY0ODI0MCwic2lkIjoic2Vzc18yR2JEQjRlbk5kQ2E1dlMxenBDM1h6Zzl0SzkiLCJzdWIiOiJ1c2VyXzJHSXBYT0VwVnlKdzUxcmtabjlLbW5jNlN4ciJ9.n1Usc-DLDftqA0Xb-_2w8IGs4yjCmwc5RngwbSRvwevuZOIuRoeHmE2sgCdEvjfJEa7ewL6EVGVcM557TWPW--g_J1XQPwBy8tXfz7-S73CEuyRFiR97L2AHRdvRtvGtwR-o6l8aHaFxtlmfWbQXfg4kFJz2UGe9afmh3U9-f_4JOZ5fa3mI98UMy1-bo20vjXeWQ9aGrqaxHQxjnzzC-1Kpi5LdPvhQ16H0dPB8MHRTSM5TAuLKTpPV7wqixmbtcc2-0k6b9FKYZNqRVTaIyV-lifZloBvdzlfOF8nW1VVH_fx-iW5Q3hovHFcJIULHEC1kcAYTubbxzpgeVQepGg'; + +export type OrgParams = Partial & { + role?: OrganizationCustomRoleKey; + permissions?: OrganizationPermissionKey[]; +}; + +type WithUserParams = Omit< + Partial, + 'email_addresses' | 'phone_numbers' | 'external_accounts' | 'organization_memberships' +> & { + email_addresses?: Array>; + phone_numbers?: Array>; + external_accounts?: Array>; + organization_memberships?: Array; +}; + +type WithSessionParams = Partial; + +export const getOrganizationId = (orgParams: OrgParams) => orgParams?.id || orgParams?.name || 'test_id'; + +export const createOrganizationMembership = (params: OrgParams): OrganizationMembershipJSON => { + const { role, permissions, ...orgParams } = params; + return { + created_at: new Date().getTime(), + id: getOrganizationId(orgParams), + object: 'organization_membership', + organization: { + created_at: new Date().getTime(), + id: getOrganizationId(orgParams), + image_url: + 'https://img.clerk.com/eyJ0eXBlIjoiZGVmYXVsdCIsImlpZCI6Imluc18xbHlXRFppb2JyNjAwQUtVZVFEb1NsckVtb00iLCJyaWQiOiJ1c2VyXzJKbElJQTN2VXNjWXh1N2VUMnhINmFrTGgxOCIsImluaXRpYWxzIjoiREsifQ?width=160', + max_allowed_memberships: 3, + members_count: 1, + name: 'Org', + object: 'organization', + pending_invitations_count: 0, + public_metadata: {}, + slug: null, + updated_at: new Date().getTime(), + ...orgParams, + } as OrganizationJSON, + public_metadata: {}, + role: role || 'admin', + permissions: permissions || [ + 'org:sys_domains:manage', + 'org:sys_domains:read', + 'org:sys_memberships:manage', + 'org:sys_memberships:read', + 'org:sys_profile:delete', + 'org:sys_profile:manage', + ], + updated_at: new Date().getTime(), + } as OrganizationMembershipJSON; +}; + +export const createEmail = (params?: Partial): EmailAddressJSON => { + return { + object: 'email_address', + id: params?.email_address || '', + email_address: 'test@clerk.com', + reserved: false, + verification: { + status: 'verified', + strategy: 'email_link', + attempts: null, + expire_at: 1635977979774, + }, + linked_to: [], + ...params, + } as EmailAddressJSON; +}; + +export const createPhoneNumber = (params?: Partial): PhoneNumberJSON => { + return { + object: 'phone_number', + id: params?.phone_number || '', + phone_number: '+30 691 1111111', + reserved: false, + verification: { + status: 'verified', + strategy: 'phone_code', + attempts: null, + expire_at: 1635977979774, + }, + linked_to: [], + ...params, + } as PhoneNumberJSON; +}; + +export const createExternalAccount = (params?: Partial): ExternalAccountJSON => { + return { + id: params?.provider || '', + object: 'external_account', + provider: 'google', + identification_id: '98675202', + provider_user_id: '3232', + approved_scopes: '', + email_address: 'test@clerk.com', + first_name: 'First name', + last_name: 'Last name', + image_url: '', + username: '', + phoneNumber: '', + verification: { + status: 'verified', + strategy: '', + attempts: null, + expire_at: 1635977979774, + }, + ...params, + } as ExternalAccountJSON; +}; + +export const createUser = (params?: WithUserParams): UserJSON => { + const res = { + object: 'user', + id: params?.id || 'user_123', + primary_email_address_id: '', + primary_phone_number_id: '', + primary_web3_wallet_id: '', + image_url: '', + username: 'testUsername', + web3_wallets: [], + password: '', + profile_image_id: '', + first_name: 'FirstName', + last_name: 'LastName', + password_enabled: false, + totp_enabled: false, + backup_code_enabled: false, + two_factor_enabled: false, + public_metadata: {}, + unsafe_metadata: {}, + last_sign_in_at: null, + updated_at: new Date().getTime(), + created_at: new Date().getTime(), + ...params, + email_addresses: (params?.email_addresses || []).map(e => + typeof e === 'string' ? createEmail({ email_address: e }) : createEmail(e), + ), + phone_numbers: (params?.phone_numbers || []).map(n => + typeof n === 'string' ? createPhoneNumber({ phone_number: n }) : createPhoneNumber(n), + ), + external_accounts: (params?.external_accounts || []).map(p => + typeof p === 'string' ? createExternalAccount({ provider: p }) : createExternalAccount(p), + ), + organization_memberships: (params?.organization_memberships || []).map(o => + typeof o === 'string' ? createOrganizationMembership({ name: o }) : createOrganizationMembership(o), + ), + } as UserJSON; + res.primary_email_address_id = res.email_addresses[0]?.id; + return res; +}; + +export const createSession = (sessionParams: WithSessionParams = {}, user: Partial = {}) => { + return { + object: 'session', + id: sessionParams.id, + status: sessionParams.status, + expire_at: sessionParams.expire_at || Date.now() + 5000, + abandon_at: sessionParams.abandon_at, + last_active_at: sessionParams.last_active_at || Date.now(), + last_active_organization_id: sessionParams.last_active_organization_id, + actor: sessionParams.actor, + user: createUser({}), + public_user_data: { + first_name: user.first_name, + last_name: user.last_name, + image_url: user.image_url, + has_image: user.has_image, + identifier: user.email_addresses?.find(e => e.id === user.primary_email_address_id)?.email_address || '', + }, + created_at: sessionParams.created_at || Date.now() - 1000, + updated_at: sessionParams.updated_at || Date.now(), + last_active_token: { + object: 'token', + jwt: mockJwt, + }, + } as SessionJSON; +}; + +export const createSignIn = (signInParams: Partial = {}, user: Partial = {}) => { + return { + id: signInParams.id, + created_session_id: signInParams.created_session_id, + status: signInParams.status, + first_factor_verification: signInParams.first_factor_verification, + identifier: signInParams.identifier, + object: 'sign_in', + second_factor_verification: signInParams.second_factor_verification, + supported_first_factors: signInParams.supported_first_factors, + supported_second_factors: signInParams.supported_second_factors, + user_data: { + first_name: user.first_name, + last_name: user.last_name, + image_url: user.image_url, + has_image: user.has_image, + }, + } as SignInJSON; +}; + +export const createSignUp = (signUpParams: Partial = {}) => { + return { + id: signUpParams.id, + created_session_id: signUpParams.created_session_id, + status: signUpParams.status, + abandon_at: signUpParams.abandon_at, + created_user_id: signUpParams.created_user_id, + email_address: signUpParams.email_address, + external_account: signUpParams.external_account, + external_account_strategy: signUpParams.external_account_strategy, + first_name: signUpParams.first_name, + has_password: signUpParams.has_password, + last_name: signUpParams.last_name, + missing_fields: signUpParams.missing_fields, + object: 'sign_up', + optional_fields: signUpParams.optional_fields, + phone_number: signUpParams.phone_number, + required_fields: signUpParams.required_fields, + unsafe_metadata: signUpParams.unsafe_metadata, + unverified_fields: signUpParams.unverified_fields, + username: signUpParams.username, + verifications: signUpParams.verifications, + web3_wallet: signUpParams.web3_wallet, + } as SignUpJSON; +}; + +export const clerkMock = (params?: Partial) => { + return { + getFapiClient: vi.fn().mockReturnValue({ + request: vi.fn().mockReturnValue({ payload: { object: 'token', jwt: mockJwt }, status: 200 }), + }), + ...params, + }; +}; + +type RecursivePartial = { + [P in keyof T]?: RecursivePartial; +}; + +export const mockFetch = (ok = true, status = 200, responsePayload = {}) => { + // @ts-ignore + global.fetch = vi.fn(() => { + return Promise.resolve>({ + status, + statusText: status.toString(), + ok, + json: () => Promise.resolve(responsePayload), + }); + }); +}; + +export const mockNetworkFailedFetch = () => { + // @ts-ignore + global.fetch = vi.fn(() => { + return Promise.reject(new TypeError('Failed to fetch')); + }); +}; + +export const mockDevClerkInstance = { + frontendApi: 'clerk.example.com', + instanceType: 'development', + isSatellite: false, + version: 'test-0.0.0', + domain: '', +}; 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 81% rename from packages/clerk-js/src/ui/common/__tests__/redirects.test.ts rename to packages/clerk-js/src/ui/common/__tests__/redirects.spec.ts index 11c706e8c65..bf4061a08b0 100644 --- a/packages/clerk-js/src/ui/common/__tests__/redirects.test.ts +++ b/packages/clerk-js/src/ui/common/__tests__/redirects.spec.ts @@ -1,12 +1,13 @@ +import { describe, expect, it } from 'vitest'; + import { buildSSOCallbackURL, buildVerificationRedirectUrl, buildVerificationRedirectUrl } from '../redirects'; describe('buildVerificationRedirectUrl(routing, baseUrl)', () => { it('defaults to hash based routing strategy on empty routing', function () { expect( buildVerificationRedirectUrl({ ctx: { path: '', authQueryString: '' } as any, baseUrl: '', intent: 'sign-in' }), - ).toBe('http://localhost/#/verify'); + ).toBe('http://localhost:3000/#/verify'); }); - it('returns the magic link redirect url for components using path based routing ', function () { expect( buildVerificationRedirectUrl({ @@ -14,7 +15,7 @@ describe('buildVerificationRedirectUrl(routing, baseUrl)', () => { baseUrl: '', intent: 'sign-in', }), - ).toBe('http://localhost/verify'); + ).toBe('http://localhost:3000/verify'); expect( buildVerificationRedirectUrl({ @@ -22,7 +23,7 @@ describe('buildVerificationRedirectUrl(routing, baseUrl)', () => { baseUrl: '', intent: 'sign-in', }), - ).toBe('http://localhost/sign-in/verify'); + ).toBe('http://localhost:3000/sign-in/verify'); expect( buildVerificationRedirectUrl({ @@ -34,7 +35,7 @@ describe('buildVerificationRedirectUrl(routing, baseUrl)', () => { baseUrl: '', intent: 'sign-in', }), - ).toBe('http://localhost/verify?redirectUrl=https://clerk.com'); + ).toBe('http://localhost:3000/verify?redirectUrl=https://clerk.com'); expect( buildVerificationRedirectUrl({ @@ -46,7 +47,7 @@ describe('buildVerificationRedirectUrl(routing, baseUrl)', () => { baseUrl: '', intent: 'sign-in', }), - ).toBe('http://localhost/sign-in/verify?redirectUrl=https://clerk.com'); + ).toBe('http://localhost:3000/sign-in/verify?redirectUrl=https://clerk.com'); expect( buildVerificationRedirectUrl({ @@ -58,9 +59,8 @@ describe('buildVerificationRedirectUrl(routing, baseUrl)', () => { baseUrl: 'https://accounts.clerk.com/sign-in', intent: 'sign-in', }), - ).toBe('http://localhost/sign-in/verify?redirectUrl=https://clerk.com'); + ).toBe('http://localhost:3000/sign-in/verify?redirectUrl=https://clerk.com'); }); - it('returns the magic link redirect url for components using hash based routing ', function () { expect( buildVerificationRedirectUrl({ @@ -71,7 +71,7 @@ describe('buildVerificationRedirectUrl(routing, baseUrl)', () => { baseUrl: '', intent: 'sign-in', }), - ).toBe('http://localhost/#/verify'); + ).toBe('http://localhost:3000/#/verify'); expect( buildVerificationRedirectUrl({ @@ -83,7 +83,7 @@ describe('buildVerificationRedirectUrl(routing, baseUrl)', () => { baseUrl: '', intent: 'sign-in', }), - ).toBe('http://localhost/#/verify'); + ).toBe('http://localhost:3000/#/verify'); expect( buildVerificationRedirectUrl({ @@ -95,7 +95,7 @@ describe('buildVerificationRedirectUrl(routing, baseUrl)', () => { baseUrl: '', intent: 'sign-in', }), - ).toBe('http://localhost/#/verify?redirectUrl=https://clerk.com'); + ).toBe('http://localhost:3000/#/verify?redirectUrl=https://clerk.com'); expect( buildVerificationRedirectUrl({ @@ -107,7 +107,7 @@ describe('buildVerificationRedirectUrl(routing, baseUrl)', () => { baseUrl: '', intent: 'sign-in', }), - ).toBe('http://localhost/#/verify?redirectUrl=https://clerk.com'); + ).toBe('http://localhost:3000/#/verify?redirectUrl=https://clerk.com'); expect( buildVerificationRedirectUrl({ @@ -119,9 +119,8 @@ describe('buildVerificationRedirectUrl(routing, baseUrl)', () => { baseUrl: 'https://accounts.clerk.com/sign-in', intent: 'sign-in', }), - ).toBe('http://localhost/#/verify?redirectUrl=https://clerk.com'); + ).toBe('http://localhost:3000/#/verify?redirectUrl=https://clerk.com'); }); - it('returns the magic link redirect url for components using virtual routing ', function () { expect( buildVerificationRedirectUrl({ @@ -156,7 +155,7 @@ describe('buildVerificationRedirectUrl(routing, baseUrl)', () => { baseUrl: '', intent: 'sign-up', }), - ).toBe('http://localhost/sign-up/create/verify'); + ).toBe('http://localhost:3000/sign-up/create/verify'); expect( buildVerificationRedirectUrl({ @@ -168,28 +167,28 @@ describe('buildVerificationRedirectUrl(routing, baseUrl)', () => { baseUrl: '', intent: 'sign-in', }), - ).toBe('http://localhost/sign-in/verify'); + ).toBe('http://localhost:3000/sign-in/verify'); }); }); describe('buildSSOCallbackURL(ctx, baseUrl)', () => { it('returns the SSO callback URL based on sign in|up component routing or the provided base URL', () => { // Default callback URLS - expect(buildSSOCallbackURL({}, '')).toBe('http://localhost/#/sso-callback'); - expect(buildSSOCallbackURL({}, 'http://test.host')).toBe('http://localhost/#/sso-callback'); + expect(buildSSOCallbackURL({}, '')).toBe('http://localhost:3000/#/sso-callback'); + expect(buildSSOCallbackURL({}, 'http://test.host')).toBe('http://localhost:3000/#/sso-callback'); expect(buildSSOCallbackURL({ authQueryString: 'redirect_url=%2Ffoo' }, 'http://test.host')).toBe( - 'http://localhost/#/sso-callback?redirect_url=%2Ffoo', + 'http://localhost:3000/#/sso-callback?redirect_url=%2Ffoo', ); // Components mounted with hash routing - expect(buildSSOCallbackURL({ routing: 'hash' }, 'http://test.host')).toBe('http://localhost/#/sso-callback'); + expect(buildSSOCallbackURL({ routing: 'hash' }, 'http://test.host')).toBe('http://localhost:3000/#/sso-callback'); expect(buildSSOCallbackURL({ routing: 'hash', authQueryString: 'redirect_url=%2Ffoo' }, 'http://test.host')).toBe( - 'http://localhost/#/sso-callback?redirect_url=%2Ffoo', + 'http://localhost:3000/#/sso-callback?redirect_url=%2Ffoo', ); // Components mounted with path routing expect(buildSSOCallbackURL({ routing: 'path', path: 'sign-in' }, 'http://test.host')).toBe( - 'http://localhost/sign-in/sso-callback', + 'http://localhost:3000/sign-in/sso-callback', ); expect( buildSSOCallbackURL( @@ -200,7 +199,7 @@ describe('buildSSOCallbackURL(ctx, baseUrl)', () => { }, 'http://test.host', ), - ).toBe('http://localhost/sign-in/sso-callback?redirect_url=%2Ffoo'); + ).toBe('http://localhost:3000/sign-in/sso-callback?redirect_url=%2Ffoo'); // Components mounted with virtual routing expect(buildSSOCallbackURL({ routing: 'virtual' }, 'http://test.host')).toBe('http://test.host/#/sso-callback'); @@ -219,6 +218,6 @@ describe('buildSSOCallbackURL(ctx, baseUrl)', () => { ssoCallbackUrl: 'http://test.host/ctx-sso-callback', routing: 'virtual', }), - ).toBe('http://localhost/#/sso-callback'); + ).toBe('http://localhost:3000/#/sso-callback'); }); }); diff --git a/packages/clerk-js/src/ui/components/OrganizationSwitcher/__tests__/vitestUtils.ts b/packages/clerk-js/src/ui/components/OrganizationSwitcher/__tests__/vitestUtils.ts new file mode 100644 index 00000000000..b99be7ff39e --- /dev/null +++ b/packages/clerk-js/src/ui/components/OrganizationSwitcher/__tests__/vitestUtils.ts @@ -0,0 +1,145 @@ +import type { + OrganizationCustomRoleKey, + OrganizationInvitationStatus, + OrganizationMembershipResource, + OrganizationResource, + OrganizationSuggestionResource, + OrganizationSuggestionStatus, + UserOrganizationInvitationResource, +} from '@clerk/types'; +import { vi } from 'vitest'; + +export type FakeOrganizationParams = { + id: string; + name: string; + slug: string; + imageUrl?: string; + membersCount: number; + pendingInvitationsCount: number; + adminDeleteEnabled: boolean; + maxAllowedMemberships: number; + createdAt?: Date; +}; + +export const createFakeOrganization = (params: FakeOrganizationParams): OrganizationResource => { + return { + pathRoot: '', + id: params.id, + name: params.name, + slug: params.slug, + hasImage: !!params.imageUrl, + imageUrl: params.imageUrl || '', + membersCount: params.membersCount, + pendingInvitationsCount: params.pendingInvitationsCount, + publicMetadata: {}, + adminDeleteEnabled: params.adminDeleteEnabled, + 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, + }; +}; + +type FakeOrganizationInvitationParams = { + id: string; + createdAt?: Date; + emailAddress: string; + role?: OrganizationCustomRoleKey; + status?: OrganizationInvitationStatus; + publicOrganizationData?: { hasImage?: boolean; id?: string; imageUrl?: string; name?: string; slug?: string }; +}; + +export const createFakeUserOrganizationInvitation = ( + params: FakeOrganizationInvitationParams, +): UserOrganizationInvitationResource => { + return { + pathRoot: '', + emailAddress: params.emailAddress, + publicOrganizationData: { + hasImage: false, + id: '', + imageUrl: '', + name: '', + slug: '', + ...params.publicOrganizationData, + }, + role: params.role || 'basic_member', + status: params.status || 'pending', + id: params.id, + createdAt: params?.createdAt || new Date(), + updatedAt: new Date(), + publicMetadata: {}, + accept: vi.fn() as any, + reload: vi.fn() as any, + }; +}; + +type FakeUserOrganizationMembershipParams = { + id: string; + createdAt?: Date; + role?: OrganizationCustomRoleKey; + organization: FakeOrganizationParams; +}; + +export const createFakeUserOrganizationMembership = ( + params: FakeUserOrganizationMembershipParams, +): OrganizationMembershipResource => { + return { + organization: createFakeOrganization(params.organization), + pathRoot: '', + role: params.role || 'basic_member', + permissions: [], + id: params.id, + createdAt: params?.createdAt || new Date(), + updatedAt: new Date(), + publicMetadata: {}, + publicUserData: {} as any, + update: vi.fn() as any, + destroy: vi.fn() as any, + reload: vi.fn() as any, + }; +}; + +type FakeOrganizationSuggestionParams = { + id: string; + createdAt?: Date; + emailAddress: string; + role?: OrganizationCustomRoleKey; + status?: OrganizationSuggestionStatus; + publicOrganizationData?: { hasImage?: boolean; id?: string; imageUrl?: string; name?: string; slug?: string }; +}; + +export const createFakeUserOrganizationSuggestion = ( + params: FakeOrganizationSuggestionParams, +): OrganizationSuggestionResource => { + return { + pathRoot: '', + publicOrganizationData: { + hasImage: false, + id: '', + imageUrl: '', + name: '', + slug: '', + ...params.publicOrganizationData, + }, + status: params.status || 'pending', + id: params.id, + createdAt: params?.createdAt || new Date(), + updatedAt: new Date(), + accept: vi.fn() as any, + reload: vi.fn() as any, + }; +}; 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 97% 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 index 4cb14aedb62..2e4b61ad97b 100644 --- a/packages/clerk-js/src/ui/components/SignUp/__tests__/SignUpStart.test.tsx +++ b/packages/clerk-js/src/ui/components/SignUp/__tests__/SignUpStart.spec.tsx @@ -1,12 +1,13 @@ import { OAUTH_PROVIDERS } from '@clerk/shared/oauth'; import type { SignUpResource } from '@clerk/types'; +import { describe, expect, it, vi } from 'vitest'; import { CardStateProvider } from '@/ui/elements/contexts'; -import { render, screen, waitFor } from '../../../../testUtils'; +import { render, screen, waitFor } from '../../../../vitestUtils'; import { OptionsProvider } from '../../../contexts'; import { AppearanceProvider } from '../../../customizables'; -import { bindCreateFixtures } from '../../../utils/test/createFixtures'; +import { bindCreateFixtures } from '../../../utils/vitest/createFixtures'; import { SignUpStart } from '../SignUpStart'; const { createFixtures } = bindCreateFixtures('SignUp'); @@ -298,7 +299,7 @@ describe('SignUpStart', () => { }); Object.defineProperty(window, 'history', { writable: true, - value: { replaceState: jest.fn() }, + value: { replaceState: vi.fn() }, }); render( @@ -333,7 +334,7 @@ describe('SignUpStart', () => { }); Object.defineProperty(window, 'history', { writable: true, - value: { replaceState: jest.fn() }, + value: { replaceState: vi.fn() }, }); render( @@ -373,7 +374,7 @@ describe('SignUpStart', () => { }); Object.defineProperty(window, 'history', { writable: true, - value: { replaceState: jest.fn() }, + value: { replaceState: vi.fn() }, }); render( 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 96% rename from packages/clerk-js/src/ui/elements/__tests__/PlainInput.test.tsx rename to packages/clerk-js/src/ui/elements/__tests__/PlainInput.spec.tsx index ff7cd32b4b3..17bfbf135ae 100644 --- a/packages/clerk-js/src/ui/elements/__tests__/PlainInput.test.tsx +++ b/packages/clerk-js/src/ui/elements/__tests__/PlainInput.spec.tsx @@ -1,9 +1,9 @@ -import { describe, it } from '@jest/globals'; import { fireEvent, render } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import { describe, expect, it } from 'vitest'; import { useFormControl } from '../../utils'; -import { bindCreateFixtures } from '../../utils/test/createFixtures'; +import { bindCreateFixtures } from '../../utils/vitest/createFixtures'; import { withCardStateProvider } from '../contexts'; import { Form } from '../Form'; @@ -135,7 +135,7 @@ describe('PlainInput', () => { await userEvent.click(getByRole('button', { name: /set error/i })); - expect(await findByText(/some error/i)).toBeInTheDocument(); + expect(await findByText(/Some Error/i)).toBeInTheDocument(); const label = getByLabelText(/some label/i); expect(label).toHaveAttribute('aria-invalid', 'true'); 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 96% rename from packages/clerk-js/src/ui/elements/__tests__/RadioGroup.test.tsx rename to packages/clerk-js/src/ui/elements/__tests__/RadioGroup.spec.tsx index dcb8f2951d3..52fb2e9e39d 100644 --- a/packages/clerk-js/src/ui/elements/__tests__/RadioGroup.test.tsx +++ b/packages/clerk-js/src/ui/elements/__tests__/RadioGroup.spec.tsx @@ -1,9 +1,9 @@ -import { describe, it } from '@jest/globals'; import { fireEvent, render } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import { describe, expect, it } from 'vitest'; import { useFormControl } from '../../utils'; -import { bindCreateFixtures } from '../../utils/test/createFixtures'; +import { bindCreateFixtures } from '../../utils/vitest/createFixtures'; import { withCardStateProvider } from '../contexts'; import { Form } from '../Form'; @@ -167,7 +167,7 @@ describe('RadioGroup', () => { ); await userEvent.click(getByRole('button', { name: /set error/i })); - expect(await findByText(/some error/i)).toBeInTheDocument(); + expect(await findByText(/Some Error/i)).toBeInTheDocument(); const radios = getAllByRole('radio'); radios.forEach(radio => { 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 98% rename from packages/clerk-js/src/ui/hooks/__tests__/useCoreOrganizationList.test.tsx rename to packages/clerk-js/src/ui/hooks/__tests__/useCoreOrganizationList.spec.tsx index 1f7a4a4137a..632858ab8c6 100644 --- a/packages/clerk-js/src/ui/hooks/__tests__/useCoreOrganizationList.test.tsx +++ b/packages/clerk-js/src/ui/hooks/__tests__/useCoreOrganizationList.spec.tsx @@ -1,13 +1,13 @@ import { useOrganizationList } from '@clerk/shared/react'; -import { describe } from '@jest/globals'; +import { describe, expect, it } from 'vitest'; -import { act, renderHook, waitFor } from '../../../testUtils'; +import { act, renderHook, waitFor } from '../../../vitestUtils'; import { createFakeUserOrganizationInvitation, createFakeUserOrganizationMembership, createFakeUserOrganizationSuggestion, -} from '../../components/OrganizationSwitcher/__tests__/utlis'; -import { bindCreateFixtures } from '../../utils/test/createFixtures'; +} from '../../components/OrganizationSwitcher/__tests__/vitestUtils'; +import { bindCreateFixtures } from '../../utils/vitest/createFixtures'; const { createFixtures } = bindCreateFixtures('OrganizationSwitcher'); 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 93% rename from packages/clerk-js/src/ui/hooks/__tests__/useDevMode.test.tsx rename to packages/clerk-js/src/ui/hooks/__tests__/useDevMode.spec.tsx index 59db4893c46..cf932e78391 100644 --- a/packages/clerk-js/src/ui/hooks/__tests__/useDevMode.test.tsx +++ b/packages/clerk-js/src/ui/hooks/__tests__/useDevMode.spec.tsx @@ -1,20 +1,21 @@ import type { EnvironmentResource } from '@clerk/types'; import { renderHook } from '@testing-library/react'; +import { describe, expect, test, vi } from 'vitest'; import { useDevMode } from '../useDevMode'; -const mockUseEnvironment = jest.fn(); -const mockUseOptions = jest.fn(); -const mockUseAppearance = jest.fn(); +const mockUseEnvironment = vi.fn(); +const mockUseOptions = vi.fn(); +const mockUseAppearance = vi.fn(); -jest.mock('../../contexts', () => { +vi.mock('../../contexts', () => { return { useEnvironment: () => mockUseEnvironment(), useOptions: () => mockUseOptions(), }; }); -jest.mock('../../customizables', () => { +vi.mock('../../customizables', () => { return { useAppearance: () => mockUseAppearance(), }; 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 88% rename from packages/clerk-js/src/ui/hooks/__tests__/useSupportEmail.test.tsx rename to packages/clerk-js/src/ui/hooks/__tests__/useSupportEmail.spec.tsx index 7f31c0dc218..8b342df0090 100644 --- a/packages/clerk-js/src/ui/hooks/__tests__/useSupportEmail.test.tsx +++ b/packages/clerk-js/src/ui/hooks/__tests__/useSupportEmail.spec.tsx @@ -1,16 +1,17 @@ import { renderHook } from '@testing-library/react'; +import { describe, expect, test, vi } from 'vitest'; import { useSupportEmail } from '../useSupportEmail'; -const mockUseOptions = jest.fn(); -const mockUseEnvironment = jest.fn(); +const mockUseOptions = vi.fn(); +const mockUseEnvironment = vi.fn(); -jest.mock('@clerk/shared/react', () => ({ +vi.mock('@clerk/shared/react', () => ({ useClerk: () => ({ publishableKey: 'pk_live_Y2xlcmsuY2xlcmsuY29tJA', }), })); -jest.mock('../../contexts', () => { +vi.mock('../../contexts', () => { return { useEnvironment: () => mockUseEnvironment(), useOptions: () => mockUseOptions(), 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 97% rename from packages/clerk-js/src/ui/localization/__tests__/applyTokensToString.test.ts rename to packages/clerk-js/src/ui/localization/__tests__/applyTokensToString.spec.ts index ed9ff255d7e..49ec57a07c8 100644 --- a/packages/clerk-js/src/ui/localization/__tests__/applyTokensToString.test.ts +++ b/packages/clerk-js/src/ui/localization/__tests__/applyTokensToString.spec.ts @@ -1,3 +1,5 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; + import { applyTokensToString } from '../applyTokensToString'; describe('applyTokensToString', function () { @@ -34,7 +36,7 @@ describe('applyTokensToString', function () { describe('Date related tokens and modifiers', () => { beforeEach(() => { - jest.spyOn(console, 'warn').mockImplementation(() => {}); + vi.spyOn(console, 'warn').mockImplementation(() => {}); }); const tokens = { diff --git a/packages/clerk-js/src/ui/router/__mocks__/RouteContext.tsx b/packages/clerk-js/src/ui/router/__mocks__/RouteContext.tsx index 7693d51fe48..35d50486d09 100644 --- a/packages/clerk-js/src/ui/router/__mocks__/RouteContext.tsx +++ b/packages/clerk-js/src/ui/router/__mocks__/RouteContext.tsx @@ -1,11 +1,12 @@ import { noop } from '@clerk/shared/utils'; +import { vi } from 'vitest'; export const useRouter = () => ({ - resolve: jest.fn(() => ({ + resolve: vi.fn(() => ({ toURL: { href: 'http://test.host/test-href', }, })), - matches: jest.fn(noop), - navigate: jest.fn(noop), + matches: vi.fn(noop), + navigate: vi.fn(noop), }); 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 94% rename from packages/clerk-js/src/ui/router/__tests__/PathRouter.test.tsx rename to packages/clerk-js/src/ui/router/__tests__/PathRouter.spec.tsx index 52a2dff21a8..90ab5eae5d6 100644 --- a/packages/clerk-js/src/ui/router/__tests__/PathRouter.test.tsx +++ b/packages/clerk-js/src/ui/router/__tests__/PathRouter.spec.tsx @@ -1,13 +1,14 @@ import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React from 'react'; +import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; import type { Clerk } from '../../../core/clerk'; import { PathRouter, Route, useRouter } from '..'; -const mockNavigate = jest.fn(); +const mockNavigate = vi.fn(); -jest.mock('@clerk/shared/react', () => { +vi.mock('@clerk/shared/react', () => { return { useClerk: () => { return { 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 94% rename from packages/clerk-js/src/ui/router/__tests__/Switch.test.tsx rename to packages/clerk-js/src/ui/router/__tests__/Switch.spec.tsx index c27bd019140..1ebcdd0a7e3 100644 --- a/packages/clerk-js/src/ui/router/__tests__/Switch.test.tsx +++ b/packages/clerk-js/src/ui/router/__tests__/Switch.spec.tsx @@ -1,13 +1,14 @@ import { render, screen } from '@testing-library/react'; import React from 'react'; +import { afterAll, afterEach, describe, expect, it, vi } from 'vitest'; import { HashRouter, Route, Switch } from '../../router'; -const mockNavigate = jest.fn(); +const mockNavigate = vi.fn(); -jest.mock('@clerk/shared/react', () => ({ +vi.mock('@clerk/shared/react', () => ({ useClerk: () => ({ - navigate: jest.fn(to => { + navigate: vi.fn(to => { mockNavigate(to); if (to) { // @ts-ignore @@ -29,7 +30,7 @@ const setWindowOrigin = (origin: string) => { describe('', () => { afterEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); afterAll(() => { 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 93% rename from packages/clerk-js/src/ui/router/__tests__/VirtualRouter.test.tsx rename to packages/clerk-js/src/ui/router/__tests__/VirtualRouter.spec.tsx index 18b7eef065f..a9ecc9649e2 100644 --- a/packages/clerk-js/src/ui/router/__tests__/VirtualRouter.test.tsx +++ b/packages/clerk-js/src/ui/router/__tests__/VirtualRouter.spec.tsx @@ -1,14 +1,15 @@ import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React from 'react'; +import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; import { Route, useRouter, VirtualRouter } from '..'; -const mockNavigate = jest.fn(); +const mockNavigate = vi.fn(); -jest.mock('@clerk/shared/react', () => ({ +vi.mock('@clerk/shared/react', () => ({ useClerk: () => ({ - navigate: jest.fn(to => { + navigate: vi.fn(to => { mockNavigate(to); if (to) { // @ts-ignore diff --git a/packages/clerk-js/src/ui/utils/__tests__/normalizeColorString.test.ts b/packages/clerk-js/src/ui/utils/__tests__/normalizeColorString.spec.ts similarity index 91% rename from packages/clerk-js/src/ui/utils/__tests__/normalizeColorString.test.ts rename to packages/clerk-js/src/ui/utils/__tests__/normalizeColorString.spec.ts index 40ec028c83e..cc2ab505544 100644 --- a/packages/clerk-js/src/ui/utils/__tests__/normalizeColorString.test.ts +++ b/packages/clerk-js/src/ui/utils/__tests__/normalizeColorString.spec.ts @@ -1,12 +1,14 @@ +import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; + import { normalizeColorString } from '../normalizeColorString'; describe('normalizeColorString', () => { beforeEach(() => { - jest.spyOn(console, 'warn').mockImplementation(() => {}) as jest.Mock; + vi.spyOn(console, 'warn').mockImplementation(() => {}) as vi.Mock; }); afterEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); // Hex color tests @@ -34,7 +36,7 @@ describe('normalizeColorString', () => { expect(normalizeColorString('#12')).toBe('#12'); expect(console.warn).toHaveBeenCalledTimes(1); - (console.warn as jest.Mock).mockClear(); + (console.warn as vi.Mock).mockClear(); expect(normalizeColorString('#12345')).toBe('#12345'); expect(console.warn).toHaveBeenCalledTimes(1); }); @@ -78,11 +80,11 @@ describe('normalizeColorString', () => { expect(normalizeColorString('')).toBe(''); expect(console.warn).toHaveBeenCalledTimes(1); - (console.warn as jest.Mock).mockClear(); + (console.warn as vi.Mock).mockClear(); expect(normalizeColorString('invalid')).toBe('invalid'); expect(console.warn).toHaveBeenCalledTimes(1); - (console.warn as jest.Mock).mockClear(); + (console.warn as vi.Mock).mockClear(); expect(normalizeColorString('rgb(255,0)')).toBe('rgb(255,0)'); expect(console.warn).toHaveBeenCalledTimes(1); }); @@ -91,7 +93,7 @@ describe('normalizeColorString', () => { expect(normalizeColorString(null as any)).toBe(''); expect(console.warn).toHaveBeenCalledTimes(1); - (console.warn as jest.Mock).mockClear(); + (console.warn as vi.Mock).mockClear(); expect(normalizeColorString(123 as any)).toBe(123 as any); expect(console.warn).toHaveBeenCalledTimes(1); }); diff --git a/packages/clerk-js/src/ui/utils/vitest/createFixtures.tsx b/packages/clerk-js/src/ui/utils/vitest/createFixtures.tsx new file mode 100644 index 00000000000..897f65ca603 --- /dev/null +++ b/packages/clerk-js/src/ui/utils/vitest/createFixtures.tsx @@ -0,0 +1,131 @@ +// 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'; + +import { Clerk as ClerkCtor } from '../../../core/clerk'; +import { Client, Environment } from '../../../core/resources'; +import { + ComponentContextProvider, + CoreClerkContextWrapper, + EnvironmentProvider, + OptionsProvider, +} from '../../contexts'; +import { AppearanceProvider } from '../../customizables'; +import { RouteContext } from '../../router'; +import { InternalThemeProvider } from '../../styledSystem'; +import type { AvailableComponentName, AvailableComponentProps } from '../../types'; +import { createClientFixtureHelpers, createEnvironmentFixtureHelpers } from './fixtureHelpers'; +import { createBaseClientJSON, createBaseEnvironmentJSON } from './fixtures'; +import { mockClerkMethods, mockRouteContextValue } from './mockHelpers'; + +const createInitialStateConfigParam = (baseEnvironment: EnvironmentJSON, baseClient: ClientJSON) => { + return { + ...createEnvironmentFixtureHelpers(baseEnvironment), + ...createClientFixtureHelpers(baseClient), + }; +}; + +type FParam = ReturnType; +type ConfigFn = (f: FParam) => void; + +export const bindCreateFixtures = ( + componentName: Parameters[0], + mockOpts?: { + router?: Parameters[0]; + }, +) => { + return { createFixtures: unboundCreateFixtures(componentName, mockOpts) }; +}; + +const unboundCreateFixtures = ( + componentName: AvailableComponentName, + mockOpts?: { + router?: Parameters[0]; + }, +) => { + const createFixtures = async (...configFns: ConfigFn[]) => { + const baseEnvironment = createBaseEnvironmentJSON(); + const baseClient = createBaseClientJSON(); + configFns = configFns.filter(Boolean); + + if (configFns.length) { + const fParam = createInitialStateConfigParam(baseEnvironment, baseClient); + configFns.forEach(configFn => configFn(fParam)); + } + + const environmentMock = new Environment(baseEnvironment); + Environment.getInstance().fetch = vi.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)); + + // Use a FAPI value for local production instances to avoid triggering the devInit flow during testing + const productionPublishableKey = 'pk_live_Y2xlcmsuYWJjZWYuMTIzNDUucHJvZC5sY2xjbGVyay5jb20k'; + const tempClerk = new ClerkCtor(productionPublishableKey); + await tempClerk.load(); + const clerkMock = mockClerkMethods(tempClerk as LoadedClerk); + const optionsMock = {} as ClerkOptions; + const routerMock = mockRouteContextValue(mockOpts?.router || {}); + + const fixtures = { + clerk: clerkMock, + session: clerkMock.session, + signIn: clerkMock.client.signIn, + signUp: clerkMock.client.signUp, + environment: environmentMock, + router: routerMock, + options: optionsMock, + }; + + let componentContextProps: AvailableComponentProps; + const props = { + setProps: (props: typeof componentContextProps) => { + componentContextProps = props; + }, + }; + + const MockClerkProvider = (props: any) => { + const { children } = props; + + const componentsWithoutContext = ['UsernameSection', 'UserProfileSection']; + const contextWrappedChildren = !componentsWithoutContext.includes(componentName) ? ( + + {children} + + ) : ( + <>{children} + ); + + return ( + new Map() }} + > + + + + + + {contextWrappedChildren} + + + + + + + ); + }; + + return { wrapper: MockClerkProvider, fixtures, props }; + }; + createFixtures.config = (fn: ConfigFn) => fn; + return createFixtures; +}; diff --git a/packages/clerk-js/src/ui/utils/vitest/fixtureHelpers.ts b/packages/clerk-js/src/ui/utils/vitest/fixtureHelpers.ts new file mode 100644 index 00000000000..3ad508cdf05 --- /dev/null +++ b/packages/clerk-js/src/ui/utils/vitest/fixtureHelpers.ts @@ -0,0 +1,566 @@ +import type { + ClientJSON, + DisplayConfigJSON, + EmailAddressJSON, + EnvironmentJSON, + ExternalAccountJSON, + OAuthProvider, + OrganizationEnrollmentMode, + PhoneNumberJSON, + PublicUserDataJSON, + SamlAccountJSON, + SessionJSON, + SignInJSON, + SignUpJSON, + UserJSON, + UserSettingsJSON, + VerificationJSON, +} from '@clerk/types'; + +import { SIGN_UP_MODES } from '../../../core/constants'; +import type { OrgParams } from '../../../core/vitest/fixtures'; +import { createUser, getOrganizationId } from '../../../core/vitest/fixtures'; +import { createUserFixture } from './fixtures'; + +export const createEnvironmentFixtureHelpers = (baseEnvironment: EnvironmentJSON) => { + return { + ...createAuthConfigFixtureHelpers(baseEnvironment), + ...createDisplayConfigFixtureHelpers(baseEnvironment), + ...createOrganizationSettingsFixtureHelpers(baseEnvironment), + ...createUserSettingsFixtureHelpers(baseEnvironment), + }; +}; + +export const createClientFixtureHelpers = (baseClient: ClientJSON) => { + return { + ...createSignInFixtureHelpers(baseClient), + ...createSignUpFixtureHelpers(baseClient), + ...createUserFixtureHelpers(baseClient), + }; +}; + +const createUserFixtureHelpers = (baseClient: ClientJSON) => { + type WithUserParams = Omit< + Partial, + 'email_addresses' | 'phone_numbers' | 'external_accounts' | 'saml_accounts' | 'organization_memberships' + > & { + email_addresses?: Array>; + phone_numbers?: Array>; + external_accounts?: Array>; + saml_accounts?: Array>; + organization_memberships?: Array; + }; + + const createPublicUserData = (params: WithUserParams) => { + return { + first_name: 'FirstName', + last_name: 'LastName', + image_url: '', + identifier: 'email@test.com', + user_id: '', + ...params, + } as PublicUserDataJSON; + }; + + const withUser = (params: WithUserParams) => { + baseClient.sessions = baseClient.sessions || []; + + // set the first organization as active + let activeOrganization: string | null = null; + if (params?.organization_memberships?.length) { + activeOrganization = + typeof params.organization_memberships[0] === 'string' + ? params.organization_memberships[0] + : getOrganizationId(params.organization_memberships[0]); + } + + const session = { + status: 'active', + id: baseClient.sessions.length.toString(), + object: 'session', + last_active_organization_id: activeOrganization, + actor: null, + user: createUser(params), + public_user_data: createPublicUserData(params), + created_at: new Date().getTime(), + updated_at: new Date().getTime(), + last_active_token: { + jwt: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NzU4NzY3OTAsImRhdGEiOiJmb29iYXIiLCJpYXQiOjE2NzU4NzY3MzB9.Z1BC47lImYvaAtluJlY-kBo0qOoAk42Xb-gNrB2SxJg', + }, + } as SessionJSON; + baseClient.sessions.push(session); + }; + + return { withUser }; +}; + +const createSignInFixtureHelpers = (baseClient: ClientJSON) => { + type SignInWithEmailAddressParams = { + identifier?: string; + supportPassword?: boolean; + supportEmailCode?: boolean; + supportEmailLink?: boolean; + supportResetPassword?: boolean; + supportPasskey?: boolean; + }; + + type SignInWithPhoneNumberParams = { + identifier?: string; + supportPassword?: boolean; + supportPhoneCode?: boolean; + supportResetPassword?: boolean; + }; + + type SignInFactorTwoParams = { + identifier?: string; + supportPhoneCode?: boolean; + supportTotp?: boolean; + supportBackupCode?: boolean; + supportResetPasswordEmail?: boolean; + supportResetPasswordPhone?: boolean; + }; + + const startSignInWithEmailAddress = (params?: SignInWithEmailAddressParams) => { + const { + identifier = 'hello@clerk.com', + supportPassword = true, + supportEmailCode, + supportEmailLink, + supportResetPassword, + supportPasskey, + } = params || {}; + baseClient.sign_in = { + status: 'needs_first_factor', + identifier, + supported_identifiers: ['email_address'], + supported_first_factors: [ + ...(supportPasskey ? [{ strategy: 'passkey' }] : []), + ...(supportPassword ? [{ strategy: 'password' }] : []), + ...(supportEmailCode ? [{ strategy: 'email_code', safe_identifier: identifier || 'n*****@clerk.com' }] : []), + ...(supportEmailLink ? [{ strategy: 'email_link', safe_identifier: identifier || 'n*****@clerk.com' }] : []), + ...(supportResetPassword + ? [ + { + strategy: 'reset_password_email_code', + safe_identifier: identifier || 'n*****@clerk.com', + emailAddressId: 'someEmailId', + }, + ] + : []), + ], + user_data: { ...(createUserFixture() as any) }, + } as SignInJSON; + }; + + const startSignInWithPhoneNumber = (params?: SignInWithPhoneNumberParams) => { + const { + identifier = '+301234567890', + supportPassword = true, + supportPhoneCode, + supportResetPassword, + } = params || {}; + baseClient.sign_in = { + status: 'needs_first_factor', + identifier, + supported_identifiers: ['phone_number'], + supported_first_factors: [ + ...(supportPassword ? [{ strategy: 'password' }] : []), + ...(supportPhoneCode ? [{ strategy: 'phone_code', safe_identifier: '+30********90' }] : []), + ...(supportResetPassword + ? [ + { + strategy: 'reset_password_phone_code', + safe_identifier: identifier || '+30********90', + phoneNumberId: 'someNumberId', + }, + ] + : []), + ], + user_data: { ...(createUserFixture() as any) }, + } as SignInJSON; + }; + + const startSignInFactorTwo = (params?: SignInFactorTwoParams) => { + const { + identifier = '+30 691 1111111', + supportPhoneCode = true, + supportTotp, + supportBackupCode, + supportResetPasswordEmail, + supportResetPasswordPhone, + } = params || {}; + baseClient.sign_in = { + status: 'needs_second_factor', + identifier, + ...(supportResetPasswordEmail + ? { + first_factor_verification: { + status: 'verified', + strategy: 'reset_password_email_code', + }, + } + : {}), + ...(supportResetPasswordPhone + ? { + first_factor_verification: { + status: 'verified', + strategy: 'reset_password_phone_code', + }, + } + : {}), + supported_identifiers: ['email_address', 'phone_number'], + supported_second_factors: [ + ...(supportPhoneCode ? [{ strategy: 'phone_code', safe_identifier: identifier || 'n*****@clerk.com' }] : []), + ...(supportTotp ? [{ strategy: 'totp', safe_identifier: identifier || 'n*****@clerk.com' }] : []), + ...(supportBackupCode ? [{ strategy: 'backup_code', safe_identifier: identifier || 'n*****@clerk.com' }] : []), + ], + user_data: { ...(createUserFixture() as any) }, + } as SignInJSON; + }; + + return { startSignInWithEmailAddress, startSignInWithPhoneNumber, startSignInFactorTwo }; +}; + +const createSignUpFixtureHelpers = (baseClient: ClientJSON) => { + type SignUpWithEmailAddressParams = { + emailAddress?: string; + supportEmailCode?: boolean; + supportEmailLink?: boolean; + emailVerificationStatus?: VerificationJSON['status']; + }; + + type SignUpWithPhoneNumberParams = { + phoneNumber?: string; + }; + + const startSignUpWithEmailAddress = (params?: SignUpWithEmailAddressParams) => { + const { + emailAddress = 'hello@clerk.com', + supportEmailLink = true, + supportEmailCode = true, + emailVerificationStatus = 'unverified', + } = params || {}; + baseClient.sign_up = { + id: 'sua_2HseAXFGN12eqlwARPMxyyUa9o9', + status: 'missing_requirements', + email_address: emailAddress, + verifications: (supportEmailLink || supportEmailCode) && { + email_address: { + strategy: (supportEmailLink && 'email_link') || (supportEmailCode && 'email_code'), + status: emailVerificationStatus, + }, + }, + missing_fields: [], + unverified_fields: emailVerificationStatus === 'unverified' ? ['email_address'] : [], + } as SignUpJSON; + }; + + const startSignUpWithPhoneNumber = (params?: SignUpWithPhoneNumberParams) => { + const { phoneNumber = '+301234567890' } = params || {}; + baseClient.sign_up = { + id: 'sua_2HseAXFGN12eqlwARPMxyyUa9o9', + status: 'missing_requirements', + phone_number: phoneNumber, + } as SignUpJSON; + }; + + const startSignUpWithMissingLegalAccepted = () => { + baseClient.sign_up = { + id: 'sua_2HseAXFGN12eqlwARPMxyyUa9o9', + status: 'missing_requirements', + legal_accepted_at: null, + missing_fields: ['legal_accepted'], + } as SignUpJSON; + }; + + const startSignUpWithMissingLegalAcceptedAndUnverifiedFields = (emailAddress = 'hello@clerk.com') => { + baseClient.sign_up = { + id: 'sua_2HseAXFGN12eqlwARPMxyyUa9o9', + status: 'missing_requirements', + legal_accepted_at: null, + missing_fields: ['legal_accepted'], + email_address: emailAddress, + unverified_fields: ['email_address'], + } as SignUpJSON; + }; + + return { + startSignUpWithEmailAddress, + startSignUpWithPhoneNumber, + startSignUpWithMissingLegalAccepted, + startSignUpWithMissingLegalAcceptedAndUnverifiedFields, + }; +}; + +const createAuthConfigFixtureHelpers = (environment: EnvironmentJSON) => { + const ac = environment.auth_config; + const withMultiSessionMode = () => { + // TODO: + ac.single_session_mode = false; + }; + const withReverification = () => { + ac.reverification = true; + }; + return { withMultiSessionMode, withReverification }; +}; + +const createDisplayConfigFixtureHelpers = (environment: EnvironmentJSON) => { + const dc = environment.display_config; + const withSupportEmail = (opts?: { email: string }) => { + dc.support_email = opts?.email || 'support@clerk.com'; + }; + const withoutClerkBranding = () => { + dc.branded = false; + }; + const withPreferredSignInStrategy = (opts: { strategy: DisplayConfigJSON['preferred_sign_in_strategy'] }) => { + dc.preferred_sign_in_strategy = opts.strategy; + }; + + const withTermsPrivacyPolicyUrls = (opts: { + termsOfService?: DisplayConfigJSON['terms_url']; + privacyPolicy?: DisplayConfigJSON['privacy_policy_url']; + }) => { + dc.terms_url = opts.termsOfService || ''; + dc.privacy_policy_url = opts.privacyPolicy || ''; + }; + return { withSupportEmail, withoutClerkBranding, withPreferredSignInStrategy, withTermsPrivacyPolicyUrls }; +}; + +const createOrganizationSettingsFixtureHelpers = (environment: EnvironmentJSON) => { + const os = environment.organization_settings; + const withOrganizations = () => { + os.enabled = true; + }; + const withMaxAllowedMemberships = ({ max = 5 }) => { + os.max_allowed_memberships = max; + }; + const withForceOrganizationSelection = () => { + os.force_organization_selection = true; + }; + + const withOrganizationDomains = (modes?: OrganizationEnrollmentMode[], defaultRole?: string) => { + os.domains.enabled = true; + os.domains.enrollment_modes = modes || ['automatic_invitation', 'automatic_invitation', 'manual_invitation']; + os.domains.default_role = defaultRole ?? null; + }; + return { withOrganizations, withMaxAllowedMemberships, withOrganizationDomains, withForceOrganizationSelection }; +}; + +const createUserSettingsFixtureHelpers = (environment: EnvironmentJSON) => { + const us = environment.user_settings; + us.password_settings = { + allowed_special_characters: '', + disable_hibp: false, + min_length: 8, + max_length: 999, + require_special_char: false, + require_numbers: false, + require_uppercase: false, + require_lowercase: false, + show_zxcvbn: false, + min_zxcvbn_strength: 0, + }; + us.sign_up = { + ...us.sign_up, + mode: SIGN_UP_MODES.PUBLIC, + }; + + us.username_settings = { + min_length: 4, + max_length: 40, + }; + + const emptyAttribute = { + first_factors: [], + second_factors: [], + verifications: [], + used_for_first_factor: false, + used_for_second_factor: false, + verify_at_sign_up: false, + }; + + const withPasswordComplexity = (opts?: Partial) => { + us.password_settings = { + ...us.password_settings, + ...opts, + }; + }; + + const withEmailAddress = (opts?: Partial) => { + us.attributes.email_address = { + ...emptyAttribute, + enabled: true, + required: false, + used_for_first_factor: true, + first_factors: ['email_code'], + used_for_second_factor: false, + second_factors: [], + verifications: ['email_code'], + verify_at_sign_up: true, + ...opts, + }; + }; + + const withEmailLink = () => { + withEmailAddress({ first_factors: ['email_link'], verifications: ['email_link'] }); + }; + + const withPhoneNumber = (opts?: Partial) => { + us.attributes.phone_number = { + ...emptyAttribute, + enabled: true, + required: false, + used_for_first_factor: true, + first_factors: ['phone_code'], + used_for_second_factor: false, + second_factors: [], + verifications: ['phone_code'], + verify_at_sign_up: true, + ...opts, + }; + }; + + const withPasskey = (opts?: Partial) => { + us.attributes.passkey = { + ...emptyAttribute, + enabled: true, + required: false, + used_for_first_factor: true, + first_factors: ['passkey'], + used_for_second_factor: false, + second_factors: [], + verifications: ['passkey'], + verify_at_sign_up: false, + ...opts, + }; + }; + + const withPasskeySettings = (opts?: Partial) => { + us.passkey_settings = { + ...us.passkey_settings, + ...opts, + }; + }; + + const withUsername = (opts?: Partial) => { + us.attributes.username = { + ...emptyAttribute, + enabled: true, + required: false, + used_for_first_factor: true, + ...opts, + }; + }; + + const withWeb3Wallet = (opts?: Partial) => { + us.attributes.web3_wallet = { + ...emptyAttribute, + enabled: true, + required: false, + used_for_first_factor: true, + first_factors: ['web3_metamask_signature'], + verifications: ['web3_metamask_signature'], + ...opts, + }; + }; + + const withName = (opts?: Partial) => { + const attr = { + ...emptyAttribute, + enabled: true, + required: false, + ...opts, + }; + us.attributes.first_name = attr; + us.attributes.last_name = attr; + }; + + const withPassword = (opts?: Partial) => { + us.attributes.password = { + ...emptyAttribute, + enabled: true, + required: false, + ...opts, + }; + }; + + const withSocialProvider = (opts: { provider: OAuthProvider; authenticatable?: boolean }) => { + const { authenticatable = true, provider } = opts || {}; + const strategy = 'oauth_' + provider; + // @ts-expect-error + us.social[strategy] = { + enabled: true, + authenticatable, + strategy: strategy, + }; + }; + + const withEnterpriseSso = () => { + us.saml = { enabled: true }; + us.enterprise_sso = { enabled: true }; + }; + + const withBackupCode = (opts?: Partial) => { + us.attributes.backup_code = { + ...emptyAttribute, + enabled: true, + required: false, + used_for_first_factor: false, + first_factors: [], + used_for_second_factor: true, + second_factors: ['backup_code'], + verifications: [], + verify_at_sign_up: false, + ...opts, + }; + }; + + const withAuthenticatorApp = (opts?: Partial) => { + us.attributes.authenticator_app = { + ...emptyAttribute, + enabled: false, + required: false, + used_for_first_factor: false, + first_factors: [], + used_for_second_factor: true, + second_factors: ['totp'], + verifications: [], + verify_at_sign_up: false, + ...opts, + }; + }; + + const withRestrictedMode = () => { + us.sign_up.mode = SIGN_UP_MODES.RESTRICTED; + }; + + const withLegalConsent = () => { + us.sign_up.legal_consent_enabled = true; + }; + + const withWaitlistMode = () => { + us.sign_up.mode = SIGN_UP_MODES.WAITLIST; + }; + + // TODO: Add the rest, consult pkg/generate/auth_config.go + + return { + withEmailAddress, + withEmailLink, + withPhoneNumber, + withUsername, + withWeb3Wallet, + withName, + withPassword, + withPasswordComplexity, + withSocialProvider, + withEnterpriseSso, + withBackupCode, + withAuthenticatorApp, + withPasskey, + withPasskeySettings, + withRestrictedMode, + withLegalConsent, + withWaitlistMode, + }; +}; diff --git a/packages/clerk-js/src/ui/utils/vitest/fixtures.ts b/packages/clerk-js/src/ui/utils/vitest/fixtures.ts new file mode 100644 index 00000000000..9eb97488214 --- /dev/null +++ b/packages/clerk-js/src/ui/utils/vitest/fixtures.ts @@ -0,0 +1,220 @@ +import type { + AuthConfigJSON, + ClientJSON, + DisplayConfigJSON, + EnvironmentJSON, + OrganizationSettingsJSON, + UserJSON, + UserSettingsJSON, +} from '@clerk/types'; + +import { containsAllOfType } from '../containsAllOf'; + +export const createBaseEnvironmentJSON = (): EnvironmentJSON => { + return { + id: 'env_1', + object: 'environment', + auth_config: createBaseAuthConfig(), + display_config: createBaseDisplayConfig(), + organization_settings: createBaseOrganizationSettings(), + user_settings: createBaseUserSettings(), + meta: { responseHeaders: { country: 'us' } }, + }; +}; + +const createBaseAuthConfig = (): AuthConfigJSON => { + return { + object: 'auth_config', + id: 'aac_1', + single_session_mode: true, + }; +}; + +const createBaseDisplayConfig = (): DisplayConfigJSON => { + return { + object: 'display_config', + id: 'display_config_1', + instance_environment_type: 'production', + application_name: 'TestApp', + theme: { + buttons: { + font_color: '#ffffff', + font_family: '"Inter", sans-serif', + font_weight: '600', + }, + general: { + color: '#6c47ff', + padding: '1em', + box_shadow: '0 2px 8px rgba(0, 0, 0, 0.2)', + font_color: '#151515', + font_family: '"Inter", sans-serif', + border_radius: '0.5em', + background_color: '#ffffff', + label_font_weight: '600', + }, + accounts: { + background_color: '#f2f2f2', + }, + }, + preferred_sign_in_strategy: 'password', + logo_image_url: 'https://images.clerk.com/uploaded/img_logo.png', + favicon_image_url: 'https://images.clerk.com/uploaded/img_favicon.png', + home_url: 'https://dashboard.clerk.com', + sign_in_url: 'https://dashboard.clerk.com/sign-in', + sign_up_url: 'https://dashboard.clerk.com/sign-up', + user_profile_url: 'https://accounts.clerk.com/user', + after_sign_in_url: 'https://dashboard.clerk.com', + after_sign_up_url: 'https://dashboard.clerk.com', + after_sign_out_one_url: 'https://accounts.clerk.com/sign-in/choose', + after_sign_out_all_url: 'https://dashboard.clerk.com/sign-in', + after_switch_session_url: 'https://dashboard.clerk.com', + organization_profile_url: 'https://accounts.clerk.com/organization', + create_organization_url: 'https://accounts.clerk.com/create-organization', + after_leave_organization_url: 'https://dashboard.clerk.com', + after_create_organization_url: 'https://dashboard.clerk.com', + support_email: '', + branded: true, + clerk_js_version: '4', + }; +}; + +const createBaseOrganizationSettings = (): OrganizationSettingsJSON => { + return { + enabled: false, + max_allowed_memberships: 5, + force_organization_selection: false, + domains: { + enabled: false, + enrollment_modes: [], + }, + } as unknown as OrganizationSettingsJSON; +}; + +const attributes = Object.freeze( + containsAllOfType()([ + 'email_address', + 'phone_number', + 'username', + 'web3_wallet', + 'first_name', + 'last_name', + 'password', + 'authenticator_app', + 'backup_code', + 'passkey', + ]), +); + +const socials = Object.freeze( + containsAllOfType()([ + 'oauth_facebook', + 'oauth_google', + 'oauth_hubspot', + 'oauth_github', + 'oauth_tiktok', + 'oauth_gitlab', + 'oauth_discord', + 'oauth_twitter', + 'oauth_twitch', + 'oauth_linkedin', + 'oauth_linkedin_oidc', + 'oauth_dropbox', + 'oauth_atlassian', + 'oauth_bitbucket', + 'oauth_microsoft', + 'oauth_notion', + 'oauth_apple', + 'oauth_line', + 'oauth_instagram', + 'oauth_coinbase', + 'oauth_spotify', + 'oauth_xero', + 'oauth_box', + 'oauth_slack', + 'oauth_linear', + 'oauth_x', + ]), +); + +const createBaseUserSettings = (): UserSettingsJSON => { + const attributeConfig = Object.fromEntries( + attributes.map(attribute => [ + attribute, + { + enabled: false, + required: false, + used_for_first_factor: false, + first_factors: [], + used_for_second_factor: false, + second_factors: [], + verifications: [], + verify_at_sign_up: false, + }, + ]), + ) as any as UserSettingsJSON['attributes']; + + const socialConfig = Object.fromEntries( + socials.map(social => [social, { enabled: false, required: false, authenticatable: false, strategy: social }]), + ) as any as UserSettingsJSON['social']; + + const passwordSettingsConfig = { + allowed_special_characters: '', + max_length: 0, + min_length: 8, + require_special_char: false, + require_numbers: false, + require_lowercase: false, + require_uppercase: false, + disable_hibp: true, + show_zxcvbn: false, + min_zxcvbn_strength: 0, + } as UserSettingsJSON['password_settings']; + + const passkeySettingsConfig = { + allow_autofill: false, + show_sign_in_button: false, + } as UserSettingsJSON['passkey_settings']; + + return { + attributes: { ...attributeConfig }, + actions: { delete_self: false, create_organization: false }, + social: { ...socialConfig }, + saml: { enabled: false }, + enterprise_sso: { enabled: false }, + sign_in: { + second_factor: { + required: false, + }, + }, + sign_up: { + custom_action_required: false, + progressive: true, + captcha_enabled: false, + disable_hibp: false, + mode: 'public', + }, + restrictions: { + allowlist: { + enabled: false, + }, + blocklist: { + enabled: false, + }, + }, + password_settings: passwordSettingsConfig, + passkey_settings: passkeySettingsConfig, + }; +}; + +export const createBaseClientJSON = (): ClientJSON => { + return {} as ClientJSON; +}; + +export const createUserFixture = (): UserJSON => { + return { + first_name: 'Firstname', + last_name: 'Lastname', + image_url: + 'https://img.clerk.com/eyJ0eXBlIjoicHJveHkiLCJzcmMiOiJodHRwczovL2xoMy5nb29nbGV1c2VyY29udGVudC5jb20vYS9BQ2c4b2NLTmR2TUtFQzN5cUVpMVFjV0UzQjExbF9WUEVOWW5manlLMlVQd0tCSWw9czEwMDAtYyIsInMiOiJkRkowS3dTSkRINndiODE5cXJTUUxxaWF1ZS9QcHdndC84L0lUUlpYNHpnIn0?width=160', + } as UserJSON; +}; diff --git a/packages/clerk-js/src/ui/utils/vitest/mockHelpers.ts b/packages/clerk-js/src/ui/utils/vitest/mockHelpers.ts new file mode 100644 index 00000000000..1ac4a244562 --- /dev/null +++ b/packages/clerk-js/src/ui/utils/vitest/mockHelpers.ts @@ -0,0 +1,103 @@ +// import { jest } from '@jest/globals'; +import type { LoadedClerk } from '@clerk/types'; +import type { ActiveSessionResource } from '@clerk/types'; +import { vi, type Mocked } from 'vitest'; + +import type { RouteContextValue } from '../../router'; + +type FunctionLike = (...args: any) => any; + +type DeepVitestMocked = T extends FunctionLike + ? Mocked + : T extends object + ? { + [k in keyof T]: DeepVitestMocked; + } + : T; + +// 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; +}; + +const mockProp = (obj: T, k: keyof T, mocks?: MockMap) => { + if (typeof obj[k] === 'function') { + // @ts-ignore - Assume it's assignable for mocking + obj[k] = mocks?.[k] ?? vi.fn(); + } +}; + +const mockMethodsOf = | null = any>( + obj: T, + options?: { + exclude: (keyof T)[]; + mocks: MockMap; + }, +) => { + if (!obj) { + return; + } + Object.keys(obj) + .filter(key => !options?.exclude.includes(key as keyof T)) + // Pass the specific MockMap for the object T being mocked + .forEach(k => mockProp(obj, k, options?.mocks)); +}; + +export const mockClerkMethods = (clerk: LoadedClerk): DeepVitestMocked => { + // Cast clerk to any to allow mocking properties + const clerkAny = clerk as any; + + mockMethodsOf(clerkAny); + if (clerkAny.client) { + mockMethodsOf(clerkAny.client.signIn); + mockMethodsOf(clerkAny.client.signUp); + clerkAny.client.sessions?.forEach((session: ActiveSessionResource) => { + const sessionAny = session as any; + mockMethodsOf(sessionAny, { + exclude: ['checkAuthorization'], + mocks: { + // Ensure touch mock matches expected signature if available, otherwise basic mock + touch: vi.fn(() => Promise.resolve(session)), + }, + }); + if (sessionAny.user) { + mockMethodsOf(sessionAny.user); + sessionAny.user.emailAddresses?.forEach((m: any) => mockMethodsOf(m)); + sessionAny.user.phoneNumbers?.forEach((m: any) => mockMethodsOf(m)); + sessionAny.user.externalAccounts?.forEach((m: any) => mockMethodsOf(m)); + sessionAny.user.organizationMemberships?.forEach((m: any) => { + mockMethodsOf(m); + if (m.organization) { + mockMethodsOf(m.organization); + } + }); + sessionAny.user.passkeys?.forEach((m: any) => mockMethodsOf(m)); + } + }); + } + mockProp(clerkAny, 'navigate'); + mockProp(clerkAny, 'setActive'); + mockProp(clerkAny, 'redirectWithAuth'); + mockProp(clerkAny, '__internal_navigateWithError'); + return clerkAny as DeepVitestMocked; +}; + +export const mockRouteContextValue = ({ queryString = '' }: Partial>) => { + return { + basePath: '', + startPath: '', + flowStartPath: '', + fullPath: '', + indexPath: '', + currentPath: '', + queryString, + queryParams: {}, + getMatchData: vi.fn(), + matches: vi.fn(), + baseNavigate: vi.fn(), + navigate: vi.fn(() => Promise.resolve(true)), + resolve: vi.fn((to: string) => new URL(to, 'https://clerk.com')), + refresh: vi.fn(), + params: {}, + } as RouteContextValue; // Keep original type assertion, DeepVitestMocked applied to input only +}; diff --git a/packages/clerk-js/src/ui/utils/vitest/runFakeTimers.ts b/packages/clerk-js/src/ui/utils/vitest/runFakeTimers.ts new file mode 100644 index 00000000000..7766954fb0d --- /dev/null +++ b/packages/clerk-js/src/ui/utils/vitest/runFakeTimers.ts @@ -0,0 +1,36 @@ +import { vi } from 'vitest'; +// 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(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 const runFakeTimers = >( + cb: T, +): R extends Promise ? Promise : void => { + vi.useFakeTimers(); + const res = cb(createFakeTimersHelpers()); + if (res && 'then' in res) { + // @ts-expect-error + return res.finally(() => vi.useRealTimers()); + } + vi.useRealTimers(); + // @ts-ignore + return; +}; diff --git a/packages/clerk-js/src/utils/__tests__/captcha.test.ts b/packages/clerk-js/src/utils/__tests__/captcha.spec.ts similarity index 92% rename from packages/clerk-js/src/utils/__tests__/captcha.test.ts rename to packages/clerk-js/src/utils/__tests__/captcha.spec.ts index 0b64aaee20b..63cb871f1a0 100644 --- a/packages/clerk-js/src/utils/__tests__/captcha.test.ts +++ b/packages/clerk-js/src/utils/__tests__/captcha.spec.ts @@ -1,3 +1,5 @@ +import { describe, expect, it } from 'vitest'; + import { shouldRetryTurnstileErrorCode } from '../captcha/turnstile'; describe('shouldRetryTurnstileErrorCode', () => { diff --git a/packages/clerk-js/src/utils/__tests__/completeSignUpFlow.test.ts b/packages/clerk-js/src/utils/__tests__/completeSignUpFlow.spec.ts similarity index 98% rename from packages/clerk-js/src/utils/__tests__/completeSignUpFlow.test.ts rename to packages/clerk-js/src/utils/__tests__/completeSignUpFlow.spec.ts index 8c82f685d0b..032e1f33416 100644 --- a/packages/clerk-js/src/utils/__tests__/completeSignUpFlow.test.ts +++ b/packages/clerk-js/src/utils/__tests__/completeSignUpFlow.spec.ts @@ -1,10 +1,11 @@ import type { SignUpField, SignUpResource } from '@clerk/types'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; import { completeSignUpFlow } from '../completeSignUpFlow'; -const mockHandleComplete = jest.fn(); -const mockNavigate = jest.fn(); -const mockAuthenticateWithRedirect = jest.fn(); +const mockHandleComplete = vi.fn(); +const mockNavigate = vi.fn(); +const mockAuthenticateWithRedirect = vi.fn(); describe('completeSignUpFlow', () => { beforeEach(() => { diff --git a/packages/clerk-js/src/utils/__tests__/date.test.ts b/packages/clerk-js/src/utils/__tests__/date.spec.ts similarity index 82% rename from packages/clerk-js/src/utils/__tests__/date.test.ts rename to packages/clerk-js/src/utils/__tests__/date.spec.ts index dfb60addb73..33c8e28a069 100644 --- a/packages/clerk-js/src/utils/__tests__/date.test.ts +++ b/packages/clerk-js/src/utils/__tests__/date.spec.ts @@ -1,13 +1,14 @@ import { unixEpochToDate } from '../date'; +import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'; describe('date utilities', () => { beforeAll(() => { - jest.useFakeTimers('modern'); - jest.setSystemTime(); + vi.useFakeTimers(); + vi.setSystemTime(Date.now()); }); afterAll(() => { - jest.useRealTimers(); + vi.useRealTimers(); }); describe('unixEpochToDate', () => { diff --git a/packages/clerk-js/src/utils/__tests__/dynamicParamParser.test.ts b/packages/clerk-js/src/utils/__tests__/dynamicParamParser.spec.ts similarity index 94% rename from packages/clerk-js/src/utils/__tests__/dynamicParamParser.test.ts rename to packages/clerk-js/src/utils/__tests__/dynamicParamParser.spec.ts index 3f9ceafd853..712de7756a4 100644 --- a/packages/clerk-js/src/utils/__tests__/dynamicParamParser.test.ts +++ b/packages/clerk-js/src/utils/__tests__/dynamicParamParser.spec.ts @@ -1,4 +1,4 @@ -import { describe } from '@jest/globals'; +import { describe, expect, it } from 'vitest'; import { createDynamicParamParser } from '../dynamicParamParser'; diff --git a/packages/clerk-js/src/utils/__tests__/errors.test.ts b/packages/clerk-js/src/utils/__tests__/errors.spec.ts similarity index 91% rename from packages/clerk-js/src/utils/__tests__/errors.test.ts rename to packages/clerk-js/src/utils/__tests__/errors.spec.ts index 9de84ae2216..f7c3477f5b8 100644 --- a/packages/clerk-js/src/utils/__tests__/errors.test.ts +++ b/packages/clerk-js/src/utils/__tests__/errors.spec.ts @@ -1,3 +1,5 @@ +import { describe, expect, it } from 'vitest'; + import type { ClerkAPIResponseError } from '../../core/resources/Error'; import { isError } from '../errors'; diff --git a/packages/clerk-js/src/utils/__tests__/ignoreEventValue.test.ts b/packages/clerk-js/src/utils/__tests__/ignoreEventValue.spec.ts similarity index 97% rename from packages/clerk-js/src/utils/__tests__/ignoreEventValue.test.ts rename to packages/clerk-js/src/utils/__tests__/ignoreEventValue.spec.ts index 0968e8c5101..3ed749b54b1 100644 --- a/packages/clerk-js/src/utils/__tests__/ignoreEventValue.test.ts +++ b/packages/clerk-js/src/utils/__tests__/ignoreEventValue.spec.ts @@ -1,3 +1,5 @@ +import { describe, expect, it } from 'vitest'; + import { ignoreEventValue } from '../ignoreEventValue'; const noop = (..._args: any[]): void => { diff --git a/packages/clerk-js/src/utils/__tests__/instance.test.ts b/packages/clerk-js/src/utils/__tests__/instance.spec.ts similarity index 95% rename from packages/clerk-js/src/utils/__tests__/instance.test.ts rename to packages/clerk-js/src/utils/__tests__/instance.spec.ts index f0e4c1cbcef..b73e8c68d0b 100644 --- a/packages/clerk-js/src/utils/__tests__/instance.test.ts +++ b/packages/clerk-js/src/utils/__tests__/instance.spec.ts @@ -1,3 +1,5 @@ +import { describe, expect, it } from 'vitest'; + import { validateFrontendApi } from '../instance'; describe('validateFrontendApi(str)', () => { diff --git a/packages/clerk-js/src/utils/__tests__/jwt.test.ts b/packages/clerk-js/src/utils/__tests__/jwt.spec.ts similarity index 94% rename from packages/clerk-js/src/utils/__tests__/jwt.test.ts rename to packages/clerk-js/src/utils/__tests__/jwt.spec.ts index f7f5e564b1a..3d58ea4f19f 100644 --- a/packages/clerk-js/src/utils/__tests__/jwt.test.ts +++ b/packages/clerk-js/src/utils/__tests__/jwt.spec.ts @@ -1,3 +1,5 @@ +import { describe, expect, it } from 'vitest'; + import { decode } from '../jwt'; const jwt = diff --git a/packages/clerk-js/src/utils/__tests__/localStorage.test.ts b/packages/clerk-js/src/utils/__tests__/localStorage.spec.ts similarity index 95% rename from packages/clerk-js/src/utils/__tests__/localStorage.test.ts rename to packages/clerk-js/src/utils/__tests__/localStorage.spec.ts index 792f0acfa7f..c86fb12d587 100644 --- a/packages/clerk-js/src/utils/__tests__/localStorage.test.ts +++ b/packages/clerk-js/src/utils/__tests__/localStorage.spec.ts @@ -1,4 +1,5 @@ import { SafeLocalStorage } from '../localStorage'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; describe('SafeLocalStorage', () => { let mockStorage: { [key: string]: string } = {}; @@ -26,7 +27,7 @@ describe('SafeLocalStorage', () => { afterEach(() => { mockStorage = {}; - jest.restoreAllMocks(); + vi.restoreAllMocks(); }); describe('setItem', () => { @@ -53,13 +54,13 @@ describe('SafeLocalStorage', () => { }); it('sets expiration when provided', () => { - jest.useFakeTimers(); + vi.useFakeTimers(); const now = Date.now(); SafeLocalStorage.setItem('test', 'value', 1000); const stored = JSON.parse(mockStorage['__clerk_test']); expect(stored.exp).toBe(now + 1000); - jest.useRealTimers(); + vi.useRealTimers(); }); it('stores complex objects correctly', () => { @@ -105,15 +106,15 @@ describe('SafeLocalStorage', () => { }); it('returns default value and removes item when expired', () => { - jest.useFakeTimers(); + vi.useFakeTimers(); SafeLocalStorage.setItem('test', 'value', 1_000); // Advance time beyond expiration - jest.advanceTimersByTime(1_001); + vi.advanceTimersByTime(1_001); expect(SafeLocalStorage.getItem('test', 'default')).toBe('default'); expect(mockStorage['__clerk_test']).toBeUndefined(); - jest.useRealTimers(); + vi.useRealTimers(); }); it('handles malformed JSON data by returning default value', () => { diff --git a/packages/clerk-js/src/utils/__tests__/memoizeStateListenerCallback.test.ts b/packages/clerk-js/src/utils/__tests__/memoizeStateListenerCallback.spec.ts similarity index 96% rename from packages/clerk-js/src/utils/__tests__/memoizeStateListenerCallback.test.ts rename to packages/clerk-js/src/utils/__tests__/memoizeStateListenerCallback.spec.ts index b553ae25003..1be794dde9f 100644 --- a/packages/clerk-js/src/utils/__tests__/memoizeStateListenerCallback.test.ts +++ b/packages/clerk-js/src/utils/__tests__/memoizeStateListenerCallback.spec.ts @@ -1,5 +1,7 @@ // TODO: jest fails because of a circular dependency on Client -> Base -> Client // This circular dep is a known issue we plan to address soon. Enable the tests then +import { describe, it } from 'vitest'; + describe.skip('memoizeStateListenerCallback', () => { it.skip('runs', () => { // TODO diff --git a/packages/clerk-js/src/utils/__tests__/organization.test.ts b/packages/clerk-js/src/utils/__tests__/organization.spec.ts similarity index 93% rename from packages/clerk-js/src/utils/__tests__/organization.test.ts rename to packages/clerk-js/src/utils/__tests__/organization.spec.ts index 4262f073372..b722ef4b27b 100644 --- a/packages/clerk-js/src/utils/__tests__/organization.test.ts +++ b/packages/clerk-js/src/utils/__tests__/organization.spec.ts @@ -1,3 +1,5 @@ +import { describe, expect, it } from 'vitest'; + import { isOrganizationId } from '../organization'; describe('isOrganizationId(string)', () => { diff --git a/packages/clerk-js/src/utils/__tests__/passkeys.test.ts b/packages/clerk-js/src/utils/__tests__/passkeys.spec.ts similarity index 99% rename from packages/clerk-js/src/utils/__tests__/passkeys.test.ts rename to packages/clerk-js/src/utils/__tests__/passkeys.spec.ts index c78c0f40b49..16516d1e745 100644 --- a/packages/clerk-js/src/utils/__tests__/passkeys.test.ts +++ b/packages/clerk-js/src/utils/__tests__/passkeys.spec.ts @@ -4,6 +4,7 @@ import type { PublicKeyCredentialWithAuthenticatorAssertionResponse, PublicKeyCredentialWithAuthenticatorAttestationResponse, } from '@clerk/types'; +import { describe, expect, it } from 'vitest'; import { bufferToBase64Url, diff --git a/packages/clerk-js/src/utils/__tests__/path.test.ts b/packages/clerk-js/src/utils/__tests__/path.spec.ts similarity index 91% rename from packages/clerk-js/src/utils/__tests__/path.test.ts rename to packages/clerk-js/src/utils/__tests__/path.spec.ts index fd1ae1c2ac2..97686eb9553 100644 --- a/packages/clerk-js/src/utils/__tests__/path.test.ts +++ b/packages/clerk-js/src/utils/__tests__/path.spec.ts @@ -1,3 +1,5 @@ +import { describe, expect, it } from 'vitest'; + import { joinPaths } from '../path'; describe('joinPaths(a, b)', () => { diff --git a/packages/clerk-js/src/utils/__tests__/queryStateParams.test.ts b/packages/clerk-js/src/utils/__tests__/queryStateParams.spec.ts similarity index 95% rename from packages/clerk-js/src/utils/__tests__/queryStateParams.test.ts rename to packages/clerk-js/src/utils/__tests__/queryStateParams.spec.ts index e19a7b5fb2d..9a5d44292eb 100644 --- a/packages/clerk-js/src/utils/__tests__/queryStateParams.test.ts +++ b/packages/clerk-js/src/utils/__tests__/queryStateParams.spec.ts @@ -1,3 +1,5 @@ +import { describe, expect, it } from 'vitest'; + import { appendModalState } from '../queryStateParams'; describe('appendModalState function', () => { diff --git a/packages/clerk-js/src/utils/__tests__/querystring.test.ts b/packages/clerk-js/src/utils/__tests__/querystring.spec.ts similarity index 98% rename from packages/clerk-js/src/utils/__tests__/querystring.test.ts rename to packages/clerk-js/src/utils/__tests__/querystring.spec.ts index 75a5b54a322..b1050cd89e6 100644 --- a/packages/clerk-js/src/utils/__tests__/querystring.test.ts +++ b/packages/clerk-js/src/utils/__tests__/querystring.spec.ts @@ -1,4 +1,5 @@ import { camelToSnake } from '@clerk/shared/underscore'; +import { describe, expect, it } from 'vitest'; import { getQueryParams, stringifyQueryParams } from '../querystring'; diff --git a/packages/clerk-js/src/utils/__tests__/redirectUrls.test.ts b/packages/clerk-js/src/utils/__tests__/redirectUrls.spec.ts similarity index 99% rename from packages/clerk-js/src/utils/__tests__/redirectUrls.test.ts rename to packages/clerk-js/src/utils/__tests__/redirectUrls.spec.ts index 8f7a3c4c51c..16cd00075d9 100644 --- a/packages/clerk-js/src/utils/__tests__/redirectUrls.test.ts +++ b/packages/clerk-js/src/utils/__tests__/redirectUrls.spec.ts @@ -1,5 +1,6 @@ import { snakeToCamel } from '@clerk/shared/underscore'; import type { RedirectOptions } from '@clerk/types'; +import { afterAll, beforeEach, describe, expect, it } from 'vitest'; import { RedirectUrls } from '../redirectUrls'; diff --git a/packages/clerk-js/src/utils/__tests__/resourceParams.test.ts b/packages/clerk-js/src/utils/__tests__/resourceParams.spec.ts similarity index 96% rename from packages/clerk-js/src/utils/__tests__/resourceParams.test.ts rename to packages/clerk-js/src/utils/__tests__/resourceParams.spec.ts index 31c694eae25..a15b9253808 100644 --- a/packages/clerk-js/src/utils/__tests__/resourceParams.test.ts +++ b/packages/clerk-js/src/utils/__tests__/resourceParams.spec.ts @@ -1,4 +1,5 @@ import type { UpdateUserParams } from '@clerk/types'; +import { describe, expect, it } from 'vitest'; import { normalizeUnsafeMetadata } from '../resourceParams'; diff --git a/packages/clerk-js/src/utils/__tests__/url.test.ts b/packages/clerk-js/src/utils/__tests__/url.spec.ts similarity index 97% rename from packages/clerk-js/src/utils/__tests__/url.test.ts rename to packages/clerk-js/src/utils/__tests__/url.spec.ts index 53176d95584..9e37b01cc08 100644 --- a/packages/clerk-js/src/utils/__tests__/url.test.ts +++ b/packages/clerk-js/src/utils/__tests__/url.spec.ts @@ -1,5 +1,6 @@ import { logger } from '@clerk/shared/logger'; import type { SignUpResource } from '@clerk/types'; +import { afterAll, beforeEach, describe, expect, it, test, vi } from 'vitest'; import { buildURL, @@ -148,7 +149,7 @@ describe('hasBannedProtocol(url)', () => { describe('buildURL(options: URLParams, skipOrigin)', () => { it('builds a URL()', () => { - expect(buildURL({}, { stringify: true })).toBe('http://localhost/'); + expect(buildURL({}, { stringify: true })).toBe('http://localhost:3000/'); expect( buildURL( { @@ -158,7 +159,7 @@ describe('buildURL(options: URLParams, skipOrigin)', () => { }, { stringify: true }, ), - ).toBe('http://localhost/my-path?my-search#my-hash?my-hashed-search'); + ).toBe('http://localhost:3000/my-path?my-search#my-hash?my-hashed-search'); expect( buildURL( { @@ -499,7 +500,7 @@ describe('isAllowedRedirect', () => { ['..//evil.com', ['https://www.clerk.com'], false], ]; - const warnMock = jest.spyOn(logger, 'warnOnce'); + const warnMock = vi.spyOn(logger, 'warnOnce'); beforeEach(() => warnMock.mockClear()); afterAll(() => warnMock.mockRestore()); @@ -516,11 +517,7 @@ describe('createAllowedRedirectOrigins', () => { const allowedRedirectOriginsValuesUndefined = createAllowedRedirectOrigins(undefined, frontendApi, 'production'); const allowedRedirectOriginsValuesEmptyArray = createAllowedRedirectOrigins([], frontendApi, 'production'); - const expectedAllowedRedirectOrigins = [ - 'http://localhost', // Current location - `https://example.com`, // Primary domain - `https://*.example.com`, // Wildcard subdomains - ]; + const expectedAllowedRedirectOrigins = ['http://localhost:3000', `https://example.com`, `https://*.example.com`]; expect(allowedRedirectOriginsValuesUndefined).toEqual(expectedAllowedRedirectOrigins); expect(allowedRedirectOriginsValuesEmptyArray).toEqual(expectedAllowedRedirectOrigins); @@ -532,10 +529,10 @@ describe('createAllowedRedirectOrigins', () => { const allowedRedirectOriginsValuesEmptyArray = createAllowedRedirectOrigins([], frontendApi, 'development'); const expectedAllowedRedirectOrigins = [ - 'http://localhost', // Current location - `https://foo-bar-42.accounts.dev`, // Account Portal - `https://*.foo-bar-42.accounts.dev`, // Account Portal subdomains - `https://foo-bar-42.clerk.accounts.dev`, // Frontend API + 'http://localhost:3000', + `https://foo-bar-42.accounts.dev`, + `https://*.foo-bar-42.accounts.dev`, + `https://foo-bar-42.clerk.accounts.dev`, ]; expect(allowedRedirectOriginsValuesUndefined).toEqual(expectedAllowedRedirectOrigins); diff --git a/packages/clerk-js/src/vitestUtils.ts b/packages/clerk-js/src/vitestUtils.ts new file mode 100644 index 00000000000..58500f47371 --- /dev/null +++ b/packages/clerk-js/src/vitestUtils.ts @@ -0,0 +1,78 @@ +// 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, expect, type SpyInstance, vi } from 'vitest'; + +expect.extend(matchers); + +Element.prototype.scrollIntoView = vi.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: SpyInstance; + let spyNavigator: SpyInstance; + + beforeAll(() => { + spyDocument = vi.spyOn(globalThis, 'document', 'get'); + spyDocument.mockReturnValue(undefined); + + spyNavigator = vi.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/vitest/runFakeTimers'; +export * from './ui/utils/vitest/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/vitest.config.mts b/packages/clerk-js/vitest.config.mts index f9d9cf2ecd4..1164a1c70a4 100644 --- a/packages/clerk-js/vitest.config.mts +++ b/packages/clerk-js/vitest.config.mts @@ -1,14 +1,58 @@ -import { defineConfig } from 'vitest/config'; import react from '@vitejs/plugin-react'; +import { resolve } from 'path'; +import svgr from 'vite-plugin-svgr'; +import { defineConfig } from 'vitest/config'; + +function viteSvgMockPlugin() { + return { + name: 'svg-mock', + transform(code: string, id: string) { + if (id.endsWith('.svg') && process.env.NODE_ENV === 'test') { + return { + code: ` + import React from 'react'; + const SvgMock = React.forwardRef((props, ref) => React.createElement('span', { ref, ...props })); + export default SvgMock; + export { SvgMock as ReactComponent }; + `, + map: null, + }; + } + }, + }; +} export default defineConfig({ - plugins: [react({ jsxRuntime: 'automatic', jsxImportSource: '@emotion/react' })], + plugins: [react({ jsxRuntime: 'automatic', jsxImportSource: '@emotion/react' }), svgr(), viteSvgMockPlugin()], define: { + __BUILD_VARIANT_CHIPS__: JSON.stringify(false), __PKG_NAME__: JSON.stringify('@clerk/clerk-js'), + __PKG_VERSION__: JSON.stringify('test'), }, test: { - include: ['**/*.spec.?(c|m)[jt]s?(x)'], + coverage: { + provider: 'v8', + enabled: true, + reporter: ['text', 'json', 'html'], + include: ['src/**/*.{ts,tsx}'], + exclude: [ + 'src/**/*.d.ts', + 'src/**/index.ts', + 'src/**/index.browser.ts', + 'src/**/index.headless.ts', + 'src/**/index.headless.browser.ts', + 'src/**/coverage/**', + 'src/**/dist/**', + 'src/**/node_modules/**', + 'src/(ui|utils|core)/__tests__/**', + ], + }, environment: 'jsdom', + globals: false, + include: ['**/*.spec.?(c|m)[jt]s?(x)'], setupFiles: './vitest.setup.mts', }, + resolve: { + alias: [{ find: /^@\//, replacement: `${resolve(__dirname, 'src')}/` }], + }, }); diff --git a/packages/clerk-js/vitest.setup.mts b/packages/clerk-js/vitest.setup.mts index 5740515e5d5..c93f9912ce2 100644 --- a/packages/clerk-js/vitest.setup.mts +++ b/packages/clerk-js/vitest.setup.mts @@ -1,5 +1,98 @@ -import { afterEach } from 'vitest'; -import { cleanup } from '@testing-library/react'; import '@testing-library/jest-dom/vitest'; +import * as crypto from 'node:crypto'; +import { TextDecoder, TextEncoder } from 'node:util'; + +import { cleanup } from '@testing-library/react'; +import { afterAll, afterEach, beforeAll, vi } from 'vitest'; + afterEach(cleanup); + +// Store the original method +// eslint-disable-next-line @typescript-eslint/unbound-method +const ogToLocaleDateString = Date.prototype.toLocaleDateString; + +beforeAll(() => { + // Make sure our tests always use the same locale + Date.prototype.toLocaleDateString = function (...args: any[]) { + // Call original method with 'en-US' locale + return ogToLocaleDateString.call(this, 'en-US', args[1]); // Pass options if provided + }; + + // --- Setup from jest.jsdom-with-timezone.ts --- + // Set a default timezone (e.g., UTC) for consistency + process.env.TZ = 'UTC'; +}); + +afterAll(() => { + // Restore original Date method + Date.prototype.toLocaleDateString = ogToLocaleDateString; +}); + +// --- Setup from package jest.setup.ts --- + +// Mock Response class if not already defined by jsdom/happy-dom +class FakeResponse {} + +// Polyfill/mock global objects for the jsdom environment +if (typeof window !== 'undefined') { + Object.defineProperties(globalThis, { + TextDecoder: { value: TextDecoder }, + TextEncoder: { value: TextEncoder }, + Response: { value: FakeResponse }, + crypto: { value: crypto.webcrypto }, + }); + + // Mock ResizeObserver + window.ResizeObserver = + window.ResizeObserver || + vi.fn().mockImplementation(() => ({ + disconnect: vi.fn(), + observe: vi.fn(), + unobserve: vi.fn(), + })); + + // Mock matchMedia + Object.defineProperty(window, 'matchMedia', { + writable: true, + value: vi.fn().mockImplementation(query => ({ + matches: false, + media: query, + onchange: null, + addEventListener: vi.fn(), + removeEventListener: vi.fn(), + dispatchEvent: vi.fn(), + })), + }); + + // Mock IntersectionObserver + //@ts-expect-error - Mocking class + globalThis.IntersectionObserver = class IntersectionObserver { + constructor() {} + disconnect() { + return null; + } + observe() { + return null; + } + takeRecords() { + return []; // Return empty array as per spec + } + unobserve() { + return null; + } + }; +} + +// Mock jest-chrome if its functionality is needed +// Example: Mocking chrome.runtime.sendMessage +// global.chrome = { +// runtime: { +// sendMessage: vi.fn(), +// // ... other chrome APIs needed +// }, +// // ... other chrome namespaces needed +// }; + +// Add any other global setup needed for your tests +console.log('ClerkJS Vitest setup complete.'); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e97b0cb8d27..bbb30226754 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -501,6 +501,9 @@ importers: specifier: 2.3.3 version: 2.3.3(react@18.3.1) devDependencies: + '@emotion/jest': + specifier: ^11.13.0 + version: 11.13.0(@types/jest@29.5.12) '@rsdoctor/rspack-plugin': specifier: ^0.4.13 version: 0.4.13(@rspack/core@1.2.8(@swc/helpers@0.5.17))(webpack@5.94.0(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.25.0)) @@ -525,6 +528,12 @@ importers: '@types/webpack-env': specifier: ^1.18.8 version: 1.18.8 + jsdom: + specifier: ^24.1.1 + version: 24.1.3 + vite-plugin-svgr: + specifier: ^4.2.0 + version: 4.3.0(rollup@4.39.0)(typescript@5.8.3)(vite@6.2.6(@types/node@22.15.29)(jiti@2.4.2)(lightningcss@1.27.0)(terser@5.39.0)(tsx@4.19.2)(yaml@2.7.1)) webpack-merge: specifier: ^5.10.0 version: 5.10.0 @@ -2991,7 +3000,7 @@ packages: '@expo/bunyan@4.0.1': resolution: {integrity: sha512-+Lla7nYSiHZirgK+U/uYzsLv/X+HaJienbD5AKX1UQZHYfWaP+9uuQluRB4GrEVWF0GZ7vEVp/jzaOT9k/SQlg==} - engines: {node: '>=0.10.0'} + engines: {'0': node >=0.10.0} '@expo/cli@0.22.26': resolution: {integrity: sha512-I689wc8Fn/AX7aUGiwrh3HnssiORMJtR2fpksX+JIe8Cj/EDleblYMSwRPd0025wrwOV9UN1KM/RuEt/QjCS3Q==} @@ -4565,6 +4574,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@svgr/babel-plugin-add-jsx-attribute@8.0.0': + resolution: {integrity: sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@svgr/babel-plugin-remove-jsx-attribute@8.0.0': resolution: {integrity: sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==} engines: {node: '>=14'} @@ -4583,50 +4598,100 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0': + resolution: {integrity: sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@svgr/babel-plugin-svg-dynamic-title@6.5.1': resolution: {integrity: sha512-FwOEi0Il72iAzlkaHrlemVurgSQRDFbk0OC8dSvD5fSBPHltNh7JtLsxmZUhjYBZo2PpcU/RJvvi6Q0l7O7ogw==} engines: {node: '>=10'} peerDependencies: '@babel/core': ^7.0.0-0 + '@svgr/babel-plugin-svg-dynamic-title@8.0.0': + resolution: {integrity: sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@svgr/babel-plugin-svg-em-dimensions@6.5.1': resolution: {integrity: sha512-gWGsiwjb4tw+ITOJ86ndY/DZZ6cuXMNE/SjcDRg+HLuCmwpcjOktwRF9WgAiycTqJD/QXqL2f8IzE2Rzh7aVXA==} engines: {node: '>=10'} peerDependencies: '@babel/core': ^7.0.0-0 + '@svgr/babel-plugin-svg-em-dimensions@8.0.0': + resolution: {integrity: sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@svgr/babel-plugin-transform-react-native-svg@6.5.1': resolution: {integrity: sha512-2jT3nTayyYP7kI6aGutkyfJ7UMGtuguD72OjeGLwVNyfPRBD8zQthlvL+fAbAKk5n9ZNcvFkp/b1lZ7VsYqVJg==} engines: {node: '>=10'} peerDependencies: '@babel/core': ^7.0.0-0 + '@svgr/babel-plugin-transform-react-native-svg@8.1.0': + resolution: {integrity: sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@svgr/babel-plugin-transform-svg-component@6.5.1': resolution: {integrity: sha512-a1p6LF5Jt33O3rZoVRBqdxL350oge54iZWHNI6LJB5tQ7EelvD/Mb1mfBiZNAan0dt4i3VArkFRjA4iObuNykQ==} engines: {node: '>=12'} peerDependencies: '@babel/core': ^7.0.0-0 + '@svgr/babel-plugin-transform-svg-component@8.0.0': + resolution: {integrity: sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==} + engines: {node: '>=12'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@svgr/babel-preset@6.5.1': resolution: {integrity: sha512-6127fvO/FF2oi5EzSQOAjo1LE3OtNVh11R+/8FXa+mHx1ptAaS4cknIjnUA7e6j6fwGGJ17NzaTJFUwOV2zwCw==} engines: {node: '>=10'} peerDependencies: '@babel/core': ^7.0.0-0 + '@svgr/babel-preset@8.1.0': + resolution: {integrity: sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@svgr/core@6.5.1': resolution: {integrity: sha512-/xdLSWxK5QkqG524ONSjvg3V/FkNyCv538OIBdQqPNaAta3AsXj/Bd2FbvR87yMbXO2hFSWiAe/Q6IkVPDw+mw==} engines: {node: '>=10'} + '@svgr/core@8.1.0': + resolution: {integrity: sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==} + engines: {node: '>=14'} + '@svgr/hast-util-to-babel-ast@6.5.1': resolution: {integrity: sha512-1hnUxxjd83EAxbL4a0JDJoD3Dao3hmjvyvyEV8PzWmLK3B9m9NPlW7GKjFyoWE8nM7HnXzPcmmSyOW8yOddSXw==} engines: {node: '>=10'} + '@svgr/hast-util-to-babel-ast@8.0.0': + resolution: {integrity: sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==} + engines: {node: '>=14'} + '@svgr/plugin-jsx@6.5.1': resolution: {integrity: sha512-+UdQxI3jgtSjCykNSlEMuy1jSRQlGC7pqBCPvkG/2dATdWo082zHTTK3uhnAju2/6XpE6B5mZ3z4Z8Ns01S8Gw==} engines: {node: '>=10'} peerDependencies: '@svgr/core': ^6.0.0 + '@svgr/plugin-jsx@8.1.0': + resolution: {integrity: sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==} + engines: {node: '>=14'} + peerDependencies: + '@svgr/core': '*' + '@svgr/plugin-svgo@6.5.1': resolution: {integrity: sha512-omvZKf8ixP9z6GWgwbtmP9qQMPX4ODXi+wzbVZgomNFsUIlHA1sf4fThdwTWSsZGgvGAG6yE+b/F5gWUkcZ/iQ==} engines: {node: '>=10'} @@ -6955,6 +7020,15 @@ packages: resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} engines: {node: '>=10'} + cosmiconfig@8.3.6: + resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==} + engines: {node: '>=14'} + peerDependencies: + typescript: '>=4.9.5' + peerDependenciesMeta: + typescript: + optional: true + cosmiconfig@9.0.0: resolution: {integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==} engines: {node: '>=14'} @@ -11902,7 +11976,6 @@ packages: engines: {node: '>=0.6.0', teleport: '>=0.2.0'} deprecated: |- You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other. - (For a CapTP with native promises, see @endo/eventual-send and @endo/captp) qrcode-terminal@0.11.0: @@ -14061,6 +14134,11 @@ packages: '@nuxt/kit': optional: true + vite-plugin-svgr@4.3.0: + resolution: {integrity: sha512-Jy9qLB2/PyWklpYy0xk0UU3TlU0t2UMpJXZvf+hWII1lAmRHrOUKi11Uw8N3rxoNk7atZNYO3pR3vI1f7oi+6w==} + peerDependencies: + vite: '>=2.6.0' + vite-plugin-vue-tracer@0.1.3: resolution: {integrity: sha512-+fN6oo0//dwZP9Ax9gRKeUroCqpQ43P57qlWgL0ljCIxAs+Rpqn/L4anIPZPgjDPga5dZH+ZJsshbF0PNJbm3Q==} peerDependencies: @@ -14781,7 +14859,6 @@ snapshots: '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) '@csstools/css-tokenizer': 3.0.3 lru-cache: 10.4.3 - optional: true '@astrojs/compiler@2.11.0': {} @@ -16062,14 +16139,12 @@ snapshots: '@jridgewell/trace-mapping': 0.3.9 optional: true - '@csstools/color-helpers@5.0.2': - optional: true + '@csstools/color-helpers@5.0.2': {} '@csstools/css-calc@2.1.3(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)': dependencies: '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) '@csstools/css-tokenizer': 3.0.3 - optional: true '@csstools/css-color-parser@3.0.9(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)': dependencies: @@ -16077,15 +16152,12 @@ snapshots: '@csstools/css-calc': 2.1.3(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) '@csstools/css-tokenizer': 3.0.3 - optional: true '@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3)': dependencies: '@csstools/css-tokenizer': 3.0.3 - optional: true - '@csstools/css-tokenizer@3.0.3': - optional: true + '@csstools/css-tokenizer@3.0.3': {} '@cypress/request@3.0.6': dependencies: @@ -18977,6 +19049,10 @@ snapshots: dependencies: '@babel/core': 7.27.4 + '@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.27.4)': + dependencies: + '@babel/core': 7.27.4 + '@svgr/babel-plugin-remove-jsx-attribute@8.0.0(@babel/core@7.27.4)': dependencies: '@babel/core': 7.27.4 @@ -18989,22 +19065,42 @@ snapshots: dependencies: '@babel/core': 7.27.4 + '@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0(@babel/core@7.27.4)': + dependencies: + '@babel/core': 7.27.4 + '@svgr/babel-plugin-svg-dynamic-title@6.5.1(@babel/core@7.27.4)': dependencies: '@babel/core': 7.27.4 + '@svgr/babel-plugin-svg-dynamic-title@8.0.0(@babel/core@7.27.4)': + dependencies: + '@babel/core': 7.27.4 + '@svgr/babel-plugin-svg-em-dimensions@6.5.1(@babel/core@7.27.4)': dependencies: '@babel/core': 7.27.4 + '@svgr/babel-plugin-svg-em-dimensions@8.0.0(@babel/core@7.27.4)': + dependencies: + '@babel/core': 7.27.4 + '@svgr/babel-plugin-transform-react-native-svg@6.5.1(@babel/core@7.27.4)': dependencies: '@babel/core': 7.27.4 + '@svgr/babel-plugin-transform-react-native-svg@8.1.0(@babel/core@7.27.4)': + dependencies: + '@babel/core': 7.27.4 + '@svgr/babel-plugin-transform-svg-component@6.5.1(@babel/core@7.27.4)': dependencies: '@babel/core': 7.27.4 + '@svgr/babel-plugin-transform-svg-component@8.0.0(@babel/core@7.27.4)': + dependencies: + '@babel/core': 7.27.4 + '@svgr/babel-preset@6.5.1(@babel/core@7.27.4)': dependencies: '@babel/core': 7.27.4 @@ -19017,6 +19113,18 @@ snapshots: '@svgr/babel-plugin-transform-react-native-svg': 6.5.1(@babel/core@7.27.4) '@svgr/babel-plugin-transform-svg-component': 6.5.1(@babel/core@7.27.4) + '@svgr/babel-preset@8.1.0(@babel/core@7.27.4)': + dependencies: + '@babel/core': 7.27.4 + '@svgr/babel-plugin-add-jsx-attribute': 8.0.0(@babel/core@7.27.4) + '@svgr/babel-plugin-remove-jsx-attribute': 8.0.0(@babel/core@7.27.4) + '@svgr/babel-plugin-remove-jsx-empty-expression': 8.0.0(@babel/core@7.27.4) + '@svgr/babel-plugin-replace-jsx-attribute-value': 8.0.0(@babel/core@7.27.4) + '@svgr/babel-plugin-svg-dynamic-title': 8.0.0(@babel/core@7.27.4) + '@svgr/babel-plugin-svg-em-dimensions': 8.0.0(@babel/core@7.27.4) + '@svgr/babel-plugin-transform-react-native-svg': 8.1.0(@babel/core@7.27.4) + '@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.27.4) + '@svgr/core@6.5.1': dependencies: '@babel/core': 7.27.4 @@ -19027,11 +19135,27 @@ snapshots: transitivePeerDependencies: - supports-color + '@svgr/core@8.1.0(typescript@5.8.3)': + dependencies: + '@babel/core': 7.27.4 + '@svgr/babel-preset': 8.1.0(@babel/core@7.27.4) + camelcase: 6.3.0 + cosmiconfig: 8.3.6(typescript@5.8.3) + snake-case: 3.0.4 + transitivePeerDependencies: + - supports-color + - typescript + '@svgr/hast-util-to-babel-ast@6.5.1': dependencies: '@babel/types': 7.27.6 entities: 4.5.0 + '@svgr/hast-util-to-babel-ast@8.0.0': + dependencies: + '@babel/types': 7.27.6 + entities: 4.5.0 + '@svgr/plugin-jsx@6.5.1(@svgr/core@6.5.1)': dependencies: '@babel/core': 7.27.4 @@ -19042,6 +19166,16 @@ snapshots: transitivePeerDependencies: - supports-color + '@svgr/plugin-jsx@8.1.0(@svgr/core@8.1.0(typescript@5.8.3))': + dependencies: + '@babel/core': 7.27.4 + '@svgr/babel-preset': 8.1.0(@babel/core@7.27.4) + '@svgr/core': 8.1.0(typescript@5.8.3) + '@svgr/hast-util-to-babel-ast': 8.0.0 + svg-parser: 2.0.4 + transitivePeerDependencies: + - supports-color + '@svgr/plugin-svgo@6.5.1(@svgr/core@6.5.1)': dependencies: '@svgr/core': 6.5.1 @@ -22212,6 +22346,15 @@ snapshots: path-type: 4.0.0 yaml: 1.10.2 + cosmiconfig@8.3.6(typescript@5.8.3): + dependencies: + import-fresh: 3.3.0 + js-yaml: 4.1.0 + parse-json: 5.2.0 + path-type: 4.0.0 + optionalDependencies: + typescript: 5.8.3 + cosmiconfig@9.0.0(typescript@5.8.3): dependencies: env-paths: 2.2.1 @@ -22418,7 +22561,6 @@ snapshots: dependencies: '@asamuzakjp/css-color': 3.1.5 rrweb-cssom: 0.8.0 - optional: true csstype@3.1.3: {} @@ -22485,7 +22627,6 @@ snapshots: dependencies: whatwg-mimetype: 4.0.0 whatwg-url: 14.2.0 - optional: true data-view-buffer@1.0.2: dependencies: @@ -24724,7 +24865,6 @@ snapshots: html-encoding-sniffer@4.0.0: dependencies: whatwg-encoding: 3.1.1 - optional: true html-entities@2.5.2: {} @@ -24773,7 +24913,6 @@ snapshots: debug: 4.4.0(supports-color@8.1.1) transitivePeerDependencies: - supports-color - optional: true http-proxy-middleware@2.0.6(@types/express@4.17.22): dependencies: @@ -25918,7 +26057,6 @@ snapshots: - bufferutil - supports-color - utf-8-validate - optional: true jsesc@3.0.2: {} @@ -29084,11 +29222,9 @@ snapshots: parseurl: 1.3.3 path-to-regexp: 8.2.0 - rrweb-cssom@0.7.1: - optional: true + rrweb-cssom@0.7.1: {} - rrweb-cssom@0.8.0: - optional: true + rrweb-cssom@0.8.0: {} rslog@1.2.3: {} @@ -30210,7 +30346,6 @@ snapshots: tr46@5.1.1: dependencies: punycode: 2.3.1 - optional: true tree-dump@1.0.2(tslib@2.8.1): dependencies: @@ -31120,6 +31255,17 @@ snapshots: transitivePeerDependencies: - supports-color + vite-plugin-svgr@4.3.0(rollup@4.39.0)(typescript@5.8.3)(vite@6.2.6(@types/node@22.15.29)(jiti@2.4.2)(lightningcss@1.27.0)(terser@5.39.0)(tsx@4.19.2)(yaml@2.7.1)): + dependencies: + '@rollup/pluginutils': 5.1.4(rollup@4.39.0) + '@svgr/core': 8.1.0(typescript@5.8.3) + '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.8.3)) + vite: 6.2.6(@types/node@22.15.29)(jiti@2.4.2)(lightningcss@1.27.0)(terser@5.39.0)(tsx@4.19.2)(yaml@2.7.1) + transitivePeerDependencies: + - rollup + - supports-color + - typescript + vite-plugin-vue-tracer@0.1.3(vite@6.2.6(@types/node@22.15.29)(jiti@2.4.2)(lightningcss@1.27.0)(terser@5.39.0)(tsx@4.19.2)(yaml@2.7.1))(vue@3.5.13(typescript@5.8.3)): dependencies: estree-walker: 3.0.3 @@ -31260,7 +31406,6 @@ snapshots: w3c-xmlserializer@5.0.0: dependencies: xml-name-validator: 5.0.0 - optional: true walker@1.0.8: dependencies: @@ -31432,14 +31577,12 @@ snapshots: whatwg-encoding@3.1.1: dependencies: iconv-lite: 0.6.3 - optional: true whatwg-fetch@3.6.20: {} whatwg-mimetype@3.0.0: {} - whatwg-mimetype@4.0.0: - optional: true + whatwg-mimetype@4.0.0: {} whatwg-url-without-unicode@8.0.0-3: dependencies: @@ -31456,7 +31599,6 @@ snapshots: dependencies: tr46: 5.1.1 webidl-conversions: 7.0.0 - optional: true whatwg-url@5.0.0: dependencies: @@ -31615,8 +31757,7 @@ snapshots: xml-name-validator@4.0.0: {} - xml-name-validator@5.0.0: - optional: true + xml-name-validator@5.0.0: {} xml2js@0.6.0: dependencies: From 3b9f48c5b122743129e3c70a9edff1a11d2d7040 Mon Sep 17 00:00:00 2001 From: Jacek Date: Mon, 9 Jun 2025 14:56:33 -0500 Subject: [PATCH 02/12] fix vitest coverage module version --- package.json | 2 +- .../src/ui/utils/test/createFixtures.tsx | 1 - pnpm-lock.yaml | 18 ++++++++++-------- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index cc8c188eff6..8cfa86563f1 100644 --- a/package.json +++ b/package.json @@ -83,7 +83,7 @@ "@types/react": "catalog:react", "@types/react-dom": "catalog:react", "@vitejs/plugin-react": "^4.5.1", - "@vitest/coverage-v8": "3.0.2", + "@vitest/coverage-v8": "3.0.5", "chalk": "4.1.2", "citty": "^0.1.6", "conventional-changelog-conventionalcommits": "^4.6.3", diff --git a/packages/clerk-js/src/ui/utils/test/createFixtures.tsx b/packages/clerk-js/src/ui/utils/test/createFixtures.tsx index 3fcda66ebd4..4187dfdda57 100644 --- a/packages/clerk-js/src/ui/utils/test/createFixtures.tsx +++ b/packages/clerk-js/src/ui/utils/test/createFixtures.tsx @@ -1,6 +1,5 @@ import type { ClerkOptions, ClientJSON, EnvironmentJSON, LoadedClerk } from '@clerk/types'; import { jest } from '@jest/globals'; -import React from 'react'; import { FlowMetadataProvider } from '@/ui/elements/contexts'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bbb30226754..cd7ff761ec6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -131,8 +131,8 @@ importers: specifier: ^4.5.1 version: 4.5.1(vite@6.2.6(@types/node@22.15.29)(jiti@2.4.2)(lightningcss@1.27.0)(terser@5.39.0)(tsx@4.19.2)(yaml@2.7.1)) '@vitest/coverage-v8': - specifier: 3.0.2 - version: 3.0.2(vitest@3.0.5(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@22.15.29)(jiti@2.4.2)(jsdom@24.1.3)(lightningcss@1.27.0)(msw@2.8.7(@types/node@22.15.29)(typescript@5.8.3))(terser@5.39.0)(tsx@4.19.2)(yaml@2.7.1)) + specifier: 3.0.5 + version: 3.0.5(vitest@3.0.5(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@22.15.29)(jiti@2.4.2)(jsdom@24.1.3)(lightningcss@1.27.0)(msw@2.8.7(@types/node@22.15.29)(typescript@5.8.3))(terser@5.39.0)(tsx@4.19.2)(yaml@2.7.1)) chalk: specifier: 4.1.2 version: 4.1.2 @@ -3000,7 +3000,7 @@ packages: '@expo/bunyan@4.0.1': resolution: {integrity: sha512-+Lla7nYSiHZirgK+U/uYzsLv/X+HaJienbD5AKX1UQZHYfWaP+9uuQluRB4GrEVWF0GZ7vEVp/jzaOT9k/SQlg==} - engines: {'0': node >=0.10.0} + engines: {node: '>=0.10.0'} '@expo/cli@0.22.26': resolution: {integrity: sha512-I689wc8Fn/AX7aUGiwrh3HnssiORMJtR2fpksX+JIe8Cj/EDleblYMSwRPd0025wrwOV9UN1KM/RuEt/QjCS3Q==} @@ -5547,11 +5547,11 @@ packages: vite: ^5.0.0 || ^6.0.0 vue: ^3.2.25 - '@vitest/coverage-v8@3.0.2': - resolution: {integrity: sha512-U+hZYb0FtgNDb6B3E9piAHzXXIuxuBw2cd6Lvepc9sYYY4KjgiwCBmo3Sird9ZRu3ggLpLBTfw1ZRr77ipiSfw==} + '@vitest/coverage-v8@3.0.5': + resolution: {integrity: sha512-zOOWIsj5fHh3jjGwQg+P+J1FW3s4jBu1Zqga0qW60yutsBtqEqNEJKWYh7cYn1yGD+1bdPsPdC/eL4eVK56xMg==} peerDependencies: - '@vitest/browser': 3.0.2 - vitest: 3.0.2 + '@vitest/browser': 3.0.5 + vitest: 3.0.5 peerDependenciesMeta: '@vitest/browser': optional: true @@ -11976,6 +11976,7 @@ packages: engines: {node: '>=0.6.0', teleport: '>=0.2.0'} deprecated: |- You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other. + (For a CapTP with native promises, see @endo/eventual-send and @endo/captp) qrcode-terminal@0.11.0: @@ -14236,6 +14237,7 @@ packages: vitest-environment-miniflare@2.14.4: resolution: {integrity: sha512-DzwQWdY42sVYR6aUndw9FdCtl/i0oh3NkbkQpw+xq5aYQw5eiJn5kwnKaKQEWaoBe8Cso71X2i1EJGvi1jZ2xw==} engines: {node: '>=16.13'} + deprecated: Miniflare v2 is no longer supported. Please upgrade to Miniflare v4 peerDependencies: vitest: '>=0.23.0' @@ -20565,7 +20567,7 @@ snapshots: vite: 6.2.6(@types/node@22.15.29)(jiti@2.4.2)(lightningcss@1.27.0)(terser@5.39.0)(tsx@4.19.2)(yaml@2.7.1) vue: 3.5.13(typescript@5.8.3) - '@vitest/coverage-v8@3.0.2(vitest@3.0.5(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@22.15.29)(jiti@2.4.2)(jsdom@24.1.3)(lightningcss@1.27.0)(msw@2.8.7(@types/node@22.15.29)(typescript@5.8.3))(terser@5.39.0)(tsx@4.19.2)(yaml@2.7.1))': + '@vitest/coverage-v8@3.0.5(vitest@3.0.5(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@22.15.29)(jiti@2.4.2)(jsdom@24.1.3)(lightningcss@1.27.0)(msw@2.8.7(@types/node@22.15.29)(typescript@5.8.3))(terser@5.39.0)(tsx@4.19.2)(yaml@2.7.1))': dependencies: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 1.0.2 From 576bd71b90284dff6bf6970061222d5d3cc6278a Mon Sep 17 00:00:00 2001 From: Jacek Date: Mon, 9 Jun 2025 14:58:05 -0500 Subject: [PATCH 03/12] remove unnecessary console output --- packages/clerk-js/vitest.setup.mts | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/clerk-js/vitest.setup.mts b/packages/clerk-js/vitest.setup.mts index c93f9912ce2..72bbb1619f2 100644 --- a/packages/clerk-js/vitest.setup.mts +++ b/packages/clerk-js/vitest.setup.mts @@ -93,6 +93,3 @@ if (typeof window !== 'undefined') { // }, // // ... other chrome namespaces needed // }; - -// Add any other global setup needed for your tests -console.log('ClerkJS Vitest setup complete.'); From 7a406a0c4284c41e8a8194bcb4ea27df1ea3451b Mon Sep 17 00:00:00 2001 From: Jacek Date: Mon, 9 Jun 2025 15:03:22 -0500 Subject: [PATCH 04/12] chore: empty changeset --- .changeset/chatty-wombats-rest.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changeset/chatty-wombats-rest.md diff --git a/.changeset/chatty-wombats-rest.md b/.changeset/chatty-wombats-rest.md new file mode 100644 index 00000000000..a845151cc84 --- /dev/null +++ b/.changeset/chatty-wombats-rest.md @@ -0,0 +1,2 @@ +--- +--- From 9449e5373c71f4f10e872f4d703d7aca3537d3d1 Mon Sep 17 00:00:00 2001 From: Jacek Date: Mon, 9 Jun 2025 15:10:56 -0500 Subject: [PATCH 05/12] wip --- eslint.config.mjs | 8 ++++++++ .../src/core/resources/__tests__/ExternalAccount.spec.ts | 3 ++- .../clerk-js/src/core/resources/__tests__/Session.spec.ts | 2 +- packages/clerk-js/src/ui/utils/vitest/mockHelpers.ts | 2 +- packages/clerk-js/src/ui/utils/vitest/runFakeTimers.ts | 2 +- packages/clerk-js/src/utils/__tests__/date.spec.ts | 3 ++- .../clerk-js/src/utils/__tests__/localStorage.spec.ts | 3 ++- 7 files changed, 17 insertions(+), 6 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index 079990b1443..83e6cc3b04a 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -369,6 +369,14 @@ export default tseslint.config([ 'custom-rules/no-navigate-useClerk': 'error', }, }, + { + name: 'packages/clerk-js - vitest', + files: ['packages/clerk-js/src/**/*.spec.{ts,tsx}'], + rules: { + 'jest/unbound-method': 'off', + '@typescript-eslint/unbound-method': 'off', + }, + }, { name: 'packages/expo-passkeys', files: ['packages/expo-passkeys/src/**/*'], diff --git a/packages/clerk-js/src/core/resources/__tests__/ExternalAccount.spec.ts b/packages/clerk-js/src/core/resources/__tests__/ExternalAccount.spec.ts index d19bd96cf33..9c59639cdb1 100644 --- a/packages/clerk-js/src/core/resources/__tests__/ExternalAccount.spec.ts +++ b/packages/clerk-js/src/core/resources/__tests__/ExternalAccount.spec.ts @@ -1,6 +1,7 @@ -import { BaseResource, ExternalAccount } from '../internal'; import { describe, expect, it, vi } from 'vitest'; +import { BaseResource, ExternalAccount } from '../internal'; + describe('External account', () => { it('reauthorize', async () => { const targetId = 'test_id'; diff --git a/packages/clerk-js/src/core/resources/__tests__/Session.spec.ts b/packages/clerk-js/src/core/resources/__tests__/Session.spec.ts index eb065923c14..e49c16a431d 100644 --- a/packages/clerk-js/src/core/resources/__tests__/Session.spec.ts +++ b/packages/clerk-js/src/core/resources/__tests__/Session.spec.ts @@ -3,8 +3,8 @@ import { afterEach, beforeEach, describe, expect, it, type Mock, vi } from 'vite import { eventBus } from '../../events'; import { createFapiClient } from '../../fapiClient'; -import { clerkMock, createUser, mockJwt, mockNetworkFailedFetch } from '../../vitest/fixtures'; import { SessionTokenCache } from '../../tokenCache'; +import { clerkMock, createUser, mockJwt, mockNetworkFailedFetch } from '../../vitest/fixtures'; import { BaseResource, Organization, Session } from '../internal'; const baseFapiClientOptions = { diff --git a/packages/clerk-js/src/ui/utils/vitest/mockHelpers.ts b/packages/clerk-js/src/ui/utils/vitest/mockHelpers.ts index 1ac4a244562..c635c593897 100644 --- a/packages/clerk-js/src/ui/utils/vitest/mockHelpers.ts +++ b/packages/clerk-js/src/ui/utils/vitest/mockHelpers.ts @@ -1,7 +1,7 @@ // import { jest } from '@jest/globals'; import type { LoadedClerk } from '@clerk/types'; import type { ActiveSessionResource } from '@clerk/types'; -import { vi, type Mocked } from 'vitest'; +import { type Mocked, vi } from 'vitest'; import type { RouteContextValue } from '../../router'; diff --git a/packages/clerk-js/src/ui/utils/vitest/runFakeTimers.ts b/packages/clerk-js/src/ui/utils/vitest/runFakeTimers.ts index 7766954fb0d..f14da9f671d 100644 --- a/packages/clerk-js/src/ui/utils/vitest/runFakeTimers.ts +++ b/packages/clerk-js/src/ui/utils/vitest/runFakeTimers.ts @@ -1,6 +1,6 @@ -import { vi } from 'vitest'; // import { jest } from '@jest/globals'; import { act } from '@testing-library/react'; +import { vi } from 'vitest'; type WithAct = (fn: T) => T; const withAct = ((fn: any) => diff --git a/packages/clerk-js/src/utils/__tests__/date.spec.ts b/packages/clerk-js/src/utils/__tests__/date.spec.ts index 33c8e28a069..3c1ff3a3ca7 100644 --- a/packages/clerk-js/src/utils/__tests__/date.spec.ts +++ b/packages/clerk-js/src/utils/__tests__/date.spec.ts @@ -1,6 +1,7 @@ -import { unixEpochToDate } from '../date'; import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'; +import { unixEpochToDate } from '../date'; + describe('date utilities', () => { beforeAll(() => { vi.useFakeTimers(); diff --git a/packages/clerk-js/src/utils/__tests__/localStorage.spec.ts b/packages/clerk-js/src/utils/__tests__/localStorage.spec.ts index c86fb12d587..64ad8dd0e2f 100644 --- a/packages/clerk-js/src/utils/__tests__/localStorage.spec.ts +++ b/packages/clerk-js/src/utils/__tests__/localStorage.spec.ts @@ -1,6 +1,7 @@ -import { SafeLocalStorage } from '../localStorage'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { SafeLocalStorage } from '../localStorage'; + describe('SafeLocalStorage', () => { let mockStorage: { [key: string]: string } = {}; From d72f1bdaf5c2ce8bc0b2a2f769c748e040031e82 Mon Sep 17 00:00:00 2001 From: Jacek Date: Mon, 9 Jun 2025 19:14:02 -0500 Subject: [PATCH 06/12] Coderabbit suggestions --- packages/clerk-js/src/core/__tests__/fapiClient.spec.ts | 3 +++ packages/clerk-js/src/ui/common/__tests__/redirects.spec.ts | 2 +- packages/clerk-js/src/ui/utils/vitest/fixtureHelpers.ts | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/clerk-js/src/core/__tests__/fapiClient.spec.ts b/packages/clerk-js/src/core/__tests__/fapiClient.spec.ts index 585917f4533..728b259fe94 100644 --- a/packages/clerk-js/src/core/__tests__/fapiClient.spec.ts +++ b/packages/clerk-js/src/core/__tests__/fapiClient.spec.ts @@ -25,6 +25,8 @@ type RecursivePartial = { [P in keyof T]?: RecursivePartial; }; +const originalFetch = global.fetch; + // @ts-ignore -- We don't need to fully satisfy the fetch types for the sake of this mock global.fetch = vi.fn(() => Promise.resolve>({ @@ -61,6 +63,7 @@ beforeEach(() => { afterAll(() => { window.location = oldWindowLocation; delete window.Clerk; + global.fetch = originalFetch; }); describe('buildUrl(options)', () => { diff --git a/packages/clerk-js/src/ui/common/__tests__/redirects.spec.ts b/packages/clerk-js/src/ui/common/__tests__/redirects.spec.ts index bf4061a08b0..c9be5720603 100644 --- a/packages/clerk-js/src/ui/common/__tests__/redirects.spec.ts +++ b/packages/clerk-js/src/ui/common/__tests__/redirects.spec.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; -import { buildSSOCallbackURL, buildVerificationRedirectUrl, buildVerificationRedirectUrl } from '../redirects'; +import { buildSSOCallbackURL, buildVerificationRedirectUrl } from '../redirects'; describe('buildVerificationRedirectUrl(routing, baseUrl)', () => { it('defaults to hash based routing strategy on empty routing', function () { diff --git a/packages/clerk-js/src/ui/utils/vitest/fixtureHelpers.ts b/packages/clerk-js/src/ui/utils/vitest/fixtureHelpers.ts index 3ad508cdf05..567bf8ada6e 100644 --- a/packages/clerk-js/src/ui/utils/vitest/fixtureHelpers.ts +++ b/packages/clerk-js/src/ui/utils/vitest/fixtureHelpers.ts @@ -340,7 +340,7 @@ const createOrganizationSettingsFixtureHelpers = (environment: EnvironmentJSON) const withOrganizationDomains = (modes?: OrganizationEnrollmentMode[], defaultRole?: string) => { os.domains.enabled = true; - os.domains.enrollment_modes = modes || ['automatic_invitation', 'automatic_invitation', 'manual_invitation']; + os.domains.enrollment_modes = modes || ['automatic_invitation', 'manual_invitation']; os.domains.default_role = defaultRole ?? null; }; return { withOrganizations, withMaxAllowedMemberships, withOrganizationDomains, withForceOrganizationSelection }; From d07d3ce3b1d1d2373685641baede8b76cdc62f3e Mon Sep 17 00:00:00 2001 From: Jacek Date: Mon, 9 Jun 2025 19:21:45 -0500 Subject: [PATCH 07/12] wip --- .../core/resources/__tests__/Client.spec.ts | 13 ++++++++++- .../src/ui/utils/vitest/runFakeTimers.ts | 22 +++++++++---------- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/packages/clerk-js/src/core/resources/__tests__/Client.spec.ts b/packages/clerk-js/src/core/resources/__tests__/Client.spec.ts index a43096aed4c..00bc8d9e29f 100644 --- a/packages/clerk-js/src/core/resources/__tests__/Client.spec.ts +++ b/packages/clerk-js/src/core/resources/__tests__/Client.spec.ts @@ -1,9 +1,20 @@ import type { ClientJSON, ClientJSONSnapshot } from '@clerk/types'; -import { describe, expect, it, vi } from 'vitest'; +import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'; import { createSession, createSignIn, createSignUp, createUser } from '../../vitest/fixtures'; import { BaseResource, Client } from '../internal'; +const FIXED_DATE = new Date('2025-01-01T00:00:00Z'); + +beforeAll(() => { + vi.useFakeTimers(); + vi.setSystemTime(FIXED_DATE); +}); + +afterAll(() => { + vi.useRealTimers(); +}); + describe('Client Singleton', () => { describe('__internal_sendCaptchaToken', () => { it('sends captcha token', async () => { diff --git a/packages/clerk-js/src/ui/utils/vitest/runFakeTimers.ts b/packages/clerk-js/src/ui/utils/vitest/runFakeTimers.ts index f14da9f671d..e5e2365184d 100644 --- a/packages/clerk-js/src/ui/utils/vitest/runFakeTimers.ts +++ b/packages/clerk-js/src/ui/utils/vitest/runFakeTimers.ts @@ -21,16 +21,16 @@ const createFakeTimersHelpers = () => { type FakeTimersHelpers = ReturnType; type RunFakeTimersCallback = (timers: FakeTimersHelpers) => void | Promise; -export const runFakeTimers = >( - cb: T, -): R extends Promise ? Promise : void => { +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(); - const res = cb(createFakeTimersHelpers()); - if (res && 'then' in res) { - // @ts-expect-error - return res.finally(() => vi.useRealTimers()); + try { + const result = cb(createFakeTimersHelpers()); + if (result instanceof Promise) { + await result; + } + } finally { + vi.useRealTimers(); } - vi.useRealTimers(); - // @ts-ignore - return; -}; +} From b9511edeaf1cb5e315d7cfe1a5d850ff95b9f2b8 Mon Sep 17 00:00:00 2001 From: Jacek Date: Mon, 9 Jun 2025 19:22:49 -0500 Subject: [PATCH 08/12] wip --- .../clerk-js/src/ui/utils/vitest/fixtures.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/packages/clerk-js/src/ui/utils/vitest/fixtures.ts b/packages/clerk-js/src/ui/utils/vitest/fixtures.ts index 9eb97488214..40858b78c88 100644 --- a/packages/clerk-js/src/ui/utils/vitest/fixtures.ts +++ b/packages/clerk-js/src/ui/utils/vitest/fixtures.ts @@ -151,11 +151,19 @@ const createBaseUserSettings = (): UserSettingsJSON => { verify_at_sign_up: false, }, ]), - ) as any as UserSettingsJSON['attributes']; + ) as UserSettingsJSON['attributes']; - const socialConfig = Object.fromEntries( - socials.map(social => [social, { enabled: false, required: false, authenticatable: false, strategy: social }]), - ) as any as UserSettingsJSON['social']; + const socialConfig: UserSettingsJSON['social'] = Object.fromEntries( + socials.map(social => [ + social, + { + enabled: false, + required: false, + authenticatable: false, + strategy: social, + }, + ]), + ); const passwordSettingsConfig = { allowed_special_characters: '', From 0f148597bdcb3b21c8c983d9e557a13983a07f10 Mon Sep 17 00:00:00 2001 From: Jacek Date: Mon, 9 Jun 2025 19:25:37 -0500 Subject: [PATCH 09/12] wip --- packages/clerk-js/src/ui/utils/vitest/mockHelpers.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/clerk-js/src/ui/utils/vitest/mockHelpers.ts b/packages/clerk-js/src/ui/utils/vitest/mockHelpers.ts index c635c593897..8be7b9ae20d 100644 --- a/packages/clerk-js/src/ui/utils/vitest/mockHelpers.ts +++ b/packages/clerk-js/src/ui/utils/vitest/mockHelpers.ts @@ -22,8 +22,8 @@ type MockMap = { const mockProp = (obj: T, k: keyof T, mocks?: MockMap) => { if (typeof obj[k] === 'function') { - // @ts-ignore - Assume it's assignable for mocking - obj[k] = mocks?.[k] ?? vi.fn(); + const mockFn = mocks?.[k] ?? vi.fn(); + (obj[k] as unknown as ReturnType) = mockFn; } }; From 9dbcdf3439b3d6b321310d87121088b567970e6e Mon Sep 17 00:00:00 2001 From: Jacek Date: Mon, 9 Jun 2025 19:32:37 -0500 Subject: [PATCH 10/12] delete unused file --- packages/clerk-js/src/__tests__/mocks/svgMock.tsx | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 packages/clerk-js/src/__tests__/mocks/svgMock.tsx diff --git a/packages/clerk-js/src/__tests__/mocks/svgMock.tsx b/packages/clerk-js/src/__tests__/mocks/svgMock.tsx deleted file mode 100644 index 87ea82c61fd..00000000000 --- a/packages/clerk-js/src/__tests__/mocks/svgMock.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import React from 'react'; - -// A simple span component to mock SVG imports in Vitest -const SvgMock = React.forwardRef>((props, ref) => ( - -)); - -export const ReactComponent = SvgMock; -export default SvgMock; From 7c53d5288d4ebc1db154f029380df03f26afb232 Mon Sep 17 00:00:00 2001 From: Jacek Radko Date: Tue, 10 Jun 2025 21:35:11 -0500 Subject: [PATCH 11/12] Update packages/clerk-js/vitest.config.mts Co-authored-by: Tom Milewski --- packages/clerk-js/vitest.config.mts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/clerk-js/vitest.config.mts b/packages/clerk-js/vitest.config.mts index 1164a1c70a4..4f5ccc1a28f 100644 --- a/packages/clerk-js/vitest.config.mts +++ b/packages/clerk-js/vitest.config.mts @@ -39,8 +39,10 @@ export default defineConfig({ 'src/**/*.d.ts', 'src/**/index.ts', 'src/**/index.browser.ts', + 'src/**/index.chips.browser.ts', 'src/**/index.headless.ts', 'src/**/index.headless.browser.ts', + 'src/**/index.legacy.browser.ts', 'src/**/coverage/**', 'src/**/dist/**', 'src/**/node_modules/**', From 684c7bf543cd757de79baafcd271e540430fd77b Mon Sep 17 00:00:00 2001 From: Jacek Date: Wed, 11 Jun 2025 11:16:45 -0500 Subject: [PATCH 12/12] remove unneeded deps --- packages/clerk-js/package.json | 1 - packages/clerk-js/vitest.config.mts | 3 +- pnpm-lock.yaml | 171 ---------------------------- 3 files changed, 1 insertion(+), 174 deletions(-) diff --git a/packages/clerk-js/package.json b/packages/clerk-js/package.json index 43e862ed049..36c01bbcb9c 100644 --- a/packages/clerk-js/package.json +++ b/packages/clerk-js/package.json @@ -91,7 +91,6 @@ "@types/cloudflare-turnstile": "^0.2.2", "@types/webpack-env": "^1.18.8", "jsdom": "^24.1.1", - "vite-plugin-svgr": "^4.2.0", "webpack-merge": "^5.10.0" }, "peerDependencies": { diff --git a/packages/clerk-js/vitest.config.mts b/packages/clerk-js/vitest.config.mts index 4f5ccc1a28f..542579f6c7d 100644 --- a/packages/clerk-js/vitest.config.mts +++ b/packages/clerk-js/vitest.config.mts @@ -1,6 +1,5 @@ import react from '@vitejs/plugin-react'; import { resolve } from 'path'; -import svgr from 'vite-plugin-svgr'; import { defineConfig } from 'vitest/config'; function viteSvgMockPlugin() { @@ -23,7 +22,7 @@ function viteSvgMockPlugin() { } export default defineConfig({ - plugins: [react({ jsxRuntime: 'automatic', jsxImportSource: '@emotion/react' }), svgr(), viteSvgMockPlugin()], + plugins: [react({ jsxRuntime: 'automatic', jsxImportSource: '@emotion/react' }), viteSvgMockPlugin()], define: { __BUILD_VARIANT_CHIPS__: JSON.stringify(false), __PKG_NAME__: JSON.stringify('@clerk/clerk-js'), diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f4a2982ea4a..9707e66b295 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -531,9 +531,6 @@ importers: jsdom: specifier: ^24.1.1 version: 24.1.3 - vite-plugin-svgr: - specifier: ^4.2.0 - version: 4.3.0(rollup@4.39.0)(typescript@5.8.3)(vite@6.2.6(@types/node@22.15.29)(jiti@2.4.2)(lightningcss@1.27.0)(terser@5.39.0)(tsx@4.19.2)(yaml@2.7.1)) webpack-merge: specifier: ^5.10.0 version: 5.10.0 @@ -3476,82 +3473,66 @@ packages: '@miniflare/cache@2.14.4': resolution: {integrity: sha512-ayzdjhcj+4mjydbNK7ZGDpIXNliDbQY4GPcY2KrYw0v1OSUdj5kZUkygD09fqoGRfAks0d91VelkyRsAXX8FQA==} engines: {node: '>=16.13'} - deprecated: Miniflare v2 is no longer supported. Please upgrade to Miniflare v4 '@miniflare/core@2.14.4': resolution: {integrity: sha512-FMmZcC1f54YpF4pDWPtdQPIO8NXfgUxCoR9uyrhxKJdZu7M6n8QKopPVNuaxR40jcsdxb7yKoQoFWnHfzJD9GQ==} engines: {node: '>=16.13'} - deprecated: Miniflare v2 is no longer supported. Please upgrade to Miniflare v4 '@miniflare/d1@2.14.4': resolution: {integrity: sha512-pMBVq9XWxTDdm+RRCkfXZP+bREjPg1JC8s8C0JTovA9OGmLQXqGTnFxIaS9vf1d8k3uSUGhDzPTzHr0/AUW1gA==} engines: {node: '>=16.7'} - deprecated: Miniflare v2 is no longer supported. Please upgrade to Miniflare v4 '@miniflare/durable-objects@2.14.4': resolution: {integrity: sha512-+JrmHP6gHHrjxV8S3axVw5lGHLgqmAGdcO/1HJUPswAyJEd3Ah2YnKhpo+bNmV4RKJCtEq9A2hbtVjBTD2YzwA==} engines: {node: '>=16.13'} - deprecated: Miniflare v2 is no longer supported. Please upgrade to Miniflare v4 '@miniflare/html-rewriter@2.14.4': resolution: {integrity: sha512-GB/vZn7oLbnhw+815SGF+HU5EZqSxbhIa3mu2L5MzZ2q5VOD5NHC833qG8c2GzDPhIaZ99ITY+ZJmbR4d+4aNQ==} engines: {node: '>=16.13'} - deprecated: Miniflare v2 is no longer supported. Please upgrade to Miniflare v4 '@miniflare/kv@2.14.4': resolution: {integrity: sha512-QlERH0Z+klwLg0xw+/gm2yC34Nnr/I0GcQ+ASYqXeIXBwjqOtMBa3YVQnocaD+BPy/6TUtSpOAShHsEj76R2uw==} engines: {node: '>=16.13'} - deprecated: Miniflare v2 is no longer supported. Please upgrade to Miniflare v4 '@miniflare/queues@2.14.4': resolution: {integrity: sha512-aXQ5Ik8Iq1KGMBzGenmd6Js/jJgqyYvjom95/N9GptCGpiVWE5F0XqC1SL5rCwURbHN+aWY191o8XOFyY2nCUA==} engines: {node: '>=16.7'} - deprecated: Miniflare v2 is no longer supported. Please upgrade to Miniflare v4 '@miniflare/r2@2.14.4': resolution: {integrity: sha512-4ctiZWh7Ty7LB3brUjmbRiGMqwyDZgABYaczDtUidblo2DxX4JZPnJ/ZAyxMPNJif32kOJhcg6arC2hEthR9Sw==} engines: {node: '>=16.13'} - deprecated: Miniflare v2 is no longer supported. Please upgrade to Miniflare v4 '@miniflare/runner-vm@2.14.4': resolution: {integrity: sha512-Nog0bB9SVhPbZAkTWfO4lpLAUsBXKEjlb4y+y66FJw77mPlmPlVdpjElCvmf8T3VN/pqh83kvELGM+/fucMf4g==} engines: {node: '>=16.13'} - deprecated: Miniflare v2 is no longer supported. Please upgrade to Miniflare v4 '@miniflare/shared-test-environment@2.14.4': resolution: {integrity: sha512-FdU2/8wEd00vIu+MfofLiHcfZWz+uCbE2VTL85KpyYfBsNGAbgRtzFMpOXdoXLqQfRu6MBiRwWpb2FbMrBzi7g==} engines: {node: '>=16.13'} - deprecated: Miniflare v2 is no longer supported. Please upgrade to Miniflare v4 '@miniflare/shared@2.14.4': resolution: {integrity: sha512-upl4RSB3hyCnITOFmRZjJj4A72GmkVrtfZTilkdq5Qe5TTlzsjVeDJp7AuNUM9bM8vswRo+N5jOiot6O4PVwwQ==} engines: {node: '>=16.13'} - deprecated: Miniflare v2 is no longer supported. Please upgrade to Miniflare v4 '@miniflare/sites@2.14.4': resolution: {integrity: sha512-O5npWopi+fw9W9Ki0gy99nuBbgDva/iXy8PDC4dAXDB/pz45nISDqldabk0rL2t4W2+lY6LXKzdOw+qJO1GQTA==} engines: {node: '>=16.13'} - deprecated: Miniflare v2 is no longer supported. Please upgrade to Miniflare v4 '@miniflare/storage-file@2.14.4': resolution: {integrity: sha512-JxcmX0hXf4cB0cC9+s6ZsgYCq+rpyUKRPCGzaFwymWWplrO3EjPVxKCcMxG44jsdgsII6EZihYUN2J14wwCT7A==} engines: {node: '>=16.13'} - deprecated: Miniflare v2 is no longer supported. Please upgrade to Miniflare v4 '@miniflare/storage-memory@2.14.4': resolution: {integrity: sha512-9jB5BqNkMZ3SFjbPFeiVkLi1BuSahMhc/W1Y9H0W89qFDrrD+z7EgRgDtHTG1ZRyi9gIlNtt9qhkO1B6W2qb2A==} engines: {node: '>=16.13'} - deprecated: Miniflare v2 is no longer supported. Please upgrade to Miniflare v4 '@miniflare/watcher@2.14.4': resolution: {integrity: sha512-PYn05ET2USfBAeXF6NZfWl0O32KVyE8ncQ/ngysrh3hoIV7l3qGGH7ubeFx+D8VWQ682qYhwGygUzQv2j1tGGg==} engines: {node: '>=16.13'} - deprecated: Miniflare v2 is no longer supported. Please upgrade to Miniflare v4 '@miniflare/web-sockets@2.14.4': resolution: {integrity: sha512-stTxvLdJ2IcGOs76AnvGYAzGvx8JvQPRxC5DW0P5zdAAnhL33noqb5LKdPt3P37BKp9FzBKZHuihQI9oVqwm0g==} engines: {node: '>=16.13'} - deprecated: Miniflare v2 is no longer supported. Please upgrade to Miniflare v4 '@modelcontextprotocol/sdk@1.7.0': resolution: {integrity: sha512-IYPe/FLpvF3IZrd/f5p5ffmWhMc3aEMuM2wGJASDqC2Ge7qatVCdbfPx3n/5xFeb19xN0j/911M2AaFuircsWA==} @@ -4590,12 +4571,6 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@svgr/babel-plugin-add-jsx-attribute@8.0.0': - resolution: {integrity: sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==} - engines: {node: '>=14'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@svgr/babel-plugin-remove-jsx-attribute@8.0.0': resolution: {integrity: sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==} engines: {node: '>=14'} @@ -4614,100 +4589,50 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0': - resolution: {integrity: sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==} - engines: {node: '>=14'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@svgr/babel-plugin-svg-dynamic-title@6.5.1': resolution: {integrity: sha512-FwOEi0Il72iAzlkaHrlemVurgSQRDFbk0OC8dSvD5fSBPHltNh7JtLsxmZUhjYBZo2PpcU/RJvvi6Q0l7O7ogw==} engines: {node: '>=10'} peerDependencies: '@babel/core': ^7.0.0-0 - '@svgr/babel-plugin-svg-dynamic-title@8.0.0': - resolution: {integrity: sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==} - engines: {node: '>=14'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@svgr/babel-plugin-svg-em-dimensions@6.5.1': resolution: {integrity: sha512-gWGsiwjb4tw+ITOJ86ndY/DZZ6cuXMNE/SjcDRg+HLuCmwpcjOktwRF9WgAiycTqJD/QXqL2f8IzE2Rzh7aVXA==} engines: {node: '>=10'} peerDependencies: '@babel/core': ^7.0.0-0 - '@svgr/babel-plugin-svg-em-dimensions@8.0.0': - resolution: {integrity: sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==} - engines: {node: '>=14'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@svgr/babel-plugin-transform-react-native-svg@6.5.1': resolution: {integrity: sha512-2jT3nTayyYP7kI6aGutkyfJ7UMGtuguD72OjeGLwVNyfPRBD8zQthlvL+fAbAKk5n9ZNcvFkp/b1lZ7VsYqVJg==} engines: {node: '>=10'} peerDependencies: '@babel/core': ^7.0.0-0 - '@svgr/babel-plugin-transform-react-native-svg@8.1.0': - resolution: {integrity: sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==} - engines: {node: '>=14'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@svgr/babel-plugin-transform-svg-component@6.5.1': resolution: {integrity: sha512-a1p6LF5Jt33O3rZoVRBqdxL350oge54iZWHNI6LJB5tQ7EelvD/Mb1mfBiZNAan0dt4i3VArkFRjA4iObuNykQ==} engines: {node: '>=12'} peerDependencies: '@babel/core': ^7.0.0-0 - '@svgr/babel-plugin-transform-svg-component@8.0.0': - resolution: {integrity: sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==} - engines: {node: '>=12'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@svgr/babel-preset@6.5.1': resolution: {integrity: sha512-6127fvO/FF2oi5EzSQOAjo1LE3OtNVh11R+/8FXa+mHx1ptAaS4cknIjnUA7e6j6fwGGJ17NzaTJFUwOV2zwCw==} engines: {node: '>=10'} peerDependencies: '@babel/core': ^7.0.0-0 - '@svgr/babel-preset@8.1.0': - resolution: {integrity: sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==} - engines: {node: '>=14'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@svgr/core@6.5.1': resolution: {integrity: sha512-/xdLSWxK5QkqG524ONSjvg3V/FkNyCv538OIBdQqPNaAta3AsXj/Bd2FbvR87yMbXO2hFSWiAe/Q6IkVPDw+mw==} engines: {node: '>=10'} - '@svgr/core@8.1.0': - resolution: {integrity: sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==} - engines: {node: '>=14'} - '@svgr/hast-util-to-babel-ast@6.5.1': resolution: {integrity: sha512-1hnUxxjd83EAxbL4a0JDJoD3Dao3hmjvyvyEV8PzWmLK3B9m9NPlW7GKjFyoWE8nM7HnXzPcmmSyOW8yOddSXw==} engines: {node: '>=10'} - '@svgr/hast-util-to-babel-ast@8.0.0': - resolution: {integrity: sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==} - engines: {node: '>=14'} - '@svgr/plugin-jsx@6.5.1': resolution: {integrity: sha512-+UdQxI3jgtSjCykNSlEMuy1jSRQlGC7pqBCPvkG/2dATdWo082zHTTK3uhnAju2/6XpE6B5mZ3z4Z8Ns01S8Gw==} engines: {node: '>=10'} peerDependencies: '@svgr/core': ^6.0.0 - '@svgr/plugin-jsx@8.1.0': - resolution: {integrity: sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==} - engines: {node: '>=14'} - peerDependencies: - '@svgr/core': '*' - '@svgr/plugin-svgo@6.5.1': resolution: {integrity: sha512-omvZKf8ixP9z6GWgwbtmP9qQMPX4ODXi+wzbVZgomNFsUIlHA1sf4fThdwTWSsZGgvGAG6yE+b/F5gWUkcZ/iQ==} engines: {node: '>=10'} @@ -7036,15 +6961,6 @@ packages: resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} engines: {node: '>=10'} - cosmiconfig@8.3.6: - resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==} - engines: {node: '>=14'} - peerDependencies: - typescript: '>=4.9.5' - peerDependenciesMeta: - typescript: - optional: true - cosmiconfig@9.0.0: resolution: {integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==} engines: {node: '>=14'} @@ -14151,11 +14067,6 @@ packages: '@nuxt/kit': optional: true - vite-plugin-svgr@4.3.0: - resolution: {integrity: sha512-Jy9qLB2/PyWklpYy0xk0UU3TlU0t2UMpJXZvf+hWII1lAmRHrOUKi11Uw8N3rxoNk7atZNYO3pR3vI1f7oi+6w==} - peerDependencies: - vite: '>=2.6.0' - vite-plugin-vue-tracer@0.1.3: resolution: {integrity: sha512-+fN6oo0//dwZP9Ax9gRKeUroCqpQ43P57qlWgL0ljCIxAs+Rpqn/L4anIPZPgjDPga5dZH+ZJsshbF0PNJbm3Q==} peerDependencies: @@ -19067,10 +18978,6 @@ snapshots: dependencies: '@babel/core': 7.27.4 - '@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.27.4)': - dependencies: - '@babel/core': 7.27.4 - '@svgr/babel-plugin-remove-jsx-attribute@8.0.0(@babel/core@7.27.4)': dependencies: '@babel/core': 7.27.4 @@ -19083,42 +18990,22 @@ snapshots: dependencies: '@babel/core': 7.27.4 - '@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0(@babel/core@7.27.4)': - dependencies: - '@babel/core': 7.27.4 - '@svgr/babel-plugin-svg-dynamic-title@6.5.1(@babel/core@7.27.4)': dependencies: '@babel/core': 7.27.4 - '@svgr/babel-plugin-svg-dynamic-title@8.0.0(@babel/core@7.27.4)': - dependencies: - '@babel/core': 7.27.4 - '@svgr/babel-plugin-svg-em-dimensions@6.5.1(@babel/core@7.27.4)': dependencies: '@babel/core': 7.27.4 - '@svgr/babel-plugin-svg-em-dimensions@8.0.0(@babel/core@7.27.4)': - dependencies: - '@babel/core': 7.27.4 - '@svgr/babel-plugin-transform-react-native-svg@6.5.1(@babel/core@7.27.4)': dependencies: '@babel/core': 7.27.4 - '@svgr/babel-plugin-transform-react-native-svg@8.1.0(@babel/core@7.27.4)': - dependencies: - '@babel/core': 7.27.4 - '@svgr/babel-plugin-transform-svg-component@6.5.1(@babel/core@7.27.4)': dependencies: '@babel/core': 7.27.4 - '@svgr/babel-plugin-transform-svg-component@8.0.0(@babel/core@7.27.4)': - dependencies: - '@babel/core': 7.27.4 - '@svgr/babel-preset@6.5.1(@babel/core@7.27.4)': dependencies: '@babel/core': 7.27.4 @@ -19131,18 +19018,6 @@ snapshots: '@svgr/babel-plugin-transform-react-native-svg': 6.5.1(@babel/core@7.27.4) '@svgr/babel-plugin-transform-svg-component': 6.5.1(@babel/core@7.27.4) - '@svgr/babel-preset@8.1.0(@babel/core@7.27.4)': - dependencies: - '@babel/core': 7.27.4 - '@svgr/babel-plugin-add-jsx-attribute': 8.0.0(@babel/core@7.27.4) - '@svgr/babel-plugin-remove-jsx-attribute': 8.0.0(@babel/core@7.27.4) - '@svgr/babel-plugin-remove-jsx-empty-expression': 8.0.0(@babel/core@7.27.4) - '@svgr/babel-plugin-replace-jsx-attribute-value': 8.0.0(@babel/core@7.27.4) - '@svgr/babel-plugin-svg-dynamic-title': 8.0.0(@babel/core@7.27.4) - '@svgr/babel-plugin-svg-em-dimensions': 8.0.0(@babel/core@7.27.4) - '@svgr/babel-plugin-transform-react-native-svg': 8.1.0(@babel/core@7.27.4) - '@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.27.4) - '@svgr/core@6.5.1': dependencies: '@babel/core': 7.27.4 @@ -19153,27 +19028,11 @@ snapshots: transitivePeerDependencies: - supports-color - '@svgr/core@8.1.0(typescript@5.8.3)': - dependencies: - '@babel/core': 7.27.4 - '@svgr/babel-preset': 8.1.0(@babel/core@7.27.4) - camelcase: 6.3.0 - cosmiconfig: 8.3.6(typescript@5.8.3) - snake-case: 3.0.4 - transitivePeerDependencies: - - supports-color - - typescript - '@svgr/hast-util-to-babel-ast@6.5.1': dependencies: '@babel/types': 7.27.6 entities: 4.5.0 - '@svgr/hast-util-to-babel-ast@8.0.0': - dependencies: - '@babel/types': 7.27.6 - entities: 4.5.0 - '@svgr/plugin-jsx@6.5.1(@svgr/core@6.5.1)': dependencies: '@babel/core': 7.27.4 @@ -19184,16 +19043,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@svgr/plugin-jsx@8.1.0(@svgr/core@8.1.0(typescript@5.8.3))': - dependencies: - '@babel/core': 7.27.4 - '@svgr/babel-preset': 8.1.0(@babel/core@7.27.4) - '@svgr/core': 8.1.0(typescript@5.8.3) - '@svgr/hast-util-to-babel-ast': 8.0.0 - svg-parser: 2.0.4 - transitivePeerDependencies: - - supports-color - '@svgr/plugin-svgo@6.5.1(@svgr/core@6.5.1)': dependencies: '@svgr/core': 6.5.1 @@ -22364,15 +22213,6 @@ snapshots: path-type: 4.0.0 yaml: 1.10.2 - cosmiconfig@8.3.6(typescript@5.8.3): - dependencies: - import-fresh: 3.3.0 - js-yaml: 4.1.0 - parse-json: 5.2.0 - path-type: 4.0.0 - optionalDependencies: - typescript: 5.8.3 - cosmiconfig@9.0.0(typescript@5.8.3): dependencies: env-paths: 2.2.1 @@ -31273,17 +31113,6 @@ snapshots: transitivePeerDependencies: - supports-color - vite-plugin-svgr@4.3.0(rollup@4.39.0)(typescript@5.8.3)(vite@6.2.6(@types/node@22.15.29)(jiti@2.4.2)(lightningcss@1.27.0)(terser@5.39.0)(tsx@4.19.2)(yaml@2.7.1)): - dependencies: - '@rollup/pluginutils': 5.1.4(rollup@4.39.0) - '@svgr/core': 8.1.0(typescript@5.8.3) - '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.8.3)) - vite: 6.2.6(@types/node@22.15.29)(jiti@2.4.2)(lightningcss@1.27.0)(terser@5.39.0)(tsx@4.19.2)(yaml@2.7.1) - transitivePeerDependencies: - - rollup - - supports-color - - typescript - vite-plugin-vue-tracer@0.1.3(vite@6.2.6(@types/node@22.15.29)(jiti@2.4.2)(lightningcss@1.27.0)(terser@5.39.0)(tsx@4.19.2)(yaml@2.7.1))(vue@3.5.13(typescript@5.8.3)): dependencies: estree-walker: 3.0.3