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
12 changes: 6 additions & 6 deletions src/components/ReportActionAvatars/ReportActionAvatar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ function ReportActionAvatarMultipleHorizontal({
const theme = useTheme();
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
const {localeCompare, formatPhoneNumber} = useLocalize();
const {localeCompare, formatPhoneNumber, translate} = useLocalize();

const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST);

Expand Down Expand Up @@ -370,8 +370,8 @@ function ReportActionAvatarMultipleHorizontal({
}, [icons, maxAvatarsInRow, shouldDisplayAvatarsInRows]);

const tooltipTexts = useMemo(
() => (shouldShowTooltip ? icons.map((icon) => getUserDetailTooltipText(Number(icon.id), formatPhoneNumber, icon.name)) : ['']),
[shouldShowTooltip, icons, formatPhoneNumber],
() => (shouldShowTooltip ? icons.map((icon) => getUserDetailTooltipText(Number(icon.id), formatPhoneNumber, translate, icon.name)) : ['']),
[shouldShowTooltip, icons, formatPhoneNumber, translate],
);

return avatarRows.map((avatars, rowIndex) => (
Expand Down Expand Up @@ -482,11 +482,11 @@ function ReportActionAvatarMultipleDiagonal({
const theme = useTheme();
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
const {formatPhoneNumber} = useLocalize();
const {formatPhoneNumber, translate} = useLocalize();

const tooltipTexts = useMemo(
() => (shouldShowTooltip ? icons.map((icon) => getUserDetailTooltipText(Number(icon.id), formatPhoneNumber, icon.name)) : ['']),
[shouldShowTooltip, icons, formatPhoneNumber],
() => (shouldShowTooltip ? icons.map((icon) => getUserDetailTooltipText(Number(icon.id), formatPhoneNumber, translate, icon.name)) : ['']),
[shouldShowTooltip, icons, formatPhoneNumber, translate],
);
const removeRightMargin = icons.length === 2 && size === CONST.AVATAR_SIZE.X_LARGE;
const avatarContainerStyles = StyleUtils.getContainerStyles(size, isInReportAction);
Expand Down
4 changes: 2 additions & 2 deletions src/components/Search/SearchList/ListItem/AttendeesCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ function AttendeesCell({attendees, isHovered, isPressed}: AttendeesCellProps) {
const theme = useTheme();
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
const {localeCompare, formatPhoneNumber} = useLocalize();
const {localeCompare, formatPhoneNumber, translate} = useLocalize();

const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST);

Expand All @@ -59,7 +59,7 @@ function AttendeesCell({attendees, isHovered, isPressed}: AttendeesCellProps) {
const avatarContainerStyles = StyleUtils.combineStyles([styles.alignItemsCenter, styles.flexRow, StyleUtils.getHeight(height), styles.overflowHidden]);

const icons = sortIconsByName(attendeeIcons, personalDetails, localeCompare);
const tooltipTexts = icons.map((icon) => getUserDetailTooltipText(Number(icon.id), formatPhoneNumber, icon.name));
const tooltipTexts = icons.map((icon) => getUserDetailTooltipText(Number(icon.id), formatPhoneNumber, translate, icon.name));

return (
<View
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ function BaseUserDetailsTooltip({accountID, fallbackUserDetails, icon, delegateA
const isCurrentUserAnonymous = session?.accountID === accountID && isAnonymousUser(session);

const userDetails = personalDetails?.[accountID] ?? fallbackUserDetails ?? {};
let userDisplayName = getUserDetailTooltipText(accountID, formatPhoneNumber, userDetails.displayName ? userDetails.displayName.trim() : '');
let userDisplayName = getUserDetailTooltipText(accountID, formatPhoneNumber, translate, userDetails.displayName ? userDetails.displayName.trim() : '');
let userLogin = !isCurrentUserAnonymous && userDetails.login?.trim() && userDetails.login !== userDetails.displayName ? formatPhoneNumber(userDetails.login) : '';

let userAvatar = userDetails.avatar;
Expand All @@ -35,7 +35,7 @@ function BaseUserDetailsTooltip({accountID, fallbackUserDetails, icon, delegateA
// the Copilot feature is implemented.
if (delegateAccountID && delegateAccountID > 0) {
const delegateUserDetails = personalDetails?.[delegateAccountID];
const delegateUserDisplayName = getUserDetailTooltipText(delegateAccountID, formatPhoneNumber);
const delegateUserDisplayName = getUserDetailTooltipText(delegateAccountID, formatPhoneNumber, translate);
userDisplayName = `${delegateUserDisplayName} (${translate('reportAction.asCopilot')} ${userDisplayName})`;
userLogin = delegateUserDetails?.login ?? '';
userAvatar = delegateUserDetails?.avatar;
Expand Down
23 changes: 14 additions & 9 deletions src/libs/ReportUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4154,8 +4154,8 @@ function getDisplayNamesWithTooltips(
/**
* Returns the the display names of the given user accountIDs
*/
function getUserDetailTooltipText(accountID: number, formatPhoneNumber: LocaleContextProps['formatPhoneNumber'], fallbackUserDisplayName = ''): string {
const displayNameForParticipant = getDisplayNameForParticipant({accountID, formatPhoneNumber});
function getUserDetailTooltipText(accountID: number, formatPhoneNumber: LocaleContextProps['formatPhoneNumber'], translate: LocalizedTranslate, fallbackUserDisplayName = ''): string {
const displayNameForParticipant = getDisplayNameForParticipant({accountID, formatPhoneNumber, translate});
return displayNameForParticipant || fallbackUserDisplayName;
}

Expand Down Expand Up @@ -5782,7 +5782,7 @@ function getReportPreviewMessage(translate: LocalizedTranslate, params: GetRepor
const policyName = getPolicyName({report: parentReport ?? report, policy});
const payerName = isExpenseReport(report)
? policyName
: getDisplayNameForParticipant({accountID: report.managerID, shouldUseShortForm: !isPreviewMessageForParentChatReport, formatPhoneNumber: formatPhoneNumberPhoneUtils});
: getDisplayNameForParticipant({accountID: report.managerID, shouldUseShortForm: !isPreviewMessageForParentChatReport, formatPhoneNumber: formatPhoneNumberPhoneUtils, translate});

const formattedAmount = convertToDisplayString(totalAmount, report.currency);

Expand Down Expand Up @@ -5839,7 +5839,7 @@ function getReportPreviewMessage(translate: LocalizedTranslate, params: GetRepor
let actualPayerName =
report.managerID === deprecatedCurrentUserAccountID && !isForListPreview
? ''
: getDisplayNameForParticipant({accountID: payerAccountID, shouldUseShortForm: true, formatPhoneNumber: formatPhoneNumberPhoneUtils});
: getDisplayNameForParticipant({accountID: payerAccountID, shouldUseShortForm: true, formatPhoneNumber: formatPhoneNumberPhoneUtils, translate});

actualPayerName = actualPayerName && isForListPreview && !isPreviewMessageForParentChatReport ? `${actualPayerName}:` : actualPayerName;
const payerDisplayName = isPreviewMessageForParentChatReport ? payerName : actualPayerName;
Expand All @@ -5858,7 +5858,8 @@ function getReportPreviewMessage(translate: LocalizedTranslate, params: GetRepor
}

if (report.isWaitingOnBankAccount) {
const submitterDisplayName = getDisplayNameForParticipant({accountID: report.ownerAccountID, shouldUseShortForm: true, formatPhoneNumber: formatPhoneNumberPhoneUtils}) ?? '';
const submitterDisplayName =
getDisplayNameForParticipant({accountID: report.ownerAccountID, shouldUseShortForm: true, formatPhoneNumber: formatPhoneNumberPhoneUtils, translate}) ?? '';
return translate('iou.waitingOnBankAccount', submitterDisplayName);
}

Expand Down Expand Up @@ -5887,13 +5888,17 @@ function getReportPreviewMessage(translate: LocalizedTranslate, params: GetRepor
// We only want to show the actor name in the preview if it's not the current user who took the action
const requestorName =
lastActorID && lastActorID !== deprecatedCurrentUserAccountID
? getDisplayNameForParticipant({accountID: lastActorID, shouldUseShortForm: !isPreviewMessageForParentChatReport, formatPhoneNumber: formatPhoneNumberPhoneUtils})
? getDisplayNameForParticipant({accountID: lastActorID, shouldUseShortForm: !isPreviewMessageForParentChatReport, formatPhoneNumber: formatPhoneNumberPhoneUtils, translate})
: '';
return `${requestorName ? `${requestorName}: ` : ''}${translate('iou.expenseAmount', amountToDisplay, comment)}`;
}

if (containsNonReimbursable) {
return translate('iou.payerSpentAmount', formattedAmount, getDisplayNameForParticipant({accountID: report.ownerAccountID, formatPhoneNumber: formatPhoneNumberPhoneUtils}) ?? '');
return translate(
'iou.payerSpentAmount',
formattedAmount,
getDisplayNameForParticipant({accountID: report.ownerAccountID, formatPhoneNumber: formatPhoneNumberPhoneUtils, translate}) ?? '',
);
}
return translate('iou.payerOwesAmount', formattedAmount, payerName ?? '', comment);
}
Expand Down Expand Up @@ -6227,7 +6232,7 @@ function getModifiedExpenseOriginalMessage(
/**
* Get the payee name given a report.
*/
function getPayeeName(report: OnyxEntry<Report>): string | undefined {
function getPayeeName(report: OnyxEntry<Report>, translate: LocalizedTranslate): string | undefined {
if (isEmptyObject(report)) {
return undefined;
}
Expand All @@ -6239,7 +6244,7 @@ function getPayeeName(report: OnyxEntry<Report>): string | undefined {
if (participantsWithoutCurrentUser.length === 0) {
return undefined;
}
return getDisplayNameForParticipant({accountID: participantsWithoutCurrentUser.at(0), shouldUseShortForm: true, formatPhoneNumber: formatPhoneNumberPhoneUtils});
return getDisplayNameForParticipant({accountID: participantsWithoutCurrentUser.at(0), shouldUseShortForm: true, formatPhoneNumber: formatPhoneNumberPhoneUtils, translate});
}

// TODO: currentUserEmail will be required eventually so this becomes a pure function. Subscribe the data via useOnyx and pass it from the component. Refactor issue: https://github.com/Expensify/App/issues/66412
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ function AttachmentPickerWithMenuItems({
[CONST.IOU.TYPE.PAY]: [
{
icon: getIconForAction(CONST.IOU.TYPE.SEND, icons),
text: translate('iou.paySomeone', getPayeeName(report)),
text: translate('iou.paySomeone', getPayeeName(report, translate)),
shouldCallAfterModalHide: shouldUseNarrowLayout,
sentryLabel: CONST.SENTRY_LABEL.REPORT.ATTACHMENT_PICKER_MENU_PAY_SOMEONE,
onSelected: () => {
Expand Down
4 changes: 2 additions & 2 deletions src/pages/iou/request/DistanceRequestStartPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ function DistanceRequestStartPage({
const tabTitles = {
[CONST.IOU.TYPE.REQUEST]: translate('iou.trackDistance'),
[CONST.IOU.TYPE.SUBMIT]: translate('iou.trackDistance'),
[CONST.IOU.TYPE.SEND]: translate('iou.paySomeone', getPayeeName(report)),
[CONST.IOU.TYPE.PAY]: translate('iou.paySomeone', getPayeeName(report)),
[CONST.IOU.TYPE.SEND]: translate('iou.paySomeone', getPayeeName(report, translate)),
[CONST.IOU.TYPE.PAY]: translate('iou.paySomeone', getPayeeName(report, translate)),
[CONST.IOU.TYPE.SPLIT]: translate('iou.splitExpense'),
[CONST.IOU.TYPE.SPLIT_EXPENSE]: translate('iou.splitExpense'),
[CONST.IOU.TYPE.TRACK]: translate('iou.trackDistance'),
Expand Down
4 changes: 2 additions & 2 deletions src/pages/iou/request/IOURequestStartPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,8 @@ function IOURequestStartPage({
const tabTitles = {
[CONST.IOU.TYPE.REQUEST]: translate('iou.createExpense'),
[CONST.IOU.TYPE.SUBMIT]: translate('iou.createExpense'),
[CONST.IOU.TYPE.SEND]: translate('iou.paySomeone', getPayeeName(report)),
[CONST.IOU.TYPE.PAY]: translate('iou.paySomeone', getPayeeName(report)),
[CONST.IOU.TYPE.SEND]: translate('iou.paySomeone', getPayeeName(report, translate)),
[CONST.IOU.TYPE.PAY]: translate('iou.paySomeone', getPayeeName(report, translate)),
[CONST.IOU.TYPE.SPLIT]: translate('iou.splitExpense'),
[CONST.IOU.TYPE.SPLIT_EXPENSE]: translate('iou.splitExpense'),
[CONST.IOU.TYPE.TRACK]: translate('iou.createExpense'),
Expand Down
73 changes: 73 additions & 0 deletions tests/unit/ReportUtilsTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ import {
getParentNavigationSubtitle,
getParsedComment,
getParticipantsList,
getPayeeName,
getPolicyChangeLogCopyMessage,
getPolicyExpenseChat,
getPolicyIDsWithEmptyReportsForAccount,
Expand All @@ -134,6 +135,7 @@ import {
getTransactionsWithReceipts,
getUnheldReimbursableTotal,
getUnreportedTransactionMessage,
getUserDetailTooltipText,
getViolatingReportIDForRBRInLHN,
getWorkspaceIcon,
getWhisperDisplayNames,
Expand Down Expand Up @@ -16185,6 +16187,33 @@ describe('ReportUtils', () => {
// ...but the report-action-message variant is always the English text, regardless of the loaded locale
expect(getReportPreviewReportActionMessage(params)).toBe(getReportPreviewMessage(englishTranslate, params));
});

it('routes the participant display name through the injected translate function', async () => {
const hiddenManagerAccountID = 987654;
const iouReport: Report = {
...LHNTestUtils.getFakeReport(),
reportID: 'preview-marker-report',
type: CONST.REPORT.TYPE.IOU,
currency: CONST.CURRENCY.USD,
managerID: hiddenManagerAccountID,
stateNum: CONST.REPORT.STATE_NUM.OPEN,
statusNum: CONST.REPORT.STATUS_NUM.OPEN,
};
await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`, iouReport);
// A participant with no name resolves to the "hidden" copy, which is produced by the injected translate
await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, {
[hiddenManagerAccountID]: {accountID: hiddenManagerAccountID, login: '', displayName: ''},
});

// A translate that tags the hidden-participant fallback so we can prove the preview used it
const translateWithMarker: LocalizedTranslate = (path, ...parameters) =>
path === 'common.hidden' ? 'HiddenParticipantMarker' : translate(CONST.LOCALES.EN, path, ...parameters);

const result = getReportPreviewMessage(translateWithMarker, {reportOrID: iouReport});

// The manager's name resolves to the marker, proving getDisplayNameForParticipant received the injected translate
expect(result).toContain('HiddenParticipantMarker');
});
});

describe('getReportPreviewReportActionMessage (hardcoded English)', () => {
Expand All @@ -16209,6 +16238,50 @@ describe('ReportUtils', () => {
});
});

describe('getUserDetailTooltipText', () => {
it('routes the display name through the injected translate function', async () => {
const hiddenAccountID = 778899;
// A participant with no name resolves to the "hidden" copy, which is produced by the injected translate
await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, {
[hiddenAccountID]: {accountID: hiddenAccountID, login: '', displayName: ''},
});
await waitForBatchedUpdates();

const translateWithMarker: LocalizedTranslate = (path, ...parameters) => (path === 'common.hidden' ? 'HiddenTooltipMarker' : translate(CONST.LOCALES.EN, path, ...parameters));

// The nameless participant resolves to the marker, proving getDisplayNameForParticipant received the injected translate
expect(getUserDetailTooltipText(hiddenAccountID, formatPhoneNumber, translateWithMarker)).toBe('HiddenTooltipMarker');
});

it('falls back to the provided display name when the participant has no resolvable name', () => {
// accountID 0 short-circuits getDisplayNameForParticipant to '', so the fallback name is returned
expect(getUserDetailTooltipText(0, formatPhoneNumber, translateLocal, 'Fallback Name')).toBe('Fallback Name');
});
});

describe('getPayeeName', () => {
it('routes the payee display name through the injected translate function', async () => {
const hiddenPayeeAccountID = 665544;
const report: Report = {
...LHNTestUtils.getFakeReport(),
reportID: 'payee-marker-report',
participants: {
[hiddenPayeeAccountID]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS},
},
};
// A payee with no name resolves to the "hidden" copy, which is produced by the injected translate
await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, {
[hiddenPayeeAccountID]: {accountID: hiddenPayeeAccountID, login: '', displayName: ''},
});
await waitForBatchedUpdates();

const translateWithMarker: LocalizedTranslate = (path, ...parameters) => (path === 'common.hidden' ? 'HiddenPayeeMarker' : translate(CONST.LOCALES.EN, path, ...parameters));

// The nameless payee resolves to the marker, proving getDisplayNameForParticipant received the injected translate
expect(getPayeeName(report, translateWithMarker)).toBe('HiddenPayeeMarker');
});
});

describe('isConciergeChatReport', () => {
it('should return false for null/undefined report', () => {
expect(isConciergeChatReport(null, '12345')).toBe(false);
Expand Down
Loading