Skip to content
Open
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
3 changes: 3 additions & 0 deletions src/components/MoneyReportHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,7 @@ function MoneyReportHeader({
const canTriggerAutomaticPDFDownload = useRef(false);
const hasFinishedPDFDownload = reportPDFFilename && reportPDFFilename !== CONST.REPORT_DETAILS_MENU_ITEM.ERROR;

const [recentWaypoints] = useOnyx(ONYXKEYS.NVP_RECENT_WAYPOINTS, {canBeMissing: true});
const [quickAction] = useOnyx(ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE, {canBeMissing: true});
const [isSelfTourViewed = false] = useOnyx(ONYXKEYS.NVP_ONBOARDING, {canBeMissing: true, selector: hasSeenTourSelector});

Expand Down Expand Up @@ -708,6 +709,7 @@ function MoneyReportHeader({
targetReport: activePolicyExpenseChat,
betas,
personalDetails,
recentWaypoints,
});
}
},
Expand All @@ -724,6 +726,7 @@ function MoneyReportHeader({
isSelfTourViewed,
betas,
personalDetails,
recentWaypoints,
],
);

Expand Down
3 changes: 3 additions & 0 deletions src/components/MoneyRequestHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre
const isASAPSubmitBetaEnabled = isBetaEnabled(CONST.BETAS.ASAP_SUBMIT);
const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED, {canBeMissing: true});
const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID, {canBeMissing: true});
const [recentWaypoints] = useOnyx(ONYXKEYS.NVP_RECENT_WAYPOINTS, {canBeMissing: true});

const {isDelegateAccessRestricted, showDelegateNoAccessModal} = useContext(DelegateNoAccessContext);
const isReportInRHP = route.name === SCREENS.RIGHT_MODAL.SEARCH_REPORT;
Expand Down Expand Up @@ -214,6 +215,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre
targetReport: activePolicyExpenseChat,
betas,
personalDetails,
recentWaypoints,
});
}
},
Expand All @@ -230,6 +232,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre
isSelfTourViewed,
betas,
personalDetails,
recentWaypoints,
],
);

