Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/libs/actions/Policy/Policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4299,19 +4299,23 @@ function createWorkspaceFromIOUPayment(
});
}

// To optimistically remove the GBR from the DM we need to update the hasOutstandingChildRequest param to false
// To optimistically remove the GBR from the DM we need to update the hasOutstandingChildRequest param to false.
// We also clear iouReportID so the moved IOU report is no longer resolved from this DM when a new expense is
// created there (otherwise the moved report would be reused and "reappear" in the DM preview).
optimisticData.push({
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT}${oldChatReportID}`,
value: {
hasOutstandingChildRequest: false,
iouReportID: null,
},
});
failureData.push({
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT}${oldChatReportID}`,
value: {
hasOutstandingChildRequest: true,
iouReportID,
},
});

Expand Down
58 changes: 58 additions & 0 deletions tests/actions/PolicyTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6765,5 +6765,63 @@ describe('actions/Policy', () => {
apiWriteSpy.mockRestore();
isIOUReportUsingReportSpy.mockRestore();
});

it('should clear iouReportID on the old DM chat optimistically and restore it on failure', async () => {
await Onyx.set(ONYXKEYS.SESSION, {email: ESH_EMAIL, accountID: ESH_ACCOUNT_ID});
await waitForBatchedUpdates();

const employeeAccountID = 400;
const iouReportOwnerEmail = 'employee@example.com';
const oldChatReportID = '901';
const movedIouReportID = '900';

const iouReport: Report = {
...createRandomReport(1, undefined),
reportID: movedIouReportID,
type: CONST.REPORT.TYPE.IOU,
ownerAccountID: employeeAccountID,
chatReportID: oldChatReportID,
policyID: 'oldPolicyID',
currency: CONST.CURRENCY.USD,
total: 1500,
};

await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`, iouReport);
await waitForBatchedUpdates();

const isIOUReportUsingReportSpy = jest.spyOn(ReportUtils, 'isIOUReportUsingReport').mockReturnValue(true);
const apiWriteSpy = jest.spyOn(require('@libs/API'), 'write').mockImplementation(() => Promise.resolve());

const mockTranslate = ((key: string) => key) as unknown as Parameters<typeof Policy.createWorkspaceFromIOUPayment>[8];
Policy.createWorkspaceFromIOUPayment(iouReport, undefined, ESH_ACCOUNT_ID, ESH_EMAIL, iouReportOwnerEmail, undefined, CONST.CURRENCY.USD, undefined, mockTranslate, {});
await waitForBatchedUpdates();

const writeOptions = apiWriteSpy.mock.calls.at(0)?.at(2) as {
optimisticData?: Array<{onyxMethod?: string; key?: string; value?: Record<string, unknown> | null}>;
failureData?: Array<{onyxMethod?: string; key?: string; value?: Record<string, unknown> | null}>;
};

const oldChatKey = `${ONYXKEYS.COLLECTION.REPORT}${oldChatReportID}`;
const optimisticOldChatUpdate = (writeOptions?.optimisticData ?? []).find(
(update) => update.key === oldChatKey && (update.value as {iouReportID?: string | null} | null)?.iouReportID !== undefined,
);
const failureOldChatUpdate = (writeOptions?.failureData ?? []).find(
(update) => update.key === oldChatKey && (update.value as {iouReportID?: string | null} | null)?.iouReportID !== undefined,
);

// Optimistic update should clear the dangling pointer so a fresh IOU report is built
// for the next expense in this DM (the moved report otherwise gets reused via getMoneyRequestInformation).
expect(optimisticOldChatUpdate).toBeDefined();
expect((optimisticOldChatUpdate?.value as {iouReportID?: string | null})?.iouReportID).toBeNull();
expect((optimisticOldChatUpdate?.value as {hasOutstandingChildRequest?: boolean})?.hasOutstandingChildRequest).toBe(false);

// Failure rollback must restore the previous iouReportID so the DM chat is back to its prior state.
expect(failureOldChatUpdate).toBeDefined();
expect((failureOldChatUpdate?.value as {iouReportID?: string | null})?.iouReportID).toBe(movedIouReportID);
expect((failureOldChatUpdate?.value as {hasOutstandingChildRequest?: boolean})?.hasOutstandingChildRequest).toBe(true);

apiWriteSpy.mockRestore();
isIOUReportUsingReportSpy.mockRestore();
});
});
});
Loading