@@ -21,8 +21,10 @@ import {
2121 webAuthnAuthJSCallback70StoredUsername ,
2222 webAuthnRegMetaCallback70StoredUsername ,
2323 webAuthnAuthMetaCallback70StoredUsername ,
24+ webAuthnAuthConditionalMetaCallback ,
2425} from './fr-webauthn.mock.data' ;
2526import FRStep from '../fr-auth/fr-step' ;
27+ import Config from '../config' ;
2628
2729describe ( 'Test FRWebAuthn class with 6.5.3 "Passwordless"' , ( ) => {
2830 it ( 'should return Registration type with register text-output callbacks' , ( ) => {
@@ -104,3 +106,115 @@ describe('Test FRWebAuthn class with 7.0 "Usernameless"', () => {
104106 expect ( stepType ) . toBe ( WebAuthnStepType . Authentication ) ;
105107 } ) ;
106108} ) ;
109+
110+ describe ( 'Test FRWebAuthn class with Conditional UI' , ( ) => {
111+ beforeEach ( ( ) => {
112+ // Mock navigator.credentials and window.PublicKeyCredential
113+ Object . defineProperty ( global . navigator , 'credentials' , {
114+ value : {
115+ get : vi . fn ( ) . mockResolvedValue ( null ) ,
116+ create : vi . fn ( ) ,
117+ } ,
118+ writable : true ,
119+ } ) ;
120+ Object . defineProperty ( window , 'PublicKeyCredential' , {
121+ value : {
122+ isConditionalMediationAvailable : vi . fn ( ) ,
123+ } ,
124+ writable : true ,
125+ } ) ;
126+ } ) ;
127+
128+ afterEach ( ( ) => {
129+ vi . restoreAllMocks ( ) ;
130+ } ) ;
131+
132+ it ( 'should detect if conditional UI is supported' , async ( ) => {
133+ vi . spyOn ( window . PublicKeyCredential , 'isConditionalMediationAvailable' ) . mockResolvedValue ( true ) ;
134+ const isSupported = await FRWebAuthn . isConditionalUISupported ( ) ;
135+ expect ( isSupported ) . toBe ( true ) ;
136+ } ) ;
137+
138+ it ( 'should return Authentication type with conditional UI metadata callback' , ( ) => {
139+ const step = new FRStep ( webAuthnAuthConditionalMetaCallback as any ) ;
140+ const stepType = FRWebAuthn . getWebAuthnStepType ( step ) ;
141+ expect ( stepType ) . toBe ( WebAuthnStepType . Authentication ) ;
142+ } ) ;
143+
144+ it ( 'should create authentication public key with empty allowCredentials for conditional UI' , ( ) => {
145+ const metadata : any = {
146+ _action : 'webauthn_authentication' ,
147+ challenge : 'JEisuqkVMhI490jM0/iEgrRz+j94OoGc7gdY4gYicSk=' ,
148+ allowCredentials : '' ,
149+ _allowCredentials : [ ] ,
150+ timeout : 60000 ,
151+ userVerification : 'preferred' ,
152+ conditionalWebAuthn : true ,
153+ relyingPartyId : '' ,
154+ _relyingPartyId : 'example.com' ,
155+ extensions : { } ,
156+ supportsJsonResponse : true ,
157+ } ;
158+
159+ const publicKey = FRWebAuthn . createAuthenticationPublicKey ( metadata ) ;
160+
161+ expect ( publicKey . challenge ) . toBeDefined ( ) ;
162+ expect ( publicKey . timeout ) . toBe ( 60000 ) ;
163+ expect ( publicKey . userVerification ) . toBe ( 'preferred' ) ;
164+ expect ( publicKey . rpId ) . toBe ( 'example.com' ) ;
165+ // allowCredentials should not be present for conditional UI with empty credentials
166+ expect ( publicKey . allowCredentials ) . toBeUndefined ( ) ;
167+ } ) ;
168+
169+ it ( 'should warn and return false if conditional UI is requested but not supported' , async ( ) => {
170+ Config . set ( {
171+ serverConfig : {
172+ baseUrl : 'http://localhost:8080' ,
173+ } ,
174+ clientId : 'test' ,
175+ realmPath : 'alpha' ,
176+ logLevel : 'warn' ,
177+ } ) ;
178+
179+ // Mock browser support for conditional UI to be false
180+ vi . spyOn ( window . PublicKeyCredential , 'isConditionalMediationAvailable' ) . mockResolvedValue (
181+ false ,
182+ ) ;
183+ // FIX APPLIED HERE: Added block comment to empty function
184+ const consoleSpy = vi . spyOn ( console , 'warn' ) . mockImplementation ( ( ) => {
185+ /* empty */
186+ } ) ;
187+ const getSpy = vi . spyOn ( navigator . credentials , 'get' ) ;
188+
189+ // Attempt to authenticate with conditional UI requested
190+ await FRWebAuthn . getAuthenticationCredential ( { } , true ) ;
191+
192+ // Expect a warning to be logged
193+ expect ( consoleSpy ) . toHaveBeenCalledWith (
194+ 'Conditional UI was requested, but is not supported by this browser.' ,
195+ ) ;
196+
197+ // Expect the call to navigator.credentials.get to NOT have the mediation property
198+ expect ( getSpy ) . toHaveBeenCalledWith (
199+ expect . not . objectContaining ( {
200+ mediation : 'conditional' ,
201+ } ) ,
202+ ) ;
203+ } ) ;
204+
205+ it ( 'should set mediation to conditional if supported' , async ( ) => {
206+ // Mock browser support for conditional UI to be true
207+ vi . spyOn ( window . PublicKeyCredential , 'isConditionalMediationAvailable' ) . mockResolvedValue ( true ) ;
208+ const getSpy = vi . spyOn ( navigator . credentials , 'get' ) ;
209+
210+ // Attempt to authenticate with conditional UI requested
211+ await FRWebAuthn . getAuthenticationCredential ( { } , true ) ;
212+
213+ // Expect the call to navigator.credentials.get to have the mediation property
214+ expect ( getSpy ) . toHaveBeenCalledWith (
215+ expect . objectContaining ( {
216+ mediation : 'conditional' ,
217+ } ) ,
218+ ) ;
219+ } ) ;
220+ } ) ;
0 commit comments