diff --git a/.changeset/breezy-flowers-float.md b/.changeset/breezy-flowers-float.md new file mode 100644 index 00000000000..a845151cc84 --- /dev/null +++ b/.changeset/breezy-flowers-float.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/packages/shared/customJSDOMEnvironment.ts b/packages/shared/customJSDOMEnvironment.ts deleted file mode 100644 index 8259ab4bb68..00000000000 --- a/packages/shared/customJSDOMEnvironment.ts +++ /dev/null @@ -1,9 +0,0 @@ -import JSDOMEnvironment from 'jest-environment-jsdom'; - -export default class CustomJSDOMEnvironment extends JSDOMEnvironment { - constructor(...args: ConstructorParameters) { - super(...args); - - this.global.Response = Response; - } -} diff --git a/packages/shared/jest.config.js b/packages/shared/jest.config.js deleted file mode 100644 index ccd6fafeba2..00000000000 --- a/packages/shared/jest.config.js +++ /dev/null @@ -1,28 +0,0 @@ -const { name } = require('./package.json'); -const { version: clerkJsVersion } = require('../clerk-js/package.json'); - -/** @type {import('ts-jest').JestConfigWithTsJest} */ -const config = { - displayName: name.replace('@clerk', ''), - injectGlobals: true, - - testEnvironment: './customJSDOMEnvironment.ts', - roots: ['/src'], - setupFiles: ['./jest.setup.ts'], - testRegex: ['/src/.*.test.[jt]sx?$'], - - collectCoverage: false, - coverageProvider: 'v8', - coverageDirectory: 'coverage', - - moduleDirectories: ['node_modules', '/src'], - transform: { - '^.+\\.m?tsx?$': ['ts-jest', { tsconfig: 'tsconfig.test.json' }], - }, - - globals: { - JS_PACKAGE_VERSION: clerkJsVersion, - }, -}; - -module.exports = config; diff --git a/packages/shared/jest.setup.ts b/packages/shared/jest.setup.ts deleted file mode 100644 index 82f8e2098c3..00000000000 --- a/packages/shared/jest.setup.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { webcrypto } from 'node:crypto'; - -import { TextDecoder, TextEncoder } from 'util'; - -const navigatorMock = {}; - -Object.defineProperty(navigatorMock, 'userAgent', { - get() { - return 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/109.0'; - }, - configurable: true, -}); - -Object.defineProperty(navigatorMock, 'webdriver', { - get() { - return false; - }, - configurable: true, -}); - -Object.defineProperty(navigatorMock, 'onLine', { - get() { - return true; - }, - configurable: true, -}); - -Object.defineProperty(navigatorMock, 'connection', { - get() { - return { downlink: 10, rtt: 100 }; - }, - configurable: true, -}); - -Object.defineProperty(global.window, 'navigator', { - value: navigatorMock, - writable: true, -}); - -// polyfill TextDecoder, TextEncoder for jsdom >= 16 -Object.assign(global, { TextDecoder, TextEncoder }); - -// polyfill using webcrypto.subtle to fix issue with missing crypto.subtle -// @ts-ignore -globalThis.crypto.subtle = webcrypto.subtle; diff --git a/packages/shared/package.json b/packages/shared/package.json index e5b4262045e..fea8b58ec42 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -148,10 +148,9 @@ "lint:attw": "attw --pack . --profile node16", "lint:publint": "publint", "publish:local": "pnpm yalc push --replace --sig", - "test": "jest && vitest", - "test:cache:clear": "jest --clearCache --useStderr", - "test:ci": "jest --maxWorkers=70%", - "test:coverage": "jest --collectCoverage && open coverage/lcov-report/index.html" + "test": "vitest", + "test:ci": "vitest --maxWorkers=70%", + "test:coverage": "vitest --collectCoverage && open coverage/lcov-report/index.html" }, "dependencies": { "@clerk/types": "workspace:^", diff --git a/packages/shared/src/__tests__/apiUrlFromPublishableKey.test.ts b/packages/shared/src/__tests__/apiUrlFromPublishableKey.spec.ts similarity index 95% rename from packages/shared/src/__tests__/apiUrlFromPublishableKey.test.ts rename to packages/shared/src/__tests__/apiUrlFromPublishableKey.spec.ts index 7288f82e050..32c089dffad 100644 --- a/packages/shared/src/__tests__/apiUrlFromPublishableKey.test.ts +++ b/packages/shared/src/__tests__/apiUrlFromPublishableKey.spec.ts @@ -1,3 +1,5 @@ +import { describe, expect, test } from 'vitest'; + import { apiUrlFromPublishableKey } from '../apiUrlFromPublishableKey'; describe('apiUrlFromPublishableKey', () => { diff --git a/packages/shared/src/__tests__/browser.test.ts b/packages/shared/src/__tests__/browser.spec.ts similarity index 81% rename from packages/shared/src/__tests__/browser.test.ts rename to packages/shared/src/__tests__/browser.spec.ts index 1c34776789f..d370f886d4e 100644 --- a/packages/shared/src/__tests__/browser.test.ts +++ b/packages/shared/src/__tests__/browser.spec.ts @@ -1,15 +1,17 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + import { inBrowser, isValidBrowser, isValidBrowserOnline, userAgentIsRobot } from '../browser'; describe('inBrowser()', () => { afterEach(() => { - jest.restoreAllMocks(); + vi.restoreAllMocks(); }); it('returns true if window is defined', () => { expect(inBrowser()).toBe(true); }); it('returns false if window is undefined', () => { - const windowSpy = jest.spyOn(global, 'window', 'get'); + const windowSpy = vi.spyOn(global, 'window', 'get'); // @ts-ignore - Test windowSpy.mockReturnValue(undefined); expect(inBrowser()).toBe(false); @@ -21,16 +23,23 @@ describe('isValidBrowser', () => { let webdriverGetter: any; beforeEach(() => { - userAgentGetter = jest.spyOn(window.navigator, 'userAgent', 'get'); - webdriverGetter = jest.spyOn(window.navigator, 'webdriver', 'get'); + userAgentGetter = vi.spyOn(window.navigator, 'userAgent', 'get'); + // Define webdriver property if it doesn't exist + if (!('webdriver' in window.navigator)) { + Object.defineProperty(window.navigator, 'webdriver', { + configurable: true, + get: () => false, + }); + } + webdriverGetter = vi.spyOn(window.navigator, 'webdriver', 'get'); }); afterEach(() => { - jest.restoreAllMocks(); + vi.restoreAllMocks(); }); it('returns false if not in browser', () => { - const windowSpy = jest.spyOn(global, 'window', 'get'); + const windowSpy = vi.spyOn(global, 'window', 'get'); // @ts-ignore - Test windowSpy.mockReturnValue(undefined); @@ -99,11 +108,27 @@ describe('isValidBrowserOnline', () => { let connectionGetter: any; beforeEach(() => { - userAgentGetter = jest.spyOn(window.navigator, 'userAgent', 'get'); - webdriverGetter = jest.spyOn(window.navigator, 'webdriver', 'get'); - onLineGetter = jest.spyOn(window.navigator, 'onLine', 'get'); + userAgentGetter = vi.spyOn(window.navigator, 'userAgent', 'get'); + // Define webdriver property if it doesn't exist + if (!('webdriver' in window.navigator)) { + Object.defineProperty(window.navigator, 'webdriver', { + configurable: true, + get: () => false, + }); + } + webdriverGetter = vi.spyOn(window.navigator, 'webdriver', 'get'); + onLineGetter = vi.spyOn(window.navigator, 'onLine', 'get'); + // Define connection property if it doesn't exist + // @ts-ignore + if (!('connection' in window.navigator)) { + // @ts-ignore + Object.defineProperty(window.navigator, 'connection', { + configurable: true, + get: () => ({ downlink: 10, rtt: 100 }), + }); + } // @ts-ignore - connectionGetter = jest.spyOn(window.navigator, 'connection', 'get'); + connectionGetter = vi.spyOn(window.navigator, 'connection', 'get'); }); it('returns TRUE if connection is online, navigator is online, has disabled webdriver, and not a bot', () => { diff --git a/packages/shared/src/__tests__/buildAccountsBaseUrl.test.ts b/packages/shared/src/__tests__/buildAccountsBaseUrl.spec.ts similarity index 93% rename from packages/shared/src/__tests__/buildAccountsBaseUrl.test.ts rename to packages/shared/src/__tests__/buildAccountsBaseUrl.spec.ts index 1192b858788..49b1e4e8e97 100644 --- a/packages/shared/src/__tests__/buildAccountsBaseUrl.test.ts +++ b/packages/shared/src/__tests__/buildAccountsBaseUrl.spec.ts @@ -1,3 +1,5 @@ +import { expect, test } from 'vitest'; + import { buildAccountsBaseUrl } from '../buildAccountsBaseUrl'; test.each([ diff --git a/packages/shared/src/__tests__/color.spec.ts b/packages/shared/src/__tests__/color.spec.ts new file mode 100644 index 00000000000..0b54661b9ce --- /dev/null +++ b/packages/shared/src/__tests__/color.spec.ts @@ -0,0 +1,72 @@ +import type { Color } from '@clerk/types'; +import { describe, expect, it } from 'vitest'; + +import { colorToSameTypeString, hexStringToRgbaColor, stringToHslaColor, stringToSameTypeColor } from '../color'; + +describe('stringToHslaColor(color)', function () { + const hsla = { h: 195, s: 1, l: 0.5 }; + const cases: Array<[string, Color | null]> = [ + ['', null], + ['transparent', { h: 0, s: 0, l: 0, a: 0 }], + ['#00bfff', hsla], + ['00bfff', hsla], + ['rgb(0, 191, 255)', hsla], + ['rgba(0, 191, 255, 0.3)', { ...hsla, a: 0.3 }], + ]; + + it.each(cases)('.stringToHslaColor(%s) => %s', (a, expected) => { + expect(stringToHslaColor(a)).toEqual(expected); + }); +}); + +describe('hexStringToRgbaColor(color)', function () { + const cases: Array<[string, Color | null]> = [ + ['#00bfff', { r: 0, g: 191, b: 255 }], + ['00bfff', { r: 0, g: 191, b: 255 }], + ]; + + it.each(cases)('.hexStringToRgbaColor(%s) => %s', (a, expected) => { + expect(hexStringToRgbaColor(a)).toEqual(expected); + }); +}); + +describe('stringToSameTypeColor(color)', function () { + const cases: Array<[string, Color | null]> = [ + ['', ''], + ['invalid', ''], + ['12ff12', '#12ff12'], + ['#12ff12', '#12ff12'], + ['1ff', '#1ff'], + ['transparent', 'transparent'], + ['rgb(100,100,100)', { r: 100, g: 100, b: 100, a: undefined }], + ['rgba(100,100,100,0.5)', { r: 100, g: 100, b: 100, a: 0.5 }], + ['rgb(100,100,100)', { r: 100, g: 100, b: 100, a: undefined }], + ['rgba(100,100,100,0.5)', { r: 100, g: 100, b: 100, a: 0.5 }], + ['hsl(244,66%,33%)', { h: 244, s: 0.66, l: 0.33, a: undefined }], + ['hsla(244,66%,33%,0.5)', { h: 244, s: 0.66, l: 0.33, a: 0.5 }], + ['hsl(244,66%,33)', ''], + ['hsla(244,66%,33,0.5)', ''], + ]; + + it.each(cases)('.stringToSameTypeColor(%s) => %s', (a, expected) => { + expect(stringToSameTypeColor(a)).toEqual(expected); + }); +}); + +describe('colorToSameTypeString(color)', function () { + const cases: Array<[Color, string]> = [ + ['', ''], + ['invalid', ''], + ['#12ff12', '#12ff12'], + ['#12ff12', '#12ff12'], + ['#1ff', '#1ff'], + [{ r: 100, g: 100, b: 100, a: undefined }, 'rgb(100,100,100)'], + [{ r: 100, g: 100, b: 100, a: 0.5 }, 'rgba(100,100,100,0.5)'], + [{ h: 100, s: 0.55, l: 0.33, a: undefined }, 'hsl(100,55%,33%)'], + [{ h: 100, s: 1, l: 1, a: 0.5 }, 'hsla(100,100%,100%,0.5)'], + ]; + + it.each(cases)('.colorToSameTypeString(%s) => %s', (a, expected) => { + expect(colorToSameTypeString(a)).toEqual(expected); + }); +}); diff --git a/packages/shared/src/__tests__/color.test.ts b/packages/shared/src/__tests__/color.test.ts.bak similarity index 100% rename from packages/shared/src/__tests__/color.test.ts rename to packages/shared/src/__tests__/color.test.ts.bak diff --git a/packages/shared/src/__tests__/date.test.ts b/packages/shared/src/__tests__/date.spec.ts similarity index 98% rename from packages/shared/src/__tests__/date.test.ts rename to packages/shared/src/__tests__/date.spec.ts index 56acc621178..a96562fa68f 100644 --- a/packages/shared/src/__tests__/date.test.ts +++ b/packages/shared/src/__tests__/date.spec.ts @@ -1,3 +1,5 @@ +import { describe, expect, it } from 'vitest'; + import type { RelativeDateCase } from '../date'; import { addYears, dateTo12HourTime, differenceInCalendarDays, formatRelative } from '../date'; diff --git a/packages/shared/src/__tests__/deprecated.test.ts b/packages/shared/src/__tests__/deprecated.spec.ts similarity index 90% rename from packages/shared/src/__tests__/deprecated.test.ts rename to packages/shared/src/__tests__/deprecated.spec.ts index d4986924ba8..ca4e35bff71 100644 --- a/packages/shared/src/__tests__/deprecated.test.ts +++ b/packages/shared/src/__tests__/deprecated.spec.ts @@ -1,10 +1,14 @@ -jest.mock('../utils/runtimeEnvironment', () => { +import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; + +vi.mock('../utils/runtimeEnvironment', () => { return { - isTestEnvironment: jest.fn(() => false), - isProductionEnvironment: jest.fn(() => false), + isTestEnvironment: vi.fn(() => false), + isProductionEnvironment: vi.fn(() => false), }; }); +import type { Mock } from 'vitest'; + import { deprecated, deprecatedObjectProperty, deprecatedProperty } from '../deprecated'; import { isProductionEnvironment, isTestEnvironment } from '../utils/runtimeEnvironment'; @@ -12,7 +16,7 @@ describe('deprecated(fnName, warning)', () => { let consoleWarnSpy; beforeEach(() => { - consoleWarnSpy = jest.spyOn(global.console, 'warn').mockImplementation(); + consoleWarnSpy = vi.spyOn(global.console, 'warn').mockImplementation(() => {}); }); afterEach(() => { consoleWarnSpy.mockRestore(); @@ -135,10 +139,10 @@ describe('deprecated(fnName, warning)', () => { describe('for test environment', () => { beforeEach(() => { - (isTestEnvironment as jest.Mock).mockReturnValue(true); + (isTestEnvironment as Mock).mockReturnValue(true); }); afterEach(() => { - (isTestEnvironment as jest.Mock).mockReturnValue(false); + (isTestEnvironment as Mock).mockReturnValue(false); }); test('deprecate function does not show warning', () => { @@ -157,10 +161,10 @@ describe('deprecated(fnName, warning)', () => { describe('for production environment', () => { beforeEach(() => { - (isProductionEnvironment as jest.Mock).mockReturnValue(true); + (isProductionEnvironment as Mock).mockReturnValue(true); }); afterEach(() => { - (isProductionEnvironment as jest.Mock).mockReturnValue(false); + (isProductionEnvironment as Mock).mockReturnValue(false); }); test('deprecate function does not show warning', () => { @@ -179,11 +183,11 @@ describe('deprecated(fnName, warning)', () => { }); describe('deprecatedProperty(cls, propName, warning, isStatic = false)', () => { - let consoleWarnSpy = jest.fn(); + let consoleWarnSpy = vi.fn(); beforeEach(() => { // @ts-ignore - consoleWarnSpy = jest.spyOn(global.console, 'warn').mockImplementation(); + consoleWarnSpy = vi.spyOn(global.console, 'warn').mockImplementation(() => {}); }); afterEach(() => { consoleWarnSpy.mockRestore(); @@ -257,10 +261,10 @@ describe('deprecatedProperty(cls, propName, warning, isStatic = false)', () => { describe('for test environment', () => { beforeEach(() => { - (isTestEnvironment as jest.Mock).mockReturnValue(true); + (isTestEnvironment as Mock).mockReturnValue(true); }); afterEach(() => { - (isTestEnvironment as jest.Mock).mockReturnValue(false); + (isTestEnvironment as Mock).mockReturnValue(false); }); test('deprecate class readonly property does not show warning', () => { @@ -285,10 +289,10 @@ describe('deprecatedProperty(cls, propName, warning, isStatic = false)', () => { describe('for production environment', () => { beforeEach(() => { - (isProductionEnvironment as jest.Mock).mockReturnValue(true); + (isProductionEnvironment as Mock).mockReturnValue(true); }); afterEach(() => { - (isProductionEnvironment as jest.Mock).mockReturnValue(false); + (isProductionEnvironment as Mock).mockReturnValue(false); }); test('deprecate class readonly property does not show warning', () => { @@ -313,11 +317,11 @@ describe('deprecatedProperty(cls, propName, warning, isStatic = false)', () => { }); describe('deprecatedObjectProperty(obj, propName, warning)', () => { - let consoleWarnSpy = jest.fn(); + let consoleWarnSpy = vi.fn(); beforeEach(() => { // @ts-ignore - consoleWarnSpy = jest.spyOn(global.console, 'warn').mockImplementation(); + consoleWarnSpy = vi.spyOn(global.console, 'warn').mockImplementation(() => {}); }); afterEach(() => { consoleWarnSpy.mockRestore(); @@ -341,10 +345,10 @@ describe('deprecatedObjectProperty(obj, propName, warning)', () => { describe('for test environment', () => { beforeEach(() => { - (isTestEnvironment as jest.Mock).mockReturnValue(true); + (isTestEnvironment as Mock).mockReturnValue(true); }); afterEach(() => { - (isTestEnvironment as jest.Mock).mockReturnValue(false); + (isTestEnvironment as Mock).mockReturnValue(false); }); test('deprecate object property does not show warning', () => { @@ -363,10 +367,10 @@ describe('deprecatedObjectProperty(obj, propName, warning)', () => { describe('for production environment', () => { beforeEach(() => { - (isProductionEnvironment as jest.Mock).mockReturnValue(true); + (isProductionEnvironment as Mock).mockReturnValue(true); }); afterEach(() => { - (isProductionEnvironment as jest.Mock).mockReturnValue(false); + (isProductionEnvironment as Mock).mockReturnValue(false); }); test('deprecate object property does not show warning', () => { diff --git a/packages/shared/src/__tests__/deriveState.test.ts b/packages/shared/src/__tests__/deriveState.spec.ts similarity index 96% rename from packages/shared/src/__tests__/deriveState.test.ts rename to packages/shared/src/__tests__/deriveState.spec.ts index 244cb3fe857..2f2dd22ee99 100644 --- a/packages/shared/src/__tests__/deriveState.test.ts +++ b/packages/shared/src/__tests__/deriveState.spec.ts @@ -1,4 +1,5 @@ import type { InitialState, Resources } from '@clerk/types'; +import { describe, expect, it } from 'vitest'; import { deriveState } from '../deriveState'; diff --git a/packages/shared/src/__tests__/devbrowser.test.ts b/packages/shared/src/__tests__/devbrowser.spec.ts similarity index 95% rename from packages/shared/src/__tests__/devbrowser.test.ts rename to packages/shared/src/__tests__/devbrowser.spec.ts index 9ae6523608b..29fb31cb92c 100644 --- a/packages/shared/src/__tests__/devbrowser.test.ts +++ b/packages/shared/src/__tests__/devbrowser.spec.ts @@ -1,3 +1,5 @@ +import { afterEach, beforeEach, describe, expect, it, test, vi } from 'vitest'; + import { extractDevBrowserJWTFromURL, setDevBrowserJWTInURL } from '../devBrowser'; const DUMMY_URL_BASE = 'http://clerk-dummy'; @@ -32,7 +34,7 @@ describe('setDevBrowserJWTInURL(url, jwt)', () => { const oldHistory = globalThis.history; describe('getDevBrowserJWTFromURL(url)', () => { - const replaceStateMock = jest.fn(); + const replaceStateMock = vi.fn(); beforeEach(() => { const mockHistory = { diff --git a/packages/shared/src/__tests__/error.test.ts b/packages/shared/src/__tests__/error.spec.ts similarity index 98% rename from packages/shared/src/__tests__/error.test.ts rename to packages/shared/src/__tests__/error.spec.ts index bc282cad179..d04a312977a 100644 --- a/packages/shared/src/__tests__/error.test.ts +++ b/packages/shared/src/__tests__/error.spec.ts @@ -1,3 +1,5 @@ +import { describe, expect, it } from 'vitest'; + import type { ErrorThrowerOptions } from '../error'; import { buildErrorThrower, ClerkRuntimeError, isClerkRuntimeError } from '../error'; diff --git a/packages/shared/src/__tests__/eventBus.test.ts b/packages/shared/src/__tests__/eventBus.spec.ts similarity index 88% rename from packages/shared/src/__tests__/eventBus.test.ts rename to packages/shared/src/__tests__/eventBus.spec.ts index ce808e52505..b0c970fe3f4 100644 --- a/packages/shared/src/__tests__/eventBus.test.ts +++ b/packages/shared/src/__tests__/eventBus.spec.ts @@ -1,3 +1,5 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; + import { createEventBus } from '../eventBus'; // Define types for our test events @@ -18,7 +20,7 @@ describe('eventBus', () => { describe('on()', () => { it('registers an event handler', () => { // Arrange - const handler = jest.fn(); + const handler = vi.fn(); // Act eventBus.on('test-event', handler); @@ -31,8 +33,8 @@ describe('eventBus', () => { it('registers multiple handlers for the same event', () => { // Arrange - const handler1 = jest.fn(); - const handler2 = jest.fn(); + const handler1 = vi.fn(); + const handler2 = vi.fn(); // Act eventBus.on('test-event', handler1); @@ -47,7 +49,7 @@ describe('eventBus', () => { it('calls handler immediately with notify=true if there is a latest payload', () => { // Arrange - const handler = jest.fn(); + const handler = vi.fn(); const payload = 'test-message'; // Act @@ -61,7 +63,7 @@ describe('eventBus', () => { it('does not call handler with notify=false even if there is a latest payload', () => { // Arrange - const handler = jest.fn(); + const handler = vi.fn(); const payload = 'test-message'; // Act @@ -74,7 +76,7 @@ describe('eventBus', () => { it('does not call handler without notify option even if there is a latest payload', () => { // Arrange - const handler = jest.fn(); + const handler = vi.fn(); const payload = 'test-message'; // Act @@ -89,7 +91,7 @@ describe('eventBus', () => { describe('prioritizedOn()', () => { it('registers a pre-dispatch event handler', () => { // Arrange - const handler = jest.fn(); + const handler = vi.fn(); // Act eventBus.prioritizedOn('test-event', handler); @@ -104,8 +106,8 @@ describe('eventBus', () => { describe('emit()', () => { it('calls all handlers for the given event', () => { // Arrange - const handler1 = jest.fn(); - const handler2 = jest.fn(); + const handler1 = vi.fn(); + const handler2 = vi.fn(); const payload = 'test-message'; eventBus.on('test-event', handler1); @@ -121,7 +123,7 @@ describe('eventBus', () => { it('calls handlers with object payload', () => { // Arrange - const handler = jest.fn(); + const handler = vi.fn(); const payload = { message: 'test-message', id: 123 }; eventBus.on('test-event-with-object', handler); @@ -136,8 +138,8 @@ describe('eventBus', () => { it('calls prioritized handlers before regular handlers', () => { // Arrange const calls: string[] = []; - const preHandler = jest.fn(() => calls.push('pre')); - const regularHandler = jest.fn(() => calls.push('regular')); + const preHandler = vi.fn(() => calls.push('pre')); + const regularHandler = vi.fn(() => calls.push('regular')); const payload = 'test-message'; eventBus.prioritizedOn('test-event', preHandler); @@ -156,7 +158,7 @@ describe('eventBus', () => { // Arrange const payload1 = 'test-message-1'; const payload2 = 'test-message-2'; - const handler = jest.fn(); + const handler = vi.fn(); // Act eventBus.emit('test-event', payload1); @@ -170,8 +172,8 @@ describe('eventBus', () => { it('does not call handlers for other events', () => { // Arrange - const handler1 = jest.fn(); - const handler2 = jest.fn(); + const handler1 = vi.fn(); + const handler2 = vi.fn(); eventBus.on('test-event', handler1); eventBus.on('test-event-with-number', handler2); @@ -188,8 +190,8 @@ describe('eventBus', () => { describe('off()', () => { it('removes a specific handler for an event', () => { // Arrange - const handler1 = jest.fn(); - const handler2 = jest.fn(); + const handler1 = vi.fn(); + const handler2 = vi.fn(); eventBus.on('test-event', handler1); eventBus.on('test-event', handler2); @@ -205,8 +207,8 @@ describe('eventBus', () => { it('removes all handlers for an event when handler is not specified', () => { // Arrange - const handler1 = jest.fn(); - const handler2 = jest.fn(); + const handler1 = vi.fn(); + const handler2 = vi.fn(); eventBus.on('test-event', handler1); eventBus.on('test-event', handler2); @@ -225,8 +227,8 @@ describe('eventBus', () => { describe('offPreDispatch()', () => { it('removes a specific pre-dispatch handler', () => { // Arrange - const preHandler1 = jest.fn(); - const preHandler2 = jest.fn(); + const preHandler1 = vi.fn(); + const preHandler2 = vi.fn(); eventBus.prioritizedOn('test-event', preHandler1); eventBus.prioritizedOn('test-event', preHandler2); @@ -242,8 +244,8 @@ describe('eventBus', () => { it('removes all pre-dispatch handlers when handler is not specified', () => { // Arrange - const preHandler1 = jest.fn(); - const preHandler2 = jest.fn(); + const preHandler1 = vi.fn(); + const preHandler2 = vi.fn(); eventBus.prioritizedOn('test-event', preHandler1); eventBus.prioritizedOff('test-event', preHandler2); @@ -266,8 +268,8 @@ describe('eventBus', () => { it('returns all registered listeners for an event', () => { // Arrange - const handler1 = jest.fn(); - const handler2 = jest.fn(); + const handler1 = vi.fn(); + const handler2 = vi.fn(); eventBus.on('test-event', handler1); eventBus.on('test-event', handler2); diff --git a/packages/shared/src/__tests__/fastDeepMerge.test.ts b/packages/shared/src/__tests__/fastDeepMerge.spec.ts similarity index 98% rename from packages/shared/src/__tests__/fastDeepMerge.test.ts rename to packages/shared/src/__tests__/fastDeepMerge.spec.ts index 178cf401f40..8ac9bdf1594 100644 --- a/packages/shared/src/__tests__/fastDeepMerge.test.ts +++ b/packages/shared/src/__tests__/fastDeepMerge.spec.ts @@ -1,3 +1,5 @@ +import { describe, expect, it } from 'vitest'; + import { fastDeepMergeAndKeep, fastDeepMergeAndReplace } from '../utils/fastDeepMerge'; describe('fastDeepMergeReplace', () => { diff --git a/packages/shared/src/__tests__/handleValueOrFn.test.ts b/packages/shared/src/__tests__/handleValueOrFn.spec.ts similarity index 95% rename from packages/shared/src/__tests__/handleValueOrFn.test.ts rename to packages/shared/src/__tests__/handleValueOrFn.spec.ts index 669cb0619d4..44eef4681f9 100644 --- a/packages/shared/src/__tests__/handleValueOrFn.test.ts +++ b/packages/shared/src/__tests__/handleValueOrFn.spec.ts @@ -1,3 +1,5 @@ +import { describe, expect, it } from 'vitest'; + import { handleValueOrFn } from '../utils/handleValueOrFn'; const url = new URL('https://example.com'); diff --git a/packages/shared/src/__tests__/jwtPayloadParser.test.ts b/packages/shared/src/__tests__/jwtPayloadParser.spec.ts similarity index 99% rename from packages/shared/src/__tests__/jwtPayloadParser.test.ts rename to packages/shared/src/__tests__/jwtPayloadParser.spec.ts index 71d9b950c17..014a7292f48 100644 --- a/packages/shared/src/__tests__/jwtPayloadParser.test.ts +++ b/packages/shared/src/__tests__/jwtPayloadParser.spec.ts @@ -1,3 +1,5 @@ +import { describe, expect, test } from 'vitest'; + import { splitByScope } from '../authorization'; import { __experimental_JWTPayloadToAuthObjectProperties as JWTPayloadToAuthObjectProperties } from '../jwtPayloadParser'; diff --git a/packages/shared/src/__tests__/keys.test.ts b/packages/shared/src/__tests__/keys.spec.ts similarity index 99% rename from packages/shared/src/__tests__/keys.test.ts rename to packages/shared/src/__tests__/keys.spec.ts index 3ce71504786..a4e493171ac 100644 --- a/packages/shared/src/__tests__/keys.test.ts +++ b/packages/shared/src/__tests__/keys.spec.ts @@ -1,3 +1,5 @@ +import { describe, expect, it, test } from 'vitest'; + import { buildPublishableKey, createDevOrStagingUrlCache, diff --git a/packages/shared/src/__tests__/loadClerkJsScript.test.ts b/packages/shared/src/__tests__/loadClerkJsScript.spec.ts similarity index 92% rename from packages/shared/src/__tests__/loadClerkJsScript.test.ts rename to packages/shared/src/__tests__/loadClerkJsScript.spec.ts index d135708634d..d7b5d754d58 100644 --- a/packages/shared/src/__tests__/loadClerkJsScript.test.ts +++ b/packages/shared/src/__tests__/loadClerkJsScript.spec.ts @@ -1,3 +1,6 @@ +import type { Mock } from 'vitest'; +import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; + import { ClerkRuntimeError } from '../error'; import { buildClerkJsScriptAttributes, @@ -8,7 +11,7 @@ import { import { loadScript } from '../loadScript'; import { getMajorVersion } from '../versionSelector'; -jest.mock('../loadScript'); +vi.mock('../loadScript'); setClerkJsLoadingErrorPackageName('@clerk/clerk-react'); const jsPackageMajorVersion = getMajorVersion(JS_PACKAGE_VERSION); @@ -16,24 +19,24 @@ const jsPackageMajorVersion = getMajorVersion(JS_PACKAGE_VERSION); const mockClerk = { status: 'ready', loaded: true, - load: jest.fn(), + load: vi.fn(), }; describe('loadClerkJsScript(options)', () => { const mockPublishableKey = 'pk_test_Zm9vLWJhci0xMy5jbGVyay5hY2NvdW50cy5kZXYk'; beforeEach(() => { - jest.clearAllMocks(); - (loadScript as jest.Mock).mockResolvedValue(undefined); - document.querySelector = jest.fn().mockReturnValue(null); + vi.clearAllMocks(); + (loadScript as Mock).mockResolvedValue(undefined); + document.querySelector = vi.fn().mockReturnValue(null); (window as any).Clerk = undefined; - jest.useFakeTimers(); + vi.useFakeTimers(); }); afterEach(() => { - jest.useRealTimers(); + vi.useRealTimers(); }); test('throws error when publishableKey is missing', async () => { @@ -59,7 +62,7 @@ describe('loadClerkJsScript(options)', () => { }, 250); // Advance timers to allow polling to detect Clerk - jest.advanceTimersByTime(300); + vi.advanceTimersByTime(300); const result = await loadPromise; expect(result).toBeNull(); @@ -81,7 +84,7 @@ describe('loadClerkJsScript(options)', () => { const loadPromise = loadClerkJsScript({ publishableKey: mockPublishableKey, scriptLoadTimeout: 1000 }); try { - jest.advanceTimersByTime(1000); + vi.advanceTimersByTime(1000); await loadPromise; } catch (error) { rejectedWith = error; @@ -94,7 +97,7 @@ describe('loadClerkJsScript(options)', () => { test('waits for existing script with timeout', async () => { const mockExistingScript = document.createElement('script'); - document.querySelector = jest.fn().mockReturnValue(mockExistingScript); + document.querySelector = vi.fn().mockReturnValue(mockExistingScript); const loadPromise = loadClerkJsScript({ publishableKey: mockPublishableKey }); @@ -104,7 +107,7 @@ describe('loadClerkJsScript(options)', () => { }, 250); // Advance timers to allow polling to detect Clerk - jest.advanceTimersByTime(300); + vi.advanceTimersByTime(300); const result = await loadPromise; expect(result).toBeNull(); @@ -118,7 +121,7 @@ describe('loadClerkJsScript(options)', () => { (window as any).Clerk = mockClerk; }, 999); - jest.advanceTimersByTime(1000); + vi.advanceTimersByTime(1000); const result = await loadPromise; expect(result).toBeNull(); @@ -132,7 +135,7 @@ describe('loadClerkJsScript(options)', () => { (window as any).Clerk = { status: 'ready' }; }, 100); - jest.advanceTimersByTime(15000); + vi.advanceTimersByTime(15000); try { await loadPromise; diff --git a/packages/shared/src/__tests__/localStorageBroadcastChannel.test.ts b/packages/shared/src/__tests__/localStorageBroadcastChannel.spec.ts similarity index 83% rename from packages/shared/src/__tests__/localStorageBroadcastChannel.test.ts rename to packages/shared/src/__tests__/localStorageBroadcastChannel.spec.ts index 110ef7e053f..e925ed901ef 100644 --- a/packages/shared/src/__tests__/localStorageBroadcastChannel.test.ts +++ b/packages/shared/src/__tests__/localStorageBroadcastChannel.spec.ts @@ -1,3 +1,5 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; + import { LocalStorageBroadcastChannel } from '../localStorageBroadcastChannel'; const bcName = 'clerk'; @@ -8,11 +10,11 @@ describe('LocalStorageBroadcastChannel', () => { localStorageMock = (() => { const store: Record = {}; return { - setItem: jest.fn((key, value) => { + setItem: vi.fn((key, value) => { store[key] = value; window.dispatchEvent(new StorageEvent('storage', { key, newValue: value })); }), - removeItem: jest.fn(key => { + removeItem: vi.fn(key => { store[key] = undefined; window.dispatchEvent(new Event('storage')); }), @@ -22,9 +24,9 @@ describe('LocalStorageBroadcastChannel', () => { }); it('notifies other LocalStorageBroadcastChannel with same name', () => { - const tab1ClerkBCListener = jest.fn(); - const tab1DifferentBCListener = jest.fn(); - const tab2ClerkBCListener = jest.fn(); + const tab1ClerkBCListener = vi.fn(); + const tab1DifferentBCListener = vi.fn(); + const tab2ClerkBCListener = vi.fn(); const tab1ClerkBC = new LocalStorageBroadcastChannel(bcName); const tab1DifferentBC = new LocalStorageBroadcastChannel('somethingElse'); diff --git a/packages/shared/src/__tests__/logger.test.ts b/packages/shared/src/__tests__/logger.spec.ts similarity index 75% rename from packages/shared/src/__tests__/logger.test.ts rename to packages/shared/src/__tests__/logger.spec.ts index 2b6de46131b..c7280b182df 100644 --- a/packages/shared/src/__tests__/logger.test.ts +++ b/packages/shared/src/__tests__/logger.spec.ts @@ -1,8 +1,10 @@ +import { afterAll, beforeEach, describe, expect, test, vi } from 'vitest'; + import { logger } from '../logger'; describe('logger', () => { describe('warnOnce', () => { - const warnMock = jest.spyOn(global.console, 'warn').mockImplementation(); + const warnMock = vi.spyOn(global.console, 'warn').mockImplementation(() => {}); beforeEach(() => warnMock.mockClear()); afterAll(() => warnMock.mockRestore()); @@ -17,7 +19,7 @@ describe('logger', () => { }); describe('logOnce', () => { - const logMock = jest.spyOn(global.console, 'log').mockImplementation(); + const logMock = vi.spyOn(global.console, 'log').mockImplementation(() => {}); beforeEach(() => logMock.mockClear()); afterAll(() => logMock.mockRestore()); diff --git a/packages/shared/src/__tests__/netlifyCacheHandler.test.ts b/packages/shared/src/__tests__/netlifyCacheHandler.spec.ts similarity index 97% rename from packages/shared/src/__tests__/netlifyCacheHandler.test.ts rename to packages/shared/src/__tests__/netlifyCacheHandler.spec.ts index b5bc9111cdf..e4f9c3e7984 100644 --- a/packages/shared/src/__tests__/netlifyCacheHandler.test.ts +++ b/packages/shared/src/__tests__/netlifyCacheHandler.spec.ts @@ -1,4 +1,6 @@ /* eslint-disable turbo/no-undeclared-env-vars */ +import { beforeEach, describe, expect, it } from 'vitest'; + import { CLERK_NETLIFY_CACHE_BUST_PARAM, handleNetlifyCacheInDevInstance } from '../netlifyCacheHandler'; const mockPublishableKey = 'pk_test_YW55LW9mZabcZS1wYWdlX3BhZ2VfcG9pbnRlci1pZF90ZXN0XzE'; diff --git a/packages/shared/src/__tests__/organization.test.ts b/packages/shared/src/__tests__/organization.spec.ts similarity index 95% rename from packages/shared/src/__tests__/organization.test.ts rename to packages/shared/src/__tests__/organization.spec.ts index 21d13ad4da4..6aa1dbba74c 100644 --- a/packages/shared/src/__tests__/organization.test.ts +++ b/packages/shared/src/__tests__/organization.spec.ts @@ -1,4 +1,5 @@ import type { OrganizationMembershipResource } from '@clerk/types'; +import { describe, expect, it } from 'vitest'; import { getCurrentOrganizationMembership } from '../organization'; diff --git a/packages/shared/src/__tests__/pathMatcher.test.ts b/packages/shared/src/__tests__/pathMatcher.spec.ts similarity index 95% rename from packages/shared/src/__tests__/pathMatcher.test.ts rename to packages/shared/src/__tests__/pathMatcher.spec.ts index 525aa106d98..236ff2bc90c 100644 --- a/packages/shared/src/__tests__/pathMatcher.test.ts +++ b/packages/shared/src/__tests__/pathMatcher.spec.ts @@ -1,6 +1,8 @@ +import { describe, expect, test, vi } from 'vitest'; + import { createPathMatcher } from '../pathMatcher'; -jest.mock('../pathToRegexp', () => ({ +vi.mock('../pathToRegexp', () => ({ pathToRegexp: (pattern: string) => new RegExp(`^${pattern.replace('(.*)', '.*')}$`), })); diff --git a/packages/shared/src/__tests__/proxy.test.ts b/packages/shared/src/__tests__/proxy.spec.ts similarity index 96% rename from packages/shared/src/__tests__/proxy.test.ts rename to packages/shared/src/__tests__/proxy.spec.ts index a34aca2dbed..4a898391ee6 100644 --- a/packages/shared/src/__tests__/proxy.test.ts +++ b/packages/shared/src/__tests__/proxy.spec.ts @@ -1,3 +1,5 @@ +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; + import { isHttpOrHttps, isProxyUrlRelative, isValidProxyUrl, proxyUrlToAbsoluteURL } from '../proxy'; describe('isValidProxyUrl(key)', () => { diff --git a/packages/shared/src/__tests__/retry.test.ts b/packages/shared/src/__tests__/retry.spec.ts similarity index 80% rename from packages/shared/src/__tests__/retry.test.ts rename to packages/shared/src/__tests__/retry.spec.ts index 2848b0869b8..781f106716d 100644 --- a/packages/shared/src/__tests__/retry.test.ts +++ b/packages/shared/src/__tests__/retry.spec.ts @@ -1,12 +1,14 @@ +import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; + import { retry } from '../retry'; describe('retry', () => { beforeEach(() => { - jest.useFakeTimers(); + vi.useFakeTimers(); }); afterEach(() => { - jest.useRealTimers(); + vi.useRealTimers(); }); test('resolves with the result of the callback', async () => { @@ -30,13 +32,13 @@ describe('retry', () => { jitter: false, }, ); - await jest.advanceTimersByTimeAsync(200); + await vi.advanceTimersByTimeAsync(200); expect(await result).toBe('success'); expect(attempts).toBe(2); }); test('maxDelayBetweenRetries prevents delays from growing beyond the limit', async () => { - jest.useFakeTimers(); + vi.useFakeTimers(); let attempts = 0; retry( @@ -56,12 +58,12 @@ describe('retry', () => { }); // Run all timer advances before testing the promise - await jest.advanceTimersByTimeAsync(100); - await jest.advanceTimersByTimeAsync(300); - await jest.advanceTimersByTimeAsync(300); - await jest.advanceTimersByTimeAsync(300); + await vi.advanceTimersByTimeAsync(100); + await vi.advanceTimersByTimeAsync(300); + await vi.advanceTimersByTimeAsync(300); + await vi.advanceTimersByTimeAsync(300); expect(attempts).toBe(1 + 4); - jest.useRealTimers(); + vi.useRealTimers(); }); test('respects initialDelay option', async () => { @@ -75,9 +77,9 @@ describe('retry', () => { ).catch(() => {}); expect(attempts).toBe(1); - await jest.advanceTimersByTimeAsync(200); + await vi.advanceTimersByTimeAsync(200); expect(attempts).toBe(2); - await jest.advanceTimersByTimeAsync(400); + await vi.advanceTimersByTimeAsync(400); expect(attempts).toBe(3); }); @@ -97,9 +99,9 @@ describe('retry', () => { ).catch(() => {}); expect(attempts).toBe(1); - await jest.advanceTimersByTimeAsync(101); + await vi.advanceTimersByTimeAsync(101); expect(attempts).toBe(2); - await jest.advanceTimersByTimeAsync(1000); + await vi.advanceTimersByTimeAsync(1000); expect(attempts).toBe(3); }); @@ -119,9 +121,9 @@ describe('retry', () => { ).catch(() => {}); expect(attempts).toBe(1); - await jest.advanceTimersByTimeAsync(200); + await vi.advanceTimersByTimeAsync(200); expect(attempts).toBe(2); - await jest.advanceTimersByTimeAsync(400); + await vi.advanceTimersByTimeAsync(400); expect(attempts).toBe(3); }); @@ -162,18 +164,18 @@ describe('retry', () => { ).catch(() => {}); expect(attempts).toBe(1); - await jest.advanceTimersByTimeAsync(100); + await vi.advanceTimersByTimeAsync(100); expect(attempts).toBe(2); - await jest.advanceTimersByTimeAsync(400); + await vi.advanceTimersByTimeAsync(400); expect(attempts).toBe(3); - await jest.advanceTimersByTimeAsync(1600); + await vi.advanceTimersByTimeAsync(1600); expect(attempts).toBe(4); }); test('applies jitter by default', async () => { let attempts = 0; - jest.spyOn(Math, 'random').mockReturnValue(0.5); + vi.spyOn(Math, 'random').mockReturnValue(0.5); retry( () => { @@ -192,17 +194,17 @@ describe('retry', () => { // Flush all microtasks await Promise.resolve(); // Normal delay without jitter - await jest.advanceTimersByTimeAsync(100); + await vi.advanceTimersByTimeAsync(100); // But the attempt is still 1 because with the jitter enabled, // the delay is now 150 expect(attempts).toBe(1); // Wait for 50ms more (100 + 50) - await jest.advanceTimersByTimeAsync(50); + await vi.advanceTimersByTimeAsync(50); // Should now reach the second attempt expect(attempts).toBe(2); - await jest.advanceTimersByTimeAsync(150); + await vi.advanceTimersByTimeAsync(150); expect(attempts).toBe(3); - await jest.advanceTimersByTimeAsync(150); + await vi.advanceTimersByTimeAsync(150); expect(attempts).toBe(3); }); }); diff --git a/packages/shared/src/__tests__/telemetry.logs.test.ts b/packages/shared/src/__tests__/telemetry.logs.spec.ts similarity index 92% rename from packages/shared/src/__tests__/telemetry.logs.test.ts rename to packages/shared/src/__tests__/telemetry.logs.spec.ts index a5602de1567..c9bc9854719 100644 --- a/packages/shared/src/__tests__/telemetry.logs.test.ts +++ b/packages/shared/src/__tests__/telemetry.logs.spec.ts @@ -1,18 +1,20 @@ import 'cross-fetch/polyfill'; +import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; + import { TelemetryCollector } from '../telemetry'; -jest.useFakeTimers(); +vi.useFakeTimers(); const TEST_PK = 'pk_test_Zm9vLWJhci0xMy5jbGVyay5hY2NvdW50cy5kZXYk'; describe('TelemetryCollector.recordLog', () => { - let fetchSpy: jest.SpyInstance; - let windowSpy: jest.SpyInstance; + let fetchSpy: any; + let windowSpy: any; beforeEach(() => { - fetchSpy = jest.spyOn(global, 'fetch'); - windowSpy = jest.spyOn(window, 'window', 'get'); + fetchSpy = vi.spyOn(global, 'fetch'); + windowSpy = vi.spyOn(window, 'window', 'get'); }); afterEach(() => { @@ -31,7 +33,7 @@ describe('TelemetryCollector.recordLog', () => { context: { a: 1, b: undefined, c: () => {} }, } as any); - jest.runAllTimers(); + vi.runAllTimers(); expect(fetchSpy).toHaveBeenCalled(); const [url, init] = fetchSpy.mock.calls[0]; @@ -63,7 +65,7 @@ describe('TelemetryCollector.recordLog', () => { fetchSpy.mockClear(); collector.recordLog({ ...base, context: undefined } as any); - jest.runAllTimers(); + vi.runAllTimers(); const initOptions1 = fetchSpy.mock.calls[0][1] as RequestInit; expect(typeof initOptions1.body).toBe('string'); let body = JSON.parse(initOptions1.body as string); @@ -71,7 +73,7 @@ describe('TelemetryCollector.recordLog', () => { fetchSpy.mockClear(); collector.recordLog({ ...base, context: [1, 2, 3] } as any); - jest.runAllTimers(); + vi.runAllTimers(); const initOptions2 = fetchSpy.mock.calls[0][1] as RequestInit; expect(typeof initOptions2.body).toBe('string'); body = JSON.parse(initOptions2.body as string); @@ -81,7 +83,7 @@ describe('TelemetryCollector.recordLog', () => { const circular: any = { foo: 'bar' }; circular.self = circular; collector.recordLog({ ...base, context: circular } as any); - jest.runAllTimers(); + vi.runAllTimers(); const initOptions3 = fetchSpy.mock.calls[0][1] as RequestInit; expect(typeof initOptions3.body).toBe('string'); body = JSON.parse(initOptions3.body as string); @@ -97,7 +99,7 @@ describe('TelemetryCollector.recordLog', () => { message: 'ok', timestamp: Date.now(), } as any); - jest.runAllTimers(); + vi.runAllTimers(); expect(fetchSpy).not.toHaveBeenCalled(); fetchSpy.mockClear(); @@ -106,7 +108,7 @@ describe('TelemetryCollector.recordLog', () => { message: '', timestamp: Date.now(), }); - jest.runAllTimers(); + vi.runAllTimers(); expect(fetchSpy).not.toHaveBeenCalled(); fetchSpy.mockClear(); @@ -115,7 +117,7 @@ describe('TelemetryCollector.recordLog', () => { message: 'ok', timestamp: Number.NaN, }); - jest.runAllTimers(); + vi.runAllTimers(); expect(fetchSpy).not.toHaveBeenCalled(); }); @@ -130,7 +132,7 @@ describe('TelemetryCollector.recordLog', () => { timestamp: tsString, }); - jest.runAllTimers(); + vi.runAllTimers(); expect(fetchSpy).toHaveBeenCalled(); const initOptions4 = fetchSpy.mock.calls[0][1] as RequestInit; expect(typeof initOptions4.body).toBe('string'); @@ -157,7 +159,7 @@ describe('TelemetryCollector.recordLog', () => { }); }).not.toThrow(); - jest.runAllTimers(); + vi.runAllTimers(); expect(fetchSpy).toHaveBeenCalled(); const [url, init] = fetchSpy.mock.calls[0]; @@ -191,7 +193,7 @@ describe('TelemetryCollector.recordLog', () => { }); }).not.toThrow(); - jest.runAllTimers(); + vi.runAllTimers(); expect(fetchSpy).toHaveBeenCalled(); const [url, init] = fetchSpy.mock.calls[0]; @@ -215,7 +217,7 @@ describe('TelemetryCollector.recordLog', () => { }); }).not.toThrow(); - jest.runAllTimers(); + vi.runAllTimers(); expect(fetchSpy).not.toHaveBeenCalled(); }); }); diff --git a/packages/shared/src/__tests__/telemetry.test.ts b/packages/shared/src/__tests__/telemetry.test.ts deleted file mode 100644 index 66bdae35c68..00000000000 --- a/packages/shared/src/__tests__/telemetry.test.ts +++ /dev/null @@ -1,535 +0,0 @@ -import 'cross-fetch/polyfill'; - -import type { TelemetryEvent } from '@clerk/types'; -// @ts-ignore -import assert from 'assert'; - -import { TelemetryCollector } from '../telemetry'; - -jest.useFakeTimers(); - -const TEST_PK = 'pk_test_Zm9vLWJhci0xMy5jbGVyay5hY2NvdW50cy5kZXYk'; - -describe('TelemetryCollector', () => { - let windowSpy; - let fetchSpy; - - beforeEach(() => { - fetchSpy = jest.spyOn(global, 'fetch'); - windowSpy = jest.spyOn(window, 'window', 'get'); - }); - - afterEach(() => { - windowSpy.mockRestore(); - fetchSpy.mockRestore(); - }); - - test('does nothing when disabled', async () => { - const collector = new TelemetryCollector({ - disabled: true, - publishableKey: TEST_PK, - }); - - collector.record({ event: 'TEST_EVENT', payload: {} }); - - jest.runAllTimers(); - - expect(fetchSpy).not.toHaveBeenCalled(); - }); - - test('does nothing when CLERK_TELEMETRY_DISABLED is set', async () => { - process.env.CLERK_TELEMETRY_DISABLED = '1'; - - const collector = new TelemetryCollector({ - publishableKey: TEST_PK, - }); - - collector.record({ event: 'TEST_EVENT', payload: {} }); - - jest.runAllTimers(); - - expect(fetchSpy).not.toHaveBeenCalled(); - - process.env.CLERK_TELEMETRY_DISABLED = undefined; - }); - - test('does not send events when debug is enabled, logs them instead', async () => { - const consoleGroupSpy = jest.spyOn(console, 'groupCollapsed').mockImplementation(() => {}); - const consoleSpy = jest.spyOn(console, 'log').mockImplementation(() => {}); - - const collector = new TelemetryCollector({ - debug: true, - publishableKey: TEST_PK, - }); - - collector.record({ event: 'TEST_EVENT', payload: {} }); - - jest.runAllTimers(); - - expect(fetchSpy).not.toHaveBeenCalled(); - - expect(consoleSpy.mock.calls).toMatchInlineSnapshot(` - [ - [ - { - "cv": "", - "event": "TEST_EVENT", - "it": "development", - "payload": {}, - "pk": "pk_test_Zm9vLWJhci0xMy5jbGVyay5hY2NvdW50cy5kZXYk", - "sdk": undefined, - "sdkv": undefined, - }, - ], - ] - `); - - consoleGroupSpy.mockRestore(); - consoleSpy.mockRestore(); - }); - - test('enables debug via environment variable', async () => { - process.env.CLERK_TELEMETRY_DEBUG = '1'; - - const consoleGroupSpy = jest.spyOn(console, 'groupCollapsed').mockImplementation(() => {}); - const consoleSpy = jest.spyOn(console, 'log').mockImplementation(() => {}); - - const collector = new TelemetryCollector({ - publishableKey: TEST_PK, - }); - - collector.record({ event: 'TEST_EVENT', payload: {} }); - - jest.runAllTimers(); - - expect(fetchSpy).not.toHaveBeenCalled(); - - expect(consoleSpy.mock.calls).toMatchInlineSnapshot(` - [ - [ - { - "cv": "", - "event": "TEST_EVENT", - "it": "development", - "payload": {}, - "pk": "pk_test_Zm9vLWJhci0xMy5jbGVyay5hY2NvdW50cy5kZXYk", - "sdk": undefined, - "sdkv": undefined, - }, - ], - ] - `); - - consoleGroupSpy.mockRestore(); - consoleSpy.mockRestore(); - - process.env.CLERK_TELEMETRY_DEBUG = undefined; - }); - - test('sends events after a delay when buffer is not full', async () => { - const collector = new TelemetryCollector({ - publishableKey: TEST_PK, - }); - - collector.record({ event: 'TEST_EVENT', payload: {} }); - - jest.runAllTimers(); - - expect(fetchSpy).toHaveBeenCalled(); - }); - - test('sends events immediately when the buffer limit is reached', async () => { - const collector = new TelemetryCollector({ - maxBufferSize: 2, - publishableKey: TEST_PK, - }); - - collector.record({ event: 'TEST_EVENT', payload: { method: 'useFoo' } }); - collector.record({ event: 'TEST_EVENT', payload: { method: 'useBar' } }); - - expect(fetchSpy).toHaveBeenCalled(); - }); - - test('events are isolated between batches (no shared buffer reference)', () => { - const collector = new TelemetryCollector({ - maxBufferSize: 2, - publishableKey: TEST_PK, - }); - const capturedBatches: TelemetryEvent[] = []; - - fetchSpy.mockImplementation((_, options) => { - capturedBatches.push(JSON.parse(options.body).events); - return Promise.resolve({ ok: true }); - }); - - // First batch - collector.record({ event: 'A', payload: { id: 1 } }); - collector.record({ event: 'B', payload: { id: 2 } }); - - // Second batch - collector.record({ event: 'C', payload: { id: 3 } }); - collector.record({ event: 'D', payload: { id: 4 } }); - - // If there's a closure leak, events might be mixed or duplicated - expect(capturedBatches[0]).toEqual([ - expect.objectContaining({ event: 'A', payload: { id: 1 } }), - expect.objectContaining({ event: 'B', payload: { id: 2 } }), - ]); - - expect(capturedBatches[1]).toEqual([ - expect.objectContaining({ event: 'C', payload: { id: 3 } }), - expect.objectContaining({ event: 'D', payload: { id: 4 } }), - ]); - }); - - describe('with server-side sampling', () => { - test('does not send events if the random seed does not exceed the event-specific sampling rate', async () => { - windowSpy.mockImplementation(() => undefined); - - const randomSpy = jest.spyOn(Math, 'random').mockReturnValue(0.1); - - const collector = new TelemetryCollector({ - publishableKey: TEST_PK, - }); - - collector.record({ event: 'TEST_EVENT', eventSamplingRate: 0.01, payload: {} }); - - jest.runAllTimers(); - - expect(fetchSpy).not.toHaveBeenCalled(); - - randomSpy.mockRestore(); - }); - - test('ignores event-specific sampling rate when eventSampling is false', async () => { - windowSpy.mockImplementation(() => undefined); - - const randomSpy = jest.spyOn(Math, 'random').mockReturnValue(0.5); - - const collector = new TelemetryCollector({ - publishableKey: TEST_PK, - samplingRate: 1.0, // Global sampling rate allows all events - perEventSampling: false, // Disable event-specific sampling - }); - - // This event would normally be rejected due to low eventSamplingRate (0.1 < 0.5) - // but should be sent because eventSampling is disabled - collector.record({ event: 'TEST_EVENT', eventSamplingRate: 0.1, payload: {} }); - - jest.runAllTimers(); - - expect(fetchSpy).toHaveBeenCalled(); - - randomSpy.mockRestore(); - }); - - test('respects event-specific sampling rate when eventSampling is true (default)', async () => { - windowSpy.mockImplementation(() => undefined); - - const randomSpy = jest.spyOn(Math, 'random').mockReturnValue(0.5); - - const collector = new TelemetryCollector({ - publishableKey: TEST_PK, - samplingRate: 1.0, // Global sampling rate allows all events - perEventSampling: true, // Enable event-specific sampling (default) - }); - - // This event should be rejected due to low eventSamplingRate (0.1 < 0.5) - collector.record({ event: 'TEST_EVENT', eventSamplingRate: 0.1, payload: {} }); - - jest.runAllTimers(); - - expect(fetchSpy).not.toHaveBeenCalled(); - - randomSpy.mockRestore(); - }); - }); - - describe('with client-side throttling', () => { - beforeEach(() => { - localStorage.clear(); - }); - - test('sends event when it is not in the cache', () => { - const collector = new TelemetryCollector({ - publishableKey: TEST_PK, - }); - - collector.record({ - event: 'TEST_EVENT', - payload: { - foo: true, - }, - }); - - jest.runAllTimers(); - - expect(fetchSpy).toHaveBeenCalled(); - - fetchSpy.mockRestore(); - }); - - test('sends event when it is in the cache but has expired', () => { - const originalDateNow = Date.now; - const cacheTtl = 86400000; - - let now = originalDateNow(); - const dateNowSpy = jest.spyOn(Date, 'now').mockImplementation(() => now); - - const collector = new TelemetryCollector({ - publishableKey: TEST_PK, - maxBufferSize: 1, - }); - - const event = 'TEST_EVENT'; - const payload = { - foo: true, - }; - - collector.record({ - event, - payload, - }); - - // Move time forward beyond the cache TTL - now += cacheTtl + 1; - - collector.record({ - event, - payload, - }); - - collector.record({ - event, - payload, - }); - - jest.runAllTimers(); - - expect(fetchSpy).toHaveBeenCalledTimes(2); - - dateNowSpy.mockRestore(); - }); - - test('does not send event when it is in the cache', () => { - const collector = new TelemetryCollector({ - publishableKey: TEST_PK, - }); - - const event = 'TEST_EVENT'; - - collector.record({ - event, - payload: { - foo: true, - }, - }); - - collector.record({ - event, - payload: { - foo: true, - }, - }); - - jest.runAllTimers(); - - expect(fetchSpy).toHaveBeenCalledTimes(1); - }); - - test('fallbacks to event-specific sampling rate when storage is not supported', () => { - windowSpy.mockImplementation(() => ({ - localStorage: undefined, - })); - - const randomSpy = jest.spyOn(Math, 'random').mockReturnValue(0.1); - - const collector = new TelemetryCollector({ - publishableKey: TEST_PK, - }); - - collector.record({ - event: 'TEST_EVENT', - eventSamplingRate: 0.01, - payload: { - foo: true, - }, - }); - - expect(fetchSpy).not.toHaveBeenCalled(); - - randomSpy.mockRestore(); - }); - - test('generates unique key without credentials based on event payload', () => { - const collector = new TelemetryCollector({ - publishableKey: TEST_PK, - }); - - const event: TelemetryEvent = { - sk: '123', - pk: TEST_PK, - it: 'development', - event: 'TEST_EVENT', - cv: '0.1', - sdkv: '0.1', - payload: { - foo: true, - }, - }; - - collector.record(event); - collector.record(event); - - jest.runAllTimers(); - - const item = localStorage.getItem('clerk_telemetry_throttler'); - assert(item); - const expectedKey = '["","TEST_EVENT",true,"development",null,null]'; - - expect(JSON.parse(item)[expectedKey]).toEqual(expect.any(Number)); - - fetchSpy.mockRestore(); - }); - }); - - describe('with in-memory throttling (React Native)', () => { - beforeEach(() => { - // Mock React Native environment - no window - windowSpy.mockImplementation(() => undefined); - }); - - test('throttles events using in-memory cache when localStorage is not available', () => { - const collector = new TelemetryCollector({ - publishableKey: TEST_PK, - }); - - const event = 'TEST_EVENT'; - const payload = { foo: true }; - - // First event should go through - collector.record({ event, payload }); - jest.runAllTimers(); - expect(fetchSpy).toHaveBeenCalledTimes(1); - - // Same event should be throttled - collector.record({ event, payload }); - jest.runAllTimers(); - expect(fetchSpy).toHaveBeenCalledTimes(1); - - // Different event should go through - collector.record({ event: 'DIFFERENT_EVENT', payload }); - jest.runAllTimers(); - expect(fetchSpy).toHaveBeenCalledTimes(2); - }); - - test('allows event after TTL expires in memory cache', () => { - const originalDateNow = Date.now; - const cacheTtl = 86400000; // 24 hours - - let now = originalDateNow(); - const dateNowSpy = jest.spyOn(Date, 'now').mockImplementation(() => now); - - const collector = new TelemetryCollector({ - publishableKey: TEST_PK, - maxBufferSize: 1, - }); - - const event = 'TEST_EVENT'; - const payload = { foo: true }; - - // First event - collector.record({ event, payload }); - - // Move time forward beyond the cache TTL - now += cacheTtl + 1; - - // Same event should now be allowed - collector.record({ event, payload }); - - jest.runAllTimers(); - - expect(fetchSpy).toHaveBeenCalledTimes(2); - - dateNowSpy.mockRestore(); - }); - - test('clears memory cache when it exceeds size limit', () => { - const collector = new TelemetryCollector({ - publishableKey: TEST_PK, - }); - - // Generate many different events to exceed the cache size limit - for (let i = 0; i < 10001; i++) { - collector.record({ - event: 'TEST_EVENT', - payload: { id: i }, - }); - } - - jest.runAllTimers(); - - // Should have been called for all events since cache was cleared - expect(fetchSpy).toHaveBeenCalled(); - }); - }); - - describe('error handling', () => { - test('record() method does not bubble up errors from internal operations', () => { - const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); - - const collector = new TelemetryCollector({ - publishableKey: TEST_PK, - }); - - const circularPayload = (() => { - const obj: any = { test: 'value' }; - obj.self = obj; - return obj; - })(); - - expect(() => { - collector.record({ event: 'TEST_EVENT', payload: circularPayload }); - }).not.toThrow(); - - expect(consoleErrorSpy).toHaveBeenCalledWith( - '[clerk/telemetry] Error recording telemetry event', - expect.any(Error), - ); - - consoleErrorSpy.mockRestore(); - }); - - test('record() method handles errors gracefully and continues operation', () => { - const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); - - const collector = new TelemetryCollector({ - publishableKey: TEST_PK, - }); - - const problematicPayload = (() => { - const obj: any = { test: 'value' }; - obj.self = obj; - return obj; - })(); - - expect(() => { - collector.record({ event: 'TEST_EVENT', payload: problematicPayload }); - }).not.toThrow(); - - expect(consoleErrorSpy).toHaveBeenCalledWith( - '[clerk/telemetry] Error recording telemetry event', - expect.any(Error), - ); - - expect(() => { - collector.record({ event: 'TEST_EVENT', payload: { normal: 'data' } }); - }).not.toThrow(); - - jest.runAllTimers(); - expect(fetchSpy).toHaveBeenCalled(); - - consoleErrorSpy.mockRestore(); - }); - }); -}); diff --git a/packages/shared/src/__tests__/underscore.test.ts b/packages/shared/src/__tests__/underscore.spec.ts similarity index 99% rename from packages/shared/src/__tests__/underscore.test.ts rename to packages/shared/src/__tests__/underscore.spec.ts index cc79525ab24..d4aceaba7d3 100644 --- a/packages/shared/src/__tests__/underscore.test.ts +++ b/packages/shared/src/__tests__/underscore.spec.ts @@ -1,3 +1,5 @@ +import { describe, expect, it } from 'vitest'; + import { deepCamelToSnake, deepSnakeToCamel, diff --git a/packages/shared/src/__tests__/url.test.ts b/packages/shared/src/__tests__/url.spec.ts similarity index 99% rename from packages/shared/src/__tests__/url.test.ts rename to packages/shared/src/__tests__/url.spec.ts index 5ed849b1aa5..4fcf79b3d0a 100644 --- a/packages/shared/src/__tests__/url.test.ts +++ b/packages/shared/src/__tests__/url.spec.ts @@ -1,3 +1,5 @@ +import { describe, expect, it, test } from 'vitest'; + import { addClerkPrefix, cleanDoubleSlashes, diff --git a/packages/shared/src/__tests__/versionSelector.test.ts b/packages/shared/src/__tests__/versionSelector.spec.ts similarity index 97% rename from packages/shared/src/__tests__/versionSelector.test.ts rename to packages/shared/src/__tests__/versionSelector.spec.ts index c839fe3c95f..97feba8fac9 100644 --- a/packages/shared/src/__tests__/versionSelector.test.ts +++ b/packages/shared/src/__tests__/versionSelector.spec.ts @@ -1,4 +1,6 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ +import { describe, expect, it, test } from 'vitest'; + import { versionSelector } from '../versionSelector'; describe('versionSelector', () => { diff --git a/packages/shared/src/react/__tests__/useReverification.test.ts b/packages/shared/src/react/__tests__/useReverification.spec.ts similarity index 87% rename from packages/shared/src/react/__tests__/useReverification.test.ts rename to packages/shared/src/react/__tests__/useReverification.spec.ts index b1c32581fb8..c8a59dbaa0e 100644 --- a/packages/shared/src/react/__tests__/useReverification.test.ts +++ b/packages/shared/src/react/__tests__/useReverification.spec.ts @@ -1,13 +1,14 @@ import { act, renderHook } from '@testing-library/react'; import { expectTypeOf } from 'expect-type'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; import { reverificationError } from '../../authorization-errors'; import { useReverification as useReverificationImp } from '../hooks/useReverification'; -jest.mock('../hooks/useClerk', () => { +vi.mock('../hooks/useClerk', () => { const mockClerk = { - __internal_openReverification: jest.fn(), - telemetry: { record: jest.fn() }, + __internal_openReverification: vi.fn(), + telemetry: { record: vi.fn() }, }; return { useClerk: () => mockClerk, @@ -53,14 +54,14 @@ describe('useReverification type tests', () => { }); }); describe('useReverification', () => { - const mockFetcherInner = jest.fn().mockResolvedValue({ ok: true }); + const mockFetcherInner = vi.fn().mockResolvedValue({ ok: true }); beforeEach(() => { mockFetcherInner.mockClear(); }); it('returns a stable function reference across re-renders when fetcher is stable', () => { - const stableFetcher = jest.fn().mockResolvedValue({ data: 'test' }); + const stableFetcher = vi.fn().mockResolvedValue({ data: 'test' }); const { result, rerender } = renderHook(() => useReverificationImp(stableFetcher)); const firstResult = result.current; @@ -72,7 +73,7 @@ describe('useReverification', () => { }); it('keeps the same handler even when an inline fetcher changes on every render', async () => { - const fetchSpy = jest.fn(async v => ({ v })); + const fetchSpy = vi.fn(async v => ({ v })); const { result, rerender } = renderHook(({ value }) => useReverificationImp(() => fetchSpy(value)), { initialProps: { value: 'A' }, }); diff --git a/packages/shared/src/router/__tests__/router.test.ts b/packages/shared/src/router/__tests__/router.spec.ts similarity index 95% rename from packages/shared/src/router/__tests__/router.test.ts rename to packages/shared/src/router/__tests__/router.spec.ts index df1e5a3f497..a8b96da4630 100644 --- a/packages/shared/src/router/__tests__/router.test.ts +++ b/packages/shared/src/router/__tests__/router.spec.ts @@ -1,18 +1,20 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; + import { createClerkRouter } from '../router'; describe('createClerkRouter', () => { const mockRouter = { name: 'mockRouter', mode: 'path' as const, - pathname: jest.fn(), - searchParams: jest.fn(), - push: jest.fn(), - shallowPush: jest.fn(), - replace: jest.fn(), + pathname: vi.fn(), + searchParams: vi.fn(), + push: vi.fn(), + shallowPush: vi.fn(), + replace: vi.fn(), }; beforeEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); it('creates a ClerkRouter instance with the correct base path', () => { diff --git a/packages/shared/src/telemetry/events/__tests__/theme-usage.test.ts b/packages/shared/src/telemetry/events/__tests__/theme-usage.spec.ts similarity index 98% rename from packages/shared/src/telemetry/events/__tests__/theme-usage.test.ts rename to packages/shared/src/telemetry/events/__tests__/theme-usage.spec.ts index a5165b29298..c86dd75b53d 100644 --- a/packages/shared/src/telemetry/events/__tests__/theme-usage.test.ts +++ b/packages/shared/src/telemetry/events/__tests__/theme-usage.spec.ts @@ -1,3 +1,5 @@ +import { describe, expect, it } from 'vitest'; + import { EVENT_SAMPLING_RATE, EVENT_THEME_USAGE, eventThemeUsage } from '../theme-usage'; describe('eventThemeUsage', () => { diff --git a/packages/shared/src/utils/__tests__/createDeferredPromise.test.ts b/packages/shared/src/utils/__tests__/createDeferredPromise.spec.ts similarity index 93% rename from packages/shared/src/utils/__tests__/createDeferredPromise.test.ts rename to packages/shared/src/utils/__tests__/createDeferredPromise.spec.ts index 9cd04a20e9d..667cd86a50b 100644 --- a/packages/shared/src/utils/__tests__/createDeferredPromise.test.ts +++ b/packages/shared/src/utils/__tests__/createDeferredPromise.spec.ts @@ -1,3 +1,5 @@ +import { describe, expect, test } from 'vitest'; + import { createDeferredPromise } from '../createDeferredPromise'; describe('createDeferredPromise', () => { diff --git a/packages/shared/src/utils/__tests__/instance.test.ts b/packages/shared/src/utils/__tests__/instance.spec.ts similarity index 93% rename from packages/shared/src/utils/__tests__/instance.test.ts rename to packages/shared/src/utils/__tests__/instance.spec.ts index c877a195063..9e2d7598299 100644 --- a/packages/shared/src/utils/__tests__/instance.test.ts +++ b/packages/shared/src/utils/__tests__/instance.spec.ts @@ -1,3 +1,5 @@ +import { describe, expect, it } from 'vitest'; + import { isStaging } from '../instance'; describe('isStaging', () => { diff --git a/packages/shared/vitest.setup.mts b/packages/shared/vitest.setup.mts index 46a9c434051..4a7eab9d28d 100644 --- a/packages/shared/vitest.setup.mts +++ b/packages/shared/vitest.setup.mts @@ -1,8 +1,22 @@ -import { afterEach } from 'vitest'; +import { webcrypto } from 'node:crypto'; + import { cleanup } from '@testing-library/react'; +import { afterEach } from 'vitest'; globalThis.__DEV__ = true; globalThis.PACKAGE_NAME = '@clerk/clerk-react'; globalThis.PACKAGE_VERSION = '0.0.0-test'; +globalThis.JS_PACKAGE_VERSION = '5.0.0'; + +// Setup Web Crypto API for tests (Node.js 18+ compatibility) +if (!globalThis.crypto) { + // @ts-ignore - Node.js 18+ Web Crypto API + globalThis.crypto = webcrypto as Crypto; +} +// Ensure crypto.subtle is available (needed for Node.js 18) +if (globalThis.crypto && !globalThis.crypto.subtle) { + // @ts-ignore + globalThis.crypto.subtle = webcrypto.subtle; +} afterEach(cleanup);