From 78b65f7b24d8ee1109a3ebff53952a55e028e0f5 Mon Sep 17 00:00:00 2001 From: Maruf Sharifi Date: Sun, 1 Mar 2026 19:49:27 +0430 Subject: [PATCH 1/2] remove manual snapshot updates now that snapshotMergeKeys exists --- src/components/MoneyReportHeader.tsx | 2 +- src/hooks/useSearchBulkActions.ts | 1 - src/libs/actions/Report/index.ts | 24 ------------------- .../PopoverReportActionContextMenu.tsx | 2 +- 4 files changed, 2 insertions(+), 27 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index f4a258b9d6f81..5cb439b9d8b09 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -1575,7 +1575,7 @@ function MoneyReportHeader({ Navigation.goBack(backToRoute); // eslint-disable-next-line @typescript-eslint/no-deprecated InteractionManager.runAfterInteractions(() => { - deleteAppReport(moneyRequestReport, selfDMReport, email ?? '', accountID, reportTransactions, allTransactionViolations, bankAccountList, currentSearchHash); + deleteAppReport(moneyRequestReport, selfDMReport, email ?? '', accountID, reportTransactions, allTransactionViolations, bankAccountList); }); }); }, diff --git a/src/hooks/useSearchBulkActions.ts b/src/hooks/useSearchBulkActions.ts index 9f223d2ac07d5..fdd614d6f4c53 100644 --- a/src/hooks/useSearchBulkActions.ts +++ b/src/hooks/useSearchBulkActions.ts @@ -462,7 +462,6 @@ function useSearchBulkActions({queryJSON}: UseSearchBulkActionsParams) { validTransactions, allTransactionViolations, bankAccountList, - hash, ); } } else { diff --git a/src/libs/actions/Report/index.ts b/src/libs/actions/Report/index.ts index 65c295ba91476..1df0936c02f73 100644 --- a/src/libs/actions/Report/index.ts +++ b/src/libs/actions/Report/index.ts @@ -5343,7 +5343,6 @@ function deleteAppReport( reportTransactions: Record, allTransactionViolations: OnyxCollection, bankAccountList: OnyxEntry, - hash?: number, ) { if (!report?.reportID) { Log.warn('[Report] deleteAppReport called with no reportID'); @@ -5351,16 +5350,6 @@ function deleteAppReport( } const reportID = report.reportID; - // Update search results to mark report as deleted when called from search - if (hash) { - Onyx.merge(`${ONYXKEYS.COLLECTION.SNAPSHOT}${hash}`, { - // @ts-expect-error - will be solved in https://github.com/Expensify/App/issues/73830 - data: { - [`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]: {pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE}, - }, - }); - } - const optimisticData: Array< OnyxUpdate< | typeof ONYXKEYS.COLLECTION.REPORT @@ -5715,19 +5704,6 @@ function deleteAppReport( value: {hasOutstandingChildRequest: report?.hasOutstandingChildRequest}, }); - if (hash) { - failureData.push({ - onyxMethod: Onyx.METHOD.MERGE, - // @ts-expect-error - will be solved in https://github.com/Expensify/App/issues/73830 - key: `${ONYXKEYS.COLLECTION.SNAPSHOT}${hash}`, - value: { - data: { - [`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]: {pendingAction: null}, - }, - }, - }); - } - const parameters: DeleteAppReportParams = { reportID, transactionIDToReportActionAndThreadData: JSON.stringify(transactionIDToReportActionAndThreadData), diff --git a/src/pages/inbox/report/ContextMenu/PopoverReportActionContextMenu.tsx b/src/pages/inbox/report/ContextMenu/PopoverReportActionContextMenu.tsx index 1c8cf5467ccd7..a644ecf5a759f 100644 --- a/src/pages/inbox/report/ContextMenu/PopoverReportActionContextMenu.tsx +++ b/src/pages/inbox/report/ContextMenu/PopoverReportActionContextMenu.tsx @@ -370,7 +370,7 @@ function PopoverReportActionContextMenu({ref}: PopoverReportActionContextMenuPro deleteTransactions([originalMessage.IOUTransactionID], duplicateTransactions, duplicateTransactionViolations, currentSearchHash); } } else if (isReportPreviewAction(reportAction)) { - deleteAppReport(childReport, selfDMReport, email ?? '', currentUserAccountID, reportTransactions, allTransactionViolations, bankAccountList, currentSearchHash); + deleteAppReport(childReport, selfDMReport, email ?? '', currentUserAccountID, reportTransactions, allTransactionViolations, bankAccountList); } else if (reportAction) { // eslint-disable-next-line @typescript-eslint/no-deprecated InteractionManager.runAfterInteractions(() => { From aff815d9b94162020f9f431699e7d42726ad2009 Mon Sep 17 00:00:00 2001 From: Maruf Sharifi Date: Sun, 1 Mar 2026 19:49:38 +0430 Subject: [PATCH 2/2] added unit test --- tests/actions/IOUTest.ts | 47 ++++++++++++++++++++++++++++++ tests/unit/setup/AppSetupTest.ts | 50 ++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 tests/unit/setup/AppSetupTest.ts diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index f173cef2edc29..6f75a3ad60b22 100644 --- a/tests/actions/IOUTest.ts +++ b/tests/actions/IOUTest.ts @@ -7118,6 +7118,53 @@ describe('actions/IOU', () => { afterEach(PusherHelper.teardown); + it('should set pendingAction on the transaction without sending snapshot fanout updates', async () => { + const writeSpy = jest.spyOn(API, 'write').mockImplementation(jest.fn()); + + try { + if (transaction && createIOUAction) { + deleteMoneyRequest({ + transactionID: transaction.transactionID, + reportAction: createIOUAction, + transactions: {}, + violations: {}, + iouReport, + chatReport, + isChatIOUReportArchived: true, + allTransactionViolationsParam: {}, + currentUserAccountID: TEST_USER_ACCOUNT_ID, + }); + } + await waitForBatchedUpdates(); + + const deleteRequestCall = writeSpy.mock.calls.find(([command]) => command === WRITE_COMMANDS.DELETE_MONEY_REQUEST); + expect(deleteRequestCall).toBeDefined(); + if (!deleteRequestCall) { + throw new Error('Expected DELETE_MONEY_REQUEST to be called'); + } + + const [, , requestData] = deleteRequestCall as [ + ApiCommand, + Record, + {optimisticData?: Array<{key: string; value?: {pendingAction?: string | null}}>; failureData?: Array<{key: string; value?: {pendingAction?: string | null}}>} + ]; + + const optimisticData = requestData.optimisticData ?? []; + const failureData = requestData.failureData ?? []; + const transactionKey = `${ONYXKEYS.COLLECTION.TRANSACTION}${transaction?.transactionID}`; + + const optimisticTransactionUpdate = optimisticData.find((update) => update.key === transactionKey); + const failureTransactionUpdate = failureData.find((update) => update.key === transactionKey); + + expect(optimisticTransactionUpdate?.value?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE); + expect(failureTransactionUpdate?.value?.pendingAction).toBeNull(); + expect(optimisticData.some((update) => update.key.startsWith(ONYXKEYS.COLLECTION.SNAPSHOT))).toBe(false); + expect(failureData.some((update) => update.key.startsWith(ONYXKEYS.COLLECTION.SNAPSHOT))).toBe(false); + } finally { + writeSpy.mockRestore(); + } + }); + it('delete an expense (IOU Action and transaction) successfully', async () => { // Given the fetch operations are paused and an expense is initiated mockFetch?.pause?.(); diff --git a/tests/unit/setup/AppSetupTest.ts b/tests/unit/setup/AppSetupTest.ts new file mode 100644 index 0000000000000..d0868e9daa5ad --- /dev/null +++ b/tests/unit/setup/AppSetupTest.ts @@ -0,0 +1,50 @@ +import Onyx from 'react-native-onyx'; +import appSetup from '@src/setup'; + +jest.mock('array.prototype.tosorted', () => ({ + __esModule: true, + default: { + shim: jest.fn(), + }, +})); + +jest.mock('react-native', () => ({ + I18nManager: { + allowRTL: jest.fn(), + forceRTL: jest.fn(), + }, +})); + +jest.mock('react-native-config', () => ({})); + +jest.mock('react-native-onyx', () => ({ + __esModule: true, + default: { + init: jest.fn(), + }, +})); + +jest.mock('@libs/IntlPolyfill', () => jest.fn()); +jest.mock('@userActions/Device', () => ({ + setDeviceID: jest.fn(), +})); +jest.mock('@userActions/OnyxDerived', () => jest.fn()); +jest.mock('@src/setup/addUtilsToWindow', () => jest.fn()); +jest.mock('@src/setup/platformSetup', () => jest.fn()); +jest.mock('@src/setup/telemetry', () => jest.fn()); + +describe('appSetup', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should initialize Onyx with snapshotMergeKeys for pending state propagation', () => { + appSetup(); + + expect(Onyx.init).toHaveBeenCalledWith( + expect.objectContaining({ + snapshotMergeKeys: ['pendingAction', 'pendingFields'], + }), + ); + }); +});