@@ -16,6 +16,7 @@ vi.mock('../../../utils/authenticateWithPopup', async () => {
1616
1717// Import the mocked function after mocking
1818import { _futureAuthenticateWithPopup } from '../../../utils/authenticateWithPopup' ;
19+ import { CaptchaChallenge } from '../../../utils/captcha/CaptchaChallenge' ;
1920
2021// Mock the CaptchaChallenge module
2122vi . mock ( '../../../utils/captcha/CaptchaChallenge' , ( ) => ( {
@@ -42,9 +43,14 @@ describe('SignUp', () => {
4243 } ) ;
4344
4445 describe ( 'create' , ( ) => {
46+ beforeEach ( ( ) => {
47+ SignUp . clerk = { } as any ;
48+ } ) ;
49+
4550 afterEach ( ( ) => {
4651 vi . clearAllMocks ( ) ;
4752 vi . unstubAllGlobals ( ) ;
53+ SignUp . clerk = { } as any ;
4854 } ) ;
4955
5056 it ( 'includes locale in request when navigator.language is available' , async ( ) => {
@@ -106,6 +112,46 @@ describe('SignUp', () => {
106112
107113 expect ( result ) . toHaveProperty ( 'error' , null ) ;
108114 } ) ;
115+
116+ it ( 'runs captcha challenge when bypass is not enabled' , async ( ) => {
117+ const mockFetch = vi . fn ( ) . mockResolvedValue ( {
118+ client : null ,
119+ response : { id : 'signup_123' , status : 'missing_requirements' } ,
120+ } ) ;
121+ BaseResource . _fetch = mockFetch ;
122+
123+ const signUp = new SignUp ( ) ;
124+ await signUp . __internal_future . create ( { emailAddress : 'user@example.com' } ) ;
125+
126+ expect ( CaptchaChallenge ) . toHaveBeenCalledWith ( SignUp . clerk ) ;
127+ const challengeInstance = vi . mocked ( CaptchaChallenge ) . mock . results [ 0 ] ?. value as {
128+ managedOrInvisible : ReturnType < typeof vi . fn > ;
129+ } ;
130+ expect ( challengeInstance . managedOrInvisible ) . toHaveBeenCalledWith ( { action : 'signup' } ) ;
131+ expect ( mockFetch . mock . calls [ 0 ] ?. [ 0 ] . body ) . toHaveProperty ( 'captchaToken' , 'mock_token' ) ;
132+ expect ( mockFetch . mock . calls [ 0 ] ?. [ 0 ] . body ) . toHaveProperty ( 'captchaWidgetType' , 'invisible' ) ;
133+ } ) ;
134+
135+ it ( 'skips captcha challenge when client captcha bypass is enabled' , async ( ) => {
136+ const mockFetch = vi . fn ( ) . mockResolvedValue ( {
137+ client : null ,
138+ response : { id : 'signup_123' , status : 'missing_requirements' } ,
139+ } ) ;
140+ BaseResource . _fetch = mockFetch ;
141+ SignUp . clerk = {
142+ client : {
143+ captchaBypass : true ,
144+ } ,
145+ } as any ;
146+
147+ const signUp = new SignUp ( ) ;
148+ await signUp . __internal_future . create ( { emailAddress : 'user@example.com' } ) ;
149+
150+ expect ( CaptchaChallenge ) . not . toHaveBeenCalled ( ) ;
151+ expect ( mockFetch . mock . calls [ 0 ] ?. [ 0 ] . body ) . toHaveProperty ( 'captchaToken' , undefined ) ;
152+ expect ( mockFetch . mock . calls [ 0 ] ?. [ 0 ] . body ) . toHaveProperty ( 'captchaWidgetType' , undefined ) ;
153+ expect ( mockFetch . mock . calls [ 0 ] ?. [ 0 ] . body ) . toHaveProperty ( 'captchaError' , undefined ) ;
154+ } ) ;
109155 } ) ;
110156
111157 describe ( 'sendPhoneCode' , ( ) => {
@@ -333,6 +379,7 @@ describe('SignUp', () => {
333379 afterEach ( ( ) => {
334380 vi . clearAllMocks ( ) ;
335381 vi . unstubAllGlobals ( ) ;
382+ SignUp . clerk = { } as any ;
336383 } ) ;
337384
338385 it ( 'handles relative redirectUrl by converting to absolute' , async ( ) => {
@@ -341,6 +388,11 @@ describe('SignUp', () => {
341388 const mockBuildUrlWithAuth = vi . fn ( ) . mockReturnValue ( 'https://example.com/sso-callback' ) ;
342389 SignUp . clerk = {
343390 buildUrlWithAuth : mockBuildUrlWithAuth ,
391+ __internal_environment : {
392+ displayConfig : {
393+ captchaOauthBypass : [ ] ,
394+ } ,
395+ } ,
344396 } as any ;
345397
346398 const mockFetch = vi . fn ( ) . mockResolvedValue ( {
@@ -383,6 +435,11 @@ describe('SignUp', () => {
383435 const mockBuildUrlWithAuth = vi . fn ( ) . mockReturnValue ( 'https://example.com/sso-callback' ) ;
384436 SignUp . clerk = {
385437 buildUrlWithAuth : mockBuildUrlWithAuth ,
438+ __internal_environment : {
439+ displayConfig : {
440+ captchaOauthBypass : [ ] ,
441+ } ,
442+ } ,
386443 } as any ;
387444
388445 const mockFetch = vi . fn ( ) . mockResolvedValue ( {
@@ -436,6 +493,9 @@ describe('SignUp', () => {
436493 buildUrl : vi . fn ( ) . mockImplementation ( path => 'https://example.com' + path ) ,
437494 frontendApi : 'clerk.example.com' ,
438495 __internal_environment : {
496+ displayConfig : {
497+ captchaOauthBypass : [ ] ,
498+ } ,
439499 reload : vi . fn ( ) . mockResolvedValue ( { } ) ,
440500 } ,
441501 } as any ;
@@ -487,6 +547,45 @@ describe('SignUp', () => {
487547 } ) ,
488548 ) ;
489549 } ) ;
550+
551+ it ( 'skips captcha challenge for strategies configured in captcha oauth bypass' , async ( ) => {
552+ vi . stubGlobal ( 'window' , { location : { origin : 'https://example.com' } } ) ;
553+
554+ const mockBuildUrlWithAuth = vi . fn ( ) . mockReturnValue ( 'https://example.com/sso-callback' ) ;
555+ SignUp . clerk = {
556+ buildUrlWithAuth : mockBuildUrlWithAuth ,
557+ __internal_environment : {
558+ displayConfig : {
559+ captchaOauthBypass : [ 'oauth_google' ] ,
560+ } ,
561+ } ,
562+ } as any ;
563+
564+ const mockFetch = vi . fn ( ) . mockResolvedValue ( {
565+ client : null ,
566+ response : {
567+ id : 'signup_123' ,
568+ verifications : {
569+ externalAccount : {
570+ status : 'complete' ,
571+ } ,
572+ } ,
573+ } ,
574+ } ) ;
575+ BaseResource . _fetch = mockFetch ;
576+
577+ const signUp = new SignUp ( ) ;
578+ await signUp . __internal_future . sso ( {
579+ strategy : 'oauth_google' ,
580+ redirectUrl : '/complete' ,
581+ redirectCallbackUrl : '/sso-callback' ,
582+ } ) ;
583+
584+ expect ( CaptchaChallenge ) . not . toHaveBeenCalled ( ) ;
585+ expect ( mockFetch . mock . calls [ 0 ] ?. [ 0 ] . body ) . toHaveProperty ( 'captchaToken' , undefined ) ;
586+ expect ( mockFetch . mock . calls [ 0 ] ?. [ 0 ] . body ) . toHaveProperty ( 'captchaWidgetType' , undefined ) ;
587+ expect ( mockFetch . mock . calls [ 0 ] ?. [ 0 ] . body ) . toHaveProperty ( 'captchaError' , undefined ) ;
588+ } ) ;
490589 } ) ;
491590
492591 describe ( 'web3' , ( ) => {
0 commit comments