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: 2 additions & 1 deletion src/components/Search/FilterComponents/InSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ function InSelector({value = [], selectionListTextInputStyle, selectionListStyle
conciergeReportID,
reportAttributesDerived,
isTrackIntentUser,
currentUserAccountID,
}),
),
isSelected,
Expand All @@ -102,7 +103,7 @@ function InSelector({value = [], selectionListTextInputStyle, selectionListStyle
const alternateText = getAlternateText(
report,
{},
{dateFnsLocale, isReportArchived, personalDetails, policy, reportAttributesDerived, policyTags: reportPolicyTags, conciergeReportID, isTrackIntentUser},
{dateFnsLocale, isReportArchived, personalDetails, policy, reportAttributesDerived, policyTags: reportPolicyTags, conciergeReportID, isTrackIntentUser, currentUserAccountID},
);
return {...report, alternateText};
};
Expand Down
2 changes: 2 additions & 0 deletions src/components/Search/SearchRouter/SearchRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ function SearchRouter({onRouterClose, shouldHideInputCaret, isSearchRouterDispla
showPersonalDetails: isOneOnOneChat(contextualReport),
},
isTrackIntentUser,
currentUserAccountID,
});
reportForContextualSearch = option;
}
Expand Down Expand Up @@ -299,6 +300,7 @@ function SearchRouter({onRouterClose, shouldHideInputCaret, isSearchRouterDispla
reportAttributes,
isTrackIntentUser,
dateFnsLocale,
currentUserAccountID,
],
);

Expand Down
3 changes: 3 additions & 0 deletions src/libs/OptionsListUtils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1967,6 +1967,7 @@ type CreateOptionFromReportParams = {
policyTags?: OnyxEntry<PolicyTagLists>;
visibleReportActionsData?: VisibleReportActionsDerivedValue;
isTrackIntentUser?: boolean;
currentUserAccountID: number;
};

