From d0cb56d40e08175a3f872dd894e7f0672e5c2694 Mon Sep 17 00:00:00 2001 From: Wiktor Gut Date: Thu, 22 Jan 2026 15:17:46 +0100 Subject: [PATCH 01/21] add recentWaypointCollection to trackExpense --- .../handleFileRetry.ts | 13 ++++++ src/libs/actions/IOU/Duplicate.ts | 10 ++++ src/libs/actions/IOU/MoneyRequest.ts | 15 +++++- src/libs/actions/IOU/index.ts | 4 +- src/pages/Share/SubmitDetailsPage.tsx | 2 + .../iou/request/step/IOURequestStepAmount.tsx | 3 ++ .../step/IOURequestStepDistanceOdometer.tsx | 3 ++ tests/actions/IOU/MoneyRequestTest.ts | 26 ++++++++++- tests/actions/IOUTest.ts | 46 ++++++++++++++++++- tests/ui/UnreadIndicatorsTest.tsx | 11 ++++- tests/unit/GoogleTagManagerTest.tsx | 9 +++- 11 files changed, 134 insertions(+), 8 deletions(-) diff --git a/src/libs/ReceiptUploadRetryHandler/handleFileRetry.ts b/src/libs/ReceiptUploadRetryHandler/handleFileRetry.ts index 4bf3a4777edf..45e79b71e2b4 100644 --- a/src/libs/ReceiptUploadRetryHandler/handleFileRetry.ts +++ b/src/libs/ReceiptUploadRetryHandler/handleFileRetry.ts @@ -1,7 +1,19 @@ +import Onyx from 'react-native-onyx'; import * as IOU from '@userActions/IOU'; import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type * as OnyxTypes from '@src/types/onyx'; import type {ReceiptError} from '@src/types/onyx/Transaction'; +// TODO: remove `recentWaypoints` from this file [YET TO CREATE ISSUE] +// `recentWaypoints` was moved here temporarily from `src/libs/actions/IOU/index.ts` during the `Deprecate Onyx.connect` refactor. +// All uses of this variable should be replaced with `useOnyx`. +let recentWaypoints: OnyxTypes.RecentWaypoint[] = []; +Onyx.connect({ + key: ONYXKEYS.NVP_RECENT_WAYPOINTS, + callback: (val) => (recentWaypoints = val ?? []), +}); + export default function handleFileRetry(message: ReceiptError, file: File, dismissError: () => void, setShouldShowErrorModal: (value: boolean) => void) { const retryParams: IOU.ReplaceReceipt | IOU.StartSplitBilActionParams | IOU.CreateTrackExpenseParams | IOU.RequestMoneyInformation = typeof message.retryParams === 'string' @@ -30,6 +42,7 @@ export default function handleFileRetry(message: ReceiptError, file: File, dismi trackExpenseParams.transactionParams.receipt = file; trackExpenseParams.isRetry = true; trackExpenseParams.shouldPlaySound = false; + trackExpenseParams.recentWaypointsCollection = recentWaypoints; IOU.trackExpense(trackExpenseParams); break; } diff --git a/src/libs/actions/IOU/Duplicate.ts b/src/libs/actions/IOU/Duplicate.ts index d7e0205efbe4..6a1f146e3a03 100644 --- a/src/libs/actions/IOU/Duplicate.ts +++ b/src/libs/actions/IOU/Duplicate.ts @@ -37,6 +37,15 @@ import { trackExpense, } from '.'; +// TODO: remove `recentWaypoints` from this file [YET TO CREATE ISSUE] +// `recentWaypoints` was moved here temporarily from `src/libs/actions/IOU/index.ts` during the `Deprecate Onyx.connect` refactor. +// All uses of this variable should be replaced with `useOnyx`. +let recentWaypoints: OnyxTypes.RecentWaypoint[] = []; +Onyx.connect({ + key: ONYXKEYS.NVP_RECENT_WAYPOINTS, + callback: (val) => (recentWaypoints = val ?? []), +}); + function getIOUActionForTransactions(transactionIDList: Array, iouReportID: string | undefined): Array> { const allReportActions = getAllReportActionsFromIOU(); return Object.values(allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReportID}`] ?? {})?.filter( @@ -549,6 +558,7 @@ function duplicateExpenseTransaction({ introSelected, activePolicyID, quickAction, + recentWaypointsCollection: recentWaypoints, }; return trackExpense(trackExpenseParams); } diff --git a/src/libs/actions/IOU/MoneyRequest.ts b/src/libs/actions/IOU/MoneyRequest.ts index 67977cc7cb5a..c870d75df1fc 100644 --- a/src/libs/actions/IOU/MoneyRequest.ts +++ b/src/libs/actions/IOU/MoneyRequest.ts @@ -1,4 +1,5 @@ import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; +import Onyx from 'react-native-onyx'; import DistanceRequestUtils from '@libs/DistanceRequestUtils'; import getCurrentPosition from '@libs/getCurrentPosition'; import {navigateToConfirmationPage, navigateToParticipantPage} from '@libs/IOUUtils'; @@ -15,9 +16,10 @@ import {setTransactionReport} from '@userActions/Transaction'; import type {IOUType} from '@src/CONST'; import CONST from '@src/CONST'; import type {TranslationParameters, TranslationPaths} from '@src/languages/types'; +import ONYXKEYS from '@src/ONYXKEYS'; import type {Route} from '@src/ROUTES'; import ROUTES from '@src/ROUTES'; -import type {Beta, IntroSelected, LastSelectedDistanceRates, PersonalDetailsList, Policy, QuickAction, Report, Transaction, TransactionViolation} from '@src/types/onyx'; +import type {Beta, IntroSelected, LastSelectedDistanceRates, PersonalDetailsList, Policy, QuickAction, RecentWaypoint, Report, Transaction, TransactionViolation} from '@src/types/onyx'; import type {ReportAttributes, ReportAttributesDerivedValue} from '@src/types/onyx/DerivedValues'; import type {Participant} from '@src/types/onyx/IOU'; import type {Receipt, WaypointCollection} from '@src/types/onyx/Transaction'; @@ -134,6 +136,15 @@ type MoneyRequestStepDistanceNavigationParams = { allBetas: OnyxEntry; }; +// TODO: remove `recentWaypoints` from this file [YET TO CREATE ISSUE] +// `recentWaypoints` was moved here temporarily from `src/libs/actions/IOU/index.ts` during the `Deprecate Onyx.connect` refactor. +// All uses of this variable should be replaced with `useOnyx`. +let recentWaypoints: RecentWaypoint[] = []; +Onyx.connect({ + key: ONYXKEYS.NVP_RECENT_WAYPOINTS, + callback: (val) => (recentWaypoints = val ?? []), +}); + function createTransaction({ transactions, iouType, @@ -188,6 +199,7 @@ function createTransaction({ activePolicyID, quickAction, allBetas, + recentWaypointsCollection: recentWaypoints, }); } else { requestMoney({ @@ -551,6 +563,7 @@ function handleMoneyRequestStepDistanceNavigation({ activePolicyID, quickAction, allBetas, + recentWaypointsCollection: recentWaypoints, }); return; } diff --git a/src/libs/actions/IOU/index.ts b/src/libs/actions/IOU/index.ts index dc8d87528645..c4ad38bc9842 100644 --- a/src/libs/actions/IOU/index.ts +++ b/src/libs/actions/IOU/index.ts @@ -719,6 +719,7 @@ type CreateTrackExpenseParams = { activePolicyID: string | undefined; quickAction: OnyxEntry; allBetas: OnyxEntry; + recentWaypointsCollection: OnyxTypes.RecentWaypoint[] | undefined; }; type GetTrackExpenseInformationTransactionParams = { @@ -6527,6 +6528,7 @@ function trackExpense(params: CreateTrackExpenseParams) { activePolicyID, quickAction, allBetas, + recentWaypointsCollection = [], } = params; const {participant, payeeAccountID, payeeEmail} = participantParams; const {policy, policyCategories, policyTagList} = policyData; @@ -6662,7 +6664,7 @@ function trackExpense(params: CreateTrackExpenseParams) { }) ?? {}; const activeReportID = isMoneyRequestReport ? report?.reportID : chatReport?.reportID; - const recentServerValidatedWaypoints = recentWaypoints.filter((item) => !item.pendingAction); + const recentServerValidatedWaypoints = recentWaypointsCollection.filter((item) => !item.pendingAction); onyxData?.failureData?.push({ onyxMethod: Onyx.METHOD.SET, key: `${ONYXKEYS.NVP_RECENT_WAYPOINTS}`, diff --git a/src/pages/Share/SubmitDetailsPage.tsx b/src/pages/Share/SubmitDetailsPage.tsx index 6b6b65537e25..7d44dbbec25b 100644 --- a/src/pages/Share/SubmitDetailsPage.tsx +++ b/src/pages/Share/SubmitDetailsPage.tsx @@ -119,6 +119,7 @@ function SubmitDetailsPage({ const defaultTaxCode = getDefaultTaxCode(policy, transaction); const transactionTaxCode = (transaction?.taxCode ? transaction?.taxCode : defaultTaxCode) ?? ''; const isASAPSubmitBetaEnabled = isBetaEnabled(CONST.BETAS.ASAP_SUBMIT); + const [recentWaypoints] = useOnyx(ONYXKEYS.NVP_RECENT_WAYPOINTS, {canBeMissing: true}); const finishRequestAndNavigate = (participant: Participant, receipt: Receipt, gpsPoint?: GpsPoint) => { if (!transaction) { @@ -157,6 +158,7 @@ function SubmitDetailsPage({ introSelected, quickAction, allBetas, + recentWaypointsCollection: recentWaypoints, }); } else { requestMoney({ diff --git a/src/pages/iou/request/step/IOURequestStepAmount.tsx b/src/pages/iou/request/step/IOURequestStepAmount.tsx index f05f4155fac4..bc8b85a5c43d 100644 --- a/src/pages/iou/request/step/IOURequestStepAmount.tsx +++ b/src/pages/iou/request/step/IOURequestStepAmount.tsx @@ -182,6 +182,8 @@ function IOURequestStepAmount({ Navigation.goBack(backTo); }; + const [recentWaypoints] = useOnyx(ONYXKEYS.NVP_RECENT_WAYPOINTS, {canBeMissing: true}); + const navigateToNextPage = ({amount, paymentMethod}: AmountParams) => { isSaveButtonPressed.current = true; const amountInSmallestCurrencyUnits = convertToBackendAmount(Number.parseFloat(amount)); @@ -277,6 +279,7 @@ function IOURequestStepAmount({ activePolicyID, quickAction, allBetas, + recentWaypointsCollection: recentWaypoints, }); return; } diff --git a/src/pages/iou/request/step/IOURequestStepDistanceOdometer.tsx b/src/pages/iou/request/step/IOURequestStepDistanceOdometer.tsx index 6fb6d39f8ad3..4b9886e79b34 100644 --- a/src/pages/iou/request/step/IOURequestStepDistanceOdometer.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistanceOdometer.tsx @@ -310,6 +310,8 @@ function IOURequestStepDistanceOdometer({ Navigation.goBack(); }; + const [recentWaypoints] = useOnyx(ONYXKEYS.NVP_RECENT_WAYPOINTS, {canBeMissing: true}); + // Navigate to next page following Manual tab pattern const navigateToNextPage = () => { const start = parseFloat(startReading); @@ -418,6 +420,7 @@ function IOURequestStepDistanceOdometer({ activePolicyID, quickAction, allBetas, + recentWaypointsCollection: recentWaypoints, }); return; } diff --git a/tests/actions/IOU/MoneyRequestTest.ts b/tests/actions/IOU/MoneyRequestTest.ts index c769b8e1d2f4..05f8a8216afc 100644 --- a/tests/actions/IOU/MoneyRequestTest.ts +++ b/tests/actions/IOU/MoneyRequestTest.ts @@ -9,7 +9,7 @@ import type {ReceiptFile} from '@pages/iou/request/step/IOURequestStepScan/types import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import type {QuickAction} from '@src/types/onyx'; +import type {QuickAction, RecentWaypoint} from '@src/types/onyx'; import type {SplitShares} from '@src/types/onyx/Transaction'; import * as IOU from '../../../src/libs/actions/IOU'; import * as ReportUtils from '../../../src/libs/ReportUtils'; @@ -17,6 +17,7 @@ import createRandomPolicy from '../../utils/collections/policies'; import {createRandomReport} from '../../utils/collections/reports'; import createRandomTransaction from '../../utils/collections/transaction'; import getOnyxValue from '../../utils/getOnyxValue'; +import {getOnyxData} from '../../utils/TestHelper'; import waitForBatchedUpdates from '../../utils/waitForBatchedUpdates'; jest.mock('@libs/actions/IOU', () => { @@ -88,7 +89,7 @@ describe('MoneyRequest', () => { jest.clearAllMocks(); }); - it('should call trackExpense for TRACK iouType', () => { + it('should call trackExpense for TRACK iouType', async () => { createTransaction({ ...baseParams, iouType: CONST.IOU.TYPE.TRACK, @@ -489,6 +490,12 @@ describe('MoneyRequest', () => { await waitForBatchedUpdates(); + let recentWaypoints: RecentWaypoint[] = []; + await getOnyxData({ + key: ONYXKEYS.NVP_RECENT_WAYPOINTS, + callback: (val) => (recentWaypoints = val ?? []), + }); + expect(IOU.trackExpense).toHaveBeenCalledWith({ report: baseParams.report, isDraftPolicy: false, @@ -517,6 +524,7 @@ describe('MoneyRequest', () => { quickAction: baseParams.quickAction, shouldHandleNavigation: true, allBetas: baseParams.allBetas, + recentWaypointsCollection: recentWaypoints, }); // Should not call request money inside createTransaction function expect(IOU.requestMoney).not.toHaveBeenCalled(); @@ -774,6 +782,12 @@ describe('MoneyRequest', () => { expect(IOU.resetSplitShares).not.toHaveBeenCalled(); + let recentWaypoints: RecentWaypoint[] = []; + await getOnyxData({ + key: ONYXKEYS.NVP_RECENT_WAYPOINTS, + callback: (val) => (recentWaypoints = val ?? []), + }); + expect(IOU.trackExpense).toHaveBeenCalledWith({ report: baseParams.report, isDraftPolicy: false, @@ -810,6 +824,7 @@ describe('MoneyRequest', () => { currentUserEmailParam: baseParams.currentUserLogin, quickAction: baseParams.quickAction, allBetas: baseParams.allBetas, + recentWaypointsCollection: recentWaypoints, }); // The function must return after trackExpense and not call createDistanceRequest @@ -836,6 +851,12 @@ describe('MoneyRequest', () => { waypoints: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, }); + let recentWaypoints: RecentWaypoint[] = []; + await getOnyxData({ + key: ONYXKEYS.NVP_RECENT_WAYPOINTS, + callback: (val) => (recentWaypoints = val ?? []), + }); + expect(IOU.trackExpense).toHaveBeenCalledWith({ report: baseParams.report, isDraftPolicy: false, @@ -872,6 +893,7 @@ describe('MoneyRequest', () => { currentUserEmailParam: baseParams.currentUserLogin, quickAction: baseParams.quickAction, allBetas: baseParams.allBetas, + recentWaypointsCollection: recentWaypoints, }); }); diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index 467ef75f94e8..10cf58f38b83 100644 --- a/tests/actions/IOUTest.ts +++ b/tests/actions/IOUTest.ts @@ -76,7 +76,7 @@ import * as API from '@src/libs/API'; import DateUtils from '@src/libs/DateUtils'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import type {PersonalDetailsList, Policy, PolicyTagLists, RecentlyUsedTags, Report, ReportNameValuePairs, SearchResults} from '@src/types/onyx'; +import type {PersonalDetailsList, Policy, PolicyTagLists, RecentlyUsedTags, RecentWaypoint, Report, ReportNameValuePairs, SearchResults} from '@src/types/onyx'; import type {Accountant, Attendee, SplitExpense} from '@src/types/onyx/IOU'; import type {CurrentUserPersonalDetails} from '@src/types/onyx/PersonalDetails'; import type {Participant} from '@src/types/onyx/Report'; @@ -491,6 +491,12 @@ describe('actions/IOU', () => { await Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${fakeTransaction.transactionID}`, fakeTransaction); mockFetch?.pause?.(); + let recentWaypoints: RecentWaypoint[] = []; + await getOnyxData({ + key: ONYXKEYS.NVP_RECENT_WAYPOINTS, + callback: (val) => (recentWaypoints = val ?? []), + }); + // When the user submits the transaction to the selfDM report trackExpense({ report: selfDMReport, @@ -520,6 +526,7 @@ describe('actions/IOU', () => { activePolicyID: undefined, quickAction: undefined, allBetas: [CONST.BETAS.ALL], + recentWaypointsCollection: recentWaypoints, }); await waitForBatchedUpdates(); await mockFetch?.resume?.(); @@ -621,6 +628,7 @@ describe('actions/IOU', () => { activePolicyID: undefined, quickAction: undefined, allBetas: [CONST.BETAS.ALL], + recentWaypointsCollection: recentWaypoints, }); await waitForBatchedUpdates(); await mockFetch?.resume?.(); @@ -681,6 +689,12 @@ describe('actions/IOU', () => { await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${policyExpenseChat.reportID}`, policyExpenseChat); await Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transaction.transactionID}`, transaction); + let recentWaypoints: RecentWaypoint[] = []; + await getOnyxData({ + key: ONYXKEYS.NVP_RECENT_WAYPOINTS, + callback: (val) => (recentWaypoints = val ?? []), + }); + // Create a tracked expense trackExpense({ report: selfDMReport, @@ -705,6 +719,7 @@ describe('actions/IOU', () => { activePolicyID: undefined, quickAction: undefined, allBetas: [CONST.BETAS.ALL], + recentWaypointsCollection: recentWaypoints, }); await waitForBatchedUpdates(); @@ -758,6 +773,7 @@ describe('actions/IOU', () => { activePolicyID: undefined, quickAction: undefined, allBetas: [CONST.BETAS.ALL], + recentWaypointsCollection: recentWaypoints, }); await waitForBatchedUpdates(); @@ -813,6 +829,11 @@ describe('actions/IOU', () => { await Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transaction.transactionID}`, transaction); await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, {[accountant.accountID]: accountant}); + let recentWaypoints: RecentWaypoint[] = []; + await getOnyxData({ + key: ONYXKEYS.NVP_RECENT_WAYPOINTS, + callback: (val) => (recentWaypoints = val ?? []), + }); // Create a tracked expense trackExpense({ report: selfDMReport, @@ -837,6 +858,7 @@ describe('actions/IOU', () => { activePolicyID: undefined, quickAction: undefined, allBetas: [CONST.BETAS.ALL], + recentWaypointsCollection: recentWaypoints, }); await waitForBatchedUpdates(); @@ -890,6 +912,7 @@ describe('actions/IOU', () => { activePolicyID: undefined, quickAction: undefined, allBetas: [CONST.BETAS.ALL], + recentWaypointsCollection: recentWaypoints, }); await waitForBatchedUpdates(); @@ -2166,6 +2189,12 @@ describe('actions/IOU', () => { ]); await waitForBatchedUpdates(); + let recentWaypoints: RecentWaypoint[] = []; + await getOnyxData({ + key: ONYXKEYS.NVP_RECENT_WAYPOINTS, + callback: (val) => (recentWaypoints = val ?? []), + }); + // First create a tracked expense in self DM trackExpense({ report: selfDMReport, @@ -2191,6 +2220,7 @@ describe('actions/IOU', () => { activePolicyID: undefined, quickAction: undefined, allBetas: [CONST.BETAS.ALL], + recentWaypointsCollection: recentWaypoints, }); mockFetch?.resume?.(); @@ -2257,6 +2287,7 @@ describe('actions/IOU', () => { activePolicyID: undefined, quickAction: undefined, allBetas: [CONST.BETAS.ALL], + recentWaypointsCollection: recentWaypoints, }); await waitForBatchedUpdates(); @@ -7601,6 +7632,11 @@ describe('actions/IOU', () => { [WRITE_COMMANDS.CATEGORIZE_TRACKED_EXPENSE, CONST.IOU.ACTION.CATEGORIZE], [WRITE_COMMANDS.SHARE_TRACKED_EXPENSE, CONST.IOU.ACTION.SHARE], ])('%s', async (expectedCommand: ApiCommand, action: IOUAction) => { + let recentWaypoints: RecentWaypoint[] = []; + await getOnyxData({ + key: ONYXKEYS.NVP_RECENT_WAYPOINTS, + callback: (val) => (recentWaypoints = val ?? []), + }); // When a track expense is created trackExpense({ report: {reportID: '123', policyID: 'A'}, @@ -7633,6 +7669,7 @@ describe('actions/IOU', () => { activePolicyID: undefined, quickAction: undefined, allBetas: [CONST.BETAS.ALL], + recentWaypointsCollection: recentWaypoints, }); await waitForBatchedUpdates(); @@ -9265,6 +9302,12 @@ describe('actions/IOU', () => { const amount = 100; + let recentWaypoints: RecentWaypoint[] = []; + await getOnyxData({ + key: ONYXKEYS.NVP_RECENT_WAYPOINTS, + callback: (val) => (recentWaypoints = val ?? []), + }); + trackExpense({ report: selfDMReport, isDraftPolicy: true, @@ -9289,6 +9332,7 @@ describe('actions/IOU', () => { activePolicyID: undefined, quickAction: undefined, allBetas: [CONST.BETAS.ALL], + recentWaypointsCollection: recentWaypoints, }); await getOnyxData({ key: ONYXKEYS.COLLECTION.TRANSACTION, diff --git a/tests/ui/UnreadIndicatorsTest.tsx b/tests/ui/UnreadIndicatorsTest.tsx index 129b63749372..ed898ca1e10c 100644 --- a/tests/ui/UnreadIndicatorsTest.tsx +++ b/tests/ui/UnreadIndicatorsTest.tsx @@ -22,13 +22,13 @@ import FontUtils from '@styles/utils/FontUtils'; import App from '@src/App'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {ReportAction, ReportActions} from '@src/types/onyx'; +import type {RecentWaypoint, ReportAction, ReportActions} from '@src/types/onyx'; import type {NativeNavigationMock} from '../../__mocks__/@react-navigation/native'; import {createRandomReport} from '../utils/collections/reports'; import createRandomTransaction from '../utils/collections/transaction'; import PusherHelper from '../utils/PusherHelper'; import * as TestHelper from '../utils/TestHelper'; -import {navigateToSidebarOption} from '../utils/TestHelper'; +import {getOnyxData, navigateToSidebarOption} from '../utils/TestHelper'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; import waitForBatchedUpdatesWithAct from '../utils/waitForBatchedUpdatesWithAct'; @@ -698,6 +698,12 @@ describe('Unread Indicators', () => { comment: 'description', }; + let recentWaypoints: RecentWaypoint[] = []; + await getOnyxData({ + key: ONYXKEYS.NVP_RECENT_WAYPOINTS, + callback: (val) => (recentWaypoints = val ?? []), + }); + // When the user track an expense on the self DM const participant = {login: USER_A_EMAIL, accountID: USER_A_ACCOUNT_ID}; trackExpense({ @@ -721,6 +727,7 @@ describe('Unread Indicators', () => { activePolicyID: undefined, quickAction: undefined, allBetas: [CONST.BETAS.ALL], + recentWaypointsCollection: recentWaypoints, }); await waitForBatchedUpdates(); diff --git a/tests/unit/GoogleTagManagerTest.tsx b/tests/unit/GoogleTagManagerTest.tsx index 2c8dfc9f201d..b552e994def3 100644 --- a/tests/unit/GoogleTagManagerTest.tsx +++ b/tests/unit/GoogleTagManagerTest.tsx @@ -11,7 +11,8 @@ import navigationRef from '@libs/Navigation/navigationRef'; import {getCardForSubscriptionBilling} from '@libs/SubscriptionUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {FundList} from '@src/types/onyx'; +import type {FundList, RecentWaypoint} from '@src/types/onyx'; +import {getOnyxData} from '../utils/TestHelper'; import waitForBatchedUpdatesWithAct from '../utils/waitForBatchedUpdatesWithAct'; jest.mock('@libs/GoogleTagManager'); @@ -169,6 +170,11 @@ describe('GoogleTagManagerTest', () => { }); test('workspace_created - categorizeTrackedExpense', async () => { + let recentWaypoints: RecentWaypoint[] = []; + await getOnyxData({ + key: ONYXKEYS.NVP_RECENT_WAYPOINTS, + callback: (val) => (recentWaypoints = val ?? []), + }); trackExpense({ report: {reportID: '123'}, isDraftPolicy: true, @@ -198,6 +204,7 @@ describe('GoogleTagManagerTest', () => { activePolicyID: undefined, quickAction: undefined, allBetas: [CONST.BETAS.ALL], + recentWaypointsCollection: recentWaypoints, }); await waitForBatchedUpdatesWithAct(); From 06c377156c32fa01534e6d6a1402aeef67397a7e Mon Sep 17 00:00:00 2001 From: Wiktor Gut Date: Thu, 22 Jan 2026 15:31:50 +0100 Subject: [PATCH 02/21] update parameter in trackExpenseIOUActions --- .../step/IOURequestStepConfirmation.tsx | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx index cc0c48d6e5e2..00a8d84e201e 100644 --- a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx +++ b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx @@ -738,6 +738,8 @@ function IOURequestStepConfirmation({ ], ); + const [recentWaypoints] = useOnyx(ONYXKEYS.NVP_RECENT_WAYPOINTS, {canBeMissing: true}); + const trackExpense = useCallback( (selectedParticipants: Participant[], gpsPoint?: GpsPoint) => { if (!transactions.length) { @@ -802,31 +804,33 @@ function IOURequestStepConfirmation({ activePolicyID, quickAction, allBetas, + recentWaypointsCollection: recentWaypoints, }); } }, [ - report, transactions, - receiptFiles, + privateIsArchivedMap, + isManualDistanceRequest, + isOdometerDistanceRequest, + report, + isDraftPolicy, + action, currentUserPersonalDetails.login, currentUserPersonalDetails.accountID, - transactionTaxCode, - transactionTaxAmount, policy, - policyTags, policyCategories, - action, + policyTags, + receiptFiles, + transactionTaxCode, + transactionTaxAmount, customUnitRateID, - isDraftPolicy, - isManualDistanceRequest, - isOdometerDistanceRequest, - privateIsArchivedMap, isASAPSubmitBetaEnabled, introSelected, activePolicyID, quickAction, allBetas, + recentWaypoints, ], ); From 900c32e128c36e780ae3a12693df7237f6acc12e Mon Sep 17 00:00:00 2001 From: Wiktor Gut Date: Thu, 22 Jan 2026 17:25:50 +0100 Subject: [PATCH 03/21] bump max-warnings --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9eabb528eb2e..0d8cd2af08a2 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "test:debug": "TZ=utc NODE_OPTIONS='--inspect-brk --experimental-vm-modules' jest --runInBand", "perf-test": "NODE_OPTIONS=--experimental-vm-modules npx reassure", "typecheck": "NODE_OPTIONS=--max_old_space_size=8192 tsc", - "lint": "NODE_OPTIONS=--max_old_space_size=8192 eslint . --max-warnings=383 --cache --cache-location=node_modules/.cache/eslint --cache-strategy content --concurrency=auto", + "lint": "NODE_OPTIONS=--max_old_space_size=8192 eslint . --max-warnings=385 --cache --cache-location=node_modules/.cache/eslint --cache-strategy content --concurrency=auto", "lint-changed": "NODE_OPTIONS=--max_old_space_size=8192 ./scripts/lintChanged.sh", "check-lazy-loading": "ts-node scripts/checkLazyLoading.ts", "lint-watch": "npx eslint-watch --watch --changed", From ee2930f4ee99f6afd2b1a5a1cc7060095c1e131a Mon Sep 17 00:00:00 2001 From: Wiktor Gut Date: Fri, 23 Jan 2026 01:05:18 +0100 Subject: [PATCH 04/21] update following issues links --- src/libs/ReceiptUploadRetryHandler/handleFileRetry.ts | 2 +- src/libs/actions/IOU/Duplicate.ts | 2 +- src/libs/actions/IOU/MoneyRequest.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/ReceiptUploadRetryHandler/handleFileRetry.ts b/src/libs/ReceiptUploadRetryHandler/handleFileRetry.ts index 45e79b71e2b4..658b68b2a444 100644 --- a/src/libs/ReceiptUploadRetryHandler/handleFileRetry.ts +++ b/src/libs/ReceiptUploadRetryHandler/handleFileRetry.ts @@ -5,7 +5,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import type * as OnyxTypes from '@src/types/onyx'; import type {ReceiptError} from '@src/types/onyx/Transaction'; -// TODO: remove `recentWaypoints` from this file [YET TO CREATE ISSUE] +// TODO: remove `recentWaypoints` from this file [https://github.com/Expensify/App/issues/80268] // `recentWaypoints` was moved here temporarily from `src/libs/actions/IOU/index.ts` during the `Deprecate Onyx.connect` refactor. // All uses of this variable should be replaced with `useOnyx`. let recentWaypoints: OnyxTypes.RecentWaypoint[] = []; diff --git a/src/libs/actions/IOU/Duplicate.ts b/src/libs/actions/IOU/Duplicate.ts index 254cc4bb7df1..5464644553f4 100644 --- a/src/libs/actions/IOU/Duplicate.ts +++ b/src/libs/actions/IOU/Duplicate.ts @@ -37,7 +37,7 @@ import { trackExpense, } from '.'; -// TODO: remove `recentWaypoints` from this file [YET TO CREATE ISSUE] +// TODO: remove `recentWaypoints` from this file [https://github.com/Expensify/App/issues/80270] // `recentWaypoints` was moved here temporarily from `src/libs/actions/IOU/index.ts` during the `Deprecate Onyx.connect` refactor. // All uses of this variable should be replaced with `useOnyx`. let recentWaypoints: OnyxTypes.RecentWaypoint[] = []; diff --git a/src/libs/actions/IOU/MoneyRequest.ts b/src/libs/actions/IOU/MoneyRequest.ts index b0820e7d204b..5f96e8de0906 100644 --- a/src/libs/actions/IOU/MoneyRequest.ts +++ b/src/libs/actions/IOU/MoneyRequest.ts @@ -133,7 +133,7 @@ type MoneyRequestStepDistanceNavigationParams = { privateIsArchived?: string; }; -// TODO: remove `recentWaypoints` from this file [YET TO CREATE ISSUE] +// TODO: remove `recentWaypoints` from this file [https://github.com/Expensify/App/issues/80269] // `recentWaypoints` was moved here temporarily from `src/libs/actions/IOU/index.ts` during the `Deprecate Onyx.connect` refactor. // All uses of this variable should be replaced with `useOnyx`. let recentWaypoints: RecentWaypoint[] = []; From b61409fa53ff621fa7840ae96c736825f49b4899 Mon Sep 17 00:00:00 2001 From: Wiktor Gut Date: Sat, 24 Jan 2026 13:22:37 +0100 Subject: [PATCH 05/21] bump max-warnings --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e9ac0bdb32a6..15e5eba3b714 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "test:debug": "TZ=utc NODE_OPTIONS='--inspect-brk --experimental-vm-modules' jest --runInBand", "perf-test": "NODE_OPTIONS=--experimental-vm-modules npx reassure", "typecheck": "NODE_OPTIONS=--max_old_space_size=8192 tsc", - "lint": "NODE_OPTIONS=--max_old_space_size=8192 eslint . --max-warnings=386 --cache --cache-location=node_modules/.cache/eslint --cache-strategy content --concurrency=auto", + "lint": "NODE_OPTIONS=--max_old_space_size=8192 eslint . --max-warnings=387 --cache --cache-location=node_modules/.cache/eslint --cache-strategy content --concurrency=auto", "lint-changed": "NODE_OPTIONS=--max_old_space_size=8192 ./scripts/lintChanged.sh", "check-lazy-loading": "ts-node scripts/checkLazyLoading.ts", "lint-watch": "npx eslint-watch --watch --changed", From ddca2994a649f2cc18f1de5965a8219e4397a00d Mon Sep 17 00:00:00 2001 From: Wiktor Gut Date: Sat, 24 Jan 2026 14:04:15 +0100 Subject: [PATCH 06/21] add tests --- tests/actions/IOUTest.ts | 937 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 936 insertions(+), 1 deletion(-) diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index c3702dc7af30..de7a30f4dd54 100644 --- a/tests/actions/IOUTest.ts +++ b/tests/actions/IOUTest.ts @@ -72,7 +72,7 @@ import * as API from '@src/libs/API'; import DateUtils from '@src/libs/DateUtils'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import type {PersonalDetailsList, Policy, PolicyTagLists, RecentlyUsedTags, RecentWaypoint, Report, ReportNameValuePairs, SearchResults} from '@src/types/onyx'; +import type {PersonalDetailsList, Policy, PolicyTagLists, QuickAction, RecentlyUsedTags, RecentWaypoint, Report, ReportNameValuePairs, SearchResults} from '@src/types/onyx'; import type {Accountant, Attendee, SplitExpense} from '@src/types/onyx/IOU'; import type {CurrentUserPersonalDetails} from '@src/types/onyx/PersonalDetails'; import type {Participant} from '@src/types/onyx/Report'; @@ -930,6 +930,941 @@ describe('actions/IOU', () => { // Accountant role should change to admin expect(policyOnyx?.employeeList?.[accountant.login].role).toBe(CONST.POLICY.ROLE.ADMIN); }); + + // ===================================================== + // UNIT TESTS - Testing trackExpense function behavior + // ===================================================== + + describe('Unit Tests', () => { + it('should create optimistic transaction with correct amount and currency', async () => { + // Given a selfDM report and transaction data + const selfDMReport: Report = { + ...createRandomReport(1, CONST.REPORT.CHAT_TYPE.SELF_DM), + reportID: 'selfDM-unit-1', + }; + const testAmount = 15000; // $150.00 + const testCurrency = 'USD'; + const testMerchant = 'Unit Test Merchant'; + + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${selfDMReport.reportID}`, selfDMReport); + + // When trackExpense is called with specific amount and currency + trackExpense({ + report: selfDMReport, + isDraftPolicy: false, + action: CONST.IOU.ACTION.CREATE, + participantParams: { + payeeEmail: RORY_EMAIL, + payeeAccountID: RORY_ACCOUNT_ID, + participant: {accountID: RORY_ACCOUNT_ID}, + }, + transactionParams: { + amount: testAmount, + currency: testCurrency, + created: format(new Date(), CONST.DATE.FNS_FORMAT_STRING), + merchant: testMerchant, + billable: false, + }, + isASAPSubmitBetaEnabled: false, + currentUserAccountIDParam: RORY_ACCOUNT_ID, + currentUserEmailParam: RORY_EMAIL, + introSelected: undefined, + activePolicyID: undefined, + quickAction: undefined, + recentWaypointsCollection: [], + }); + await waitForBatchedUpdates(); + + // Then transaction should be created with correct values + const transactions = await new Promise>((resolve) => { + const connection = Onyx.connect({ + key: ONYXKEYS.COLLECTION.TRANSACTION, + waitForCollectionCallback: true, + callback: (txns) => { + Onyx.disconnect(connection); + resolve(txns); + }, + }); + }); + + const createdTransaction = Object.values(transactions ?? {}).at(0); + expect(createdTransaction).toBeTruthy(); + // Amount is stored as negative for track expenses + expect(Math.abs(createdTransaction?.amount ?? 0)).toBe(testAmount); + expect(createdTransaction?.currency).toBe(testCurrency); + expect(createdTransaction?.merchant).toBe(testMerchant); + }); + + it('should create actionable track expense whisper for selfDM reports', async () => { + // Given a selfDM report + const selfDMReport: Report = { + ...createRandomReport(1, CONST.REPORT.CHAT_TYPE.SELF_DM), + reportID: 'selfDM-unit-2', + }; + + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${selfDMReport.reportID}`, selfDMReport); + + // When trackExpense is called on selfDM + trackExpense({ + report: selfDMReport, + isDraftPolicy: false, + action: CONST.IOU.ACTION.CREATE, + participantParams: { + payeeEmail: RORY_EMAIL, + payeeAccountID: RORY_ACCOUNT_ID, + participant: {accountID: RORY_ACCOUNT_ID}, + }, + transactionParams: { + amount: 5000, + currency: 'USD', + created: format(new Date(), CONST.DATE.FNS_FORMAT_STRING), + merchant: 'Test Merchant', + billable: false, + }, + isASAPSubmitBetaEnabled: false, + currentUserAccountIDParam: RORY_ACCOUNT_ID, + currentUserEmailParam: RORY_EMAIL, + introSelected: undefined, + activePolicyID: undefined, + quickAction: undefined, + recentWaypointsCollection: [], + }); + await waitForBatchedUpdates(); + + // Then an actionable track expense whisper should be created + const reportActions = await new Promise>((resolve) => { + const connection = Onyx.connect({ + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${selfDMReport.reportID}`, + callback: (actions) => { + Onyx.disconnect(connection); + resolve(actions); + }, + }); + }); + + const actionableWhisper = Object.values(reportActions ?? {}).find((action) => isActionableTrackExpense(action)); + expect(actionableWhisper).toBeTruthy(); + }); + + it('should set correct tax fields when tax parameters are provided', async () => { + // Given a selfDM report and transaction with tax + const selfDMReport: Report = { + ...createRandomReport(1, CONST.REPORT.CHAT_TYPE.SELF_DM), + reportID: 'selfDM-unit-3', + }; + const testTaxCode = 'TAX_CODE_1'; + const testTaxAmount = 500; // $5.00 tax + + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${selfDMReport.reportID}`, selfDMReport); + + // When trackExpense is called with tax parameters + trackExpense({ + report: selfDMReport, + isDraftPolicy: false, + action: CONST.IOU.ACTION.CREATE, + participantParams: { + payeeEmail: RORY_EMAIL, + payeeAccountID: RORY_ACCOUNT_ID, + participant: {accountID: RORY_ACCOUNT_ID}, + }, + transactionParams: { + amount: 10000, + currency: 'USD', + created: format(new Date(), CONST.DATE.FNS_FORMAT_STRING), + merchant: 'Tax Test Merchant', + taxCode: testTaxCode, + taxAmount: testTaxAmount, + billable: false, + }, + isASAPSubmitBetaEnabled: false, + currentUserAccountIDParam: RORY_ACCOUNT_ID, + currentUserEmailParam: RORY_EMAIL, + introSelected: undefined, + activePolicyID: undefined, + quickAction: undefined, + recentWaypointsCollection: [], + }); + await waitForBatchedUpdates(); + + // Then transaction should have correct tax fields + const transactions = await new Promise>((resolve) => { + const connection = Onyx.connect({ + key: ONYXKEYS.COLLECTION.TRANSACTION, + waitForCollectionCallback: true, + callback: (txns) => { + Onyx.disconnect(connection); + resolve(txns); + }, + }); + }); + + const createdTransaction = Object.values(transactions ?? {}).at(0); + expect(createdTransaction?.taxCode).toBe(testTaxCode); + expect(createdTransaction?.taxAmount).toBe(testTaxAmount); + }); + + it('should set billable and reimbursable flags correctly', async () => { + // Given a selfDM report + const selfDMReport: Report = { + ...createRandomReport(1, CONST.REPORT.CHAT_TYPE.SELF_DM), + reportID: 'selfDM-unit-4', + }; + + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${selfDMReport.reportID}`, selfDMReport); + + // When trackExpense is called with billable=true and reimbursable=true + trackExpense({ + report: selfDMReport, + isDraftPolicy: false, + action: CONST.IOU.ACTION.CREATE, + participantParams: { + payeeEmail: RORY_EMAIL, + payeeAccountID: RORY_ACCOUNT_ID, + participant: {accountID: RORY_ACCOUNT_ID}, + }, + transactionParams: { + amount: 7500, + currency: 'USD', + created: format(new Date(), CONST.DATE.FNS_FORMAT_STRING), + merchant: 'Billable Test', + billable: true, + reimbursable: true, + }, + isASAPSubmitBetaEnabled: false, + currentUserAccountIDParam: RORY_ACCOUNT_ID, + currentUserEmailParam: RORY_EMAIL, + introSelected: undefined, + activePolicyID: undefined, + quickAction: undefined, + recentWaypointsCollection: [], + }); + await waitForBatchedUpdates(); + + // Then transaction should have correct billable and reimbursable flags + const transactions = await new Promise>((resolve) => { + const connection = Onyx.connect({ + key: ONYXKEYS.COLLECTION.TRANSACTION, + waitForCollectionCallback: true, + callback: (txns) => { + Onyx.disconnect(connection); + resolve(txns); + }, + }); + }); + + const createdTransaction = Object.values(transactions ?? {}).at(0); + expect(createdTransaction?.billable).toBe(true); + expect(createdTransaction?.reimbursable).toBe(true); + }); + }); + + // ===================================================== + // FUNCTIONAL TESTS - Testing complete workflows + // ===================================================== + + describe('Functional Tests', () => { + it('should complete full track expense flow: create -> categorize -> submit to workspace', async () => { + // Given a selfDM report, policy, and expense chat + const selfDMReport: Report = { + ...createRandomReport(1, CONST.REPORT.CHAT_TYPE.SELF_DM), + reportID: 'selfDM-func-1', + }; + const policy = createRandomPolicy(1); + const policyExpenseChat: Report = { + ...createRandomReport(2, CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT), + reportID: 'expense-chat-func-1', + policyID: policy.id, + type: CONST.REPORT.TYPE.CHAT, + isOwnPolicyExpenseChat: true, + }; + const policyCategories = createRandomPolicyCategories(3); + const selectedCategory = Object.keys(policyCategories).at(0) ?? ''; + + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${selfDMReport.reportID}`, selfDMReport); + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${policyExpenseChat.reportID}`, policyExpenseChat); + await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${policy.id}`, policy); + + // STEP 1: Create tracked expense in selfDM + trackExpense({ + report: selfDMReport, + isDraftPolicy: false, + action: CONST.IOU.ACTION.CREATE, + participantParams: { + payeeEmail: RORY_EMAIL, + payeeAccountID: RORY_ACCOUNT_ID, + participant: {accountID: RORY_ACCOUNT_ID}, + }, + transactionParams: { + amount: 25000, + currency: 'USD', + created: format(new Date(), CONST.DATE.FNS_FORMAT_STRING), + merchant: 'Functional Test Restaurant', + billable: false, + }, + isASAPSubmitBetaEnabled: false, + currentUserAccountIDParam: RORY_ACCOUNT_ID, + currentUserEmailParam: RORY_EMAIL, + introSelected: undefined, + activePolicyID: undefined, + quickAction: undefined, + recentWaypointsCollection: [], + }); + await waitForBatchedUpdates(); + + // Verify initial expense was created + const selfDMReportActions = await new Promise>((resolve) => { + const connection = Onyx.connect({ + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${selfDMReport.reportID}`, + callback: (actions) => { + Onyx.disconnect(connection); + resolve(actions); + }, + }); + }); + + expect(Object.values(selfDMReportActions ?? {}).length).toBe(2); + const moneyRequestAction = Object.values(selfDMReportActions ?? {}).find((action) => isMoneyRequestAction(action)); + const actionableWhisper = Object.values(selfDMReportActions ?? {}).find((action) => isActionableTrackExpense(action)); + expect(moneyRequestAction).toBeTruthy(); + expect(actionableWhisper).toBeTruthy(); + + // Get the created transaction + const transactions = await new Promise>((resolve) => { + const connection = Onyx.connect({ + key: ONYXKEYS.COLLECTION.TRANSACTION, + waitForCollectionCallback: true, + callback: (txns) => { + Onyx.disconnect(connection); + resolve(txns); + }, + }); + }); + const createdTransaction = Object.values(transactions ?? {}).at(0); + expect(createdTransaction).toBeTruthy(); + + // STEP 2: Create draft for categorization + createDraftTransactionAndNavigateToParticipantSelector( + createdTransaction?.transactionID, + selfDMReport.reportID, + CONST.IOU.ACTION.CATEGORIZE, + actionableWhisper?.reportActionID, + {choice: CONST.ONBOARDING_CHOICES.MANAGE_TEAM}, + {}, + undefined, + ); + await waitForBatchedUpdates(); + + // Verify draft was created + const transactionDrafts = await new Promise>((resolve) => { + const connection = Onyx.connect({ + key: ONYXKEYS.COLLECTION.TRANSACTION_DRAFT, + waitForCollectionCallback: true, + callback: (drafts) => { + Onyx.disconnect(connection); + resolve(drafts); + }, + }); + }); + const draftTransaction = transactionDrafts?.[`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${createdTransaction?.transactionID}`]; + expect(draftTransaction).toBeTruthy(); + + // STEP 3: Categorize and submit to workspace + trackExpense({ + report: policyExpenseChat, + isDraftPolicy: false, + action: CONST.IOU.ACTION.CATEGORIZE, + participantParams: { + payeeEmail: RORY_EMAIL, + payeeAccountID: RORY_ACCOUNT_ID, + participant: {reportID: policyExpenseChat.reportID, isPolicyExpenseChat: true}, + }, + policyParams: { + policy, + policyCategories, + }, + transactionParams: { + amount: draftTransaction?.amount ?? 25000, + currency: draftTransaction?.currency ?? 'USD', + created: format(new Date(), CONST.DATE.FNS_FORMAT_STRING), + merchant: draftTransaction?.merchant ?? 'Functional Test Restaurant', + category: selectedCategory, + actionableWhisperReportActionID: draftTransaction?.actionableWhisperReportActionID, + linkedTrackedExpenseReportAction: moneyRequestAction, + linkedTrackedExpenseReportID: selfDMReport.reportID, + }, + isASAPSubmitBetaEnabled: false, + currentUserAccountIDParam: RORY_ACCOUNT_ID, + currentUserEmailParam: RORY_EMAIL, + introSelected: undefined, + activePolicyID: undefined, + quickAction: undefined, + recentWaypointsCollection: [], + }); + await waitForBatchedUpdates(); + + // Verify transaction was categorized + const finalTransactions = await new Promise>((resolve) => { + const connection = Onyx.connect({ + key: ONYXKEYS.COLLECTION.TRANSACTION, + waitForCollectionCallback: true, + callback: (txns) => { + Onyx.disconnect(connection); + resolve(txns); + }, + }); + }); + const categorizedTransaction = finalTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${createdTransaction?.transactionID}`]; + expect(categorizedTransaction?.category).toBe(selectedCategory); + }); + + it('should handle expense with attendees correctly', async () => { + // Given a selfDM report with attendees data + const selfDMReport: Report = { + ...createRandomReport(1, CONST.REPORT.CHAT_TYPE.SELF_DM), + reportID: 'selfDM-func-2', + }; + const testAttendees = [ + {email: 'attendee1@test.com', displayName: 'Attendee One', avatarUrl: ''}, + {email: 'attendee2@test.com', displayName: 'Attendee Two', avatarUrl: ''}, + ]; + + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${selfDMReport.reportID}`, selfDMReport); + + // When trackExpense is called with attendees + trackExpense({ + report: selfDMReport, + isDraftPolicy: false, + action: CONST.IOU.ACTION.CREATE, + participantParams: { + payeeEmail: RORY_EMAIL, + payeeAccountID: RORY_ACCOUNT_ID, + participant: {accountID: RORY_ACCOUNT_ID}, + }, + transactionParams: { + amount: 30000, + currency: 'USD', + created: format(new Date(), CONST.DATE.FNS_FORMAT_STRING), + merchant: 'Team Lunch', + attendees: testAttendees, + billable: false, + }, + isASAPSubmitBetaEnabled: false, + currentUserAccountIDParam: RORY_ACCOUNT_ID, + currentUserEmailParam: RORY_EMAIL, + introSelected: undefined, + activePolicyID: undefined, + quickAction: undefined, + recentWaypointsCollection: [], + }); + await waitForBatchedUpdates(); + + // Then transaction should have attendees + const transactions = await new Promise>((resolve) => { + const connection = Onyx.connect({ + key: ONYXKEYS.COLLECTION.TRANSACTION, + waitForCollectionCallback: true, + callback: (txns) => { + Onyx.disconnect(connection); + resolve(txns); + }, + }); + }); + + const createdTransaction = Object.values(transactions ?? {}).at(0); + expect(createdTransaction?.comment?.attendees).toHaveLength(2); + expect(createdTransaction?.comment?.attendees?.at(0)?.email).toBe('attendee1@test.com'); + }); + + it('should update quick action when tracking expense to policy expense chat', async () => { + // Given a policy expense chat + const policy = createRandomPolicy(1); + const policyExpenseChat: Report = { + ...createRandomReport(1, CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT), + reportID: 'expense-chat-func-2', + policyID: policy.id, + type: CONST.REPORT.TYPE.CHAT, + isOwnPolicyExpenseChat: true, + }; + + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${policyExpenseChat.reportID}`, policyExpenseChat); + await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${policy.id}`, policy); + + // When trackExpense is called on policy expense chat + trackExpense({ + report: policyExpenseChat, + isDraftPolicy: false, + action: CONST.IOU.ACTION.CREATE, + participantParams: { + payeeEmail: RORY_EMAIL, + payeeAccountID: RORY_ACCOUNT_ID, + participant: {reportID: policyExpenseChat.reportID, isPolicyExpenseChat: true}, + }, + policyParams: { + policy, + }, + transactionParams: { + amount: 12000, + currency: 'USD', + created: format(new Date(), CONST.DATE.FNS_FORMAT_STRING), + merchant: 'Quick Action Test', + billable: false, + }, + isASAPSubmitBetaEnabled: false, + currentUserAccountIDParam: RORY_ACCOUNT_ID, + currentUserEmailParam: RORY_EMAIL, + introSelected: undefined, + activePolicyID: undefined, + quickAction: undefined, + recentWaypointsCollection: [], + }); + await waitForBatchedUpdates(); + + // Then quick action should be updated + const quickAction = await new Promise>((resolve) => { + const connection = Onyx.connect({ + key: ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE, + callback: (qa) => { + Onyx.disconnect(connection); + resolve(qa); + }, + }); + }); + + expect(quickAction?.chatReportID).toBe(policyExpenseChat.reportID); + }); + }); + + // ===================================================== + // QA TESTS - Testing edge cases and error scenarios + // ===================================================== + + describe('QA Tests', () => { + it('should handle tracking expense without merchant gracefully', async () => { + // Given a selfDM report and no merchant + const selfDMReport: Report = { + ...createRandomReport(1, CONST.REPORT.CHAT_TYPE.SELF_DM), + reportID: 'selfDM-qa-1', + }; + + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${selfDMReport.reportID}`, selfDMReport); + + // When trackExpense is called without merchant + trackExpense({ + report: selfDMReport, + isDraftPolicy: false, + action: CONST.IOU.ACTION.CREATE, + participantParams: { + payeeEmail: RORY_EMAIL, + payeeAccountID: RORY_ACCOUNT_ID, + participant: {accountID: RORY_ACCOUNT_ID}, + }, + transactionParams: { + amount: 5000, + currency: 'USD', + created: format(new Date(), CONST.DATE.FNS_FORMAT_STRING), + merchant: '', // Empty merchant + billable: false, + }, + isASAPSubmitBetaEnabled: false, + currentUserAccountIDParam: RORY_ACCOUNT_ID, + currentUserEmailParam: RORY_EMAIL, + introSelected: undefined, + activePolicyID: undefined, + quickAction: undefined, + recentWaypointsCollection: [], + }); + await waitForBatchedUpdates(); + + // Then transaction should still be created + const transactions = await new Promise>((resolve) => { + const connection = Onyx.connect({ + key: ONYXKEYS.COLLECTION.TRANSACTION, + waitForCollectionCallback: true, + callback: (txns) => { + Onyx.disconnect(connection); + resolve(txns); + }, + }); + }); + + expect(Object.values(transactions ?? {}).length).toBeGreaterThan(0); + }); + + it('should handle zero amount expense', async () => { + // Given a selfDM report + const selfDMReport: Report = { + ...createRandomReport(1, CONST.REPORT.CHAT_TYPE.SELF_DM), + reportID: 'selfDM-qa-2', + }; + + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${selfDMReport.reportID}`, selfDMReport); + + // When trackExpense is called with zero amount + trackExpense({ + report: selfDMReport, + isDraftPolicy: false, + action: CONST.IOU.ACTION.CREATE, + participantParams: { + payeeEmail: RORY_EMAIL, + payeeAccountID: RORY_ACCOUNT_ID, + participant: {accountID: RORY_ACCOUNT_ID}, + }, + transactionParams: { + amount: 0, + currency: 'USD', + created: format(new Date(), CONST.DATE.FNS_FORMAT_STRING), + merchant: 'Zero Amount Test', + billable: false, + }, + isASAPSubmitBetaEnabled: false, + currentUserAccountIDParam: RORY_ACCOUNT_ID, + currentUserEmailParam: RORY_EMAIL, + introSelected: undefined, + activePolicyID: undefined, + quickAction: undefined, + recentWaypointsCollection: [], + }); + await waitForBatchedUpdates(); + + // Then transaction should be created with zero amount + const transactions = await new Promise>((resolve) => { + const connection = Onyx.connect({ + key: ONYXKEYS.COLLECTION.TRANSACTION, + waitForCollectionCallback: true, + callback: (txns) => { + Onyx.disconnect(connection); + resolve(txns); + }, + }); + }); + + const createdTransaction = Object.values(transactions ?? {}).at(0); + // trackExpense negates the amount, so 0 becomes -0, defaults to 1 to be able to use Math.abs + expect(createdTransaction).toBeTruthy(); + expect(Object.is(Math.abs(createdTransaction?.amount ?? 1), 0)).toBe(true); + }); + + it('should handle different currency codes correctly', async () => { + // Given a selfDM report + const selfDMReport: Report = { + ...createRandomReport(1, CONST.REPORT.CHAT_TYPE.SELF_DM), + reportID: 'selfDM-qa-3', + }; + const testCurrency = 'EUR'; + + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${selfDMReport.reportID}`, selfDMReport); + + // When trackExpense is called with EUR currency + trackExpense({ + report: selfDMReport, + isDraftPolicy: false, + action: CONST.IOU.ACTION.CREATE, + participantParams: { + payeeEmail: RORY_EMAIL, + payeeAccountID: RORY_ACCOUNT_ID, + participant: {accountID: RORY_ACCOUNT_ID}, + }, + transactionParams: { + amount: 8500, + currency: testCurrency, + created: format(new Date(), CONST.DATE.FNS_FORMAT_STRING), + merchant: 'European Merchant', + billable: false, + }, + isASAPSubmitBetaEnabled: false, + currentUserAccountIDParam: RORY_ACCOUNT_ID, + currentUserEmailParam: RORY_EMAIL, + introSelected: undefined, + activePolicyID: undefined, + quickAction: undefined, + recentWaypointsCollection: [], + }); + await waitForBatchedUpdates(); + + // Then transaction should have correct currency + const transactions = await new Promise>((resolve) => { + const connection = Onyx.connect({ + key: ONYXKEYS.COLLECTION.TRANSACTION, + waitForCollectionCallback: true, + callback: (txns) => { + Onyx.disconnect(connection); + resolve(txns); + }, + }); + }); + + const createdTransaction = Object.values(transactions ?? {}).at(0); + expect(createdTransaction?.currency).toBe(testCurrency); + }); + + it('should create optimistic selfDM report when none exists', async () => { + // Given no selfDM report exists (cleared Onyx) + // The function should handle this by creating an optimistic selfDM + + // When trackExpense is called with undefined report (will trigger selfDM creation) + trackExpense({ + report: undefined, + isDraftPolicy: false, + action: CONST.IOU.ACTION.CREATE, + participantParams: { + payeeEmail: RORY_EMAIL, + payeeAccountID: RORY_ACCOUNT_ID, + participant: {accountID: RORY_ACCOUNT_ID}, + }, + transactionParams: { + amount: 3000, + currency: 'USD', + created: format(new Date(), CONST.DATE.FNS_FORMAT_STRING), + merchant: 'Optimistic SelfDM Test', + billable: false, + }, + isASAPSubmitBetaEnabled: false, + currentUserAccountIDParam: RORY_ACCOUNT_ID, + currentUserEmailParam: RORY_EMAIL, + introSelected: undefined, + activePolicyID: undefined, + quickAction: undefined, + recentWaypointsCollection: [], + }); + await waitForBatchedUpdates(); + + // Then a selfDM report should be created optimistically + const reports = await new Promise>((resolve) => { + const connection = Onyx.connect({ + key: ONYXKEYS.COLLECTION.REPORT, + waitForCollectionCallback: true, + callback: (rpts) => { + Onyx.disconnect(connection); + resolve(rpts); + }, + }); + }); + + const selfDMReports = Object.values(reports ?? {}).filter((r) => r?.chatType === CONST.REPORT.CHAT_TYPE.SELF_DM); + expect(selfDMReports.length).toBeGreaterThan(0); + }); + + it('should handle API failure gracefully with failure data', async () => { + // Given a selfDM report + const selfDMReport: Report = { + ...createRandomReport(1, CONST.REPORT.CHAT_TYPE.SELF_DM), + reportID: 'selfDM-qa-5', + }; + + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${selfDMReport.reportID}`, selfDMReport); + + // Simulate API failure + mockFetch?.fail?.(); + + // When trackExpense is called + trackExpense({ + report: selfDMReport, + isDraftPolicy: false, + action: CONST.IOU.ACTION.CREATE, + participantParams: { + payeeEmail: RORY_EMAIL, + payeeAccountID: RORY_ACCOUNT_ID, + participant: {accountID: RORY_ACCOUNT_ID}, + }, + transactionParams: { + amount: 5000, + currency: 'USD', + created: format(new Date(), CONST.DATE.FNS_FORMAT_STRING), + merchant: 'Failure Test', + billable: false, + }, + isASAPSubmitBetaEnabled: false, + currentUserAccountIDParam: RORY_ACCOUNT_ID, + currentUserEmailParam: RORY_EMAIL, + introSelected: undefined, + activePolicyID: undefined, + quickAction: undefined, + recentWaypointsCollection: [], + }); + await waitForBatchedUpdates(); + + // Then optimistic data should still be created initially + const transactions = await new Promise>((resolve) => { + const connection = Onyx.connect({ + key: ONYXKEYS.COLLECTION.TRANSACTION, + waitForCollectionCallback: true, + callback: (txns) => { + Onyx.disconnect(connection); + resolve(txns); + }, + }); + }); + + // Transaction should exist (optimistically) + expect(Object.values(transactions ?? {}).length).toBeGreaterThan(0); + + // Reset mock to succeed for other tests + mockFetch?.succeed?.(); + }); + + it('should handle category and tag together correctly', async () => { + // Given a selfDM report with category and tag + const selfDMReport: Report = { + ...createRandomReport(1, CONST.REPORT.CHAT_TYPE.SELF_DM), + reportID: 'selfDM-qa-6', + }; + const testCategory = 'Travel'; + const testTag = 'Business Trip'; + + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${selfDMReport.reportID}`, selfDMReport); + + // When trackExpense is called with category and tag + trackExpense({ + report: selfDMReport, + isDraftPolicy: false, + action: CONST.IOU.ACTION.CREATE, + participantParams: { + payeeEmail: RORY_EMAIL, + payeeAccountID: RORY_ACCOUNT_ID, + participant: {accountID: RORY_ACCOUNT_ID}, + }, + transactionParams: { + amount: 50000, + currency: 'USD', + created: format(new Date(), CONST.DATE.FNS_FORMAT_STRING), + merchant: 'Airline', + category: testCategory, + tag: testTag, + billable: false, + }, + isASAPSubmitBetaEnabled: false, + currentUserAccountIDParam: RORY_ACCOUNT_ID, + currentUserEmailParam: RORY_EMAIL, + introSelected: undefined, + activePolicyID: undefined, + quickAction: undefined, + recentWaypointsCollection: [], + }); + await waitForBatchedUpdates(); + + // Then transaction should have correct category and tag + const transactions = await new Promise>((resolve) => { + const connection = Onyx.connect({ + key: ONYXKEYS.COLLECTION.TRANSACTION, + waitForCollectionCallback: true, + callback: (txns) => { + Onyx.disconnect(connection); + resolve(txns); + }, + }); + }); + + const createdTransaction = Object.values(transactions ?? {}).at(0); + expect(createdTransaction?.category).toBe(testCategory); + expect(createdTransaction?.tag).toBe(testTag); + }); + + it('should handle very large expense amounts', async () => { + // Given a selfDM report + const selfDMReport: Report = { + ...createRandomReport(1, CONST.REPORT.CHAT_TYPE.SELF_DM), + reportID: 'selfDM-qa-7', + }; + const largeAmount = 99999999; // Large amount in cents + + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${selfDMReport.reportID}`, selfDMReport); + + // When trackExpense is called with very large amount + trackExpense({ + report: selfDMReport, + isDraftPolicy: false, + action: CONST.IOU.ACTION.CREATE, + participantParams: { + payeeEmail: RORY_EMAIL, + payeeAccountID: RORY_ACCOUNT_ID, + participant: {accountID: RORY_ACCOUNT_ID}, + }, + transactionParams: { + amount: largeAmount, + currency: 'USD', + created: format(new Date(), CONST.DATE.FNS_FORMAT_STRING), + merchant: 'Large Purchase', + billable: false, + }, + isASAPSubmitBetaEnabled: false, + currentUserAccountIDParam: RORY_ACCOUNT_ID, + currentUserEmailParam: RORY_EMAIL, + introSelected: undefined, + activePolicyID: undefined, + quickAction: undefined, + recentWaypointsCollection: [], + }); + await waitForBatchedUpdates(); + + // Then transaction should handle large amount correctly + const transactions = await new Promise>((resolve) => { + const connection = Onyx.connect({ + key: ONYXKEYS.COLLECTION.TRANSACTION, + waitForCollectionCallback: true, + callback: (txns) => { + Onyx.disconnect(connection); + resolve(txns); + }, + }); + }); + + const createdTransaction = Object.values(transactions ?? {}).at(0); + expect(Math.abs(createdTransaction?.amount ?? 0)).toBe(largeAmount); + }); + + it('should handle expense with special characters in merchant name', async () => { + // Given a selfDM report + const selfDMReport: Report = { + ...createRandomReport(1, CONST.REPORT.CHAT_TYPE.SELF_DM), + reportID: 'selfDM-qa-8', + }; + const specialMerchant = "McDonald's & Café ñ 日本語"; + + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${selfDMReport.reportID}`, selfDMReport); + + // When trackExpense is called with special characters in merchant + trackExpense({ + report: selfDMReport, + isDraftPolicy: false, + action: CONST.IOU.ACTION.CREATE, + participantParams: { + payeeEmail: RORY_EMAIL, + payeeAccountID: RORY_ACCOUNT_ID, + participant: {accountID: RORY_ACCOUNT_ID}, + }, + transactionParams: { + amount: 1500, + currency: 'USD', + created: format(new Date(), CONST.DATE.FNS_FORMAT_STRING), + merchant: specialMerchant, + billable: false, + }, + isASAPSubmitBetaEnabled: false, + currentUserAccountIDParam: RORY_ACCOUNT_ID, + currentUserEmailParam: RORY_EMAIL, + introSelected: undefined, + activePolicyID: undefined, + quickAction: undefined, + recentWaypointsCollection: [], + }); + await waitForBatchedUpdates(); + + // Then transaction should preserve special characters + const transactions = await new Promise>((resolve) => { + const connection = Onyx.connect({ + key: ONYXKEYS.COLLECTION.TRANSACTION, + waitForCollectionCallback: true, + callback: (txns) => { + Onyx.disconnect(connection); + resolve(txns); + }, + }); + }); + + const createdTransaction = Object.values(transactions ?? {}).at(0); + expect(createdTransaction?.merchant).toBe(specialMerchant); + }); + }); }); describe('createDraftTransactionAndNavigateToParticipantSelector', () => { From cef14928b6d49edef324d3ba253fa955eaf1ccd2 Mon Sep 17 00:00:00 2001 From: Wiktor Gut Date: Sat, 24 Jan 2026 14:19:02 +0100 Subject: [PATCH 07/21] fix spell check --- tests/actions/IOUTest.ts | 52 ++++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index de7a30f4dd54..4e219e1c5e14 100644 --- a/tests/actions/IOUTest.ts +++ b/tests/actions/IOUTest.ts @@ -980,9 +980,9 @@ describe('actions/IOU', () => { const connection = Onyx.connect({ key: ONYXKEYS.COLLECTION.TRANSACTION, waitForCollectionCallback: true, - callback: (txns) => { + callback: (transactions) => { Onyx.disconnect(connection); - resolve(txns); + resolve(transactions); }, }); }); @@ -1091,9 +1091,9 @@ describe('actions/IOU', () => { const connection = Onyx.connect({ key: ONYXKEYS.COLLECTION.TRANSACTION, waitForCollectionCallback: true, - callback: (txns) => { + callback: (transactions) => { Onyx.disconnect(connection); - resolve(txns); + resolve(transactions); }, }); }); @@ -1145,9 +1145,9 @@ describe('actions/IOU', () => { const connection = Onyx.connect({ key: ONYXKEYS.COLLECTION.TRANSACTION, waitForCollectionCallback: true, - callback: (txns) => { + callback: (transactions) => { Onyx.disconnect(connection); - resolve(txns); + resolve(transactions); }, }); }); @@ -1233,9 +1233,9 @@ describe('actions/IOU', () => { const connection = Onyx.connect({ key: ONYXKEYS.COLLECTION.TRANSACTION, waitForCollectionCallback: true, - callback: (txns) => { + callback: (transactions) => { Onyx.disconnect(connection); - resolve(txns); + resolve(transactions); }, }); }); @@ -1307,9 +1307,9 @@ describe('actions/IOU', () => { const connection = Onyx.connect({ key: ONYXKEYS.COLLECTION.TRANSACTION, waitForCollectionCallback: true, - callback: (txns) => { + callback: (transactions) => { Onyx.disconnect(connection); - resolve(txns); + resolve(transactions); }, }); }); @@ -1363,9 +1363,9 @@ describe('actions/IOU', () => { const connection = Onyx.connect({ key: ONYXKEYS.COLLECTION.TRANSACTION, waitForCollectionCallback: true, - callback: (txns) => { + callback: (transactions) => { Onyx.disconnect(connection); - resolve(txns); + resolve(transactions); }, }); }); @@ -1480,9 +1480,9 @@ describe('actions/IOU', () => { const connection = Onyx.connect({ key: ONYXKEYS.COLLECTION.TRANSACTION, waitForCollectionCallback: true, - callback: (txns) => { + callback: (transactions) => { Onyx.disconnect(connection); - resolve(txns); + resolve(transactions); }, }); }); @@ -1531,9 +1531,9 @@ describe('actions/IOU', () => { const connection = Onyx.connect({ key: ONYXKEYS.COLLECTION.TRANSACTION, waitForCollectionCallback: true, - callback: (txns) => { + callback: (transactions) => { Onyx.disconnect(connection); - resolve(txns); + resolve(transactions); }, }); }); @@ -1586,9 +1586,9 @@ describe('actions/IOU', () => { const connection = Onyx.connect({ key: ONYXKEYS.COLLECTION.TRANSACTION, waitForCollectionCallback: true, - callback: (txns) => { + callback: (transactions) => { Onyx.disconnect(connection); - resolve(txns); + resolve(transactions); }, }); }); @@ -1688,9 +1688,9 @@ describe('actions/IOU', () => { const connection = Onyx.connect({ key: ONYXKEYS.COLLECTION.TRANSACTION, waitForCollectionCallback: true, - callback: (txns) => { + callback: (transactions) => { Onyx.disconnect(connection); - resolve(txns); + resolve(transactions); }, }); }); @@ -1747,9 +1747,9 @@ describe('actions/IOU', () => { const connection = Onyx.connect({ key: ONYXKEYS.COLLECTION.TRANSACTION, waitForCollectionCallback: true, - callback: (txns) => { + callback: (transactions) => { Onyx.disconnect(connection); - resolve(txns); + resolve(transactions); }, }); }); @@ -1801,9 +1801,9 @@ describe('actions/IOU', () => { const connection = Onyx.connect({ key: ONYXKEYS.COLLECTION.TRANSACTION, waitForCollectionCallback: true, - callback: (txns) => { + callback: (transactions) => { Onyx.disconnect(connection); - resolve(txns); + resolve(transactions); }, }); }); @@ -1854,9 +1854,9 @@ describe('actions/IOU', () => { const connection = Onyx.connect({ key: ONYXKEYS.COLLECTION.TRANSACTION, waitForCollectionCallback: true, - callback: (txns) => { + callback: (transactions) => { Onyx.disconnect(connection); - resolve(txns); + resolve(transactions); }, }); }); From e6e6274d9c42481062e0ed8859301d5441f54ee5 Mon Sep 17 00:00:00 2001 From: Wiktor Gut Date: Sat, 24 Jan 2026 14:21:09 +0100 Subject: [PATCH 08/21] fix spell check 2 --- tests/actions/IOUTest.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index 4e219e1c5e14..cdf0e9f4b155 100644 --- a/tests/actions/IOUTest.ts +++ b/tests/actions/IOUTest.ts @@ -1633,9 +1633,9 @@ describe('actions/IOU', () => { const connection = Onyx.connect({ key: ONYXKEYS.COLLECTION.REPORT, waitForCollectionCallback: true, - callback: (rpts) => { + callback: (reports) => { Onyx.disconnect(connection); - resolve(rpts); + resolve(reports); }, }); }); From f6cb410999448da0a14705a223f4dd455778cba3 Mon Sep 17 00:00:00 2001 From: Wiktor Gut Date: Sat, 24 Jan 2026 15:30:31 +0100 Subject: [PATCH 09/21] fix lint --- tests/actions/IOUTest.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index cdf0e9f4b155..863e3950c40b 100644 --- a/tests/actions/IOUTest.ts +++ b/tests/actions/IOUTest.ts @@ -1633,9 +1633,9 @@ describe('actions/IOU', () => { const connection = Onyx.connect({ key: ONYXKEYS.COLLECTION.REPORT, waitForCollectionCallback: true, - callback: (reports) => { + callback: (val) => { Onyx.disconnect(connection); - resolve(reports); + resolve(val); }, }); }); From 0547f89756ee7c6859ea1fe4b34648941b4bc1a5 Mon Sep 17 00:00:00 2001 From: Wiktor Gut Date: Sat, 24 Jan 2026 15:36:56 +0100 Subject: [PATCH 10/21] fix lint transactions --- tests/actions/IOUTest.ts | 52 ++++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index 863e3950c40b..09ae6003dddf 100644 --- a/tests/actions/IOUTest.ts +++ b/tests/actions/IOUTest.ts @@ -980,9 +980,9 @@ describe('actions/IOU', () => { const connection = Onyx.connect({ key: ONYXKEYS.COLLECTION.TRANSACTION, waitForCollectionCallback: true, - callback: (transactions) => { + callback: (val) => { Onyx.disconnect(connection); - resolve(transactions); + resolve(val); }, }); }); @@ -1091,9 +1091,9 @@ describe('actions/IOU', () => { const connection = Onyx.connect({ key: ONYXKEYS.COLLECTION.TRANSACTION, waitForCollectionCallback: true, - callback: (transactions) => { + callback: (val) => { Onyx.disconnect(connection); - resolve(transactions); + resolve(val); }, }); }); @@ -1145,9 +1145,9 @@ describe('actions/IOU', () => { const connection = Onyx.connect({ key: ONYXKEYS.COLLECTION.TRANSACTION, waitForCollectionCallback: true, - callback: (transactions) => { + callback: (val) => { Onyx.disconnect(connection); - resolve(transactions); + resolve(val); }, }); }); @@ -1233,9 +1233,9 @@ describe('actions/IOU', () => { const connection = Onyx.connect({ key: ONYXKEYS.COLLECTION.TRANSACTION, waitForCollectionCallback: true, - callback: (transactions) => { + callback: (val) => { Onyx.disconnect(connection); - resolve(transactions); + resolve(val); }, }); }); @@ -1307,9 +1307,9 @@ describe('actions/IOU', () => { const connection = Onyx.connect({ key: ONYXKEYS.COLLECTION.TRANSACTION, waitForCollectionCallback: true, - callback: (transactions) => { + callback: (val) => { Onyx.disconnect(connection); - resolve(transactions); + resolve(val); }, }); }); @@ -1363,9 +1363,9 @@ describe('actions/IOU', () => { const connection = Onyx.connect({ key: ONYXKEYS.COLLECTION.TRANSACTION, waitForCollectionCallback: true, - callback: (transactions) => { + callback: (val) => { Onyx.disconnect(connection); - resolve(transactions); + resolve(val); }, }); }); @@ -1480,9 +1480,9 @@ describe('actions/IOU', () => { const connection = Onyx.connect({ key: ONYXKEYS.COLLECTION.TRANSACTION, waitForCollectionCallback: true, - callback: (transactions) => { + callback: (val) => { Onyx.disconnect(connection); - resolve(transactions); + resolve(val); }, }); }); @@ -1531,9 +1531,9 @@ describe('actions/IOU', () => { const connection = Onyx.connect({ key: ONYXKEYS.COLLECTION.TRANSACTION, waitForCollectionCallback: true, - callback: (transactions) => { + callback: (val) => { Onyx.disconnect(connection); - resolve(transactions); + resolve(val); }, }); }); @@ -1586,9 +1586,9 @@ describe('actions/IOU', () => { const connection = Onyx.connect({ key: ONYXKEYS.COLLECTION.TRANSACTION, waitForCollectionCallback: true, - callback: (transactions) => { + callback: (val) => { Onyx.disconnect(connection); - resolve(transactions); + resolve(val); }, }); }); @@ -1688,9 +1688,9 @@ describe('actions/IOU', () => { const connection = Onyx.connect({ key: ONYXKEYS.COLLECTION.TRANSACTION, waitForCollectionCallback: true, - callback: (transactions) => { + callback: (val) => { Onyx.disconnect(connection); - resolve(transactions); + resolve(val); }, }); }); @@ -1747,9 +1747,9 @@ describe('actions/IOU', () => { const connection = Onyx.connect({ key: ONYXKEYS.COLLECTION.TRANSACTION, waitForCollectionCallback: true, - callback: (transactions) => { + callback: (val) => { Onyx.disconnect(connection); - resolve(transactions); + resolve(val); }, }); }); @@ -1801,9 +1801,9 @@ describe('actions/IOU', () => { const connection = Onyx.connect({ key: ONYXKEYS.COLLECTION.TRANSACTION, waitForCollectionCallback: true, - callback: (transactions) => { + callback: (val) => { Onyx.disconnect(connection); - resolve(transactions); + resolve(val); }, }); }); @@ -1854,9 +1854,9 @@ describe('actions/IOU', () => { const connection = Onyx.connect({ key: ONYXKEYS.COLLECTION.TRANSACTION, waitForCollectionCallback: true, - callback: (transactions) => { + callback: (val) => { Onyx.disconnect(connection); - resolve(transactions); + resolve(val); }, }); }); From 88072b915315b757506fa6ea1255f812a6f4d74b Mon Sep 17 00:00:00 2001 From: Wiktor Gut Date: Sun, 25 Jan 2026 11:15:29 +0100 Subject: [PATCH 11/21] recentWaypoints not needed in handleFileRetry --- .../ReceiptUploadRetryHandler/handleFileRetry.ts | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/libs/ReceiptUploadRetryHandler/handleFileRetry.ts b/src/libs/ReceiptUploadRetryHandler/handleFileRetry.ts index fea46835081f..a8e7dae2cc1b 100644 --- a/src/libs/ReceiptUploadRetryHandler/handleFileRetry.ts +++ b/src/libs/ReceiptUploadRetryHandler/handleFileRetry.ts @@ -1,20 +1,8 @@ -import Onyx from 'react-native-onyx'; import * as IOU from '@userActions/IOU'; import {startSplitBill} from '@userActions/IOU/Split'; import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; -import type * as OnyxTypes from '@src/types/onyx'; import type {ReceiptError} from '@src/types/onyx/Transaction'; -// TODO: remove `recentWaypoints` from this file [https://github.com/Expensify/App/issues/80268] -// `recentWaypoints` was moved here temporarily from `src/libs/actions/IOU/index.ts` during the `Deprecate Onyx.connect` refactor. -// All uses of this variable should be replaced with `useOnyx`. -let recentWaypoints: OnyxTypes.RecentWaypoint[] = []; -Onyx.connect({ - key: ONYXKEYS.NVP_RECENT_WAYPOINTS, - callback: (val) => (recentWaypoints = val ?? []), -}); - export default function handleFileRetry(message: ReceiptError, file: File, dismissError: () => void, setShouldShowErrorModal: (value: boolean) => void) { const retryParams: IOU.ReplaceReceipt | IOU.StartSplitBilActionParams | IOU.CreateTrackExpenseParams | IOU.RequestMoneyInformation = typeof message.retryParams === 'string' @@ -43,7 +31,6 @@ export default function handleFileRetry(message: ReceiptError, file: File, dismi trackExpenseParams.transactionParams.receipt = file; trackExpenseParams.isRetry = true; trackExpenseParams.shouldPlaySound = false; - trackExpenseParams.recentWaypointsCollection = recentWaypoints; IOU.trackExpense(trackExpenseParams); break; } From cbcbf689cb7299da492aedc71ab515cd1a41de0d Mon Sep 17 00:00:00 2001 From: Wiktor Gut Date: Sun, 25 Jan 2026 11:21:38 +0100 Subject: [PATCH 12/21] fix type --- src/libs/actions/IOU/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/IOU/index.ts b/src/libs/actions/IOU/index.ts index 73563f0a420d..ebb4a5779f73 100644 --- a/src/libs/actions/IOU/index.ts +++ b/src/libs/actions/IOU/index.ts @@ -712,7 +712,7 @@ type CreateTrackExpenseParams = { introSelected: OnyxEntry; activePolicyID: string | undefined; quickAction: OnyxEntry; - recentWaypointsCollection: OnyxTypes.RecentWaypoint[] | undefined; + recentWaypointsCollection: OnyxEntry; }; type GetTrackExpenseInformationTransactionParams = { From beb24219bed6f4ce50cecc60f9db0421e1cad6c9 Mon Sep 17 00:00:00 2001 From: Wiktor Gut Date: Sun, 25 Jan 2026 17:13:22 +0100 Subject: [PATCH 13/21] fix recentWaypointsCollection and deprecatedRecentWaypoints names and occurences --- src/libs/actions/IOU/Duplicate.ts | 2 +- src/libs/actions/IOU/MoneyRequest.ts | 4 +- src/libs/actions/IOU/index.ts | 21 ++++---- src/pages/Share/SubmitDetailsPage.tsx | 2 +- .../iou/request/step/IOURequestStepAmount.tsx | 2 +- .../step/IOURequestStepConfirmation.tsx | 2 +- .../step/IOURequestStepDistanceOdometer.tsx | 2 +- tests/actions/IOU/MoneyRequestTest.ts | 6 +-- tests/actions/IOUTest.ts | 52 +++++++++---------- tests/ui/UnreadIndicatorsTest.tsx | 2 +- tests/unit/GoogleTagManagerTest.tsx | 2 +- 11 files changed, 49 insertions(+), 48 deletions(-) diff --git a/src/libs/actions/IOU/Duplicate.ts b/src/libs/actions/IOU/Duplicate.ts index 8ed9dd9e0b13..b02bf30b1cbb 100644 --- a/src/libs/actions/IOU/Duplicate.ts +++ b/src/libs/actions/IOU/Duplicate.ts @@ -559,7 +559,7 @@ function duplicateExpenseTransaction({ introSelected, activePolicyID, quickAction, - recentWaypointsCollection: recentWaypoints, + recentWaypoints, }; return trackExpense(trackExpenseParams); } diff --git a/src/libs/actions/IOU/MoneyRequest.ts b/src/libs/actions/IOU/MoneyRequest.ts index 0cf4039c023c..5bd12482a526 100644 --- a/src/libs/actions/IOU/MoneyRequest.ts +++ b/src/libs/actions/IOU/MoneyRequest.ts @@ -195,7 +195,7 @@ function createTransaction({ introSelected, activePolicyID, quickAction, - recentWaypointsCollection: recentWaypoints, + recentWaypoints, }); } else { requestMoney({ @@ -560,7 +560,7 @@ function handleMoneyRequestStepDistanceNavigation({ introSelected, activePolicyID, quickAction, - recentWaypointsCollection: recentWaypoints, + recentWaypoints, }); return; } diff --git a/src/libs/actions/IOU/index.ts b/src/libs/actions/IOU/index.ts index ebb4a5779f73..319d35fdec42 100644 --- a/src/libs/actions/IOU/index.ts +++ b/src/libs/actions/IOU/index.ts @@ -712,7 +712,7 @@ type CreateTrackExpenseParams = { introSelected: OnyxEntry; activePolicyID: string | undefined; quickAction: OnyxEntry; - recentWaypointsCollection: OnyxEntry; + recentWaypoints: OnyxEntry; }; type GetTrackExpenseInformationTransactionParams = { @@ -934,13 +934,10 @@ Onyx.connectWithoutView({ callback: (value) => (recentAttendees = value), }); -// TODO: remove `recentWaypoints` from this file (https://github.com/Expensify/App/issues/73024) -// `recentWaypoints` was moved here temporarily from `src/libs/actions/Policy/Tag.ts` during the `Deprecate Onyx.connect` refactor. -// All uses of this variable should be replaced with `useOnyx`. -let recentWaypoints: OnyxTypes.RecentWaypoint[] = []; +let deprecatedRecentWaypoints: OnyxTypes.RecentWaypoint[] = []; Onyx.connect({ key: ONYXKEYS.NVP_RECENT_WAYPOINTS, - callback: (val) => (recentWaypoints = val ?? []), + callback: (val) => (deprecatedRecentWaypoints = val ?? []), }); function getAllPersonalDetails(): OnyxTypes.PersonalDetailsList { @@ -971,6 +968,10 @@ function getUserAccountID(): number { return userAccountID; } +function getRecentWaypoints(): OnyxTypes.RecentWaypoint[] { + return deprecatedRecentWaypoints; +} + /** * @private * After finishing the action in RHP from the Inbox tab, besides dismissing the modal, we should open the report. @@ -5307,7 +5308,7 @@ function updateMoneyRequestDistance({ } if (!distance) { - const recentServerValidatedWaypoints = recentWaypoints.filter((item) => !item.pendingAction); + const recentServerValidatedWaypoints = deprecatedRecentWaypoints.filter((item) => !item.pendingAction); onyxData?.failureData?.push({ onyxMethod: Onyx.METHOD.SET, key: `${ONYXKEYS.NVP_RECENT_WAYPOINTS}`, @@ -6507,7 +6508,7 @@ function trackExpense(params: CreateTrackExpenseParams) { introSelected, activePolicyID, quickAction, - recentWaypointsCollection = [], + recentWaypoints = [], } = params; const {participant, payeeAccountID, payeeEmail} = participantParams; const {policy, policyCategories, policyTagList} = policyData; @@ -6644,7 +6645,7 @@ function trackExpense(params: CreateTrackExpenseParams) { }) ?? {}; const activeReportID = isMoneyRequestReport ? report?.reportID : chatReport?.reportID; - const recentServerValidatedWaypoints = recentWaypointsCollection.filter((item) => !item.pendingAction); + const recentServerValidatedWaypoints = recentWaypoints.filter((item) => !item.pendingAction); onyxData?.failureData?.push({ onyxMethod: Onyx.METHOD.SET, key: `${ONYXKEYS.NVP_RECENT_WAYPOINTS}`, @@ -7646,7 +7647,7 @@ function createDistanceRequest(distanceRequestInformation: CreateDistanceRequest }; } - const recentServerValidatedWaypoints = recentWaypoints.filter((item) => !item.pendingAction); + const recentServerValidatedWaypoints = deprecatedRecentWaypoints.filter((item) => !item.pendingAction); onyxData?.failureData?.push({ onyxMethod: Onyx.METHOD.SET, key: `${ONYXKEYS.NVP_RECENT_WAYPOINTS}`, diff --git a/src/pages/Share/SubmitDetailsPage.tsx b/src/pages/Share/SubmitDetailsPage.tsx index c3426fb08226..dfa55083fdf3 100644 --- a/src/pages/Share/SubmitDetailsPage.tsx +++ b/src/pages/Share/SubmitDetailsPage.tsx @@ -159,7 +159,7 @@ function SubmitDetailsPage({ activePolicyID, introSelected, quickAction, - recentWaypointsCollection: recentWaypoints, + recentWaypoints, }); } else { requestMoney({ diff --git a/src/pages/iou/request/step/IOURequestStepAmount.tsx b/src/pages/iou/request/step/IOURequestStepAmount.tsx index be0104cd1504..81a30ecb1bf8 100644 --- a/src/pages/iou/request/step/IOURequestStepAmount.tsx +++ b/src/pages/iou/request/step/IOURequestStepAmount.tsx @@ -277,7 +277,7 @@ function IOURequestStepAmount({ introSelected, activePolicyID, quickAction, - recentWaypointsCollection: recentWaypoints, + recentWaypoints, }); return; } diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx index 31c3e8f7a9d4..8b5f6c5e86a4 100644 --- a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx +++ b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx @@ -801,7 +801,7 @@ function IOURequestStepConfirmation({ introSelected, activePolicyID, quickAction, - recentWaypointsCollection: recentWaypoints, + recentWaypoints, }); } }, diff --git a/src/pages/iou/request/step/IOURequestStepDistanceOdometer.tsx b/src/pages/iou/request/step/IOURequestStepDistanceOdometer.tsx index e0caa20c82ca..398bfca91559 100644 --- a/src/pages/iou/request/step/IOURequestStepDistanceOdometer.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistanceOdometer.tsx @@ -410,7 +410,7 @@ function IOURequestStepDistanceOdometer({ introSelected, activePolicyID, quickAction, - recentWaypointsCollection: recentWaypoints, + recentWaypoints, }); return; } diff --git a/tests/actions/IOU/MoneyRequestTest.ts b/tests/actions/IOU/MoneyRequestTest.ts index 52ffe59692dc..1b04468876a3 100644 --- a/tests/actions/IOU/MoneyRequestTest.ts +++ b/tests/actions/IOU/MoneyRequestTest.ts @@ -528,7 +528,7 @@ describe('MoneyRequest', () => { currentUserEmailParam: baseParams.currentUserLogin, quickAction: baseParams.quickAction, shouldHandleNavigation: true, - recentWaypointsCollection: recentWaypoints, + recentWaypoints, }); // Should not call request money inside createTransaction function expect(IOU.requestMoney).not.toHaveBeenCalled(); @@ -826,7 +826,7 @@ describe('MoneyRequest', () => { currentUserAccountIDParam: baseParams.currentUserAccountID, currentUserEmailParam: baseParams.currentUserLogin, quickAction: baseParams.quickAction, - recentWaypointsCollection: recentWaypoints, + recentWaypoints, }); // The function must return after trackExpense and not call createDistanceRequest @@ -894,7 +894,7 @@ describe('MoneyRequest', () => { currentUserAccountIDParam: baseParams.currentUserAccountID, currentUserEmailParam: baseParams.currentUserLogin, quickAction: baseParams.quickAction, - recentWaypointsCollection: recentWaypoints, + recentWaypoints, }); }); diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index 09ae6003dddf..e282093004cd 100644 --- a/tests/actions/IOUTest.ts +++ b/tests/actions/IOUTest.ts @@ -516,7 +516,7 @@ describe('actions/IOU', () => { introSelected: undefined, activePolicyID: undefined, quickAction: undefined, - recentWaypointsCollection: recentWaypoints, + recentWaypoints, }); await waitForBatchedUpdates(); await mockFetch?.resume?.(); @@ -617,7 +617,7 @@ describe('actions/IOU', () => { introSelected: undefined, activePolicyID: undefined, quickAction: undefined, - recentWaypointsCollection: recentWaypoints, + recentWaypoints, }); await waitForBatchedUpdates(); await mockFetch?.resume?.(); @@ -707,7 +707,7 @@ describe('actions/IOU', () => { introSelected: undefined, activePolicyID: undefined, quickAction: undefined, - recentWaypointsCollection: recentWaypoints, + recentWaypoints, }); await waitForBatchedUpdates(); @@ -760,7 +760,7 @@ describe('actions/IOU', () => { introSelected: undefined, activePolicyID: undefined, quickAction: undefined, - recentWaypointsCollection: recentWaypoints, + recentWaypoints, }); await waitForBatchedUpdates(); @@ -844,7 +844,7 @@ describe('actions/IOU', () => { introSelected: undefined, activePolicyID: undefined, quickAction: undefined, - recentWaypointsCollection: recentWaypoints, + recentWaypoints, }); await waitForBatchedUpdates(); @@ -897,7 +897,7 @@ describe('actions/IOU', () => { introSelected: undefined, activePolicyID: undefined, quickAction: undefined, - recentWaypointsCollection: recentWaypoints, + recentWaypoints, }); await waitForBatchedUpdates(); @@ -971,7 +971,7 @@ describe('actions/IOU', () => { introSelected: undefined, activePolicyID: undefined, quickAction: undefined, - recentWaypointsCollection: [], + recentWaypoints: [], }); await waitForBatchedUpdates(); @@ -1027,7 +1027,7 @@ describe('actions/IOU', () => { introSelected: undefined, activePolicyID: undefined, quickAction: undefined, - recentWaypointsCollection: [], + recentWaypoints: [], }); await waitForBatchedUpdates(); @@ -1082,7 +1082,7 @@ describe('actions/IOU', () => { introSelected: undefined, activePolicyID: undefined, quickAction: undefined, - recentWaypointsCollection: [], + recentWaypoints: [], }); await waitForBatchedUpdates(); @@ -1136,7 +1136,7 @@ describe('actions/IOU', () => { introSelected: undefined, activePolicyID: undefined, quickAction: undefined, - recentWaypointsCollection: [], + recentWaypoints: [], }); await waitForBatchedUpdates(); @@ -1207,7 +1207,7 @@ describe('actions/IOU', () => { introSelected: undefined, activePolicyID: undefined, quickAction: undefined, - recentWaypointsCollection: [], + recentWaypoints: [], }); await waitForBatchedUpdates(); @@ -1298,7 +1298,7 @@ describe('actions/IOU', () => { introSelected: undefined, activePolicyID: undefined, quickAction: undefined, - recentWaypointsCollection: [], + recentWaypoints: [], }); await waitForBatchedUpdates(); @@ -1354,7 +1354,7 @@ describe('actions/IOU', () => { introSelected: undefined, activePolicyID: undefined, quickAction: undefined, - recentWaypointsCollection: [], + recentWaypoints: [], }); await waitForBatchedUpdates(); @@ -1415,7 +1415,7 @@ describe('actions/IOU', () => { introSelected: undefined, activePolicyID: undefined, quickAction: undefined, - recentWaypointsCollection: [], + recentWaypoints: [], }); await waitForBatchedUpdates(); @@ -1471,7 +1471,7 @@ describe('actions/IOU', () => { introSelected: undefined, activePolicyID: undefined, quickAction: undefined, - recentWaypointsCollection: [], + recentWaypoints: [], }); await waitForBatchedUpdates(); @@ -1522,7 +1522,7 @@ describe('actions/IOU', () => { introSelected: undefined, activePolicyID: undefined, quickAction: undefined, - recentWaypointsCollection: [], + recentWaypoints: [], }); await waitForBatchedUpdates(); @@ -1577,7 +1577,7 @@ describe('actions/IOU', () => { introSelected: undefined, activePolicyID: undefined, quickAction: undefined, - recentWaypointsCollection: [], + recentWaypoints: [], }); await waitForBatchedUpdates(); @@ -1624,7 +1624,7 @@ describe('actions/IOU', () => { introSelected: undefined, activePolicyID: undefined, quickAction: undefined, - recentWaypointsCollection: [], + recentWaypoints: [], }); await waitForBatchedUpdates(); @@ -1679,7 +1679,7 @@ describe('actions/IOU', () => { introSelected: undefined, activePolicyID: undefined, quickAction: undefined, - recentWaypointsCollection: [], + recentWaypoints: [], }); await waitForBatchedUpdates(); @@ -1738,7 +1738,7 @@ describe('actions/IOU', () => { introSelected: undefined, activePolicyID: undefined, quickAction: undefined, - recentWaypointsCollection: [], + recentWaypoints: [], }); await waitForBatchedUpdates(); @@ -1792,7 +1792,7 @@ describe('actions/IOU', () => { introSelected: undefined, activePolicyID: undefined, quickAction: undefined, - recentWaypointsCollection: [], + recentWaypoints: [], }); await waitForBatchedUpdates(); @@ -1845,7 +1845,7 @@ describe('actions/IOU', () => { introSelected: undefined, activePolicyID: undefined, quickAction: undefined, - recentWaypointsCollection: [], + recentWaypoints: [], }); await waitForBatchedUpdates(); @@ -3135,7 +3135,7 @@ describe('actions/IOU', () => { introSelected: undefined, activePolicyID: undefined, quickAction: undefined, - recentWaypointsCollection: recentWaypoints, + recentWaypoints, }); mockFetch?.resume?.(); @@ -3201,7 +3201,7 @@ describe('actions/IOU', () => { introSelected: undefined, activePolicyID: undefined, quickAction: undefined, - recentWaypointsCollection: recentWaypoints, + recentWaypoints, }); await waitForBatchedUpdates(); @@ -7089,7 +7089,7 @@ describe('actions/IOU', () => { introSelected: undefined, activePolicyID: undefined, quickAction: undefined, - recentWaypointsCollection: recentWaypoints, + recentWaypoints, }); await waitForBatchedUpdates(); @@ -8749,7 +8749,7 @@ describe('actions/IOU', () => { introSelected: undefined, activePolicyID: undefined, quickAction: undefined, - recentWaypointsCollection: recentWaypoints, + recentWaypoints, }); await getOnyxData({ key: ONYXKEYS.COLLECTION.TRANSACTION, diff --git a/tests/ui/UnreadIndicatorsTest.tsx b/tests/ui/UnreadIndicatorsTest.tsx index f1cbf07c7d2a..43f807914f16 100644 --- a/tests/ui/UnreadIndicatorsTest.tsx +++ b/tests/ui/UnreadIndicatorsTest.tsx @@ -726,7 +726,7 @@ describe('Unread Indicators', () => { introSelected: undefined, activePolicyID: undefined, quickAction: undefined, - recentWaypointsCollection: recentWaypoints, + recentWaypoints, }); await waitForBatchedUpdates(); diff --git a/tests/unit/GoogleTagManagerTest.tsx b/tests/unit/GoogleTagManagerTest.tsx index ecef3c4ce2a2..7f1d5b5a2c72 100644 --- a/tests/unit/GoogleTagManagerTest.tsx +++ b/tests/unit/GoogleTagManagerTest.tsx @@ -203,7 +203,7 @@ describe('GoogleTagManagerTest', () => { introSelected: undefined, activePolicyID: undefined, quickAction: undefined, - recentWaypointsCollection: recentWaypoints, + recentWaypoints, }); await waitForBatchedUpdatesWithAct(); From 45d5daeec7c4738d6158ce8d396526b64345edda Mon Sep 17 00:00:00 2001 From: Wiktor Gut Date: Sun, 25 Jan 2026 18:18:56 +0100 Subject: [PATCH 14/21] use getRecentWaypoints --- src/libs/actions/IOU/Duplicate.ts | 11 ++--------- src/libs/actions/IOU/MoneyRequest.ts | 13 ++++--------- src/libs/actions/IOU/index.ts | 1 + 3 files changed, 7 insertions(+), 18 deletions(-) diff --git a/src/libs/actions/IOU/Duplicate.ts b/src/libs/actions/IOU/Duplicate.ts index b02bf30b1cbb..d187adefd24e 100644 --- a/src/libs/actions/IOU/Duplicate.ts +++ b/src/libs/actions/IOU/Duplicate.ts @@ -34,20 +34,12 @@ import { getAllTransactionViolations, getCurrentUserEmail, getMoneyRequestParticipantsFromReport, + getRecentWaypoints, getUserAccountID, requestMoney, trackExpense, } from '.'; -// TODO: remove `recentWaypoints` from this file [https://github.com/Expensify/App/issues/80270] -// `recentWaypoints` was moved here temporarily from `src/libs/actions/IOU/index.ts` during the `Deprecate Onyx.connect` refactor. -// All uses of this variable should be replaced with `useOnyx`. -let recentWaypoints: OnyxTypes.RecentWaypoint[] = []; -Onyx.connect({ - key: ONYXKEYS.NVP_RECENT_WAYPOINTS, - callback: (val) => (recentWaypoints = val ?? []), -}); - function getIOUActionForTransactions(transactionIDList: Array, iouReportID: string | undefined): Array> { const allReportActions = getAllReportActionsFromIOU(); return Object.values(allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReportID}`] ?? {})?.filter( @@ -496,6 +488,7 @@ function duplicateExpenseTransaction({ const userAccountID = getUserAccountID(); const currentUserEmail = getCurrentUserEmail(); + const recentWaypoints = getRecentWaypoints(); const participants = getMoneyRequestParticipantsFromReport(targetReport); const transactionDetails = getTransactionDetails(transaction); diff --git a/src/libs/actions/IOU/MoneyRequest.ts b/src/libs/actions/IOU/MoneyRequest.ts index 5bd12482a526..7ba695ff5982 100644 --- a/src/libs/actions/IOU/MoneyRequest.ts +++ b/src/libs/actions/IOU/MoneyRequest.ts @@ -26,6 +26,7 @@ import type {GpsPoint} from './index'; import { createDistanceRequest, getMoneyRequestParticipantsFromReport, + getRecentWaypoints, requestMoney, resetSplitShares, setCustomUnitRateID, @@ -134,15 +135,6 @@ type MoneyRequestStepDistanceNavigationParams = { gpsDistance?: number; }; -// TODO: remove `recentWaypoints` from this file [https://github.com/Expensify/App/issues/80269] -// `recentWaypoints` was moved here temporarily from `src/libs/actions/IOU/index.ts` during the `Deprecate Onyx.connect` refactor. -// All uses of this variable should be replaced with `useOnyx`. -let recentWaypoints: RecentWaypoint[] = []; -Onyx.connect({ - key: ONYXKEYS.NVP_RECENT_WAYPOINTS, - callback: (val) => (recentWaypoints = val ?? []), -}); - function createTransaction({ transactions, iouType, @@ -164,6 +156,8 @@ function createTransaction({ billable, reimbursable = true, }: CreateTransactionParams) { + const recentWaypoints = getRecentWaypoints(); + for (const [index, receiptFile] of files.entries()) { const transaction = transactions.find((item) => item.transactionID === receiptFile.transactionID); const receipt: Receipt = receiptFile.file ?? {}; @@ -496,6 +490,7 @@ function handleMoneyRequestStepDistanceNavigation({ }: MoneyRequestStepDistanceNavigationParams) { const isManualDistance = manualDistance !== undefined; const isGPSDistance = gpsDistance !== undefined && gpsCoordinates !== undefined; + const recentWaypoints = getRecentWaypoints(); if (transaction?.splitShares && !isManualDistance) { resetSplitShares(transaction); diff --git a/src/libs/actions/IOU/index.ts b/src/libs/actions/IOU/index.ts index 319d35fdec42..80ebda3c5954 100644 --- a/src/libs/actions/IOU/index.ts +++ b/src/libs/actions/IOU/index.ts @@ -13133,6 +13133,7 @@ export { getAllReportActionsFromIOU, getCurrentUserEmail, getUserAccountID, + getRecentWaypoints, getReceiptError, getSearchOnyxUpdate, setMoneyRequestTimeRate, From 537dfe130135bb6e80bbfc1c67780b2f60138548 Mon Sep 17 00:00:00 2001 From: Wiktor Gut Date: Sun, 25 Jan 2026 18:42:40 +0100 Subject: [PATCH 15/21] revert bumping max-warnings --- package.json | 2 +- src/libs/actions/IOU/MoneyRequest.ts | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 15e5eba3b714..557f84ddc7f8 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "test:debug": "TZ=utc NODE_OPTIONS='--inspect-brk --experimental-vm-modules' jest --runInBand", "perf-test": "NODE_OPTIONS=--experimental-vm-modules npx reassure", "typecheck": "NODE_OPTIONS=--max_old_space_size=8192 tsc", - "lint": "NODE_OPTIONS=--max_old_space_size=8192 eslint . --max-warnings=387 --cache --cache-location=node_modules/.cache/eslint --cache-strategy content --concurrency=auto", + "lint": "NODE_OPTIONS=--max_old_space_size=8192 eslint . --max-warnings=384 --cache --cache-location=node_modules/.cache/eslint --cache-strategy content --concurrency=auto", "lint-changed": "NODE_OPTIONS=--max_old_space_size=8192 ./scripts/lintChanged.sh", "check-lazy-loading": "ts-node scripts/checkLazyLoading.ts", "lint-watch": "npx eslint-watch --watch --changed", diff --git a/src/libs/actions/IOU/MoneyRequest.ts b/src/libs/actions/IOU/MoneyRequest.ts index 7ba695ff5982..b74fe7010411 100644 --- a/src/libs/actions/IOU/MoneyRequest.ts +++ b/src/libs/actions/IOU/MoneyRequest.ts @@ -1,5 +1,4 @@ import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; -import Onyx from 'react-native-onyx'; import DistanceRequestUtils from '@libs/DistanceRequestUtils'; import getCurrentPosition from '@libs/getCurrentPosition'; import {navigateToConfirmationPage, navigateToParticipantPage} from '@libs/IOUUtils'; @@ -15,10 +14,9 @@ import {setTransactionReport} from '@userActions/Transaction'; import type {IOUType} from '@src/CONST'; import CONST from '@src/CONST'; import type {TranslationParameters, TranslationPaths} from '@src/languages/types'; -import ONYXKEYS from '@src/ONYXKEYS'; import type {Route} from '@src/ROUTES'; import ROUTES from '@src/ROUTES'; -import type {IntroSelected, LastSelectedDistanceRates, PersonalDetailsList, Policy, QuickAction, RecentWaypoint, Report, Transaction, TransactionViolation} from '@src/types/onyx'; +import type {IntroSelected, LastSelectedDistanceRates, PersonalDetailsList, Policy, QuickAction, Report, Transaction, TransactionViolation} from '@src/types/onyx'; import type {ReportAttributes, ReportAttributesDerivedValue} from '@src/types/onyx/DerivedValues'; import type {Participant} from '@src/types/onyx/IOU'; import type {Receipt, WaypointCollection} from '@src/types/onyx/Transaction'; From ca8027163a55064d78e74407868a83ee174662d6 Mon Sep 17 00:00:00 2001 From: Wiktor Gut Date: Sun, 25 Jan 2026 23:36:14 +0100 Subject: [PATCH 16/21] change possible Onyx.connects in test files to getOnyxValue --- tests/actions/IOU/MoneyRequestTest.ts | 21 ++------ tests/actions/IOUTest.ts | 72 +++++---------------------- tests/ui/UnreadIndicatorsTest.tsx | 11 ++-- tests/unit/GoogleTagManagerTest.tsx | 11 ++-- 4 files changed, 25 insertions(+), 90 deletions(-) diff --git a/tests/actions/IOU/MoneyRequestTest.ts b/tests/actions/IOU/MoneyRequestTest.ts index 1b04468876a3..6482a62e7d4f 100644 --- a/tests/actions/IOU/MoneyRequestTest.ts +++ b/tests/actions/IOU/MoneyRequestTest.ts @@ -10,7 +10,7 @@ import type {ReceiptFile} from '@pages/iou/request/step/IOURequestStepScan/types import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import type {QuickAction, RecentWaypoint} from '@src/types/onyx'; +import type {QuickAction} from '@src/types/onyx'; import type {SplitShares} from '@src/types/onyx/Transaction'; import * as IOU from '../../../src/libs/actions/IOU'; import * as ReportUtils from '../../../src/libs/ReportUtils'; @@ -18,7 +18,6 @@ import createRandomPolicy from '../../utils/collections/policies'; import {createRandomReport} from '../../utils/collections/reports'; import createRandomTransaction from '../../utils/collections/transaction'; import getOnyxValue from '../../utils/getOnyxValue'; -import {getOnyxData} from '../../utils/TestHelper'; import waitForBatchedUpdates from '../../utils/waitForBatchedUpdates'; jest.mock('@libs/actions/IOU', () => { @@ -495,11 +494,7 @@ describe('MoneyRequest', () => { await waitForBatchedUpdates(); - let recentWaypoints: RecentWaypoint[] = []; - await getOnyxData({ - key: ONYXKEYS.NVP_RECENT_WAYPOINTS, - callback: (val) => (recentWaypoints = val ?? []), - }); + const recentWaypoints = (await getOnyxValue(ONYXKEYS.NVP_RECENT_WAYPOINTS)) ?? []; expect(IOU.trackExpense).toHaveBeenCalledWith({ report: baseParams.report, @@ -785,11 +780,7 @@ describe('MoneyRequest', () => { expect(IOU.resetSplitShares).not.toHaveBeenCalled(); - let recentWaypoints: RecentWaypoint[] = []; - await getOnyxData({ - key: ONYXKEYS.NVP_RECENT_WAYPOINTS, - callback: (val) => (recentWaypoints = val ?? []), - }); + const recentWaypoints = (await getOnyxValue(ONYXKEYS.NVP_RECENT_WAYPOINTS)) ?? []; expect(IOU.trackExpense).toHaveBeenCalledWith({ report: baseParams.report, @@ -853,11 +844,7 @@ describe('MoneyRequest', () => { waypoints: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, }); - let recentWaypoints: RecentWaypoint[] = []; - await getOnyxData({ - key: ONYXKEYS.NVP_RECENT_WAYPOINTS, - callback: (val) => (recentWaypoints = val ?? []), - }); + const recentWaypoints = (await getOnyxValue(ONYXKEYS.NVP_RECENT_WAYPOINTS)) ?? []; expect(IOU.trackExpense).toHaveBeenCalledWith({ report: baseParams.report, diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index e282093004cd..9e7c9007fc19 100644 --- a/tests/actions/IOUTest.ts +++ b/tests/actions/IOUTest.ts @@ -72,7 +72,7 @@ import * as API from '@src/libs/API'; import DateUtils from '@src/libs/DateUtils'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import type {PersonalDetailsList, Policy, PolicyTagLists, QuickAction, RecentlyUsedTags, RecentWaypoint, Report, ReportNameValuePairs, SearchResults} from '@src/types/onyx'; +import type {PersonalDetailsList, Policy, PolicyTagLists, RecentlyUsedTags, Report, ReportNameValuePairs, SearchResults} from '@src/types/onyx'; import type {Accountant, Attendee, SplitExpense} from '@src/types/onyx/IOU'; import type {CurrentUserPersonalDetails} from '@src/types/onyx/PersonalDetails'; import type {Participant} from '@src/types/onyx/Report'; @@ -482,11 +482,7 @@ describe('actions/IOU', () => { await Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${fakeTransaction.transactionID}`, fakeTransaction); mockFetch?.pause?.(); - let recentWaypoints: RecentWaypoint[] = []; - await getOnyxData({ - key: ONYXKEYS.NVP_RECENT_WAYPOINTS, - callback: (val) => (recentWaypoints = val ?? []), - }); + const recentWaypoints = (await getOnyxValue(ONYXKEYS.NVP_RECENT_WAYPOINTS)) ?? []; // When the user submits the transaction to the selfDM report trackExpense({ @@ -678,11 +674,7 @@ describe('actions/IOU', () => { await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${policyExpenseChat.reportID}`, policyExpenseChat); await Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transaction.transactionID}`, transaction); - let recentWaypoints: RecentWaypoint[] = []; - await getOnyxData({ - key: ONYXKEYS.NVP_RECENT_WAYPOINTS, - callback: (val) => (recentWaypoints = val ?? []), - }); + const recentWaypoints = (await getOnyxValue(ONYXKEYS.NVP_RECENT_WAYPOINTS)) ?? []; // Create a tracked expense trackExpense({ @@ -816,11 +808,8 @@ describe('actions/IOU', () => { await Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transaction.transactionID}`, transaction); await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, {[accountant.accountID]: accountant}); - let recentWaypoints: RecentWaypoint[] = []; - await getOnyxData({ - key: ONYXKEYS.NVP_RECENT_WAYPOINTS, - callback: (val) => (recentWaypoints = val ?? []), - }); + const recentWaypoints = (await getOnyxValue(ONYXKEYS.NVP_RECENT_WAYPOINTS)) ?? []; + // Create a tracked expense trackExpense({ report: selfDMReport, @@ -1032,15 +1021,7 @@ describe('actions/IOU', () => { await waitForBatchedUpdates(); // Then an actionable track expense whisper should be created - const reportActions = await new Promise>((resolve) => { - const connection = Onyx.connect({ - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${selfDMReport.reportID}`, - callback: (actions) => { - Onyx.disconnect(connection); - resolve(actions); - }, - }); - }); + const reportActions = await getOnyxValue(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${selfDMReport.reportID}`); const actionableWhisper = Object.values(reportActions ?? {}).find((action) => isActionableTrackExpense(action)); expect(actionableWhisper).toBeTruthy(); @@ -1212,15 +1193,7 @@ describe('actions/IOU', () => { await waitForBatchedUpdates(); // Verify initial expense was created - const selfDMReportActions = await new Promise>((resolve) => { - const connection = Onyx.connect({ - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${selfDMReport.reportID}`, - callback: (actions) => { - Onyx.disconnect(connection); - resolve(actions); - }, - }); - }); + const selfDMReportActions = await getOnyxValue(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${selfDMReport.reportID}`); expect(Object.values(selfDMReportActions ?? {}).length).toBe(2); const moneyRequestAction = Object.values(selfDMReportActions ?? {}).find((action) => isMoneyRequestAction(action)); @@ -1420,16 +1393,8 @@ describe('actions/IOU', () => { await waitForBatchedUpdates(); // Then quick action should be updated - const quickAction = await new Promise>((resolve) => { - const connection = Onyx.connect({ - key: ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE, - callback: (qa) => { - Onyx.disconnect(connection); - resolve(qa); - }, - }); - }); - + const quickAction = await getOnyxValue(ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE); + expect(quickAction).toBeTruthy(); expect(quickAction?.chatReportID).toBe(policyExpenseChat.reportID); }); }); @@ -3105,11 +3070,7 @@ describe('actions/IOU', () => { ]); await waitForBatchedUpdates(); - let recentWaypoints: RecentWaypoint[] = []; - await getOnyxData({ - key: ONYXKEYS.NVP_RECENT_WAYPOINTS, - callback: (val) => (recentWaypoints = val ?? []), - }); + const recentWaypoints = (await getOnyxValue(ONYXKEYS.NVP_RECENT_WAYPOINTS)) ?? []; // First create a tracked expense in self DM trackExpense({ @@ -7053,11 +7014,8 @@ describe('actions/IOU', () => { [WRITE_COMMANDS.CATEGORIZE_TRACKED_EXPENSE, CONST.IOU.ACTION.CATEGORIZE], [WRITE_COMMANDS.SHARE_TRACKED_EXPENSE, CONST.IOU.ACTION.SHARE], ])('%s', async (expectedCommand: ApiCommand, action: IOUAction) => { - let recentWaypoints: RecentWaypoint[] = []; - await getOnyxData({ - key: ONYXKEYS.NVP_RECENT_WAYPOINTS, - callback: (val) => (recentWaypoints = val ?? []), - }); + const recentWaypoints = (await getOnyxValue(ONYXKEYS.NVP_RECENT_WAYPOINTS)) ?? []; + // When a track expense is created trackExpense({ report: {reportID: '123', policyID: 'A'}, @@ -8720,11 +8678,7 @@ describe('actions/IOU', () => { const amount = 100; - let recentWaypoints: RecentWaypoint[] = []; - await getOnyxData({ - key: ONYXKEYS.NVP_RECENT_WAYPOINTS, - callback: (val) => (recentWaypoints = val ?? []), - }); + const recentWaypoints = (await getOnyxValue(ONYXKEYS.NVP_RECENT_WAYPOINTS)) ?? []; trackExpense({ report: selfDMReport, diff --git a/tests/ui/UnreadIndicatorsTest.tsx b/tests/ui/UnreadIndicatorsTest.tsx index 43f807914f16..c1fd100f6f19 100644 --- a/tests/ui/UnreadIndicatorsTest.tsx +++ b/tests/ui/UnreadIndicatorsTest.tsx @@ -9,6 +9,7 @@ import type {TextStyle, ViewStyle} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import OnyxUtils from 'react-native-onyx/dist/OnyxUtils'; +import getOnyxValue from 'tests/utils/getOnyxValue'; import {setSidebarLoaded} from '@libs/actions/App'; import {trackExpense} from '@libs/actions/IOU'; import {addComment, deleteReportComment, markCommentAsUnread, readNewestAction} from '@libs/actions/Report'; @@ -22,13 +23,13 @@ import FontUtils from '@styles/utils/FontUtils'; import App from '@src/App'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {RecentWaypoint, ReportAction, ReportActions} from '@src/types/onyx'; +import type {ReportAction, ReportActions} from '@src/types/onyx'; import type {NativeNavigationMock} from '../../__mocks__/@react-navigation/native'; import {createRandomReport} from '../utils/collections/reports'; import createRandomTransaction from '../utils/collections/transaction'; import PusherHelper from '../utils/PusherHelper'; import * as TestHelper from '../utils/TestHelper'; -import {getOnyxData, navigateToSidebarOption} from '../utils/TestHelper'; +import {navigateToSidebarOption} from '../utils/TestHelper'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; import waitForBatchedUpdatesWithAct from '../utils/waitForBatchedUpdatesWithAct'; @@ -698,11 +699,7 @@ describe('Unread Indicators', () => { comment: 'description', }; - let recentWaypoints: RecentWaypoint[] = []; - await getOnyxData({ - key: ONYXKEYS.NVP_RECENT_WAYPOINTS, - callback: (val) => (recentWaypoints = val ?? []), - }); + const recentWaypoints = (await getOnyxValue(ONYXKEYS.NVP_RECENT_WAYPOINTS)) ?? []; // When the user track an expense on the self DM const participant = {login: USER_A_EMAIL, accountID: USER_A_ACCOUNT_ID}; diff --git a/tests/unit/GoogleTagManagerTest.tsx b/tests/unit/GoogleTagManagerTest.tsx index 7f1d5b5a2c72..39c20d5f30cf 100644 --- a/tests/unit/GoogleTagManagerTest.tsx +++ b/tests/unit/GoogleTagManagerTest.tsx @@ -2,6 +2,7 @@ import {NavigationContainer} from '@react-navigation/native'; import type * as NativeNavigation from '@react-navigation/native'; import {act, render} from '@testing-library/react-native'; import Onyx from 'react-native-onyx'; +import getOnyxValue from 'tests/utils/getOnyxValue'; import {trackExpense} from '@libs/actions/IOU'; import {addPaymentCard, addSubscriptionPaymentCard} from '@libs/actions/PaymentMethods'; import {createWorkspace} from '@libs/actions/Policy/Policy'; @@ -11,8 +12,7 @@ import navigationRef from '@libs/Navigation/navigationRef'; import {getCardForSubscriptionBilling} from '@libs/SubscriptionUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {FundList, RecentWaypoint} from '@src/types/onyx'; -import {getOnyxData} from '../utils/TestHelper'; +import type {FundList} from '@src/types/onyx'; import waitForBatchedUpdatesWithAct from '../utils/waitForBatchedUpdatesWithAct'; jest.mock('@libs/GoogleTagManager'); @@ -170,11 +170,8 @@ describe('GoogleTagManagerTest', () => { }); test('workspace_created - categorizeTrackedExpense', async () => { - let recentWaypoints: RecentWaypoint[] = []; - await getOnyxData({ - key: ONYXKEYS.NVP_RECENT_WAYPOINTS, - callback: (val) => (recentWaypoints = val ?? []), - }); + const recentWaypoints = (await getOnyxValue(ONYXKEYS.NVP_RECENT_WAYPOINTS)) ?? []; + trackExpense({ report: {reportID: '123'}, isDraftPolicy: true, From 3d6f2e0bffe71d07cf6bf12ee645e4b39c0be91f Mon Sep 17 00:00:00 2001 From: Wiktor Gut Date: Mon, 26 Jan 2026 00:57:05 +0100 Subject: [PATCH 17/21] change getting transactions to getOnyxData --- tests/actions/IOUTest.ts | 336 +++++++++++++++++---------------------- 1 file changed, 147 insertions(+), 189 deletions(-) diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index 9e7c9007fc19..1218f349ddfa 100644 --- a/tests/actions/IOUTest.ts +++ b/tests/actions/IOUTest.ts @@ -569,15 +569,13 @@ describe('actions/IOU', () => { await waitForBatchedUpdates(); // Then the transaction draft should be saved successfully - const allTransactionsDraft = await new Promise>((resolve) => { - const connection = Onyx.connect({ - key: ONYXKEYS.COLLECTION.TRANSACTION_DRAFT, - waitForCollectionCallback: true, - callback: (transactionDrafts) => { - Onyx.disconnect(connection); - resolve(transactionDrafts); - }, - }); + let allTransactionsDraft: OnyxCollection; + await getOnyxData({ + key: ONYXKEYS.COLLECTION.TRANSACTION_DRAFT, + waitForCollectionCallback: true, + callback: (val) => { + allTransactionsDraft = val; + }, }); const transactionDraft = allTransactionsDraft?.[`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transaction?.transactionID}`]; @@ -965,15 +963,13 @@ describe('actions/IOU', () => { await waitForBatchedUpdates(); // Then transaction should be created with correct values - const transactions = await new Promise>((resolve) => { - const connection = Onyx.connect({ - key: ONYXKEYS.COLLECTION.TRANSACTION, - waitForCollectionCallback: true, - callback: (val) => { - Onyx.disconnect(connection); - resolve(val); - }, - }); + let transactions: OnyxCollection; + await getOnyxData({ + key: ONYXKEYS.COLLECTION.TRANSACTION, + waitForCollectionCallback: true, + callback: (val) => { + transactions = val; + }, }); const createdTransaction = Object.values(transactions ?? {}).at(0); @@ -1068,15 +1064,13 @@ describe('actions/IOU', () => { await waitForBatchedUpdates(); // Then transaction should have correct tax fields - const transactions = await new Promise>((resolve) => { - const connection = Onyx.connect({ - key: ONYXKEYS.COLLECTION.TRANSACTION, - waitForCollectionCallback: true, - callback: (val) => { - Onyx.disconnect(connection); - resolve(val); - }, - }); + let transactions: OnyxCollection; + await getOnyxData({ + key: ONYXKEYS.COLLECTION.TRANSACTION, + waitForCollectionCallback: true, + callback: (val) => { + transactions = val; + }, }); const createdTransaction = Object.values(transactions ?? {}).at(0); @@ -1122,15 +1116,13 @@ describe('actions/IOU', () => { await waitForBatchedUpdates(); // Then transaction should have correct billable and reimbursable flags - const transactions = await new Promise>((resolve) => { - const connection = Onyx.connect({ - key: ONYXKEYS.COLLECTION.TRANSACTION, - waitForCollectionCallback: true, - callback: (val) => { - Onyx.disconnect(connection); - resolve(val); - }, - }); + let transactions: OnyxCollection; + await getOnyxData({ + key: ONYXKEYS.COLLECTION.TRANSACTION, + waitForCollectionCallback: true, + callback: (val) => { + transactions = val; + }, }); const createdTransaction = Object.values(transactions ?? {}).at(0); @@ -1202,15 +1194,13 @@ describe('actions/IOU', () => { expect(actionableWhisper).toBeTruthy(); // Get the created transaction - const transactions = await new Promise>((resolve) => { - const connection = Onyx.connect({ - key: ONYXKEYS.COLLECTION.TRANSACTION, - waitForCollectionCallback: true, - callback: (val) => { - Onyx.disconnect(connection); - resolve(val); - }, - }); + let transactions: OnyxCollection; + await getOnyxData({ + key: ONYXKEYS.COLLECTION.TRANSACTION, + waitForCollectionCallback: true, + callback: (val) => { + transactions = val; + }, }); const createdTransaction = Object.values(transactions ?? {}).at(0); expect(createdTransaction).toBeTruthy(); @@ -1228,15 +1218,13 @@ describe('actions/IOU', () => { await waitForBatchedUpdates(); // Verify draft was created - const transactionDrafts = await new Promise>((resolve) => { - const connection = Onyx.connect({ - key: ONYXKEYS.COLLECTION.TRANSACTION_DRAFT, - waitForCollectionCallback: true, - callback: (drafts) => { - Onyx.disconnect(connection); - resolve(drafts); - }, - }); + let transactionDrafts: OnyxCollection; + await getOnyxData({ + key: ONYXKEYS.COLLECTION.TRANSACTION_DRAFT, + waitForCollectionCallback: true, + callback: (val) => { + transactionDrafts = val; + }, }); const draftTransaction = transactionDrafts?.[`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${createdTransaction?.transactionID}`]; expect(draftTransaction).toBeTruthy(); @@ -1276,15 +1264,13 @@ describe('actions/IOU', () => { await waitForBatchedUpdates(); // Verify transaction was categorized - const finalTransactions = await new Promise>((resolve) => { - const connection = Onyx.connect({ - key: ONYXKEYS.COLLECTION.TRANSACTION, - waitForCollectionCallback: true, - callback: (val) => { - Onyx.disconnect(connection); - resolve(val); - }, - }); + let finalTransactions: OnyxCollection; + await getOnyxData({ + key: ONYXKEYS.COLLECTION.TRANSACTION, + waitForCollectionCallback: true, + callback: (val) => { + finalTransactions = val; + }, }); const categorizedTransaction = finalTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${createdTransaction?.transactionID}`]; expect(categorizedTransaction?.category).toBe(selectedCategory); @@ -1332,15 +1318,13 @@ describe('actions/IOU', () => { await waitForBatchedUpdates(); // Then transaction should have attendees - const transactions = await new Promise>((resolve) => { - const connection = Onyx.connect({ - key: ONYXKEYS.COLLECTION.TRANSACTION, - waitForCollectionCallback: true, - callback: (val) => { - Onyx.disconnect(connection); - resolve(val); - }, - }); + let transactions: OnyxCollection; + await getOnyxData({ + key: ONYXKEYS.COLLECTION.TRANSACTION, + waitForCollectionCallback: true, + callback: (val) => { + transactions = val; + }, }); const createdTransaction = Object.values(transactions ?? {}).at(0); @@ -1441,15 +1425,13 @@ describe('actions/IOU', () => { await waitForBatchedUpdates(); // Then transaction should still be created - const transactions = await new Promise>((resolve) => { - const connection = Onyx.connect({ - key: ONYXKEYS.COLLECTION.TRANSACTION, - waitForCollectionCallback: true, - callback: (val) => { - Onyx.disconnect(connection); - resolve(val); - }, - }); + let transactions: OnyxCollection; + await getOnyxData({ + key: ONYXKEYS.COLLECTION.TRANSACTION, + waitForCollectionCallback: true, + callback: (val) => { + transactions = val; + }, }); expect(Object.values(transactions ?? {}).length).toBeGreaterThan(0); @@ -1492,15 +1474,13 @@ describe('actions/IOU', () => { await waitForBatchedUpdates(); // Then transaction should be created with zero amount - const transactions = await new Promise>((resolve) => { - const connection = Onyx.connect({ - key: ONYXKEYS.COLLECTION.TRANSACTION, - waitForCollectionCallback: true, - callback: (val) => { - Onyx.disconnect(connection); - resolve(val); - }, - }); + let transactions: OnyxCollection; + await getOnyxData({ + key: ONYXKEYS.COLLECTION.TRANSACTION, + waitForCollectionCallback: true, + callback: (val) => { + transactions = val; + }, }); const createdTransaction = Object.values(transactions ?? {}).at(0); @@ -1547,15 +1527,13 @@ describe('actions/IOU', () => { await waitForBatchedUpdates(); // Then transaction should have correct currency - const transactions = await new Promise>((resolve) => { - const connection = Onyx.connect({ - key: ONYXKEYS.COLLECTION.TRANSACTION, - waitForCollectionCallback: true, - callback: (val) => { - Onyx.disconnect(connection); - resolve(val); - }, - }); + let transactions: OnyxCollection; + await getOnyxData({ + key: ONYXKEYS.COLLECTION.TRANSACTION, + waitForCollectionCallback: true, + callback: (val) => { + transactions = val; + }, }); const createdTransaction = Object.values(transactions ?? {}).at(0); @@ -1649,15 +1627,13 @@ describe('actions/IOU', () => { await waitForBatchedUpdates(); // Then optimistic data should still be created initially - const transactions = await new Promise>((resolve) => { - const connection = Onyx.connect({ - key: ONYXKEYS.COLLECTION.TRANSACTION, - waitForCollectionCallback: true, - callback: (val) => { - Onyx.disconnect(connection); - resolve(val); - }, - }); + let transactions: OnyxCollection; + await getOnyxData({ + key: ONYXKEYS.COLLECTION.TRANSACTION, + waitForCollectionCallback: true, + callback: (val) => { + transactions = val; + }, }); // Transaction should exist (optimistically) @@ -1708,15 +1684,13 @@ describe('actions/IOU', () => { await waitForBatchedUpdates(); // Then transaction should have correct category and tag - const transactions = await new Promise>((resolve) => { - const connection = Onyx.connect({ - key: ONYXKEYS.COLLECTION.TRANSACTION, - waitForCollectionCallback: true, - callback: (val) => { - Onyx.disconnect(connection); - resolve(val); - }, - }); + let transactions: OnyxCollection; + await getOnyxData({ + key: ONYXKEYS.COLLECTION.TRANSACTION, + waitForCollectionCallback: true, + callback: (val) => { + transactions = val; + }, }); const createdTransaction = Object.values(transactions ?? {}).at(0); @@ -1762,15 +1736,13 @@ describe('actions/IOU', () => { await waitForBatchedUpdates(); // Then transaction should handle large amount correctly - const transactions = await new Promise>((resolve) => { - const connection = Onyx.connect({ - key: ONYXKEYS.COLLECTION.TRANSACTION, - waitForCollectionCallback: true, - callback: (val) => { - Onyx.disconnect(connection); - resolve(val); - }, - }); + let transactions: OnyxCollection; + await getOnyxData({ + key: ONYXKEYS.COLLECTION.TRANSACTION, + waitForCollectionCallback: true, + callback: (val) => { + transactions = val; + }, }); const createdTransaction = Object.values(transactions ?? {}).at(0); @@ -1815,15 +1787,13 @@ describe('actions/IOU', () => { await waitForBatchedUpdates(); // Then transaction should preserve special characters - const transactions = await new Promise>((resolve) => { - const connection = Onyx.connect({ - key: ONYXKEYS.COLLECTION.TRANSACTION, - waitForCollectionCallback: true, - callback: (val) => { - Onyx.disconnect(connection); - resolve(val); - }, - }); + let transactions: OnyxCollection; + await getOnyxData({ + key: ONYXKEYS.COLLECTION.TRANSACTION, + waitForCollectionCallback: true, + callback: (val) => { + transactions = val; + }, }); const createdTransaction = Object.values(transactions ?? {}).at(0); @@ -1852,15 +1822,13 @@ describe('actions/IOU', () => { await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${selfDMReport.reportID}`, selfDMReport); // Get the existing drafts to pass to the function - const allTransactionDrafts = await new Promise>((resolve) => { - const connection = Onyx.connect({ - key: ONYXKEYS.COLLECTION.TRANSACTION_DRAFT, - waitForCollectionCallback: true, - callback: (transactionDrafts) => { - Onyx.disconnect(connection); - resolve(transactionDrafts); - }, - }); + let allTransactionDrafts: OnyxCollection; + await getOnyxData({ + key: ONYXKEYS.COLLECTION.TRANSACTION_DRAFT, + waitForCollectionCallback: true, + callback: (val) => { + allTransactionDrafts = val; + }, }); // Verify existing drafts exist before calling the function @@ -1880,15 +1848,13 @@ describe('actions/IOU', () => { await waitForBatchedUpdates(); // Then the existing draft transactions should be cleared - const updatedTransactionDrafts = await new Promise>((resolve) => { - const connection = Onyx.connect({ - key: ONYXKEYS.COLLECTION.TRANSACTION_DRAFT, - waitForCollectionCallback: true, - callback: (transactionDrafts) => { - Onyx.disconnect(connection); - resolve(transactionDrafts); - }, - }); + let updatedTransactionDrafts: OnyxCollection; + await getOnyxData({ + key: ONYXKEYS.COLLECTION.TRANSACTION_DRAFT, + waitForCollectionCallback: true, + callback: (val) => { + updatedTransactionDrafts = val; + }, }); // Old drafts should be cleared @@ -1928,15 +1894,13 @@ describe('actions/IOU', () => { await waitForBatchedUpdates(); // Then a draft transaction should be created with the correct data - const transactionDrafts = await new Promise>((resolve) => { - const connection = Onyx.connect({ - key: ONYXKEYS.COLLECTION.TRANSACTION_DRAFT, - waitForCollectionCallback: true, - callback: (drafts) => { - Onyx.disconnect(connection); - resolve(drafts); - }, - }); + let transactionDrafts: OnyxCollection; + await getOnyxData({ + key: ONYXKEYS.COLLECTION.TRANSACTION_DRAFT, + waitForCollectionCallback: true, + callback: (val) => { + transactionDrafts = val; + }, }); const draftTransaction = transactionDrafts?.[`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${originalTransaction.transactionID}`]; @@ -1965,15 +1929,13 @@ describe('actions/IOU', () => { await waitForBatchedUpdates(); // Then no draft transaction should be created - const transactionDrafts = await new Promise>((resolve) => { - const connection = Onyx.connect({ - key: ONYXKEYS.COLLECTION.TRANSACTION_DRAFT, - waitForCollectionCallback: true, - callback: (drafts) => { - Onyx.disconnect(connection); - resolve(drafts); - }, - }); + let transactionDrafts: OnyxCollection; + await getOnyxData({ + key: ONYXKEYS.COLLECTION.TRANSACTION_DRAFT, + waitForCollectionCallback: true, + callback: (val) => { + transactionDrafts = val; + }, }); expect(Object.keys(transactionDrafts ?? {}).length).toBe(0); @@ -1997,15 +1959,13 @@ describe('actions/IOU', () => { await waitForBatchedUpdates(); // Then no draft transaction should be created - const transactionDrafts = await new Promise>((resolve) => { - const connection = Onyx.connect({ - key: ONYXKEYS.COLLECTION.TRANSACTION_DRAFT, - waitForCollectionCallback: true, - callback: (drafts) => { - Onyx.disconnect(connection); - resolve(drafts); - }, - }); + let transactionDrafts: OnyxCollection; + await getOnyxData({ + key: ONYXKEYS.COLLECTION.TRANSACTION_DRAFT, + waitForCollectionCallback: true, + callback: (val) => { + transactionDrafts = val; + }, }); expect(Object.keys(transactionDrafts ?? {}).length).toBe(0); @@ -4544,15 +4504,13 @@ describe('actions/IOU', () => { expect(createIOUAction && getOriginalMessage(createIOUAction)?.IOUReportID).toBe(iouReport?.reportID); // When fetching all transactions from Onyx - const allTransactions = await new Promise>((resolve) => { - const connection = Onyx.connect({ - key: ONYXKEYS.COLLECTION.TRANSACTION, - waitForCollectionCallback: true, - callback: (transactions) => { - Onyx.disconnect(connection); - resolve(transactions); - }, - }); + let allTransactions: OnyxCollection; + await getOnyxData({ + key: ONYXKEYS.COLLECTION.TRANSACTION, + waitForCollectionCallback: true, + callback: (val) => { + allTransactions = val; + }, }); // Then we should find a specific transaction with relevant properties From 9dcf54f794d80857163bbe0c30fbc12c40af1cd1 Mon Sep 17 00:00:00 2001 From: Wiktor Gut Date: Mon, 26 Jan 2026 01:12:30 +0100 Subject: [PATCH 18/21] fix tests --- tests/ui/UnreadIndicatorsTest.tsx | 9 ++++++--- tests/unit/GoogleTagManagerTest.tsx | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/tests/ui/UnreadIndicatorsTest.tsx b/tests/ui/UnreadIndicatorsTest.tsx index c1fd100f6f19..0c00ba592e2b 100644 --- a/tests/ui/UnreadIndicatorsTest.tsx +++ b/tests/ui/UnreadIndicatorsTest.tsx @@ -9,7 +9,6 @@ import type {TextStyle, ViewStyle} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import OnyxUtils from 'react-native-onyx/dist/OnyxUtils'; -import getOnyxValue from 'tests/utils/getOnyxValue'; import {setSidebarLoaded} from '@libs/actions/App'; import {trackExpense} from '@libs/actions/IOU'; import {addComment, deleteReportComment, markCommentAsUnread, readNewestAction} from '@libs/actions/Report'; @@ -23,7 +22,7 @@ import FontUtils from '@styles/utils/FontUtils'; import App from '@src/App'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {ReportAction, ReportActions} from '@src/types/onyx'; +import type {RecentWaypoint, ReportAction, ReportActions} from '@src/types/onyx'; import type {NativeNavigationMock} from '../../__mocks__/@react-navigation/native'; import {createRandomReport} from '../utils/collections/reports'; import createRandomTransaction from '../utils/collections/transaction'; @@ -699,7 +698,11 @@ describe('Unread Indicators', () => { comment: 'description', }; - const recentWaypoints = (await getOnyxValue(ONYXKEYS.NVP_RECENT_WAYPOINTS)) ?? []; + let recentWaypoints: RecentWaypoint[] = []; + Onyx.connect({ + key: ONYXKEYS.NVP_RECENT_WAYPOINTS, + callback: (val) => (recentWaypoints = val ?? []), + }); // When the user track an expense on the self DM const participant = {login: USER_A_EMAIL, accountID: USER_A_ACCOUNT_ID}; diff --git a/tests/unit/GoogleTagManagerTest.tsx b/tests/unit/GoogleTagManagerTest.tsx index 39c20d5f30cf..bb7e490708d3 100644 --- a/tests/unit/GoogleTagManagerTest.tsx +++ b/tests/unit/GoogleTagManagerTest.tsx @@ -2,7 +2,6 @@ import {NavigationContainer} from '@react-navigation/native'; import type * as NativeNavigation from '@react-navigation/native'; import {act, render} from '@testing-library/react-native'; import Onyx from 'react-native-onyx'; -import getOnyxValue from 'tests/utils/getOnyxValue'; import {trackExpense} from '@libs/actions/IOU'; import {addPaymentCard, addSubscriptionPaymentCard} from '@libs/actions/PaymentMethods'; import {createWorkspace} from '@libs/actions/Policy/Policy'; @@ -13,6 +12,7 @@ import {getCardForSubscriptionBilling} from '@libs/SubscriptionUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {FundList} from '@src/types/onyx'; +import getOnyxValue from '../utils/getOnyxValue'; import waitForBatchedUpdatesWithAct from '../utils/waitForBatchedUpdatesWithAct'; jest.mock('@libs/GoogleTagManager'); From 32e4901950c9c66137136dbc1705887443c8424e Mon Sep 17 00:00:00 2001 From: Wiktor Gut Date: Wed, 28 Jan 2026 16:47:04 +0100 Subject: [PATCH 19/21] restructure tests --- tests/actions/IOUTest.ts | 1564 +++++++++++++++++++------------------- 1 file changed, 773 insertions(+), 791 deletions(-) diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index 458b6e4de959..65f070088c04 100644 --- a/tests/actions/IOUTest.ts +++ b/tests/actions/IOUTest.ts @@ -925,887 +925,869 @@ describe('actions/IOU', () => { expect(policyOnyx?.employeeList?.[accountant.login].role).toBe(CONST.POLICY.ROLE.ADMIN); }); - // ===================================================== - // UNIT TESTS - Testing trackExpense function behavior - // ===================================================== - - describe('Unit Tests', () => { - it('should create optimistic transaction with correct amount and currency', async () => { - // Given a selfDM report and transaction data - const selfDMReport: Report = { - ...createRandomReport(1, CONST.REPORT.CHAT_TYPE.SELF_DM), - reportID: 'selfDM-unit-1', - }; - const testAmount = 15000; // $150.00 - const testCurrency = 'USD'; - const testMerchant = 'Unit Test Merchant'; - - await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${selfDMReport.reportID}`, selfDMReport); - - // When trackExpense is called with specific amount and currency - trackExpense({ - report: selfDMReport, - isDraftPolicy: false, - action: CONST.IOU.ACTION.CREATE, - participantParams: { - payeeEmail: RORY_EMAIL, - payeeAccountID: RORY_ACCOUNT_ID, - participant: {accountID: RORY_ACCOUNT_ID}, - }, - transactionParams: { - amount: testAmount, - currency: testCurrency, - created: format(new Date(), CONST.DATE.FNS_FORMAT_STRING), - merchant: testMerchant, - billable: false, - }, - isASAPSubmitBetaEnabled: false, - currentUserAccountIDParam: RORY_ACCOUNT_ID, - currentUserEmailParam: RORY_EMAIL, - introSelected: undefined, - activePolicyID: undefined, - quickAction: undefined, - recentWaypoints: [], - }); - await waitForBatchedUpdates(); + it('should create optimistic transaction with correct amount and currency', async () => { + // Given a selfDM report and transaction data + const selfDMReport: Report = { + ...createRandomReport(1, CONST.REPORT.CHAT_TYPE.SELF_DM), + reportID: 'selfDM-unit-1', + }; + const testAmount = 15000; // $150.00 + const testCurrency = 'USD'; + const testMerchant = 'Unit Test Merchant'; - // Then transaction should be created with correct values - let transactions: OnyxCollection; - await getOnyxData({ - key: ONYXKEYS.COLLECTION.TRANSACTION, - waitForCollectionCallback: true, - callback: (val) => { - transactions = val; - }, - }); + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${selfDMReport.reportID}`, selfDMReport); - const createdTransaction = Object.values(transactions ?? {}).at(0); - expect(createdTransaction).toBeTruthy(); - // Amount is stored as negative for track expenses - expect(Math.abs(createdTransaction?.amount ?? 0)).toBe(testAmount); - expect(createdTransaction?.currency).toBe(testCurrency); - expect(createdTransaction?.merchant).toBe(testMerchant); + // When trackExpense is called with specific amount and currency + trackExpense({ + report: selfDMReport, + isDraftPolicy: false, + action: CONST.IOU.ACTION.CREATE, + participantParams: { + payeeEmail: RORY_EMAIL, + payeeAccountID: RORY_ACCOUNT_ID, + participant: {accountID: RORY_ACCOUNT_ID}, + }, + transactionParams: { + amount: testAmount, + currency: testCurrency, + created: format(new Date(), CONST.DATE.FNS_FORMAT_STRING), + merchant: testMerchant, + billable: false, + }, + isASAPSubmitBetaEnabled: false, + currentUserAccountIDParam: RORY_ACCOUNT_ID, + currentUserEmailParam: RORY_EMAIL, + introSelected: undefined, + activePolicyID: undefined, + quickAction: undefined, + recentWaypoints: [], }); + await waitForBatchedUpdates(); - it('should create actionable track expense whisper for selfDM reports', async () => { - // Given a selfDM report - const selfDMReport: Report = { - ...createRandomReport(1, CONST.REPORT.CHAT_TYPE.SELF_DM), - reportID: 'selfDM-unit-2', - }; + // Then transaction should be created with correct values + let transactions: OnyxCollection; + await getOnyxData({ + key: ONYXKEYS.COLLECTION.TRANSACTION, + waitForCollectionCallback: true, + callback: (val) => { + transactions = val; + }, + }); - await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${selfDMReport.reportID}`, selfDMReport); + const createdTransaction = Object.values(transactions ?? {}).at(0); + expect(createdTransaction).toBeTruthy(); + // Amount is stored as negative for track expenses + expect(Math.abs(createdTransaction?.amount ?? 0)).toBe(testAmount); + expect(createdTransaction?.currency).toBe(testCurrency); + expect(createdTransaction?.merchant).toBe(testMerchant); + }); - // When trackExpense is called on selfDM - trackExpense({ - report: selfDMReport, - isDraftPolicy: false, - action: CONST.IOU.ACTION.CREATE, - participantParams: { - payeeEmail: RORY_EMAIL, - payeeAccountID: RORY_ACCOUNT_ID, - participant: {accountID: RORY_ACCOUNT_ID}, - }, - transactionParams: { - amount: 5000, - currency: 'USD', - created: format(new Date(), CONST.DATE.FNS_FORMAT_STRING), - merchant: 'Test Merchant', - billable: false, - }, - isASAPSubmitBetaEnabled: false, - currentUserAccountIDParam: RORY_ACCOUNT_ID, - currentUserEmailParam: RORY_EMAIL, - introSelected: undefined, - activePolicyID: undefined, - quickAction: undefined, - recentWaypoints: [], - }); - await waitForBatchedUpdates(); + it('should create actionable track expense whisper for selfDM reports', async () => { + // Given a selfDM report + const selfDMReport: Report = { + ...createRandomReport(1, CONST.REPORT.CHAT_TYPE.SELF_DM), + reportID: 'selfDM-unit-2', + }; - // Then an actionable track expense whisper should be created - const reportActions = await getOnyxValue(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${selfDMReport.reportID}`); + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${selfDMReport.reportID}`, selfDMReport); - const actionableWhisper = Object.values(reportActions ?? {}).find((action) => isActionableTrackExpense(action)); - expect(actionableWhisper).toBeTruthy(); + // When trackExpense is called on selfDM + trackExpense({ + report: selfDMReport, + isDraftPolicy: false, + action: CONST.IOU.ACTION.CREATE, + participantParams: { + payeeEmail: RORY_EMAIL, + payeeAccountID: RORY_ACCOUNT_ID, + participant: {accountID: RORY_ACCOUNT_ID}, + }, + transactionParams: { + amount: 5000, + currency: 'USD', + created: format(new Date(), CONST.DATE.FNS_FORMAT_STRING), + merchant: 'Test Merchant', + billable: false, + }, + isASAPSubmitBetaEnabled: false, + currentUserAccountIDParam: RORY_ACCOUNT_ID, + currentUserEmailParam: RORY_EMAIL, + introSelected: undefined, + activePolicyID: undefined, + quickAction: undefined, + recentWaypoints: [], }); + await waitForBatchedUpdates(); - it('should set correct tax fields when tax parameters are provided', async () => { - // Given a selfDM report and transaction with tax - const selfDMReport: Report = { - ...createRandomReport(1, CONST.REPORT.CHAT_TYPE.SELF_DM), - reportID: 'selfDM-unit-3', - }; - const testTaxCode = 'TAX_CODE_1'; - const testTaxAmount = 500; // $5.00 tax + // Then an actionable track expense whisper should be created + const reportActions = await getOnyxValue(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${selfDMReport.reportID}`); - await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${selfDMReport.reportID}`, selfDMReport); + const actionableWhisper = Object.values(reportActions ?? {}).find((action) => isActionableTrackExpense(action)); + expect(actionableWhisper).toBeTruthy(); + }); - // When trackExpense is called with tax parameters - trackExpense({ - report: selfDMReport, - isDraftPolicy: false, - action: CONST.IOU.ACTION.CREATE, - participantParams: { - payeeEmail: RORY_EMAIL, - payeeAccountID: RORY_ACCOUNT_ID, - participant: {accountID: RORY_ACCOUNT_ID}, - }, - transactionParams: { - amount: 10000, - currency: 'USD', - created: format(new Date(), CONST.DATE.FNS_FORMAT_STRING), - merchant: 'Tax Test Merchant', - taxCode: testTaxCode, - taxAmount: testTaxAmount, - billable: false, - }, - isASAPSubmitBetaEnabled: false, - currentUserAccountIDParam: RORY_ACCOUNT_ID, - currentUserEmailParam: RORY_EMAIL, - introSelected: undefined, - activePolicyID: undefined, - quickAction: undefined, - recentWaypoints: [], - }); - await waitForBatchedUpdates(); + it('should set correct tax fields when tax parameters are provided', async () => { + // Given a selfDM report and transaction with tax + const selfDMReport: Report = { + ...createRandomReport(1, CONST.REPORT.CHAT_TYPE.SELF_DM), + reportID: 'selfDM-unit-3', + }; + const testTaxCode = 'TAX_CODE_1'; + const testTaxAmount = 500; // $5.00 tax - // Then transaction should have correct tax fields - let transactions: OnyxCollection; - await getOnyxData({ - key: ONYXKEYS.COLLECTION.TRANSACTION, - waitForCollectionCallback: true, - callback: (val) => { - transactions = val; - }, - }); + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${selfDMReport.reportID}`, selfDMReport); + + // When trackExpense is called with tax parameters + trackExpense({ + report: selfDMReport, + isDraftPolicy: false, + action: CONST.IOU.ACTION.CREATE, + participantParams: { + payeeEmail: RORY_EMAIL, + payeeAccountID: RORY_ACCOUNT_ID, + participant: {accountID: RORY_ACCOUNT_ID}, + }, + transactionParams: { + amount: 10000, + currency: 'USD', + created: format(new Date(), CONST.DATE.FNS_FORMAT_STRING), + merchant: 'Tax Test Merchant', + taxCode: testTaxCode, + taxAmount: testTaxAmount, + billable: false, + }, + isASAPSubmitBetaEnabled: false, + currentUserAccountIDParam: RORY_ACCOUNT_ID, + currentUserEmailParam: RORY_EMAIL, + introSelected: undefined, + activePolicyID: undefined, + quickAction: undefined, + recentWaypoints: [], + }); + await waitForBatchedUpdates(); - const createdTransaction = Object.values(transactions ?? {}).at(0); - expect(createdTransaction?.taxCode).toBe(testTaxCode); - expect(createdTransaction?.taxAmount).toBe(testTaxAmount); + // Then transaction should have correct tax fields + let transactions: OnyxCollection; + await getOnyxData({ + key: ONYXKEYS.COLLECTION.TRANSACTION, + waitForCollectionCallback: true, + callback: (val) => { + transactions = val; + }, }); - it('should set billable and reimbursable flags correctly', async () => { - // Given a selfDM report - const selfDMReport: Report = { - ...createRandomReport(1, CONST.REPORT.CHAT_TYPE.SELF_DM), - reportID: 'selfDM-unit-4', - }; + const createdTransaction = Object.values(transactions ?? {}).at(0); + expect(createdTransaction?.taxCode).toBe(testTaxCode); + expect(createdTransaction?.taxAmount).toBe(testTaxAmount); + }); - await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${selfDMReport.reportID}`, selfDMReport); + it('should set billable and reimbursable flags correctly', async () => { + // Given a selfDM report + const selfDMReport: Report = { + ...createRandomReport(1, CONST.REPORT.CHAT_TYPE.SELF_DM), + reportID: 'selfDM-unit-4', + }; - // When trackExpense is called with billable=true and reimbursable=true - trackExpense({ - report: selfDMReport, - isDraftPolicy: false, - action: CONST.IOU.ACTION.CREATE, - participantParams: { - payeeEmail: RORY_EMAIL, - payeeAccountID: RORY_ACCOUNT_ID, - participant: {accountID: RORY_ACCOUNT_ID}, - }, - transactionParams: { - amount: 7500, - currency: 'USD', - created: format(new Date(), CONST.DATE.FNS_FORMAT_STRING), - merchant: 'Billable Test', - billable: true, - reimbursable: true, - }, - isASAPSubmitBetaEnabled: false, - currentUserAccountIDParam: RORY_ACCOUNT_ID, - currentUserEmailParam: RORY_EMAIL, - introSelected: undefined, - activePolicyID: undefined, - quickAction: undefined, - recentWaypoints: [], - }); - await waitForBatchedUpdates(); + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${selfDMReport.reportID}`, selfDMReport); - // Then transaction should have correct billable and reimbursable flags - let transactions: OnyxCollection; - await getOnyxData({ - key: ONYXKEYS.COLLECTION.TRANSACTION, - waitForCollectionCallback: true, - callback: (val) => { - transactions = val; - }, - }); + // When trackExpense is called with billable=true and reimbursable=true + trackExpense({ + report: selfDMReport, + isDraftPolicy: false, + action: CONST.IOU.ACTION.CREATE, + participantParams: { + payeeEmail: RORY_EMAIL, + payeeAccountID: RORY_ACCOUNT_ID, + participant: {accountID: RORY_ACCOUNT_ID}, + }, + transactionParams: { + amount: 7500, + currency: 'USD', + created: format(new Date(), CONST.DATE.FNS_FORMAT_STRING), + merchant: 'Billable Test', + billable: true, + reimbursable: true, + }, + isASAPSubmitBetaEnabled: false, + currentUserAccountIDParam: RORY_ACCOUNT_ID, + currentUserEmailParam: RORY_EMAIL, + introSelected: undefined, + activePolicyID: undefined, + quickAction: undefined, + recentWaypoints: [], + }); + await waitForBatchedUpdates(); - const createdTransaction = Object.values(transactions ?? {}).at(0); - expect(createdTransaction?.billable).toBe(true); - expect(createdTransaction?.reimbursable).toBe(true); + // Then transaction should have correct billable and reimbursable flags + let transactions: OnyxCollection; + await getOnyxData({ + key: ONYXKEYS.COLLECTION.TRANSACTION, + waitForCollectionCallback: true, + callback: (val) => { + transactions = val; + }, }); + + const createdTransaction = Object.values(transactions ?? {}).at(0); + expect(createdTransaction?.billable).toBe(true); + expect(createdTransaction?.reimbursable).toBe(true); }); - // ===================================================== - // FUNCTIONAL TESTS - Testing complete workflows - // ===================================================== + it('should complete full track expense flow: create -> categorize -> submit to workspace', async () => { + // Given a selfDM report, policy, and expense chat + const selfDMReport: Report = { + ...createRandomReport(1, CONST.REPORT.CHAT_TYPE.SELF_DM), + reportID: 'selfDM-func-1', + }; + const policy = createRandomPolicy(1); + const policyExpenseChat: Report = { + ...createRandomReport(2, CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT), + reportID: 'expense-chat-func-1', + policyID: policy.id, + type: CONST.REPORT.TYPE.CHAT, + isOwnPolicyExpenseChat: true, + }; + const policyCategories = createRandomPolicyCategories(3); + const selectedCategory = Object.keys(policyCategories).at(0) ?? ''; - describe('Functional Tests', () => { - it('should complete full track expense flow: create -> categorize -> submit to workspace', async () => { - // Given a selfDM report, policy, and expense chat - const selfDMReport: Report = { - ...createRandomReport(1, CONST.REPORT.CHAT_TYPE.SELF_DM), - reportID: 'selfDM-func-1', - }; - const policy = createRandomPolicy(1); - const policyExpenseChat: Report = { - ...createRandomReport(2, CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT), - reportID: 'expense-chat-func-1', - policyID: policy.id, - type: CONST.REPORT.TYPE.CHAT, - isOwnPolicyExpenseChat: true, - }; - const policyCategories = createRandomPolicyCategories(3); - const selectedCategory = Object.keys(policyCategories).at(0) ?? ''; - - await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${selfDMReport.reportID}`, selfDMReport); - await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${policyExpenseChat.reportID}`, policyExpenseChat); - await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${policy.id}`, policy); - - // STEP 1: Create tracked expense in selfDM - trackExpense({ - report: selfDMReport, - isDraftPolicy: false, - action: CONST.IOU.ACTION.CREATE, - participantParams: { - payeeEmail: RORY_EMAIL, - payeeAccountID: RORY_ACCOUNT_ID, - participant: {accountID: RORY_ACCOUNT_ID}, - }, - transactionParams: { - amount: 25000, - currency: 'USD', - created: format(new Date(), CONST.DATE.FNS_FORMAT_STRING), - merchant: 'Functional Test Restaurant', - billable: false, - }, - isASAPSubmitBetaEnabled: false, - currentUserAccountIDParam: RORY_ACCOUNT_ID, - currentUserEmailParam: RORY_EMAIL, - introSelected: undefined, - activePolicyID: undefined, - quickAction: undefined, - recentWaypoints: [], - }); - await waitForBatchedUpdates(); + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${selfDMReport.reportID}`, selfDMReport); + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${policyExpenseChat.reportID}`, policyExpenseChat); + await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${policy.id}`, policy); - // Verify initial expense was created - const selfDMReportActions = await getOnyxValue(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${selfDMReport.reportID}`); + // STEP 1: Create tracked expense in selfDM + trackExpense({ + report: selfDMReport, + isDraftPolicy: false, + action: CONST.IOU.ACTION.CREATE, + participantParams: { + payeeEmail: RORY_EMAIL, + payeeAccountID: RORY_ACCOUNT_ID, + participant: {accountID: RORY_ACCOUNT_ID}, + }, + transactionParams: { + amount: 25000, + currency: 'USD', + created: format(new Date(), CONST.DATE.FNS_FORMAT_STRING), + merchant: 'Functional Test Restaurant', + billable: false, + }, + isASAPSubmitBetaEnabled: false, + currentUserAccountIDParam: RORY_ACCOUNT_ID, + currentUserEmailParam: RORY_EMAIL, + introSelected: undefined, + activePolicyID: undefined, + quickAction: undefined, + recentWaypoints: [], + }); + await waitForBatchedUpdates(); - expect(Object.values(selfDMReportActions ?? {}).length).toBe(2); - const moneyRequestAction = Object.values(selfDMReportActions ?? {}).find((action) => isMoneyRequestAction(action)); - const actionableWhisper = Object.values(selfDMReportActions ?? {}).find((action) => isActionableTrackExpense(action)); - expect(moneyRequestAction).toBeTruthy(); - expect(actionableWhisper).toBeTruthy(); + // Verify initial expense was created + const selfDMReportActions = await getOnyxValue(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${selfDMReport.reportID}`); - // Get the created transaction - let transactions: OnyxCollection; - await getOnyxData({ - key: ONYXKEYS.COLLECTION.TRANSACTION, - waitForCollectionCallback: true, - callback: (val) => { - transactions = val; - }, - }); - const createdTransaction = Object.values(transactions ?? {}).at(0); - expect(createdTransaction).toBeTruthy(); + expect(Object.values(selfDMReportActions ?? {}).length).toBe(2); + const moneyRequestAction = Object.values(selfDMReportActions ?? {}).find((action) => isMoneyRequestAction(action)); + const actionableWhisper = Object.values(selfDMReportActions ?? {}).find((action) => isActionableTrackExpense(action)); + expect(moneyRequestAction).toBeTruthy(); + expect(actionableWhisper).toBeTruthy(); - // STEP 2: Create draft for categorization - createDraftTransactionAndNavigateToParticipantSelector( - createdTransaction?.transactionID, - selfDMReport.reportID, - CONST.IOU.ACTION.CATEGORIZE, - actionableWhisper?.reportActionID, - {choice: CONST.ONBOARDING_CHOICES.MANAGE_TEAM}, - {}, - undefined, - ); - await waitForBatchedUpdates(); + // Get the created transaction + let transactions: OnyxCollection; + await getOnyxData({ + key: ONYXKEYS.COLLECTION.TRANSACTION, + waitForCollectionCallback: true, + callback: (val) => { + transactions = val; + }, + }); + const createdTransaction = Object.values(transactions ?? {}).at(0); + expect(createdTransaction).toBeTruthy(); - // Verify draft was created - let transactionDrafts: OnyxCollection; - await getOnyxData({ - key: ONYXKEYS.COLLECTION.TRANSACTION_DRAFT, - waitForCollectionCallback: true, - callback: (val) => { - transactionDrafts = val; - }, - }); - const draftTransaction = transactionDrafts?.[`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${createdTransaction?.transactionID}`]; - expect(draftTransaction).toBeTruthy(); + // STEP 2: Create draft for categorization + createDraftTransactionAndNavigateToParticipantSelector( + createdTransaction?.transactionID, + selfDMReport.reportID, + CONST.IOU.ACTION.CATEGORIZE, + actionableWhisper?.reportActionID, + {choice: CONST.ONBOARDING_CHOICES.MANAGE_TEAM}, + {}, + undefined, + ); + await waitForBatchedUpdates(); - // STEP 3: Categorize and submit to workspace - trackExpense({ - report: policyExpenseChat, - isDraftPolicy: false, - action: CONST.IOU.ACTION.CATEGORIZE, - participantParams: { - payeeEmail: RORY_EMAIL, - payeeAccountID: RORY_ACCOUNT_ID, - participant: {reportID: policyExpenseChat.reportID, isPolicyExpenseChat: true}, - }, - policyParams: { - policy, - policyCategories, - }, - transactionParams: { - amount: draftTransaction?.amount ?? 25000, - currency: draftTransaction?.currency ?? 'USD', - created: format(new Date(), CONST.DATE.FNS_FORMAT_STRING), - merchant: draftTransaction?.merchant ?? 'Functional Test Restaurant', - category: selectedCategory, - actionableWhisperReportActionID: draftTransaction?.actionableWhisperReportActionID, - linkedTrackedExpenseReportAction: moneyRequestAction, - linkedTrackedExpenseReportID: selfDMReport.reportID, - }, - isASAPSubmitBetaEnabled: false, - currentUserAccountIDParam: RORY_ACCOUNT_ID, - currentUserEmailParam: RORY_EMAIL, - introSelected: undefined, - activePolicyID: undefined, - quickAction: undefined, - recentWaypoints: [], - }); - await waitForBatchedUpdates(); + // Verify draft was created + let transactionDrafts: OnyxCollection; + await getOnyxData({ + key: ONYXKEYS.COLLECTION.TRANSACTION_DRAFT, + waitForCollectionCallback: true, + callback: (val) => { + transactionDrafts = val; + }, + }); + const draftTransaction = transactionDrafts?.[`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${createdTransaction?.transactionID}`]; + expect(draftTransaction).toBeTruthy(); - // Verify transaction was categorized - let finalTransactions: OnyxCollection; - await getOnyxData({ - key: ONYXKEYS.COLLECTION.TRANSACTION, - waitForCollectionCallback: true, - callback: (val) => { - finalTransactions = val; - }, - }); - const categorizedTransaction = finalTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${createdTransaction?.transactionID}`]; - expect(categorizedTransaction?.category).toBe(selectedCategory); + // STEP 3: Categorize and submit to workspace + trackExpense({ + report: policyExpenseChat, + isDraftPolicy: false, + action: CONST.IOU.ACTION.CATEGORIZE, + participantParams: { + payeeEmail: RORY_EMAIL, + payeeAccountID: RORY_ACCOUNT_ID, + participant: {reportID: policyExpenseChat.reportID, isPolicyExpenseChat: true}, + }, + policyParams: { + policy, + policyCategories, + }, + transactionParams: { + amount: draftTransaction?.amount ?? 25000, + currency: draftTransaction?.currency ?? 'USD', + created: format(new Date(), CONST.DATE.FNS_FORMAT_STRING), + merchant: draftTransaction?.merchant ?? 'Functional Test Restaurant', + category: selectedCategory, + actionableWhisperReportActionID: draftTransaction?.actionableWhisperReportActionID, + linkedTrackedExpenseReportAction: moneyRequestAction, + linkedTrackedExpenseReportID: selfDMReport.reportID, + }, + isASAPSubmitBetaEnabled: false, + currentUserAccountIDParam: RORY_ACCOUNT_ID, + currentUserEmailParam: RORY_EMAIL, + introSelected: undefined, + activePolicyID: undefined, + quickAction: undefined, + recentWaypoints: [], }); + await waitForBatchedUpdates(); - it('should handle expense with attendees correctly', async () => { - // Given a selfDM report with attendees data - const selfDMReport: Report = { - ...createRandomReport(1, CONST.REPORT.CHAT_TYPE.SELF_DM), - reportID: 'selfDM-func-2', - }; - const testAttendees = [ - {email: 'attendee1@test.com', displayName: 'Attendee One', avatarUrl: ''}, - {email: 'attendee2@test.com', displayName: 'Attendee Two', avatarUrl: ''}, - ]; - - await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${selfDMReport.reportID}`, selfDMReport); - - // When trackExpense is called with attendees - trackExpense({ - report: selfDMReport, - isDraftPolicy: false, - action: CONST.IOU.ACTION.CREATE, - participantParams: { - payeeEmail: RORY_EMAIL, - payeeAccountID: RORY_ACCOUNT_ID, - participant: {accountID: RORY_ACCOUNT_ID}, - }, - transactionParams: { - amount: 30000, - currency: 'USD', - created: format(new Date(), CONST.DATE.FNS_FORMAT_STRING), - merchant: 'Team Lunch', - attendees: testAttendees, - billable: false, - }, - isASAPSubmitBetaEnabled: false, - currentUserAccountIDParam: RORY_ACCOUNT_ID, - currentUserEmailParam: RORY_EMAIL, - introSelected: undefined, - activePolicyID: undefined, - quickAction: undefined, - recentWaypoints: [], - }); - await waitForBatchedUpdates(); + // Verify transaction was categorized + let finalTransactions: OnyxCollection; + await getOnyxData({ + key: ONYXKEYS.COLLECTION.TRANSACTION, + waitForCollectionCallback: true, + callback: (val) => { + finalTransactions = val; + }, + }); + const categorizedTransaction = finalTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${createdTransaction?.transactionID}`]; + expect(categorizedTransaction?.category).toBe(selectedCategory); + }); - // Then transaction should have attendees - let transactions: OnyxCollection; - await getOnyxData({ - key: ONYXKEYS.COLLECTION.TRANSACTION, - waitForCollectionCallback: true, - callback: (val) => { - transactions = val; - }, - }); + it('should handle expense with attendees correctly', async () => { + // Given a selfDM report with attendees data + const selfDMReport: Report = { + ...createRandomReport(1, CONST.REPORT.CHAT_TYPE.SELF_DM), + reportID: 'selfDM-func-2', + }; + const testAttendees = [ + {email: 'attendee1@test.com', displayName: 'Attendee One', avatarUrl: ''}, + {email: 'attendee2@test.com', displayName: 'Attendee Two', avatarUrl: ''}, + ]; - const createdTransaction = Object.values(transactions ?? {}).at(0); - expect(createdTransaction?.comment?.attendees).toHaveLength(2); - expect(createdTransaction?.comment?.attendees?.at(0)?.email).toBe('attendee1@test.com'); + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${selfDMReport.reportID}`, selfDMReport); + + // When trackExpense is called with attendees + trackExpense({ + report: selfDMReport, + isDraftPolicy: false, + action: CONST.IOU.ACTION.CREATE, + participantParams: { + payeeEmail: RORY_EMAIL, + payeeAccountID: RORY_ACCOUNT_ID, + participant: {accountID: RORY_ACCOUNT_ID}, + }, + transactionParams: { + amount: 30000, + currency: 'USD', + created: format(new Date(), CONST.DATE.FNS_FORMAT_STRING), + merchant: 'Team Lunch', + attendees: testAttendees, + billable: false, + }, + isASAPSubmitBetaEnabled: false, + currentUserAccountIDParam: RORY_ACCOUNT_ID, + currentUserEmailParam: RORY_EMAIL, + introSelected: undefined, + activePolicyID: undefined, + quickAction: undefined, + recentWaypoints: [], }); + await waitForBatchedUpdates(); - it('should update quick action when tracking expense to policy expense chat', async () => { - // Given a policy expense chat - const policy = createRandomPolicy(1); - const policyExpenseChat: Report = { - ...createRandomReport(1, CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT), - reportID: 'expense-chat-func-2', - policyID: policy.id, - type: CONST.REPORT.TYPE.CHAT, - isOwnPolicyExpenseChat: true, - }; + // Then transaction should have attendees + let transactions: OnyxCollection; + await getOnyxData({ + key: ONYXKEYS.COLLECTION.TRANSACTION, + waitForCollectionCallback: true, + callback: (val) => { + transactions = val; + }, + }); - await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${policyExpenseChat.reportID}`, policyExpenseChat); - await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${policy.id}`, policy); + const createdTransaction = Object.values(transactions ?? {}).at(0); + expect(createdTransaction?.comment?.attendees).toHaveLength(2); + expect(createdTransaction?.comment?.attendees?.at(0)?.email).toBe('attendee1@test.com'); + }); - // When trackExpense is called on policy expense chat - trackExpense({ - report: policyExpenseChat, - isDraftPolicy: false, - action: CONST.IOU.ACTION.CREATE, - participantParams: { - payeeEmail: RORY_EMAIL, - payeeAccountID: RORY_ACCOUNT_ID, - participant: {reportID: policyExpenseChat.reportID, isPolicyExpenseChat: true}, - }, - policyParams: { - policy, - }, - transactionParams: { - amount: 12000, - currency: 'USD', - created: format(new Date(), CONST.DATE.FNS_FORMAT_STRING), - merchant: 'Quick Action Test', - billable: false, - }, - isASAPSubmitBetaEnabled: false, - currentUserAccountIDParam: RORY_ACCOUNT_ID, - currentUserEmailParam: RORY_EMAIL, - introSelected: undefined, - activePolicyID: undefined, - quickAction: undefined, - recentWaypoints: [], - }); - await waitForBatchedUpdates(); + it('should update quick action when tracking expense to policy expense chat', async () => { + // Given a policy expense chat + const policy = createRandomPolicy(1); + const policyExpenseChat: Report = { + ...createRandomReport(1, CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT), + reportID: 'expense-chat-func-2', + policyID: policy.id, + type: CONST.REPORT.TYPE.CHAT, + isOwnPolicyExpenseChat: true, + }; + + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${policyExpenseChat.reportID}`, policyExpenseChat); + await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${policy.id}`, policy); - // Then quick action should be updated - const quickAction = await getOnyxValue(ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE); - expect(quickAction).toBeTruthy(); - expect(quickAction?.chatReportID).toBe(policyExpenseChat.reportID); + // When trackExpense is called on policy expense chat + trackExpense({ + report: policyExpenseChat, + isDraftPolicy: false, + action: CONST.IOU.ACTION.CREATE, + participantParams: { + payeeEmail: RORY_EMAIL, + payeeAccountID: RORY_ACCOUNT_ID, + participant: {reportID: policyExpenseChat.reportID, isPolicyExpenseChat: true}, + }, + policyParams: { + policy, + }, + transactionParams: { + amount: 12000, + currency: 'USD', + created: format(new Date(), CONST.DATE.FNS_FORMAT_STRING), + merchant: 'Quick Action Test', + billable: false, + }, + isASAPSubmitBetaEnabled: false, + currentUserAccountIDParam: RORY_ACCOUNT_ID, + currentUserEmailParam: RORY_EMAIL, + introSelected: undefined, + activePolicyID: undefined, + quickAction: undefined, + recentWaypoints: [], }); + await waitForBatchedUpdates(); + + // Then quick action should be updated + const quickAction = await getOnyxValue(ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE); + expect(quickAction).toBeTruthy(); + expect(quickAction?.chatReportID).toBe(policyExpenseChat.reportID); }); - // ===================================================== - // QA TESTS - Testing edge cases and error scenarios - // ===================================================== + it('should handle tracking expense without merchant gracefully', async () => { + // Given a selfDM report and no merchant + const selfDMReport: Report = { + ...createRandomReport(1, CONST.REPORT.CHAT_TYPE.SELF_DM), + reportID: 'selfDM-qa-1', + }; - describe('QA Tests', () => { - it('should handle tracking expense without merchant gracefully', async () => { - // Given a selfDM report and no merchant - const selfDMReport: Report = { - ...createRandomReport(1, CONST.REPORT.CHAT_TYPE.SELF_DM), - reportID: 'selfDM-qa-1', - }; + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${selfDMReport.reportID}`, selfDMReport); - await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${selfDMReport.reportID}`, selfDMReport); + // When trackExpense is called without merchant + trackExpense({ + report: selfDMReport, + isDraftPolicy: false, + action: CONST.IOU.ACTION.CREATE, + participantParams: { + payeeEmail: RORY_EMAIL, + payeeAccountID: RORY_ACCOUNT_ID, + participant: {accountID: RORY_ACCOUNT_ID}, + }, + transactionParams: { + amount: 5000, + currency: 'USD', + created: format(new Date(), CONST.DATE.FNS_FORMAT_STRING), + merchant: '', // Empty merchant + billable: false, + }, + isASAPSubmitBetaEnabled: false, + currentUserAccountIDParam: RORY_ACCOUNT_ID, + currentUserEmailParam: RORY_EMAIL, + introSelected: undefined, + activePolicyID: undefined, + quickAction: undefined, + recentWaypoints: [], + }); + await waitForBatchedUpdates(); - // When trackExpense is called without merchant - trackExpense({ - report: selfDMReport, - isDraftPolicy: false, - action: CONST.IOU.ACTION.CREATE, - participantParams: { - payeeEmail: RORY_EMAIL, - payeeAccountID: RORY_ACCOUNT_ID, - participant: {accountID: RORY_ACCOUNT_ID}, - }, - transactionParams: { - amount: 5000, - currency: 'USD', - created: format(new Date(), CONST.DATE.FNS_FORMAT_STRING), - merchant: '', // Empty merchant - billable: false, - }, - isASAPSubmitBetaEnabled: false, - currentUserAccountIDParam: RORY_ACCOUNT_ID, - currentUserEmailParam: RORY_EMAIL, - introSelected: undefined, - activePolicyID: undefined, - quickAction: undefined, - recentWaypoints: [], - }); - await waitForBatchedUpdates(); + // Then transaction should still be created + let transactions: OnyxCollection; + await getOnyxData({ + key: ONYXKEYS.COLLECTION.TRANSACTION, + waitForCollectionCallback: true, + callback: (val) => { + transactions = val; + }, + }); - // Then transaction should still be created - let transactions: OnyxCollection; - await getOnyxData({ - key: ONYXKEYS.COLLECTION.TRANSACTION, - waitForCollectionCallback: true, - callback: (val) => { - transactions = val; - }, - }); + expect(Object.values(transactions ?? {}).length).toBeGreaterThan(0); + }); + + it('should handle zero amount expense', async () => { + // Given a selfDM report + const selfDMReport: Report = { + ...createRandomReport(1, CONST.REPORT.CHAT_TYPE.SELF_DM), + reportID: 'selfDM-qa-2', + }; + + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${selfDMReport.reportID}`, selfDMReport); + + // When trackExpense is called with zero amount + trackExpense({ + report: selfDMReport, + isDraftPolicy: false, + action: CONST.IOU.ACTION.CREATE, + participantParams: { + payeeEmail: RORY_EMAIL, + payeeAccountID: RORY_ACCOUNT_ID, + participant: {accountID: RORY_ACCOUNT_ID}, + }, + transactionParams: { + amount: 0, + currency: 'USD', + created: format(new Date(), CONST.DATE.FNS_FORMAT_STRING), + merchant: 'Zero Amount Test', + billable: false, + }, + isASAPSubmitBetaEnabled: false, + currentUserAccountIDParam: RORY_ACCOUNT_ID, + currentUserEmailParam: RORY_EMAIL, + introSelected: undefined, + activePolicyID: undefined, + quickAction: undefined, + recentWaypoints: [], + }); + await waitForBatchedUpdates(); - expect(Object.values(transactions ?? {}).length).toBeGreaterThan(0); + // Then transaction should be created with zero amount + let transactions: OnyxCollection; + await getOnyxData({ + key: ONYXKEYS.COLLECTION.TRANSACTION, + waitForCollectionCallback: true, + callback: (val) => { + transactions = val; + }, }); - it('should handle zero amount expense', async () => { - // Given a selfDM report - const selfDMReport: Report = { - ...createRandomReport(1, CONST.REPORT.CHAT_TYPE.SELF_DM), - reportID: 'selfDM-qa-2', - }; + const createdTransaction = Object.values(transactions ?? {}).at(0); + // trackExpense negates the amount, so 0 becomes -0, defaults to 1 to be able to use Math.abs + expect(createdTransaction).toBeTruthy(); + expect(Object.is(Math.abs(createdTransaction?.amount ?? 1), 0)).toBe(true); + }); - await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${selfDMReport.reportID}`, selfDMReport); + it('should handle different currency codes correctly', async () => { + // Given a selfDM report + const selfDMReport: Report = { + ...createRandomReport(1, CONST.REPORT.CHAT_TYPE.SELF_DM), + reportID: 'selfDM-qa-3', + }; + const testCurrency = 'EUR'; - // When trackExpense is called with zero amount - trackExpense({ - report: selfDMReport, - isDraftPolicy: false, - action: CONST.IOU.ACTION.CREATE, - participantParams: { - payeeEmail: RORY_EMAIL, - payeeAccountID: RORY_ACCOUNT_ID, - participant: {accountID: RORY_ACCOUNT_ID}, - }, - transactionParams: { - amount: 0, - currency: 'USD', - created: format(new Date(), CONST.DATE.FNS_FORMAT_STRING), - merchant: 'Zero Amount Test', - billable: false, - }, - isASAPSubmitBetaEnabled: false, - currentUserAccountIDParam: RORY_ACCOUNT_ID, - currentUserEmailParam: RORY_EMAIL, - introSelected: undefined, - activePolicyID: undefined, - quickAction: undefined, - recentWaypoints: [], - }); - await waitForBatchedUpdates(); + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${selfDMReport.reportID}`, selfDMReport); - // Then transaction should be created with zero amount - let transactions: OnyxCollection; - await getOnyxData({ - key: ONYXKEYS.COLLECTION.TRANSACTION, - waitForCollectionCallback: true, - callback: (val) => { - transactions = val; - }, - }); + // When trackExpense is called with EUR currency + trackExpense({ + report: selfDMReport, + isDraftPolicy: false, + action: CONST.IOU.ACTION.CREATE, + participantParams: { + payeeEmail: RORY_EMAIL, + payeeAccountID: RORY_ACCOUNT_ID, + participant: {accountID: RORY_ACCOUNT_ID}, + }, + transactionParams: { + amount: 8500, + currency: testCurrency, + created: format(new Date(), CONST.DATE.FNS_FORMAT_STRING), + merchant: 'European Merchant', + billable: false, + }, + isASAPSubmitBetaEnabled: false, + currentUserAccountIDParam: RORY_ACCOUNT_ID, + currentUserEmailParam: RORY_EMAIL, + introSelected: undefined, + activePolicyID: undefined, + quickAction: undefined, + recentWaypoints: [], + }); + await waitForBatchedUpdates(); - const createdTransaction = Object.values(transactions ?? {}).at(0); - // trackExpense negates the amount, so 0 becomes -0, defaults to 1 to be able to use Math.abs - expect(createdTransaction).toBeTruthy(); - expect(Object.is(Math.abs(createdTransaction?.amount ?? 1), 0)).toBe(true); + // Then transaction should have correct currency + let transactions: OnyxCollection; + await getOnyxData({ + key: ONYXKEYS.COLLECTION.TRANSACTION, + waitForCollectionCallback: true, + callback: (val) => { + transactions = val; + }, }); - it('should handle different currency codes correctly', async () => { - // Given a selfDM report - const selfDMReport: Report = { - ...createRandomReport(1, CONST.REPORT.CHAT_TYPE.SELF_DM), - reportID: 'selfDM-qa-3', - }; - const testCurrency = 'EUR'; + const createdTransaction = Object.values(transactions ?? {}).at(0); + expect(createdTransaction?.currency).toBe(testCurrency); + }); - await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${selfDMReport.reportID}`, selfDMReport); + it('should create optimistic selfDM report when none exists', async () => { + // Given no selfDM report exists (cleared Onyx) + // The function should handle this by creating an optimistic selfDM - // When trackExpense is called with EUR currency - trackExpense({ - report: selfDMReport, - isDraftPolicy: false, - action: CONST.IOU.ACTION.CREATE, - participantParams: { - payeeEmail: RORY_EMAIL, - payeeAccountID: RORY_ACCOUNT_ID, - participant: {accountID: RORY_ACCOUNT_ID}, - }, - transactionParams: { - amount: 8500, - currency: testCurrency, - created: format(new Date(), CONST.DATE.FNS_FORMAT_STRING), - merchant: 'European Merchant', - billable: false, - }, - isASAPSubmitBetaEnabled: false, - currentUserAccountIDParam: RORY_ACCOUNT_ID, - currentUserEmailParam: RORY_EMAIL, - introSelected: undefined, - activePolicyID: undefined, - quickAction: undefined, - recentWaypoints: [], - }); - await waitForBatchedUpdates(); + // When trackExpense is called with undefined report (will trigger selfDM creation) + trackExpense({ + report: undefined, + isDraftPolicy: false, + action: CONST.IOU.ACTION.CREATE, + participantParams: { + payeeEmail: RORY_EMAIL, + payeeAccountID: RORY_ACCOUNT_ID, + participant: {accountID: RORY_ACCOUNT_ID}, + }, + transactionParams: { + amount: 3000, + currency: 'USD', + created: format(new Date(), CONST.DATE.FNS_FORMAT_STRING), + merchant: 'Optimistic SelfDM Test', + billable: false, + }, + isASAPSubmitBetaEnabled: false, + currentUserAccountIDParam: RORY_ACCOUNT_ID, + currentUserEmailParam: RORY_EMAIL, + introSelected: undefined, + activePolicyID: undefined, + quickAction: undefined, + recentWaypoints: [], + }); + await waitForBatchedUpdates(); - // Then transaction should have correct currency - let transactions: OnyxCollection; - await getOnyxData({ - key: ONYXKEYS.COLLECTION.TRANSACTION, + // Then a selfDM report should be created optimistically + const reports = await new Promise>((resolve) => { + const connection = Onyx.connect({ + key: ONYXKEYS.COLLECTION.REPORT, waitForCollectionCallback: true, callback: (val) => { - transactions = val; + Onyx.disconnect(connection); + resolve(val); }, }); - - const createdTransaction = Object.values(transactions ?? {}).at(0); - expect(createdTransaction?.currency).toBe(testCurrency); }); - it('should create optimistic selfDM report when none exists', async () => { - // Given no selfDM report exists (cleared Onyx) - // The function should handle this by creating an optimistic selfDM + const selfDMReports = Object.values(reports ?? {}).filter((r) => r?.chatType === CONST.REPORT.CHAT_TYPE.SELF_DM); + expect(selfDMReports.length).toBeGreaterThan(0); + }); - // When trackExpense is called with undefined report (will trigger selfDM creation) - trackExpense({ - report: undefined, - isDraftPolicy: false, - action: CONST.IOU.ACTION.CREATE, - participantParams: { - payeeEmail: RORY_EMAIL, - payeeAccountID: RORY_ACCOUNT_ID, - participant: {accountID: RORY_ACCOUNT_ID}, - }, - transactionParams: { - amount: 3000, - currency: 'USD', - created: format(new Date(), CONST.DATE.FNS_FORMAT_STRING), - merchant: 'Optimistic SelfDM Test', - billable: false, - }, - isASAPSubmitBetaEnabled: false, - currentUserAccountIDParam: RORY_ACCOUNT_ID, - currentUserEmailParam: RORY_EMAIL, - introSelected: undefined, - activePolicyID: undefined, - quickAction: undefined, - recentWaypoints: [], - }); - await waitForBatchedUpdates(); + it('should handle API failure gracefully with failure data', async () => { + // Given a selfDM report + const selfDMReport: Report = { + ...createRandomReport(1, CONST.REPORT.CHAT_TYPE.SELF_DM), + reportID: 'selfDM-qa-5', + }; - // Then a selfDM report should be created optimistically - const reports = await new Promise>((resolve) => { - const connection = Onyx.connect({ - key: ONYXKEYS.COLLECTION.REPORT, - waitForCollectionCallback: true, - callback: (val) => { - Onyx.disconnect(connection); - resolve(val); - }, - }); - }); + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${selfDMReport.reportID}`, selfDMReport); - const selfDMReports = Object.values(reports ?? {}).filter((r) => r?.chatType === CONST.REPORT.CHAT_TYPE.SELF_DM); - expect(selfDMReports.length).toBeGreaterThan(0); - }); + // Simulate API failure + mockFetch?.fail?.(); - it('should handle API failure gracefully with failure data', async () => { - // Given a selfDM report - const selfDMReport: Report = { - ...createRandomReport(1, CONST.REPORT.CHAT_TYPE.SELF_DM), - reportID: 'selfDM-qa-5', - }; + // When trackExpense is called + trackExpense({ + report: selfDMReport, + isDraftPolicy: false, + action: CONST.IOU.ACTION.CREATE, + participantParams: { + payeeEmail: RORY_EMAIL, + payeeAccountID: RORY_ACCOUNT_ID, + participant: {accountID: RORY_ACCOUNT_ID}, + }, + transactionParams: { + amount: 5000, + currency: 'USD', + created: format(new Date(), CONST.DATE.FNS_FORMAT_STRING), + merchant: 'Failure Test', + billable: false, + }, + isASAPSubmitBetaEnabled: false, + currentUserAccountIDParam: RORY_ACCOUNT_ID, + currentUserEmailParam: RORY_EMAIL, + introSelected: undefined, + activePolicyID: undefined, + quickAction: undefined, + recentWaypoints: [], + }); + await waitForBatchedUpdates(); - await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${selfDMReport.reportID}`, selfDMReport); + // Then optimistic data should still be created initially + let transactions: OnyxCollection; + await getOnyxData({ + key: ONYXKEYS.COLLECTION.TRANSACTION, + waitForCollectionCallback: true, + callback: (val) => { + transactions = val; + }, + }); - // Simulate API failure - mockFetch?.fail?.(); + // Transaction should exist (optimistically) + expect(Object.values(transactions ?? {}).length).toBeGreaterThan(0); - // When trackExpense is called - trackExpense({ - report: selfDMReport, - isDraftPolicy: false, - action: CONST.IOU.ACTION.CREATE, - participantParams: { - payeeEmail: RORY_EMAIL, - payeeAccountID: RORY_ACCOUNT_ID, - participant: {accountID: RORY_ACCOUNT_ID}, - }, - transactionParams: { - amount: 5000, - currency: 'USD', - created: format(new Date(), CONST.DATE.FNS_FORMAT_STRING), - merchant: 'Failure Test', - billable: false, - }, - isASAPSubmitBetaEnabled: false, - currentUserAccountIDParam: RORY_ACCOUNT_ID, - currentUserEmailParam: RORY_EMAIL, - introSelected: undefined, - activePolicyID: undefined, - quickAction: undefined, - recentWaypoints: [], - }); - await waitForBatchedUpdates(); + // Reset mock to succeed for other tests + mockFetch?.succeed?.(); + }); - // Then optimistic data should still be created initially - let transactions: OnyxCollection; - await getOnyxData({ - key: ONYXKEYS.COLLECTION.TRANSACTION, - waitForCollectionCallback: true, - callback: (val) => { - transactions = val; - }, - }); + it('should handle category and tag together correctly', async () => { + // Given a selfDM report with category and tag + const selfDMReport: Report = { + ...createRandomReport(1, CONST.REPORT.CHAT_TYPE.SELF_DM), + reportID: 'selfDM-qa-6', + }; + const testCategory = 'Travel'; + const testTag = 'Business Trip'; - // Transaction should exist (optimistically) - expect(Object.values(transactions ?? {}).length).toBeGreaterThan(0); + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${selfDMReport.reportID}`, selfDMReport); - // Reset mock to succeed for other tests - mockFetch?.succeed?.(); + // When trackExpense is called with category and tag + trackExpense({ + report: selfDMReport, + isDraftPolicy: false, + action: CONST.IOU.ACTION.CREATE, + participantParams: { + payeeEmail: RORY_EMAIL, + payeeAccountID: RORY_ACCOUNT_ID, + participant: {accountID: RORY_ACCOUNT_ID}, + }, + transactionParams: { + amount: 50000, + currency: 'USD', + created: format(new Date(), CONST.DATE.FNS_FORMAT_STRING), + merchant: 'Airline', + category: testCategory, + tag: testTag, + billable: false, + }, + isASAPSubmitBetaEnabled: false, + currentUserAccountIDParam: RORY_ACCOUNT_ID, + currentUserEmailParam: RORY_EMAIL, + introSelected: undefined, + activePolicyID: undefined, + quickAction: undefined, + recentWaypoints: [], }); + await waitForBatchedUpdates(); - it('should handle category and tag together correctly', async () => { - // Given a selfDM report with category and tag - const selfDMReport: Report = { - ...createRandomReport(1, CONST.REPORT.CHAT_TYPE.SELF_DM), - reportID: 'selfDM-qa-6', - }; - const testCategory = 'Travel'; - const testTag = 'Business Trip'; - - await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${selfDMReport.reportID}`, selfDMReport); - - // When trackExpense is called with category and tag - trackExpense({ - report: selfDMReport, - isDraftPolicy: false, - action: CONST.IOU.ACTION.CREATE, - participantParams: { - payeeEmail: RORY_EMAIL, - payeeAccountID: RORY_ACCOUNT_ID, - participant: {accountID: RORY_ACCOUNT_ID}, - }, - transactionParams: { - amount: 50000, - currency: 'USD', - created: format(new Date(), CONST.DATE.FNS_FORMAT_STRING), - merchant: 'Airline', - category: testCategory, - tag: testTag, - billable: false, - }, - isASAPSubmitBetaEnabled: false, - currentUserAccountIDParam: RORY_ACCOUNT_ID, - currentUserEmailParam: RORY_EMAIL, - introSelected: undefined, - activePolicyID: undefined, - quickAction: undefined, - recentWaypoints: [], - }); - await waitForBatchedUpdates(); - - // Then transaction should have correct category and tag - let transactions: OnyxCollection; - await getOnyxData({ - key: ONYXKEYS.COLLECTION.TRANSACTION, - waitForCollectionCallback: true, - callback: (val) => { - transactions = val; - }, - }); - - const createdTransaction = Object.values(transactions ?? {}).at(0); - expect(createdTransaction?.category).toBe(testCategory); - expect(createdTransaction?.tag).toBe(testTag); + // Then transaction should have correct category and tag + let transactions: OnyxCollection; + await getOnyxData({ + key: ONYXKEYS.COLLECTION.TRANSACTION, + waitForCollectionCallback: true, + callback: (val) => { + transactions = val; + }, }); - it('should handle very large expense amounts', async () => { - // Given a selfDM report - const selfDMReport: Report = { - ...createRandomReport(1, CONST.REPORT.CHAT_TYPE.SELF_DM), - reportID: 'selfDM-qa-7', - }; - const largeAmount = 99999999; // Large amount in cents + const createdTransaction = Object.values(transactions ?? {}).at(0); + expect(createdTransaction?.category).toBe(testCategory); + expect(createdTransaction?.tag).toBe(testTag); + }); - await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${selfDMReport.reportID}`, selfDMReport); + it('should handle very large expense amounts', async () => { + // Given a selfDM report + const selfDMReport: Report = { + ...createRandomReport(1, CONST.REPORT.CHAT_TYPE.SELF_DM), + reportID: 'selfDM-qa-7', + }; + const largeAmount = 99999999; // Large amount in cents - // When trackExpense is called with very large amount - trackExpense({ - report: selfDMReport, - isDraftPolicy: false, - action: CONST.IOU.ACTION.CREATE, - participantParams: { - payeeEmail: RORY_EMAIL, - payeeAccountID: RORY_ACCOUNT_ID, - participant: {accountID: RORY_ACCOUNT_ID}, - }, - transactionParams: { - amount: largeAmount, - currency: 'USD', - created: format(new Date(), CONST.DATE.FNS_FORMAT_STRING), - merchant: 'Large Purchase', - billable: false, - }, - isASAPSubmitBetaEnabled: false, - currentUserAccountIDParam: RORY_ACCOUNT_ID, - currentUserEmailParam: RORY_EMAIL, - introSelected: undefined, - activePolicyID: undefined, - quickAction: undefined, - recentWaypoints: [], - }); - await waitForBatchedUpdates(); + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${selfDMReport.reportID}`, selfDMReport); - // Then transaction should handle large amount correctly - let transactions: OnyxCollection; - await getOnyxData({ - key: ONYXKEYS.COLLECTION.TRANSACTION, - waitForCollectionCallback: true, - callback: (val) => { - transactions = val; - }, - }); + // When trackExpense is called with very large amount + trackExpense({ + report: selfDMReport, + isDraftPolicy: false, + action: CONST.IOU.ACTION.CREATE, + participantParams: { + payeeEmail: RORY_EMAIL, + payeeAccountID: RORY_ACCOUNT_ID, + participant: {accountID: RORY_ACCOUNT_ID}, + }, + transactionParams: { + amount: largeAmount, + currency: 'USD', + created: format(new Date(), CONST.DATE.FNS_FORMAT_STRING), + merchant: 'Large Purchase', + billable: false, + }, + isASAPSubmitBetaEnabled: false, + currentUserAccountIDParam: RORY_ACCOUNT_ID, + currentUserEmailParam: RORY_EMAIL, + introSelected: undefined, + activePolicyID: undefined, + quickAction: undefined, + recentWaypoints: [], + }); + await waitForBatchedUpdates(); - const createdTransaction = Object.values(transactions ?? {}).at(0); - expect(Math.abs(createdTransaction?.amount ?? 0)).toBe(largeAmount); + // Then transaction should handle large amount correctly + let transactions: OnyxCollection; + await getOnyxData({ + key: ONYXKEYS.COLLECTION.TRANSACTION, + waitForCollectionCallback: true, + callback: (val) => { + transactions = val; + }, }); - it('should handle expense with special characters in merchant name', async () => { - // Given a selfDM report - const selfDMReport: Report = { - ...createRandomReport(1, CONST.REPORT.CHAT_TYPE.SELF_DM), - reportID: 'selfDM-qa-8', - }; - const specialMerchant = "McDonald's & Café ñ 日本語"; + const createdTransaction = Object.values(transactions ?? {}).at(0); + expect(Math.abs(createdTransaction?.amount ?? 0)).toBe(largeAmount); + }); - await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${selfDMReport.reportID}`, selfDMReport); + it('should handle expense with special characters in merchant name', async () => { + // Given a selfDM report + const selfDMReport: Report = { + ...createRandomReport(1, CONST.REPORT.CHAT_TYPE.SELF_DM), + reportID: 'selfDM-qa-8', + }; + const specialMerchant = "McDonald's & Café ñ 日本語"; - // When trackExpense is called with special characters in merchant - trackExpense({ - report: selfDMReport, - isDraftPolicy: false, - action: CONST.IOU.ACTION.CREATE, - participantParams: { - payeeEmail: RORY_EMAIL, - payeeAccountID: RORY_ACCOUNT_ID, - participant: {accountID: RORY_ACCOUNT_ID}, - }, - transactionParams: { - amount: 1500, - currency: 'USD', - created: format(new Date(), CONST.DATE.FNS_FORMAT_STRING), - merchant: specialMerchant, - billable: false, - }, - isASAPSubmitBetaEnabled: false, - currentUserAccountIDParam: RORY_ACCOUNT_ID, - currentUserEmailParam: RORY_EMAIL, - introSelected: undefined, - activePolicyID: undefined, - quickAction: undefined, - recentWaypoints: [], - }); - await waitForBatchedUpdates(); + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${selfDMReport.reportID}`, selfDMReport); - // Then transaction should preserve special characters - let transactions: OnyxCollection; - await getOnyxData({ - key: ONYXKEYS.COLLECTION.TRANSACTION, - waitForCollectionCallback: true, - callback: (val) => { - transactions = val; - }, - }); + // When trackExpense is called with special characters in merchant + trackExpense({ + report: selfDMReport, + isDraftPolicy: false, + action: CONST.IOU.ACTION.CREATE, + participantParams: { + payeeEmail: RORY_EMAIL, + payeeAccountID: RORY_ACCOUNT_ID, + participant: {accountID: RORY_ACCOUNT_ID}, + }, + transactionParams: { + amount: 1500, + currency: 'USD', + created: format(new Date(), CONST.DATE.FNS_FORMAT_STRING), + merchant: specialMerchant, + billable: false, + }, + isASAPSubmitBetaEnabled: false, + currentUserAccountIDParam: RORY_ACCOUNT_ID, + currentUserEmailParam: RORY_EMAIL, + introSelected: undefined, + activePolicyID: undefined, + quickAction: undefined, + recentWaypoints: [], + }); + await waitForBatchedUpdates(); - const createdTransaction = Object.values(transactions ?? {}).at(0); - expect(createdTransaction?.merchant).toBe(specialMerchant); + // Then transaction should preserve special characters + let transactions: OnyxCollection; + await getOnyxData({ + key: ONYXKEYS.COLLECTION.TRANSACTION, + waitForCollectionCallback: true, + callback: (val) => { + transactions = val; + }, }); + + const createdTransaction = Object.values(transactions ?? {}).at(0); + expect(createdTransaction?.merchant).toBe(specialMerchant); }); }); From 5e1feae96f96693c6bde75318fe33239cf033c02 Mon Sep 17 00:00:00 2001 From: Wiktor Gut Date: Wed, 28 Jan 2026 17:32:13 +0100 Subject: [PATCH 20/21] separate trachExpense params to getDefaultTrackExpenseParams --- tests/actions/IOUTest.ts | 406 +++++---------------------------------- 1 file changed, 53 insertions(+), 353 deletions(-) diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index 65f070088c04..52f3c54cc5b9 100644 --- a/tests/actions/IOUTest.ts +++ b/tests/actions/IOUTest.ts @@ -925,21 +925,15 @@ describe('actions/IOU', () => { expect(policyOnyx?.employeeList?.[accountant.login].role).toBe(CONST.POLICY.ROLE.ADMIN); }); - it('should create optimistic transaction with correct amount and currency', async () => { - // Given a selfDM report and transaction data - const selfDMReport: Report = { - ...createRandomReport(1, CONST.REPORT.CHAT_TYPE.SELF_DM), - reportID: 'selfDM-unit-1', - }; - const testAmount = 15000; // $150.00 - const testCurrency = 'USD'; - const testMerchant = 'Unit Test Merchant'; - - await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${selfDMReport.reportID}`, selfDMReport); - - // When trackExpense is called with specific amount and currency - trackExpense({ - report: selfDMReport, + /** + * Creates default trackExpense parameters - only override what's needed for each test + */ + function getDefaultTrackExpenseParams( + report: Report | undefined, + transactionOverrides: Partial[0]['transactionParams']> = {}, + ): Parameters[0] { + return { + report, isDraftPolicy: false, action: CONST.IOU.ACTION.CREATE, participantParams: { @@ -948,11 +942,12 @@ describe('actions/IOU', () => { participant: {accountID: RORY_ACCOUNT_ID}, }, transactionParams: { - amount: testAmount, - currency: testCurrency, + amount: 10000, + currency: 'USD', created: format(new Date(), CONST.DATE.FNS_FORMAT_STRING), - merchant: testMerchant, + merchant: 'Test Merchant', billable: false, + ...transactionOverrides, }, isASAPSubmitBetaEnabled: false, currentUserAccountIDParam: RORY_ACCOUNT_ID, @@ -961,7 +956,23 @@ describe('actions/IOU', () => { activePolicyID: undefined, quickAction: undefined, recentWaypoints: [], - }); + }; + } + + it('should create optimistic transaction with correct amount and currency', async () => { + // Given a selfDM report and transaction data + const selfDMReport: Report = { + ...createRandomReport(1, CONST.REPORT.CHAT_TYPE.SELF_DM), + reportID: 'selfDM-unit-1', + }; + const testAmount = 15000; // $150.00 + const testCurrency = 'USD'; + const testMerchant = 'Unit Test Merchant'; + + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${selfDMReport.reportID}`, selfDMReport); + + // When trackExpense is called with specific amount and currency + trackExpense(getDefaultTrackExpenseParams(selfDMReport, {amount: testAmount, currency: testCurrency, merchant: testMerchant})); await waitForBatchedUpdates(); // Then transaction should be created with correct values @@ -992,30 +1003,7 @@ describe('actions/IOU', () => { await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${selfDMReport.reportID}`, selfDMReport); // When trackExpense is called on selfDM - trackExpense({ - report: selfDMReport, - isDraftPolicy: false, - action: CONST.IOU.ACTION.CREATE, - participantParams: { - payeeEmail: RORY_EMAIL, - payeeAccountID: RORY_ACCOUNT_ID, - participant: {accountID: RORY_ACCOUNT_ID}, - }, - transactionParams: { - amount: 5000, - currency: 'USD', - created: format(new Date(), CONST.DATE.FNS_FORMAT_STRING), - merchant: 'Test Merchant', - billable: false, - }, - isASAPSubmitBetaEnabled: false, - currentUserAccountIDParam: RORY_ACCOUNT_ID, - currentUserEmailParam: RORY_EMAIL, - introSelected: undefined, - activePolicyID: undefined, - quickAction: undefined, - recentWaypoints: [], - }); + trackExpense(getDefaultTrackExpenseParams(selfDMReport, {amount: 5000})); await waitForBatchedUpdates(); // Then an actionable track expense whisper should be created @@ -1037,32 +1025,7 @@ describe('actions/IOU', () => { await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${selfDMReport.reportID}`, selfDMReport); // When trackExpense is called with tax parameters - trackExpense({ - report: selfDMReport, - isDraftPolicy: false, - action: CONST.IOU.ACTION.CREATE, - participantParams: { - payeeEmail: RORY_EMAIL, - payeeAccountID: RORY_ACCOUNT_ID, - participant: {accountID: RORY_ACCOUNT_ID}, - }, - transactionParams: { - amount: 10000, - currency: 'USD', - created: format(new Date(), CONST.DATE.FNS_FORMAT_STRING), - merchant: 'Tax Test Merchant', - taxCode: testTaxCode, - taxAmount: testTaxAmount, - billable: false, - }, - isASAPSubmitBetaEnabled: false, - currentUserAccountIDParam: RORY_ACCOUNT_ID, - currentUserEmailParam: RORY_EMAIL, - introSelected: undefined, - activePolicyID: undefined, - quickAction: undefined, - recentWaypoints: [], - }); + trackExpense(getDefaultTrackExpenseParams(selfDMReport, {merchant: 'Tax Test Merchant', taxCode: testTaxCode, taxAmount: testTaxAmount})); await waitForBatchedUpdates(); // Then transaction should have correct tax fields @@ -1090,31 +1053,7 @@ describe('actions/IOU', () => { await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${selfDMReport.reportID}`, selfDMReport); // When trackExpense is called with billable=true and reimbursable=true - trackExpense({ - report: selfDMReport, - isDraftPolicy: false, - action: CONST.IOU.ACTION.CREATE, - participantParams: { - payeeEmail: RORY_EMAIL, - payeeAccountID: RORY_ACCOUNT_ID, - participant: {accountID: RORY_ACCOUNT_ID}, - }, - transactionParams: { - amount: 7500, - currency: 'USD', - created: format(new Date(), CONST.DATE.FNS_FORMAT_STRING), - merchant: 'Billable Test', - billable: true, - reimbursable: true, - }, - isASAPSubmitBetaEnabled: false, - currentUserAccountIDParam: RORY_ACCOUNT_ID, - currentUserEmailParam: RORY_EMAIL, - introSelected: undefined, - activePolicyID: undefined, - quickAction: undefined, - recentWaypoints: [], - }); + trackExpense(getDefaultTrackExpenseParams(selfDMReport, {amount: 7500, merchant: 'Billable Test', billable: true, reimbursable: true})); await waitForBatchedUpdates(); // Then transaction should have correct billable and reimbursable flags @@ -1153,34 +1092,11 @@ describe('actions/IOU', () => { await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${policyExpenseChat.reportID}`, policyExpenseChat); await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${policy.id}`, policy); - // STEP 1: Create tracked expense in selfDM - trackExpense({ - report: selfDMReport, - isDraftPolicy: false, - action: CONST.IOU.ACTION.CREATE, - participantParams: { - payeeEmail: RORY_EMAIL, - payeeAccountID: RORY_ACCOUNT_ID, - participant: {accountID: RORY_ACCOUNT_ID}, - }, - transactionParams: { - amount: 25000, - currency: 'USD', - created: format(new Date(), CONST.DATE.FNS_FORMAT_STRING), - merchant: 'Functional Test Restaurant', - billable: false, - }, - isASAPSubmitBetaEnabled: false, - currentUserAccountIDParam: RORY_ACCOUNT_ID, - currentUserEmailParam: RORY_EMAIL, - introSelected: undefined, - activePolicyID: undefined, - quickAction: undefined, - recentWaypoints: [], - }); + // When trackExpense is called to create a tracked expense in selfDM + trackExpense(getDefaultTrackExpenseParams(selfDMReport, {amount: 25000, merchant: 'Functional Test Restaurant'})); await waitForBatchedUpdates(); - // Verify initial expense was created + // Then the initial expense should be created with report actions const selfDMReportActions = await getOnyxValue(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${selfDMReport.reportID}`); expect(Object.values(selfDMReportActions ?? {}).length).toBe(2); @@ -1189,7 +1105,6 @@ describe('actions/IOU', () => { expect(moneyRequestAction).toBeTruthy(); expect(actionableWhisper).toBeTruthy(); - // Get the created transaction let transactions: OnyxCollection; await getOnyxData({ key: ONYXKEYS.COLLECTION.TRANSACTION, @@ -1201,7 +1116,7 @@ describe('actions/IOU', () => { const createdTransaction = Object.values(transactions ?? {}).at(0); expect(createdTransaction).toBeTruthy(); - // STEP 2: Create draft for categorization + // When a draft is created for categorization createDraftTransactionAndNavigateToParticipantSelector( createdTransaction?.transactionID, selfDMReport.reportID, @@ -1213,7 +1128,7 @@ describe('actions/IOU', () => { ); await waitForBatchedUpdates(); - // Verify draft was created + // Then the draft should be created let transactionDrafts: OnyxCollection; await getOnyxData({ key: ONYXKEYS.COLLECTION.TRANSACTION_DRAFT, @@ -1225,7 +1140,7 @@ describe('actions/IOU', () => { const draftTransaction = transactionDrafts?.[`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${createdTransaction?.transactionID}`]; expect(draftTransaction).toBeTruthy(); - // STEP 3: Categorize and submit to workspace + // When the expense is categorized and submitted to workspace trackExpense({ report: policyExpenseChat, isDraftPolicy: false, @@ -1259,7 +1174,7 @@ describe('actions/IOU', () => { }); await waitForBatchedUpdates(); - // Verify transaction was categorized + // Then the transaction should be categorized let finalTransactions: OnyxCollection; await getOnyxData({ key: ONYXKEYS.COLLECTION.TRANSACTION, @@ -1286,31 +1201,7 @@ describe('actions/IOU', () => { await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${selfDMReport.reportID}`, selfDMReport); // When trackExpense is called with attendees - trackExpense({ - report: selfDMReport, - isDraftPolicy: false, - action: CONST.IOU.ACTION.CREATE, - participantParams: { - payeeEmail: RORY_EMAIL, - payeeAccountID: RORY_ACCOUNT_ID, - participant: {accountID: RORY_ACCOUNT_ID}, - }, - transactionParams: { - amount: 30000, - currency: 'USD', - created: format(new Date(), CONST.DATE.FNS_FORMAT_STRING), - merchant: 'Team Lunch', - attendees: testAttendees, - billable: false, - }, - isASAPSubmitBetaEnabled: false, - currentUserAccountIDParam: RORY_ACCOUNT_ID, - currentUserEmailParam: RORY_EMAIL, - introSelected: undefined, - activePolicyID: undefined, - quickAction: undefined, - recentWaypoints: [], - }); + trackExpense(getDefaultTrackExpenseParams(selfDMReport, {amount: 30000, merchant: 'Team Lunch', attendees: testAttendees})); await waitForBatchedUpdates(); // Then transaction should have attendees @@ -1379,7 +1270,7 @@ describe('actions/IOU', () => { }); it('should handle tracking expense without merchant gracefully', async () => { - // Given a selfDM report and no merchant + // Given a selfDM report const selfDMReport: Report = { ...createRandomReport(1, CONST.REPORT.CHAT_TYPE.SELF_DM), reportID: 'selfDM-qa-1', @@ -1388,30 +1279,7 @@ describe('actions/IOU', () => { await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${selfDMReport.reportID}`, selfDMReport); // When trackExpense is called without merchant - trackExpense({ - report: selfDMReport, - isDraftPolicy: false, - action: CONST.IOU.ACTION.CREATE, - participantParams: { - payeeEmail: RORY_EMAIL, - payeeAccountID: RORY_ACCOUNT_ID, - participant: {accountID: RORY_ACCOUNT_ID}, - }, - transactionParams: { - amount: 5000, - currency: 'USD', - created: format(new Date(), CONST.DATE.FNS_FORMAT_STRING), - merchant: '', // Empty merchant - billable: false, - }, - isASAPSubmitBetaEnabled: false, - currentUserAccountIDParam: RORY_ACCOUNT_ID, - currentUserEmailParam: RORY_EMAIL, - introSelected: undefined, - activePolicyID: undefined, - quickAction: undefined, - recentWaypoints: [], - }); + trackExpense(getDefaultTrackExpenseParams(selfDMReport, {amount: 5000, merchant: ''})); await waitForBatchedUpdates(); // Then transaction should still be created @@ -1437,30 +1305,7 @@ describe('actions/IOU', () => { await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${selfDMReport.reportID}`, selfDMReport); // When trackExpense is called with zero amount - trackExpense({ - report: selfDMReport, - isDraftPolicy: false, - action: CONST.IOU.ACTION.CREATE, - participantParams: { - payeeEmail: RORY_EMAIL, - payeeAccountID: RORY_ACCOUNT_ID, - participant: {accountID: RORY_ACCOUNT_ID}, - }, - transactionParams: { - amount: 0, - currency: 'USD', - created: format(new Date(), CONST.DATE.FNS_FORMAT_STRING), - merchant: 'Zero Amount Test', - billable: false, - }, - isASAPSubmitBetaEnabled: false, - currentUserAccountIDParam: RORY_ACCOUNT_ID, - currentUserEmailParam: RORY_EMAIL, - introSelected: undefined, - activePolicyID: undefined, - quickAction: undefined, - recentWaypoints: [], - }); + trackExpense(getDefaultTrackExpenseParams(selfDMReport, {amount: 0, merchant: 'Zero Amount Test'})); await waitForBatchedUpdates(); // Then transaction should be created with zero amount @@ -1490,30 +1335,7 @@ describe('actions/IOU', () => { await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${selfDMReport.reportID}`, selfDMReport); // When trackExpense is called with EUR currency - trackExpense({ - report: selfDMReport, - isDraftPolicy: false, - action: CONST.IOU.ACTION.CREATE, - participantParams: { - payeeEmail: RORY_EMAIL, - payeeAccountID: RORY_ACCOUNT_ID, - participant: {accountID: RORY_ACCOUNT_ID}, - }, - transactionParams: { - amount: 8500, - currency: testCurrency, - created: format(new Date(), CONST.DATE.FNS_FORMAT_STRING), - merchant: 'European Merchant', - billable: false, - }, - isASAPSubmitBetaEnabled: false, - currentUserAccountIDParam: RORY_ACCOUNT_ID, - currentUserEmailParam: RORY_EMAIL, - introSelected: undefined, - activePolicyID: undefined, - quickAction: undefined, - recentWaypoints: [], - }); + trackExpense(getDefaultTrackExpenseParams(selfDMReport, {amount: 8500, currency: testCurrency, merchant: 'European Merchant'})); await waitForBatchedUpdates(); // Then transaction should have correct currency @@ -1531,34 +1353,10 @@ describe('actions/IOU', () => { }); it('should create optimistic selfDM report when none exists', async () => { - // Given no selfDM report exists (cleared Onyx) - // The function should handle this by creating an optimistic selfDM + // Given no selfDM report exists - // When trackExpense is called with undefined report (will trigger selfDM creation) - trackExpense({ - report: undefined, - isDraftPolicy: false, - action: CONST.IOU.ACTION.CREATE, - participantParams: { - payeeEmail: RORY_EMAIL, - payeeAccountID: RORY_ACCOUNT_ID, - participant: {accountID: RORY_ACCOUNT_ID}, - }, - transactionParams: { - amount: 3000, - currency: 'USD', - created: format(new Date(), CONST.DATE.FNS_FORMAT_STRING), - merchant: 'Optimistic SelfDM Test', - billable: false, - }, - isASAPSubmitBetaEnabled: false, - currentUserAccountIDParam: RORY_ACCOUNT_ID, - currentUserEmailParam: RORY_EMAIL, - introSelected: undefined, - activePolicyID: undefined, - quickAction: undefined, - recentWaypoints: [], - }); + // When trackExpense is called with undefined report + trackExpense(getDefaultTrackExpenseParams(undefined, {amount: 3000, merchant: 'Optimistic SelfDM Test'})); await waitForBatchedUpdates(); // Then a selfDM report should be created optimistically @@ -1585,35 +1383,10 @@ describe('actions/IOU', () => { }; await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${selfDMReport.reportID}`, selfDMReport); - - // Simulate API failure mockFetch?.fail?.(); - // When trackExpense is called - trackExpense({ - report: selfDMReport, - isDraftPolicy: false, - action: CONST.IOU.ACTION.CREATE, - participantParams: { - payeeEmail: RORY_EMAIL, - payeeAccountID: RORY_ACCOUNT_ID, - participant: {accountID: RORY_ACCOUNT_ID}, - }, - transactionParams: { - amount: 5000, - currency: 'USD', - created: format(new Date(), CONST.DATE.FNS_FORMAT_STRING), - merchant: 'Failure Test', - billable: false, - }, - isASAPSubmitBetaEnabled: false, - currentUserAccountIDParam: RORY_ACCOUNT_ID, - currentUserEmailParam: RORY_EMAIL, - introSelected: undefined, - activePolicyID: undefined, - quickAction: undefined, - recentWaypoints: [], - }); + // When trackExpense is called and the API fails + trackExpense(getDefaultTrackExpenseParams(selfDMReport, {amount: 5000, merchant: 'Failure Test'})); await waitForBatchedUpdates(); // Then optimistic data should still be created initially @@ -1626,10 +1399,8 @@ describe('actions/IOU', () => { }, }); - // Transaction should exist (optimistically) expect(Object.values(transactions ?? {}).length).toBeGreaterThan(0); - // Reset mock to succeed for other tests mockFetch?.succeed?.(); }); @@ -1645,32 +1416,7 @@ describe('actions/IOU', () => { await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${selfDMReport.reportID}`, selfDMReport); // When trackExpense is called with category and tag - trackExpense({ - report: selfDMReport, - isDraftPolicy: false, - action: CONST.IOU.ACTION.CREATE, - participantParams: { - payeeEmail: RORY_EMAIL, - payeeAccountID: RORY_ACCOUNT_ID, - participant: {accountID: RORY_ACCOUNT_ID}, - }, - transactionParams: { - amount: 50000, - currency: 'USD', - created: format(new Date(), CONST.DATE.FNS_FORMAT_STRING), - merchant: 'Airline', - category: testCategory, - tag: testTag, - billable: false, - }, - isASAPSubmitBetaEnabled: false, - currentUserAccountIDParam: RORY_ACCOUNT_ID, - currentUserEmailParam: RORY_EMAIL, - introSelected: undefined, - activePolicyID: undefined, - quickAction: undefined, - recentWaypoints: [], - }); + trackExpense(getDefaultTrackExpenseParams(selfDMReport, {amount: 50000, merchant: 'Airline', category: testCategory, tag: testTag})); await waitForBatchedUpdates(); // Then transaction should have correct category and tag @@ -1699,30 +1445,7 @@ describe('actions/IOU', () => { await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${selfDMReport.reportID}`, selfDMReport); // When trackExpense is called with very large amount - trackExpense({ - report: selfDMReport, - isDraftPolicy: false, - action: CONST.IOU.ACTION.CREATE, - participantParams: { - payeeEmail: RORY_EMAIL, - payeeAccountID: RORY_ACCOUNT_ID, - participant: {accountID: RORY_ACCOUNT_ID}, - }, - transactionParams: { - amount: largeAmount, - currency: 'USD', - created: format(new Date(), CONST.DATE.FNS_FORMAT_STRING), - merchant: 'Large Purchase', - billable: false, - }, - isASAPSubmitBetaEnabled: false, - currentUserAccountIDParam: RORY_ACCOUNT_ID, - currentUserEmailParam: RORY_EMAIL, - introSelected: undefined, - activePolicyID: undefined, - quickAction: undefined, - recentWaypoints: [], - }); + trackExpense(getDefaultTrackExpenseParams(selfDMReport, {amount: largeAmount, merchant: 'Large Purchase'})); await waitForBatchedUpdates(); // Then transaction should handle large amount correctly @@ -1750,30 +1473,7 @@ describe('actions/IOU', () => { await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${selfDMReport.reportID}`, selfDMReport); // When trackExpense is called with special characters in merchant - trackExpense({ - report: selfDMReport, - isDraftPolicy: false, - action: CONST.IOU.ACTION.CREATE, - participantParams: { - payeeEmail: RORY_EMAIL, - payeeAccountID: RORY_ACCOUNT_ID, - participant: {accountID: RORY_ACCOUNT_ID}, - }, - transactionParams: { - amount: 1500, - currency: 'USD', - created: format(new Date(), CONST.DATE.FNS_FORMAT_STRING), - merchant: specialMerchant, - billable: false, - }, - isASAPSubmitBetaEnabled: false, - currentUserAccountIDParam: RORY_ACCOUNT_ID, - currentUserEmailParam: RORY_EMAIL, - introSelected: undefined, - activePolicyID: undefined, - quickAction: undefined, - recentWaypoints: [], - }); + trackExpense(getDefaultTrackExpenseParams(selfDMReport, {amount: 1500, merchant: specialMerchant})); await waitForBatchedUpdates(); // Then transaction should preserve special characters From bed3e484651c469396ea3d5e34e2389169f00367 Mon Sep 17 00:00:00 2001 From: Wiktor Gut Date: Wed, 28 Jan 2026 17:40:05 +0100 Subject: [PATCH 21/21] update comment --- src/libs/actions/IOU/index.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/libs/actions/IOU/index.ts b/src/libs/actions/IOU/index.ts index 5f2af27a92ce..22e8c7987696 100644 --- a/src/libs/actions/IOU/index.ts +++ b/src/libs/actions/IOU/index.ts @@ -987,6 +987,11 @@ function getRecentWaypoints(): OnyxTypes.RecentWaypoint[] { return deprecatedRecentWaypoints; } +/** + * This function uses Onyx.connect and should be replaced with useOnyx for reactive data access. + * TODO: remove `getPolicyTagsData` from this file (https://github.com/Expensify/App/issues/72721) + * All usages of this function should be replaced with params passed to the functions or useOnyx hook in React components. + */ function getPolicyTags(): OnyxCollection { return allPolicyTags; }