diff --git a/.changeset/major-parks-study.md b/.changeset/major-parks-study.md new file mode 100644 index 00000000000..55bf83977a6 --- /dev/null +++ b/.changeset/major-parks-study.md @@ -0,0 +1,5 @@ +--- +"@clerk/clerk-js": patch +--- + +Support both `clerkUICtor` and `clerkUiCtor` option names for backwards compatibility diff --git a/packages/clerk-js/src/core/__tests__/clerk.test.ts b/packages/clerk-js/src/core/__tests__/clerk.test.ts index ab27b1d1646..f46c3a4686e 100644 --- a/packages/clerk-js/src/core/__tests__/clerk.test.ts +++ b/packages/clerk-js/src/core/__tests__/clerk.test.ts @@ -2732,4 +2732,69 @@ describe('Clerk singleton', () => { }); }); }); + + describe('clerkUICtor backwards compatibility', () => { + beforeEach(() => { + mockEnvironmentFetch.mockReturnValue( + Promise.resolve({ + userSettings: mockUserSettings, + displayConfig: mockDisplayConfig, + isSingleSession: () => false, + isProduction: () => true, + isDevelopmentOrStaging: () => false, + }), + ); + mockClientFetch.mockReturnValue( + Promise.resolve({ + signedInSessions: [], + }), + ); + }); + + it('uses clerkUICtor when provided with correct casing', async () => { + const mockClerkUIInstance = { mount: vi.fn() }; + const mockClerkUICtor = vi.fn(() => mockClerkUIInstance); + + const sut = new Clerk(productionPublishableKey); + await sut.load({ + ...mockedLoadOptions, + clerkUICtor: mockClerkUICtor, + }); + + expect(mockClerkUICtor).toHaveBeenCalled(); + }); + + it('uses clerkUiCtor (legacy casing) for backwards compatibility', async () => { + const mockClerkUIInstance = { mount: vi.fn() }; + const mockClerkUICtor = vi.fn(() => mockClerkUIInstance); + + const sut = new Clerk(productionPublishableKey); + // Use legacy casing - cast to bypass TypeScript since clerkUiCtor is not in the type + await sut.load({ + ...mockedLoadOptions, + clerkUiCtor: mockClerkUICtor, + } as Parameters[0]); + + expect(mockClerkUICtor).toHaveBeenCalled(); + }); + + it('prefers clerkUICtor over clerkUiCtor when both are provided', async () => { + const mockClerkUIInstanceCorrect = { mount: vi.fn() }; + const mockClerkUICtorCorrect = vi.fn(() => mockClerkUIInstanceCorrect); + + const mockClerkUIInstanceLegacy = { mount: vi.fn() }; + const mockClerkUICtorLegacy = vi.fn(() => mockClerkUIInstanceLegacy); + + const sut = new Clerk(productionPublishableKey); + // Provide both - the correct casing should take precedence + await sut.load({ + ...mockedLoadOptions, + clerkUICtor: mockClerkUICtorCorrect, + clerkUiCtor: mockClerkUICtorLegacy, + } as Parameters[0]); + + expect(mockClerkUICtorCorrect).toHaveBeenCalled(); + expect(mockClerkUICtorLegacy).not.toHaveBeenCalled(); + }); + }); }); diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts index 01395bac1b4..52ad8d2a7df 100644 --- a/packages/clerk-js/src/core/clerk.ts +++ b/packages/clerk-js/src/core/clerk.ts @@ -3248,9 +3248,14 @@ export class Clerk implements ClerkInterface { }; #initOptions = (options?: ClerkOptions): ClerkOptions => { + // Support both clerkUICtor (correct) and clerkUiCtor (legacy) for backwards compatibility + const clerkUICtor = + options?.clerkUICtor ?? (options as Record | undefined)?.clerkUiCtor ?? undefined; + return { ...defaultOptions, ...options, + clerkUICtor: clerkUICtor as ClerkOptions['clerkUICtor'], allowedRedirectOrigins: createAllowedRedirectOrigins( options?.allowedRedirectOrigins, this.frontendApi,