From 099496341a052ecb6989848a00c3c07f61b1b174 Mon Sep 17 00:00:00 2001 From: panteliselef Date: Thu, 9 Jan 2025 17:14:41 +0200 Subject: [PATCH] fix(clerk-js): Fix custom username field error to allow fallbacks (#4858) --- .changeset/hip-spiders-punch.md | 5 ++ .../SignUp/__tests__/SignUpContinue.test.tsx | 86 ++++++++++++++++++- .../src/ui/utils/test/fixtureHelpers.ts | 6 ++ .../clerk-js/src/ui/utils/usernameUtils.ts | 27 ++++-- 4 files changed, 113 insertions(+), 11 deletions(-) create mode 100644 .changeset/hip-spiders-punch.md diff --git a/.changeset/hip-spiders-punch.md b/.changeset/hip-spiders-punch.md new file mode 100644 index 0000000000..82b9bb5775 --- /dev/null +++ b/.changeset/hip-spiders-punch.md @@ -0,0 +1,5 @@ +--- +'@clerk/clerk-js': patch +--- + +Fixes username form field errors to display messages according to the respective code sent in the error response. diff --git a/packages/clerk-js/src/ui/components/SignUp/__tests__/SignUpContinue.test.tsx b/packages/clerk-js/src/ui/components/SignUp/__tests__/SignUpContinue.test.tsx index e058ca32fd..0db3dc269a 100644 --- a/packages/clerk-js/src/ui/components/SignUp/__tests__/SignUpContinue.test.tsx +++ b/packages/clerk-js/src/ui/components/SignUp/__tests__/SignUpContinue.test.tsx @@ -1,7 +1,9 @@ -import { OAUTH_PROVIDERS } from '@clerk/types'; +import { ClerkAPIResponseError } from '@clerk/shared/error'; +import { OAUTH_PROVIDERS } from '@clerk/shared/oauth'; +import { waitFor } from '@testing-library/dom'; import React from 'react'; -import { render, screen } from '../../../../testUtils'; +import { render, runFakeTimers, screen } from '../../../../testUtils'; import { bindCreateFixtures } from '../../../utils/test/createFixtures'; import { SignUpContinue } from '../SignUpContinue'; @@ -106,6 +108,86 @@ describe('SignUpContinue', () => { screen.getByText(`Continue with ${name}`); }); + it('renders error for invalid username length', async () => { + const { wrapper, fixtures } = await createFixtures(f => { + f.withEmailAddress({ required: true }); + f.withUsername({ required: true }); + f.startSignUpWithEmailAddress({ + emailVerificationStatus: 'verified', + }); + }); + + fixtures.signUp.update.mockRejectedValue( + new ClerkAPIResponseError('Error', { + data: [ + { + code: 'form_username_invalid_length', + long_message: 'some server error', + message: 'some server error', + meta: { param_name: 'username' }, + }, + ], + status: 400, + }), + ); + + await runFakeTimers(async timers => { + const { userEvent } = render(, { wrapper }); + expect(screen.queryByText(/username/i)).toBeInTheDocument(); + await userEvent.type(screen.getByLabelText(/username/i), 'clerkUser'); + timers.runOnlyPendingTimers(); + const button = screen.getByText('Continue'); + await userEvent.click(button); + timers.runOnlyPendingTimers(); + + await waitFor(() => expect(fixtures.signUp.update).toHaveBeenCalled()); + timers.runOnlyPendingTimers(); + await waitFor(() => + expect(screen.queryByText(/^Your username must be between 4 and 40 characters long./i)).toBeInTheDocument(), + ); + }); + }); + + it('renders error for existing username', async () => { + const { wrapper, fixtures } = await createFixtures(f => { + f.withEmailAddress({ required: true }); + f.withUsername({ required: true }); + f.startSignUpWithEmailAddress({ + emailVerificationStatus: 'verified', + }); + }); + + fixtures.signUp.update.mockRejectedValue( + new ClerkAPIResponseError('Error', { + data: [ + { + code: 'form_identifier_exists', + long_message: 'some server error', + message: 'some server error', + meta: { param_name: 'username' }, + }, + ], + status: 400, + }), + ); + + await runFakeTimers(async timers => { + const { userEvent } = render(, { wrapper }); + expect(screen.queryByText(/username/i)).toBeInTheDocument(); + await userEvent.type(screen.getByLabelText(/username/i), 'clerkUser'); + timers.runOnlyPendingTimers(); + const button = screen.getByText('Continue'); + await userEvent.click(button); + timers.runOnlyPendingTimers(); + + await waitFor(() => expect(fixtures.signUp.update).toHaveBeenCalled()); + timers.runOnlyPendingTimers(); + await waitFor(() => + expect(screen.queryByText(/^This username is taken. Please try another./i)).toBeInTheDocument(), + ); + }); + }); + describe('Sign in Link', () => { it('Shows the Sign In message with the appropriate link', async () => { const { wrapper, fixtures } = await createFixtures(f => { diff --git a/packages/clerk-js/src/ui/utils/test/fixtureHelpers.ts b/packages/clerk-js/src/ui/utils/test/fixtureHelpers.ts index 4edbdaa623..618589791b 100644 --- a/packages/clerk-js/src/ui/utils/test/fixtureHelpers.ts +++ b/packages/clerk-js/src/ui/utils/test/fixtureHelpers.ts @@ -250,6 +250,7 @@ const createSignUpFixtureHelpers = (baseClient: ClientJSON) => { status: emailVerificationStatus, }, }, + missing_fields: [], } as SignUpJSON; }; @@ -341,6 +342,11 @@ const createUserSettingsFixtureHelpers = (environment: EnvironmentJSON) => { mode: SIGN_UP_MODES.PUBLIC, }; + us.username_settings = { + min_length: 4, + max_length: 40, + }; + const emptyAttribute = { first_factors: [], second_factors: [], diff --git a/packages/clerk-js/src/ui/utils/usernameUtils.ts b/packages/clerk-js/src/ui/utils/usernameUtils.ts index 8cf8732843..b198998793 100644 --- a/packages/clerk-js/src/ui/utils/usernameUtils.ts +++ b/packages/clerk-js/src/ui/utils/usernameUtils.ts @@ -8,19 +8,28 @@ type LocalizationConfigProps = { usernameSettings: Pick; }; -export const createUsernameError = (errors: ClerkAPIError[], localizationConfig: LocalizationConfigProps) => { +const INVALID_LENGTH = 'form_username_invalid_length'; + +export const createUsernameError = ( + errors: ClerkAPIError[], + localizationConfig: LocalizationConfigProps, +): ClerkAPIError | string | undefined => { const { t, usernameSettings } = localizationConfig; + const clerkApiError = errors[0] as ClerkAPIError | undefined; + if (!localizationConfig) { - return errors[0].longMessage; + return clerkApiError; } - const msg = t( - localizationKeys('unstable__errors.form_username_invalid_length', { - min_length: usernameSettings.min_length, - max_length: usernameSettings.max_length, - }), - ); + if (clerkApiError?.code === INVALID_LENGTH) { + return t( + localizationKeys(`unstable__errors.${INVALID_LENGTH}`, { + min_length: usernameSettings.min_length, + max_length: usernameSettings.max_length, + }), + ); + } - return msg; + return clerkApiError; };