Expand Down
4 changes: 2 additions & 2 deletions src/libs/actions/IOU/Duplicate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ import {
getCurrentUserEmail,
getMoneyRequestParticipantsFromReport,
getPolicyTags,
getRecentWaypoints,
getUserAccountID,
requestMoney,
submitPerDiemExpense,
Expand Down Expand Up @@ -505,6 +504,7 @@ type DuplicateExpenseTransactionParams = {
targetReport?: OnyxTypes.Report;
betas: OnyxEntry<OnyxTypes.Beta[]>;
personalDetails: OnyxEntry<OnyxTypes.PersonalDetailsList>;
recentWaypoints: OnyxEntry<OnyxTypes.RecentWaypoint[]>;
};

function duplicateExpenseTransaction({
Expand All @@ -523,14 +523,14 @@ function duplicateExpenseTransaction({
targetReport,
betas,
personalDetails,
recentWaypoints,
}: DuplicateExpenseTransactionParams) {
if (!transaction) {
return;
}

const userAccountID = getUserAccountID();
const currentUserEmail = getCurrentUserEmail();
const recentWaypoints = getRecentWaypoints();

const participants = getMoneyRequestParticipantsFromReport(targetReport, userAccountID);
const transactionDetails = getTransactionDetails(transaction);
Expand Down
122 changes: 120 additions & 2 deletions tests/actions/IOUTest/DuplicateTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import IntlStore from '@src/languages/IntlStore';
import OnyxUpdateManager from '@src/libs/actions/OnyxUpdateManager';
import * as API from '@src/libs/API';
import ONYXKEYS from '@src/ONYXKEYS';
import type {OriginalMessageIOU, Report, ReportActions} from '@src/types/onyx';
import type {OriginalMessageIOU, RecentWaypoint, Report, ReportActions} from '@src/types/onyx';
import type ReportAction from '@src/types/onyx/ReportAction';
import type {ReportActionsCollectionDataSet} from '@src/types/onyx/ReportAction';
import type Transaction from '@src/types/onyx/Transaction';
Expand Down Expand Up @@ -890,6 +890,7 @@ describe('actions/Duplicate', () => {

describe('duplicateExpenseTransaction', () => {
let writeSpy: jest.SpyInstance;
let recentWaypoints: RecentWaypoint[] = [];

const mockOptimisticChatReportID = '789';
const mockOptimisticIOUReportID = '987';
Expand All @@ -900,7 +901,7 @@ describe('actions/Duplicate', () => {
const policyExpenseChat = createRandomReport(1, CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT);
const fakePolicyCategories = createRandomPolicyCategories(3);

beforeEach(() => {
beforeEach(async () => {
jest.clearAllMocks();
global.fetch = getGlobalFetchMock();
// eslint-disable-next-line rulesdir/no-multiple-api-calls
Expand All @@ -917,6 +918,7 @@ describe('actions/Duplicate', () => {
}
return Promise.resolve();
});
recentWaypoints = (await getOnyxValue(ONYXKEYS.NVP_RECENT_WAYPOINTS)) ?? [];
return Onyx.clear();
});

Expand Down Expand Up @@ -952,6 +954,7 @@ describe('actions/Duplicate', () => {
targetReport: policyExpenseChat,
betas: [CONST.BETAS.ALL],
personalDetails: {},
recentWaypoints,
});

await waitForBatchedUpdates();
Expand Down Expand Up @@ -1011,6 +1014,7 @@ describe('actions/Duplicate', () => {
targetReport: policyExpenseChat,
betas: [CONST.BETAS.ALL],
personalDetails: {},
recentWaypoints,
});

await waitForBatchedUpdates();
Expand Down Expand Up @@ -1070,6 +1074,7 @@ describe('actions/Duplicate', () => {
targetReport: policyExpenseChat,
betas: [CONST.BETAS.ALL],
personalDetails: {},
recentWaypoints,
});

await waitForBatchedUpdates();
Expand Down Expand Up @@ -1146,6 +1151,7 @@ describe('actions/Duplicate', () => {
targetReport: policyExpenseChat,
betas: [CONST.BETAS.ALL],
personalDetails: {},
recentWaypoints,
});

await waitForBatchedUpdates();
Expand All @@ -1161,6 +1167,118 @@ describe('actions/Duplicate', () => {
const apiParams = requestMoneyCall?.[1] as Record<string, unknown>;
expect(apiParams?.transactionThreadReportID).not.toBe(existingLinkedReportActionChildReportID);
});

it('should call trackExpense API when targetPolicy is not provided', async () => {
const {waypoints, ...restOfComment} = mockTransaction.comment ?? {};
const mockCashExpenseTransaction = {
...mockTransaction,
amount: mockTransaction.amount * -1,
comment: {
...restOfComment,
},
};

await Onyx.clear();

// When duplicating the transaction without targetPolicy
duplicateExpenseTransaction({
transaction: mockCashExpenseTransaction,
optimisticChatReportID: mockOptimisticChatReportID,
optimisticIOUReportID: mockOptimisticIOUReportID,
isASAPSubmitBetaEnabled: mockIsASAPSubmitBetaEnabled,
introSelected: undefined,
activePolicyID: undefined,
quickAction: undefined,
policyRecentlyUsedCurrencies: [],
isSelfTourViewed: false,
customUnitPolicyID: '',
targetPolicy: undefined,
targetPolicyCategories: undefined,
targetReport: undefined,
betas: [CONST.BETAS.ALL],
personalDetails: {},
recentWaypoints,
});

await waitForBatchedUpdates();

// Then the API should have been called with TRACK_EXPENSE instead of REQUEST_MONEY
const trackExpenseCall = writeSpy.mock.calls.find((call: [string, Record<string, unknown>]) => call[0] === WRITE_COMMANDS.TRACK_EXPENSE);
const requestMoneyCall = writeSpy.mock.calls.find((call: [string, Record<string, unknown>]) => call[0] === WRITE_COMMANDS.REQUEST_MONEY);

expect(trackExpenseCall).toBeDefined();
expect(requestMoneyCall).toBeUndefined();

// Then a transaction should be created successfully
let duplicatedTransaction: OnyxEntry<Transaction>;

await getOnyxData({
key: ONYXKEYS.COLLECTION.TRANSACTION,
waitForCollectionCallback: true,
callback: (allTransactions) => {
duplicatedTransaction = Object.values(allTransactions ?? {}).find((t) => !!t);
},
});

expect(duplicatedTransaction).toBeDefined();
expect(duplicatedTransaction?.transactionID).not.toBe(mockCashExpenseTransaction.transactionID);
});

it('should preserve all transaction fields when duplicating Cash expense', async () => {
// Given a transaction with all fields populated using mockTransaction values
const {waypoints, ...restOfComment} = mockTransaction.comment ?? {};
const mockCashExpense: Transaction = {
...mockTransaction,
amount: mockTransaction.amount * -1,
comment: {
...restOfComment,
},
};

await Onyx.clear();

// When duplicating the transaction
duplicateExpenseTransaction({
transaction: mockCashExpense,
optimisticChatReportID: mockOptimisticChatReportID,
optimisticIOUReportID: mockOptimisticIOUReportID,
isASAPSubmitBetaEnabled: mockIsASAPSubmitBetaEnabled,
introSelected: undefined,
activePolicyID: undefined,
quickAction: undefined,
policyRecentlyUsedCurrencies: [],
isSelfTourViewed: false,
customUnitPolicyID: '',
targetPolicy: mockPolicy,
targetPolicyCategories: fakePolicyCategories,
targetReport: policyExpenseChat,
betas: [CONST.BETAS.ALL],
personalDetails: {},
recentWaypoints,
});

await waitForBatchedUpdates();

// The duplicated transaction should have all fields preserved
let duplicatedTransaction: OnyxEntry<Transaction>;

await getOnyxData({
key: ONYXKEYS.COLLECTION.TRANSACTION,
waitForCollectionCallback: true,
callback: (allTransactions) => {
duplicatedTransaction = Object.values(allTransactions ?? {}).find((t) => !!t);
},
});

expect(duplicatedTransaction).toBeDefined();
expect(duplicatedTransaction?.transactionID).not.toBe(mockCashExpense.transactionID);
expect(duplicatedTransaction?.category).toBe(mockTransaction.category);
expect(duplicatedTransaction?.tag).toBe(mockTransaction.tag);
expect(duplicatedTransaction?.billable).toBe(mockTransaction.billable);
expect(duplicatedTransaction?.reimbursable).toBe(mockTransaction.reimbursable);
expect(duplicatedTransaction?.currency).toBe(mockTransaction.currency);
expect(Math.abs(duplicatedTransaction?.amount ?? 0)).toBe(Math.abs(mockTransaction.amount));
});
});

describe('resolveDuplicate', () => {
Expand Down
Loading