From 18b0964dec166377cc3d7d360e9068604526e8d6 Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Thu, 7 May 2026 09:47:51 -0700 Subject: [PATCH 1/4] fix(ui): Add missing noop methods to unsafeMetadata field in SignUpStart --- packages/ui/src/components/SignUp/SignUpStart.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/components/SignUp/SignUpStart.tsx b/packages/ui/src/components/SignUp/SignUpStart.tsx index 8400c8984ce..e719bbaf5c3 100644 --- a/packages/ui/src/components/SignUp/SignUpStart.tsx +++ b/packages/ui/src/components/SignUp/SignUpStart.tsx @@ -259,7 +259,15 @@ function SignUpStartInternal(): JSX.Element { }, [] as Array); if (unsafeMetadata) { - fieldsToSubmit.push({ id: 'unsafeMetadata', value: unsafeMetadata } as any); + const noop = () => {}; + fieldsToSubmit.push({ + id: 'unsafeMetadata', + value: unsafeMetadata, + clearFeedback: noop, + setValue: noop, + onChange: noop, + setError: noop, + } as any); } if (fields.ticket || hasExistingSignUpWithTicket) { From d14c415de2de49e7d6e4fec18df64c88debbe1b7 Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Thu, 7 May 2026 09:53:03 -0700 Subject: [PATCH 2/4] chore: add test --- .../SignUp/__tests__/SignUpStart.test.tsx | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/packages/ui/src/components/SignUp/__tests__/SignUpStart.test.tsx b/packages/ui/src/components/SignUp/__tests__/SignUpStart.test.tsx index e85ee5184fa..1bab83ea1de 100644 --- a/packages/ui/src/components/SignUp/__tests__/SignUpStart.test.tsx +++ b/packages/ui/src/components/SignUp/__tests__/SignUpStart.test.tsx @@ -1,3 +1,4 @@ +import { ClerkAPIResponseError } from '@clerk/shared/error'; import { OAUTH_PROVIDERS } from '@clerk/shared/oauth'; import type { SignUpResource } from '@clerk/shared/types'; import { describe, expect, it, vi } from 'vitest'; @@ -452,6 +453,45 @@ describe('SignUpStart', () => { ); }); + it('does not throw when unsafeMetadata is set and signUp.create rejects with an API error', async () => { + const { wrapper, fixtures, props } = await createFixtures(f => { + f.withEmailAddress(); + f.withPassword(); + }); + fixtures.signUp.create.mockRejectedValueOnce( + new ClerkAPIResponseError('Error', { + data: [ + { + code: 'form_password_pwned', + long_message: 'Password has been found in an online data breach.', + message: 'Password has been found in an online data breach.', + meta: { param_name: 'password' }, + }, + ], + status: 422, + }), + ); + props.setProps({ unsafeMetadata: { foo: 'bar' } }); + + Object.defineProperty(window, 'location', { + writable: true, + value: { href: 'http://localhost/sign-up?__clerk_ticket=test_ticket' }, + }); + Object.defineProperty(window, 'history', { + writable: true, + value: { replaceState: vi.fn() }, + }); + + render( + + + , + { wrapper }, + ); + + await waitFor(() => expect(fixtures.signUp.create).toHaveBeenCalled()); + }); + it('removes the ticket from the url when completing the sign up', async () => { const { wrapper, fixtures } = await createFixtures(f => { f.withEmailAddress(); From d2fb45b09759dbf94a8a784632fb0060886f384e Mon Sep 17 00:00:00 2001 From: Robert Soriano Date: Thu, 7 May 2026 09:53:41 -0700 Subject: [PATCH 3/4] chore: add changeset --- .changeset/wild-insects-design.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/wild-insects-design.md diff --git a/.changeset/wild-insects-design.md b/.changeset/wild-insects-design.md new file mode 100644 index 00000000000..b0051fad7ec --- /dev/null +++ b/.changeset/wild-insects-design.md @@ -0,0 +1,5 @@ +--- +"@clerk/ui": patch +--- + +Fixed unhandled TypeError when `unsafeMetadata` is passed to `` From 1f95f072d2ba3af86f459e039e398483f0c3a467 Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Thu, 7 May 2026 11:05:26 -0700 Subject: [PATCH 4/4] chore: address comments and verify test fails when fix is removed --- .../SignUp/__tests__/SignUpStart.test.tsx | 92 +++++++++++-------- 1 file changed, 53 insertions(+), 39 deletions(-) diff --git a/packages/ui/src/components/SignUp/__tests__/SignUpStart.test.tsx b/packages/ui/src/components/SignUp/__tests__/SignUpStart.test.tsx index 1bab83ea1de..d5f4349e365 100644 --- a/packages/ui/src/components/SignUp/__tests__/SignUpStart.test.tsx +++ b/packages/ui/src/components/SignUp/__tests__/SignUpStart.test.tsx @@ -453,45 +453,6 @@ describe('SignUpStart', () => { ); }); - it('does not throw when unsafeMetadata is set and signUp.create rejects with an API error', async () => { - const { wrapper, fixtures, props } = await createFixtures(f => { - f.withEmailAddress(); - f.withPassword(); - }); - fixtures.signUp.create.mockRejectedValueOnce( - new ClerkAPIResponseError('Error', { - data: [ - { - code: 'form_password_pwned', - long_message: 'Password has been found in an online data breach.', - message: 'Password has been found in an online data breach.', - meta: { param_name: 'password' }, - }, - ], - status: 422, - }), - ); - props.setProps({ unsafeMetadata: { foo: 'bar' } }); - - Object.defineProperty(window, 'location', { - writable: true, - value: { href: 'http://localhost/sign-up?__clerk_ticket=test_ticket' }, - }); - Object.defineProperty(window, 'history', { - writable: true, - value: { replaceState: vi.fn() }, - }); - - render( - - - , - { wrapper }, - ); - - await waitFor(() => expect(fixtures.signUp.create).toHaveBeenCalled()); - }); - it('removes the ticket from the url when completing the sign up', async () => { const { wrapper, fixtures } = await createFixtures(f => { f.withEmailAddress(); @@ -562,4 +523,57 @@ describe('SignUpStart', () => { await waitFor(() => screen.getByText(/create your account/i)); }); }); + + describe('unsafeMetadata', () => { + it('does not throw when signUp.create rejects with an API error', async () => { + Object.defineProperty(window, 'location', { + writable: true, + value: { href: 'http://localhost/sign-up' }, + }); + + let unhandledError: unknown = null; + const onUnhandledRejection = (reason: unknown) => { + unhandledError = reason; + }; + process.on('unhandledRejection', onUnhandledRejection); + + const { wrapper, fixtures, props } = await createFixtures(f => { + f.withEmailAddress({ required: true }); + f.withPassword({ required: true }); + }); + fixtures.signUp.create.mockRejectedValueOnce( + new ClerkAPIResponseError('Error', { + data: [ + { + code: 'form_password_pwned', + long_message: 'Password has been found in an online data breach.', + message: 'Password has been found in an online data breach.', + meta: { param_name: 'password' }, + }, + ], + status: 422, + }), + ); + props.setProps({ unsafeMetadata: { foo: 'bar' } }); + + const { userEvent } = render( + + + , + { wrapper }, + ); + + await userEvent.type(screen.getByLabelText(/email address/i), 'test@example.com'); + await userEvent.type(screen.getByPlaceholderText(/create a password/i), 'password123'); + await userEvent.click(screen.getByText(/continue/i)); + + await waitFor(() => expect(fixtures.signUp.create).toHaveBeenCalled()); + await screen.findByTestId('form-feedback-error'); + // Flush pending microtasks so any unhandled rejection event has a chance to fire. + await new Promise(resolve => setTimeout(resolve, 0)); + + process.off('unhandledRejection', onUnhandledRejection); + expect(unhandledError).toBeNull(); + }); + }); });