@@ -283,11 +283,26 @@ describe('Error while logging in', function () {
283283describe ( 'Passkey login' , ( ) => {
284284 let mockCredentialsGet
285285 let mockPublicKeyCredential
286+ let mockAppConfig
286287
287288 beforeEach ( ( ) => {
288289 // Clear all mocks
289290 jest . clearAllMocks ( )
290291
292+ // Override getConfig to return config with passkey enabled
293+ mockAppConfig = {
294+ ...mockConfig . app ,
295+ login : {
296+ ...mockConfig . app . login ,
297+ passkey : { enabled : true }
298+ }
299+ }
300+
301+ getConfig . mockReturnValue ( {
302+ ...mockConfig ,
303+ app : mockAppConfig
304+ } )
305+
291306 // Mock WebAuthn API - default to never resolving (simulating no user action)
292307 mockCredentialsGet = jest . fn ( ) . mockImplementation ( ( ) => new Promise ( ( ) => { } ) )
293308 mockPublicKeyCredential = {
@@ -302,6 +317,12 @@ describe('Passkey login', () => {
302317 get : mockCredentialsGet
303318 }
304319
320+ // Mock parseRequestOptionsFromJSON to return mock options
321+ mockPublicKeyCredential . parseRequestOptionsFromJSON . mockReturnValue ( {
322+ challenge : 'mock-challenge' ,
323+ allowCredentials : [ ]
324+ } )
325+
305326 // Clear localStorage
306327 localStorage . clear ( )
307328
@@ -345,21 +366,6 @@ describe('Passkey login', () => {
345366 } )
346367
347368 test ( 'Sets up conditional mediation on page load when passkey enabled' , async ( ) => {
348- const mockAppConfig = {
349- ...mockConfig . app ,
350- login : {
351- ...mockConfig . app . login ,
352- passkey : { enabled : true }
353- }
354- }
355-
356- const mockPublicKeyOptions = {
357- challenge : 'mock-challenge' ,
358- allowCredentials : [ ]
359- }
360-
361- mockPublicKeyCredential . parseRequestOptionsFromJSON . mockReturnValue ( mockPublicKeyOptions )
362-
363369 // Mock that conditional mediation starts but user doesn't select
364370 mockCredentialsGet . mockImplementation (
365371 ( ) =>
@@ -395,23 +401,42 @@ describe('Passkey login', () => {
395401 )
396402 } )
397403
398- test ( 'Successfully logs in with passkey in passwordless mode on login page ' , async ( ) => {
404+ test ( 'Does not trigger passkey when passkey is disabled ' , async ( ) => {
399405 const mockAppConfig = {
400406 ...mockConfig . app ,
401407 login : {
402408 ...mockConfig . app . login ,
403- passwordless : { enabled : true } ,
404- passkey : { enabled : true }
409+ passkey : { enabled : false }
405410 }
406411 }
407412
408- const mockPublicKeyOptions = {
409- challenge : 'mock-challenge' ,
410- allowCredentials : [ ]
411- }
413+ // Override getConfig to return config with passkey disabled
414+ getConfig . mockReturnValue ( {
415+ ...mockConfig ,
416+ app : mockAppConfig
417+ } )
412418
413- mockPublicKeyCredential . parseRequestOptionsFromJSON . mockReturnValue ( mockPublicKeyOptions )
419+ renderWithProviders ( < MockedComponent /> , {
420+ wrapperProps : {
421+ siteAlias : 'uk' ,
422+ locale : { id : 'en-GB' } ,
423+ appConfig : mockAppConfig ,
424+ bypassAuth : false
425+ }
426+ } )
427+
428+ await waitFor ( ( ) => {
429+ expect ( screen . getByTestId ( 'login-page' ) ) . toBeInTheDocument ( )
430+ } )
431+
432+ // Give it a moment for any async effects to run
433+ await new Promise ( ( resolve ) => setTimeout ( resolve , 100 ) )
414434
435+ // Should not call credentials API when passkey is disabled
436+ expect ( mockCredentialsGet ) . not . toHaveBeenCalled ( )
437+ } )
438+
439+ test ( 'Successfully logs in with passkey' , async ( ) => {
415440 const mockCredential = {
416441 id : 'mock-credential-id' ,
417442 rawId : new ArrayBuffer ( 32 ) ,
@@ -438,7 +463,7 @@ describe('Passkey login', () => {
438463
439464 mockCredentialsGet . mockResolvedValue ( mockCredential )
440465
441- // Mock successful auth after passkey
466+ // Mock customer as registered after passkey login
442467 global . server . use (
443468 rest . post ( '*/oauth2/token' , ( req , res , ctx ) =>
444469 res (
@@ -456,7 +481,7 @@ describe('Passkey login', () => {
456481 )
457482 )
458483
459- const { user } = renderWithProviders ( < MockedComponent /> , {
484+ renderWithProviders ( < MockedComponent /> , {
460485 wrapperProps : {
461486 siteAlias : 'uk' ,
462487 locale : { id : 'en-GB' } ,
@@ -465,41 +490,26 @@ describe('Passkey login', () => {
465490 }
466491 } )
467492
468- // Enter email (don't enter password for passwordless)
469- await user . type ( screen . getByLabelText ( 'Email' ) , 'test@salesforce.com' )
470- await user . click ( screen . getByRole ( 'button' , { name : / s i g n i n / i} ) )
471-
472- // Should trigger passkey authentication with credentials.get
493+ // Wait for passkey flow to be triggered when modal opens
473494 await waitFor (
474495 ( ) => {
475496 expect ( mockCredentialsGet ) . toHaveBeenCalled ( )
476497 } ,
477498 { timeout : 5000 }
478499 )
479500
480- // After successful passkey login, should redirect to account page
481- await waitFor (
482- ( ) => {
483- expect ( window . location . pathname ) . toBe ( '/uk/en-GB/account' )
484- } ,
485- { timeout : 5000 }
486- )
501+ // login successfully and navigate to account page
502+ await waitFor ( ( ) => {
503+ expect ( window . location . pathname ) . toBe ( '/uk/en-GB/account' )
504+ expect ( screen . getByText ( / M y P r o f i l e / i) ) . toBeInTheDocument ( )
505+ } )
487506 } )
488507
489- test ( 'Does not trigger passkey when passkey is disabled' , async ( ) => {
490- const mockAppConfig = {
491- ...mockConfig . app ,
492- login : {
493- ...mockConfig . app . login ,
494- passkey : { enabled : false }
495- }
496- }
497-
498- // Override getConfig to return config with passkey disabled
499- getConfig . mockReturnValue ( {
500- ...mockConfig ,
501- app : mockAppConfig
502- } )
508+ test ( 'User can select other login method when passkey login is cancelled' , async ( ) => {
509+ // User cancels passkey selection
510+ const notAllowedError = new Error ( 'User cancelled' )
511+ notAllowedError . name = 'NotAllowedError'
512+ mockCredentialsGet . mockRejectedValue ( notAllowedError )
503513
504514 renderWithProviders ( < MockedComponent /> , {
505515 wrapperProps : {
@@ -510,38 +520,50 @@ describe('Passkey login', () => {
510520 }
511521 } )
512522
523+ // Login form should be shown
513524 await waitFor ( ( ) => {
525+ expect ( mockCredentialsGet ) . toHaveBeenCalled ( )
526+ expect ( screen . getByText ( / w e l c o m e b a c k / i) ) . toBeInTheDocument ( )
527+ expect ( screen . getByLabelText ( 'Email' ) ) . toBeInTheDocument ( )
528+ expect ( screen . getByLabelText ( 'Password' ) ) . toBeInTheDocument ( )
529+ expect ( screen . getByRole ( 'button' , { name : / s i g n i n / i} ) ) . toBeInTheDocument ( )
514530 expect ( screen . getByTestId ( 'login-page' ) ) . toBeInTheDocument ( )
515531 } )
516-
517- // Give it a moment for any async effects to run
518- await new Promise ( ( resolve ) => setTimeout ( resolve , 100 ) )
519-
520- // Should not call credentials API when passkey is disabled
521- expect ( mockCredentialsGet ) . not . toHaveBeenCalled ( )
522532 } )
523533
524- test ( 'Handles passkey login cancellation gracefully' , async ( ) => {
525- const mockAppConfig = {
526- ...mockConfig . app ,
527- login : {
528- ...mockConfig . app . login ,
529- passwordless : { enabled : true } ,
530- passkey : { enabled : true }
534+ test ( 'Shows error when passkey authentication fails with error from the browser' , async ( ) => {
535+ // Simulate error in navigator.credentials.get hook
536+ mockCredentialsGet . mockRejectedValue ( new Error ( 'Authentication failed' ) )
537+
538+ renderWithProviders ( < MockedComponent /> , {
539+ wrapperProps : {
540+ siteAlias : 'uk' ,
541+ locale : { id : 'en-GB' } ,
542+ appConfig : mockAppConfig ,
543+ bypassAuth : false
531544 }
532- }
545+ } )
533546
534- mockPublicKeyCredential . parseRequestOptionsFromJSON . mockReturnValue ( {
535- challenge : 'mock-challenge' ,
536- allowCredentials : [ ]
547+ // Should show error - passkey error should be caught and handled
548+ await waitFor ( ( ) => {
549+ expect ( mockCredentialsGet ) . toHaveBeenCalled ( )
550+ expect ( screen . getByText ( / S o m e t h i n g w e n t w r o n g . T r y a g a i n ! / i) ) . toBeInTheDocument ( )
537551 } )
552+ } )
538553
539- // User cancels passkey selection
540- const notAllowedError = new Error ( 'User cancelled' )
541- notAllowedError . name = 'NotAllowedError'
542- mockCredentialsGet . mockRejectedValue ( notAllowedError )
554+ test ( 'Shows error when passkey authentication fails with error from the WebAuthn API' , async ( ) => {
555+ // Simulate error in WebAuthn API
556+ global . server . use (
557+ rest . post ( '*/oauth2/webauthn/authenticate/start' , ( req , res , ctx ) => {
558+ return res (
559+ ctx . delay ( 0 ) ,
560+ ctx . status ( 401 ) ,
561+ ctx . json ( { message : 'Authentication failed' } )
562+ )
563+ } )
564+ )
543565
544- const { user } = renderWithProviders ( < MockedComponent /> , {
566+ renderWithProviders ( < MockedComponent /> , {
545567 wrapperProps : {
546568 siteAlias : 'uk' ,
547569 locale : { id : 'en-GB' } ,
@@ -550,31 +572,15 @@ describe('Passkey login', () => {
550572 }
551573 } )
552574
553- // Enter email without password for passwordless
554- await user . type ( screen . getByLabelText ( 'Email' ) , 'test@salesforce.com' )
555- await user . click ( screen . getByRole ( 'button' , { name : / s i g n i n / i} ) )
556-
557- // Should not show error for cancelled passkey
558- // Page should remain on login page
575+ // Should show error - 401 error from WebAuthn API should be caught and converted to user-friendly message
559576 await waitFor ( ( ) => {
560- expect ( screen . getByTestId ( 'login-page' ) ) . toBeInTheDocument ( )
577+ expect (
578+ screen . getByText ( / T h i s f e a t u r e i s n o t c u r r e n t l y a v a i l a b l e ./ i)
579+ ) . toBeInTheDocument ( )
561580 } )
562581 } )
563582
564583 test ( 'Shows passkey registration prompt after successful login when passkey enabled and not registered' , async ( ) => {
565- const mockAppConfig = {
566- ...mockConfig . app ,
567- login : {
568- ...mockConfig . app . login ,
569- passkey : { enabled : true }
570- }
571- }
572-
573- mockPublicKeyCredential . parseRequestOptionsFromJSON . mockReturnValue ( {
574- challenge : 'mock-challenge' ,
575- allowCredentials : [ ]
576- } )
577-
578584 const { user} = renderWithProviders ( < MockedComponent /> , {
579585 wrapperProps : {
580586 siteAlias : 'uk' ,
0 commit comments