diff --git a/src/components/SidePanel/RHPVariantTest/index.ts b/src/components/SidePanel/RHPVariantTest/index.ts index b301a5c8e89b..43cfe8e7d11d 100644 --- a/src/components/SidePanel/RHPVariantTest/index.ts +++ b/src/components/SidePanel/RHPVariantTest/index.ts @@ -47,9 +47,6 @@ const shouldOpenRHPVariant: ShouldOpenRHPVariant = () => { const handleRHPVariantNavigation: HandleRHPVariantNavigation = (onboardingPolicyID) => { Navigation.navigate(ROUTES.WORKSPACE_OVERVIEW.getRoute(onboardingPolicyID)); SidePanelActions.openSidePanel(true); - Navigation.isNavigationReady().then(() => { - Navigation.navigate(ROUTES.TEST_DRIVE_MODAL_ROOT.route); - }); }; export {shouldOpenRHPVariant, handleRHPVariantNavigation}; diff --git a/src/libs/Navigation/guards/TestDriveModalGuard.ts b/src/libs/Navigation/guards/TestDriveModalGuard.ts deleted file mode 100644 index 5adb7146824b..000000000000 --- a/src/libs/Navigation/guards/TestDriveModalGuard.ts +++ /dev/null @@ -1,163 +0,0 @@ -import type {NavigationAction, NavigationState} from '@react-navigation/native'; -import {findFocusedRoute} from '@react-navigation/native'; -import {hasCompletedGuidedSetupFlowSelector} from '@selectors/Onboarding'; -import Onyx from 'react-native-onyx'; -import type {OnyxEntry} from 'react-native-onyx'; -import Log from '@libs/Log'; -import CONST from '@src/CONST'; -import NAVIGATORS from '@src/NAVIGATORS'; -import ONYXKEYS from '@src/ONYXKEYS'; -import ROUTES from '@src/ROUTES'; -import SCREENS from '@src/SCREENS'; -import type Onboarding from '@src/types/onyx/Onboarding'; -import type {GuardResult, NavigationGuard} from './types'; - -/** - * Module-level Onyx subscriptions for TestDriveModalGuard - */ -let onboarding: OnyxEntry; -let onboardingPolicyID: OnyxEntry; -let hasNonPersonalPolicy: OnyxEntry; - -let hasRedirectedToTestDriveModal = false; - -/** - * Reset the session flag (for testing purposes) - */ -function resetSessionFlag() { - hasRedirectedToTestDriveModal = false; -} - -Onyx.connectWithoutView({ - key: ONYXKEYS.NVP_ONBOARDING, - callback: (value) => { - onboarding = value; - - // Reset the session flag when modal is dismissed - if (value?.testDriveModalDismissed === true) { - hasRedirectedToTestDriveModal = false; - } - }, -}); - -Onyx.connectWithoutView({ - key: ONYXKEYS.ONBOARDING_POLICY_ID, - callback: (value) => { - onboardingPolicyID = value; - }, -}); - -Onyx.connectWithoutView({ - key: ONYXKEYS.HAS_NON_PERSONAL_POLICY, - callback: (value) => { - hasNonPersonalPolicy = value; - }, -}); - -/** - * Check if navigation should be blocked while the test drive modal is active. - * After the guard redirects to the modal, there's a delay before the native Modal overlay becomes visible - * During this window, tab switches can push screens on top of the modal navigator, - * causing DISMISS_MODAL to fail since it only checks the last route. - */ -function shouldBlockWhileModalActive(state: NavigationState, action: NavigationAction): boolean { - const isModalDismissed = onboarding?.testDriveModalDismissed === true; - if (!hasRedirectedToTestDriveModal || isModalDismissed) { - return false; - } - - // Only block when the test drive modal is the LAST route (on top of the stack). - // If something was already pushed on top (broken state), don't block — the user - // needs to be able to navigate to recover from the error. - const lastRoute = state.routes.at(-1); - if (lastRoute?.name !== NAVIGATORS.TEST_DRIVE_MODAL_NAVIGATOR) { - return false; - } - - // Allow DISMISS_MODAL (Skip button) and GO_BACK (close/X button, confirm flow) - if (action.type === CONST.NAVIGATION.ACTION_TYPE.DISMISS_MODAL || action.type === CONST.NAVIGATION.ACTION_TYPE.GO_BACK) { - return false; - } - - return true; -} - -/** - * Check if we're already on or navigating to the test drive modal - * This prevents redirect loops where our redirect creates new navigation actions - */ -function isNavigatingToTestDriveModal(state: NavigationState, action: NavigationAction): boolean { - const currentRoute = findFocusedRoute(state); - if (currentRoute?.name === SCREENS.TEST_DRIVE_MODAL.ROOT) { - return true; - } - - if (action.type === 'RESET' && action.payload) { - const targetRoute = findFocusedRoute(action.payload as NavigationState); - if (targetRoute?.name === SCREENS.TEST_DRIVE_MODAL.ROOT) { - return true; - } - } - - return false; -} - -/** - * TestDriveModalGuard handles the test drive modal flow - * This modal appears after a user completes the guided setup flow but hasn't dismissed the test drive modal yet - */ -const TestDriveModalGuard: NavigationGuard = { - name: 'TestDriveModalGuard', - - evaluate: (state: NavigationState, action: NavigationAction, context): GuardResult => { - if (context.isLoading) { - return {type: 'ALLOW'}; - } - - if (shouldBlockWhileModalActive(state, action)) { - return {type: 'BLOCK', reason: '[TestDriveModalGuard] Blocking navigation while test drive modal is active'}; - } - - const isModalDismissed = onboarding?.testDriveModalDismissed === true; - const isNavigatingToModal = isNavigatingToTestDriveModal(state, action); - - // Redirect to home if trying to access dismissed test drive modal (prevent URL navigation) - if (isNavigatingToModal && isModalDismissed) { - Log.info('[TestDriveModalGuard] Redirecting to home - test drive modal has been dismissed'); - return {type: 'REDIRECT', route: ROUTES.HOME}; - } - - // Allow if we're already navigating to the modal (prevents redirect loops) - if (isNavigatingToModal) { - return {type: 'ALLOW'}; - } - - // Skip if already redirected or user has accessible policy - if (hasRedirectedToTestDriveModal || (onboardingPolicyID && hasNonPersonalPolicy)) { - Log.info('[TestDriveModalGuard] Already redirected or user has accessible policy, allowing'); - return {type: 'ALLOW'}; - } - - // Check if user has completed the guided setup flow - const hasCompletedGuidedSetup = hasCompletedGuidedSetupFlowSelector(onboarding) ?? false; - - // Check if test drive modal should be shown - const shouldShowTestDriveModal = onboarding?.testDriveModalDismissed === false; - - // If user completed setup and modal should be shown, redirect to it once - if (hasCompletedGuidedSetup && shouldShowTestDriveModal) { - Log.info('[TestDriveModalGuard] Redirecting to test drive modal'); - hasRedirectedToTestDriveModal = true; - - return { - type: 'REDIRECT', - route: ROUTES.TEST_DRIVE_MODAL_ROOT.route, - }; - } - - return {type: 'ALLOW'}; - }, -}; - -export default TestDriveModalGuard; -export {resetSessionFlag}; diff --git a/src/libs/Navigation/guards/index.ts b/src/libs/Navigation/guards/index.ts index d6e036358cac..33851c2907cc 100644 --- a/src/libs/Navigation/guards/index.ts +++ b/src/libs/Navigation/guards/index.ts @@ -6,7 +6,6 @@ import ONYXKEYS from '@src/ONYXKEYS'; import type {Session} from '@src/types/onyx'; import MigratedUserWelcomeModalGuard from './MigratedUserWelcomeModalGuard'; import OnboardingGuard from './OnboardingGuard'; -import TestDriveModalGuard from './TestDriveModalGuard'; import type {GuardContext, GuardResult, NavigationGuard} from './types'; /** @@ -102,7 +101,6 @@ function clearGuards(): void { registerGuard(OnboardingGuard); registerGuard(MigratedUserWelcomeModalGuard); -registerGuard(TestDriveModalGuard); export {registerGuard, createGuardContext, evaluateGuards, getRegisteredGuards, clearGuards}; export type {NavigationGuard, GuardResult, GuardContext}; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 96d8f512959e..22eb7007262f 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -6426,7 +6426,7 @@ function buildOptimisticAddCommentReportAction( function buildConciergeGreetingReportAction(reportID: string, greetingText: string, created: string): ReportAction { return { - reportActionID: CONST.CONCIERGE_GREETING_ACTION_ID, + reportActionID: String(CONST.CONCIERGE_GREETING_ACTION_ID), reportID, actionName: CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT, actorAccountID: CONST.ACCOUNT_ID.CONCIERGE, @@ -11616,14 +11616,7 @@ function prepareOnboardingOnyxData({ (policy) => policy.type !== CONST.POLICY.TYPE.PERSONAL && getPolicyRole(policy, currentUserEmail) === CONST.POLICY.ROLE.ADMIN, ); - let testDriveURL: string; - if (([CONST.ONBOARDING_CHOICES.MANAGE_TEAM, CONST.ONBOARDING_CHOICES.TEST_DRIVE_RECEIVER, CONST.ONBOARDING_CHOICES.TRACK_WORKSPACE] as OnboardingPurpose[]).includes(engagementChoice)) { - testDriveURL = ROUTES.TEST_DRIVE_DEMO_ROOT; - } else if (introSelected?.choice === CONST.ONBOARDING_CHOICES.SUBMIT && introSelected.inviteType === CONST.ONBOARDING_INVITE_TYPES.WORKSPACE) { - testDriveURL = ROUTES.TEST_DRIVE_DEMO_ROOT; - } else { - testDriveURL = ROUTES.TEST_DRIVE_MODAL_ROOT.route; - } + const testDriveURL = ROUTES.TEST_DRIVE_DEMO_ROOT; const onboardingTaskParams: OnboardingTaskLinks = { integrationName, diff --git a/src/libs/actions/IOU/index.ts b/src/libs/actions/IOU/index.ts index 9f4b96d81181..99cd0f4e1871 100644 --- a/src/libs/actions/IOU/index.ts +++ b/src/libs/actions/IOU/index.ts @@ -11255,7 +11255,6 @@ function completePaymentOnboarding( onboardingPolicyID, paymentSelected, wasInvited: true, - shouldSkipTestDriveModal: true, companySize: introSelected?.companySize as OnboardingCompanySize, introSelected, isSelfTourViewed, diff --git a/src/libs/actions/Report/index.ts b/src/libs/actions/Report/index.ts index f22a10ea138c..f4e03d26c5a2 100644 --- a/src/libs/actions/Report/index.ts +++ b/src/libs/actions/Report/index.ts @@ -4876,7 +4876,6 @@ type CompleteOnboardingProps = { userReportedIntegration?: OnboardingAccounting; wasInvited?: boolean; selectedInterestedFeatures?: string[]; - shouldSkipTestDriveModal?: boolean; isInvitedAccountant?: boolean; onboardingPurposeSelected?: OnboardingPurpose; shouldWaitForRHPVariantInitialization?: boolean; @@ -4897,7 +4896,6 @@ async function completeOnboarding({ userReportedIntegration, wasInvited, selectedInterestedFeatures, - shouldSkipTestDriveModal, isInvitedAccountant, onboardingPurposeSelected, shouldWaitForRHPVariantInitialization = false, @@ -4942,27 +4940,6 @@ async function completeOnboarding({ optimisticConciergeReportActionID, }; - // We should only set testDriveModalDismissed to false if it's not already true (i.e., if the modal hasn't been dismissed yet). - if (!shouldSkipTestDriveModal && onboarding?.testDriveModalDismissed !== true) { - optimisticData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.NVP_ONBOARDING, - value: {testDriveModalDismissed: false}, - }); - - successData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.NVP_ONBOARDING, - value: {testDriveModalDismissed: false}, - }); - - failureData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.NVP_ONBOARDING, - value: {testDriveModalDismissed: null}, - }); - } - if (shouldWaitForRHPVariantInitialization) { // Wait for the workspace to be created before completing the guided setup await waitForWrites(SIDE_EFFECT_REQUEST_COMMANDS.COMPLETE_GUIDED_SETUP); diff --git a/src/libs/actions/Tour.ts b/src/libs/actions/Tour.ts index c9be2457b27d..5f524e3f6cea 100644 --- a/src/libs/actions/Tour.ts +++ b/src/libs/actions/Tour.ts @@ -7,18 +7,19 @@ import type {IntroSelected} from './Report'; function startTestDrive(introSelected: IntroSelected | undefined, hasUserBeenAddedToNudgeMigration: boolean, isUserPaidPolicyMember: boolean) { // eslint-disable-next-line @typescript-eslint/no-deprecated InteractionManager.runAfterInteractions(() => { - if ( + const shouldNavigateToDemo = hasUserBeenAddedToNudgeMigration || isUserPaidPolicyMember || introSelected?.choice === CONST.ONBOARDING_CHOICES.MANAGE_TEAM || introSelected?.choice === CONST.ONBOARDING_CHOICES.TEST_DRIVE_RECEIVER || introSelected?.choice === CONST.ONBOARDING_CHOICES.TRACK_WORKSPACE || - (introSelected?.choice === CONST.ONBOARDING_CHOICES.SUBMIT && introSelected.inviteType === CONST.ONBOARDING_INVITE_TYPES.WORKSPACE) - ) { + (introSelected?.choice === CONST.ONBOARDING_CHOICES.SUBMIT && introSelected.inviteType === CONST.ONBOARDING_INVITE_TYPES.WORKSPACE); + + if (shouldNavigateToDemo) { Navigation.navigate(ROUTES.TEST_DRIVE_DEMO_ROOT); - } else { - Navigation.navigate(ROUTES.TEST_DRIVE_MODAL_ROOT.route); + return; } + Navigation.navigate(ROUTES.TEST_DRIVE_DEMO_ROOT); }); } diff --git a/src/libs/actions/Welcome/OnboardingFlow.ts b/src/libs/actions/Welcome/OnboardingFlow.ts index e981503a53ce..cfc514c62ec8 100644 --- a/src/libs/actions/Welcome/OnboardingFlow.ts +++ b/src/libs/actions/Welcome/OnboardingFlow.ts @@ -115,10 +115,6 @@ function getOnboardingInitialPath(getOnboardingInitialPathParams: GetOnboardingI const isIndividual = currentOnboardingValues?.signupQualifier === CONST.ONBOARDING_SIGNUP_QUALIFIERS.INDIVIDUAL; const isCurrentOnboardingPurposeManageTeam = currentOnboardingPurposeSelected === CONST.ONBOARDING_CHOICES.MANAGE_TEAM; - if (onboardingInitialPath.includes(ROUTES.TEST_DRIVE_MODAL_ROOT.route)) { - return `/${ROUTES.TEST_DRIVE_MODAL_ROOT.route}`; - } - if (isVsb) { Onyx.set(ONYXKEYS.ONBOARDING_PURPOSE_SELECTED, CONST.ONBOARDING_CHOICES.MANAGE_TEAM); Onyx.set(ONYXKEYS.ONBOARDING_COMPANY_SIZE, CONST.ONBOARDING_COMPANY_SIZE.MICRO); diff --git a/src/pages/OnboardingInterestedFeatures/BaseOnboardingInterestedFeatures.tsx b/src/pages/OnboardingInterestedFeatures/BaseOnboardingInterestedFeatures.tsx index e96579a73f50..9429cd1531cc 100644 --- a/src/pages/OnboardingInterestedFeatures/BaseOnboardingInterestedFeatures.tsx +++ b/src/pages/OnboardingInterestedFeatures/BaseOnboardingInterestedFeatures.tsx @@ -222,7 +222,6 @@ function BaseOnboardingInterestedFeatures({shouldUseNativeStyles}: BaseOnboardin firstName: currentUserPersonalDetails?.firstName, lastName: currentUserPersonalDetails?.lastName, selectedInterestedFeatures: featuresMap.filter((feature) => feature.enabled).map((feature) => feature.id), - shouldSkipTestDriveModal: !!policyID && !adminsChatReportID, shouldWaitForRHPVariantInitialization: isSidePanelReportSupported, introSelected, isSelfTourViewed, diff --git a/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx b/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx index 895a98ebc273..baa2010f44b6 100644 --- a/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx +++ b/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx @@ -83,7 +83,6 @@ function BaseOnboardingPersonalDetails({currentUserPersonalDetails, shouldUseNat lastName, adminsChatReportID: onboardingAdminsChatReportID, onboardingPolicyID, - shouldSkipTestDriveModal: !!onboardingPolicyID && !mergedAccountConciergeReportID, introSelected, isSelfTourViewed, betas, diff --git a/src/pages/OnboardingWorkspaceInvite/BaseOnboardingWorkspaceInvite.tsx b/src/pages/OnboardingWorkspaceInvite/BaseOnboardingWorkspaceInvite.tsx index f488545ae846..4d868424c6fc 100644 --- a/src/pages/OnboardingWorkspaceInvite/BaseOnboardingWorkspaceInvite.tsx +++ b/src/pages/OnboardingWorkspaceInvite/BaseOnboardingWorkspaceInvite.tsx @@ -127,7 +127,6 @@ function BaseOnboardingWorkspaceInvite({shouldUseNativeStyles}: BaseOnboardingWo lastName: currentUserPersonalDetails.lastName, adminsChatReportID: onboardingAdminsChatReportID, onboardingPolicyID, - shouldSkipTestDriveModal: !!onboardingPolicyID && !onboardingAdminsChatReportID, isInvitedAccountant, onboardingPurposeSelected, introSelected, diff --git a/src/pages/OnboardingWorkspaceOptional/BaseOnboardingWorkspaceOptional.tsx b/src/pages/OnboardingWorkspaceOptional/BaseOnboardingWorkspaceOptional.tsx index 477012b06dcf..844c29059a53 100644 --- a/src/pages/OnboardingWorkspaceOptional/BaseOnboardingWorkspaceOptional.tsx +++ b/src/pages/OnboardingWorkspaceOptional/BaseOnboardingWorkspaceOptional.tsx @@ -109,7 +109,6 @@ function BaseOnboardingWorkspaceOptional({shouldUseNativeStyles}: BaseOnboarding lastName: currentUserPersonalDetails.lastName, adminsChatReportID: resolvedAdminsChatReportID, onboardingPolicyID: resolvedPolicyID, - shouldSkipTestDriveModal: (!!resolvedPolicyID && !resolvedAdminsChatReportID) || engagementChoice === CONST.ONBOARDING_CHOICES.PERSONAL_SPEND, introSelected, betas, isSelfTourViewed, diff --git a/src/pages/OnboardingWorkspaces/BaseOnboardingWorkspaces.tsx b/src/pages/OnboardingWorkspaces/BaseOnboardingWorkspaces.tsx index 459598df8d0f..4a816ba01091 100644 --- a/src/pages/OnboardingWorkspaces/BaseOnboardingWorkspaces.tsx +++ b/src/pages/OnboardingWorkspaces/BaseOnboardingWorkspaces.tsx @@ -76,7 +76,6 @@ function BaseOnboardingWorkspaces({route, shouldUseNativeStyles}: BaseOnboarding onboardingMessage: onboardingMessages[CONST.ONBOARDING_CHOICES.LOOKING_AROUND], firstName: onboardingPersonalDetails?.firstName ?? '', lastName: onboardingPersonalDetails?.lastName ?? '', - shouldSkipTestDriveModal: !!(policy.automaticJoiningEnabled ? policy.policyID : undefined), companySize: onboardingCompanySize, introSelected, isSelfTourViewed, diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index 061d1acb2b5c..b578ad549813 100644 --- a/tests/actions/IOUTest.ts +++ b/tests/actions/IOUTest.ts @@ -15654,7 +15654,6 @@ describe('actions/IOU', () => { engagementChoice: CONST.ONBOARDING_CHOICES.MANAGE_TEAM, paymentSelected: CONST.PAYMENT_SELECTED.BBA, wasInvited: true, - shouldSkipTestDriveModal: true, companySize: CONST.ONBOARDING_COMPANY_SIZE.MICRO, introSelected, isSelfTourViewed: false, @@ -15675,7 +15674,6 @@ describe('actions/IOU', () => { engagementChoice: CONST.ONBOARDING_CHOICES.CHAT_SPLIT, paymentSelected: CONST.PAYMENT_SELECTED.PBA, wasInvited: true, - shouldSkipTestDriveModal: true, companySize: CONST.ONBOARDING_COMPANY_SIZE.SMALL, introSelected, isSelfTourViewed: false, diff --git a/tests/actions/TourTest.ts b/tests/actions/TourTest.ts index b08b9f179e7c..63355e80b3a2 100644 --- a/tests/actions/TourTest.ts +++ b/tests/actions/TourTest.ts @@ -97,11 +97,11 @@ describe('actions/Tour', () => { expect(Navigation.navigate).toHaveBeenCalledWith(ROUTES.TEST_DRIVE_DEMO_ROOT); }); - it.each(onboardingChoices.filter((choice) => !onboardingDemoChoices.has(choice)))('should show the Test Drive modal if user has "%s" onboarding choice', async (choice) => { + it.each(onboardingChoices.filter((choice) => !onboardingDemoChoices.has(choice)))('should show the Test Drive demo if user has "%s" onboarding choice', async (choice) => { startTestDrive({choice}, false, false); await waitForBatchedUpdates(); - expect(Navigation.navigate).toHaveBeenCalledWith(ROUTES.TEST_DRIVE_MODAL_ROOT.route); + expect(Navigation.navigate).toHaveBeenCalledWith(ROUTES.TEST_DRIVE_DEMO_ROOT); }); it('should show the Test Drive demo if user is an invited employee', async () => { diff --git a/tests/unit/Navigation/guards/TestDriveModalGuard.test.ts b/tests/unit/Navigation/guards/TestDriveModalGuard.test.ts deleted file mode 100644 index 9883848f0cfb..000000000000 --- a/tests/unit/Navigation/guards/TestDriveModalGuard.test.ts +++ /dev/null @@ -1,220 +0,0 @@ -import type {NavigationAction, NavigationState} from '@react-navigation/native'; -import Onyx from 'react-native-onyx'; -import TestDriveModalGuard, {resetSessionFlag} from '@libs/Navigation/guards/TestDriveModalGuard'; -import type {GuardContext} from '@libs/Navigation/guards/types'; -import CONST from '@src/CONST'; -import NAVIGATORS from '@src/NAVIGATORS'; -import ONYXKEYS from '@src/ONYXKEYS'; -import ROUTES from '@src/ROUTES'; -import SCREENS from '@src/SCREENS'; -import waitForBatchedUpdates from '../../../utils/waitForBatchedUpdates'; - -describe('TestDriveModalGuard', () => { - const mockState: NavigationState = { - key: 'root', - index: 0, - routeNames: [SCREENS.HOME], - routes: [{key: 'home', name: SCREENS.HOME}], - stale: false, - type: 'root', - }; - - const mockAction: NavigationAction = { - type: 'NAVIGATE', - payload: {name: SCREENS.HOME}, - }; - - const defaultContext: GuardContext = { - isAuthenticated: true, - isLoading: false, - currentUrl: '', - }; - - beforeAll(() => { - Onyx.init({keys: ONYXKEYS}); - }); - - beforeEach(async () => { - await Onyx.clear(); - resetSessionFlag(); - await waitForBatchedUpdates(); - }); - - it('should allow when app is loading', () => { - const result = TestDriveModalGuard.evaluate(mockState, mockAction, {...defaultContext, isLoading: true}); - expect(result.type).toBe('ALLOW'); - }); - - it('should redirect when onboarding complete and modal not dismissed', async () => { - await Onyx.merge(ONYXKEYS.NVP_ONBOARDING, { - hasCompletedGuidedSetupFlow: true, - testDriveModalDismissed: false, - }); - await waitForBatchedUpdates(); - - const result = TestDriveModalGuard.evaluate(mockState, mockAction, defaultContext); - expect(result.type).toBe('REDIRECT'); - if (result.type === 'REDIRECT') { - expect(result.route).toBe(ROUTES.TEST_DRIVE_MODAL_ROOT.route); - } - }); - - it('should allow when modal dismissed', async () => { - await Onyx.merge(ONYXKEYS.NVP_ONBOARDING, { - hasCompletedGuidedSetupFlow: true, - testDriveModalDismissed: true, - }); - await waitForBatchedUpdates(); - - const result = TestDriveModalGuard.evaluate(mockState, mockAction, defaultContext); - expect(result.type).toBe('ALLOW'); - }); - - it('should allow when onboarding not complete', async () => { - await Onyx.merge(ONYXKEYS.NVP_ONBOARDING, { - hasCompletedGuidedSetupFlow: false, - testDriveModalDismissed: false, - }); - await waitForBatchedUpdates(); - - const result = TestDriveModalGuard.evaluate(mockState, mockAction, defaultContext); - expect(result.type).toBe('ALLOW'); - }); - - it('should not redirect multiple times (session flag)', async () => { - await Onyx.merge(ONYXKEYS.NVP_ONBOARDING, { - hasCompletedGuidedSetupFlow: true, - testDriveModalDismissed: false, - }); - await waitForBatchedUpdates(); - - const firstResult = TestDriveModalGuard.evaluate(mockState, mockAction, defaultContext); - expect(firstResult.type).toBe('REDIRECT'); - - const secondResult = TestDriveModalGuard.evaluate(mockState, mockAction, defaultContext); - expect(secondResult.type).toBe('ALLOW'); - }); - - it('should skip modal when user has accessible workspace', async () => { - await Onyx.merge(ONYXKEYS.NVP_ONBOARDING, { - hasCompletedGuidedSetupFlow: true, - testDriveModalDismissed: false, - }); - await Onyx.set(ONYXKEYS.ONBOARDING_POLICY_ID, 'policy123'); - await Onyx.set(ONYXKEYS.HAS_NON_PERSONAL_POLICY, true); - await waitForBatchedUpdates(); - - const result = TestDriveModalGuard.evaluate(mockState, mockAction, defaultContext); - expect(result.type).toBe('ALLOW'); - }); - - it('should redirect to home when accessing dismissed modal via URL', async () => { - await Onyx.merge(ONYXKEYS.NVP_ONBOARDING, { - hasCompletedGuidedSetupFlow: true, - testDriveModalDismissed: true, - }); - await waitForBatchedUpdates(); - - const testDriveModalState: NavigationState = { - key: 'root', - index: 0, - routeNames: [SCREENS.TEST_DRIVE_MODAL.ROOT], - routes: [{key: 'testDrive', name: SCREENS.TEST_DRIVE_MODAL.ROOT}], - stale: false, - type: 'root', - }; - - const result = TestDriveModalGuard.evaluate(testDriveModalState, mockAction, defaultContext); - expect(result.type).toBe('REDIRECT'); - if (result.type === 'REDIRECT') { - expect(result.route).toBe(ROUTES.HOME); - } - }); - - describe('shouldBlockWhileModalActive', () => { - const stateWithModalOnTop: NavigationState = { - key: 'root', - index: 1, - routeNames: [SCREENS.HOME, NAVIGATORS.TEST_DRIVE_MODAL_NAVIGATOR], - routes: [ - {key: 'home', name: SCREENS.HOME}, - {key: 'testDriveModal', name: NAVIGATORS.TEST_DRIVE_MODAL_NAVIGATOR}, - ], - stale: false, - type: 'stack', - }; - - const tabSwitchAction: NavigationAction = { - type: CONST.NAVIGATION.ACTION_TYPE.PUSH, - payload: {name: NAVIGATORS.SETTINGS_SPLIT_NAVIGATOR}, - }; - - const dismissModalAction: NavigationAction = { - type: CONST.NAVIGATION.ACTION_TYPE.DISMISS_MODAL, - }; - - const goBackAction: NavigationAction = { - type: CONST.NAVIGATION.ACTION_TYPE.GO_BACK, - }; - - it('should block tab switches when the test drive modal is on top', async () => { - await Onyx.merge(ONYXKEYS.NVP_ONBOARDING, { - hasCompletedGuidedSetupFlow: true, - testDriveModalDismissed: false, - }); - await waitForBatchedUpdates(); - - // Trigger the redirect first so hasRedirectedToTestDriveModal is true - TestDriveModalGuard.evaluate(mockState, mockAction, defaultContext); - - const result = TestDriveModalGuard.evaluate(stateWithModalOnTop, tabSwitchAction, defaultContext); - expect(result.type).toBe('BLOCK'); - }); - - it('should allow DISMISS_MODAL when the test drive modal is on top', async () => { - await Onyx.merge(ONYXKEYS.NVP_ONBOARDING, { - hasCompletedGuidedSetupFlow: true, - testDriveModalDismissed: false, - }); - await waitForBatchedUpdates(); - - // Trigger the redirect first - TestDriveModalGuard.evaluate(mockState, mockAction, defaultContext); - - const result = TestDriveModalGuard.evaluate(stateWithModalOnTop, dismissModalAction, defaultContext); - expect(result.type).not.toBe('BLOCK'); - }); - - it('should allow GO_BACK when the test drive modal is on top', async () => { - await Onyx.merge(ONYXKEYS.NVP_ONBOARDING, { - hasCompletedGuidedSetupFlow: true, - testDriveModalDismissed: false, - }); - await waitForBatchedUpdates(); - - // Trigger the redirect first - TestDriveModalGuard.evaluate(mockState, mockAction, defaultContext); - - const result = TestDriveModalGuard.evaluate(stateWithModalOnTop, goBackAction, defaultContext); - expect(result.type).not.toBe('BLOCK'); - }); - - it('should not block when the modal has been dismissed', async () => { - await Onyx.merge(ONYXKEYS.NVP_ONBOARDING, { - hasCompletedGuidedSetupFlow: true, - testDriveModalDismissed: false, - }); - await waitForBatchedUpdates(); - - // Trigger the redirect first - TestDriveModalGuard.evaluate(mockState, mockAction, defaultContext); - - // Now mark modal as dismissed - await Onyx.merge(ONYXKEYS.NVP_ONBOARDING, {testDriveModalDismissed: true}); - await waitForBatchedUpdates(); - - const result = TestDriveModalGuard.evaluate(stateWithModalOnTop, tabSwitchAction, defaultContext); - expect(result.type).not.toBe('BLOCK'); - }); - }); -});