@@ -326,6 +326,94 @@ describe("create-client", () => {
326326 } ) ;
327327 } ) ;
328328
329+ describe ( "getSignInUrl" , ( ) => {
330+ beforeEach ( ( ) => {
331+ mockLocation ( ) ;
332+ } ) ;
333+
334+ afterEach ( ( ) => {
335+ restoreLocation ( ) ;
336+ } ) ;
337+
338+ it ( "generates a PKCE challenge and returns the AuthKit sign-in page URL" , async ( ) => {
339+ const { scope } = nockRefresh ( ) ;
340+ expect ( sessionStorage . getItem ( storageKeys . codeVerifier ) ) . toBeNull ( ) ;
341+
342+ client = await createClient ( "client_123abc" , {
343+ redirectUri : "https://example.com/" ,
344+ } ) ;
345+ const signInUrl = await client . getSignInUrl ( ) ;
346+ const url = new URL ( signInUrl ) ;
347+
348+ // Location.assign should not be called
349+ expect ( jest . mocked ( location . assign ) ) . not . toHaveBeenCalled ( ) ;
350+ expect ( {
351+ url,
352+ searchParams : Object . fromEntries ( url . searchParams . entries ( ) ) ,
353+ } ) . toEqual ( {
354+ url : expect . objectContaining ( {
355+ origin : "https://api.workos.com" ,
356+ pathname : "/user_management/authorize" ,
357+ } ) ,
358+ searchParams : {
359+ client_id : "client_123abc" ,
360+ code_challenge : expect . any ( String ) ,
361+ code_challenge_method : "S256" ,
362+ provider : "authkit" ,
363+ redirect_uri : "https://example.com/" ,
364+ response_type : "code" ,
365+ screen_hint : "sign-in" ,
366+ } ,
367+ } ) ;
368+ expect ( sessionStorage . getItem ( storageKeys . codeVerifier ) ) . toBeDefined ( ) ;
369+ scope . done ( ) ;
370+ } ) ;
371+ } ) ;
372+
373+ describe ( "getSignUpUrl" , ( ) => {
374+ beforeEach ( ( ) => {
375+ mockLocation ( ) ;
376+ } ) ;
377+
378+ afterEach ( ( ) => {
379+ restoreLocation ( ) ;
380+ } ) ;
381+
382+ it ( "generates a PKCE challenge and returns the AuthKit sign-up page URL" , async ( ) => {
383+ const { scope } = nockRefresh ( ) ;
384+ expect ( sessionStorage . getItem ( storageKeys . codeVerifier ) ) . toBeNull ( ) ;
385+
386+ client = await createClient ( "client_123abc" , {
387+ redirectUri : "https://example.com/" ,
388+ } ) ;
389+ const signUpUrl = await client . getSignUpUrl ( ) ;
390+ const url = new URL ( signUpUrl ) ;
391+
392+ // Location.assign should not be called
393+ expect ( jest . mocked ( location . assign ) ) . not . toHaveBeenCalled ( ) ;
394+ expect ( {
395+ url,
396+ searchParams : Object . fromEntries ( url . searchParams . entries ( ) ) ,
397+ } ) . toEqual ( {
398+ url : expect . objectContaining ( {
399+ origin : "https://api.workos.com" ,
400+ pathname : "/user_management/authorize" ,
401+ } ) ,
402+ searchParams : {
403+ client_id : "client_123abc" ,
404+ code_challenge : expect . any ( String ) ,
405+ code_challenge_method : "S256" ,
406+ provider : "authkit" ,
407+ redirect_uri : "https://example.com/" ,
408+ response_type : "code" ,
409+ screen_hint : "sign-up" ,
410+ } ,
411+ } ) ;
412+ expect ( sessionStorage . getItem ( storageKeys . codeVerifier ) ) . toBeDefined ( ) ;
413+ scope . done ( ) ;
414+ } ) ;
415+ } ) ;
416+
329417 describe ( "signOut" , ( ) => {
330418 beforeEach ( ( ) => {
331419 mockLocation ( ) ;
@@ -384,6 +472,92 @@ describe("create-client", () => {
384472 } ) ;
385473 } ) ;
386474
475+ describe ( "when the `navigate` option is set to false" , ( ) => {
476+ it ( "makes a fetch request instead of redirecting" , async ( ) => {
477+ const { scope } = nockRefresh ( ) ;
478+
479+ client = await createClient ( "client_123abc" , {
480+ redirectUri : "https://example.com/" ,
481+ } ) ;
482+
483+ const originalFetch = global . fetch ;
484+ const mockFetch = jest . fn ( ) . mockResolvedValue ( {
485+ ok : true ,
486+ } ) ;
487+ global . fetch = mockFetch ;
488+
489+ try {
490+ await client . signOut ( { navigate : false } ) ;
491+
492+ // Location.assign should not be called
493+ expect ( jest . mocked ( location . assign ) ) . not . toHaveBeenCalled ( ) ;
494+
495+ // Fetch should be called with the correct URL
496+ expect ( mockFetch ) . toHaveBeenCalledTimes ( 1 ) ;
497+
498+ const fetchUrl = new URL ( mockFetch . mock . calls [ 0 ] [ 0 ] ) ;
499+ expect ( {
500+ fetchUrl,
501+ searchParams : Object . fromEntries ( fetchUrl . searchParams . entries ( ) ) ,
502+ } ) . toEqual ( {
503+ fetchUrl : expect . objectContaining ( {
504+ origin : "https://api.workos.com" ,
505+ pathname : "/user_management/sessions/logout" ,
506+ } ) ,
507+ searchParams : { session_id : "session_123abc" } ,
508+ } ) ;
509+ scope . done ( ) ;
510+ } finally {
511+ global . fetch = originalFetch ;
512+ }
513+ } ) ;
514+
515+ it ( "includes the `returnTo` parameter" , async ( ) => {
516+ const { scope } = nockRefresh ( ) ;
517+
518+ client = await createClient ( "client_123abc" , {
519+ redirectUri : "https://example.com/" ,
520+ } ) ;
521+
522+ const originalFetch = global . fetch ;
523+ const mockFetch = jest . fn ( ) . mockResolvedValue ( {
524+ ok : true ,
525+ } ) ;
526+ global . fetch = mockFetch ;
527+
528+ try {
529+ await client . signOut ( {
530+ returnTo : "https://example.com" ,
531+ navigate : false ,
532+ } ) ;
533+
534+ // Location.assign should not be called
535+ expect ( jest . mocked ( location . assign ) ) . not . toHaveBeenCalled ( ) ;
536+
537+ expect ( mockFetch ) . toHaveBeenCalledTimes ( 1 ) ;
538+
539+ const fetchUrl = new URL ( mockFetch . mock . calls [ 0 ] [ 0 ] ) ;
540+ expect ( {
541+ fetchUrl,
542+ searchParams : Object . fromEntries ( fetchUrl . searchParams . entries ( ) ) ,
543+ } ) . toEqual ( {
544+ fetchUrl : expect . objectContaining ( {
545+ origin : "https://api.workos.com" ,
546+ pathname : "/user_management/sessions/logout" ,
547+ } ) ,
548+ searchParams : {
549+ session_id : "session_123abc" ,
550+ return_to : "https://example.com" ,
551+ } ,
552+ } ) ;
553+
554+ scope . done ( ) ;
555+ } finally {
556+ global . fetch = originalFetch ;
557+ }
558+ } ) ;
559+ } ) ;
560+
387561 describe ( "when tokens are persisted in local storage in development" , ( ) => {
388562 it ( "clears the tokens" , async ( ) => {
389563 localStorage . setItem ( storageKeys . refreshToken , "refresh_token" ) ;
@@ -398,6 +572,48 @@ describe("create-client", () => {
398572 expect ( localStorage . getItem ( storageKeys . refreshToken ) ) . toBeNull ( ) ;
399573 scope . done ( ) ;
400574 } ) ;
575+
576+ describe ( "when `returnTo` is provided" , ( ) => {
577+ it ( "clears the tokens" , async ( ) => {
578+ localStorage . setItem ( storageKeys . refreshToken , "refresh_token" ) ;
579+ const { scope } = nockRefresh ( { devMode : true } ) ;
580+
581+ client = await createClient ( "client_123abc" , {
582+ redirectUri : "https://example.com/" ,
583+ devMode : true ,
584+ } ) ;
585+ client . signOut ( { returnTo : "https://example.com" } ) ;
586+
587+ expect ( localStorage . getItem ( storageKeys . refreshToken ) ) . toBeNull ( ) ;
588+ scope . done ( ) ;
589+ } ) ;
590+ } ) ;
591+
592+ describe ( "when the `navigate` is set to false" , ( ) => {
593+ it ( "clears the tokens" , async ( ) => {
594+ localStorage . setItem ( storageKeys . refreshToken , "refresh_token" ) ;
595+ const { scope } = nockRefresh ( { devMode : true } ) ;
596+
597+ client = await createClient ( "client_123abc" , {
598+ redirectUri : "https://example.com/" ,
599+ devMode : true ,
600+ } ) ;
601+
602+ const originalFetch = global . fetch ;
603+ const mockFetch = jest . fn ( ) . mockResolvedValue ( {
604+ ok : true ,
605+ } ) ;
606+ global . fetch = mockFetch ;
607+
608+ try {
609+ await client . signOut ( { navigate : false } ) ;
610+ expect ( localStorage . getItem ( storageKeys . refreshToken ) ) . toBeNull ( ) ;
611+ scope . done ( ) ;
612+ } finally {
613+ global . fetch = originalFetch ;
614+ }
615+ } ) ;
616+ } ) ;
401617 } ) ;
402618 } ) ;
403619
0 commit comments