From 2cc87765ad9b1062143942495a2826c73c3f65be Mon Sep 17 00:00:00 2001 From: cretadn22 Date: Wed, 22 Jul 2026 00:48:53 +0700 Subject: [PATCH 1/4] getOriginalReportID --- .../Reactions/ReportActionItemEmojiReactions.tsx | 3 ++- src/libs/ReportUtils.ts | 3 +-- src/libs/actions/EmojiReactions.ts | 5 +++-- src/libs/actions/Report/index.ts | 7 ++++--- .../ContextMenu/BaseReportActionContextMenu.tsx | 1 + .../report/ContextMenu/ContextMenuActions.tsx | 9 +++++---- .../PopoverReportActionContextMenu.tsx | 1 + .../ComposerWithSuggestions.tsx | 10 +++++++--- tests/unit/ReportUtilsTest.ts | 15 +++++++++++++++ 9 files changed, 39 insertions(+), 15 deletions(-) diff --git a/src/components/Reactions/ReportActionItemEmojiReactions.tsx b/src/components/Reactions/ReportActionItemEmojiReactions.tsx index 72faecd0838f..bc5a53e50bef 100644 --- a/src/components/Reactions/ReportActionItemEmojiReactions.tsx +++ b/src/components/Reactions/ReportActionItemEmojiReactions.tsx @@ -79,6 +79,7 @@ function ReportActionItemEmojiReactions({reportAction, reportID, isEditingInline const reportActionID = reportAction.reportActionID; const [emojiReactions = getEmptyObject()] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${reportActionID}`); + const [reportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, {canBeMissing: true}); // Prime the locale emoji table when this action has reactions. // Skip the default locale since getLocalizedEmojiName never reads localeEmojis for it. @@ -97,7 +98,7 @@ function ReportActionItemEmojiReactions({reportAction, reportID, isEditingInline }); return; } - toggleEmojiReaction(reportID, reportAction, emoji, emojiReactions, skinTone, currentUserAccountID, ignoreSkinToneOnCompare); + toggleEmojiReaction(reportID, reportAction, emoji, emojiReactions, skinTone, currentUserAccountID, reportActions, ignoreSkinToneOnCompare); }; // Each emoji is sorted by the oldest timestamp of user reactions so that they will always appear in the same order for everyone diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index adc6b0755786..991c4d1c38c2 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -10624,11 +10624,10 @@ function canUserPerformWriteAction(report: OnyxEntry, isReportArchived: /** * Returns ID of the original report from which the given reportAction is first created. */ -function getOriginalReportID(reportID: string | undefined, reportAction: OnyxInputOrEntry, reportActionsParam: OnyxEntry | undefined): string | undefined { +function getOriginalReportID(reportID: string | undefined, reportAction: OnyxInputOrEntry, reportActions: OnyxEntry | undefined): string | undefined { if (!reportID) { return undefined; } - const reportActions = reportActionsParam ?? allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]; const currentReportAction = reportAction?.reportActionID ? reportActions?.[reportAction.reportActionID] : undefined; const report = deprecatedAllReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]; const chatReport = deprecatedAllReports?.[`${ONYXKEYS.COLLECTION.REPORT}${report?.chatReportID}`]; diff --git a/src/libs/actions/EmojiReactions.ts b/src/libs/actions/EmojiReactions.ts index 22db33a189fd..64530ca77bb4 100644 --- a/src/libs/actions/EmojiReactions.ts +++ b/src/libs/actions/EmojiReactions.ts @@ -9,7 +9,7 @@ import {getOriginalReportID} from '@libs/ReportUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {ReportAction, ReportActionReactions} from '@src/types/onyx'; +import type {ReportAction, ReportActionReactions, ReportActions} from '@src/types/onyx'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import type {OnyxEntry, OnyxUpdate} from 'react-native-onyx'; @@ -106,9 +106,10 @@ function toggleEmojiReaction( existingReactions: OnyxEntry, paramSkinTone: number, currentUserAccountID: number, + reportActions?: OnyxEntry, ignoreSkinToneOnCompare = false, ) { - const originalReportID = getOriginalReportID(reportID, reportAction, undefined); + const originalReportID = getOriginalReportID(reportID, reportAction, reportActions); if (!originalReportID) { return; diff --git a/src/libs/actions/Report/index.ts b/src/libs/actions/Report/index.ts index 4542bc3593cd..8d0fcd2a64ae 100644 --- a/src/libs/actions/Report/index.ts +++ b/src/libs/actions/Report/index.ts @@ -3049,9 +3049,10 @@ function deleteReportComment( isOriginalReportArchived: boolean | undefined, currentEmail: string, visibleReportActionsDataParam?: VisibleReportActionsDerivedValue, + reportActions?: OnyxEntry, ) { const reportID = report?.reportID; - const originalReportID = getOriginalReportID(reportID, reportAction, undefined); + const originalReportID = getOriginalReportID(reportID, reportAction, reportActions); const reportActionID = reportAction.reportActionID; if (!reportActionID || !originalReportID || !reportID) { @@ -3461,12 +3462,12 @@ function clearAllReportActionDrafts() { } /** Saves the draft for a comment report action. This will put the comment into "edit mode" */ -function saveReportActionDraft(reportID: string | undefined, reportAction: ReportAction | null, draftMessage: string) { +function saveReportActionDraft(reportID: string | undefined, reportAction: ReportAction | null, reportActions: OnyxEntry, draftMessage: string) { if (!reportAction) { return; } - const originalReportID = getOriginalReportID(reportID, reportAction, undefined); + const originalReportID = getOriginalReportID(reportID, reportAction, reportActions); if (!originalReportID) { return; } diff --git a/src/pages/inbox/report/ContextMenu/BaseReportActionContextMenu.tsx b/src/pages/inbox/report/ContextMenu/BaseReportActionContextMenu.tsx index f02a125ee5bc..eb95722d69b1 100755 --- a/src/pages/inbox/report/ContextMenu/BaseReportActionContextMenu.tsx +++ b/src/pages/inbox/report/ContextMenu/BaseReportActionContextMenu.tsx @@ -404,6 +404,7 @@ function BaseReportActionContextMenu({ const payload: ContextMenuActionPayload = { reportActions, childReportActions, + originalReportActions, // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style reportAction: (reportAction ?? null) as ReportAction, reportID, diff --git a/src/pages/inbox/report/ContextMenu/ContextMenuActions.tsx b/src/pages/inbox/report/ContextMenu/ContextMenuActions.tsx index a78ff43799c1..5c8a9633de49 100644 --- a/src/pages/inbox/report/ContextMenu/ContextMenuActions.tsx +++ b/src/pages/inbox/report/ContextMenu/ContextMenuActions.tsx @@ -307,6 +307,7 @@ type ShouldShow = (args: { type ContextMenuActionPayload = { reportActions: OnyxEntry; childReportActions: OnyxEntry; + originalReportActions: OnyxEntry; reportAction: ReportAction; transaction?: OnyxEntry; reportID: string | undefined; @@ -430,7 +431,7 @@ const ContextMenuActions: ContextMenuAction[] = [ const isDynamicWorkflowRoutedAction = isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.DYNAMIC_EXTERNAL_WORKFLOW_ROUTED); return type === CONST.CONTEXT_MENU_TYPES.REPORT_ACTION && !!reportAction && 'message' in reportAction && !isMessageDeleted(reportAction) && !isDynamicWorkflowRoutedAction; }, - renderContent: (closePopover, {reportID, reportAction, currentUserAccountID, close: closeManually, openContextMenu, setIsEmojiPickerActive}) => { + renderContent: (closePopover, {reportID, reportActions, reportAction, currentUserAccountID, close: closeManually, openContextMenu, setIsEmojiPickerActive}) => { const isMini = !closePopover; const closeContextMenu = (onHideCallback?: () => void) => { @@ -445,7 +446,7 @@ const ContextMenuActions: ContextMenuAction[] = [ }; const toggleEmojiAndCloseMenu = (emoji: Emoji, existingReactions: OnyxEntry, preferredSkinTone: number) => { - toggleEmojiReaction(reportID, reportAction, emoji, existingReactions, preferredSkinTone, currentUserAccountID); + toggleEmojiReaction(reportID, reportAction, emoji, existingReactions, preferredSkinTone, currentUserAccountID, reportActions); closeContextMenu(); setIsEmojiPickerActive?.(false); }; @@ -607,7 +608,7 @@ const ContextMenuActions: ContextMenuAction[] = [ (canEditReportAction(reportAction, iouTransaction) || canEditReportAction(moneyRequestAction, iouTransaction)) && !isArchivedRoom && !isChronosReport, - onPress: (closePopover, {reportID, originalReportID, reportAction, moneyRequestAction, introSelected, betas, childReportActions}) => { + onPress: (closePopover, {reportID, reportActions, originalReportActions, originalReportID, reportAction, moneyRequestAction, introSelected, betas, childReportActions}) => { if (isMoneyRequestAction(reportAction) || isMoneyRequestAction(moneyRequestAction)) { const editExpense = () => { const childReportID = reportAction?.childReportID; @@ -622,7 +623,7 @@ const ContextMenuActions: ContextMenuAction[] = [ return; } const editAction = () => { - saveReportActionDraft(originalReportID ?? reportID, reportAction, Parser.htmlToMarkdown(getActionHtml(reportAction))); + saveReportActionDraft(originalReportID ?? reportID, reportAction, originalReportActions ?? reportActions, Parser.htmlToMarkdown(getActionHtml(reportAction))); }; if (closePopover) { diff --git a/src/pages/inbox/report/ContextMenu/PopoverReportActionContextMenu.tsx b/src/pages/inbox/report/ContextMenu/PopoverReportActionContextMenu.tsx index 10517d5b77c4..0b379abc6f20 100644 --- a/src/pages/inbox/report/ContextMenu/PopoverReportActionContextMenu.tsx +++ b/src/pages/inbox/report/ContextMenu/PopoverReportActionContextMenu.tsx @@ -427,6 +427,7 @@ function PopoverReportActionContextMenu({ref}: PopoverReportActionContextMenuPro isOriginalReportArchived, email ?? '', visibleReportActionsData ?? undefined, + reportActions, ); }; } diff --git a/src/pages/inbox/report/ReportActionCompose/ComposerWithSuggestions.tsx b/src/pages/inbox/report/ReportActionCompose/ComposerWithSuggestions.tsx index 1eff778dc45c..e11921c1e511 100644 --- a/src/pages/inbox/report/ReportActionCompose/ComposerWithSuggestions.tsx +++ b/src/pages/inbox/report/ReportActionCompose/ComposerWithSuggestions.tsx @@ -289,7 +289,8 @@ function ComposerWithSuggestions({ const {saveDraft: debouncedSaveReportActionDraft, isSavePending: isDraftSavePending} = useDebouncedSaveDraft( useCallback( (comment: string) => { - saveReportActionDraft(reportID, editingReportAction, comment); + // The edited action is always local to reportID, so a minimal actions map is enough for saveReportActionDraft's getOriginalReportID to resolve to reportID (avoids subscribing the composer to REPORT_ACTIONS). + saveReportActionDraft(reportID, editingReportAction, editingReportAction ? {[editingReportAction.reportActionID]: editingReportAction} : undefined, comment); }, [reportID, editingReportAction], ), @@ -562,7 +563,9 @@ function ComposerWithSuggestions({ return; } - saveReportActionDraft(reportID, {reportActionID: editingReportActionID} as OnyxTypes.ReportAction, newCommentConverted); + // The edited action is always local to reportID, so a minimal actions map is enough for saveReportActionDraft's getOriginalReportID to resolve to reportID (avoids subscribing the composer to REPORT_ACTIONS). + const editingReportActionForDraft = {reportActionID: editingReportActionID} as OnyxTypes.ReportAction; + saveReportActionDraft(reportID, editingReportActionForDraft, editingReportActionID ? {[editingReportActionID]: editingReportActionForDraft} : undefined, newCommentConverted); return; } @@ -633,7 +636,8 @@ function ComposerWithSuggestions({ webEvent.preventDefault(); if (lastReportAction) { const message = Array.isArray(lastReportAction?.message) ? (lastReportAction?.message?.at(-1) ?? null) : (lastReportAction?.message ?? null); - saveReportActionDraft(reportID, lastReportAction, Parser.htmlToMarkdown(message?.html ?? '')); + // The edited action is always local to reportID, so a minimal actions map is enough for saveReportActionDraft's getOriginalReportID to resolve to reportID (avoids subscribing the composer to REPORT_ACTIONS). + saveReportActionDraft(reportID, lastReportAction, {[lastReportAction.reportActionID]: lastReportAction}, Parser.htmlToMarkdown(message?.html ?? '')); } } // Flag emojis like "Wales" have several code points. Default backspace key action does not remove such flag emojis completely. diff --git a/tests/unit/ReportUtilsTest.ts b/tests/unit/ReportUtilsTest.ts index f0f1f6234e1b..7ae88454f97d 100644 --- a/tests/unit/ReportUtilsTest.ts +++ b/tests/unit/ReportUtilsTest.ts @@ -18626,6 +18626,21 @@ describe('ReportUtils', () => { const result = getOriginalReportID(reportID, reportAction, {}); expect(result).toBe(reportID); }); + + it('should return the parent report ID for a thread parent action that is missing from the passed reportActions', async () => { + const reportID = 'getOriginalReportID-thread'; + const parentReportID = 'getOriginalReportID-parent'; + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, {reportID, parentReportID}); + await waitForBatchedUpdates(); + + // The action is the thread's parent message: its childReportID points back to the thread and it is not present in the thread's own actions, + // so getOriginalReportID must resolve to the parent report using only the passed reportActions (no module-level Onyx.connect fallback). + const reportAction = {...createRandomReportAction(1), childReportID: reportID}; + expect(getOriginalReportID(reportID, reportAction, {})).toBe(parentReportID); + + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, null); + await waitForBatchedUpdates(); + }); }); describe('hasVisibleReportFieldViolations', () => { From 45b2b9f3f2cf8c2fe1eb0e2479bc10fa8c5221d5 Mon Sep 17 00:00:00 2001 From: cretadn22 Date: Tue, 28 Jul 2026 00:25:11 +0700 Subject: [PATCH 2/4] Updated function signatures to require instead of making it optional, ensuring consistency across the codebase --- .../ReportActionItemEmojiReactions.tsx | 2 +- src/libs/actions/EmojiReactions.ts | 2 +- src/libs/actions/Report/index.ts | 4 +- .../PopoverReportActionContextMenu.tsx | 2 +- .../report/ReportActionItemMessageEdit.tsx | 5 +- tests/actions/AttachmentTest.ts | 4 +- tests/actions/ReportTest.ts | 54 ++++++++++--------- tests/ui/UnreadIndicatorsTest.tsx | 4 +- tests/unit/EmojiReactionsTest.ts | 8 +-- 9 files changed, 45 insertions(+), 40 deletions(-) diff --git a/src/components/Reactions/ReportActionItemEmojiReactions.tsx b/src/components/Reactions/ReportActionItemEmojiReactions.tsx index bc5a53e50bef..591ab4098b03 100644 --- a/src/components/Reactions/ReportActionItemEmojiReactions.tsx +++ b/src/components/Reactions/ReportActionItemEmojiReactions.tsx @@ -79,7 +79,7 @@ function ReportActionItemEmojiReactions({reportAction, reportID, isEditingInline const reportActionID = reportAction.reportActionID; const [emojiReactions = getEmptyObject()] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${reportActionID}`); - const [reportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, {canBeMissing: true}); + const [reportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`); // Prime the locale emoji table when this action has reactions. // Skip the default locale since getLocalizedEmojiName never reads localeEmojis for it. diff --git a/src/libs/actions/EmojiReactions.ts b/src/libs/actions/EmojiReactions.ts index 64530ca77bb4..e5635975e3de 100644 --- a/src/libs/actions/EmojiReactions.ts +++ b/src/libs/actions/EmojiReactions.ts @@ -106,7 +106,7 @@ function toggleEmojiReaction( existingReactions: OnyxEntry, paramSkinTone: number, currentUserAccountID: number, - reportActions?: OnyxEntry, + reportActions: OnyxEntry, ignoreSkinToneOnCompare = false, ) { const originalReportID = getOriginalReportID(reportID, reportAction, reportActions); diff --git a/src/libs/actions/Report/index.ts b/src/libs/actions/Report/index.ts index e9ae3209515a..9e4b02c56a41 100644 --- a/src/libs/actions/Report/index.ts +++ b/src/libs/actions/Report/index.ts @@ -3056,13 +3056,15 @@ function broadcastUserIsLeavingRoom(reportID: string, currentUserAccountID: numb function deleteReportComment( report: OnyxEntry, reportAction: ReportAction, + // Report actions of the original report, used to locate actionable mention whispers to hide. originalReportActions: OnyxEntry, + // Report actions of `report`, used to resolve the original report ID. + reportActions: OnyxEntry, ancestors: Ancestor[], isReportArchived: boolean | undefined, isOriginalReportArchived: boolean | undefined, currentEmail: string, visibleReportActionsDataParam?: VisibleReportActionsDerivedValue, - reportActions?: OnyxEntry, ) { const reportID = report?.reportID; const originalReportID = getOriginalReportID(reportID, reportAction, reportActions); diff --git a/src/pages/inbox/report/ContextMenu/PopoverReportActionContextMenu.tsx b/src/pages/inbox/report/ContextMenu/PopoverReportActionContextMenu.tsx index 0b379abc6f20..859cb66d2361 100644 --- a/src/pages/inbox/report/ContextMenu/PopoverReportActionContextMenu.tsx +++ b/src/pages/inbox/report/ContextMenu/PopoverReportActionContextMenu.tsx @@ -422,12 +422,12 @@ function PopoverReportActionContextMenu({ref}: PopoverReportActionContextMenuPro report, reportAction, originalReportActions, + reportActions, ancestorsRef.current, isReportArchived, isOriginalReportArchived, email ?? '', visibleReportActionsData ?? undefined, - reportActions, ); }; } diff --git a/src/pages/inbox/report/ReportActionItemMessageEdit.tsx b/src/pages/inbox/report/ReportActionItemMessageEdit.tsx index d1cc20fbdd9d..8d97141d9f65 100644 --- a/src/pages/inbox/report/ReportActionItemMessageEdit.tsx +++ b/src/pages/inbox/report/ReportActionItemMessageEdit.tsx @@ -91,6 +91,7 @@ function ReportActionItemMessageEdit({action, reportID, originalReportID, policy const index = useContext(ReportActionIndexContext); const [preferredSkinTone = CONST.EMOJI_DEFAULT_SKIN_TONE] = useOnyx(ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE); const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(reportID)}`); + const [reportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${getNonEmptyStringOnyxID(reportID)}`); const isOriginalReportArchived = useReportIsArchived(originalReportID); const [originalReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(originalReportID)}`); const blockedFromConcierge = useBlockedFromConcierge(); @@ -245,9 +246,9 @@ function ReportActionItemMessageEdit({action, reportID, originalReportID, policy setEditingMessage(newDraft); // We want to escape the draft message to differentiate the HTML from the report action and the HTML the user drafted. - saveDraft(reportID, action, newDraft); + saveDraft(reportID, action, reportActions, newDraft); }, - [action, preferredLocale, preferredSkinTone, raiseIsScrollLayoutTriggered, reportID, selection.end, setEditingMessage, setSelection, saveDraft], + [action, preferredLocale, preferredSkinTone, raiseIsScrollLayoutTriggered, reportID, reportActions, selection.end, setEditingMessage, setSelection, saveDraft], ); useEffect(() => { diff --git a/tests/actions/AttachmentTest.ts b/tests/actions/AttachmentTest.ts index 39445c7c51f3..2a7074499c99 100644 --- a/tests/actions/AttachmentTest.ts +++ b/tests/actions/AttachmentTest.ts @@ -269,7 +269,7 @@ describe('AttachmentStorage', () => { } // Delete attachment - deleteReportComment({reportID}, attachmentAction, undefined, [], false, false, 'test@user.com'); + deleteReportComment({reportID}, attachmentAction, undefined, undefined, [], false, false, 'test@user.com'); await waitForBatchedUpdates(); // Then the attachment should be removed @@ -332,7 +332,7 @@ describe('AttachmentStorage', () => { } // Delete attachment - deleteReportComment({reportID}, attachmentAction, undefined, [], false, false, 'test@user.com'); + deleteReportComment({reportID}, attachmentAction, undefined, undefined, [], false, false, 'test@user.com'); await waitForBatchedUpdates(); const removedAttachment = attachments?.[`${ONYXKEYS.COLLECTION.ATTACHMENT}${attachmentID}`]; diff --git a/tests/actions/ReportTest.ts b/tests/actions/ReportTest.ts index 85d69195385f..4d724cd65a2c 100644 --- a/tests/actions/ReportTest.ts +++ b/tests/actions/ReportTest.ts @@ -806,7 +806,7 @@ describe('actions/Report', () => { .then(() => { rerender(report); // If the user deletes a comment that is before the last read - Report.deleteReportComment(report, {...reportActions[200]}, undefined, ancestors.current, undefined, undefined, USER_1_LOGIN); + Report.deleteReportComment(report, {...reportActions[200]}, undefined, undefined, ancestors.current, undefined, undefined, USER_1_LOGIN); return waitForBatchedUpdates(); }) .then(() => { @@ -825,7 +825,7 @@ describe('actions/Report', () => { rerender(report); // If the user deletes the last comment after the lastReadTime the lastMessageText will reflect the new last comment - Report.deleteReportComment(report, {...reportActions[400]}, undefined, ancestors.current, undefined, undefined, USER_1_LOGIN); + Report.deleteReportComment(report, {...reportActions[400]}, undefined, undefined, ancestors.current, undefined, undefined, USER_1_LOGIN); return waitForBatchedUpdates(); }) .then(() => getOnyxValue(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}` as const)) @@ -1035,7 +1035,7 @@ describe('actions/Report', () => { if (reportAction) { // Add a reaction to the comment - toggleEmojiReaction(REPORT_ID, reportAction, EMOJI, reportActionsReactions[0], CONST.EMOJI_DEFAULT_SKIN_TONE, TEST_USER_ACCOUNT_ID); + toggleEmojiReaction(REPORT_ID, reportAction, EMOJI, reportActionsReactions[0], CONST.EMOJI_DEFAULT_SKIN_TONE, TEST_USER_ACCOUNT_ID, undefined); } return waitForBatchedUpdates(); }) @@ -1055,7 +1055,7 @@ describe('actions/Report', () => { if (reportAction) { // Now we remove the reaction - toggleEmojiReaction(REPORT_ID, reportAction, EMOJI, reportActionReaction, CONST.EMOJI_DEFAULT_SKIN_TONE, TEST_USER_ACCOUNT_ID); + toggleEmojiReaction(REPORT_ID, reportAction, EMOJI, reportActionReaction, CONST.EMOJI_DEFAULT_SKIN_TONE, TEST_USER_ACCOUNT_ID, undefined); } return waitForBatchedUpdates(); }) @@ -1070,7 +1070,7 @@ describe('actions/Report', () => { if (reportAction) { // Add the same reaction to the same report action with a different skin tone - toggleEmojiReaction(REPORT_ID, reportAction, EMOJI, reportActionsReactions[0], CONST.EMOJI_DEFAULT_SKIN_TONE, TEST_USER_ACCOUNT_ID); + toggleEmojiReaction(REPORT_ID, reportAction, EMOJI, reportActionsReactions[0], CONST.EMOJI_DEFAULT_SKIN_TONE, TEST_USER_ACCOUNT_ID, undefined); } return waitForBatchedUpdates() .then(() => { @@ -1078,7 +1078,7 @@ describe('actions/Report', () => { const reportActionReaction = reportActionsReactions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${reportActionID}`]; if (reportAction) { - toggleEmojiReaction(REPORT_ID, reportAction, EMOJI, reportActionReaction, EMOJI_SKIN_TONE, TEST_USER_ACCOUNT_ID); + toggleEmojiReaction(REPORT_ID, reportAction, EMOJI, reportActionReaction, EMOJI_SKIN_TONE, TEST_USER_ACCOUNT_ID, undefined); } return waitForBatchedUpdates(); }) @@ -1103,7 +1103,7 @@ describe('actions/Report', () => { if (reportAction) { // Now we remove the reaction, and expect that both variations are removed - toggleEmojiReaction(REPORT_ID, reportAction, EMOJI, reportActionReaction, CONST.EMOJI_DEFAULT_SKIN_TONE, TEST_USER_ACCOUNT_ID); + toggleEmojiReaction(REPORT_ID, reportAction, EMOJI, reportActionReaction, CONST.EMOJI_DEFAULT_SKIN_TONE, TEST_USER_ACCOUNT_ID, undefined); } return waitForBatchedUpdates(); }) @@ -1173,7 +1173,7 @@ describe('actions/Report', () => { if (resultAction) { // Add a reaction to the comment - toggleEmojiReaction(REPORT_ID, resultAction, EMOJI, {}, CONST.EMOJI_DEFAULT_SKIN_TONE, TEST_USER_ACCOUNT_ID); + toggleEmojiReaction(REPORT_ID, resultAction, EMOJI, {}, CONST.EMOJI_DEFAULT_SKIN_TONE, TEST_USER_ACCOUNT_ID, undefined); } return waitForBatchedUpdates(); }) @@ -1185,7 +1185,7 @@ describe('actions/Report', () => { // should get removed instead of added again. const reportActionReaction = reportActionsReactions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${resultAction?.reportActionID}`]; if (resultAction) { - toggleEmojiReaction(REPORT_ID, resultAction, EMOJI, reportActionReaction, 2, TEST_USER_ACCOUNT_ID); + toggleEmojiReaction(REPORT_ID, resultAction, EMOJI, reportActionReaction, 2, TEST_USER_ACCOUNT_ID, undefined); } return waitForBatchedUpdates(); }) @@ -1465,7 +1465,7 @@ describe('actions/Report', () => { }); rerender(originalReport); - Report.deleteReportComment(originalReport, newReportAction, undefined, ancestors.current, undefined, undefined, ''); + Report.deleteReportComment(originalReport, newReportAction, undefined, undefined, ancestors.current, undefined, undefined, ''); await waitForBatchedUpdates(); expect(PersistedRequests.getAll().length).toBe(0); @@ -1522,7 +1522,7 @@ describe('actions/Report', () => { expect(persistedRequests?.at(0)?.command).toBe(WRITE_COMMANDS.ADD_COMMENT); rerender(originalReport); - Report.deleteReportComment(originalReport, newReportAction, undefined, ancestors.current, undefined, undefined, currentUserEmail); + Report.deleteReportComment(originalReport, newReportAction, undefined, undefined, ancestors.current, undefined, undefined, currentUserEmail); await waitForBatchedUpdates(); expect(PersistedRequests.getAll().length).toBe(0); @@ -1592,7 +1592,7 @@ describe('actions/Report', () => { }); rerender(originalReport); - Report.deleteReportComment(originalReport, reportAction, undefined, ancestors.current, undefined, undefined, ''); + Report.deleteReportComment(originalReport, reportAction, undefined, undefined, ancestors.current, undefined, undefined, ''); await waitForBatchedUpdates(); expect(PersistedRequests.getAll().length).toBe(1); @@ -1651,7 +1651,7 @@ describe('actions/Report', () => { }), ); - Report.deleteReportComment(REPORT, reportAction, undefined, [], undefined, undefined, ''); + Report.deleteReportComment(REPORT, reportAction, undefined, undefined, [], undefined, undefined, ''); jest.runOnlyPendingTimers(); await waitForBatchedUpdates(); @@ -1727,7 +1727,7 @@ describe('actions/Report', () => { }); }); - Report.deleteReportComment(REPORT, newReportAction, undefined, [], undefined, undefined, ''); + Report.deleteReportComment(REPORT, newReportAction, undefined, undefined, [], undefined, undefined, ''); await waitForBatchedUpdates(); expect(PersistedRequests.getAll().length).toBe(0); @@ -1811,7 +1811,7 @@ describe('actions/Report', () => { }); }); - Report.deleteReportComment(REPORT, newReportAction, undefined, [], undefined, undefined, ''); + Report.deleteReportComment(REPORT, newReportAction, undefined, undefined, [], undefined, undefined, ''); await waitForBatchedUpdates(); expect(PersistedRequests.getAll().length).toBe(0); @@ -2028,7 +2028,7 @@ describe('actions/Report', () => { }), }; - Report.deleteReportComment(REPORT, commentAction, reportActionsForReport, [], undefined, undefined, '', undefined); + Report.deleteReportComment(REPORT, commentAction, reportActionsForReport, undefined, [], undefined, undefined, '', undefined); await waitForBatchedUpdates(); const reportActions = await getOnyxValue(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${REPORT_ID}` as const); @@ -2114,7 +2114,7 @@ describe('actions/Report', () => { }; // Delete comment B — only whisper B (at commentB + 1) should be marked deleted - Report.deleteReportComment(REPORT, commentBAction, reportActionsForReport, [], undefined, undefined, '', undefined); + Report.deleteReportComment(REPORT, commentBAction, reportActionsForReport, undefined, [], undefined, undefined, '', undefined); await waitForBatchedUpdates(); const reportActions = await getOnyxValue(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${REPORT_ID}` as const); @@ -2158,7 +2158,7 @@ describe('actions/Report', () => { await waitForBatchedUpdates(); - toggleEmojiReaction(REPORT_ID, newReportAction, {name: 'smile', code: '😄', hexcode: '1F604'}, {}, CONST.EMOJI_DEFAULT_SKIN_TONE, TEST_USER_ACCOUNT_ID); + toggleEmojiReaction(REPORT_ID, newReportAction, {name: 'smile', code: '😄', hexcode: '1F604'}, {}, CONST.EMOJI_DEFAULT_SKIN_TONE, TEST_USER_ACCOUNT_ID, undefined); toggleEmojiReaction( REPORT_ID, newReportAction, @@ -2180,6 +2180,7 @@ describe('actions/Report', () => { }, CONST.EMOJI_DEFAULT_SKIN_TONE, TEST_USER_ACCOUNT_ID, + undefined, ); await waitForBatchedUpdates(); @@ -2211,7 +2212,7 @@ describe('actions/Report', () => { }); }); - Report.deleteReportComment(REPORT, newReportAction, undefined, [], undefined, undefined, ''); + Report.deleteReportComment(REPORT, newReportAction, undefined, undefined, [], undefined, undefined, ''); await waitForBatchedUpdates(); expect(PersistedRequests.getAll().length).toBe(0); @@ -2274,7 +2275,7 @@ describe('actions/Report', () => { // wait for Onyx.connect execute the callback and start processing the queue await Promise.resolve(); - toggleEmojiReaction(REPORT_ID, reportAction, {name: 'smile', code: '😄', hexcode: '1F604'}, {}, CONST.EMOJI_DEFAULT_SKIN_TONE, TEST_USER_ACCOUNT_ID); + toggleEmojiReaction(REPORT_ID, reportAction, {name: 'smile', code: '😄', hexcode: '1F604'}, {}, CONST.EMOJI_DEFAULT_SKIN_TONE, TEST_USER_ACCOUNT_ID, undefined); toggleEmojiReaction( REPORT_ID, reportAction, @@ -2296,6 +2297,7 @@ describe('actions/Report', () => { }, CONST.EMOJI_DEFAULT_SKIN_TONE, TEST_USER_ACCOUNT_ID, + undefined, ); await waitForBatchedUpdates(); @@ -2311,7 +2313,7 @@ describe('actions/Report', () => { }); }); - Report.deleteReportComment(REPORT, reportAction, undefined, [], undefined, undefined, ''); + Report.deleteReportComment(REPORT, reportAction, undefined, undefined, [], undefined, undefined, ''); await waitForBatchedUpdates(); expect(PersistedRequests.getAll().length).toBe(1); @@ -2372,7 +2374,7 @@ describe('actions/Report', () => { const {result: ancestors} = renderHook(() => useAncestors({reportID: REPORT_ID})); - Report.deleteReportComment(REPORT, reportAction, undefined, ancestors.current, undefined, undefined, ''); + Report.deleteReportComment(REPORT, reportAction, undefined, undefined, ancestors.current, undefined, undefined, ''); expect(PersistedRequests.getAll().length).toBe(3); @@ -2619,8 +2621,8 @@ describe('actions/Report', () => { const {result: ancestors} = renderHook(() => useAncestors(report)); - Report.deleteReportComment(report, mentionAction, undefined, ancestors.current, undefined, undefined, ''); - Report.deleteReportComment(report, mentionAction2, undefined, ancestors.current, undefined, undefined, ''); + Report.deleteReportComment(report, mentionAction, undefined, undefined, ancestors.current, undefined, undefined, ''); + Report.deleteReportComment(report, mentionAction2, undefined, undefined, ancestors.current, undefined, undefined, ''); await waitForBatchedUpdates(); @@ -2657,7 +2659,7 @@ describe('actions/Report', () => { mockNavigation.getTopmostSearchReportRouteParams.mockReturnValue({reportID: CHILD_REPORT_ID}); const {result: ancestors} = renderHook(() => useAncestors(parentReport)); - Report.deleteReportComment(parentReport, reportAction, undefined, ancestors.current, undefined, undefined, ''); + Report.deleteReportComment(parentReport, reportAction, undefined, undefined, ancestors.current, undefined, undefined, ''); await waitForBatchedUpdates(); expect(mockNavigation.goBack).toHaveBeenCalled(); @@ -2675,7 +2677,7 @@ describe('actions/Report', () => { mockNavigation.getTopmostSearchReportRouteParams.mockReturnValue({reportID: CHILD_REPORT_ID, reportActionID: 'action-999'}); const {result: ancestors} = renderHook(() => useAncestors(parentReport)); - Report.deleteReportComment(parentReport, reportAction, undefined, ancestors.current, undefined, undefined, ''); + Report.deleteReportComment(parentReport, reportAction, undefined, undefined, ancestors.current, undefined, undefined, ''); await waitForBatchedUpdates(); expect(mockNavigation.goBack).toHaveBeenCalled(); diff --git a/tests/ui/UnreadIndicatorsTest.tsx b/tests/ui/UnreadIndicatorsTest.tsx index e2888b3db697..ed643e2237fd 100644 --- a/tests/ui/UnreadIndicatorsTest.tsx +++ b/tests/ui/UnreadIndicatorsTest.tsx @@ -643,7 +643,7 @@ describe('Unread Indicators', () => { const report = await OnyxUtils.get(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`); if (lastReportAction) { - deleteReportComment(report, lastReportAction, undefined, [], undefined, undefined, ''); + deleteReportComment(report, lastReportAction, undefined, undefined, [], undefined, undefined, ''); } return waitForBatchedUpdates(); }) @@ -699,7 +699,7 @@ describe('Unread Indicators', () => { await waitForBatchedUpdates(); - deleteReportComment(report, firstNewReportAction, undefined, [], undefined, undefined, ''); + deleteReportComment(report, firstNewReportAction, undefined, undefined, [], undefined, undefined, ''); await waitForBatchedUpdates(); } diff --git a/tests/unit/EmojiReactionsTest.ts b/tests/unit/EmojiReactionsTest.ts index 02c80d70014c..93ebfe876208 100644 --- a/tests/unit/EmojiReactionsTest.ts +++ b/tests/unit/EmojiReactionsTest.ts @@ -59,7 +59,7 @@ describe('toggleEmojiReaction — mixed-format Onyx state', () => { }, }; - toggleEmojiReaction(REPORT_ID, ACTION, THUMBSUP, existingReactions, SKIN_TONE, USER_A); + toggleEmojiReaction(REPORT_ID, ACTION, THUMBSUP, existingReactions, SKIN_TONE, USER_A, undefined); expect(writeMock).toHaveBeenCalledTimes(1); const [command, params] = writeMock.mock.calls.at(0) as [string, {emojiCode: string}]; @@ -77,7 +77,7 @@ describe('toggleEmojiReaction — mixed-format Onyx state', () => { }, }; - toggleEmojiReaction(REPORT_ID, ACTION, THUMBSUP, existingReactions, SKIN_TONE, USER_A); + toggleEmojiReaction(REPORT_ID, ACTION, THUMBSUP, existingReactions, SKIN_TONE, USER_A, undefined); expect(writeMock).toHaveBeenCalledTimes(1); const [command, params] = writeMock.mock.calls.at(0) as [string, {emojiCode: string}]; @@ -95,7 +95,7 @@ describe('toggleEmojiReaction — mixed-format Onyx state', () => { }, }; - toggleEmojiReaction(REPORT_ID, ACTION, THUMBSUP, existingReactions, SKIN_TONE, USER_A); + toggleEmojiReaction(REPORT_ID, ACTION, THUMBSUP, existingReactions, SKIN_TONE, USER_A, undefined); expect(writeMock).toHaveBeenCalledTimes(1); const [command] = writeMock.mock.calls.at(0) as [string]; @@ -118,7 +118,7 @@ describe('toggleEmojiReaction — mixed-format Onyx state', () => { }, }; - toggleEmojiReaction(REPORT_ID, ACTION, THUMBSUP, existingReactions, SKIN_TONE, USER_A); + toggleEmojiReaction(REPORT_ID, ACTION, THUMBSUP, existingReactions, SKIN_TONE, USER_A, undefined); expect(writeMock).toHaveBeenCalledTimes(1); const [command, params] = writeMock.mock.calls.at(0) as [string, {emojiCode: string}]; From ebc9743265854871171ecb9630eedb89f404a58d Mon Sep 17 00:00:00 2001 From: cretadn22 Date: Tue, 28 Jul 2026 00:41:28 +0700 Subject: [PATCH 3/4] utilize reportActions directly, improving draft saving logic in ComposerWithSuggestions --- .../ComposerWithSuggestions.tsx | 15 ++-- tests/actions/ReportTest.ts | 68 +++++++++++++++++++ tests/unit/ReportUtilsTest.ts | 28 ++++++++ 3 files changed, 103 insertions(+), 8 deletions(-) diff --git a/src/pages/inbox/report/ReportActionCompose/ComposerWithSuggestions.tsx b/src/pages/inbox/report/ReportActionCompose/ComposerWithSuggestions.tsx index 04fe413a17a4..4c8d5ed875df 100644 --- a/src/pages/inbox/report/ReportActionCompose/ComposerWithSuggestions.tsx +++ b/src/pages/inbox/report/ReportActionCompose/ComposerWithSuggestions.tsx @@ -271,6 +271,7 @@ function ComposerWithSuggestions({ const {editingState, editingReportActionID, editingReportAction, effectiveDraft, currentEditMessageSelection} = useComposerEditState(); const {setEditingMessage, setCurrentEditMessageSelection} = useReportActionActiveEditActions(); + const [reportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`); const isEditing = editingState !== CONST.REPORT_ACTION_EDIT_MESSAGE_STATE.OFF; const text = useComposerText(); @@ -289,10 +290,9 @@ function ComposerWithSuggestions({ const {saveDraft: debouncedSaveReportActionDraft, isSavePending: isDraftSavePending} = useDebouncedSaveDraft( useCallback( (comment: string) => { - // The edited action is always local to reportID, so a minimal actions map is enough for saveReportActionDraft's getOriginalReportID to resolve to reportID (avoids subscribing the composer to REPORT_ACTIONS). - saveReportActionDraft(reportID, editingReportAction, editingReportAction ? {[editingReportAction.reportActionID]: editingReportAction} : undefined, comment); + saveReportActionDraft(reportID, editingReportAction, reportActions, comment); }, - [reportID, editingReportAction], + [reportID, editingReportAction, reportActions], ), ); @@ -565,9 +565,7 @@ function ComposerWithSuggestions({ return; } - // The edited action is always local to reportID, so a minimal actions map is enough for saveReportActionDraft's getOriginalReportID to resolve to reportID (avoids subscribing the composer to REPORT_ACTIONS). - const editingReportActionForDraft = {reportActionID: editingReportActionID} as OnyxTypes.ReportAction; - saveReportActionDraft(reportID, editingReportActionForDraft, editingReportActionID ? {[editingReportActionID]: editingReportActionForDraft} : undefined, newCommentConverted); + saveReportActionDraft(reportID, {reportActionID: editingReportActionID} as OnyxTypes.ReportAction, reportActions, newCommentConverted); return; } @@ -597,6 +595,7 @@ function ComposerWithSuggestions({ setEditingMessage, reportID, editingReportActionID, + reportActions, debouncedSaveReportActionDraft, debouncedSaveComment, currentUserAccountID, @@ -638,8 +637,7 @@ function ComposerWithSuggestions({ webEvent.preventDefault(); if (lastReportAction) { const message = Array.isArray(lastReportAction?.message) ? (lastReportAction?.message?.at(-1) ?? null) : (lastReportAction?.message ?? null); - // The edited action is always local to reportID, so a minimal actions map is enough for saveReportActionDraft's getOriginalReportID to resolve to reportID (avoids subscribing the composer to REPORT_ACTIONS). - saveReportActionDraft(reportID, lastReportAction, {[lastReportAction.reportActionID]: lastReportAction}, Parser.htmlToMarkdown(message?.html ?? '')); + saveReportActionDraft(reportID, lastReportAction, reportActions, Parser.htmlToMarkdown(message?.html ?? '')); } } // Flag emojis like "Wales" have several code points. Default backspace key action does not remove such flag emojis completely. @@ -688,6 +686,7 @@ function ComposerWithSuggestions({ onEnterKeyPress, lastReportAction, reportID, + reportActions, updateComment, setCurrentEditMessageSelection, ], diff --git a/tests/actions/ReportTest.ts b/tests/actions/ReportTest.ts index 4d724cd65a2c..2ce98cfe0e2f 100644 --- a/tests/actions/ReportTest.ts +++ b/tests/actions/ReportTest.ts @@ -2684,6 +2684,74 @@ describe('actions/Report', () => { }); }); + describe('saveReportActionDraft', () => { + const TEST_USER_ACCOUNT_ID = 1; + + it('saves the draft under the given report when the edited action belongs to it', async () => { + const reportID = '9101'; + const created = format(addSeconds(subMinutes(new Date(), 10), 10), CONST.DATE.FNS_DB_FORMAT_STRING); + const reportAction = TestHelper.buildTestReportComment(created, TEST_USER_ACCOUNT_ID, 'draft-local-action'); + + Report.saveReportActionDraft(reportID, reportAction, {[reportAction.reportActionID]: reportAction}, 'edited message'); + await waitForBatchedUpdates(); + + const drafts = await OnyxUtils.get(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}${reportID}`); + expect(drafts?.['draft-local-action']?.message).toBe('edited message'); + }); + + it('saves the draft under the parent report when editing a thread parent message from the child thread', async () => { + const parentReportID = '9102'; + const threadReportID = '9103'; + const created = format(addSeconds(subMinutes(new Date(), 10), 10), CONST.DATE.FNS_DB_FORMAT_STRING); + const parentReportAction = {...TestHelper.buildTestReportComment(created, TEST_USER_ACCOUNT_ID, 'draft-thread-parent-action'), childReportID: threadReportID}; + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${threadReportID}`, {reportID: threadReportID, parentReportID, parentReportActionID: parentReportAction.reportActionID}); + await waitForBatchedUpdates(); + + // The parent message is not part of the child thread's own actions, so the draft must be stored under the parent report + Report.saveReportActionDraft(threadReportID, parentReportAction, {}, 'edited thread parent'); + await waitForBatchedUpdates(); + + const parentDrafts = await OnyxUtils.get(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}${parentReportID}`); + expect(parentDrafts?.['draft-thread-parent-action']?.message).toBe('edited thread parent'); + const threadDrafts = await OnyxUtils.get(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}${threadReportID}`); + expect(threadDrafts?.['draft-thread-parent-action']).toBeUndefined(); + }); + + it('does nothing when reportAction is null', async () => { + const reportID = '9104'; + + Report.saveReportActionDraft(reportID, null, undefined, 'should not be saved'); + await waitForBatchedUpdates(); + + const drafts = await OnyxUtils.get(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}${reportID}`); + expect(drafts).toBeFalsy(); + }); + }); + + describe('toggleEmojiReaction', () => { + const TEST_USER_ACCOUNT_ID = 1; + + it('adds the reaction to a thread parent message using the parent report actions', async () => { + global.fetch = TestHelper.createGlobalFetchMock(); + const parentReportID = '9105'; + const threadReportID = '9106'; + const created = format(addSeconds(subMinutes(new Date(), 10), 10), CONST.DATE.FNS_DB_FORMAT_STRING); + const parentReportAction = {...TestHelper.buildTestReportComment(created, TEST_USER_ACCOUNT_ID, 'react-thread-parent-action'), childReportID: threadReportID}; + + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${threadReportID}`, {reportID: threadReportID, parentReportID, parentReportActionID: parentReportAction.reportActionID}); + // Seed the parent report's actions so the reaction can resolve the action under its owning report + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`, {[parentReportAction.reportActionID]: parentReportAction}); + await waitForBatchedUpdates(); + + // The child thread's own actions do not contain the parent message, so the original report must be resolved from the thread's parent + toggleEmojiReaction(threadReportID, parentReportAction, {name: 'smile', code: '😄', hexcode: '1F604'}, {}, CONST.EMOJI_DEFAULT_SKIN_TONE, TEST_USER_ACCOUNT_ID, {}); + await waitForBatchedUpdates(); + + const reactions = await OnyxUtils.get(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${parentReportAction.reportActionID}`); + expect(reactions?.smile?.users?.[TEST_USER_ACCOUNT_ID]).toBeDefined(); + }); + }); + it('should create new report and "create report" quick action, when createNewReport gets called', async () => { const accountID = 1234; const policyID = '5678'; diff --git a/tests/unit/ReportUtilsTest.ts b/tests/unit/ReportUtilsTest.ts index f8fcba3bffd0..8188b66607cd 100644 --- a/tests/unit/ReportUtilsTest.ts +++ b/tests/unit/ReportUtilsTest.ts @@ -18842,6 +18842,34 @@ describe('ReportUtils', () => { await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, null); await waitForBatchedUpdates(); }); + + it('should return the transaction thread report ID for a one-transaction report when the action is not in the passed reportActions', async () => { + const reportID = 'getOriginalReportID-one-transaction'; + const transactionThreadReportID = 'getOriginalReportID-transaction-thread'; + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, {reportID, type: CONST.REPORT.TYPE.EXPENSE}); + await waitForBatchedUpdates(); + + // The report's only money request action, whose childReportID is the single transaction thread + const iouAction: ReportAction = { + ...createRandomReportAction(21), + actionName: CONST.REPORT.ACTIONS.TYPE.IOU, + childReportID: transactionThreadReportID, + originalMessage: { + IOUReportID: reportID, + IOUTransactionID: 'txn-one-transaction', + type: CONST.IOU.REPORT_ACTION_TYPE.CREATE, + amount: 100, + currency: 'USD', + }, + }; + // The queried action is not part of the passed reportActions and is not a thread parent action + const reportAction = {...createRandomReportAction(22), actionName: CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT, childReportID: undefined}; + + expect(getOriginalReportID(reportID, reportAction, {[iouAction.reportActionID]: iouAction})).toBe(transactionThreadReportID); + + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, null); + await waitForBatchedUpdates(); + }); }); describe('hasVisibleReportFieldViolations', () => { From b71a29934410f3a1aceed1c701ec0d8395942089 Mon Sep 17 00:00:00 2001 From: cretadn22 Date: Mon, 3 Aug 2026 20:27:48 +0700 Subject: [PATCH 4/4] Refactor report action draft saving logic in ComposerWithSuggestions component to simplify parameters and improve readability. Remove unnecessary comments in deleteReportComment function. --- src/libs/actions/Report/index.ts | 2 -- .../ReportActionCompose/ComposerWithSuggestions.tsx | 12 +++--------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/src/libs/actions/Report/index.ts b/src/libs/actions/Report/index.ts index 42459e56f9ee..7ecb6b581267 100644 --- a/src/libs/actions/Report/index.ts +++ b/src/libs/actions/Report/index.ts @@ -3058,9 +3058,7 @@ function broadcastUserIsLeavingRoom(reportID: string, currentUserAccountID: numb function deleteReportComment( report: OnyxEntry, reportAction: ReportAction, - // Report actions of the original report, used to locate actionable mention whispers to hide. originalReportActions: OnyxEntry, - // Report actions of `report`, used to resolve the original report ID. reportActions: OnyxEntry, ancestors: Ancestor[], isReportArchived: boolean | undefined, diff --git a/src/pages/inbox/report/ReportActionCompose/ComposerWithSuggestions.tsx b/src/pages/inbox/report/ReportActionCompose/ComposerWithSuggestions.tsx index 4c8d5ed875df..4b9e327f495c 100644 --- a/src/pages/inbox/report/ReportActionCompose/ComposerWithSuggestions.tsx +++ b/src/pages/inbox/report/ReportActionCompose/ComposerWithSuggestions.tsx @@ -287,14 +287,7 @@ function ComposerWithSuggestions({ }); // Save the draft of the report action. This debounced so that we're not ceaselessly saving your edit. - const {saveDraft: debouncedSaveReportActionDraft, isSavePending: isDraftSavePending} = useDebouncedSaveDraft( - useCallback( - (comment: string) => { - saveReportActionDraft(reportID, editingReportAction, reportActions, comment); - }, - [reportID, editingReportAction, reportActions], - ), - ); + const {saveDraft: debouncedSaveReportActionDraft, isSavePending: isDraftSavePending} = useDebouncedSaveDraft(saveReportActionDraft); // Save the draft of the report comment. This debounced so that we're not ceaselessly saving your edit. Saving the draft // allows one to navigate somewhere else and come back to the comment and still have it in edit mode. @@ -561,7 +554,7 @@ function ComposerWithSuggestions({ if (editingState === CONST.REPORT_ACTION_EDIT_MESSAGE_STATE.EDITING && shouldUseNarrowLayout) { setEditingMessage(newCommentConverted); if (shouldDebounceSaveComment) { - debouncedSaveReportActionDraft(newCommentConverted); + debouncedSaveReportActionDraft(reportID, editingReportAction, reportActions, newCommentConverted); return; } @@ -595,6 +588,7 @@ function ComposerWithSuggestions({ setEditingMessage, reportID, editingReportActionID, + editingReportAction, reportActions, debouncedSaveReportActionDraft, debouncedSaveComment,