From 78129a305788df4644a9bab3b46133d1553c65d0 Mon Sep 17 00:00:00 2001 From: Jasper Huang Date: Tue, 14 Jul 2026 12:58:47 -0700 Subject: [PATCH] Revert "Fix magic code navigates back on payment (SettlementButton)" --- src/components/SettlementButton/index.tsx | 202 ++++++++++-------- .../useShouldRenderOverlay.ts | 8 +- src/hooks/useVerifyAccountAndResume.ts | 65 ------ tests/ui/components/SettlementButtonTest.tsx | 10 - .../hooks/useVerifyAccountAndResume.test.ts | 200 ----------------- 5 files changed, 110 insertions(+), 375 deletions(-) delete mode 100644 src/hooks/useVerifyAccountAndResume.ts delete mode 100644 tests/unit/hooks/useVerifyAccountAndResume.test.ts diff --git a/src/components/SettlementButton/index.tsx b/src/components/SettlementButton/index.tsx index 372d71c8b0d0..b9b2c186b82d 100644 --- a/src/components/SettlementButton/index.tsx +++ b/src/components/SettlementButton/index.tsx @@ -20,12 +20,12 @@ import useOnyx from '@hooks/useOnyx'; import usePermissions from '@hooks/usePermissions'; import usePolicy from '@hooks/usePolicy'; import useThemeStyles from '@hooks/useThemeStyles'; -import useVerifyAccountAndResume from '@hooks/useVerifyAccountAndResume'; import {createWorkspace, generateDefaultWorkspaceName, isCurrencySupportedForDirectReimbursement, isCurrencySupportedForGlobalReimbursement} from '@libs/actions/Policy/Policy'; import {navigateToBankAccountRoute} from '@libs/actions/ReimbursementAccount'; import {getLastPolicyBankAccountID, getLastPolicyPaymentMethod} from '@libs/actions/Search'; import {isBankAccountPartiallySetup} from '@libs/BankAccountUtils'; +import createDynamicRoute from '@libs/Navigation/helpers/dynamicRoutesUtils/createDynamicRoute'; import Navigation from '@libs/Navigation/Navigation'; import {isTrackOnboardingChoice} from '@libs/OnboardingUtils'; import {formatPaymentMethods, getActivePaymentType, getBusinessBankAccountOptions, matchesCurrency} from '@libs/PaymentUtils'; @@ -50,7 +50,7 @@ import {navigateToConciergeChat} from '@userActions/Report'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import ROUTES from '@src/ROUTES'; +import ROUTES, {DYNAMIC_ROUTES} from '@src/ROUTES'; import type {AccountData, BankAccount, LastPaymentMethodType, Policy} from '@src/types/onyx'; import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; @@ -58,11 +58,11 @@ import {isEmptyObject} from '@src/types/utils/EmptyObject'; import type {GestureResponderEvent} from 'react-native'; import type {TupleToUnion} from 'type-fest'; -import {delegateEmailSelector} from '@selectors/Account'; +import {delegateEmailSelector, isUserValidatedSelector} from '@selectors/Account'; import {hasSeenTourSelector} from '@selectors/Onboarding'; import {personalDetailsLoginSelector} from '@selectors/PersonalDetails'; import truncate from 'lodash/truncate'; -import React, {useContext} from 'react'; +import React, {useCallback, useContext} from 'react'; import {View} from 'react-native'; import type SettlementButtonProps from './types'; @@ -127,6 +127,7 @@ function SettlementButton({ const [conciergeReportID = ''] = useOnyx(ONYXKEYS.CONCIERGE_REPORT_ID); const [iouReportNextStep] = useOnyx(`${ONYXKEYS.COLLECTION.NEXT_STEP}${iouReport?.reportID}`); const [ownerLogin] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, {selector: personalDetailsLoginSelector(iouReport?.ownerAccountID)}); + const [isUserValidated] = useOnyx(ONYXKEYS.ACCOUNT, {selector: isUserValidatedSelector}); const [amountOwed] = useOnyx(ONYXKEYS.NVP_PRIVATE_AMOUNT_OWED); const policyEmployeeAccountIDs = getPolicyEmployeeAccountIDs(policy, accountID); const reportBelongsToWorkspace = policyID ? doesReportBelongToWorkspace(chatReport, policyEmployeeAccountIDs, policyID, conciergeReportID) : false; @@ -199,73 +200,76 @@ function SettlementButton({ return formattedPaymentMethods.filter((ba) => (ba.accountData as AccountData)?.type === CONST.BANK_ACCOUNT.TYPE.PERSONAL); } - // The guards checked after the account-validation gate. Also re-checked when a payment - // interrupted by account validation resumes, since the validation gate skipped them. - const checkForPostValidationBlockers = () => { - if (isBankAccountLocked) { - showConfirmModal({ - title: translate('bankAccount.lockedBankAccount'), - prompt: ( - - - - ), - confirmText: translate('bankAccount.unlockBankAccount'), - cancelText: translate('common.cancel'), - }).then(({action}) => { - if (action !== ModalActions.CONFIRM) { - return; - } - if (policy?.achAccount?.bankAccountID === undefined) { - return; - } - pressLockedBankAccount(policy?.achAccount?.bankAccountID, translate, conciergeReportID, delegateAccountID); - navigateToConciergeChat(conciergeReportID, introSelected, currentUserAccountID, isSelfTourViewed, betas); - }); - return true; - } - - if (policy && shouldRestrictUserBillableActions(policy, ownerBillingGracePeriodEnd, userBillingGracePeriodEnds, amountOwed, currentUserAccountID)) { - Navigation.navigate(ROUTES.RESTRICTED_ACTION.getRoute(policy.id)); - return true; - } - - return false; - }; - - const {isUserValidated, verifyAccountAndResume} = useVerifyAccountAndResume((retry?: () => void) => { - // The validation gate returned before these guards could run, so apply them to the resumed. - if (checkForPostValidationBlockers()) { - return; - } - retry?.(); - }); + const checkForNecessaryAction = useCallback( + (paymentMethodType?: PaymentMethodType) => { + if (isDelegateAccessRestricted) { + showDelegateNoAccessModal(); + return true; + } - const checkForNecessaryAction = (paymentMethodType?: PaymentMethodType, retry?: () => void) => { - if (isDelegateAccessRestricted) { - showDelegateNoAccessModal(); - return true; - } + if (isAccountLocked) { + showLockedAccountModal(); + return true; + } - if (isAccountLocked) { - showLockedAccountModal(); - return true; - } + if (!isUserValidated && paymentMethodType !== CONST.IOU.PAYMENT_TYPE.ELSEWHERE) { + Navigation.navigate(createDynamicRoute(DYNAMIC_ROUTES.VERIFY_ACCOUNT.path)); + return true; + } - if (!isUserValidated && paymentMethodType !== CONST.IOU.PAYMENT_TYPE.ELSEWHERE) { - verifyAccountAndResume(retry); - return true; - } + if (isBankAccountLocked) { + showConfirmModal({ + title: translate('bankAccount.lockedBankAccount'), + prompt: ( + + + + ), + confirmText: translate('bankAccount.unlockBankAccount'), + cancelText: translate('common.cancel'), + }).then(({action}) => { + if (action !== ModalActions.CONFIRM) { + return; + } + if (policy?.achAccount?.bankAccountID === undefined) { + return; + } + pressLockedBankAccount(policy?.achAccount?.bankAccountID, translate, conciergeReportID, delegateAccountID); + navigateToConciergeChat(conciergeReportID, introSelected, currentUserAccountID, isSelfTourViewed, betas); + }); + return true; + } - return checkForPostValidationBlockers(); - }; + if (policy && shouldRestrictUserBillableActions(policy, ownerBillingGracePeriodEnd, userBillingGracePeriodEnds, amountOwed, currentUserAccountID)) { + Navigation.navigate(ROUTES.RESTRICTED_ACTION.getRoute(policy.id)); + return true; + } - const runPaymentAction = (paymentMethodType: PaymentMethodType | undefined, action: () => void) => { - if (checkForNecessaryAction(paymentMethodType, action)) { - return; - } - action(); - }; + return false; + }, + [ + isDelegateAccessRestricted, + isAccountLocked, + isUserValidated, + isBankAccountLocked, + policy, + userBillingGracePeriodEnds, + ownerBillingGracePeriodEnd, + showDelegateNoAccessModal, + showLockedAccountModal, + showConfirmModal, + translate, + styles.renderHTML, + styles.flexRow, + conciergeReportID, + introSelected, + currentUserAccountID, + isSelfTourViewed, + betas, + amountOwed, + delegateAccountID, + ], + ); const shortFormPayElsewhereButton = { text: translate('iou.pay'), @@ -336,15 +340,17 @@ function SettlementButton({ value: CONST.PAYMENT_METHODS.BUSINESS_BANK_ACCOUNT, description: account.description, shouldIgnoreCompactStyle: true, - onSelected: () => - runPaymentAction(CONST.IOU.PAYMENT_TYPE.VBBA, () => - onPress({ - paymentType: CONST.IOU.PAYMENT_TYPE.VBBA, - payAsBusiness: true, - methodID: account.methodID, - paymentMethod: CONST.PAYMENT_METHODS.BUSINESS_BANK_ACCOUNT, - }), - ), + onSelected: () => { + if (checkForNecessaryAction(CONST.IOU.PAYMENT_TYPE.VBBA)) { + return; + } + onPress({ + paymentType: CONST.IOU.PAYMENT_TYPE.VBBA, + payAsBusiness: true, + methodID: account.methodID, + paymentMethod: CONST.PAYMENT_METHODS.BUSINESS_BANK_ACCOUNT, + }); + }, }); } } @@ -397,15 +403,17 @@ function SettlementButton({ description: formattedPaymentMethod?.description ?? '', icon: formattedPaymentMethod?.icon, shouldUpdateSelectedIndex: true, - onSelected: () => - runPaymentAction(CONST.IOU.PAYMENT_TYPE.EXPENSIFY, () => - onPress({ - paymentType: CONST.IOU.PAYMENT_TYPE.EXPENSIFY, - payAsBusiness, - methodID: formattedPaymentMethod.methodID, - paymentMethod: formattedPaymentMethod.accountType, - }), - ), + onSelected: () => { + if (checkForNecessaryAction(CONST.IOU.PAYMENT_TYPE.EXPENSIFY)) { + return; + } + onPress({ + paymentType: CONST.IOU.PAYMENT_TYPE.EXPENSIFY, + payAsBusiness, + methodID: formattedPaymentMethod.methodID, + paymentMethod: formattedPaymentMethod.accountType, + }); + }, iconStyles: formattedPaymentMethod?.iconStyles, iconHeight: formattedPaymentMethod?.iconSize, iconWidth: formattedPaymentMethod?.iconSize, @@ -457,7 +465,12 @@ function SettlementButton({ icon: icons.Cash, value: CONST.IOU.PAYMENT_TYPE.ELSEWHERE, shouldUpdateSelectedIndex: true, - onSelected: () => runPaymentAction(CONST.IOU.PAYMENT_TYPE.ELSEWHERE, () => onPress({paymentType: CONST.IOU.PAYMENT_TYPE.ELSEWHERE, payAsBusiness})), + onSelected: () => { + if (checkForNecessaryAction(CONST.IOU.PAYMENT_TYPE.ELSEWHERE)) { + return; + } + onPress({paymentType: CONST.IOU.PAYMENT_TYPE.ELSEWHERE, payAsBusiness}); + }, }, ]; }; @@ -564,8 +577,17 @@ function SettlementButton({ }); }; - const executePaymentSelection = (event: KYCFlowEvent, selectedOption: string, triggerKYCFlow: TriggerKYCFlow, activePaymentType: ReturnType) => { - const {paymentType, policyFromPaymentMethod, policyFromContext, shouldSelectPaymentMethod} = activePaymentType; + const handlePaymentSelection = (event: GestureResponderEvent | KeyboardEvent | undefined, selectedOption: string, triggerKYCFlow: (params: ContinueActionParams) => void) => { + const {paymentType, policyFromPaymentMethod, policyFromContext, shouldSelectPaymentMethod} = getActivePaymentType( + selectedOption, + activeAdminPolicies, + businessBankAccountOptions, + policyIDKey, + ); + + if (checkForNecessaryAction(paymentType)) { + return; + } const isPayingWithMethod = paymentType !== CONST.IOU.PAYMENT_TYPE.ELSEWHERE; if ((!!policyFromPaymentMethod || shouldSelectPaymentMethod) && (isPayingWithMethod || !!policyFromPaymentMethod)) { @@ -576,12 +598,6 @@ function SettlementButton({ selectPaymentType(event, selectedOption as PaymentMethodType); }; - const handlePaymentSelection = (event: KYCFlowEvent, selectedOption: string, triggerKYCFlow: TriggerKYCFlow) => { - const activePaymentType = getActivePaymentType(selectedOption, activeAdminPolicies, businessBankAccountOptions, policyIDKey); - - runPaymentAction(activePaymentType.paymentType, () => executePaymentSelection(event, selectedOption, triggerKYCFlow, activePaymentType)); - }; - let customText: string; if (shouldUseShortForm) { customText = translate('iou.pay'); diff --git a/src/components/WideRHPContextProvider/useShouldRenderOverlay.ts b/src/components/WideRHPContextProvider/useShouldRenderOverlay.ts index 0e89add7693e..a4126f182f74 100644 --- a/src/components/WideRHPContextProvider/useShouldRenderOverlay.ts +++ b/src/components/WideRHPContextProvider/useShouldRenderOverlay.ts @@ -22,13 +22,7 @@ function useShouldRenderOverlay(condition: boolean, overlayProgress: Animated.Va toValue: 0, duration: OVERLAY_TIMING_DURATION, useNativeDriver: false, - }).start(({finished}) => { - // When the hide animation is interrupted by a new show animation (the condition flipped back to true - // before the animation completed), this callback still fires with finished=false. Setting the state - // to false in that case would permanently hide the overlay that should be visible. - if (!finished) { - return; - } + }).start(() => { setShouldRenderOverlay(false); }); } diff --git a/src/hooks/useVerifyAccountAndResume.ts b/src/hooks/useVerifyAccountAndResume.ts deleted file mode 100644 index 4c662e6c25d2..000000000000 --- a/src/hooks/useVerifyAccountAndResume.ts +++ /dev/null @@ -1,65 +0,0 @@ -import createDynamicRoute from '@libs/Navigation/helpers/dynamicRoutesUtils/createDynamicRoute'; -import splitPathAndQuery from '@libs/Navigation/helpers/dynamicRoutesUtils/splitPathAndQuery'; -import Navigation from '@libs/Navigation/Navigation'; -import navigationRef from '@libs/Navigation/navigationRef'; - -import ONYXKEYS from '@src/ONYXKEYS'; -import {DYNAMIC_ROUTES} from '@src/ROUTES'; - -import {isUserValidatedSelector} from '@selectors/Account'; -import {useEffect, useEffectEvent, useState} from 'react'; - -import useOnyx from './useOnyx'; - -/** - * Returns a trigger that sends an unvalidated user to the verify-account (magic code) screen and, - * once they validate there, calls `onResume` with the payload the trigger stored. - */ -function useVerifyAccountAndResume(onResume: (payload: TPayload) => void) { - const [isUserValidated] = useOnyx(ONYXKEYS.ACCOUNT, {selector: isUserValidatedSelector}); - const [pendingAction, setPendingAction] = useState<{payload: TPayload; verifyAccountPath: string} | null>(null); - - // Effect event, so the resume runs the latest `onResume` (a press-time closure would see pre-validation state). - const resumeAction = useEffectEvent(onResume); - - // Effect event, so the listener reads the freshest validation state and doesn't mistake the success-driven close of the verify-account page for an abandon. - const handleNavigationStateChange = useEffectEvent(() => { - if (!pendingAction || isUserValidated || Navigation.getActiveRouteWithoutParams() === pendingAction.verifyAccountPath) { - return; - } - setPendingAction(null); - }); - - useEffect(() => { - if (!pendingAction) { - return; - } - - // Leaving the verify-account screen without validating abandons the action, which is what makes resuming below safe without a route check. - if (!isUserValidated) { - return navigationRef.addListener('state', handleNavigationStateChange); - } - - // Validation succeeded — resume even if the verify-account page already dismissed itself. - const isVerifyAccountScreenStillOpen = Navigation.getActiveRouteWithoutParams() === pendingAction.verifyAccountPath; - const resume = () => { - setPendingAction(null); - resumeAction(pendingAction.payload); - }; - // While the page is still open its closing transition hasn't been dispatched yet, so wait for the upcoming one; otherwise the close is already in flight (or done), so only wait out active transitions. - const handle = isVerifyAccountScreenStillOpen ? Navigation.runAfterUpcomingTransition(resume) : Navigation.runAfterTransition(resume); - - return () => handle.cancel(); - }, [isUserValidated, pendingAction]); - - const verifyAccountAndResume = (payload: TPayload) => { - const verifyAccountRoute = createDynamicRoute(DYNAMIC_ROUTES.VERIFY_ACCOUNT.path); - const [verifyAccountPath] = splitPathAndQuery(verifyAccountRoute); - setPendingAction(verifyAccountPath ? {payload, verifyAccountPath} : null); - Navigation.navigate(verifyAccountRoute); - }; - - return {isUserValidated, verifyAccountAndResume}; -} - -export default useVerifyAccountAndResume; diff --git a/tests/ui/components/SettlementButtonTest.tsx b/tests/ui/components/SettlementButtonTest.tsx index 36ed0c2fd83f..1aa37a0189f0 100644 --- a/tests/ui/components/SettlementButtonTest.tsx +++ b/tests/ui/components/SettlementButtonTest.tsx @@ -81,16 +81,6 @@ jest.mock('@libs/actions/Policy/Policy', () => ({ createWorkspace: jest.fn(() => ({policyID: 'mock-created-policy-id'})), })); -const mockVerifyAccountAndResume = jest.fn(); - -jest.mock('@hooks/useVerifyAccountAndResume', () => ({ - __esModule: true, - default: jest.fn(() => ({ - isUserValidated: true, - verifyAccountAndResume: mockVerifyAccountAndResume, - })), -})); - const ACCOUNT_ID = 1; const ACCOUNT_LOGIN = 'test@user.com'; const REPORT_ID = '100'; diff --git a/tests/unit/hooks/useVerifyAccountAndResume.test.ts b/tests/unit/hooks/useVerifyAccountAndResume.test.ts deleted file mode 100644 index 5a11d9e00e0e..000000000000 --- a/tests/unit/hooks/useVerifyAccountAndResume.test.ts +++ /dev/null @@ -1,200 +0,0 @@ -import {act, renderHook, waitFor} from '@testing-library/react-native'; - -import useVerifyAccountAndResume from '@hooks/useVerifyAccountAndResume'; - -import Navigation from '@libs/Navigation/Navigation'; -import navigationRef from '@libs/Navigation/navigationRef'; - -import ONYXKEYS from '@src/ONYXKEYS'; - -import Onyx from 'react-native-onyx'; - -import waitForBatchedUpdatesWithAct from '../../utils/waitForBatchedUpdatesWithAct'; - -type ResumePayload = { - paymentID: string; -}; - -const mockVerifyAccountRoute = 'r/123/verify-account?source=pay'; -const mockVerifyAccountPath = 'r/123/verify-account'; -const mockCancelTransition = jest.fn(); -const mockNavigationStateListeners = new Set<() => void>(); -let mockPendingTransitionCallbacks: Array<() => void> = []; - -jest.mock('@libs/Navigation/helpers/dynamicRoutesUtils/createDynamicRoute', () => ({ - __esModule: true, - default: jest.fn(() => 'r/123/verify-account?source=pay'), -})); - -jest.mock('@libs/Navigation/Navigation', () => { - const mockNavigationModule = { - navigate: jest.fn(), - runAfterTransition: jest.fn(), - runAfterUpcomingTransition: jest.fn(), - getActiveRouteWithoutParams: jest.fn(), - }; - return { - __esModule: true, - default: mockNavigationModule, - ...mockNavigationModule, - }; -}); - -jest.mock('@libs/Navigation/navigationRef', () => ({ - __esModule: true, - default: { - addListener: jest.fn(), - }, -})); - -const mockedNavigation = jest.mocked(Navigation); -const mockedNavigationRef = jest.mocked(navigationRef); - -function emitNavigationStateChange() { - for (const listener of mockNavigationStateListeners) { - listener(); - } -} - -async function setIsUserValidated(validated: boolean) { - await act(async () => { - await Onyx.merge(ONYXKEYS.ACCOUNT, {validated}); - }); - await waitForBatchedUpdatesWithAct(); -} - -async function clearOnyx() { - await act(async () => { - await Onyx.clear(); - }); - await waitForBatchedUpdatesWithAct(); -} - -describe('useVerifyAccountAndResume', () => { - beforeAll(() => { - Onyx.init({keys: ONYXKEYS}); - }); - - beforeEach(async () => { - await clearOnyx(); - - jest.clearAllMocks(); - mockNavigationStateListeners.clear(); - mockPendingTransitionCallbacks = []; - mockedNavigation.getActiveRouteWithoutParams.mockReturnValue(mockVerifyAccountPath); - mockedNavigation.runAfterTransition.mockImplementation((callback: () => void) => { - mockPendingTransitionCallbacks.push(callback); - return {cancel: mockCancelTransition}; - }); - mockedNavigation.runAfterUpcomingTransition.mockImplementation((callback: () => void) => { - mockPendingTransitionCallbacks.push(callback); - return {cancel: mockCancelTransition}; - }); - mockedNavigationRef.addListener.mockImplementation((_eventName, listener) => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion - const stateListener = listener as () => void; - mockNavigationStateListeners.add(stateListener); - return () => { - mockNavigationStateListeners.delete(stateListener); - }; - }); - - await setIsUserValidated(false); - }); - - afterEach(async () => { - await clearOnyx(); - }); - - it('navigates to verify account and exposes validation state', async () => { - const onResume = jest.fn(); - const {result} = renderHook(() => useVerifyAccountAndResume(onResume)); - - await waitFor(() => { - expect(result.current.isUserValidated).toBe(false); - }); - - act(() => { - result.current.verifyAccountAndResume({paymentID: 'payment-1'}); - }); - - expect(mockedNavigation.navigate).toHaveBeenCalledWith(mockVerifyAccountRoute); - expect(onResume).not.toHaveBeenCalled(); - }); - - it('resumes the stored payload after validation on the same verify account route', async () => { - const initialOnResume = jest.fn(); - const latestOnResume = jest.fn(); - const {result, rerender} = renderHook(({onResume}: {onResume: (payload: ResumePayload) => void}) => useVerifyAccountAndResume(onResume), { - initialProps: {onResume: initialOnResume}, - }); - - act(() => { - result.current.verifyAccountAndResume({paymentID: 'payment-1'}); - }); - rerender({onResume: latestOnResume}); - - await setIsUserValidated(true); - - await waitFor(() => { - expect(mockedNavigation.runAfterUpcomingTransition).toHaveBeenCalledTimes(1); - }); - expect(latestOnResume).not.toHaveBeenCalled(); - - act(() => { - mockPendingTransitionCallbacks.at(0)?.(); - }); - - expect(initialOnResume).not.toHaveBeenCalled(); - expect(latestOnResume).toHaveBeenCalledWith({paymentID: 'payment-1'}); - }); - - it('resumes when the verify account route already closed itself before validation was observed', async () => { - const onResume = jest.fn(); - const {result} = renderHook(() => useVerifyAccountAndResume(onResume)); - - act(() => { - result.current.verifyAccountAndResume({paymentID: 'payment-1'}); - }); - - // The verify page's success effect navigates back as soon as validation succeeds, so by the time - // this hook observes the validated state the active route is already the originating screen. - mockedNavigation.getActiveRouteWithoutParams.mockReturnValue('r/123'); - await setIsUserValidated(true); - - await waitFor(() => { - expect(mockedNavigation.runAfterTransition).toHaveBeenCalledTimes(1); - }); - expect(mockedNavigation.runAfterUpcomingTransition).not.toHaveBeenCalled(); - - act(() => { - mockPendingTransitionCallbacks.at(0)?.(); - }); - - expect(onResume).toHaveBeenCalledWith({paymentID: 'payment-1'}); - }); - - it('drops the pending resume when the user leaves the verify account route before validation', async () => { - const onResume = jest.fn(); - const {result} = renderHook(() => useVerifyAccountAndResume(onResume)); - - act(() => { - result.current.verifyAccountAndResume({paymentID: 'payment-1'}); - }); - - await waitFor(() => { - expect(mockNavigationStateListeners.size).toBe(1); - }); - - mockedNavigation.getActiveRouteWithoutParams.mockReturnValue('r/123'); - act(() => { - emitNavigationStateChange(); - }); - - mockedNavigation.getActiveRouteWithoutParams.mockReturnValue(mockVerifyAccountPath); - await setIsUserValidated(true); - - expect(mockedNavigation.runAfterUpcomingTransition).not.toHaveBeenCalled(); - expect(onResume).not.toHaveBeenCalled(); - }); -});