From 5da32ed4797a8190e4917c7d24f33909c8d42e31 Mon Sep 17 00:00:00 2001 From: OSBotify Date: Tue, 11 Aug 2026 17:50:44 +0000 Subject: [PATCH 1/3] Update version to 9.4.52-4 (cherry picked from commit 6619a6695fab418ddc4664b6d98829dc2b2a0ba6) (cherry-picked to staging by Julesssss) --- android/app/build.gradle | 4 ++-- ios/NewExpensify/Info.plist | 2 +- ios/NotificationServiceExtension/Info.plist | 2 +- ios/ShareViewController/Info.plist | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index c29504116439..dd5eae047d64 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -111,8 +111,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1009045202 - versionName "9.4.52-2" + versionCode 1009045204 + versionName "9.4.52-4" // Supported language variants must be declared here to avoid from being removed during the compilation. // This also helps us to not include unnecessary language variants in the APK. resConfigs "en", "es" diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 46d9da4b5659..5a954bfc8a40 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -44,7 +44,7 @@ CFBundleVersion - 9.4.52.2 + 9.4.52.4 FullStory OrgId diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index 3137f5ac98f7..febf10bd9d1f 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -13,7 +13,7 @@ CFBundleShortVersionString 9.4.52 CFBundleVersion - 9.4.52.2 + 9.4.52.4 NSExtension NSExtensionPointIdentifier diff --git a/ios/ShareViewController/Info.plist b/ios/ShareViewController/Info.plist index 5a665d407b6e..7e2cb9bb5f80 100644 --- a/ios/ShareViewController/Info.plist +++ b/ios/ShareViewController/Info.plist @@ -13,7 +13,7 @@ CFBundleShortVersionString 9.4.52 CFBundleVersion - 9.4.52.2 + 9.4.52.4 NSExtension NSExtensionAttributes diff --git a/package-lock.json b/package-lock.json index 4e3747b70c59..5a2c1d487662 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "9.4.52-2", + "version": "9.4.52-4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "9.4.52-2", + "version": "9.4.52-4", "hasInstallScript": true, "license": "MIT", "workspaces": [ diff --git a/package.json b/package.json index 7f38cc14f139..2d84d48afa95 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "9.4.52-2", + "version": "9.4.52-4", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", From 84f7abb86c68268118f50d1a2105f8198ae74c6c Mon Sep 17 00:00:00 2001 From: OSBotify Date: Tue, 11 Aug 2026 17:52:34 +0000 Subject: [PATCH 2/3] Update Mobile-Expensify submodule version to 9.4.52-4 --- Mobile-Expensify | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mobile-Expensify b/Mobile-Expensify index 9aaf0f11f691..34b9e66c9031 160000 --- a/Mobile-Expensify +++ b/Mobile-Expensify @@ -1 +1 @@ -Subproject commit 9aaf0f11f691ac2b3229c3890b740e113e1ce82a +Subproject commit 34b9e66c90313ca55a950e650b544ac1d27b891d From b82cbcf290a41c3eba559c93476010a3d76adc8a Mon Sep 17 00:00:00 2001 From: Jack Senyitko <49812749+JS00001@users.noreply.github.com> Date: Tue, 11 Aug 2026 13:33:16 -0400 Subject: [PATCH 3/3] Merge pull request #98338 from Expensify/jsenyitko-rev-db-2 [cp staging] Revert https://github.com/Expensify/App/pull/96942 (cherry picked from commit 6f63c631004036fbd0264810666d203f9032575f) (cherry-picked to staging by Julesssss) --- .../ReportPreviewHeader.tsx | 5 +- .../TransactionPreviewContent.tsx | 196 ++++++++++++------ src/libs/TransactionPreviewUtils.ts | 93 ++++++--- tests/ui/MoneyRequestReportPreview.test.tsx | 14 +- tests/unit/TransactionPreviewUtils.test.ts | 33 ++- 5 files changed, 214 insertions(+), 127 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestReportPreview/ReportPreviewHeader.tsx b/src/components/ReportActionItem/MoneyRequestReportPreview/ReportPreviewHeader.tsx index e065af642c5d..cba808917a03 100644 --- a/src/components/ReportActionItem/MoneyRequestReportPreview/ReportPreviewHeader.tsx +++ b/src/components/ReportActionItem/MoneyRequestReportPreview/ReportPreviewHeader.tsx @@ -66,9 +66,6 @@ function ReportPreviewHeader() { [translate, numberOfRequests], ); - // A cancelled payment is a report level event and it isn't surfaced by the status badge, so we show it next to the expense count. - const supportingText = iouReport?.isCancelledIOU ? `${translate('iou.canceled')} ${CONST.DOT_SEPARATOR} ${expenseCount}` : expenseCount; - const reportStateNum = iouReport?.stateNum ?? action?.childStateNum; const reportStatusNum = iouReport?.statusNum ?? action?.childStatusNum; @@ -125,7 +122,7 @@ function ReportPreviewHeader() { tooltipText={reportStatusTooltip} /> )} - {!shouldShowAccessPlaceHolder && {supportingText}} + {!shouldShowAccessPlaceHolder && {expenseCount}} ) )} diff --git a/src/components/ReportActionItem/TransactionPreview/TransactionPreviewContent.tsx b/src/components/ReportActionItem/TransactionPreview/TransactionPreviewContent.tsx index 4cc7f18fd26a..8c941f806f44 100644 --- a/src/components/ReportActionItem/TransactionPreview/TransactionPreviewContent.tsx +++ b/src/components/ReportActionItem/TransactionPreview/TransactionPreviewContent.tsx @@ -2,7 +2,6 @@ import Button from '@components/Button'; import Icon from '@components/Icon'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import ReportActionAvatars from '@components/ReportActionAvatars'; -import {ReportPreviewDataContext} from '@components/ReportActionItem/MoneyRequestReportPreview/MoneyRequestReportPreviewContext'; import ReportActionItemImages from '@components/ReportActionItem/ReportActionItemImages'; import UserInfoCellsWithArrow from '@components/Search/SearchList/ListItem/UserInfoCellsWithArrow'; import Text from '@components/Text'; @@ -26,6 +25,7 @@ import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID'; import {calculateAmount} from '@libs/IOUUtils'; import Parser from '@libs/Parser'; import {getLoginByAccountID} from '@libs/PersonalDetailsUtils'; +import {getCommaSeparatedTagNameWithSanitizedColons} from '@libs/PolicyUtils'; import {getThumbnailAndImageURIs} from '@libs/ReceiptUtils'; import {getOriginalMessage, isMoneyRequestAction} from '@libs/ReportActionsUtils'; import {isMarkAsCashActionForTransaction} from '@libs/ReportPrimaryActionUtils'; @@ -46,7 +46,7 @@ import {cardByIdSelector} from '@src/selectors/Card'; import {getStableReportSelector} from '@src/selectors/Report'; import truncate from 'lodash/truncate'; -import React, {useContext, useMemo} from 'react'; +import React, {useMemo} from 'react'; import {View} from 'react-native'; import Animated from 'react-native-reanimated'; @@ -77,7 +77,7 @@ function TransactionPreviewContent({ isReviewDuplicateTransactionPage = false, shouldHighlight = false, }: TransactionPreviewContentProps) { - const icons = useMemoizedLazyExpensifyIcons(['DotIndicator']); + const icons = useMemoizedLazyExpensifyIcons(['DotIndicator', 'Folder', 'Tag']); const theme = useTheme(); const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -88,12 +88,14 @@ function TransactionPreviewContent({ () => getTransactionDetails(transaction, undefined, policy, isParentPolicyExpenseChat) ?? {}, [transaction, policy, isParentPolicyExpenseChat], ); - const {amount, comment: requestComment, merchant, category, currency: requestCurrency} = transactionDetails; + const {amount, comment: requestComment, merchant, tag, category, currency: requestCurrency} = transactionDetails; const [originalTransaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${getNonEmptyStringOnyxID(transaction?.comment?.originalTransactionID)}`); const filteredViolations = filterReceiptViolations(violations); const firstViolation = filteredViolations.at(0); const cardID = firstViolation?.data?.cardID; - const [card] = useOnyx(ONYXKEYS.CARD_LIST, {selector: cardByIdSelector(String(cardID))}); + const [card] = useOnyx(ONYXKEYS.CARD_LIST, { + selector: cardByIdSelector(String(cardID)), + }); const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(report?.parentReportID)}`, {selector: getStableReportSelector}); const managerID = report?.managerID ?? reportPreviewAction?.childManagerAccountID ?? CONST.DEFAULT_NUMBER_ID; const ownerAccountID = report?.ownerAccountID ?? reportPreviewAction?.childOwnerAccountID ?? CONST.DEFAULT_NUMBER_ID; @@ -131,12 +133,7 @@ function TransactionPreviewContent({ [areThereDuplicates, transactionPreviewCommonArguments, isParentPolicyExpenseChat, currentUserEmail, currentUserAccountID, reportActions], ); - const {shouldShowRBR, shouldShowMerchant, shouldShowSplitShare, shouldShowCategory, shouldShowSkeleton, shouldShowDescription} = conditionals; - - // Raw useContext (not the useReportPreviewData slice hook, which throws when absent): a missing provider means this is a - // standalone preview with no report header to carry the status, so the preview has to report a cancelled payment itself. - const isInsideReportPreview = !!useContext(ReportPreviewDataContext); - const shouldShowCanceledStatus = !isInsideReportPreview; + const {shouldShowRBR, shouldShowMerchant, shouldShowSplitShare, shouldShowTag, shouldShowCategory, shouldShowSkeleton, shouldShowDescription} = conditionals; const isIOUActionType = isMoneyRequestAction(action); const canEdit = isIOUActionType && canEditMoneyRequest(action, transaction, isChatReportArchived, report, policy); @@ -165,26 +162,33 @@ function TransactionPreviewContent({ getTransactionPreviewTextAndTranslationPaths({ ...transactionPreviewCommonArguments, shouldShowRBR, - shouldShowCanceledStatus, violationMessage, reportActions, + currentUserEmail, + currentUserAccountID, originalTransaction, convertToDisplayString, }), - [transactionPreviewCommonArguments, shouldShowRBR, shouldShowCanceledStatus, violationMessage, reportActions, originalTransaction, convertToDisplayString], + [transactionPreviewCommonArguments, shouldShowRBR, violationMessage, reportActions, currentUserEmail, currentUserAccountID, originalTransaction, convertToDisplayString], ); const getTranslatedText = (item: TranslationPathOrText) => (item.translationPath ? translate(item.translationPath) : (item.text ?? '')); + const previewHeaderText = previewText.previewHeaderText.reduce((text, currentKey) => { + return `${text}${getTranslatedText(currentKey)}`; + }, ''); + const RBRMessage = getTranslatedText(previewText.RBRMessage); const displayAmountText = getTranslatedText(previewText.displayAmountText); const displayDeleteAmountText = getTranslatedText(previewText.displayDeleteAmountText); - const displayTypeText = getTranslatedText(previewText.previewTypeText); const isDeleted = action?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || transaction?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE; + const shouldShowCategoryOrTag = shouldShowCategory || shouldShowTag; const shouldShowMerchantOrDescription = shouldShowDescription || shouldShowMerchant; const description = truncate(StringUtils.lineBreaksToSpaces(Parser.htmlToText(requestComment ?? '')), {length: CONST.REQUEST_PREVIEW.MAX_LENGTH}); - const requestMerchant = truncate(merchant, {length: CONST.REQUEST_PREVIEW.MAX_LENGTH}); + const requestMerchant = truncate(merchant, { + length: CONST.REQUEST_PREVIEW.MAX_LENGTH, + }); const isApproved = isReportApproved({report}); const pendingAction = action?.pendingAction; const isIOUSettled = !pendingAction && isSettled(report); @@ -193,10 +197,6 @@ function TransactionPreviewContent({ const displayAmount = isDeleted ? displayDeleteAmountText : displayAmountText; const receiptImages = [{...getThumbnailAndImageURIs(transaction), transaction}]; const merchantOrDescription = shouldShowMerchant ? requestMerchant : description || ''; - const previewSupportingText = [previewText.previewDateText, shouldShowCategory && category ? {text: getDecodedLeafCategoryName(category)} : undefined, ...previewText.previewStatusText] - .filter((item): item is TranslationPathOrText => !!item) - .map(getTranslatedText) - .join(` ${CONST.DOT_SEPARATOR} `); const participantAccountIDs = isMoneyRequestAction(action) && isBillSplit ? (getOriginalMessage(action)?.participantAccountIDs ?? []) : [managerID, ownerAccountID]; const isCardTransaction = isCardTransactionUtils(transaction); @@ -266,6 +266,10 @@ function TransactionPreviewContent({ getCurrencyDecimals, ]); + const shouldWrapDisplayAmount = !(isBillSplit || shouldShowMerchantOrDescription || isTransactionScanning); + const previewTextViewGap = (shouldShowCategoryOrTag || !shouldWrapDisplayAmount) && styles.gap2; + const previewTextMargin = shouldShowIOUHeader && shouldShowMerchantOrDescription && !isBillSplit && !shouldShowCategoryOrTag && styles.mbn1; + const animatedHighlightStyle = useAnimatedHighlightStyle({ shouldHighlight, highlightColor: theme.messageHighlightBG, @@ -308,63 +312,121 @@ function TransactionPreviewContent({ participantToDisplayName={to.displayName ?? to.login ?? translate('common.hidden')} participantTo={to} avatarSize={CONST.AVATAR_SIZE.XXX_SMALL} - infoCellsTextStyle={{...styles.textMicroBold, lineHeight: 14}} + infoCellsTextStyle={{ + ...styles.textMicroBold, + lineHeight: 14, + }} infoCellsAvatarStyle={styles.pr1} style={[styles.flex1, styles.dFlex, styles.alignItemsCenter, styles.gap2, styles.flexRow]} /> )} - - - - {shouldShowMerchantOrDescription && ( - - {merchantOrDescription} + + + {previewHeaderText} + {isBillSplit && ( + + + + )} + {shouldWrapDisplayAmount && ( + + {displayAmount} + + )} + + + + + {shouldShowMerchantOrDescription && ( + + {merchantOrDescription} + + )} + {!shouldWrapDisplayAmount && ( + + {displayAmount} + + )} + + + + {!!splitShare && ( + + {translate('iou.yourSplit', convertToDisplayString(splitShare, requestCurrency))} )} - {isBillSplit && ( - - + + {shouldShowCategoryOrTag && ( + + {shouldShowCategory && ( + + + + {getDecodedLeafCategoryName(category ?? '')} + + + )} + {shouldShowTag && !!tag && ( + + + + {getCommaSeparatedTagNameWithSanitizedColons(tag)} + )} - - - {displayAmount} - - - - - {previewSupportingText} - - - {displayTypeText} - - - {!!splitShare && ( - - - {translate('iou.yourSplit', convertToDisplayString(splitShare, requestCurrency))} - )} diff --git a/src/libs/TransactionPreviewUtils.ts b/src/libs/TransactionPreviewUtils.ts index 1a217531749b..232605ce469f 100644 --- a/src/libs/TransactionPreviewUtils.ts +++ b/src/libs/TransactionPreviewUtils.ts @@ -36,6 +36,7 @@ import { isCreatedMissing, isDistanceRequest, isFetchingWaypointsFromServer, + isManagedCardTransaction, isMerchantMissing, isOnHold, isPending, @@ -123,6 +124,10 @@ type TranslationPathOrText = { text?: string; }; +const dotSeparator: TranslationPathOrText = { + text: ` ${CONST.DOT_SEPARATOR} `, +}; + /** * Normalize the last four digits to always return 4 characters. * If the number is shorter than 4 digits, it will be padded with X's. @@ -195,6 +200,7 @@ function getUniqueActionErrorsForTransaction(reportActions: OnyxTypes.ReportActi function getTransactionPreviewTextAndTranslationPaths({ iouReport, + iouReportOwnerLogin, policy, transaction, action, @@ -202,13 +208,15 @@ function getTransactionPreviewTextAndTranslationPaths({ transactionDetails, isBillSplit, shouldShowRBR, - shouldShowCanceledStatus, violationMessage, reportActions, + currentUserEmail, + currentUserAccountID, originalTransaction, convertToDisplayString, }: { iouReport: OnyxEntry; + iouReportOwnerLogin: string | undefined; policy: OnyxEntry; transaction: OnyxEntry; action: OnyxEntry; @@ -216,17 +224,19 @@ function getTransactionPreviewTextAndTranslationPaths({ transactionDetails: Partial; isBillSplit: boolean; shouldShowRBR: boolean; - /** Whether a cancelled payment has to be reported on this line, because the enclosing surface doesn't show it anywhere else */ - shouldShowCanceledStatus: boolean; violationMessage?: string; reportActions?: OnyxTypes.ReportActions; + currentUserEmail: string; + currentUserAccountID: number; originalTransaction?: OnyxEntry; convertToDisplayString: CurrencyListActionsContextType['convertToDisplayString']; }) { const isFetchingWaypoints = isFetchingWaypointsFromServer(transaction); const isTransactionOnHold = isOnHold(transaction); + const isTransactionMadeWithCard = isManagedCardTransaction(transaction); const isMoneyRequestSettled = isSettled(iouReport?.reportID); const isSettlementOrApprovalPartial = !!iouReport?.pendingFields?.partial; + const isPartialHold = isSettlementOrApprovalPartial && isTransactionOnHold; // We don't use isOnHold because it's true for duplicated transaction too and we only want to show hold message if the transaction is truly on hold const shouldShowHoldMessage = !(isMoneyRequestSettled && !isSettlementOrApprovalPartial) && !!transaction?.comment?.hold; @@ -234,6 +244,8 @@ function getTransactionPreviewTextAndTranslationPaths({ const hasFieldErrors = hasMissingSmartscanFields(transaction, iouReport); const isGroupPolicy = isGroupPolicyUtil(policy); + const hasViolationsOfTypeNotice = + hasNoticeTypeViolation(transaction, violations, currentUserEmail ?? '', currentUserAccountID, iouReport, iouReportOwnerLogin, policy, true) && isGroupPolicy; const hasActionWithErrors = hasActionWithErrorsForTransaction(iouReport?.reportID, transaction, reportActions); const {amount: requestAmount, currency: requestCurrency} = transactionDetails; @@ -294,42 +306,65 @@ function getTransactionPreviewTextAndTranslationPaths({ } } - let previewTypeText: TranslationPathOrText = {translationPath: getExpenseTypeTranslationKey(getTransactionType(transaction))}; + let previewHeaderText: TranslationPathOrText[] = [ + { + translationPath: getExpenseTypeTranslationKey(getTransactionType(transaction)), + }, + ]; if (isTransactionScanning) { - previewTypeText = {translationPath: 'common.receipt'}; + previewHeaderText = [{translationPath: 'common.receipt'}]; } else if (isBillSplit) { - previewTypeText = {translationPath: 'iou.split'}; + previewHeaderText = [{translationPath: 'iou.split'}]; } if (RBRMessage?.text === CONST.ERROR.BANK_ACCOUNT_SAME_DEPOSIT_AND_WITHDRAWAL_ERROR) { - RBRMessage = {translationPath: 'bankAccount.error.sameDepositAndWithdrawalAccount'}; + RBRMessage = { + translationPath: 'bankAccount.error.sameDepositAndWithdrawalAccount', + }; } RBRMessage ??= {text: ''}; - let previewDateText: TranslationPathOrText | undefined; if (!isCreatedMissing(transaction)) { const created = getFormattedCreated(transaction); const date = DateUtils.formatWithUTCTimeZone(created, DateUtils.doesDateBelongToAPastYear(created) ? CONST.DATE.MONTH_DAY_YEAR_ABBR_FORMAT : CONST.DATE.MONTH_DAY_ABBR_FORMAT); - previewDateText = {text: date}; + previewHeaderText.unshift({text: date}, dotSeparator); } - // Paid, Approved and Review required are intentionally omitted here because the report status badge and the violation - // row already show them, so repeating them on this line is noise. Canceled is the exception: it can't be derived from - // stateNum/statusNum, so surfaces without their own report status badge have to report it here. - const previewStatusText: TranslationPathOrText[] = []; - if (isPending(transaction)) { - previewStatusText.push({translationPath: 'iou.pending'}); + previewHeaderText.push(dotSeparator, {translationPath: 'iou.pending'}); + } + + if (hasPendingRTERViolation(violations)) { + previewHeaderText.push(dotSeparator, { + translationPath: 'iou.pendingMatch', + }); + } + + let isPreviewHeaderTextComplete = false; + + if (isMoneyRequestSettled && !iouReport?.isCancelledIOU && !isPartialHold && !hasActionWithErrors) { + previewHeaderText.push(dotSeparator, { + translationPath: isTransactionMadeWithCard ? 'common.done' : 'iou.settledExpensify', + }); + isPreviewHeaderTextComplete = true; } - if (shouldShowCanceledStatus && iouReport?.isCancelledIOU) { - previewStatusText.push({translationPath: 'iou.canceled'}); - } else if (hasPendingRTERViolation(violations)) { - previewStatusText.push({translationPath: 'iou.pendingMatch'}); - } else if (shouldShowHoldMessage) { - previewStatusText.push({translationPath: 'violations.hold'}); + if (!isPreviewHeaderTextComplete) { + if (hasViolationsOfTypeNotice && transaction && !isReportApproved({report: iouReport}) && !isSettled(iouReport?.reportID)) { + previewHeaderText.push(dotSeparator, { + translationPath: 'violations.reviewRequired', + }); + } else if (isExpenseReport(iouReport) && isGroupPolicyUtil(policy) && isReportApproved({report: iouReport}) && !isSettled(iouReport?.reportID) && !isPartialHold) { + previewHeaderText.push(dotSeparator, {translationPath: 'iou.approved'}); + } else if (iouReport?.isCancelledIOU) { + previewHeaderText.push(dotSeparator, {translationPath: 'iou.canceled'}); + } else if (shouldShowHoldMessage) { + previewHeaderText.push(dotSeparator, { + translationPath: 'violations.hold', + }); + } } const amount = isBillSplit ? getAmount(originalTransaction ?? transaction) : requestAmount; @@ -339,15 +374,15 @@ function getTransactionPreviewTextAndTranslationPaths({ } const iouOriginalMessage: OnyxEntry = isMoneyRequestAction(action) ? (getOriginalMessage(action) ?? undefined) : undefined; - const displayDeleteAmountText: TranslationPathOrText = {text: convertToDisplayString(iouOriginalMessage?.amount, iouOriginalMessage?.currency)}; + const displayDeleteAmountText: TranslationPathOrText = { + text: convertToDisplayString(iouOriginalMessage?.amount, iouOriginalMessage?.currency), + }; return { RBRMessage, displayAmountText, displayDeleteAmountText, - previewDateText, - previewStatusText, - previewTypeText, + previewHeaderText, }; } @@ -382,8 +417,12 @@ function createTransactionPreviewConditionals({ }) { const {amount: requestAmount, comment: requestComment, merchant, tag, category} = transactionDetails; - const requestMerchant = truncate(merchant, {length: CONST.REQUEST_PREVIEW.MAX_LENGTH}); - const description = truncate(StringUtils.lineBreaksToSpaces(requestComment), {length: CONST.REQUEST_PREVIEW.MAX_LENGTH}); + const requestMerchant = truncate(merchant, { + length: CONST.REQUEST_PREVIEW.MAX_LENGTH, + }); + const description = truncate(StringUtils.lineBreaksToSpaces(requestComment), { + length: CONST.REQUEST_PREVIEW.MAX_LENGTH, + }); const isMoneyRequestSettled = isSettled(iouReport?.reportID); const isApproved = isReportApproved({report: iouReport}); diff --git a/tests/ui/MoneyRequestReportPreview.test.tsx b/tests/ui/MoneyRequestReportPreview.test.tsx index eca807671e13..faab3f85eb5d 100644 --- a/tests/ui/MoneyRequestReportPreview.test.tsx +++ b/tests/ui/MoneyRequestReportPreview.test.tsx @@ -176,15 +176,14 @@ const renderPage = ({isWhisper = false, isHovered = false}: Partial { +const getTransactionDisplayAmountAndHeaderText = (transaction: Transaction) => { const created = getFormattedCreated(transaction); const date = DateUtils.formatWithUTCTimeZone(created, DateUtils.doesDateBelongToAPastYear(created) ? CONST.DATE.MONTH_DAY_YEAR_ABBR_FORMAT : CONST.DATE.MONTH_DAY_ABBR_FORMAT); const isTransactionMadeWithCard = isManagedCardTransaction(transaction); - // The date leads the supporting line, which can also carry the category and the report status. - const transactionSupportingText = new RegExp(`^${date}`); - const transactionTypeText = isTransactionMadeWithCard ? TestHelper.translateLocal('iou.card') : TestHelper.translateLocal('iou.cash'); + const cashOrCard = isTransactionMadeWithCard ? TestHelper.translateLocal('iou.card') : TestHelper.translateLocal('iou.cash'); + const transactionHeaderText = `${date} ${CONST.DOT_SEPARATOR} ${cashOrCard}`; const transactionDisplayAmount = TestHelper.convertToDisplayString(-transaction.amount, transaction.currency); - return {transactionSupportingText, transactionTypeText, transactionDisplayAmount}; + return {transactionHeaderText, transactionDisplayAmount}; }; const setCurrentWidth = () => { @@ -274,11 +273,10 @@ describe('MoneyRequestReportPreview', () => { expect(screen.getByText(getReportName(mockIOUReport))).toBeOnTheScreen(); for (const transaction of arrayOfTransactions) { - const {transactionDisplayAmount, transactionSupportingText, transactionTypeText} = getTransactionDisplayAmountAndMetadataText(transaction); + const {transactionDisplayAmount, transactionHeaderText} = getTransactionDisplayAmountAndHeaderText(transaction); expect(screen.getAllByText(transactionDisplayAmount).length).toBeGreaterThan(0); - expect(screen.getAllByText(transactionSupportingText)).toHaveLength(arrayOfTransactions.length); - expect(screen.getAllByText(transactionTypeText)).toHaveLength(arrayOfTransactions.length); + expect(screen.getAllByText(transactionHeaderText)).toHaveLength(arrayOfTransactions.length); expect(screen.getAllByText(transaction.merchant)).toHaveLength(arrayOfTransactions.length); } }); diff --git a/tests/unit/TransactionPreviewUtils.test.ts b/tests/unit/TransactionPreviewUtils.test.ts index b80f1d1f3fac..1b2df91cf82f 100644 --- a/tests/unit/TransactionPreviewUtils.test.ts +++ b/tests/unit/TransactionPreviewUtils.test.ts @@ -52,7 +52,6 @@ const basicProps = { transactionDetails: {}, isBillSplit: false, shouldShowRBR: false, - shouldShowCanceledStatus: false, isReportAPolicyExpenseChat: false, areThereDuplicates: false, currentUserEmail: '', @@ -116,7 +115,7 @@ describe('TransactionPreviewUtils', () => { const functionArgs = {...basicProps, iouReport: undefined, transaction: undefined, originalTransaction: undefined}; const result = getTransactionPreviewTextAndTranslationPaths(functionArgs); expect(result.RBRMessage.text).toEqual(''); - expect(result.previewTypeText).toEqual({translationPath: 'iou.cash'}); + expect(result.previewHeaderText).toContainEqual({translationPath: 'iou.cash'}); expect(result.displayAmountText.text).toEqual('$0.00'); }); @@ -151,7 +150,7 @@ describe('TransactionPreviewUtils', () => { expect(result.RBRMessage.translationPath).toEqual('iou.missingAmount'); }); - it('should display cash or card as the preview type', () => { + it('should display showCashOrCard in previewHeaderText', () => { const functionArgsWithCardTransaction = { ...basicProps, transaction: { @@ -163,14 +162,14 @@ describe('TransactionPreviewUtils', () => { const cardTransaction = getTransactionPreviewTextAndTranslationPaths(functionArgsWithCardTransaction); const cashTransaction = getTransactionPreviewTextAndTranslationPaths({...basicProps}); - expect(cardTransaction.previewTypeText).toEqual({translationPath: 'common.card'}); - expect(cashTransaction.previewTypeText).toEqual({translationPath: 'iou.cash'}); + expect(cardTransaction.previewHeaderText).toEqual(expect.arrayContaining([{translationPath: 'common.card'}])); + expect(cashTransaction.previewHeaderText).toEqual(expect.arrayContaining([{translationPath: 'iou.cash'}])); }); it('displays appropriate header text if the transaction is bill split', () => { const functionArgs = {...basicProps, isBillSplit: true, originalTransaction: undefined}; const result = getTransactionPreviewTextAndTranslationPaths(functionArgs); - expect(result.previewTypeText).toEqual({translationPath: 'iou.split'}); + expect(result.previewHeaderText).toEqual(expect.arrayContaining([{translationPath: 'iou.split'}])); }); it('displays description when receipt is being scanned', () => { @@ -181,15 +180,13 @@ describe('TransactionPreviewUtils', () => { merchant: 'Expense', }; const result = getTransactionPreviewTextAndTranslationPaths(functionArgs); - expect(result.previewTypeText).toEqual({translationPath: 'common.receipt'}); + expect(result.previewHeaderText).toEqual(expect.arrayContaining([{translationPath: 'common.receipt'}])); }); it('should apply correct text when transaction is pending and not a bill split', () => { const functionArgs = {...basicProps, transaction: {...basicProps.transaction, status: CONST.TRANSACTION.STATUS.PENDING}, originalTransaction: undefined}; const result = getTransactionPreviewTextAndTranslationPaths(functionArgs); - // Pending is a transaction status, so it belongs to the supporting line and must not replace the expense type. - expect(result.previewStatusText).toContainEqual({translationPath: 'iou.pending'}); - expect(result.previewTypeText).toEqual({translationPath: 'iou.cash'}); + expect(result.previewHeaderText).toEqual(expect.arrayContaining([{translationPath: 'iou.pending'}])); }); it('handles currency and amount display during scanning correctly', () => { @@ -230,19 +227,13 @@ describe('TransactionPreviewUtils', () => { expect(result.displayAmountText.text).toEqual(convertAmountToDisplayString(modifiedAmount, currency)); }); - it('does not show the canceled status inside a report preview, because the preview header already shows it', () => { - const functionArgs = {...basicProps, iouReport: {...basicProps.iouReport, isCancelledIOU: true}, originalTransaction: undefined, shouldShowCanceledStatus: false}; + it('shows approved message when the iouReport is canceled', () => { + const functionArgs = {...basicProps, iouReport: {...basicProps.iouReport, isCancelledIOU: true}, originalTransaction: undefined}; const result = getTransactionPreviewTextAndTranslationPaths(functionArgs); - expect(result.previewStatusText).toEqual([]); + expect(result.previewHeaderText).toContainEqual({translationPath: 'iou.canceled'}); }); - it('shows the canceled status in a standalone preview, because nothing else on that surface reports it', () => { - const functionArgs = {...basicProps, iouReport: {...basicProps.iouReport, isCancelledIOU: true}, originalTransaction: undefined, shouldShowCanceledStatus: true}; - const result = getTransactionPreviewTextAndTranslationPaths(functionArgs); - expect(result.previewStatusText).toContainEqual({translationPath: 'iou.canceled'}); - }); - - it('does not show the approved status when the report is approved, because it is redundant with the report status badge', () => { + it('should include "Approved" in the preview when the report is approved, regardless of whether RBR is shown', () => { const functionArgs = { ...basicProps, iouReport: { @@ -257,7 +248,7 @@ describe('TransactionPreviewUtils', () => { }; const result = getTransactionPreviewTextAndTranslationPaths(functionArgs); - expect(result.previewStatusText).toEqual([]); + expect(result.previewHeaderText).toContainEqual({translationPath: 'iou.approved'}); }); it('should display the correct amount for a bill split transaction', () => {