Skip to content

Commit e0976db

Browse files
committed
Add unit tests for getReportFromHoldRequestsOnyxData totals update
1 parent 964609b commit e0976db

1 file changed

Lines changed: 166 additions & 1 deletion

File tree

tests/actions/IOUTest/HoldTest.ts

Lines changed: 166 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
22
import Onyx from 'react-native-onyx';
33
import type {OnyxEntry, OnyxInputValue} from 'react-native-onyx';
4-
import {putOnHold, putTransactionsOnHold, unholdRequest} from '@libs/actions/IOU/Hold';
4+
import {getReportFromHoldRequestsOnyxData, putOnHold, putTransactionsOnHold, unholdRequest} from '@libs/actions/IOU/Hold';
55
import initOnyxDerivedValues from '@libs/actions/OnyxDerived';
66
import {getMicroSecondOnyxErrorWithTranslationKey} from '@libs/ErrorUtils';
77
import type * as PolicyUtils from '@libs/PolicyUtils';
@@ -564,4 +564,169 @@ describe('actions/IOU/Hold', () => {
564564
});
565565
});
566566
});
567+
568+
describe('getReportFromHoldRequestsOnyxData', () => {
569+
const buildHeldTransaction = (reportID: string, amount: number) => {
570+
const transaction = buildOptimisticTransaction({
571+
transactionParams: {amount, currency: 'USD', reportID},
572+
});
573+
return {
574+
...transaction,
575+
comment: {...transaction.comment, hold: 'hold-reason'},
576+
};
577+
};
578+
579+
const buildScenario = (overrides: {total: number; nonReimbursableTotal: number; unheldTotal?: number; unheldNonReimbursableTotal?: number; heldAmount?: number}) => {
580+
const baseIouReport = buildOptimisticIOUReport(1, 2, overrides.total, '99', 'USD');
581+
const iouReport: Report = {
582+
...baseIouReport,
583+
total: overrides.total,
584+
nonReimbursableTotal: overrides.nonReimbursableTotal,
585+
unheldTotal: overrides.unheldTotal,
586+
unheldNonReimbursableTotal: overrides.unheldNonReimbursableTotal,
587+
};
588+
const chatReport: Report = {
589+
reportID: '99',
590+
iouReportID: iouReport.reportID,
591+
lastVisibleActionCreated: '2026-01-01 00:00:00.000',
592+
} as Report;
593+
594+
const heldAmount = overrides.heldAmount ?? 0;
595+
const heldTransaction = heldAmount > 0 ? buildHeldTransaction(iouReport.reportID, heldAmount) : undefined;
596+
const heldIouAction = heldTransaction
597+
? buildOptimisticIOUReportAction({
598+
type: CONST.IOU.REPORT_ACTION_TYPE.CREATE,
599+
amount: heldTransaction.amount,
600+
currency: 'USD',
601+
comment: '',
602+
participants: [],
603+
transactionID: heldTransaction.transactionID,
604+
})
605+
: undefined;
606+
607+
const onyxData: Record<string, unknown> = {
608+
[`${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`]: iouReport,
609+
[`${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`]: chatReport,
610+
};
611+
if (heldTransaction) {
612+
onyxData[`${ONYXKEYS.COLLECTION.TRANSACTION}${heldTransaction.transactionID}`] = heldTransaction;
613+
}
614+
if (heldIouAction) {
615+
onyxData[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`] = {
616+
[heldIouAction.reportActionID]: heldIouAction,
617+
};
618+
}
619+
620+
return {chatReport, iouReport, onyxData};
621+
};
622+
623+
test('should optimistically update original report total to unheldTotal when held transactions are moved out', () => {
624+
const {chatReport, iouReport, onyxData} = buildScenario({
625+
total: 300,
626+
nonReimbursableTotal: 50,
627+
unheldTotal: 200,
628+
unheldNonReimbursableTotal: 30,
629+
heldAmount: 100,
630+
});
631+
632+
return waitForBatchedUpdates()
633+
.then(() => Onyx.multiSet(onyxData))
634+
.then(() => {
635+
const result = getReportFromHoldRequestsOnyxData({
636+
chatReport,
637+
iouReport,
638+
recipient: {accountID: 1},
639+
policy: undefined,
640+
betas: [],
641+
});
642+
const totalsUpdate = result.optimisticData.find((entry) => entry.onyxMethod === Onyx.METHOD.MERGE && entry.key === `${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`);
643+
expect(totalsUpdate).toBeDefined();
644+
expect(totalsUpdate?.value).toEqual({total: 200, nonReimbursableTotal: 30});
645+
});
646+
});
647+
648+
test('should include matching failureData entry to restore original totals on rollback', () => {
649+
const {chatReport, iouReport, onyxData} = buildScenario({
650+
total: 300,
651+
nonReimbursableTotal: 50,
652+
unheldTotal: 200,
653+
unheldNonReimbursableTotal: 30,
654+
heldAmount: 100,
655+
});
656+
657+
return waitForBatchedUpdates()
658+
.then(() => Onyx.multiSet(onyxData))
659+
.then(() => {
660+
const result = getReportFromHoldRequestsOnyxData({
661+
chatReport,
662+
iouReport,
663+
recipient: {accountID: 1},
664+
policy: undefined,
665+
betas: [],
666+
});
667+
const restorationEntries = result.failureData.filter(
668+
(entry) => entry.onyxMethod === Onyx.METHOD.MERGE && entry.key === `${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`,
669+
);
670+
const totalsRestore = restorationEntries.find((entry) => {
671+
const value = entry.value as Partial<Report> | undefined;
672+
return value?.total !== undefined || value?.nonReimbursableTotal !== undefined;
673+
});
674+
expect(totalsRestore?.value).toEqual({total: 300, nonReimbursableTotal: 50});
675+
});
676+
});
677+
678+
test('should not push a totals update when no held transactions exist', () => {
679+
const {chatReport, iouReport, onyxData} = buildScenario({
680+
total: 300,
681+
nonReimbursableTotal: 50,
682+
unheldTotal: 300,
683+
unheldNonReimbursableTotal: 50,
684+
heldAmount: 0,
685+
});
686+
687+
return waitForBatchedUpdates()
688+
.then(() => Onyx.multiSet(onyxData))
689+
.then(() => {
690+
const result = getReportFromHoldRequestsOnyxData({
691+
chatReport,
692+
iouReport,
693+
recipient: {accountID: 1},
694+
policy: undefined,
695+
betas: [],
696+
});
697+
const totalsUpdates = result.optimisticData.filter((entry) => {
698+
const value = entry.value as Partial<Report> | undefined;
699+
return entry.key === `${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}` && (value?.total !== undefined || value?.nonReimbursableTotal !== undefined);
700+
});
701+
expect(totalsUpdates).toEqual([]);
702+
});
703+
});
704+
705+
test('should not push a totals update when iouReport.unheldTotal is undefined', () => {
706+
const {chatReport, iouReport, onyxData} = buildScenario({
707+
total: 300,
708+
nonReimbursableTotal: 50,
709+
unheldTotal: undefined,
710+
unheldNonReimbursableTotal: undefined,
711+
heldAmount: 100,
712+
});
713+
714+
return waitForBatchedUpdates()
715+
.then(() => Onyx.multiSet(onyxData))
716+
.then(() => {
717+
const result = getReportFromHoldRequestsOnyxData({
718+
chatReport,
719+
iouReport,
720+
recipient: {accountID: 1},
721+
policy: undefined,
722+
betas: [],
723+
});
724+
const totalsUpdates = result.optimisticData.filter((entry) => {
725+
const value = entry.value as Partial<Report> | undefined;
726+
return entry.key === `${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}` && (value?.total !== undefined || value?.nonReimbursableTotal !== undefined);
727+
});
728+
expect(totalsUpdates).toEqual([]);
729+
});
730+
});
731+
});
567732
});

0 commit comments

Comments
 (0)