Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/libs/Navigation/AppNavigator/AuthScreens.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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();

Expand Down Expand Up @@ -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 && (
<RootStack.Screen
name={NAVIGATORS.ONBOARDING_MODAL_NAVIGATOR}
options={{...rootNavigatorScreenOptions.basicModalNavigator, gestureEnabled: false}}
Expand Down
15 changes: 11 additions & 4 deletions src/libs/Navigation/Navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1127,10 +1127,16 @@ function isOnboardingFlow() {
return isOnboardingFlowName(currentFocusedRoute?.name);
}

function isValidateLoginFlow() {
const state = navigationRef.getRootState();
const currentFocusedRoute = findFocusedRoute(state);
return currentFocusedRoute?.name === SCREENS.VALIDATE_LOGIN;
function isValidateLoginFlow(state: NavigationState | undefined) {
return !!state && findFocusedRoute(state)?.name === SCREENS.VALIDATE_LOGIN;
}

/**
* Keeps onboarding from opening over the validate login screen. Once the onboarding navigator is in the root stack it stays
* registered, because unregistering it drops its route and the user's onboarding progress.
*/
function shouldHideOnboardingNavigator(state: NavigationState | undefined) {
return isValidateLoginFlow(state) && !state?.routes.some((route) => route.name === NAVIGATORS.ONBOARDING_MODAL_NAVIGATOR);
}

function clearPreloadedRoutes() {
Expand Down Expand Up @@ -1333,6 +1339,7 @@ export default {
isOnboardingFlow,
clearPreloadedRoutes,
isValidateLoginFlow,
shouldHideOnboardingNavigator,
dismissToPreviousRHP,
dismissToSuperWideRHP,
revealRouteBeforeDismissingModal,
Expand Down
48 changes: 47 additions & 1 deletion tests/navigation/NavigationTests.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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);
Expand All @@ -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);
});
});
});
Loading