diff --git a/src/libs/Navigation/helpers/linkTo/index.ts b/src/libs/Navigation/helpers/linkTo/index.ts index 1649e9c7ab29..5b77a92e4721 100644 --- a/src/libs/Navigation/helpers/linkTo/index.ts +++ b/src/libs/Navigation/helpers/linkTo/index.ts @@ -64,6 +64,33 @@ function isNavigatingToReportWithSameReportID(currentRoute: NavigationPartialRou return currentParams?.reportID === newParams?.reportID; } +function areFullScreenRoutesEqual(matchingFullScreenRoute: NavigationPartialRoute, lastFullScreenRoute: NavigationPartialRoute) { + const lastRouteInMatchingFullScreen = matchingFullScreenRoute.state?.routes?.at(-1); + const lastRouteInLastFullScreenRoute = lastFullScreenRoute.state?.routes?.at(-1); + + const isEqualFullScreenRoute = matchingFullScreenRoute.name === lastFullScreenRoute.name; + const isEqualLastRouteInFullScreenRoute = + !lastRouteInMatchingFullScreen?.name || !lastRouteInLastFullScreenRoute?.name || lastRouteInMatchingFullScreen.name === lastRouteInLastFullScreenRoute.name; + + return isEqualFullScreenRoute && isEqualLastRouteInFullScreenRoute; +} + +function isRoutePreloaded(currentState: PlatformStackNavigationState, matchingFullScreenRoute: NavigationPartialRoute) { + const lastRouteInMatchingFullScreen = matchingFullScreenRoute.state?.routes?.at(-1); + + const preloadedRoutes = currentState.preloadedRoutes; + + return preloadedRoutes.some((preloadedRoute) => { + const isMatchingFullScreenRoute = preloadedRoute.name === matchingFullScreenRoute.name; + + // Compare the last route of the preloadedRoute and the last route of the matchingFullScreenRoute to ensure the preloaded route is accepted when matching subroutes as well + const isMatchingLastRoute = + !lastRouteInMatchingFullScreen?.name || (preloadedRoute.params && 'screen' in preloadedRoute.params && preloadedRoute.params.screen === lastRouteInMatchingFullScreen?.name); + + return isMatchingFullScreenRoute && isMatchingLastRoute; + }); +} + export default function linkTo(navigation: NavigationContainerRef | null, path: Route, options?: LinkToOptions) { if (!navigation) { throw new Error("Couldn't find a navigation object. Is your component inside a screen in a navigator?"); @@ -126,9 +153,9 @@ export default function linkTo(navigation: NavigationContainerRef isFullScreenName(route.name)); - if (matchingFullScreenRoute && lastFullScreenRoute && matchingFullScreenRoute.name !== lastFullScreenRoute.name) { - const isMatchingRoutePreloaded = currentState.preloadedRoutes.some((preloadedRoute) => preloadedRoute.name === matchingFullScreenRoute.name); - if (isMatchingRoutePreloaded) { + + if (matchingFullScreenRoute && lastFullScreenRoute && !areFullScreenRoutesEqual(matchingFullScreenRoute, lastFullScreenRoute as NavigationPartialRoute)) { + if (isRoutePreloaded(currentState, matchingFullScreenRoute)) { navigation.dispatch(StackActions.push(matchingFullScreenRoute.name)); } else { const lastRouteInMatchingFullScreen = matchingFullScreenRoute.state?.routes?.at(-1); diff --git a/tests/navigation/NavigateTests.tsx b/tests/navigation/NavigateTests.tsx index 084c139cc84b..28825c199ff4 100644 --- a/tests/navigation/NavigateTests.tsx +++ b/tests/navigation/NavigateTests.tsx @@ -145,5 +145,106 @@ describe('Navigate', () => { expect(rootStateAfterNavigate?.index).toBe(1); expect(lastSplitAfterNavigate?.name).toBe(NAVIGATORS.REPORTS_SPLIT_NAVIGATOR); }); + + it('to the sub-route from a different split navigator', () => { + // Given the initialized navigation on the narrow layout with the reports split navigator + render( + , + ); + + const rootStateBeforeNavigate = navigationRef.current?.getRootState(); + const lastSplitBeforeNavigate = rootStateBeforeNavigate?.routes.at(-1); + expect(rootStateBeforeNavigate?.index).toBe(0); + expect(lastSplitBeforeNavigate?.name).toBe(NAVIGATORS.REPORTS_SPLIT_NAVIGATOR); + expect(lastSplitBeforeNavigate?.state?.routes.at(-1)?.name).toBe(SCREENS.REPORT); + + // When navigate to the page from the different split navigator + act(() => { + Navigation.navigate(ROUTES.SETTINGS_SUBSCRIPTION_ADD_PAYMENT_CARD); + }); + + // Then push a new split navigator to the navigation state + const rootStateAfterNavigate = navigationRef.current?.getRootState(); + expect(rootStateAfterNavigate?.index).toBe(2); + + const middleSplitAfterNavigate = rootStateAfterNavigate?.routes.at(-2); + expect(middleSplitAfterNavigate?.name).toBe(NAVIGATORS.SETTINGS_SPLIT_NAVIGATOR); + expect(middleSplitAfterNavigate?.state?.routes.at(-1)?.name).toBe(SCREENS.SETTINGS.SUBSCRIPTION.ROOT); + + const lastSplitAfterNavigate = rootStateAfterNavigate?.routes.at(-1); + expect(lastSplitAfterNavigate?.name).toBe(NAVIGATORS.RIGHT_MODAL_NAVIGATOR); + expect(lastSplitAfterNavigate?.state?.routes.at(-1)?.name).toBe(SCREENS.RIGHT_MODAL.SETTINGS); + }); + + it('to the sub-route from a same split navigator', () => { + // Given the initialized navigation on the narrow layout with the settings split navigator + render( + , + ); + + const rootStateBeforeNavigate = navigationRef.current?.getRootState(); + const lastSplitBeforeNavigate = rootStateBeforeNavigate?.routes.at(-1); + expect(rootStateBeforeNavigate?.index).toBe(0); + expect(lastSplitBeforeNavigate?.name).toBe(NAVIGATORS.SETTINGS_SPLIT_NAVIGATOR); + expect(lastSplitBeforeNavigate?.state?.routes.at(-1)?.name).toBe(SCREENS.SETTINGS.PROFILE.ROOT); + + // When navigate to the page from the same split navigator + act(() => { + Navigation.navigate(ROUTES.SETTINGS_SUBSCRIPTION_ADD_PAYMENT_CARD); + }); + + // Then push a new split navigator to the navigation state + const rootStateAfterNavigate = navigationRef.current?.getRootState(); + expect(rootStateAfterNavigate?.index).toBe(2); + + const middleSplitAfterNavigate = rootStateAfterNavigate?.routes.at(-2); + expect(middleSplitAfterNavigate?.name).toBe(NAVIGATORS.SETTINGS_SPLIT_NAVIGATOR); + expect(middleSplitAfterNavigate?.state?.routes.at(-1)?.name).toBe(SCREENS.SETTINGS.SUBSCRIPTION.ROOT); + + const lastSplitAfterNavigate = rootStateAfterNavigate?.routes.at(-1); + expect(lastSplitAfterNavigate?.name).toBe(NAVIGATORS.RIGHT_MODAL_NAVIGATOR); + expect(lastSplitAfterNavigate?.state?.routes.at(-1)?.name).toBe(SCREENS.RIGHT_MODAL.SETTINGS); + }); }); }); diff --git a/tests/utils/TestNavigationContainer.tsx b/tests/utils/TestNavigationContainer.tsx index 0c3370f12de0..8ae0725f83bf 100644 --- a/tests/utils/TestNavigationContainer.tsx +++ b/tests/utils/TestNavigationContainer.tsx @@ -7,6 +7,7 @@ import navigationRef from '@libs/Navigation/navigationRef'; import type { AuthScreensParamList, ReportsSplitNavigatorParamList, + RightModalNavigatorParamList, SearchFullscreenNavigatorParamList, SettingsSplitNavigatorParamList, WorkspaceSplitNavigatorParamList, @@ -21,6 +22,7 @@ const ReportsSplit = createSplitNavigator(); const SettingsSplit = createSplitNavigator(); const SearchStack = createPlatformStackNavigator(); const WorkspaceSplit = createSplitNavigator(); +const RightModalNavigatorStack = createSplitNavigator(); const getEmptyComponent = () => jest.fn(); @@ -103,6 +105,10 @@ function TestSettingsSplitNavigator() { name={SCREENS.SETTINGS.ABOUT} getComponent={getEmptyComponent} /> + ); } @@ -122,6 +128,20 @@ function TestSearchFullscreenNavigator() { ); } +function TestRightModalNavigator() { + return ( + + + + ); +} + function TestNavigationContainer({initialState}: TestNavigationContainerProps) { return ( + );