From 06bd8dec353c4f0cdde2329dab5187c1078cadfe Mon Sep 17 00:00:00 2001 From: Tim Golen Date: Tue, 17 Mar 2026 11:01:40 -0600 Subject: [PATCH 1/5] Pass originalReportID to context menu for report --- .../BaseAnchorForAttachmentsOnly.tsx | 4 ++-- .../HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx | 4 ++-- .../HTMLRenderers/MentionUserRenderer.tsx | 4 ++-- .../HTMLEngineProvider/HTMLRenderers/PreRenderer.tsx | 4 ++-- src/components/ReportActionItem/MoneyRequestAction.tsx | 5 +++++ .../MoneyRequestReportPreviewContent.tsx | 3 ++- .../ReportActionItem/MoneyRequestReportPreview/index.tsx | 3 +++ .../ReportActionItem/MoneyRequestReportPreview/types.ts | 3 +++ src/components/ReportActionItem/TaskPreview.tsx | 5 +++-- .../ReportActionItem/TransactionPreview/index.tsx | 6 +++++- .../ReportActionItem/TransactionPreview/types.ts | 3 +++ src/components/ReportActionItem/TripRoomPreview.tsx | 6 +++++- src/components/ShowContextMenuContext/default.ts | 1 + src/components/ShowContextMenuContext/index.tsx | 9 ++++----- src/components/ShowContextMenuContext/types.ts | 1 + .../VideoPlayerPreview/VideoPlayerThumbnail.tsx | 4 ++-- src/pages/inbox/report/PureReportActionItem.tsx | 7 ++++++- 17 files changed, 51 insertions(+), 21 deletions(-) diff --git a/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.tsx b/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.tsx index eca3f7e5ea53..3ba9efb87c95 100644 --- a/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.tsx +++ b/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.tsx @@ -39,7 +39,7 @@ function BaseAnchorForAttachmentsOnly({style, source = '', displayName = '', onP const encryptedAuthToken = session?.encryptedAuthToken ?? ''; const sourceURLWithAuth = addEncryptedAuthTokenToURL(source, encryptedAuthToken); - const {anchor, report, isReportArchived, action, isDisabled, shouldDisplayContextMenu} = useShowContextMenuState(); + const {anchor, report, isReportArchived, action, isDisabled, shouldDisplayContextMenu, originalReportID} = useShowContextMenuState(); const {checkIfContextMenuActive} = useShowContextMenuActions(); return ( @@ -58,7 +58,7 @@ function BaseAnchorForAttachmentsOnly({style, source = '', displayName = '', onP if (isDisabled || !shouldDisplayContextMenu) { return; } - showContextMenuForReport(event, anchor, report?.reportID, action, checkIfContextMenuActive, isArchivedNonExpenseReport(report, isReportArchived)); + showContextMenuForReport(event, anchor, report?.reportID, action, checkIfContextMenuActive, isArchivedNonExpenseReport(report, isReportArchived), originalReportID); }} shouldUseHapticsOnLongPress accessibilityLabel={displayName} diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx index 55fd1ad0eb39..a19b19976710 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx @@ -90,7 +90,7 @@ function ImageRenderer({tnode}: CustomRendererProps) { /> ); - const {anchor, report, isReportArchived, action, isDisabled, shouldDisplayContextMenu} = useShowContextMenuState(); + const {anchor, report, isReportArchived, action, isDisabled, shouldDisplayContextMenu, originalReportID} = useShowContextMenuState(); const {onShowContextMenu, checkIfContextMenuActive} = useShowContextMenuActions(); return imagePreviewModalDisabled ? ( @@ -124,7 +124,7 @@ function ImageRenderer({tnode}: CustomRendererProps) { return; } return onShowContextMenu(() => - showContextMenuForReport(event, anchor, report?.reportID, action, checkIfContextMenuActive, isArchivedNonExpenseReport(report, isReportArchived)), + showContextMenuForReport(event, anchor, report?.reportID, action, checkIfContextMenuActive, isArchivedNonExpenseReport(report, isReportArchived), originalReportID), ); }} isNested diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.tsx index 540d0b66cab9..8c70c2b94b91 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.tsx @@ -33,7 +33,7 @@ function MentionUserRenderer({style, tnode, TDefaultRenderer, currentUserPersona const htmlAttribAccountID = tnode.attributes.accountid; const personalDetails = usePersonalDetails(); const htmlAttributeAccountID = tnode.attributes.accountid; - const {anchor, report, isReportArchived, action, isDisabled, shouldDisplayContextMenu} = useShowContextMenuState(); + const {anchor, report, isReportArchived, action, isDisabled, shouldDisplayContextMenu, originalReportID} = useShowContextMenuState(); const {onShowContextMenu, checkIfContextMenuActive} = useShowContextMenuActions(); let accountID: number; @@ -83,7 +83,7 @@ function MentionUserRenderer({style, tnode, TDefaultRenderer, currentUserPersona return; } return onShowContextMenu(() => - showContextMenuForReport(event, anchor, report?.reportID, action, checkIfContextMenuActive, isArchivedNonExpenseReport(report, isReportArchived)), + showContextMenuForReport(event, anchor, report?.reportID, action, checkIfContextMenuActive, isArchivedNonExpenseReport(report, isReportArchived), originalReportID), ); }} onPress={(event) => { diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer.tsx index 122666021263..f0d782e6ed6f 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer.tsx @@ -33,7 +33,7 @@ function PreRenderer({TDefaultRenderer, onPressIn, onPressOut, onLongPress, ...d const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const {translate} = useLocalize(); - const {anchor, report, isReportArchived, action, isDisabled, shouldDisplayContextMenu} = useShowContextMenuState(); + const {anchor, report, isReportArchived, action, isDisabled, shouldDisplayContextMenu, originalReportID} = useShowContextMenuState(); const {onShowContextMenu, checkIfContextMenuActive} = useShowContextMenuActions(); const isLast = defaultRendererProps.renderIndex === defaultRendererProps.renderLength - 1; @@ -63,7 +63,7 @@ function PreRenderer({TDefaultRenderer, onPressIn, onPressOut, onLongPress, ...d if (isDisabled || !shouldDisplayContextMenu) { return; } - return showContextMenuForReport(event, anchor, report?.reportID, action, checkIfContextMenuActive, isArchivedNonExpenseReport(report, isReportArchived)); + return showContextMenuForReport(event, anchor, report?.reportID, action, checkIfContextMenuActive, isArchivedNonExpenseReport(report, isReportArchived), originalReportID); }); }} shouldUseHapticsOnLongPress diff --git a/src/components/ReportActionItem/MoneyRequestAction.tsx b/src/components/ReportActionItem/MoneyRequestAction.tsx index ef8a92f5b422..f1b7df53c563 100644 --- a/src/components/ReportActionItem/MoneyRequestAction.tsx +++ b/src/components/ReportActionItem/MoneyRequestAction.tsx @@ -68,6 +68,9 @@ type MoneyRequestActionProps = { /** Whether context menu should be shown on press */ shouldDisplayContextMenu?: boolean; + + /** ID of the original report from which the given reportAction is first created */ + originalReportID?: string; }; function MoneyRequestAction({ @@ -82,6 +85,7 @@ function MoneyRequestAction({ style, isWhisper = false, shouldDisplayContextMenu = true, + originalReportID, }: MoneyRequestActionProps) { const {shouldOpenReportInRHP} = useReportActionItemState(); const {onPreviewPressed} = useReportActionItemActions(); @@ -186,6 +190,7 @@ function MoneyRequestAction({ isHovered={isHovered} isWhisper={isWhisper} shouldDisplayContextMenu={shouldDisplayContextMenu} + originalReportID={originalReportID} /> ); } diff --git a/src/components/ReportActionItem/MoneyRequestReportPreview/MoneyRequestReportPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestReportPreview/MoneyRequestReportPreviewContent.tsx index a1536c827050..5c73838ca45f 100644 --- a/src/components/ReportActionItem/MoneyRequestReportPreview/MoneyRequestReportPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestReportPreview/MoneyRequestReportPreviewContent.tsx @@ -132,6 +132,7 @@ function MoneyRequestReportPreviewContent({ shouldShowBorder = false, onPress, forwardedFSClass, + originalReportID, }: MoneyRequestReportPreviewContentProps) { const [userBillingGraceEndPeriods] = useOnyx(ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_USER_BILLING_GRACE_PERIOD_END); const [chatReportMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}${chatReportID}`); @@ -893,7 +894,7 @@ function MoneyRequestReportPreviewContent({ if (!shouldDisplayContextMenu) { return; } - showContextMenuForReport(event, contextMenuAnchor, chatReportID, action, checkIfContextMenuActive); + showContextMenuForReport(event, contextMenuAnchor, chatReportID, action, checkIfContextMenuActive, false, originalReportID); }} shouldUseHapticsOnLongPress style={[ diff --git a/src/components/ReportActionItem/MoneyRequestReportPreview/index.tsx b/src/components/ReportActionItem/MoneyRequestReportPreview/index.tsx index 9ce325e9dc7b..d9a62a7f9e29 100644 --- a/src/components/ReportActionItem/MoneyRequestReportPreview/index.tsx +++ b/src/components/ReportActionItem/MoneyRequestReportPreview/index.tsx @@ -39,6 +39,7 @@ function MoneyRequestReportPreview({ onPaymentOptionsHide, shouldDisplayContextMenu = true, shouldShowBorder, + originalReportID, }: MoneyRequestReportPreviewProps) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); @@ -148,6 +149,7 @@ function MoneyRequestReportPreview({ onPreviewPressed={openReportFromPreview} shouldShowPayerAndReceiver={shouldShowPayerAndReceiver} shouldHighlight={!!newTransactionIDs?.has(item.transactionID)} + originalReportID={originalReportID} /> ); @@ -180,6 +182,7 @@ function MoneyRequestReportPreview({ onPress={openReportFromPreview} shouldShowBorder={shouldShowBorder} forwardedFSClass={CONST.FULLSTORY.CLASS.UNMASK} + originalReportID={originalReportID} /> ); } diff --git a/src/components/ReportActionItem/MoneyRequestReportPreview/types.ts b/src/components/ReportActionItem/MoneyRequestReportPreview/types.ts index 9114474e9f0c..e404dec12959 100644 --- a/src/components/ReportActionItem/MoneyRequestReportPreview/types.ts +++ b/src/components/ReportActionItem/MoneyRequestReportPreview/types.ts @@ -60,6 +60,9 @@ type MoneyRequestReportPreviewProps = { /** Whether to show a border to separate Reports Chat Item and Money Request Report Preview */ shouldShowBorder?: boolean; + + /** ID of the original report from which the given reportAction is first created */ + originalReportID?: string; }; type MoneyRequestReportPreviewContentOnyxProps = { diff --git a/src/components/ReportActionItem/TaskPreview.tsx b/src/components/ReportActionItem/TaskPreview.tsx index 65ac9d320d0c..65d3ba20d140 100644 --- a/src/components/ReportActionItem/TaskPreview.tsx +++ b/src/components/ReportActionItem/TaskPreview.tsx @@ -8,7 +8,7 @@ import Icon from '@components/Icon'; import {usePersonalDetails} from '@components/OnyxListItemProvider'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; import RenderHTML from '@components/RenderHTML'; -import {showContextMenuForReport} from '@components/ShowContextMenuContext'; +import {showContextMenuForReport, useShowContextMenuState} from '@components/ShowContextMenuContext'; import UserDetailsTooltip from '@components/UserDetailsTooltip'; import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails'; import type {WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails'; @@ -85,6 +85,7 @@ function TaskPreview({ const StyleUtils = useStyleUtils(); const {translate} = useLocalize(); const theme = useTheme(); + const {originalReportID} = useShowContextMenuState(); const taskReportID = taskReport?.reportID ?? action?.childReportID; const taskTitle = action?.childReportName ?? taskReport?.reportName ?? ''; @@ -133,7 +134,7 @@ function TaskPreview({ if (!shouldDisplayContextMenu) { return; } - return showContextMenuForReport(event, contextMenuAnchor, chatReportID, action, checkIfContextMenuActive); + return showContextMenuForReport(event, contextMenuAnchor, chatReportID, action, checkIfContextMenuActive, false, originalReportID); }) } shouldUseHapticsOnLongPress diff --git a/src/components/ReportActionItem/TransactionPreview/index.tsx b/src/components/ReportActionItem/TransactionPreview/index.tsx index 2b930869c6d8..2c976d67958f 100644 --- a/src/components/ReportActionItem/TransactionPreview/index.tsx +++ b/src/components/ReportActionItem/TransactionPreview/index.tsx @@ -43,6 +43,7 @@ function TransactionPreview(props: TransactionPreviewProps) { shouldHighlight, reportPreviewAction, contextAction, + originalReportID, } = props; const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${iouReportID}`); @@ -71,11 +72,14 @@ function TransactionPreview(props: TransactionPreviewProps) { const transactionDetails = getTransactionDetails(transaction); const {amount: requestAmount, currency: requestCurrency} = transactionDetails ?? {}; + const contextMenuReportID = contextAction ? chatReportID : reportID; + const contextMenuAction = contextAction ?? action; + const showContextMenu = (event: GestureResponderEvent) => { if (!shouldDisplayContextMenu) { return; } - showContextMenuForReport(event, contextMenuAnchor, contextAction ? chatReportID : reportID, contextAction ?? action, checkIfContextMenuActive); + showContextMenuForReport(event, contextMenuAnchor, contextMenuReportID, contextMenuAction, checkIfContextMenuActive, false, originalReportID); }; const offlineWithFeedbackOnClose = useCallback(() => { diff --git a/src/components/ReportActionItem/TransactionPreview/types.ts b/src/components/ReportActionItem/TransactionPreview/types.ts index 99d889bfba7a..9a30127ce1b9 100644 --- a/src/components/ReportActionItem/TransactionPreview/types.ts +++ b/src/components/ReportActionItem/TransactionPreview/types.ts @@ -72,6 +72,9 @@ type TransactionPreviewProps = { /** Whether the item should be highlighted */ shouldHighlight?: boolean; + + /** ID of the original report from which the given reportAction is first created */ + originalReportID?: string; }; type TransactionPreviewContentProps = { diff --git a/src/components/ReportActionItem/TripRoomPreview.tsx b/src/components/ReportActionItem/TripRoomPreview.tsx index a115e968bea6..eab39451fac0 100644 --- a/src/components/ReportActionItem/TripRoomPreview.tsx +++ b/src/components/ReportActionItem/TripRoomPreview.tsx @@ -51,6 +51,9 @@ type TripRoomPreviewProps = { /** Whether the corresponding report action item is hovered */ isHovered?: boolean; + /** ID of the original report from which the given reportAction is first created */ + originalReportID?: string; + /** Whether context menu should be shown on press */ shouldDisplayContextMenu?: boolean; }; @@ -123,6 +126,7 @@ function TripRoomPreview({ isHovered = false, checkIfContextMenuActive = () => {}, shouldDisplayContextMenu = true, + originalReportID, }: TripRoomPreviewProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -172,7 +176,7 @@ function TripRoomPreview({ if (!shouldDisplayContextMenu) { return; } - showContextMenuForReport(event, contextMenuAnchor, chatReportID, action, checkIfContextMenuActive); + showContextMenuForReport(event, contextMenuAnchor, chatReportID, action, checkIfContextMenuActive, false, originalReportID); }} shouldUseHapticsOnLongPress style={[styles.flexRow, styles.justifyContentBetween, styles.reportPreviewBox]} diff --git a/src/components/ShowContextMenuContext/default.ts b/src/components/ShowContextMenuContext/default.ts index fb961ad6b127..e4453753ccdf 100644 --- a/src/components/ShowContextMenuContext/default.ts +++ b/src/components/ShowContextMenuContext/default.ts @@ -8,6 +8,7 @@ const defaultShowContextMenuStateContextValue: ShowContextMenuStateContextType = transactionThreadReport: undefined, isDisabled: true, shouldDisplayContextMenu: true, + originalReportID: undefined, }; const defaultShowContextMenuActionsContextValue: ShowContextMenuActionsContextType = { diff --git a/src/components/ShowContextMenuContext/index.tsx b/src/components/ShowContextMenuContext/index.tsx index d6e7aa6183ee..ec2463a1437e 100644 --- a/src/components/ShowContextMenuContext/index.tsx +++ b/src/components/ShowContextMenuContext/index.tsx @@ -1,13 +1,10 @@ import {createContext, useContext} from 'react'; // eslint-disable-next-line no-restricted-imports import type {GestureResponderEvent} from 'react-native'; -import type {OnyxEntry} from 'react-native-onyx'; import {canUseTouchScreen} from '@libs/DeviceCapabilities'; -import {getOriginalReportID} from '@libs/ReportUtils'; import {showContextMenu} from '@pages/inbox/report/ContextMenu/ReportActionContextMenu'; import type {ContextMenuAnchor} from '@pages/inbox/report/ContextMenu/ReportActionContextMenu'; import CONST from '@src/CONST'; -import type {ReportAction} from '@src/types/onyx'; import {defaultShowContextMenuActionsContextValue, defaultShowContextMenuStateContextValue} from './default'; import type {ShowContextMenuActionsContextType, ShowContextMenuStateContextType} from './types'; @@ -31,14 +28,16 @@ function useShowContextMenuActions(): ShowContextMenuActionsContextType { * @param action - ReportAction for ContextMenu * @param checkIfContextMenuActive Callback to update context menu active state * @param isArchivedRoom - Is the report an archived room + * @param originalReportID - ID of the original report from which the given reportAction is first created */ function showContextMenuForReport( event: GestureResponderEvent | MouseEvent, anchor: ContextMenuAnchor, reportID: string | undefined, - action: OnyxEntry, + action: {reportActionID?: string} | null | undefined, checkIfContextMenuActive: () => void, isArchivedRoom = false, + originalReportID?: string, ) { if (!canUseTouchScreen()) { return; @@ -51,7 +50,7 @@ function showContextMenuForReport( contextMenuAnchor: anchor, report: { reportID, - originalReportID: reportID ? getOriginalReportID(reportID, action, undefined) : undefined, + originalReportID: originalReportID ?? reportID, isArchivedRoom, }, reportAction: { diff --git a/src/components/ShowContextMenuContext/types.ts b/src/components/ShowContextMenuContext/types.ts index a75c3376bde7..dd612d47d35b 100644 --- a/src/components/ShowContextMenuContext/types.ts +++ b/src/components/ShowContextMenuContext/types.ts @@ -10,6 +10,7 @@ type ShowContextMenuStateContextType = { transactionThreadReport?: OnyxEntry; isDisabled: boolean; shouldDisplayContextMenu?: boolean; + originalReportID?: string; }; type ShowContextMenuActionsContextType = { diff --git a/src/components/VideoPlayerPreview/VideoPlayerThumbnail.tsx b/src/components/VideoPlayerPreview/VideoPlayerThumbnail.tsx index 285ffe861f6d..8e470a012643 100644 --- a/src/components/VideoPlayerPreview/VideoPlayerThumbnail.tsx +++ b/src/components/VideoPlayerPreview/VideoPlayerThumbnail.tsx @@ -31,7 +31,7 @@ type VideoPlayerThumbnailProps = { function VideoPlayerThumbnail({thumbnailUrl, onPress, accessibilityLabel, isDeleted}: VideoPlayerThumbnailProps) { const styles = useThemeStyles(); const icons = useMemoizedLazyExpensifyIcons(['Play'] as const); - const {anchor, report, isReportArchived, action, isDisabled, shouldDisplayContextMenu} = useShowContextMenuState(); + const {anchor, report, isReportArchived, action, isDisabled, shouldDisplayContextMenu, originalReportID} = useShowContextMenuState(); const {onShowContextMenu, checkIfContextMenuActive} = useShowContextMenuActions(); return ( @@ -59,7 +59,7 @@ function VideoPlayerThumbnail({thumbnailUrl, onPress, accessibilityLabel, isDele return; } onShowContextMenu(() => { - showContextMenuForReport(event, anchor, report?.reportID, action, checkIfContextMenuActive, isArchivedNonExpenseReport(report, isReportArchived)); + showContextMenuForReport(event, anchor, report?.reportID, action, checkIfContextMenuActive, isArchivedNonExpenseReport(report, isReportArchived), originalReportID); }); }} shouldUseHapticsOnLongPress diff --git a/src/pages/inbox/report/PureReportActionItem.tsx b/src/pages/inbox/report/PureReportActionItem.tsx index 1c18f15eff51..2ab192ea3975 100644 --- a/src/pages/inbox/report/PureReportActionItem.tsx +++ b/src/pages/inbox/report/PureReportActionItem.tsx @@ -832,8 +832,9 @@ function PureReportActionItem({ transactionThreadReport, isDisabled: false, shouldDisplayContextMenu: shouldDisplayContextMenuValue, + originalReportID, }), - [report, action, transactionThreadReport, shouldDisplayContextMenuValue, isReportArchived], + [report, action, transactionThreadReport, shouldDisplayContextMenuValue, isReportArchived, originalReportID], ); const contextMenuActionsValue = useMemo( @@ -1169,6 +1170,7 @@ function PureReportActionItem({ style={displayAsGroup ? [] : [styles.mt2]} isWhisper={isWhisper} shouldDisplayContextMenu={shouldDisplayContextMenuValue} + originalReportID={originalReportID} /> ); @@ -1213,6 +1215,7 @@ function PureReportActionItem({ Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(action.childReportID, undefined, undefined, Navigation.getActiveRoute())); }} isTrackExpense={isTrackExpenseActionReportActionsUtils(action)} + originalReportID={originalReportID} /> ); @@ -1231,6 +1234,7 @@ function PureReportActionItem({ containerStyles={displayAsGroup ? [] : [styles.mt2]} checkIfContextMenuActive={toggleContextMenuFromActiveReportAction} shouldDisplayContextMenu={shouldDisplayContextMenuValue} + originalReportID={originalReportID} /> ); } else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW && isClosedExpenseReportWithNoExpenses) { @@ -1250,6 +1254,7 @@ function PureReportActionItem({ onPaymentOptionsHide={() => setIsPaymentMethodPopoverActive(false)} shouldDisplayContextMenu={shouldDisplayContextMenuValue} shouldShowBorder={shouldShowBorder} + originalReportID={originalReportID} /> ); } else if (isTaskAction(action)) { From c6fe3c263e28e63e4d1da26c95728be674d5636d Mon Sep 17 00:00:00 2001 From: Tim Golen Date: Tue, 17 Mar 2026 11:11:57 -0600 Subject: [PATCH 2/5] style --- .../HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx | 10 +++++++++- .../HTMLEngineProvider/HTMLRenderers/PreRenderer.tsx | 10 +++++++++- .../VideoPlayerPreview/VideoPlayerThumbnail.tsx | 10 +++++++++- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx index a19b19976710..e28a45a5a9c2 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx @@ -124,7 +124,15 @@ function ImageRenderer({tnode}: CustomRendererProps) { return; } return onShowContextMenu(() => - showContextMenuForReport(event, anchor, report?.reportID, action, checkIfContextMenuActive, isArchivedNonExpenseReport(report, isReportArchived), originalReportID), + showContextMenuForReport( + event, + anchor, + report?.reportID, + action, + checkIfContextMenuActive, + isArchivedNonExpenseReport(report, isReportArchived), + originalReportID, + ), ); }} isNested diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer.tsx index f0d782e6ed6f..cf64b04a3e78 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer.tsx @@ -63,7 +63,15 @@ function PreRenderer({TDefaultRenderer, onPressIn, onPressOut, onLongPress, ...d if (isDisabled || !shouldDisplayContextMenu) { return; } - return showContextMenuForReport(event, anchor, report?.reportID, action, checkIfContextMenuActive, isArchivedNonExpenseReport(report, isReportArchived), originalReportID); + return showContextMenuForReport( + event, + anchor, + report?.reportID, + action, + checkIfContextMenuActive, + isArchivedNonExpenseReport(report, isReportArchived), + originalReportID, + ); }); }} shouldUseHapticsOnLongPress diff --git a/src/components/VideoPlayerPreview/VideoPlayerThumbnail.tsx b/src/components/VideoPlayerPreview/VideoPlayerThumbnail.tsx index 8e470a012643..4cb5e8be8d83 100644 --- a/src/components/VideoPlayerPreview/VideoPlayerThumbnail.tsx +++ b/src/components/VideoPlayerPreview/VideoPlayerThumbnail.tsx @@ -59,7 +59,15 @@ function VideoPlayerThumbnail({thumbnailUrl, onPress, accessibilityLabel, isDele return; } onShowContextMenu(() => { - showContextMenuForReport(event, anchor, report?.reportID, action, checkIfContextMenuActive, isArchivedNonExpenseReport(report, isReportArchived), originalReportID); + showContextMenuForReport( + event, + anchor, + report?.reportID, + action, + checkIfContextMenuActive, + isArchivedNonExpenseReport(report, isReportArchived), + originalReportID, + ); }); }} shouldUseHapticsOnLongPress From 0b271f77203b2c540bb5dd5299d44cb49ef4cc54 Mon Sep 17 00:00:00 2001 From: Tim Golen Date: Tue, 17 Mar 2026 11:12:37 -0600 Subject: [PATCH 3/5] add a test --- tests/unit/MentionUserRendererTest.tsx | 78 ++++++++++++++++++++++++-- 1 file changed, 72 insertions(+), 6 deletions(-) diff --git a/tests/unit/MentionUserRendererTest.tsx b/tests/unit/MentionUserRendererTest.tsx index 738bf00be64c..a7ff1c64a9d7 100644 --- a/tests/unit/MentionUserRendererTest.tsx +++ b/tests/unit/MentionUserRendererTest.tsx @@ -9,11 +9,12 @@ import OnyxListItemProvider from '@components/OnyxListItemProvider'; import {ShowContextMenuActionsContext, ShowContextMenuStateContext} from '@components/ShowContextMenuContext'; import type {WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails'; import Navigation from '@libs/Navigation/Navigation'; +import {showContextMenu} from '@pages/inbox/report/ContextMenu/ReportActionContextMenu'; import CONST from '@src/CONST'; import IntlStore from '@src/languages/IntlStore'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import type {PersonalDetails} from '@src/types/onyx'; +import type {PersonalDetails, Report, ReportAction} from '@src/types/onyx'; import {translateLocal} from '../utils/TestHelper'; // Mock Navigation to avoid actual navigation calls @@ -22,6 +23,11 @@ jest.mock('@libs/Navigation/Navigation', () => ({ navigate: jest.fn(), })); +// Mock showContextMenu to verify it's called with correct parameters +jest.mock('@pages/inbox/report/ContextMenu/ReportActionContextMenu', () => ({ + showContextMenu: jest.fn(), +})); + // Simplify react-native-render-html children renderer to just echo the provided data jest.mock('react-native-render-html', () => { const ReactModule = jest.requireActual('react'); @@ -92,17 +98,26 @@ jest.mock('@libs/Log', () => ({ info: jest.fn(), })); -function withProvider(children: ReactNode) { +type ContextMenuStateOverrides = { + report?: Report; + action?: ReportAction; + originalReportID?: string; + isDisabled?: boolean; + shouldDisplayContextMenu?: boolean; +}; + +function withProvider(children: ReactNode, overrides: ContextMenuStateOverrides = {}) { return ( { renderMention({tnode}); expect(screen.getByText('@user@test.com')).toBeVisible(); }); + + test('passes originalReportID to showContextMenu on long press', () => { + // Given a MentionUserRenderer with originalReportID in context + const mockReportID = '12345'; + const mockOriginalReportID = '67890'; + const mockReportActionID = 'action123'; + const mockReport = {reportID: mockReportID} as Report; + const mockAction = {reportActionID: mockReportActionID} as ReportAction; + + mockPersonalDetails = { + 301: {login: 'test@example.com', displayName: 'Test User'}, + }; + const tnode = buildTNode({accountID: '301'}); + + const Component = MentionUserRenderer as unknown as ComponentType<{ + tnode: TText; + TDefaultRenderer: () => null; + style: Record; + }>; + + render( + withProvider( + null} + style={{}} + />, + { + report: mockReport, + action: mockAction, + originalReportID: mockOriginalReportID, + isDisabled: false, + shouldDisplayContextMenu: true, + }, + ), + ); + + // When the user long presses the mention + const mention = screen.getByTestId('mention-user'); + fireEvent(mention, 'longPress', {}); + + // Then showContextMenu should be called with the originalReportID + expect(showContextMenu).toHaveBeenCalledWith( + expect.objectContaining({ + report: expect.objectContaining({ + reportID: mockReportID, + originalReportID: mockOriginalReportID, + }), + }), + ); + }); }); From 33a99487c3c12692663f99d10d4175d7421099de Mon Sep 17 00:00:00 2001 From: Tim Golen Date: Tue, 17 Mar 2026 11:21:21 -0600 Subject: [PATCH 4/5] Clean up types --- src/components/ShowContextMenuContext/index.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/ShowContextMenuContext/index.tsx b/src/components/ShowContextMenuContext/index.tsx index ec2463a1437e..e468f7d0805d 100644 --- a/src/components/ShowContextMenuContext/index.tsx +++ b/src/components/ShowContextMenuContext/index.tsx @@ -1,10 +1,12 @@ import {createContext, useContext} from 'react'; // eslint-disable-next-line no-restricted-imports import type {GestureResponderEvent} from 'react-native'; +import type {OnyxEntry} from 'react-native-onyx'; import {canUseTouchScreen} from '@libs/DeviceCapabilities'; import {showContextMenu} from '@pages/inbox/report/ContextMenu/ReportActionContextMenu'; import type {ContextMenuAnchor} from '@pages/inbox/report/ContextMenu/ReportActionContextMenu'; import CONST from '@src/CONST'; +import type {ReportAction} from '@src/types/onyx'; import {defaultShowContextMenuActionsContextValue, defaultShowContextMenuStateContextValue} from './default'; import type {ShowContextMenuActionsContextType, ShowContextMenuStateContextType} from './types'; @@ -34,7 +36,7 @@ function showContextMenuForReport( event: GestureResponderEvent | MouseEvent, anchor: ContextMenuAnchor, reportID: string | undefined, - action: {reportActionID?: string} | null | undefined, + action: OnyxEntry, checkIfContextMenuActive: () => void, isArchivedRoom = false, originalReportID?: string, From b179be50c00b15fb043b216b703da305c979d94d Mon Sep 17 00:00:00 2001 From: Tim Golen Date: Tue, 17 Mar 2026 12:31:04 -0600 Subject: [PATCH 5/5] lint --- src/CONST/index.ts | 3 +++ src/components/ReportActionItem/TripRoomPreview.tsx | 1 + src/pages/inbox/report/PureReportActionItem.tsx | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/CONST/index.ts b/src/CONST/index.ts index c43cf197ecdc..e10783a8f9ca 100644 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -8748,6 +8748,9 @@ const CONST = { TRANSACTION_PREVIEW: { CARD: 'TransactionPreview-Card', }, + TRIP_ROOM_PREVIEW: { + CARD: 'TripRoomPreview-Card', + }, TRANSACTION_ITEM_ROW: { ARROW_RIGHT: 'TransactionItemRow-ArrowRight', }, diff --git a/src/components/ReportActionItem/TripRoomPreview.tsx b/src/components/ReportActionItem/TripRoomPreview.tsx index eab39451fac0..1f603e7ae93d 100644 --- a/src/components/ReportActionItem/TripRoomPreview.tsx +++ b/src/components/ReportActionItem/TripRoomPreview.tsx @@ -179,6 +179,7 @@ function TripRoomPreview({ showContextMenuForReport(event, contextMenuAnchor, chatReportID, action, checkIfContextMenuActive, false, originalReportID); }} shouldUseHapticsOnLongPress + sentryLabel={CONST.SENTRY_LABEL.TRIP_ROOM_PREVIEW.CARD} style={[styles.flexRow, styles.justifyContentBetween, styles.reportPreviewBox]} role={CONST.ROLE.BUTTON} accessibilityLabel={translate('iou.viewDetails')} diff --git a/src/pages/inbox/report/PureReportActionItem.tsx b/src/pages/inbox/report/PureReportActionItem.tsx index 2ab192ea3975..8ae1c80bd277 100644 --- a/src/pages/inbox/report/PureReportActionItem.tsx +++ b/src/pages/inbox/report/PureReportActionItem.tsx @@ -623,7 +623,7 @@ function PureReportActionItem({ clearError(transactionID); } clearAllRelatedReportActionErrors(reportID, action, originalReportID); - }, [action, isSendingMoney, reportID, clearAllRelatedReportActionErrors, report, chatReport, clearError]); + }, [action, isSendingMoney, reportID, clearAllRelatedReportActionErrors, report, chatReport, clearError, originalReportID]); const showDismissReceiptErrorModal = useCallback(async () => { const result = await showConfirmModal({