function createOptionFromReport({
Expand All @@ -1982,6 +1983,7 @@ function createOptionFromReport({
policyTags,
visibleReportActionsData = {},
isTrackIntentUser,
currentUserAccountID,
}: CreateOptionFromReportParams) {
const accountIDs = getParticipantsAccountIDsForDisplay(report);

Expand All @@ -2001,6 +2003,7 @@ function createOptionFromReport({
visibleReportActionsData,
sortedActions,
isTrackIntentUser,
currentUserAccountID,
}),
};
}
Expand Down
125 changes: 125 additions & 0 deletions tests/unit/OptionsListUtilsTest.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ import type {PersonalDetails, Policy, Report, ReportAction, ReportNameValuePairs
import type {ReportAttributes} from '@src/types/onyx/DerivedValues';
import type {Participant} from '@src/types/onyx/IOU';
import type Login from '@src/types/onyx/Login';
import type {OriginalMessageReimbursed} from '@src/types/onyx/OriginalMessage';

import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';

Expand Down Expand Up @@ -10842,6 +10843,7 @@ describe('OptionsListUtils', () => {
// When the threaded conciergeReportID matches the report
const conciergeOption = createOptionFromReport({
dateFnsLocale: undefined,
currentUserAccountID: CURRENT_USER_ACCOUNT_ID,
report,
personalDetails: PERSONAL_DETAILS,
privateIsArchived: undefined,
Expand All @@ -10854,6 +10856,7 @@ describe('OptionsListUtils', () => {
// And an identical report with a non-matching conciergeReportID is not treated as Concierge
const regularOption = createOptionFromReport({
dateFnsLocale: undefined,
currentUserAccountID: CURRENT_USER_ACCOUNT_ID,
report,
personalDetails: PERSONAL_DETAILS,
privateIsArchived: undefined,
Expand Down Expand Up @@ -10883,6 +10886,7 @@ describe('OptionsListUtils', () => {

const result = createOptionFromReport({
dateFnsLocale: undefined,
currentUserAccountID: CURRENT_USER_ACCOUNT_ID,
report,
personalDetails: PERSONAL_DETAILS,
privateIsArchived: undefined,
Expand Down Expand Up @@ -10915,6 +10919,7 @@ describe('OptionsListUtils', () => {

const result = createOptionFromReport({
dateFnsLocale: undefined,
currentUserAccountID: CURRENT_USER_ACCOUNT_ID,
report,
personalDetails: PERSONAL_DETAILS,
privateIsArchived: true,
Expand Down Expand Up @@ -10946,6 +10951,7 @@ describe('OptionsListUtils', () => {

const result = createOptionFromReport({
dateFnsLocale: undefined,
currentUserAccountID: CURRENT_USER_ACCOUNT_ID,
report,
personalDetails: PERSONAL_DETAILS,
privateIsArchived: undefined,
Expand Down Expand Up @@ -10977,6 +10983,7 @@ describe('OptionsListUtils', () => {

const result = createOptionFromReport({
dateFnsLocale: undefined,
currentUserAccountID: CURRENT_USER_ACCOUNT_ID,
report,
personalDetails: PERSONAL_DETAILS,
privateIsArchived: undefined,
Expand Down Expand Up @@ -11009,6 +11016,7 @@ describe('OptionsListUtils', () => {
const config = {showPersonalDetails: true};
const result = createOptionFromReport({
dateFnsLocale: undefined,
currentUserAccountID: CURRENT_USER_ACCOUNT_ID,
report,
personalDetails: PERSONAL_DETAILS,
privateIsArchived: undefined,
Expand Down Expand Up @@ -11046,6 +11054,7 @@ describe('OptionsListUtils', () => {

const roomOption = createOptionFromReport({
dateFnsLocale: undefined,
currentUserAccountID: CURRENT_USER_ACCOUNT_ID,
report,
personalDetails: PERSONAL_DETAILS,
privateIsArchived: undefined,
Expand All @@ -11055,6 +11064,7 @@ describe('OptionsListUtils', () => {
});
const personalDetailsOption = createOptionFromReport({
dateFnsLocale: undefined,
currentUserAccountID: CURRENT_USER_ACCOUNT_ID,
report,
personalDetails: PERSONAL_DETAILS,
privateIsArchived: undefined,
Expand All @@ -11068,6 +11078,120 @@ describe('OptionsListUtils', () => {
// With showPersonalDetails the option is named after the other participant (account 1 in PERSONAL_DETAILS).
expect(personalDetailsOption.text).toBe('Mister Fantastic');
});

describe('reimbursed report preview', () => {
const SUBMITTER_LOGIN = 'submitter@expensify.com';

/**
* Builds an expense report whose last visible action is a REIMBURSED action, writes it to Onyx and
* returns the params for `createOptionFromReport`. Each case uses its own reportID because the
* module-level report-action caches survive `Onyx.clear()`, so a shared ID would leak across tests.
*/
const setUpReimbursedReport = async (reportID: string, ownerAccountID: number, originalMessage: OriginalMessageReimbursed) => {
const report: Report = {
reportID,
reportName: 'Expense Report',
type: CONST.REPORT.TYPE.EXPENSE,
ownerAccountID,
// Empty so the preview falls through to getLastMessageTextForReport instead of reusing lastMessageText.
lastMessageText: '',
lastActionType: CONST.REPORT.ACTIONS.TYPE.REIMBURSED,
lastVisibleActionCreated: '2024-01-01 10:00:00.000',
};
const reimbursedAction: ReportAction<typeof CONST.REPORT.ACTIONS.TYPE.REIMBURSED> = {
actionName: CONST.REPORT.ACTIONS.TYPE.REIMBURSED,
reportActionID: `reimbursed-${reportID}`,
created: '2024-01-01 10:00:00.000',
actorAccountID: 3,
originalMessage,
message: [{type: 'COMMENT', html: 'reimbursed', text: 'reimbursed'}],
};

await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, report);
await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, {[reimbursedAction.reportActionID]: reimbursedAction});
await waitForBatchedUpdates();

return {
dateFnsLocale: undefined,
report,
personalDetails: {...PERSONAL_DETAILS, [ownerAccountID]: {accountID: ownerAccountID, login: SUBMITTER_LOGIN, displayName: 'Submitter'}},
privateIsArchived: undefined,
policy: undefined,
sortedActions: {[reportID]: [reimbursedAction]},
conciergeReportID: undefined,
config: {showChatPreviewLine: true},
};
};

it('should address the payee directly only in the preview shown to them for a Fast ACH reimbursement', async () => {
// Given an expense report whose last action is a Fast_ACH reimbursement paid to account 42
const ownerAccountID = 42;
const params = await setUpReimbursedReport('create-option-reimbursed-fast-ach', ownerAccountID, {
paymentMethod: 'Fast_ACH',
creditBankAccountLast4: '1111',
expectedDate: '2025-03-15',
});

// When the option is built for the report owner, and for somebody else
const ownOption = createOptionFromReport({...params, currentUserAccountID: ownerAccountID});
const otherOption = createOptionFromReport({...params, currentUserAccountID: 999});

// Then the wording follows the passed currentUserAccountID
expect(ownOption.alternateText).toContain('your bank account ending in 1111');
expect(otherOption.alternateText).toContain(`${SUBMITTER_LOGIN}'s bank account ending in 1111`);
});

it('should address the payee directly only in the preview shown to them for a StripeConnect reimbursement', async () => {
// Given an expense report reimbursed to account 42 through StripeConnect
const ownerAccountID = 42;
const params = await setUpReimbursedReport('create-option-reimbursed-stripe', ownerAccountID, {
paymentMethod: 'StripeConnect',
creditBankAccountLast4: '2222',
stripePaymentType: 'bank_transfer',
});

// When the option is built for the report owner, and for somebody else
const ownOption = createOptionFromReport({...params, currentUserAccountID: ownerAccountID});
const otherOption = createOptionFromReport({...params, currentUserAccountID: 999});

// Then the wording follows the passed currentUserAccountID
expect(ownOption.alternateText).toContain('your bank account ending in 2222');
expect(otherOption.alternateText).toContain(`${SUBMITTER_LOGIN}'s bank account ending in 2222`);
});

it('should build the preview for the account it is given rather than the signed-in session account', async () => {
// Given a report owned by the signed-in user (the session accountID written in beforeEach)
const params = await setUpReimbursedReport('create-option-reimbursed-session', CURRENT_USER_ACCOUNT_ID, {
paymentMethod: 'Fast_ACH',
creditBankAccountLast4: '3333',
expectedDate: '2025-03-15',
});

// When the option is built for a different account than the one in the session
const option = createOptionFromReport({...params, currentUserAccountID: 999});

// Then the passed account wins over the module-level session value, so the owner is named explicitly
expect(option.alternateText).toContain(`${SUBMITTER_LOGIN}'s bank account ending in 3333`);
expect(option.alternateText).not.toContain('your bank account');
});

it('should build the same ACH preview for every viewer', async () => {
// Given an expense report reimbursed with plain ACH, whose message never names the payee
const params = await setUpReimbursedReport('create-option-reimbursed-ach', 42, {
paymentMethod: 'ACH',
debitBankAccountLast4: '9999',
creditBankAccountLast4: '5678',
});

// When the option is built for the report owner, and for somebody else
const ownOption = createOptionFromReport({...params, currentUserAccountID: 42});
const otherOption = createOptionFromReport({...params, currentUserAccountID: 999});

// Then both previews are identical
expect(ownOption.alternateText).toBe(otherOption.alternateText);
expect(ownOption.alternateText).toContain('to the bank account ending in 5678');
});
});
});

describe('createFilteredOptionList', () => {
Expand Down Expand Up @@ -11539,6 +11663,7 @@ describe('OptionsListUtils', () => {

const result = createOptionFromReport({
dateFnsLocale: undefined,
currentUserAccountID: CURRENT_USER_ACCOUNT_ID,
report,
personalDetails: PERSONAL_DETAILS,
privateIsArchived: undefined,
Expand Down
Loading