11import type { NavigationAction , NavigationState } from '@react-navigation/native' ;
22import Onyx from 'react-native-onyx' ;
3- import MigratedUserWelcomeModalGuard , { resetSessionFlag } from '@libs/Navigation/guards/MigratedUserWelcomeModalGuard' ;
3+ import MigratedUserWelcomeModalGuard , { onSessionOrLoadingAppChanged , resetSessionFlag } from '@libs/Navigation/guards/MigratedUserWelcomeModalGuard' ;
44import type { GuardContext } from '@libs/Navigation/guards/types' ;
55import CONST from '@src/CONST' ;
66import NAVIGATORS from '@src/NAVIGATORS' ;
@@ -9,6 +9,13 @@ import ROUTES from '@src/ROUTES';
99import SCREENS from '@src/SCREENS' ;
1010import waitForBatchedUpdates from '../../../utils/waitForBatchedUpdates' ;
1111
12+ const mockNavigate = jest . fn ( ) ;
13+ jest . mock ( '@libs/Navigation/Navigation' , ( ) => ( {
14+ navigate : ( ...args : unknown [ ] ) => {
15+ mockNavigate ( ...args ) ;
16+ } ,
17+ } ) ) ;
18+
1219describe ( 'MigratedUserWelcomeModalGuard' , ( ) => {
1320 const mockState : NavigationState = {
1421 key : 'root' ,
@@ -31,8 +38,12 @@ describe('MigratedUserWelcomeModalGuard', () => {
3138 } ;
3239
3340 beforeEach ( async ( ) => {
34- await Onyx . clear ( ) ;
41+ // Reset module-level session/loading state first so stale values from
42+ // previous tests don't trigger navigation during Onyx.clear() callbacks
43+ onSessionOrLoadingAppChanged ( undefined , true ) ;
3544 resetSessionFlag ( ) ;
45+ mockNavigate . mockClear ( ) ;
46+ await Onyx . clear ( ) ;
3647 await waitForBatchedUpdates ( ) ;
3748 } ) ;
3849
@@ -324,4 +335,121 @@ describe('MigratedUserWelcomeModalGuard', () => {
324335 expect ( result . type ) . not . toBe ( 'BLOCK' ) ;
325336 } ) ;
326337 } ) ;
338+
339+ describe ( 'onSessionOrLoadingAppChanged (proactive navigation)' , ( ) => {
340+ it ( 'should navigate when all conditions are met (session, not loading, nudge migration, training loaded, not dismissed)' , async ( ) => {
341+ // Set up nudge migration (session is not set yet, so no navigation here)
342+ await Onyx . merge ( ONYXKEYS . NVP_TRY_NEW_DOT , {
343+ nudgeMigration : {
344+ timestamp : new Date ( ) ,
345+ cohort : 'test' ,
346+ } ,
347+ } ) ;
348+ await waitForBatchedUpdates ( ) ;
349+ mockNavigate . mockClear ( ) ;
350+
351+ // Now signal that session is ready and app is done loading
352+ onSessionOrLoadingAppChanged ( { authToken : 'test-token' , accountID : 123 } , false ) ;
353+
354+ expect ( mockNavigate ) . toHaveBeenCalledWith ( ROUTES . MIGRATED_USER_WELCOME_MODAL . getRoute ( ) ) ;
355+ } ) ;
356+
357+ it ( 'should not navigate when app is still loading' , async ( ) => {
358+ await Onyx . merge ( ONYXKEYS . NVP_TRY_NEW_DOT , {
359+ nudgeMigration : {
360+ timestamp : new Date ( ) ,
361+ cohort : 'test' ,
362+ } ,
363+ } ) ;
364+ await waitForBatchedUpdates ( ) ;
365+ mockNavigate . mockClear ( ) ;
366+
367+ onSessionOrLoadingAppChanged ( { authToken : 'test-token' , accountID : 123 } , true ) ;
368+
369+ expect ( mockNavigate ) . not . toHaveBeenCalled ( ) ;
370+ } ) ;
371+
372+ it ( 'should not navigate when there is no session' , async ( ) => {
373+ await Onyx . merge ( ONYXKEYS . NVP_TRY_NEW_DOT , {
374+ nudgeMigration : {
375+ timestamp : new Date ( ) ,
376+ cohort : 'test' ,
377+ } ,
378+ } ) ;
379+ await waitForBatchedUpdates ( ) ;
380+ mockNavigate . mockClear ( ) ;
381+
382+ onSessionOrLoadingAppChanged ( undefined , false ) ;
383+
384+ expect ( mockNavigate ) . not . toHaveBeenCalled ( ) ;
385+ } ) ;
386+
387+ it ( 'should not navigate when user has not been added to nudge migration' , async ( ) => {
388+ await Onyx . merge ( ONYXKEYS . NVP_TRY_NEW_DOT , {
389+ nudgeMigration : null ,
390+ } ) ;
391+ await waitForBatchedUpdates ( ) ;
392+ mockNavigate . mockClear ( ) ;
393+
394+ onSessionOrLoadingAppChanged ( { authToken : 'test-token' , accountID : 123 } , false ) ;
395+
396+ expect ( mockNavigate ) . not . toHaveBeenCalled ( ) ;
397+ } ) ;
398+
399+ it ( 'should not navigate when modal was already dismissed' , async ( ) => {
400+ await Onyx . merge ( ONYXKEYS . NVP_TRY_NEW_DOT , {
401+ nudgeMigration : {
402+ timestamp : new Date ( ) ,
403+ cohort : 'test' ,
404+ } ,
405+ } ) ;
406+ await Onyx . merge ( ONYXKEYS . NVP_DISMISSED_PRODUCT_TRAINING , {
407+ migratedUserWelcomeModal : {
408+ timestamp : new Date ( ) . toISOString ( ) ,
409+ dismissedMethod : 'click' ,
410+ } ,
411+ } ) ;
412+ await waitForBatchedUpdates ( ) ;
413+ mockNavigate . mockClear ( ) ;
414+
415+ onSessionOrLoadingAppChanged ( { authToken : 'test-token' , accountID : 123 } , false ) ;
416+
417+ expect ( mockNavigate ) . not . toHaveBeenCalled ( ) ;
418+ } ) ;
419+
420+ it ( 'should only navigate once even if called multiple times' , async ( ) => {
421+ await Onyx . merge ( ONYXKEYS . NVP_TRY_NEW_DOT , {
422+ nudgeMigration : {
423+ timestamp : new Date ( ) ,
424+ cohort : 'test' ,
425+ } ,
426+ } ) ;
427+ await waitForBatchedUpdates ( ) ;
428+ mockNavigate . mockClear ( ) ;
429+
430+ onSessionOrLoadingAppChanged ( { authToken : 'test-token' , accountID : 123 } , false ) ;
431+ onSessionOrLoadingAppChanged ( { authToken : 'test-token' , accountID : 123 } , false ) ;
432+
433+ expect ( mockNavigate ) . toHaveBeenCalledTimes ( 1 ) ;
434+ } ) ;
435+
436+ it ( 'should navigate via Onyx NVP_DISMISSED_PRODUCT_TRAINING callback when session is already set' , async ( ) => {
437+ // Set session first
438+ onSessionOrLoadingAppChanged ( { authToken : 'test-token' , accountID : 123 } , false ) ;
439+ mockNavigate . mockClear ( ) ;
440+
441+ // Set up nudge migration and dismissed product training (modal not dismissed)
442+ await Onyx . merge ( ONYXKEYS . NVP_TRY_NEW_DOT , {
443+ nudgeMigration : {
444+ timestamp : new Date ( ) ,
445+ cohort : 'test' ,
446+ } ,
447+ } ) ;
448+ await waitForBatchedUpdates ( ) ;
449+
450+ // The NVP_TRY_NEW_DOT callback triggers navigateToMigratedUserWelcomeModalIfReady
451+ // which should navigate because all conditions are met
452+ expect ( mockNavigate ) . toHaveBeenCalledWith ( ROUTES . MIGRATED_USER_WELCOME_MODAL . getRoute ( ) ) ;
453+ } ) ;
454+ } ) ;
327455} ) ;
0 commit comments