diff --git a/src/ROUTES.ts b/src/ROUTES.ts index daf5f79c0e03..12f6f4a0313d 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -1412,6 +1412,10 @@ const DYNAMIC_ROUTES = { path: 'keyboard-shortcuts', entryScreens: ['*'], }, + APP_DOWNLOAD_LINKS: { + path: 'app-download-links', + entryScreens: ['*'], + }, SETTINGS_TAG_APPROVER: { path: 'tag-approver', entryScreens: [SCREENS.SETTINGS_TAGS.DYNAMIC_SETTINGS_TAG_SETTINGS], @@ -2319,7 +2323,6 @@ const ROUTES = { getRoute: (login: string, role: string) => `settings/security/delegate/${encodeURIComponent(login)}/role/${role}/confirm/validate-code` as const, }, SETTINGS_ABOUT: 'settings/about', - SETTINGS_APP_DOWNLOAD_LINKS: 'settings/about/app-download-links', SETTINGS_WALLET: 'settings/wallet', SETTINGS_WALLET_DOMAIN_CARD: { route: 'settings/wallet/card/:cardID?', diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 6a88c2717850..7819ddc8530f 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -79,7 +79,6 @@ const SCREENS = { COPILOT: 'Settings_Copilot', ABOUT: 'Settings_About', SAVE_THE_WORLD: 'Settings_TeachersUnite', - APP_DOWNLOAD_LINKS: 'Settings_App_Download_Links', ADD_DEBIT_CARD: 'Settings_Add_Debit_Card', ADD_BANK_ACCOUNT: 'Settings_Add_Bank_Account', ADD_US_BANK_ACCOUNT: 'Settings_Add_US_Bank_Account', @@ -97,6 +96,7 @@ const SCREENS = { DYNAMIC_EXIT_SURVEY_CONFIRM: 'Dynamic_ExitSurvey_Confirm', DYNAMIC_EXIT_SURVEY_REASON: 'Dynamic_ExitSurvey_Reason', DYNAMIC_KEYBOARD_SHORTCUTS: 'Dynamic_Keyboard_Shortcuts', + DYNAMIC_APP_DOWNLOAD_LINKS: 'Dynamic_App_Download_Links', DYNAMIC_SAGE_INTACCT_EXPORT: 'Dynamic_Sage_Intacct_Export', PROFILE: { diff --git a/src/components/ReceiptAlternativeMethods.tsx b/src/components/ReceiptAlternativeMethods.tsx index 3484141df715..eda5655838b3 100644 --- a/src/components/ReceiptAlternativeMethods.tsx +++ b/src/components/ReceiptAlternativeMethods.tsx @@ -9,7 +9,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import CONST from '@src/CONST'; import {addLeadingForwardSlash} from '@src/libs/Url'; -import ROUTES, {DYNAMIC_ROUTES} from '@src/ROUTES'; +import {DYNAMIC_ROUTES} from '@src/ROUTES'; import type {LayoutChangeEvent} from 'react-native'; @@ -34,7 +34,7 @@ function ReceiptAlternativeMethods({onLayout}: ReceiptAlternativeMethodsProps) { const {hasPhoneNumberLogin, isPhoneNumberLoaded} = useHasPhoneNumberLogin(); const buildDynamicRoute = useScreenBoundDynamicRoute(); - const downloadAppHref = `${environmentURL}${addLeadingForwardSlash(ROUTES.SETTINGS_APP_DOWNLOAD_LINKS)}`; + const downloadAppHref = `${environmentURL}${addLeadingForwardSlash(buildDynamicRoute(DYNAMIC_ROUTES.APP_DOWNLOAD_LINKS.path))}`; const contactMethodsHref = `${environmentURL}${addLeadingForwardSlash(buildDynamicRoute(DYNAMIC_ROUTES.CONTACT_METHODS.path))}`; if (!isLastMobileAppLoginLoaded || !isPhoneNumberLoaded) { diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index 690716b496b4..72868e7ef5fc 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -438,7 +438,6 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../../pages/settings/Preferences/PaymentCurrencyPage').default, [SCREENS.SETTINGS.DEVICE_MANAGEMENT]: () => require('../../../../pages/settings/Security/DeviceManagementPage').default, [SCREENS.SETTINGS.CLOSE]: () => require('../../../../pages/settings/Security/CloseAccountPage').default, - [SCREENS.SETTINGS.APP_DOWNLOAD_LINKS]: () => require('../../../../pages/settings/AppDownloadLinks').default, [SCREENS.SETTINGS.WALLET.CARDS_DIGITAL_DETAILS_UPDATE_ADDRESS]: () => require('../../../../pages/settings/Profile/PersonalDetails/PersonalAddressPage').default, [SCREENS.SETTINGS.WALLET.DOMAIN_CARD]: () => require('../../../../pages/settings/Wallet/ExpensifyCardPage/index').default, [SCREENS.SETTINGS.WALLET.EXPENSIFY_CARD_SPEND_RULES]: () => require('../../../../pages/settings/Wallet/WalletExpensifyCardSpendRulesPage').default, @@ -731,6 +730,7 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../../pages/settings/Wallet/ReportCardLostConfirmValidateCodePage').default, [SCREENS.SETTINGS.DYNAMIC_KEYBOARD_SHORTCUTS]: () => require('../../../../pages/settings/DynamicKeyboardShortcutsPage').default, + [SCREENS.SETTINGS.DYNAMIC_APP_DOWNLOAD_LINKS]: () => require('../../../../pages/settings/DynamicAppDownloadLinksPage').default, [SCREENS.SETTINGS.DYNAMIC_EXIT_SURVEY_REASON]: () => require('../../../../pages/settings/ExitSurvey/DynamicExitSurveyReasonPage').default, [SCREENS.SETTINGS.DYNAMIC_EXIT_SURVEY_CONFIRM]: () => require('../../../../pages/settings/ExitSurvey/DynamicExitSurveyConfirmPage').default, [SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_SETUP]: () => require('../../../../pages/workspace/accounting/qbo/QuickbooksOnlineSetupPage').default, diff --git a/src/libs/Navigation/helpers/getAdaptedStateFromPath.ts b/src/libs/Navigation/helpers/getAdaptedStateFromPath.ts index 2362f71b5cc9..26a25a0618ab 100644 --- a/src/libs/Navigation/helpers/getAdaptedStateFromPath.ts +++ b/src/libs/Navigation/helpers/getAdaptedStateFromPath.ts @@ -3,9 +3,11 @@ import TAB_SCREENS from '@libs/Navigation/AppNavigator/Navigators/TAB_SCREENS'; import { RHP_TO_DOMAIN, RHP_TO_HOME, + RHP_TO_HOME_DEEPLINK, RHP_TO_SEARCH, RHP_TO_SEARCH_DEEPLINK, RHP_TO_SETTINGS, + RHP_TO_SETTINGS_DEEPLINK, RHP_TO_SIDEBAR, RHP_TO_WORKSPACE, RHP_TO_WORKSPACES_LIST, @@ -83,7 +85,7 @@ function isRouteWithReportID(route: NavigationRoute): route is Route 0 ? pick(route.params, paramsFromRoute) : undefined, }, ); diff --git a/src/libs/Navigation/linkingConfig/RELATIONS/HOME_TO_RHP.ts b/src/libs/Navigation/linkingConfig/RELATIONS/HOME_TO_RHP.ts index 8f34a3c1a4cf..bda49a1c3ce1 100644 --- a/src/libs/Navigation/linkingConfig/RELATIONS/HOME_TO_RHP.ts +++ b/src/libs/Navigation/linkingConfig/RELATIONS/HOME_TO_RHP.ts @@ -1,13 +1,12 @@ import SCREENS from '@src/SCREENS'; const HOME_TO_RHP: Record = { - // Transaction/report RHP screens opened from the Recently added slot keep Home underneath on refresh/deep-link. - // Time Sensitive flows (enter signer info, confirm digital wallet) keep Home underneath too. + // Transaction/report RHP screens opened from the Recently added slot keep Home underneath. + // The confirm digital wallet flow is only opened from the Time Sensitive section, so it keeps Home underneath too. [SCREENS.HOME]: [ SCREENS.RIGHT_MODAL.SEARCH_REPORT, SCREENS.RIGHT_MODAL.SEARCH_MONEY_REQUEST_REPORT, SCREENS.RIGHT_MODAL.EXPENSE_REPORT, - SCREENS.REIMBURSEMENT_ACCOUNT_ENTER_SIGNER_INFO, SCREENS.SETTINGS.WALLET.CARD_ADD_TO_DIGITAL_WALLET, ], }; diff --git a/src/libs/Navigation/linkingConfig/RELATIONS/HOME_TO_RHP_DEEPLINK.ts b/src/libs/Navigation/linkingConfig/RELATIONS/HOME_TO_RHP_DEEPLINK.ts new file mode 100644 index 000000000000..ea72050703f5 --- /dev/null +++ b/src/libs/Navigation/linkingConfig/RELATIONS/HOME_TO_RHP_DEEPLINK.ts @@ -0,0 +1,13 @@ +import SCREENS from '@src/SCREENS'; + +/** + * Deeplink-only variant of HOME_TO_RHP. Consulted ONLY when navigation state is built from a path + * (deeplink / browser refresh / cold load) via the `isDeeplink` flag in getMatchingFullScreenRoute, + * so these RHP screens still get Home underneath on a fresh load without forcing it for in-app navigation. + */ +const HOME_TO_RHP_DEEPLINK: Record = { + // Also opened from a chat message, so it must not force Home underneath on click. + [SCREENS.HOME]: [SCREENS.REIMBURSEMENT_ACCOUNT_ENTER_SIGNER_INFO], +}; + +export default HOME_TO_RHP_DEEPLINK; diff --git a/src/libs/Navigation/linkingConfig/RELATIONS/SETTINGS_TO_RHP.ts b/src/libs/Navigation/linkingConfig/RELATIONS/SETTINGS_TO_RHP.ts index 8d15bf8fbc22..2e0b33de81c9 100755 --- a/src/libs/Navigation/linkingConfig/RELATIONS/SETTINGS_TO_RHP.ts +++ b/src/libs/Navigation/linkingConfig/RELATIONS/SETTINGS_TO_RHP.ts @@ -31,7 +31,6 @@ const SETTINGS_TO_RHP: Partial> = { + // These RHP screens are also opened from a chat or from Home, so they must not force the settings page underneath on click. + [SCREENS.SETTINGS.WALLET.ROOT]: [SCREENS.SETTINGS.WALLET.PERSONAL_CARD_DETAILS, SCREENS.SETTINGS.ADD_US_BANK_ACCOUNT], + [SCREENS.SETTINGS.SUBSCRIPTION.ROOT]: [SCREENS.SETTINGS.SUBSCRIPTION.ADD_PAYMENT_CARD], +}; + +export default SETTINGS_TO_RHP_DEEPLINK; diff --git a/src/libs/Navigation/linkingConfig/RELATIONS/index.ts b/src/libs/Navigation/linkingConfig/RELATIONS/index.ts index 07620f2b769c..9ab9c618bd41 100644 --- a/src/libs/Navigation/linkingConfig/RELATIONS/index.ts +++ b/src/libs/Navigation/linkingConfig/RELATIONS/index.ts @@ -1,8 +1,10 @@ import DOMAIN_TO_RHP from './DOMAIN_TO_RHP'; import HOME_TO_RHP from './HOME_TO_RHP'; +import HOME_TO_RHP_DEEPLINK from './HOME_TO_RHP_DEEPLINK'; import SEARCH_TO_RHP from './SEARCH_TO_RHP'; import SEARCH_TO_RHP_DEEPLINK from './SEARCH_TO_RHP_DEEPLINK'; import SETTINGS_TO_RHP from './SETTINGS_TO_RHP'; +import SETTINGS_TO_RHP_DEEPLINK from './SETTINGS_TO_RHP_DEEPLINK'; import SIDEBAR_TO_RHP from './SIDEBAR_TO_RHP'; import SIDEBAR_TO_SPLIT from './SIDEBAR_TO_SPLIT'; import WORKSPACE_TO_RHP from './WORKSPACE_TO_RHP'; @@ -29,6 +31,7 @@ function createInverseRelation(relations: Pa } const RHP_TO_SETTINGS = createInverseRelation(SETTINGS_TO_RHP); +const RHP_TO_SETTINGS_DEEPLINK = createInverseRelation(SETTINGS_TO_RHP_DEEPLINK); const RHP_TO_WORKSPACE = createInverseRelation(WORKSPACE_TO_RHP); const RHP_TO_SIDEBAR = createInverseRelation(SIDEBAR_TO_RHP); const SPLIT_TO_SIDEBAR = createInverseRelation(SIDEBAR_TO_SPLIT); @@ -39,5 +42,19 @@ const RHP_TO_SEARCH = createInverseRelation(SEARCH_TO_RHP); const RHP_TO_SEARCH_DEEPLINK = createInverseRelation(SEARCH_TO_RHP_DEEPLINK); const RHP_TO_DOMAIN = createInverseRelation(DOMAIN_TO_RHP); const RHP_TO_HOME = createInverseRelation(HOME_TO_RHP); +const RHP_TO_HOME_DEEPLINK = createInverseRelation(HOME_TO_RHP_DEEPLINK); -export {RHP_TO_SETTINGS, RHP_TO_WORKSPACE, RHP_TO_SIDEBAR, RHP_TO_SEARCH, RHP_TO_SEARCH_DEEPLINK, SIDEBAR_TO_SPLIT, SPLIT_TO_SIDEBAR, RHP_TO_WORKSPACES_LIST, RHP_TO_DOMAIN, RHP_TO_HOME}; +export { + RHP_TO_SETTINGS, + RHP_TO_SETTINGS_DEEPLINK, + RHP_TO_WORKSPACE, + RHP_TO_SIDEBAR, + RHP_TO_SEARCH, + RHP_TO_SEARCH_DEEPLINK, + SIDEBAR_TO_SPLIT, + SPLIT_TO_SIDEBAR, + RHP_TO_WORKSPACES_LIST, + RHP_TO_DOMAIN, + RHP_TO_HOME, + RHP_TO_HOME_DEEPLINK, +}; diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index af8fd2301392..c2febb0294ca 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -436,10 +436,6 @@ const config: LinkingOptions['config'] = { path: ROUTES.SETTINGS_TIMEZONE_SELECT, exact: true, }, - [SCREENS.SETTINGS.APP_DOWNLOAD_LINKS]: { - path: ROUTES.SETTINGS_APP_DOWNLOAD_LINKS, - exact: true, - }, [SCREENS.SETTINGS.DYNAMIC_VERIFY_ACCOUNT]: DYNAMIC_ROUTES.VERIFY_ACCOUNT.path, [SCREENS.SETTINGS.DYNAMIC_ADD_BANK_ACCOUNT_VERIFY_ACCOUNT]: DYNAMIC_ROUTES.ADD_BANK_ACCOUNT_VERIFY_ACCOUNT.path, [SCREENS.SETTINGS.SUBSCRIPTION.DYNAMIC_PAYMENT_CARD_CURRENCY_SELECTOR]: DYNAMIC_ROUTES.PAYMENT_CARD_CURRENCY_SELECTOR.path, @@ -1217,6 +1213,7 @@ const config: LinkingOptions['config'] = { [SCREENS.DYNAMIC_REIMBURSEMENT_ACCOUNT_VERIFY_ACCOUNT]: DYNAMIC_ROUTES.BANK_ACCOUNT_VERIFY_ACCOUNT.path, [SCREENS.REIMBURSEMENT_ACCOUNT_ENTER_SIGNER_INFO]: ROUTES.BANK_ACCOUNT_ENTER_SIGNER_INFO.route, [SCREENS.SETTINGS.DYNAMIC_KEYBOARD_SHORTCUTS]: DYNAMIC_ROUTES.KEYBOARD_SHORTCUTS.path, + [SCREENS.SETTINGS.DYNAMIC_APP_DOWNLOAD_LINKS]: DYNAMIC_ROUTES.APP_DOWNLOAD_LINKS.path, [SCREENS.WORKSPACE.NAME]: ROUTES.WORKSPACE_OVERVIEW_NAME.route, [SCREENS.SETTINGS.SHARE_CODE]: { path: ROUTES.SETTINGS_SHARE_CODE, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index d54daef2d331..52d5dfe93e7e 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -1450,6 +1450,7 @@ type SettingsNavigatorParamList = { isFromDomainCardDetail?: string; }; [SCREENS.SETTINGS.DYNAMIC_KEYBOARD_SHORTCUTS]: undefined; + [SCREENS.SETTINGS.DYNAMIC_APP_DOWNLOAD_LINKS]: undefined; [SCREENS.WORKSPACE.TAX_CREATE]: { policyID: string; }; diff --git a/src/pages/settings/AboutPage/AboutPage.tsx b/src/pages/settings/AboutPage/AboutPage.tsx index 91ec2738ef10..5553f49080cc 100644 --- a/src/pages/settings/AboutPage/AboutPage.tsx +++ b/src/pages/settings/AboutPage/AboutPage.tsx @@ -29,7 +29,7 @@ import {navigateToConciergeChat} from '@userActions/Report'; import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; -import ROUTES, {DYNAMIC_ROUTES} from '@src/ROUTES'; +import {DYNAMIC_ROUTES} from '@src/ROUTES'; import type IconAsset from '@src/types/utils/IconAsset'; import type WithSentryLabel from '@src/types/utils/SentryLabel'; @@ -84,7 +84,7 @@ function AboutPage() { translationKey: 'initialSettingsPage.aboutPage.appDownloadLinks', icon: icons.Link, sentryLabel: CONST.SENTRY_LABEL.SETTINGS_ABOUT.APP_DOWNLOAD_LINKS, - action: waitForNavigate(() => Navigation.navigate(ROUTES.SETTINGS_APP_DOWNLOAD_LINKS)), + action: waitForNavigate(() => Navigation.navigate(createDynamicRoute(DYNAMIC_ROUTES.APP_DOWNLOAD_LINKS.path))), }, { translationKey: 'initialSettingsPage.aboutPage.viewKeyboardShortcuts', diff --git a/src/pages/settings/AppDownloadLinks.tsx b/src/pages/settings/DynamicAppDownloadLinksPage.tsx similarity index 91% rename from src/pages/settings/AppDownloadLinks.tsx rename to src/pages/settings/DynamicAppDownloadLinksPage.tsx index d08f55081883..80770292b247 100644 --- a/src/pages/settings/AppDownloadLinks.tsx +++ b/src/pages/settings/DynamicAppDownloadLinksPage.tsx @@ -7,6 +7,7 @@ import QRShare from '@components/QRShare'; import ScreenWrapper from '@components/ScreenWrapper'; import ScrollView from '@components/ScrollView'; +import useDynamicBackPath from '@hooks/useDynamicBackPath'; import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -18,6 +19,7 @@ import {showContextMenu} from '@pages/inbox/report/ContextMenu/ReportActionConte import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; +import {DYNAMIC_ROUTES} from '@src/ROUTES'; import type {View} from 'react-native'; @@ -29,11 +31,12 @@ type DownloadMenuItem = MenuItemProps & { link: string; }; -function AppDownloadLinksPage() { +function DynamicAppDownloadLinksPage() { const icons = useMemoizedLazyExpensifyIcons(['Android', 'Apple', 'Monitor', 'NewWindow']); const styles = useThemeStyles(); const {translate} = useLocalize(); const popoverAnchor = useRef(null); + const backPath = useDynamicBackPath(DYNAMIC_ROUTES.APP_DOWNLOAD_LINKS.path); const menuItems: DownloadMenuItem[] = [ { @@ -60,7 +63,7 @@ function AppDownloadLinksPage() { Navigation.goBack()} + onBackButtonPress={() => Navigation.goBack(backPath)} /> { + const topmostFullScreenRoute = navigationRef.current?.getRootState()?.routes.findLast((rootRoute) => isFullScreenName(rootRoute.name)); + return getActiveTabName(topmostFullScreenRoute) === NAVIGATORS.SETTINGS_SPLIT_NAVIGATOR ? ROUTES.SETTINGS_WALLET : undefined; + }; + const updateCard = () => { if (!card) { return; @@ -134,7 +142,7 @@ function PersonalCardDetailsPage({route}: PersonalCardDetailsPageProps) { return; } const savedColumnLayout = savedColumnLayouts?.[card.cardID]; - Navigation.goBack(ROUTES.SETTINGS_WALLET, { + Navigation.goBack(getExitFallbackRoute(), { afterTransition: () => deletePersonalCard({cardID: card.cardID, card, allTransactions, allReports, savedColumnLayout}), }); }); @@ -174,7 +182,7 @@ function PersonalCardDetailsPage({route}: PersonalCardDetailsPageProps) { > Navigation.goBack(ROUTES.SETTINGS_WALLET)} + onBackButtonPress={() => Navigation.goBack(getExitFallbackRoute())} /> diff --git a/tests/navigation/NavigateTests.tsx b/tests/navigation/NavigateTests.tsx index d7f578cc2cdd..8a3ff41461a4 100644 --- a/tests/navigation/NavigateTests.tsx +++ b/tests/navigation/NavigateTests.tsx @@ -4,13 +4,14 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout'; import SidePanelActions from '@libs/actions/SidePanel'; import getIsNarrowLayout from '@libs/getIsNarrowLayout'; +import createDynamicRoute from '@libs/Navigation/helpers/dynamicRoutesUtils/createDynamicRoute'; import getPathFromState from '@libs/Navigation/helpers/getPathFromState'; import Navigation from '@libs/Navigation/Navigation'; import navigationRef from '@libs/Navigation/navigationRef'; import CONST from '@src/CONST'; import NAVIGATORS from '@src/NAVIGATORS'; -import ROUTES from '@src/ROUTES'; +import ROUTES, {DYNAMIC_ROUTES} from '@src/ROUTES'; import SCREENS from '@src/SCREENS'; import React from 'react'; @@ -408,7 +409,7 @@ describe('Navigate', () => { expect(lastRootRoute?.state?.routes.at(-1)?.name).toBe(SCREENS.RIGHT_MODAL.SETTINGS); }); - it('shows Subscription behind the payment-card RHP when navigating from Reports', () => { + it('shows Profile behind the display name RHP when navigating from Reports', () => { render( { ); act(() => { - Navigation.navigate(ROUTES.SETTINGS_SUBSCRIPTION_ADD_PAYMENT_CARD); + Navigation.navigate(ROUTES.SETTINGS_DISPLAY_NAME); }); const rootState = navigationRef.current?.getRootState(); @@ -457,7 +458,54 @@ describe('Navigate', () => { const tabState = rootState?.routes.at(0)?.state; const activeTab = tabState?.routes.at(tabState.index ?? 0); expect(activeTab?.name).toBe(NAVIGATORS.SETTINGS_SPLIT_NAVIGATOR); - expect(activeTab?.state?.routes.at(-1)?.name).toBe(SCREENS.SETTINGS.SUBSCRIPTION.ROOT); + expect(activeTab?.state?.routes.at(-1)?.name).toBe(SCREENS.SETTINGS.PROFILE.ROOT); + }); + + it.each([ + ['add payment card', () => ROUTES.SETTINGS_SUBSCRIPTION_ADD_PAYMENT_CARD], + ['add US bank account', () => ROUTES.SETTINGS_ADD_US_BANK_ACCOUNT.getRoute()], + ['personal card details', () => ROUTES.SETTINGS_WALLET_PERSONAL_CARD_DETAILS.getRoute('123')], + ['enter signer info', () => ROUTES.BANK_ACCOUNT_ENTER_SIGNER_INFO.getRoute('1', '2', false)], + ['app download links', () => createDynamicRoute(DYNAMIC_ROUTES.APP_DOWNLOAD_LINKS.path)], + ])('keeps Reports behind the %s RHP when opened in-app', (_label, getRoute) => { + render( + , + ); + + act(() => { + Navigation.navigate(getRoute()); + }); + + const rootState = navigationRef.current?.getRootState(); + expect(rootState?.routes.at(-1)?.name).toBe(NAVIGATORS.RIGHT_MODAL_NAVIGATOR); + const tabState = rootState?.routes.at(0)?.state; + expect(tabState?.routes.at(tabState.index ?? 0)?.name).toBe(NAVIGATORS.REPORTS_SPLIT_NAVIGATOR); }); it('preserves report navigation history when opening a workspace from an RHP', () => { diff --git a/tests/navigation/appDownloadLinksBackgroundTests.ts b/tests/navigation/appDownloadLinksBackgroundTests.ts new file mode 100644 index 000000000000..1d2db9f2313e --- /dev/null +++ b/tests/navigation/appDownloadLinksBackgroundTests.ts @@ -0,0 +1,15 @@ +import NAVIGATORS from '@src/NAVIGATORS'; +import SCREENS from '@src/SCREENS'; + +import getFullScreenUnderRHP from '../utils/getFullScreenUnderRHP'; + +describe('app download links background on a fresh load', () => { + it.each([ + ['a report', '/r/123/app-download-links', NAVIGATORS.REPORTS_SPLIT_NAVIGATOR, SCREENS.REPORT], + ['an expense report', '/search/view/123/app-download-links', NAVIGATORS.SEARCH_FULLSCREEN_NAVIGATOR, SCREENS.SEARCH.ROOT], + ['a money request report', '/search/r/123/app-download-links', NAVIGATORS.SEARCH_FULLSCREEN_NAVIGATOR, SCREENS.SEARCH.ROOT], + ['the about page', '/settings/about/app-download-links', NAVIGATORS.SETTINGS_SPLIT_NAVIGATOR, SCREENS.SETTINGS.ABOUT], + ])('keeps the full screen it was opened from: %s', (_label, path, name, central) => { + expect(getFullScreenUnderRHP(path)).toEqual({name, central}); + }); +}); diff --git a/tests/navigation/contactMethodsBackgroundTests.ts b/tests/navigation/contactMethodsBackgroundTests.ts index 53a0c6b5fab1..60dd1b7a1c4a 100644 --- a/tests/navigation/contactMethodsBackgroundTests.ts +++ b/tests/navigation/contactMethodsBackgroundTests.ts @@ -1,19 +1,6 @@ -import getAdaptedStateFromPath from '@libs/Navigation/helpers/getAdaptedStateFromPath'; - import NAVIGATORS from '@src/NAVIGATORS'; -/** - * The contact methods screens are dynamic routes appended to whatever screen opened them, so the - * full screen under the overlay has to come from the base path. In-app navigation never exercises - * that, because linkTo skips full screen matching while an RHP is already on top, so only a fresh - * load of the URL can catch a screen that is still pinned to a central pane in the relations. - */ -function getBackgroundFullScreenName(path: string): string | undefined { - const state = getAdaptedStateFromPath(path, undefined); - const tabRoute = state?.routes?.findLast((route) => route.name === NAVIGATORS.TAB_NAVIGATOR); - const tabState = tabRoute?.state; - return tabState?.routes?.at(tabState.index ?? (tabState.routes?.length ?? 1) - 1)?.name; -} +import getFullScreenUnderRHP from '../utils/getFullScreenUnderRHP'; describe('contact methods background on a fresh load', () => { describe('resolves the base path when the contact methods screens are opened from a report', () => { @@ -23,7 +10,7 @@ describe('contact methods background on a fresh load', () => { ['the new contact method form', '/r/123/contact-methods/new-contact-method'], ['the new contact method magic code', '/r/123/contact-methods/new/confirm-validate-code'], ])('%s', (_label, path) => { - expect(getBackgroundFullScreenName(path)).toBe(NAVIGATORS.REPORTS_SPLIT_NAVIGATOR); + expect(getFullScreenUnderRHP(path).name).toBe(NAVIGATORS.REPORTS_SPLIT_NAVIGATOR); }); }); @@ -34,7 +21,7 @@ describe('contact methods background on a fresh load', () => { ['the money request report details', '/search/r/123/contact-methods/a%40b.com/details'], ['the new contact method magic code', '/search/view/123/contact-methods/new/confirm-validate-code'], ])('%s', (_label, path) => { - expect(getBackgroundFullScreenName(path)).toBe(NAVIGATORS.SEARCH_FULLSCREEN_NAVIGATOR); + expect(getFullScreenUnderRHP(path).name).toBe(NAVIGATORS.SEARCH_FULLSCREEN_NAVIGATOR); }); }); @@ -44,7 +31,7 @@ describe('contact methods background on a fresh load', () => { ['the contact method details', '/settings/profile/contact-methods/a%40b.com/details'], ['the new contact method form', '/settings/profile/contact-methods/new-contact-method'], ])('%s', (_label, path) => { - expect(getBackgroundFullScreenName(path)).toBe(NAVIGATORS.SETTINGS_SPLIT_NAVIGATOR); + expect(getFullScreenUnderRHP(path).name).toBe(NAVIGATORS.SETTINGS_SPLIT_NAVIGATOR); }); }); }); diff --git a/tests/navigation/getMatchingFullScreenRouteTests.ts b/tests/navigation/getMatchingFullScreenRouteTests.ts index d4fee769c7a6..e042223d7487 100644 --- a/tests/navigation/getMatchingFullScreenRouteTests.ts +++ b/tests/navigation/getMatchingFullScreenRouteTests.ts @@ -27,6 +27,7 @@ jest.mock('@libs/Navigation/linkingConfig/RELATIONS', () => { return { RHP_TO_DOMAIN: {}, RHP_TO_HOME: {Home: 'home'}, + RHP_TO_HOME_DEEPLINK: {[SCREENS_MOCK.REIMBURSEMENT_ACCOUNT_ENTER_SIGNER_INFO]: SCREENS_MOCK.HOME}, RHP_TO_SEARCH: {}, // Deeplink-only mapping: create-flow entry points resolve to the Search fullscreen under the RHP. RHP_TO_SEARCH_DEEPLINK: { @@ -37,6 +38,7 @@ jest.mock('@libs/Navigation/linkingConfig/RELATIONS', () => { [SCREENS_MOCK.TRACK_EXPENSE]: SCREENS_MOCK.SEARCH.ROOT, }, RHP_TO_SETTINGS: {}, + RHP_TO_SETTINGS_DEEPLINK: {[SCREENS_MOCK.SETTINGS.WALLET.PERSONAL_CARD_DETAILS]: SCREENS_MOCK.SETTINGS.WALLET.ROOT}, RHP_TO_SIDEBAR: {}, RHP_TO_WORKSPACE: {}, RHP_TO_WORKSPACES_LIST: {}, @@ -292,3 +294,31 @@ describe('getMatchingFullScreenRoute - deeplink-only search relations', () => { }, ); }); + +describe('getMatchingFullScreenRoute - deeplink-only settings and home relations', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('resolves the personal card details screen to the wallet when built from a path (isDeeplink=true)', () => { + const result = getMatchingFullScreenRoute({name: SCREENS.SETTINGS.WALLET.PERSONAL_CARD_DETAILS, params: {cardID: '1'}}, true); + + expect(result?.name).toBe(NAVIGATORS.TAB_NAVIGATOR); + const activeTab = result && 'state' in result ? result.state?.routes.at(result.state.index ?? 0) : undefined; + expect(activeTab?.state?.routes.at(-1)?.name).toBe(SCREENS.SETTINGS.WALLET.ROOT); + }); + + it('resolves the enter signer info screen to Home when built from a path (isDeeplink=true)', () => { + const result = getMatchingFullScreenRoute({name: SCREENS.REIMBURSEMENT_ACCOUNT_ENTER_SIGNER_INFO}, true); + + expect(result?.name).toBe(NAVIGATORS.TAB_NAVIGATOR); + expect(result && 'state' in result ? result.state?.routes.at(0)?.name : undefined).toBe(SCREENS.HOME); + }); + + it.each([SCREENS.SETTINGS.WALLET.PERSONAL_CARD_DETAILS, SCREENS.REIMBURSEMENT_ACCOUNT_ENTER_SIGNER_INFO])( + 'does not resolve %s to a fullscreen for in-app navigation (isDeeplink=false)', + (screenName) => { + expect(getMatchingFullScreenRoute({name: screenName})).toBeUndefined(); + }, + ); +}); diff --git a/tests/navigation/rhpBackgroundDeeplinkRelationTests.ts b/tests/navigation/rhpBackgroundDeeplinkRelationTests.ts new file mode 100644 index 000000000000..d50ea044d847 --- /dev/null +++ b/tests/navigation/rhpBackgroundDeeplinkRelationTests.ts @@ -0,0 +1,34 @@ +import {RHP_TO_HOME, RHP_TO_HOME_DEEPLINK, RHP_TO_SETTINGS, RHP_TO_SETTINGS_DEEPLINK} from '@libs/Navigation/linkingConfig/RELATIONS'; + +import NAVIGATORS from '@src/NAVIGATORS'; +import SCREENS from '@src/SCREENS'; + +import getFullScreenUnderRHP from '../utils/getFullScreenUnderRHP'; + +describe('RHP screens opened from a chat or Home', () => { + it.each([ + [SCREENS.SETTINGS.WALLET.PERSONAL_CARD_DETAILS, SCREENS.SETTINGS.WALLET.ROOT], + [SCREENS.SETTINGS.ADD_US_BANK_ACCOUNT, SCREENS.SETTINGS.WALLET.ROOT], + [SCREENS.SETTINGS.SUBSCRIPTION.ADD_PAYMENT_CARD, SCREENS.SETTINGS.SUBSCRIPTION.ROOT], + ])('%s is pinned to %s only for a deep link', (screen, settingsScreen) => { + expect(RHP_TO_SETTINGS[screen]).toBeUndefined(); + expect(RHP_TO_SETTINGS_DEEPLINK[screen]).toBe(settingsScreen); + }); + + it('enter signer info is pinned to Home only for a deep link', () => { + expect(RHP_TO_HOME[SCREENS.REIMBURSEMENT_ACCOUNT_ENTER_SIGNER_INFO]).toBeUndefined(); + expect(RHP_TO_HOME_DEEPLINK[SCREENS.REIMBURSEMENT_ACCOUNT_ENTER_SIGNER_INFO]).toBe(SCREENS.HOME); + }); + + it.each([ + ['/settings/wallet/personal-card/123', SCREENS.SETTINGS.WALLET.ROOT], + ['/settings/wallet/add-us-bank-account', SCREENS.SETTINGS.WALLET.ROOT], + ['/settings/subscription/add-payment-card', SCREENS.SETTINGS.SUBSCRIPTION.ROOT], + ])('%s still lands on the settings page on a fresh load', (path, settingsScreen) => { + expect(getFullScreenUnderRHP(path)).toEqual({name: NAVIGATORS.SETTINGS_SPLIT_NAVIGATOR, central: settingsScreen}); + }); + + it('enter signer info still lands on Home on a fresh load', () => { + expect(getFullScreenUnderRHP('/bank-account/enter-signer-info/name').name).toBe(SCREENS.HOME); + }); +}); diff --git a/tests/utils/getFullScreenUnderRHP.ts b/tests/utils/getFullScreenUnderRHP.ts new file mode 100644 index 000000000000..04dd63a827ac --- /dev/null +++ b/tests/utils/getFullScreenUnderRHP.ts @@ -0,0 +1,14 @@ +import getAdaptedStateFromPath from '@libs/Navigation/helpers/getAdaptedStateFromPath'; + +import NAVIGATORS from '@src/NAVIGATORS'; + +/** Resolves the path the way a deep link or refresh does and returns the full screen left under the RHP. */ +function getFullScreenUnderRHP(path: string) { + const state = getAdaptedStateFromPath(path, undefined); + const tabRoute = state?.routes?.findLast((route) => route.name === NAVIGATORS.TAB_NAVIGATOR); + const tabState = tabRoute?.state; + const fullScreen = tabState?.routes?.at(tabState.index ?? (tabState.routes?.length ?? 1) - 1); + return {name: fullScreen?.name, central: fullScreen?.state?.routes?.at(-1)?.name}; +} + +export default getFullScreenUnderRHP;