diff --git a/src/pages/inbox/report/ReportActionItemMessage.tsx b/src/pages/inbox/report/ReportActionItemMessage.tsx
index 4c90e169a3e0..ac908369af85 100644
--- a/src/pages/inbox/report/ReportActionItemMessage.tsx
+++ b/src/pages/inbox/report/ReportActionItemMessage.tsx
@@ -1,35 +1,26 @@
-import type {ReactElement} from 'react';
import React from 'react';
import type {StyleProp, TextStyle, ViewStyle} from 'react-native';
import {View} from 'react-native';
import Button from '@components/Button';
import Text from '@components/Text';
import useLocalize from '@hooks/useLocalize';
-import useOnyx from '@hooks/useOnyx';
import useThemeStyles from '@hooks/useThemeStyles';
-import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID';
import Navigation from '@libs/Navigation/Navigation';
import {
- getLinkedTransactionID,
getMemberChangeMessageFragment,
getOriginalMessage,
- getReportActionMessage,
- getReportActionMessageFragments,
getUpdateRoomDescriptionFragment,
- isAddCommentAction,
- isApprovedOrSubmittedReportAction as isApprovedOrSubmittedReportActionUtils,
isMemberChangeAction,
isMoneyRequestAction,
isReimbursementDirectionInformationRequiredAction,
- isThreadParentMessage,
} from '@libs/ReportActionsUtils';
-import {getIOUReportActionDisplayMessage, getReportName, hasMissingInvoiceBankAccount, isSettled} from '@libs/ReportUtils';
+import {getReportName} from '@libs/ReportUtils';
import CONST from '@src/CONST';
-import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type {ReportAction} from '@src/types/onyx';
+import IouReportActionMessage from './actionContents/IouReportActionMessage';
+import ReportActionMessageContent from './actionContents/ReportActionMessageContent';
import TextCommentFragment from './comment/TextCommentFragment';
-import ReportActionItemFragment from './ReportActionItemFragment';
type ReportActionItemMessageProps = {
/** The report action */
@@ -51,12 +42,18 @@ type ReportActionItemMessageProps = {
function ReportActionItemMessage({action, displayAsGroup, reportID, style, isHidden = false}: ReportActionItemMessageProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
- const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`);
- const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${getNonEmptyStringOnyxID(getLinkedTransactionID(action))}`);
- const [bankAccountList] = useOnyx(ONYXKEYS.BANK_ACCOUNT_LIST);
- const fragments = getReportActionMessageFragments(translate, action);
- const isIOUReport = isMoneyRequestAction(action);
+ if (isMoneyRequestAction(action)) {
+ return (
+
+ );
+ }
if (isMemberChangeAction(action)) {
// This will be fixed: https://github.com/Expensify/App/issues/76852
@@ -98,7 +95,6 @@ function ReportActionItemMessage({action, displayAsGroup, reportID, style, isHid
Navigation.navigate(ROUTES.BANK_ACCOUNT_ENTER_SIGNER_INFO.getRoute(policyID, bankAccountID, isCompleted));
};
-
if (isReimbursementDirectionInformationRequiredAction(action)) {
const {bankAccountLastFour, currency, policyID, bankAccountID, completed} =
getOriginalMessage(action) ?? {};
@@ -118,86 +114,14 @@ function ReportActionItemMessage({action, displayAsGroup, reportID, style, isHid
);
}
- let iouMessage: string | undefined;
- if (isIOUReport) {
- const originalMessage = action.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? getOriginalMessage(action) : null;
- const iouReportID = originalMessage?.IOUReportID;
- if (iouReportID) {
- iouMessage = getIOUReportActionDisplayMessage(translate, action, transaction, report, bankAccountList);
- }
- }
-
- const isApprovedOrSubmittedReportAction = isApprovedOrSubmittedReportActionUtils(action);
-
- const isHoldReportAction = [CONST.REPORT.ACTIONS.TYPE.HOLD, CONST.REPORT.ACTIONS.TYPE.UNHOLD].some((type) => type === action.actionName);
-
- /**
- * Get the ReportActionItemFragments
- * @param shouldWrapInText determines whether the fragments are wrapped in a Text component
- * @returns report action item fragments
- */
- const renderReportActionItemFragments = (shouldWrapInText: boolean): ReactElement | ReactElement[] => {
- const reportActionItemFragments = fragments.map((fragment, index) => (
-
- ));
-
- // Approving or submitting reports in oldDot results in system messages made up of multiple fragments of `TEXT` type
- // which we need to wrap in `` to prevent them rendering on separate lines.
- return shouldWrapInText ? {reportActionItemFragments} : reportActionItemFragments;
- };
-
- const openWorkspaceInvoicesPage = () => {
- const policyID = report?.policyID;
-
- if (!policyID) {
- return;
- }
-
- Navigation.navigate(ROUTES.WORKSPACE_INVOICES.getRoute(policyID));
- };
-
- const shouldShowAddBankAccountButton = action.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && hasMissingInvoiceBankAccount(reportID) && !isSettled(reportID);
-
return (
-
- {!isHidden ? (
- <>
- {renderReportActionItemFragments(isApprovedOrSubmittedReportAction)}
- {shouldShowAddBankAccountButton && (
-
- )}
- >
- ) : (
- {translate('moderation.flaggedContent')}
- )}
-
+
);
}
diff --git a/src/pages/inbox/report/actionContents/IouReportActionMessage.tsx b/src/pages/inbox/report/actionContents/IouReportActionMessage.tsx
new file mode 100644
index 000000000000..cf11b3cc3bf9
--- /dev/null
+++ b/src/pages/inbox/report/actionContents/IouReportActionMessage.tsx
@@ -0,0 +1,55 @@
+import React from 'react';
+import type {StyleProp, TextStyle, ViewStyle} from 'react-native';
+import useLocalize from '@hooks/useLocalize';
+import useOnyx from '@hooks/useOnyx';
+import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID';
+import {getLinkedTransactionID, getOriginalMessage, isActionOfType} from '@libs/ReportActionsUtils';
+import {getIOUReportActionDisplayMessage} from '@libs/ReportUtils';
+import CONST from '@src/CONST';
+import ONYXKEYS from '@src/ONYXKEYS';
+import type {ReportAction} from '@src/types/onyx';
+import ReportActionMessageContent from './ReportActionMessageContent';
+
+type IouReportActionMessageProps = {
+ /** The report action */
+ action: ReportAction;
+
+ /** Should the comment have the appearance of being grouped with the previous comment? */
+ displayAsGroup: boolean;
+
+ /** Additional styles to add after local styles. */
+ style?: StyleProp;
+
+ /** Whether or not the message is hidden by moderation */
+ isHidden?: boolean;
+
+ /** The ID of the report */
+ reportID: string | undefined;
+};
+
+function IouReportActionMessage({action, displayAsGroup, reportID, style, isHidden = false}: IouReportActionMessageProps) {
+ const {translate} = useLocalize();
+ const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(reportID)}`);
+ const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${getNonEmptyStringOnyxID(getLinkedTransactionID(action))}`);
+ const [bankAccountList] = useOnyx(ONYXKEYS.BANK_ACCOUNT_LIST);
+
+ let iouMessage: string | undefined;
+ const originalMessage = isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.IOU) ? getOriginalMessage(action) : null;
+ const iouReportID = originalMessage?.IOUReportID;
+ if (iouReportID) {
+ iouMessage = getIOUReportActionDisplayMessage(translate, action, transaction, report, bankAccountList);
+ }
+
+ return (
+
+ );
+}
+
+export default IouReportActionMessage;
diff --git a/src/pages/inbox/report/actionContents/ReportActionMessageContent.tsx b/src/pages/inbox/report/actionContents/ReportActionMessageContent.tsx
new file mode 100644
index 000000000000..ac2bc0603e25
--- /dev/null
+++ b/src/pages/inbox/report/actionContents/ReportActionMessageContent.tsx
@@ -0,0 +1,90 @@
+import type {ReactElement} from 'react';
+import React from 'react';
+import type {StyleProp, TextStyle, ViewStyle} from 'react-native';
+import {View} from 'react-native';
+import Text from '@components/Text';
+import useLocalize from '@hooks/useLocalize';
+import useThemeStyles from '@hooks/useThemeStyles';
+import {
+ getOriginalMessage,
+ getReportActionMessage,
+ getReportActionMessageFragments,
+ isAddCommentAction,
+ isApprovedOrSubmittedReportAction as isApprovedOrSubmittedReportActionUtils,
+ isThreadParentMessage,
+} from '@libs/ReportActionsUtils';
+import ReportActionItemFragment from '@pages/inbox/report/ReportActionItemFragment';
+import CONST from '@src/CONST';
+import type {ReportAction} from '@src/types/onyx';
+
+type ReportActionMessageContentProps = {
+ /** The report action */
+ action: ReportAction;
+
+ /** Should the comment have the appearance of being grouped with the previous comment? */
+ displayAsGroup: boolean;
+
+ /** Additional styles to add after local styles. */
+ style?: StyleProp;
+
+ /** Whether or not the message is hidden by moderation */
+ isHidden?: boolean;
+
+ /** The ID of the report */
+ reportID: string | undefined;
+
+ /** Optional IOU display message passed into each fragment */
+ iouMessage?: string;
+};
+
+function ReportActionMessageContent({action, displayAsGroup, reportID, style, isHidden = false, iouMessage}: ReportActionMessageContentProps) {
+ const styles = useThemeStyles();
+ const {translate} = useLocalize();
+ const isApprovedOrSubmittedReportAction = isApprovedOrSubmittedReportActionUtils(action);
+ const isHoldReportAction = [CONST.REPORT.ACTIONS.TYPE.HOLD, CONST.REPORT.ACTIONS.TYPE.UNHOLD].some((type) => type === action.actionName);
+ const fragments = getReportActionMessageFragments(translate, action);
+
+ const renderReportActionItemFragments = (shouldWrapInText: boolean): ReactElement | ReactElement[] => {
+ const reportActionItemFragments = fragments.map((fragment, index) => (
+
+ ));
+
+ // Approving or submitting reports in oldDot results in system messages made up of multiple fragments of `TEXT` type
+ // which we need to wrap in `` to prevent them rendering on separate lines.
+ return shouldWrapInText ? {reportActionItemFragments} : reportActionItemFragments;
+ };
+
+ return (
+
+ {!isHidden ? (
+ renderReportActionItemFragments(isApprovedOrSubmittedReportAction)
+ ) : (
+ {translate('moderation.flaggedContent')}
+ )}
+
+ );
+}
+
+export default ReportActionMessageContent;
diff --git a/tests/ui/PureReportActionItemTest.tsx b/tests/ui/PureReportActionItemTest.tsx
index 3cbf49f23d3d..e9bdf5b6cb04 100644
--- a/tests/ui/PureReportActionItemTest.tsx
+++ b/tests/ui/PureReportActionItemTest.tsx
@@ -15,6 +15,7 @@ import {setHasRadio} from '@libs/NetworkState';
import Parser from '@libs/Parser';
import {getIOUActionForReportID} from '@libs/ReportActionsUtils';
import PureReportActionItem from '@pages/inbox/report/PureReportActionItem';
+import ReportActionItemMessage from '@pages/inbox/report/ReportActionItemMessage';
import colors from '@styles/theme/colors';
import CONST from '@src/CONST';
import type {TranslationPaths} from '@src/languages/types';
@@ -2634,4 +2635,77 @@ describe('PureReportActionItem', () => {
expect(screen.getByText(translateLocal('actionableMentionTrackExpense.nothing' as TranslationPaths))).toBeOnTheScreen();
});
});
+
+ // Path: No FE flow creates IOU + reject/cancel/delete/approve actions; this defensive path is only
+ // reachable from BE-emitted actions
+ describe('IouReportActionMessage edge subtypes', () => {
+ const TEST_REPORT_ID = 'iouEdgeReport123';
+ const TEST_TRANSACTION_ID = 'iouEdgeTx456';
+
+ it.each([CONST.IOU.REPORT_ACTION_TYPE.REJECT, CONST.IOU.REPORT_ACTION_TYPE.CANCEL, CONST.IOU.REPORT_ACTION_TYPE.DELETE, CONST.IOU.REPORT_ACTION_TYPE.APPROVE])(
+ 'renders IOU display text for IOU + %s action when transaction exists in Onyx',
+ async (subtype) => {
+ await act(async () => {
+ await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${TEST_TRANSACTION_ID}`, {
+ transactionID: TEST_TRANSACTION_ID,
+ amount: 4200,
+ currency: 'USD',
+ reportID: TEST_REPORT_ID,
+ merchant: 'Test Merchant',
+ });
+ await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${TEST_REPORT_ID}`, {
+ reportID: TEST_REPORT_ID,
+ type: CONST.REPORT.TYPE.EXPENSE,
+ statusNum: CONST.REPORT.STATUS_NUM.OPEN,
+ stateNum: CONST.REPORT.STATE_NUM.OPEN,
+ });
+ });
+
+ const action = createReportAction(CONST.REPORT.ACTIONS.TYPE.IOU, {
+ type: subtype,
+ IOUReportID: TEST_REPORT_ID,
+ IOUTransactionID: TEST_TRANSACTION_ID,
+ amount: 4200,
+ currency: 'USD',
+ });
+ renderItemWithAction(action);
+ await waitForBatchedUpdatesWithAct();
+
+ // getIOUReportActionDisplayMessage only special-cases `type === 'pay'`. For all other action
+ // types, the displayed text is picked from the REPORT's state (isSettled, isReportApproved)
+ // and the action's structural shape (split bill, track expense).
+ expect(screen.getByText(translateLocal('iou.expenseAmount', '-$42.00', 'Test Merchant'))).toBeOnTheScreen();
+ },
+ );
+
+ it('renders flagged content text instead of IOU display when isHidden is true', async () => {
+ const action = createReportAction(CONST.REPORT.ACTIONS.TYPE.IOU, {
+ type: CONST.IOU.REPORT_ACTION_TYPE.REJECT,
+ IOUReportID: TEST_REPORT_ID,
+ IOUTransactionID: TEST_TRANSACTION_ID,
+ amount: 4200,
+ currency: 'USD',
+ });
+
+ render(
+
+
+
+
+
+
+
+
+ ,
+ );
+ await waitForBatchedUpdatesWithAct();
+
+ expect(screen.getByText(translateLocal('moderation.flaggedContent'))).toBeOnTheScreen();
+ });
+ });
});