diff --git a/src/libs/actions/IOU/Duplicate.ts b/src/libs/actions/IOU/Duplicate.ts index b1e5135aa0d7a..26262802c5688 100644 --- a/src/libs/actions/IOU/Duplicate.ts +++ b/src/libs/actions/IOU/Duplicate.ts @@ -623,6 +623,7 @@ function duplicateExpenseTransaction({ quickAction, recentWaypoints, betas, + draftTransactionIDs, isSelfTourViewed, }; return trackExpense(trackExpenseParams); diff --git a/src/libs/actions/IOU/MoneyRequest.ts b/src/libs/actions/IOU/MoneyRequest.ts index 63e93af483945..2e388596f10db 100644 --- a/src/libs/actions/IOU/MoneyRequest.ts +++ b/src/libs/actions/IOU/MoneyRequest.ts @@ -159,6 +159,7 @@ type MoneyRequestStepDistanceNavigationParams = { introSelected?: IntroSelected; activePolicyID?: string; privateIsArchived?: string; + draftTransactionIDs: string[] | undefined; selfDMReport: OnyxEntry; gpsCoordinates?: string; gpsDistance?: number; @@ -232,6 +233,7 @@ function createTransaction({ introSelected, activePolicyID, quickAction, + draftTransactionIDs, recentWaypoints, betas, isSelfTourViewed, @@ -580,6 +582,7 @@ function handleMoneyRequestStepDistanceNavigation({ introSelected, activePolicyID, privateIsArchived, + draftTransactionIDs = [], selfDMReport, gpsCoordinates, gpsDistance, @@ -673,6 +676,7 @@ function handleMoneyRequestStepDistanceNavigation({ introSelected, activePolicyID, quickAction, + draftTransactionIDs, recentWaypoints, betas, isSelfTourViewed, diff --git a/src/libs/actions/IOU/index.ts b/src/libs/actions/IOU/index.ts index 8c891bcea0c08..6062affc6ee49 100644 --- a/src/libs/actions/IOU/index.ts +++ b/src/libs/actions/IOU/index.ts @@ -531,7 +531,7 @@ type RequestMoneyInformation = { quickAction: OnyxEntry; policyRecentlyUsedCurrencies: string[]; existingTransactionDraft: OnyxEntry; - draftTransactionIDs: string[]; + draftTransactionIDs: string[] | undefined; isSelfTourViewed: boolean; betas: OnyxEntry; personalDetails: OnyxEntry; @@ -720,6 +720,7 @@ type CreateTrackExpenseParams = { quickAction: OnyxEntry; recentWaypoints: OnyxEntry; betas: OnyxEntry; + draftTransactionIDs: string[] | undefined; isSelfTourViewed: boolean; }; @@ -6203,7 +6204,7 @@ function requestMoney(requestMoneyInformation: RequestMoneyInformation): {iouRep quickAction, policyRecentlyUsedCurrencies, existingTransactionDraft, - draftTransactionIDs, + draftTransactionIDs = [], isSelfTourViewed, betas, personalDetails, @@ -6486,6 +6487,7 @@ function trackExpense(params: CreateTrackExpenseParams) { quickAction, recentWaypoints = [], betas, + draftTransactionIDs = [], isSelfTourViewed, } = params; const {participant, payeeAccountID, payeeEmail} = participantParams; @@ -6819,7 +6821,7 @@ function trackExpense(params: CreateTrackExpenseParams) { if (shouldHandleNavigation) { // eslint-disable-next-line @typescript-eslint/no-deprecated - InteractionManager.runAfterInteractions(() => removeDraftTransactions()); + InteractionManager.runAfterInteractions(() => removeDraftTransactionsByIDs(draftTransactionIDs)); } if (!params.isRetry) { diff --git a/src/pages/Share/SubmitDetailsPage.tsx b/src/pages/Share/SubmitDetailsPage.tsx index 4de8bf00c4196..02a178f3e86ef 100644 --- a/src/pages/Share/SubmitDetailsPage.tsx +++ b/src/pages/Share/SubmitDetailsPage.tsx @@ -172,6 +172,7 @@ function SubmitDetailsPage({ quickAction, recentWaypoints, betas, + draftTransactionIDs, isSelfTourViewed, }); } else { diff --git a/src/pages/iou/request/step/IOURequestStepAmount.tsx b/src/pages/iou/request/step/IOURequestStepAmount.tsx index ade7a1af848e7..8831be3e1ed13 100644 --- a/src/pages/iou/request/step/IOURequestStepAmount.tsx +++ b/src/pages/iou/request/step/IOURequestStepAmount.tsx @@ -308,6 +308,7 @@ function IOURequestStepAmount({ quickAction, recentWaypoints, betas, + draftTransactionIDs, isSelfTourViewed, }); return; diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx index ec6c64f50a71c..ad0211559be53 100644 --- a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx +++ b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx @@ -104,6 +104,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; +import {validTransactionDraftIDsSelector} from '@src/selectors/TransactionDraft'; import type {PolicyTagLists, RecentlyUsedCategories, Report} from '@src/types/onyx'; import type {Participant} from '@src/types/onyx/IOU'; import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage'; @@ -217,6 +218,7 @@ function IOURequestStepConfirmation({ const [userLocation] = useOnyx(ONYXKEYS.USER_LOCATION); const [quickAction] = useOnyx(ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE); const [isSelfTourViewed = false] = useOnyx(ONYXKEYS.NVP_ONBOARDING, {selector: hasSeenTourSelector}); + const [draftTransactionIDs] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_DRAFT, {selector: validTransactionDraftIDsSelector}); const reportAttributesDerived = useReportAttributes(); const [recentlyUsedDestinations] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_DESTINATIONS}${policyID}`); @@ -688,7 +690,7 @@ function IOURequestStepConfirmation({ policyRecentlyUsedCurrencies: policyRecentlyUsedCurrencies ?? [], quickAction, existingTransactionDraft, - draftTransactionIDs: transactionIDs, + draftTransactionIDs, isSelfTourViewed, betas, personalDetails, @@ -733,6 +735,7 @@ function IOURequestStepConfirmation({ betas, personalDetails, isGPSDistanceRequest, + draftTransactionIDs, ], ); @@ -897,6 +900,7 @@ function IOURequestStepConfirmation({ quickAction, recentWaypoints, betas, + draftTransactionIDs, isSelfTourViewed, }); } @@ -926,6 +930,7 @@ function IOURequestStepConfirmation({ quickAction, recentWaypoints, betas, + draftTransactionIDs, isSelfTourViewed, ], ); diff --git a/src/pages/iou/request/step/IOURequestStepDistance.tsx b/src/pages/iou/request/step/IOURequestStepDistance.tsx index 338e1c73a7820..5a28db0493add 100644 --- a/src/pages/iou/request/step/IOURequestStepDistance.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistance.tsx @@ -50,6 +50,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import {hasSeenTourSelector} from '@src/selectors/Onboarding'; +import {validTransactionDraftIDsSelector} from '@src/selectors/TransactionDraft'; import type {Participant} from '@src/types/onyx/IOU'; import type {Errors} from '@src/types/onyx/OnyxCommon'; import type {Waypoint, WaypointCollection} from '@src/types/onyx/Transaction'; @@ -105,6 +106,7 @@ function IOURequestStepDistance({ const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID); const {policyForMovingExpenses} = usePolicyForMovingExpenses(); const [betas] = useOnyx(ONYXKEYS.BETAS); + const [draftTransactionIDs] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_DRAFT, {selector: validTransactionDraftIDsSelector}); const [isSelfTourViewed] = useOnyx(ONYXKEYS.NVP_ONBOARDING, {selector: hasSeenTourSelector}); const isEditing = action === CONST.IOU.ACTION.EDIT; @@ -343,6 +345,7 @@ function IOURequestStepDistance({ policyForMovingExpenses, betas, recentWaypoints, + draftTransactionIDs, isSelfTourViewed: !!isSelfTourViewed, amountOwed, }); @@ -379,6 +382,7 @@ function IOURequestStepDistance({ selfDMReport, betas, recentWaypoints, + draftTransactionIDs, isSelfTourViewed, amountOwed, ]); diff --git a/src/pages/iou/request/step/IOURequestStepDistanceGPS/index.native.tsx b/src/pages/iou/request/step/IOURequestStepDistanceGPS/index.native.tsx index acf9233a7f641..378cfa2de4aff 100644 --- a/src/pages/iou/request/step/IOURequestStepDistanceGPS/index.native.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistanceGPS/index.native.tsx @@ -27,6 +27,7 @@ import withWritableReportOrNotFound from '@pages/iou/request/step/withWritableRe import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import {hasSeenTourSelector} from '@src/selectors/Onboarding'; +import {validTransactionDraftIDsSelector} from '@src/selectors/TransactionDraft'; import type {Errors} from '@src/types/onyx/OnyxCommon'; import DistanceCounter from './DistanceCounter'; import GPSButtons from './GPSButtons'; @@ -55,6 +56,7 @@ function IOURequestStepDistanceGPS({ const [quickAction] = useOnyx(ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE); const [policyRecentlyUsedCurrencies] = useOnyx(ONYXKEYS.RECENTLY_USED_CURRENCIES); const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS); + const [draftTransactionIDs] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_DRAFT, {selector: validTransactionDraftIDsSelector}); const selfDMReport = useSelfDMReport(); const {policyForMovingExpenses} = usePolicyForMovingExpenses(); const [betas] = useOnyx(ONYXKEYS.BETAS); @@ -126,6 +128,7 @@ function IOURequestStepDistanceGPS({ recentWaypoints, unit, personalOutputCurrency: personalPolicy?.outputCurrency, + draftTransactionIDs, isSelfTourViewed: !!isSelfTourViewed, amountOwed, }); diff --git a/src/pages/iou/request/step/IOURequestStepDistanceManual.tsx b/src/pages/iou/request/step/IOURequestStepDistanceManual.tsx index eeda0bb750d77..6524e9d1213b2 100644 --- a/src/pages/iou/request/step/IOURequestStepDistanceManual.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistanceManual.tsx @@ -37,6 +37,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; import {hasSeenTourSelector} from '@src/selectors/Onboarding'; +import {validTransactionDraftIDsSelector} from '@src/selectors/TransactionDraft'; import type Transaction from '@src/types/onyx/Transaction'; import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue'; import StepScreenWrapper from './StepScreenWrapper'; @@ -93,6 +94,7 @@ function IOURequestStepDistanceManual({ const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(report?.parentReportID)}`); const [parentReportNextStep] = useOnyx(`${ONYXKEYS.COLLECTION.NEXT_STEP}${getNonEmptyStringOnyxID(report?.parentReportID)}`); const [betas] = useOnyx(ONYXKEYS.BETAS); + const [draftTransactionIDs] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_DRAFT, {selector: validTransactionDraftIDsSelector}); const [isSelfTourViewed] = useOnyx(ONYXKEYS.NVP_ONBOARDING, {selector: hasSeenTourSelector}); const [splitDraftTransaction] = useOnyx(`${ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT}${transactionID}`); @@ -240,6 +242,7 @@ function IOURequestStepDistanceManual({ recentWaypoints, unit, personalOutputCurrency: personalPolicy?.outputCurrency, + draftTransactionIDs, isSelfTourViewed: !!isSelfTourViewed, amountOwed, }); @@ -286,6 +289,7 @@ function IOURequestStepDistanceManual({ selfDMReport, betas, personalPolicy?.outputCurrency, + draftTransactionIDs, isSelfTourViewed, amountOwed, ], diff --git a/src/pages/iou/request/step/IOURequestStepDistanceMap.tsx b/src/pages/iou/request/step/IOURequestStepDistanceMap.tsx index 6732d90b02cc8..d8c26f5c9d4db 100644 --- a/src/pages/iou/request/step/IOURequestStepDistanceMap.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistanceMap.tsx @@ -49,6 +49,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import {hasSeenTourSelector} from '@src/selectors/Onboarding'; +import {validTransactionDraftIDsSelector} from '@src/selectors/TransactionDraft'; import type {Participant} from '@src/types/onyx/IOU'; import type {Errors} from '@src/types/onyx/OnyxCommon'; import type {Waypoint, WaypointCollection} from '@src/types/onyx/Transaction'; @@ -169,6 +170,8 @@ function IOURequestStepDistanceMap({ const isASAPSubmitBetaEnabled = isBetaEnabled(CONST.BETAS.ASAP_SUBMIT); const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED); const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID); + const [draftTransactionIDs] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_DRAFT, {selector: validTransactionDraftIDsSelector}); + const currentUserAccountIDParam = currentUserPersonalDetails.accountID; const currentUserEmailParam = currentUserPersonalDetails.login ?? ''; @@ -335,6 +338,7 @@ function IOURequestStepDistanceMap({ policyForMovingExpenses, betas, recentWaypoints, + draftTransactionIDs, isSelfTourViewed: !!isSelfTourViewed, amountOwed, }); @@ -371,6 +375,7 @@ function IOURequestStepDistanceMap({ selfDMReport, betas, recentWaypoints, + draftTransactionIDs, isSelfTourViewed, amountOwed, ]); diff --git a/src/pages/iou/request/step/IOURequestStepDistanceOdometer.tsx b/src/pages/iou/request/step/IOURequestStepDistanceOdometer.tsx index 5f254cb84b569..772222b5cfe63 100644 --- a/src/pages/iou/request/step/IOURequestStepDistanceOdometer.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistanceOdometer.tsx @@ -42,6 +42,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import SCREENS from '@src/SCREENS'; import {hasSeenTourSelector} from '@src/selectors/Onboarding'; +import {validTransactionDraftIDsSelector} from '@src/selectors/TransactionDraft'; import type Transaction from '@src/types/onyx/Transaction'; import type {FileObject} from '@src/types/utils/Attachment'; import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue'; @@ -117,6 +118,7 @@ function IOURequestStepDistanceOdometer({ const {policyForMovingExpenses} = usePolicyForMovingExpenses(); const [isSelfTourViewed] = useOnyx(ONYXKEYS.NVP_ONBOARDING, {selector: hasSeenTourSelector}); const [selectedTab, selectedTabResult] = useOnyx(`${ONYXKEYS.COLLECTION.SELECTED_TAB}${CONST.TAB.DISTANCE_REQUEST_TYPE}`); + const [draftTransactionIDs] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_DRAFT, {selector: validTransactionDraftIDsSelector}); const isLoadingSelectedTab = isLoadingOnyxValue(selectedTabResult); // isEditing: we're changing an already existing odometer expense; isEditingConfirmation: we navigated here by pressing 'Distance' field from the confirmation step during the creation of a new odometer expense to adjust the input before submitting @@ -471,6 +473,7 @@ function IOURequestStepDistanceOdometer({ recentWaypoints, unit, personalOutputCurrency: personalPolicy?.outputCurrency, + draftTransactionIDs, isSelfTourViewed: !!isSelfTourViewed, amountOwed, }); diff --git a/src/selectors/TransactionDraft.ts b/src/selectors/TransactionDraft.ts index 92a274165eb09..f3b4d44cbbdc5 100644 --- a/src/selectors/TransactionDraft.ts +++ b/src/selectors/TransactionDraft.ts @@ -9,5 +9,12 @@ const validTransactionDraftsSelector = (drafts: OnyxCollection): Re return acc; }, {}); -// eslint-disable-next-line import/prefer-default-export -export {validTransactionDraftsSelector}; +const validTransactionDraftIDsSelector = (drafts: OnyxCollection): string[] => + Object.values(drafts ?? {}).reduce((acc, draft) => { + if (draft) { + acc.push(draft.transactionID); + } + return acc; + }, []); + +export {validTransactionDraftsSelector, validTransactionDraftIDsSelector}; diff --git a/tests/actions/IOU/MoneyRequestTest.ts b/tests/actions/IOU/MoneyRequestTest.ts index 57cf61370efd0..4820916ad172e 100644 --- a/tests/actions/IOU/MoneyRequestTest.ts +++ b/tests/actions/IOU/MoneyRequestTest.ts @@ -112,6 +112,7 @@ describe('MoneyRequest', () => { createTransaction({ ...baseParams, iouType: CONST.IOU.TYPE.TRACK, + allTransactionDrafts: {}, }); expect(IOU.trackExpense).toHaveBeenCalledTimes(1); @@ -150,6 +151,7 @@ describe('MoneyRequest', () => { createTransaction({ ...baseParams, iouType: CONST.IOU.TYPE.SEND, + allTransactionDrafts: {}, }); expect(IOU.requestMoney).toHaveBeenCalledTimes(1); @@ -199,6 +201,7 @@ describe('MoneyRequest', () => { ...baseParams, iouType: CONST.IOU.TYPE.TRACK, files, + allTransactionDrafts: {}, }); expect(IOU.trackExpense).toHaveBeenCalledTimes(files.length); @@ -229,6 +232,7 @@ describe('MoneyRequest', () => { createTransaction({ ...baseParams, files, + allTransactionDrafts: {}, }); expect(IOU.requestMoney).toHaveBeenCalledWith( @@ -247,6 +251,7 @@ describe('MoneyRequest', () => { createTransaction({ ...baseParams, currentUserEmail: undefined, + allTransactionDrafts: {}, }); expect(IOU.requestMoney).toHaveBeenCalledWith( @@ -289,6 +294,19 @@ describe('MoneyRequest', () => { ); }); + it('should default draftTransactionIDs to empty array when allTransactionDrafts is undefined', () => { + createTransaction({ + ...baseParams, + allTransactionDrafts: undefined, + }); + + expect(IOU.requestMoney).toHaveBeenCalledWith( + expect.objectContaining({ + draftTransactionIDs: [], + }), + ); + }); + it('should pass billable and reimbursable flags to trackExpense', () => { createTransaction({ ...baseParams, @@ -438,6 +456,7 @@ describe('MoneyRequest', () => { handleMoneyRequestStepScanParticipants({ ...baseParams, backTo, + allTransactionDrafts: {}, }); expect(Navigation.goBack).toHaveBeenCalledWith(backTo); @@ -457,6 +476,7 @@ describe('MoneyRequest', () => { handleMoneyRequestStepScanParticipants({ ...baseParams, isTestTransaction: true, + allTransactionDrafts: {}, personalDetails: { ...baseParams.personalDetails, [managerMcTestAccountID]: { @@ -495,6 +515,7 @@ describe('MoneyRequest', () => { ...baseParams.initialTransaction, isFromGlobalCreate: false, }, + allTransactionDrafts: {}, }); await waitForBatchedUpdates(); @@ -872,6 +893,7 @@ describe('MoneyRequest', () => { recentWaypoints: [] as RecentWaypoint[], isSelfTourViewed: false, amountOwed: 0, + draftTransactionIDs: undefined, }; const splitShares: SplitShares = { [firstSplitParticipantID]: { @@ -904,6 +926,17 @@ describe('MoneyRequest', () => { handleMoneyRequestStepDistanceNavigation({ ...baseParams, backTo, + draftTransactionIDs: [baseParams.transactionID], + }); + + expect(Navigation.goBack).toHaveBeenCalledWith(backTo); + }); + + it('should default draftTransactionIDs to empty array when undefined is passed', () => { + handleMoneyRequestStepDistanceNavigation({ + ...baseParams, + backTo, + draftTransactionIDs: undefined, }); expect(Navigation.goBack).toHaveBeenCalledWith(backTo); @@ -921,6 +954,7 @@ describe('MoneyRequest', () => { manualDistance: undefined, shouldSkipConfirmation: true, iouType: CONST.IOU.TYPE.TRACK, + draftTransactionIDs: [baseParams.transactionID], }); expect(Split.resetSplitShares).toHaveBeenCalledWith(splitTransaction); @@ -932,6 +966,7 @@ describe('MoneyRequest', () => { manualDistance: 20, shouldSkipConfirmation: true, iouType: CONST.IOU.TYPE.TRACK, + draftTransactionIDs: [baseParams.transactionID], }); expect(Split.resetSplitShares).not.toHaveBeenCalled(); @@ -970,8 +1005,8 @@ describe('MoneyRequest', () => { customUnitRateID: baseParams.customUnitRateID, attendees: fakeTransaction?.comment?.attendees, gpsCoordinates: undefined, - odometerStart: undefined, odometerEnd: undefined, + odometerStart: undefined, }, isASAPSubmitBetaEnabled: baseParams.isASAPSubmitBetaEnabled, currentUserAccountIDParam: baseParams.currentUserAccountID, @@ -979,6 +1014,7 @@ describe('MoneyRequest', () => { quickAction: baseParams.quickAction, recentWaypoints: baseParams.recentWaypoints, betas: [CONST.BETAS.ALL], + draftTransactionIDs: [baseParams.transactionID], }); // The function must return after trackExpense and not call createDistanceRequest @@ -991,6 +1027,7 @@ describe('MoneyRequest', () => { manualDistance: undefined, shouldSkipConfirmation: true, iouType: CONST.IOU.TYPE.TRACK, + draftTransactionIDs: [baseParams.transactionID], }); await waitForBatchedUpdates(); @@ -1051,6 +1088,7 @@ describe('MoneyRequest', () => { shouldSkipConfirmation: true, manualDistance: 20, iouType: CONST.IOU.TYPE.SUBMIT, + draftTransactionIDs: [baseParams.transactionID], }); expect(IOU.createDistanceRequest).toHaveBeenCalledWith( @@ -1089,6 +1127,7 @@ describe('MoneyRequest', () => { shouldSkipConfirmation: true, manualDistance: undefined, iouType: CONST.IOU.TYPE.SUBMIT, + draftTransactionIDs: [baseParams.transactionID], }); expect(IOU.createDistanceRequest).toHaveBeenCalledWith( @@ -1126,6 +1165,7 @@ describe('MoneyRequest', () => { ...baseParams, shouldSkipConfirmation: false, iouType: CONST.IOU.TYPE.SUBMIT, + draftTransactionIDs: [baseParams.transactionID], }); await waitForBatchedUpdates(); @@ -1161,6 +1201,7 @@ describe('MoneyRequest', () => { defaultExpensePolicy, isAutoReporting: true, iouType: CONST.IOU.TYPE.CREATE, + draftTransactionIDs: [baseParams.transactionID], }); await waitForBatchedUpdates(); @@ -1201,6 +1242,7 @@ describe('MoneyRequest', () => { report: undefined, defaultExpensePolicy, iouType: CONST.IOU.TYPE.CREATE, + draftTransactionIDs: [baseParams.transactionID], }); await waitForBatchedUpdates(); @@ -1212,6 +1254,7 @@ describe('MoneyRequest', () => { handleMoneyRequestStepDistanceNavigation({ ...baseParams, iouType: CONST.IOU.TYPE.CREATE, + draftTransactionIDs: [baseParams.transactionID], }); expect(Navigation.navigate).toHaveBeenCalledWith(ROUTES.MONEY_REQUEST_STEP_PARTICIPANTS.getRoute(CONST.IOU.TYPE.CREATE, baseParams.transactionID, baseParams.reportID)); diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index 9179bcd71310d..21805f5853915 100644 --- a/tests/actions/IOUTest.ts +++ b/tests/actions/IOUTest.ts @@ -581,6 +581,7 @@ describe('actions/IOU', () => { quickAction: undefined, recentWaypoints, betas: [CONST.BETAS.ALL], + draftTransactionIDs: [fakeTransaction.transactionID], isSelfTourViewed: false, }); await waitForBatchedUpdates(); @@ -684,6 +685,7 @@ describe('actions/IOU', () => { quickAction: undefined, recentWaypoints, betas: [CONST.BETAS.ALL], + draftTransactionIDs: [], isSelfTourViewed: false, }); await waitForBatchedUpdates(); @@ -772,6 +774,7 @@ describe('actions/IOU', () => { quickAction: undefined, recentWaypoints, betas: [CONST.BETAS.ALL], + draftTransactionIDs: [transaction.transactionID], isSelfTourViewed: false, }); await waitForBatchedUpdates(); @@ -827,6 +830,7 @@ describe('actions/IOU', () => { quickAction: undefined, recentWaypoints, betas: [CONST.BETAS.ALL], + draftTransactionIDs: [], isSelfTourViewed: false, }); await waitForBatchedUpdates(); @@ -910,6 +914,7 @@ describe('actions/IOU', () => { quickAction: undefined, recentWaypoints, betas: [CONST.BETAS.ALL], + draftTransactionIDs: [transaction.transactionID], isSelfTourViewed: false, }); await waitForBatchedUpdates(); @@ -965,6 +970,7 @@ describe('actions/IOU', () => { quickAction: undefined, recentWaypoints, betas: [CONST.BETAS.ALL], + draftTransactionIDs: [], isSelfTourViewed: false, }); await waitForBatchedUpdates(); @@ -1031,6 +1037,7 @@ describe('actions/IOU', () => { quickAction: undefined, recentWaypoints: [], betas: [CONST.BETAS.ALL], + draftTransactionIDs: [], isSelfTourViewed: false, }; } @@ -1249,6 +1256,7 @@ describe('actions/IOU', () => { activePolicyID: undefined, quickAction: undefined, recentWaypoints: [], + draftTransactionIDs: [], betas: [CONST.BETAS.ALL], isSelfTourViewed: false, }); @@ -1341,6 +1349,7 @@ describe('actions/IOU', () => { quickAction: undefined, recentWaypoints: [], betas: [CONST.BETAS.ALL], + draftTransactionIDs: [], isSelfTourViewed: false, }); await waitForBatchedUpdates(); @@ -3097,6 +3106,7 @@ describe('actions/IOU', () => { quickAction: undefined, recentWaypoints, betas: [CONST.BETAS.ALL], + draftTransactionIDs: [], isSelfTourViewed: false, }); @@ -3165,6 +3175,7 @@ describe('actions/IOU', () => { quickAction: undefined, recentWaypoints, betas: [CONST.BETAS.ALL], + draftTransactionIDs: [], isSelfTourViewed: false, }); await waitForBatchedUpdates(); @@ -3689,6 +3700,7 @@ describe('actions/IOU', () => { quickAction: undefined, recentWaypoints, betas: [CONST.BETAS.ALL], + draftTransactionIDs: [], isSelfTourViewed: false, }); await waitForBatchedUpdates(); @@ -8966,6 +8978,7 @@ describe('actions/IOU', () => { quickAction: undefined, recentWaypoints, betas: [CONST.BETAS.ALL], + draftTransactionIDs: [], isSelfTourViewed: false, }); await waitForBatchedUpdates(); @@ -11011,6 +11024,7 @@ describe('actions/IOU', () => { quickAction: undefined, recentWaypoints, betas: [CONST.BETAS.ALL], + draftTransactionIDs: [], isSelfTourViewed: false, }); @@ -12396,6 +12410,7 @@ describe('actions/IOU', () => { quickAction: undefined, recentWaypoints, betas: [CONST.BETAS.ALL], + draftTransactionIDs: [], isSelfTourViewed: false, }); await getOnyxData({ diff --git a/tests/ui/IOURequestStepDistanceTest.tsx b/tests/ui/IOURequestStepDistanceTest.tsx new file mode 100644 index 0000000000000..bc3228e5e0500 --- /dev/null +++ b/tests/ui/IOURequestStepDistanceTest.tsx @@ -0,0 +1,283 @@ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +import {act, render, screen} from '@testing-library/react-native'; +import React from 'react'; +import Onyx from 'react-native-onyx'; +import {CurrentUserPersonalDetailsProvider} from '@components/CurrentUserPersonalDetailsProvider'; +import OnyxListItemProvider from '@components/OnyxListItemProvider'; +import IOURequestStepDistance from '@pages/iou/request/step/IOURequestStepDistance'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import SCREENS from '@src/SCREENS'; +import type {Report, Transaction} from '@src/types/onyx'; +import type * as IOU from '../../src/libs/actions/IOU'; +import createRandomTransaction from '../utils/collections/transaction'; +import {signInWithTestUser} from '../utils/TestHelper'; +import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; +import waitForBatchedUpdatesWithAct from '../utils/waitForBatchedUpdatesWithAct'; + +jest.mock('@rnmapbox/maps', () => ({ + default: jest.fn(), + MarkerView: jest.fn(), + setAccessToken: jest.fn(), +})); + +jest.mock('@components/LocaleContextProvider', () => { + const React2 = require('react'); + + const defaultContextValue = { + translate: (path: string) => path, + numberFormat: (number: number) => String(number), + getLocalDateFromDatetime: () => new Date(), + datetimeToRelative: () => '', + datetimeToCalendarTime: () => '', + formatPhoneNumber: (phone: string) => phone, + toLocaleDigit: (digit: string) => digit, + toLocaleOrdinal: (number: number) => String(number), + fromLocaleDigit: (localeDigit: string) => localeDigit, + localeCompare: (a: string, b: string) => a.localeCompare(b), + formatTravelDate: () => '', + preferredLocale: 'en', + }; + + const LocaleContext = React2.createContext(defaultContextValue); + + return { + LocaleContext, + LocaleContextProvider: ({children}: {children: React.ReactNode}) => React2.createElement(LocaleContext.Provider, {value: defaultContextValue}, children), + }; +}); + +jest.mock('@libs/actions/IOU', () => { + const actual = jest.requireActual('@libs/actions/IOU'); + return { + ...actual, + requestMoney: jest.fn(() => ({iouReport: undefined})), + trackExpense: jest.fn(), + createDistanceRequest: jest.fn(), + }; +}); + +jest.mock('@libs/actions/IOU/MoneyRequest', () => ({ + handleMoneyRequestStepDistanceNavigation: jest.fn(), +})); + +jest.mock('@libs/actions/MapboxToken', () => ({ + init: jest.fn(), + stop: jest.fn(), +})); + +jest.mock('@libs/actions/Transaction', () => ({ + openDraftDistanceExpense: jest.fn(), + removeWaypoint: jest.fn(() => Promise.resolve()), + updateWaypoints: jest.fn(() => Promise.resolve()), + getRoute: jest.fn(), +})); + +jest.mock('@libs/actions/TransactionEdit', () => ({ + createBackupTransaction: jest.fn(), + removeBackupTransaction: jest.fn(), + restoreOriginalTransactionFromBackup: jest.fn(), +})); + +jest.mock('@components/ProductTrainingContext', () => ({ + useProductTrainingContext: () => [false], +})); +jest.mock('@src/hooks/useResponsiveLayout'); + +jest.mock('@libs/Navigation/navigationRef', () => ({ + getCurrentRoute: jest.fn(() => ({ + name: 'Money_Request_Step_Distance', + params: {}, + })), + getState: jest.fn(() => ({})), +})); + +jest.mock('@libs/Navigation/Navigation', () => { + const mockRef = { + getCurrentRoute: jest.fn(() => ({ + name: 'Money_Request_Step_Distance', + params: {}, + })), + getState: jest.fn(() => ({})), + }; + return { + navigate: jest.fn(), + goBack: jest.fn(), + dismissModalWithReport: jest.fn(), + navigationRef: mockRef, + setNavigationActionToMicrotaskQueue: jest.fn((callback: () => void) => callback()), + getReportRouteByID: jest.fn(() => undefined), + removeScreenByKey: jest.fn(), + getActiveRouteWithoutParams: jest.fn(() => ''), + getActiveRoute: jest.fn(() => ''), + }; +}); + +jest.mock('@react-navigation/native', () => { + const mockRef = { + getCurrentRoute: jest.fn(() => ({ + name: 'Money_Request_Step_Distance', + params: {}, + })), + getState: jest.fn(() => ({})), + }; + return { + createNavigationContainerRef: jest.fn(() => mockRef), + useIsFocused: () => true, + useNavigation: () => ({navigate: jest.fn(), addListener: jest.fn()}), + useFocusEffect: jest.fn(), + usePreventRemove: jest.fn(), + useRoute: jest.fn(), + }; +}); + +// Mock DistanceRequestFooter to avoid Mapbox rendering issues +jest.mock('@components/DistanceRequest/DistanceRequestFooter', () => { + return () => null; +}); + +jest.mock('@hooks/useScreenWrapperTransitionStatus', () => ({ + // eslint-disable-next-line @typescript-eslint/naming-convention + __esModule: true, + default: () => ({didScreenTransitionEnd: true}), +})); + +const ACCOUNT_ID = 1; +const ACCOUNT_LOGIN = 'test@user.com'; +const REPORT_ID = 'report-1'; +const TRANSACTION_ID = 'txn-1'; +const PARTICIPANT_ACCOUNT_ID = 2; + +function createTestReport(): Report { + return { + reportID: REPORT_ID, + chatType: CONST.REPORT.CHAT_TYPE.DOMAIN_ALL, + ownerAccountID: ACCOUNT_ID, + stateNum: CONST.REPORT.STATE_NUM.OPEN, + statusNum: CONST.REPORT.STATUS_NUM.OPEN, + isPinned: false, + lastVisibleActionCreated: '', + lastReadTime: '', + participants: { + [ACCOUNT_ID]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS, role: CONST.REPORT.ROLE.MEMBER}, + [PARTICIPANT_ACCOUNT_ID]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS, role: CONST.REPORT.ROLE.MEMBER}, + }, + }; +} + +function createDistanceTransaction(): Transaction { + const transaction = createRandomTransaction(1); + return { + ...transaction, + transactionID: TRANSACTION_ID, + reportID: REPORT_ID, + iouRequestType: CONST.IOU.REQUEST_TYPE.DISTANCE, + comment: { + ...transaction.comment, + waypoints: { + waypoint0: {address: '123 Main St', lat: 40.7128, lng: -74.006, keyForList: 'start_waypoint'}, + waypoint1: {address: '456 Oak Ave', lat: 40.7589, lng: -73.9851, keyForList: 'stop_waypoint'}, + }, + }, + }; +} + +describe('IOURequestStepDistance - draft transactions coverage', () => { + beforeAll(() => { + Onyx.init({ + keys: ONYXKEYS, + evictableKeys: [ONYXKEYS.COLLECTION.REPORT_ACTIONS], + }); + }); + + beforeEach(async () => { + jest.clearAllMocks(); + await Onyx.clear(); + await waitForBatchedUpdates(); + }); + + it('should render and read draftTransactionIDs from Onyx', async () => { + await signInWithTestUser(ACCOUNT_ID, ACCOUNT_LOGIN); + + const transaction = createDistanceTransaction(); + const report = createTestReport(); + + await act(async () => { + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, report); + await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${TRANSACTION_ID}`, transaction); + }); + + render( + + + + + , + ); + + await waitForBatchedUpdatesWithAct(); + + // Component rendered successfully with draftTransactionIDs loaded from Onyx + // When isCreatingNewRequest is true, StepScreenWrapper doesn't use ScreenWrapper so testID isn't set + // Verify component rendered by checking for waypoint content + expect(screen.getByAccessibilityHint(/123 Main St/)).toBeTruthy(); + }); + + it('should render with multiple draft transactions in Onyx', async () => { + await signInWithTestUser(ACCOUNT_ID, ACCOUNT_LOGIN); + + const transaction = createDistanceTransaction(); + const draftTransaction2 = createRandomTransaction(2); + const report = createTestReport(); + + await act(async () => { + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, report); + await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${TRANSACTION_ID}`, transaction); + await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${draftTransaction2.transactionID}`, draftTransaction2); + }); + + render( + + + + + , + ); + + await waitForBatchedUpdatesWithAct(); + + // Component rendered with multiple draft transaction IDs available + expect(screen.getByAccessibilityHint(/123 Main St/)).toBeTruthy(); + }); +}); diff --git a/tests/ui/UnreadIndicatorsTest.tsx b/tests/ui/UnreadIndicatorsTest.tsx index 8044c86b02450..7e60f44ba8197 100644 --- a/tests/ui/UnreadIndicatorsTest.tsx +++ b/tests/ui/UnreadIndicatorsTest.tsx @@ -762,6 +762,7 @@ describe('Unread Indicators', () => { quickAction: undefined, recentWaypoints, betas: [CONST.BETAS.ALL], + draftTransactionIDs: [fakeTransaction.transactionID], isSelfTourViewed: false, }); await waitForBatchedUpdates(); diff --git a/tests/unit/GoogleTagManagerTest.tsx b/tests/unit/GoogleTagManagerTest.tsx index 384ef28c38f96..77ea0d1d1c8d2 100644 --- a/tests/unit/GoogleTagManagerTest.tsx +++ b/tests/unit/GoogleTagManagerTest.tsx @@ -202,6 +202,7 @@ describe('GoogleTagManagerTest', () => { quickAction: undefined, recentWaypoints, betas: [CONST.BETAS.ALL], + draftTransactionIDs: [], isSelfTourViewed: false, });