diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.tsx b/src/libs/Navigation/AppNavigator/AuthScreens.tsx index 4180daa51e23..fcf120df0f03 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.tsx +++ b/src/libs/Navigation/AppNavigator/AuthScreens.tsx @@ -25,6 +25,7 @@ import useGPSInProgressModal from '@hooks/useGPSInProgressModal'; import useGPSTripStateChecker from '@hooks/useGPSTripStateChecker'; import useOnboardingFlowRouter from '@hooks/useOnboardingFlow'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; +import useRootNavigationState from '@hooks/useRootNavigationState'; import useShouldSuppressPromotionalUI from '@hooks/useShouldSuppressPromotionalUI'; import {SidebarOrderedReportsContextProvider} from '@hooks/useSidebarOrderedReports'; import useStyleUtils from '@hooks/useStyleUtils'; @@ -137,6 +138,8 @@ function AuthScreens() { const modalCardStyleInterpolator = useModalCardStyleInterpolator(); const {isVisitingSecureLink} = useOnboardingFlowRouter(); const shouldSuppressPromotionalUI = useShouldSuppressPromotionalUI(); + // Subscribed to navigation state so the onboarding navigator gets registered as soon as the user leaves the validate login screen + const shouldHideOnboardingNavigator = useRootNavigationState(Navigation.shouldHideOnboardingNavigator); useGPSTripStateChecker(); useGPSInProgressModal(); @@ -356,7 +359,7 @@ function AuthScreens() { * screen in and out of the RootStack mid-session (which resets the navigator's state to its initial * route). OnboardingGuard and useOnboardingFlowRouter gate whether/when a user actually lands here. */} - {!shouldSuppressPromotionalUI && !isVisitingSecureLink && !Navigation.isValidateLoginFlow() && ( + {!shouldSuppressPromotionalUI && !isVisitingSecureLink && !shouldHideOnboardingNavigator && ( route.name === NAVIGATORS.ONBOARDING_MODAL_NAVIGATOR); } function clearPreloadedRoutes() { @@ -1333,6 +1339,7 @@ export default { isOnboardingFlow, clearPreloadedRoutes, isValidateLoginFlow, + shouldHideOnboardingNavigator, dismissToPreviousRHP, dismissToSuperWideRHP, revealRouteBeforeDismissingModal, diff --git a/tests/navigation/NavigationTests.tsx b/tests/navigation/NavigationTests.tsx index 1f5f0c787b43..8408641005a8 100644 --- a/tests/navigation/NavigationTests.tsx +++ b/tests/navigation/NavigationTests.tsx @@ -4,8 +4,13 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout'; import getIsNarrowLayout from '@libs/getIsNarrowLayout'; import Navigation from '@libs/Navigation/Navigation'; +import navigationRef from '@libs/Navigation/navigationRef'; import CONST from '@src/CONST'; +import NAVIGATORS from '@src/NAVIGATORS'; +import SCREENS from '@src/SCREENS'; + +import type {NavigationState} from '@react-navigation/native'; import React from 'react'; @@ -19,6 +24,18 @@ jest.mock('@pages/inbox/sidebar/NavigationTabBarAvatar'); const mockedGetIsNarrowLayout = jest.mocked(getIsNarrowLayout); const mockedUseResponsiveLayout = jest.mocked(useResponsiveLayout); +/** Builds a root stack state with the given routes, focused on the last one */ +function buildRootState(routeNames: string[]): NavigationState { + return { + key: 'root', + index: routeNames.length - 1, + routeNames, + routes: routeNames.map((name) => ({key: `${name}-key`, name})), + type: 'stack', + stale: false, + }; +} + describe('Navigation', () => { beforeEach(() => { mockedGetIsNarrowLayout.mockReturnValue(false); @@ -41,7 +58,36 @@ describe('Navigation', () => { />, ); - expect(Navigation.isValidateLoginFlow()).toBe(true); + expect(Navigation.isValidateLoginFlow(navigationRef.getRootState())).toBe(true); + }); + }); + + describe('shouldHideOnboardingNavigator', () => { + it('should hide the onboarding navigator on the validate login screen before onboarding has started', () => { + // Given a fresh magic link sign-in, where the validate login screen is focused and onboarding isn't in the root stack yet + const state = buildRootState([NAVIGATORS.TAB_NAVIGATOR, SCREENS.VALIDATE_LOGIN]); + + // When checking whether the onboarding navigator should be registered + // Then it is hidden so onboarding doesn't open over the validate login screen + expect(Navigation.shouldHideOnboardingNavigator(state)).toBe(true); + }); + + it('should keep the onboarding navigator when the validate login screen is pushed over onboarding', () => { + // Given a user in the middle of onboarding who opens a magic link, pushing the validate login screen over the onboarding navigator + const state = buildRootState([NAVIGATORS.TAB_NAVIGATOR, NAVIGATORS.ONBOARDING_MODAL_NAVIGATOR, SCREENS.VALIDATE_LOGIN]); + + // When checking whether the onboarding navigator should be registered + // Then it stays registered, because unregistering it would drop the onboarding route and the user's progress + expect(Navigation.shouldHideOnboardingNavigator(state)).toBe(false); + }); + + it('should not hide the onboarding navigator once the user leaves the validate login screen', () => { + // Given the validate login screen was popped and the user is back on the tab navigator + const state = buildRootState([NAVIGATORS.TAB_NAVIGATOR]); + + // When checking whether the onboarding navigator should be registered + // Then it is registered so startOnboardingFlow can reset into it + expect(Navigation.shouldHideOnboardingNavigator(state)).toBe(false); }); }); });