From 81b341a28d867bf7c7fef703906c2c02be0de7b0 Mon Sep 17 00:00:00 2001 From: Vaggeilis Yfantis Date: Wed, 4 Feb 2026 15:47:06 +0200 Subject: [PATCH] tests(ui): Skip the strategy selection screen if it is the only available on setup mfa session task --- .changeset/pretty-queens-relate.md | 5 +++ .../__tests__/TaskSetupMfa.test.tsx | 44 +++++++++++++++---- .../SessionTasks/tasks/TaskSetupMfa/index.tsx | 12 ++++- 3 files changed, 51 insertions(+), 10 deletions(-) create mode 100644 .changeset/pretty-queens-relate.md diff --git a/.changeset/pretty-queens-relate.md b/.changeset/pretty-queens-relate.md new file mode 100644 index 00000000000..6a174ff05c2 --- /dev/null +++ b/.changeset/pretty-queens-relate.md @@ -0,0 +1,5 @@ +--- +'@clerk/ui': patch +--- + +Skip the strategy selection screen if only one MFA strategy is available for the setup MFA session task diff --git a/packages/ui/src/components/SessionTasks/tasks/TaskSetupMfa/__tests__/TaskSetupMfa.test.tsx b/packages/ui/src/components/SessionTasks/tasks/TaskSetupMfa/__tests__/TaskSetupMfa.test.tsx index 28fb0685422..1323ae0e5d5 100644 --- a/packages/ui/src/components/SessionTasks/tasks/TaskSetupMfa/__tests__/TaskSetupMfa.test.tsx +++ b/packages/ui/src/components/SessionTasks/tasks/TaskSetupMfa/__tests__/TaskSetupMfa.test.tsx @@ -33,6 +33,8 @@ describe('TaskSetupMFA', () => { identifier: 'test@clerk.com', tasks: [{ key: 'setup-mfa' }], }); + f.withAuthenticatorApp({ enabled: true }); + f.withPhoneNumber({ second_factors: ['phone_code'], used_for_second_factor: true }); }); const { queryByText, queryByRole } = render(, { wrapper }); @@ -60,7 +62,7 @@ describe('TaskSetupMFA', () => { expect(getByRole('button', { name: /sms code/i })).toBeInTheDocument(); }); - it('should render SMS code item on the first screen if only SMS code is enabled', async () => { + it('should skip selection screen and go directly to SMS code flow when only SMS code is enabled', async () => { const { wrapper } = await createFixtures(f => { f.withUser({ email_addresses: ['test@clerk.com'], @@ -70,14 +72,14 @@ describe('TaskSetupMFA', () => { f.withPhoneNumber({ second_factors: ['phone_code'], used_for_second_factor: true }); }); - const { getByRole, queryByRole } = render(, { wrapper }); + const { findByText, queryByText } = render(, { wrapper }); - expect(queryByRole('button', { name: /authenticator application/i })).not.toBeInTheDocument(); - expect(getByRole('button', { name: /sms code/i })).toBeInTheDocument(); + await findByText(/add phone number/i); + expect(queryByText(/set up two-step verification/i)).not.toBeInTheDocument(); }); - it('should render TOTP item on the first screen if only TOTP is enabled', async () => { - const { wrapper } = await createFixtures(f => { + it('should skip selection screen and go directly to TOTP flow when only TOTP is enabled', async () => { + const { wrapper, fixtures } = await createFixtures(f => { f.withUser({ email_addresses: ['test@clerk.com'], identifier: 'test@clerk.com', @@ -86,10 +88,15 @@ describe('TaskSetupMFA', () => { f.withAuthenticatorApp({ enabled: true }); }); - const { getByRole, queryByRole } = render(, { wrapper }); + fixtures.clerk.user?.createTOTP.mockResolvedValue({ + uri: 'otpauth://totp/Test:test@clerk.com?secret=TESTSECRET&issuer=Test', + secret: 'TESTSECRET', + } as TOTPResource); + + const { findByText, queryByText } = render(, { wrapper }); - expect(getByRole('button', { name: /authenticator application/i })).toBeInTheDocument(); - expect(queryByRole('button', { name: /sms code/i })).not.toBeInTheDocument(); + await findByText(/add authenticator application/i); + expect(queryByText(/set up two-step verification/i)).not.toBeInTheDocument(); }); }); describe('authenticator application', () => { @@ -101,6 +108,7 @@ describe('TaskSetupMFA', () => { tasks: [{ key: 'setup-mfa' }], }); f.withAuthenticatorApp({ enabled: true }); + f.withPhoneNumber({ second_factors: ['phone_code'], used_for_second_factor: true }); }); fixtures.clerk.user?.createTOTP.mockResolvedValue({ @@ -127,6 +135,7 @@ describe('TaskSetupMFA', () => { tasks: [{ key: 'setup-mfa' }], }); f.withAuthenticatorApp({ enabled: true }); + f.withPhoneNumber({ second_factors: ['phone_code'], used_for_second_factor: true }); }); fixtures.clerk.user?.createTOTP.mockResolvedValue({ @@ -159,6 +168,7 @@ describe('TaskSetupMFA', () => { tasks: [{ key: 'setup-mfa' }], }); f.withAuthenticatorApp({ enabled: true }); + f.withPhoneNumber({ second_factors: ['phone_code'], used_for_second_factor: true }); }); fixtures.clerk.user?.createTOTP.mockResolvedValue({ @@ -190,6 +200,7 @@ describe('TaskSetupMFA', () => { tasks: [{ key: 'setup-mfa' }], }); f.withAuthenticatorApp({ enabled: true }); + f.withPhoneNumber({ second_factors: ['phone_code'], used_for_second_factor: true }); f.withBackupCode(); }); @@ -234,6 +245,7 @@ describe('TaskSetupMFA', () => { tasks: [{ key: 'setup-mfa' }], }); f.withAuthenticatorApp({ enabled: true }); + f.withPhoneNumber({ second_factors: ['phone_code'], used_for_second_factor: true }); }); fixtures.clerk.user?.createTOTP.mockResolvedValue({ @@ -275,6 +287,7 @@ describe('TaskSetupMFA', () => { tasks: [{ key: 'setup-mfa' }], }); f.withAuthenticatorApp({ enabled: true }); + f.withPhoneNumber({ second_factors: ['phone_code'], used_for_second_factor: true }); }); fixtures.clerk.user?.createTOTP.mockResolvedValue({ @@ -351,6 +364,7 @@ describe('TaskSetupMFA', () => { tasks: [{ key: 'setup-mfa' }], }); f.withAuthenticatorApp({ enabled: true }); + f.withPhoneNumber({ second_factors: ['phone_code'], used_for_second_factor: true }); }); fixtures.clerk.user?.createTOTP.mockRejectedValueOnce( @@ -384,6 +398,7 @@ describe('TaskSetupMFA', () => { tasks: [{ key: 'setup-mfa' }], }); f.withAuthenticatorApp({ enabled: true }); + f.withPhoneNumber({ second_factors: ['phone_code'], used_for_second_factor: true }); }); fixtures.clerk.user?.createTOTP.mockResolvedValue({ @@ -438,6 +453,7 @@ describe('TaskSetupMFA', () => { phone_numbers: [{ phone_number: '+306911111111', id: 'phone_1' }], tasks: [{ key: 'setup-mfa' }], }); + f.withAuthenticatorApp({ enabled: true }); f.withPhoneNumber({ second_factors: ['phone_code'], used_for_second_factor: true }); }); @@ -460,6 +476,7 @@ describe('TaskSetupMFA', () => { identifier: 'test@clerk.com', tasks: [{ key: 'setup-mfa' }], }); + f.withAuthenticatorApp({ enabled: true }); f.withPhoneNumber({ second_factors: ['phone_code'], used_for_second_factor: true }); }); @@ -481,6 +498,7 @@ describe('TaskSetupMFA', () => { phone_numbers: [{ phone_number: '+306911111111', id: 'phone_1' }], tasks: [{ key: 'setup-mfa' }], }); + f.withAuthenticatorApp({ enabled: true }); f.withPhoneNumber({ second_factors: ['phone_code'], used_for_second_factor: true }); }); @@ -514,6 +532,7 @@ describe('TaskSetupMFA', () => { ], tasks: [{ key: 'setup-mfa' }], }); + f.withAuthenticatorApp({ enabled: true }); f.withPhoneNumber({ second_factors: ['phone_code'], used_for_second_factor: true }); f.withBackupCode(); }); @@ -556,6 +575,7 @@ describe('TaskSetupMFA', () => { ], tasks: [{ key: 'setup-mfa' }], }); + f.withAuthenticatorApp({ enabled: true }); f.withPhoneNumber({ second_factors: ['phone_code'], used_for_second_factor: true }); }); @@ -602,6 +622,7 @@ describe('TaskSetupMFA', () => { ], tasks: [{ key: 'setup-mfa' }], }); + f.withAuthenticatorApp({ enabled: true }); f.withPhoneNumber({ second_factors: ['phone_code'], used_for_second_factor: true }); }); @@ -662,6 +683,7 @@ describe('TaskSetupMFA', () => { ], tasks: [{ key: 'setup-mfa' }], }); + f.withAuthenticatorApp({ enabled: true }); f.withPhoneNumber({ second_factors: ['phone_code'], used_for_second_factor: true }); }); @@ -705,6 +727,7 @@ describe('TaskSetupMFA', () => { identifier: 'test@clerk.com', tasks: [{ key: 'setup-mfa' }], }); + f.withAuthenticatorApp({ enabled: true }); f.withPhoneNumber({ second_factors: ['phone_code'], used_for_second_factor: true }); }); @@ -746,6 +769,7 @@ describe('TaskSetupMFA', () => { identifier: 'test@clerk.com', tasks: [{ key: 'setup-mfa' }], }); + f.withAuthenticatorApp({ enabled: true }); f.withPhoneNumber({ second_factors: ['phone_code'], used_for_second_factor: true }); }); @@ -794,6 +818,7 @@ describe('TaskSetupMFA', () => { ], tasks: [{ key: 'setup-mfa' }], }); + f.withAuthenticatorApp({ enabled: true }); f.withPhoneNumber({ second_factors: ['phone_code'], used_for_second_factor: true }); }); @@ -846,6 +871,7 @@ describe('TaskSetupMFA', () => { ], tasks: [{ key: 'setup-mfa' }], }); + f.withAuthenticatorApp({ enabled: true }); f.withPhoneNumber({ second_factors: ['phone_code'], used_for_second_factor: true }); }); diff --git a/packages/ui/src/components/SessionTasks/tasks/TaskSetupMfa/index.tsx b/packages/ui/src/components/SessionTasks/tasks/TaskSetupMfa/index.tsx index b031231d766..299031a1ece 100644 --- a/packages/ui/src/components/SessionTasks/tasks/TaskSetupMfa/index.tsx +++ b/packages/ui/src/components/SessionTasks/tasks/TaskSetupMfa/index.tsx @@ -13,6 +13,12 @@ import { SetupMfaStartScreen } from './SetupMfaStartScreen'; import { SmsCodeFlow } from './SmsCodeFlowScreen'; import { TOTPCodeFlow } from './TOTPCodeFlowScreen'; +const WIZARD_STEPS = { + start: 0, + phoneCode: 1, + totp: 2, +} as const; + const TaskSetupMFAInternal = () => { const clerk = useClerk(); const { user } = useUser(); @@ -38,7 +44,11 @@ const TaskSetupMFAInternal = () => { }); }, [attributes, user]); - const wizard = useWizard({ defaultStep: 0 }); + const defaultStep = + secondFactorsAvailableToAdd.indexOf('phone_code') > -1 ? WIZARD_STEPS.phoneCode : WIZARD_STEPS.totp; + const wizard = useWizard({ + defaultStep: secondFactorsAvailableToAdd.length > 1 ? WIZARD_STEPS.start : defaultStep, + }); const { redirectUrlComplete } = useTaskSetupMFAContext(); const { navigateOnSetActive, redirectOnActiveSession } = useSessionTasksContext();