diff --git a/src/libs/actions/Report/index.ts b/src/libs/actions/Report/index.ts index 1a01a288d3fc..20a2fd87158e 100644 --- a/src/libs/actions/Report/index.ts +++ b/src/libs/actions/Report/index.ts @@ -5376,11 +5376,55 @@ function clearNewRoomFormError() { }); } +/** + * Builds optimistic and failure rollback data for adding invitees to a report's participants. + * Skips IDs that already exist in participants to avoid overwriting their settings. + * On failure, only nulls out the newly added keys so Onyx removes them without overwriting + * concurrent participant changes. + */ +function buildParticipantsInviteData( + targetReport: OnyxEntry, + inviteeAccountIDs: number[], +): {optimistic: Pick; failure: Pick} | undefined { + if (!targetReport || inviteeAccountIDs.length === 0) { + return undefined; + } + + const defaultPref = getDefaultNotificationPreferenceForReport(targetReport); + const participantsAfterInvitation = inviteeAccountIDs.reduce( + (acc: Participants, accountID: number) => { + if (accountID in (targetReport.participants ?? {})) { + return acc; + } + // eslint-disable-next-line no-param-reassign -- Mutating the reduce accumulator is intentional + acc[accountID] = { + notificationPreference: defaultPref, + role: CONST.REPORT.ROLE.MEMBER, + }; + return acc; + }, + {...targetReport.participants}, + ); + + const rollback: Record = {}; + for (const accountID of inviteeAccountIDs) { + if (!(accountID in (targetReport.participants ?? {}))) { + rollback[accountID] = null; + } + } + + return { + optimistic: {participants: participantsAfterInvitation}, + failure: {participants: rollback as unknown as Participants}, + }; +} + function resolveActionableMentionWhisper( report: OnyxEntry, reportAction: OnyxEntry, resolution: ValueOf | ValueOf, isReportArchived: boolean | undefined, + parentReport?: OnyxEntry, ) { const reportID = report?.reportID; if (!reportAction || !reportID) { @@ -5413,6 +5457,22 @@ function resolveActionableMentionWhisper( lastActorAccountID: report.lastActorAccountID, }; + // When the resolution is 'invited', optimistically add the invited users to report.participants + // so the members list updates immediately without waiting for the server response. + const isInviteResolution = resolution === CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.INVITE; + const originalMessage = ReportActionsUtils.getOriginalMessage(reportAction as ReportAction); + const inviteeAccountIDs = isInviteResolution ? (originalMessage?.inviteeAccountIDs ?? []) : []; + + const participantsInviteData = isInviteResolution && report ? buildParticipantsInviteData(report, inviteeAccountIDs) : undefined; + const participantsOptimisticData = participantsInviteData?.optimistic; + const participantsFailureData = participantsInviteData?.failure; + + // When the action belongs to a child report (e.g. a one-transaction thread), also update + // the parent report's participants so the members list the user is viewing updates immediately. + const parentInviteData = isInviteResolution && parentReport?.reportID && parentReport.reportID !== reportID ? buildParticipantsInviteData(parentReport, inviteeAccountIDs) : undefined; + const parentParticipantsOptimisticData = parentInviteData?.optimistic; + const parentParticipantsFailureData = parentInviteData?.failure; + const optimisticData: Array> = [ { onyxMethod: Onyx.METHOD.MERGE, @@ -5429,10 +5489,21 @@ function resolveActionableMentionWhisper( { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, - value: reportUpdateDataWithPreviousLastMessage, + value: { + ...reportUpdateDataWithPreviousLastMessage, + ...participantsOptimisticData, + }, }, ]; + if (parentParticipantsOptimisticData && parentReport?.reportID) { + optimisticData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${parentReport.reportID}`, + value: parentParticipantsOptimisticData, + }); + } + const failureData: Array> = [ { onyxMethod: Onyx.METHOD.MERGE, @@ -5449,10 +5520,21 @@ function resolveActionableMentionWhisper( { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, - value: reportUpdateDataWithCurrentLastMessage, // revert back to the current report last message data in case of failure + value: { + ...reportUpdateDataWithCurrentLastMessage, // revert back to the current report last message data in case of failure + ...participantsFailureData, + }, }, ]; + if (parentParticipantsFailureData && parentReport?.reportID) { + failureData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${parentReport.reportID}`, + value: parentParticipantsFailureData, + }); + } + const parameters: ResolveActionableMentionWhisperParams = { reportActionID: reportAction.reportActionID, resolution, diff --git a/src/pages/inbox/report/PureReportActionItem.tsx b/src/pages/inbox/report/PureReportActionItem.tsx index f6273e85f12d..e23a9b166da8 100644 --- a/src/pages/inbox/report/PureReportActionItem.tsx +++ b/src/pages/inbox/report/PureReportActionItem.tsx @@ -260,6 +260,7 @@ type PureReportActionItemProps = { reportAction: OnyxEntry, resolution: ValueOf, isReportArchived: boolean, + parentReport?: OnyxEntry, ) => void; /** Whether the provided report is a closed expense report with no expenses */ diff --git a/src/pages/inbox/report/actionContents/MentionWhisperContent.tsx b/src/pages/inbox/report/actionContents/MentionWhisperContent.tsx index da9f09fe7000..c79569dec2bc 100644 --- a/src/pages/inbox/report/actionContents/MentionWhisperContent.tsx +++ b/src/pages/inbox/report/actionContents/MentionWhisperContent.tsx @@ -25,6 +25,7 @@ type MentionWhisperContentProps = { reportAction: OnyxEntry, resolution: ValueOf, isReportArchived: boolean, + parentReport?: OnyxEntry, ) => void; }; @@ -44,19 +45,40 @@ function MentionWhisperContent({action, report, originalReport, policy, personal buttons.push({ text: 'actionableMentionWhisperOptions.inviteToSubmitExpense', key: `${action.reportActionID}-actionableMentionWhisper-${CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.INVITE_TO_SUBMIT_EXPENSE}`, - onPress: () => resolveActionableMentionWhisper(reportActionReport, action, CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.INVITE_TO_SUBMIT_EXPENSE, isOriginalReportArchived), + onPress: () => + resolveActionableMentionWhisper( + reportActionReport, + action, + CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.INVITE_TO_SUBMIT_EXPENSE, + isOriginalReportArchived, + originalReport ? report : undefined, + ), }); } buttons.push( { text: 'actionableMentionWhisperOptions.inviteToChat', key: `${action.reportActionID}-actionableMentionWhisper-${CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.INVITE}`, - onPress: () => resolveActionableMentionWhisper(reportActionReport, action, CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.INVITE, isOriginalReportArchived), + onPress: () => + resolveActionableMentionWhisper( + reportActionReport, + action, + CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.INVITE, + isOriginalReportArchived, + originalReport ? report : undefined, + ), }, { text: 'actionableMentionWhisperOptions.nothing', key: `${action.reportActionID}-actionableMentionWhisper-${CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.NOTHING}`, - onPress: () => resolveActionableMentionWhisper(reportActionReport, action, CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.NOTHING, isOriginalReportArchived), + onPress: () => + resolveActionableMentionWhisper( + reportActionReport, + action, + CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.NOTHING, + isOriginalReportArchived, + originalReport ? report : undefined, + ), }, ); diff --git a/tests/actions/ReportTest.ts b/tests/actions/ReportTest.ts index 5b14d8edfc99..9734d3456834 100644 --- a/tests/actions/ReportTest.ts +++ b/tests/actions/ReportTest.ts @@ -7140,6 +7140,349 @@ describe('actions/Report', () => { }); }); + describe('resolveActionableMentionWhisper', () => { + it('should optimistically add invited users to report.participants when resolution is INVITE', async () => { + global.fetch = TestHelper.getGlobalFetchMock(); + + const REPORT_ID = '1'; + const WHISPER_ACTION_ID = '1001'; + const EXISTING_PARTICIPANT_ID = 100; + const INVITEE_ACCOUNT_ID_1 = 200; + const INVITEE_ACCOUNT_ID_2 = 300; + + const report: OnyxTypes.Report = { + ...createRandomReport(1, undefined), + reportID: REPORT_ID, + participants: { + [EXISTING_PARTICIPANT_ID]: { + notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS, + role: CONST.REPORT.ROLE.ADMIN, + }, + }, + lastMessageText: 'Hello', + lastVisibleActionCreated: '2024-11-19 08:04:13.728', + lastActorAccountID: EXISTING_PARTICIPANT_ID, + }; + + const whisperAction = { + reportActionID: WHISPER_ACTION_ID, + reportID: REPORT_ID, + actionName: CONST.REPORT.ACTIONS.TYPE.ACTIONABLE_MENTION_WHISPER, + created: '2024-11-19 08:04:13.730', + message: [{html: 'Mentioned @user1 and @user2', text: 'Mentioned @user1 and @user2', type: 'COMMENT'}], + originalMessage: { + inviteeAccountIDs: [INVITEE_ACCOUNT_ID_1, INVITEE_ACCOUNT_ID_2], + inviteeEmails: ['user1@example.com', 'user2@example.com'], + whisperedTo: [EXISTING_PARTICIPANT_ID], + }, + } as unknown as OnyxTypes.ReportAction; + + // Seed Onyx with report and report actions + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, report); + await Onyx.mergeCollection(ONYXKEYS.COLLECTION.REPORT_ACTIONS, { + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${REPORT_ID}`]: { + [WHISPER_ACTION_ID]: whisperAction, + }, + }); + await waitForBatchedUpdates(); + + // Call resolveActionableMentionWhisper with INVITE resolution + Report.resolveActionableMentionWhisper(report, whisperAction, CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.INVITE, false); + await waitForBatchedUpdates(); + + // Verify optimistic participant update + const updatedReport = await getOnyxValue(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}` as const); + + // Existing participant should still be there with original values + expect(updatedReport?.participants?.[EXISTING_PARTICIPANT_ID]).toMatchObject({ + notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS, + role: CONST.REPORT.ROLE.ADMIN, + }); + + // Invitees should be added with default notification preference and member role + expect(updatedReport?.participants?.[INVITEE_ACCOUNT_ID_1]).toMatchObject({ + notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS, + role: CONST.REPORT.ROLE.MEMBER, + }); + expect(updatedReport?.participants?.[INVITEE_ACCOUNT_ID_2]).toMatchObject({ + notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS, + role: CONST.REPORT.ROLE.MEMBER, + }); + }); + + it('should NOT update participants when resolution is NOTHING', async () => { + global.fetch = TestHelper.getGlobalFetchMock(); + + const REPORT_ID = '2'; + const WHISPER_ACTION_ID = '2001'; + const EXISTING_PARTICIPANT_ID = 100; + const INVITEE_ACCOUNT_ID = 200; + + const report: OnyxTypes.Report = { + ...createRandomReport(2, undefined), + reportID: REPORT_ID, + participants: { + [EXISTING_PARTICIPANT_ID]: { + notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS, + role: CONST.REPORT.ROLE.ADMIN, + }, + }, + lastMessageText: 'Hello', + lastVisibleActionCreated: '2024-11-19 08:04:13.728', + lastActorAccountID: EXISTING_PARTICIPANT_ID, + }; + + const whisperAction = { + reportActionID: WHISPER_ACTION_ID, + reportID: REPORT_ID, + actionName: CONST.REPORT.ACTIONS.TYPE.ACTIONABLE_MENTION_WHISPER, + created: '2024-11-19 08:04:13.730', + message: [{html: 'Mentioned @user1', text: 'Mentioned @user1', type: 'COMMENT'}], + originalMessage: { + inviteeAccountIDs: [INVITEE_ACCOUNT_ID], + inviteeEmails: ['user1@example.com'], + whisperedTo: [EXISTING_PARTICIPANT_ID], + }, + } as unknown as OnyxTypes.ReportAction; + + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, report); + await Onyx.mergeCollection(ONYXKEYS.COLLECTION.REPORT_ACTIONS, { + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${REPORT_ID}`]: { + [WHISPER_ACTION_ID]: whisperAction, + }, + }); + await waitForBatchedUpdates(); + + // Call with NOTHING resolution + Report.resolveActionableMentionWhisper(report, whisperAction, CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.NOTHING, false); + await waitForBatchedUpdates(); + + const updatedReport = await getOnyxValue(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}` as const); + + // Existing participant should still be there + expect(updatedReport?.participants?.[EXISTING_PARTICIPANT_ID]).toBeDefined(); + + // Invitee should NOT be added + expect(updatedReport?.participants?.[INVITEE_ACCOUNT_ID]).toBeUndefined(); + }); + + it('should preserve existing participant settings when invitee is already in participants', async () => { + global.fetch = TestHelper.getGlobalFetchMock(); + + const REPORT_ID = '4'; + const WHISPER_ACTION_ID = '4001'; + const EXISTING_ADMIN_ID = 100; + const ALREADY_ADDED_USER_ID = 200; + const NEW_INVITEE_ID = 300; + + const report: OnyxTypes.Report = { + ...createRandomReport(4, undefined), + reportID: REPORT_ID, + participants: { + [EXISTING_ADMIN_ID]: { + notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS, + role: CONST.REPORT.ROLE.ADMIN, + }, + [ALREADY_ADDED_USER_ID]: { + notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.DAILY, + role: CONST.REPORT.ROLE.ADMIN, + }, + }, + lastMessageText: 'Hello', + lastVisibleActionCreated: '2024-11-19 08:04:13.728', + lastActorAccountID: EXISTING_ADMIN_ID, + }; + + // Stale whisper that includes an already-added user alongside a new one + const whisperAction = { + reportActionID: WHISPER_ACTION_ID, + reportID: REPORT_ID, + actionName: CONST.REPORT.ACTIONS.TYPE.ACTIONABLE_MENTION_WHISPER, + created: '2024-11-19 08:04:13.730', + message: [{html: 'Mentioned @user1 and @user2', text: 'Mentioned @user1 and @user2', type: 'COMMENT'}], + originalMessage: { + inviteeAccountIDs: [ALREADY_ADDED_USER_ID, NEW_INVITEE_ID], + inviteeEmails: ['user1@example.com', 'user2@example.com'], + whisperedTo: [EXISTING_ADMIN_ID], + }, + } as unknown as OnyxTypes.ReportAction; + + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, report); + await Onyx.mergeCollection(ONYXKEYS.COLLECTION.REPORT_ACTIONS, { + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${REPORT_ID}`]: { + [WHISPER_ACTION_ID]: whisperAction, + }, + }); + await waitForBatchedUpdates(); + + Report.resolveActionableMentionWhisper(report, whisperAction, CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.INVITE, false); + await waitForBatchedUpdates(); + + const updatedReport = await getOnyxValue(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}` as const); + + // Already-added user's settings must NOT be overwritten + expect(updatedReport?.participants?.[ALREADY_ADDED_USER_ID]).toMatchObject({ + notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.DAILY, + role: CONST.REPORT.ROLE.ADMIN, + }); + + // New invitee should be added with default settings + expect(updatedReport?.participants?.[NEW_INVITEE_ID]).toMatchObject({ + notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS, + role: CONST.REPORT.ROLE.MEMBER, + }); + }); + + it('should also update parent report participants when parentReport is provided (oneTransactionThread)', async () => { + global.fetch = TestHelper.getGlobalFetchMock(); + + const TRANSACTION_THREAD_ID = '10'; + const PARENT_REPORT_ID = '11'; + const WHISPER_ACTION_ID = '10001'; + const EXISTING_PARTICIPANT_ID = 100; + const INVITEE_ACCOUNT_ID = 200; + + // Transaction thread report (where the whisper action lives) + const transactionThreadReport: OnyxTypes.Report = { + ...createRandomReport(10, undefined), + reportID: TRANSACTION_THREAD_ID, + parentReportID: PARENT_REPORT_ID, + participants: { + [EXISTING_PARTICIPANT_ID]: { + notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS, + role: CONST.REPORT.ROLE.ADMIN, + }, + }, + lastMessageText: 'Receipt', + lastVisibleActionCreated: '2024-11-19 08:04:13.728', + lastActorAccountID: EXISTING_PARTICIPANT_ID, + }; + + // Parent report (what the user is viewing - the expense report) + const parentReport: OnyxTypes.Report = { + ...createRandomReport(11, undefined), + reportID: PARENT_REPORT_ID, + participants: { + [EXISTING_PARTICIPANT_ID]: { + notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS, + role: CONST.REPORT.ROLE.ADMIN, + }, + }, + }; + + const whisperAction = { + reportActionID: WHISPER_ACTION_ID, + reportID: TRANSACTION_THREAD_ID, + actionName: CONST.REPORT.ACTIONS.TYPE.ACTIONABLE_MENTION_WHISPER, + created: '2024-11-19 08:04:13.730', + message: [{html: 'Mentioned @user1', text: 'Mentioned @user1', type: 'COMMENT'}], + originalMessage: { + inviteeAccountIDs: [INVITEE_ACCOUNT_ID], + inviteeEmails: ['user1@example.com'], + whisperedTo: [EXISTING_PARTICIPANT_ID], + }, + } as unknown as OnyxTypes.ReportAction; + + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${TRANSACTION_THREAD_ID}`, transactionThreadReport); + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${PARENT_REPORT_ID}`, parentReport); + await Onyx.mergeCollection(ONYXKEYS.COLLECTION.REPORT_ACTIONS, { + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${TRANSACTION_THREAD_ID}`]: { + [WHISPER_ACTION_ID]: whisperAction, + }, + }); + await waitForBatchedUpdates(); + + // Call with parentReport to simulate oneTransactionThread context + Report.resolveActionableMentionWhisper(transactionThreadReport, whisperAction, CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.INVITE, false, parentReport); + await waitForBatchedUpdates(); + + // Verify the invitee was added to the transaction thread participants + const updatedThreadReport = await getOnyxValue(`${ONYXKEYS.COLLECTION.REPORT}${TRANSACTION_THREAD_ID}` as const); + expect(updatedThreadReport?.participants?.[INVITEE_ACCOUNT_ID]).toMatchObject({ + notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS, + role: CONST.REPORT.ROLE.MEMBER, + }); + + // Verify the invitee was also added to the parent report participants + const updatedParentReport = await getOnyxValue(`${ONYXKEYS.COLLECTION.REPORT}${PARENT_REPORT_ID}` as const); + expect(updatedParentReport?.participants?.[INVITEE_ACCOUNT_ID]).toMatchObject({ + notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS, + role: CONST.REPORT.ROLE.MEMBER, + }); + }); + + it('should remove optimistically added participants on failure rollback', async () => { + const mockFetch = TestHelper.getGlobalFetchMock() as MockFetch; + global.fetch = mockFetch; + + const REPORT_ID = '3'; + const WHISPER_ACTION_ID = '3001'; + const EXISTING_PARTICIPANT_ID = 100; + const INVITEE_ACCOUNT_ID = 200; + + const report: OnyxTypes.Report = { + ...createRandomReport(3, undefined), + reportID: REPORT_ID, + participants: { + [EXISTING_PARTICIPANT_ID]: { + notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS, + role: CONST.REPORT.ROLE.ADMIN, + }, + }, + lastMessageText: 'Hello', + lastVisibleActionCreated: '2024-11-19 08:04:13.728', + lastActorAccountID: EXISTING_PARTICIPANT_ID, + }; + + const whisperAction = { + reportActionID: WHISPER_ACTION_ID, + reportID: REPORT_ID, + actionName: CONST.REPORT.ACTIONS.TYPE.ACTIONABLE_MENTION_WHISPER, + created: '2024-11-19 08:04:13.730', + message: [{html: 'Mentioned @user1', text: 'Mentioned @user1', type: 'COMMENT'}], + originalMessage: { + inviteeAccountIDs: [INVITEE_ACCOUNT_ID], + inviteeEmails: ['user1@example.com'], + whisperedTo: [EXISTING_PARTICIPANT_ID], + }, + } as unknown as OnyxTypes.ReportAction; + + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, report); + await Onyx.mergeCollection(ONYXKEYS.COLLECTION.REPORT_ACTIONS, { + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${REPORT_ID}`]: { + [WHISPER_ACTION_ID]: whisperAction, + }, + }); + await waitForBatchedUpdates(); + + // Pause fetch so we can verify optimistic state, then trigger failure + mockFetch.pause(); + Report.resolveActionableMentionWhisper(report, whisperAction, CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.INVITE, false); + await waitForBatchedUpdates(); + + // Verify the invitee was optimistically added + let updatedReport = await getOnyxValue(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}` as const); + expect(updatedReport?.participants?.[INVITEE_ACCOUNT_ID]).toBeDefined(); + + // Now make the request fail and resume + mockFetch.fail(); + mockFetch.resume(); + await waitForBatchedUpdates(); + await waitForNetworkPromises(); + await waitForBatchedUpdates(); + + // After failure, the invitee should be removed from participants + updatedReport = await getOnyxValue(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}` as const); + expect(updatedReport?.participants?.[INVITEE_ACCOUNT_ID]).toBeFalsy(); + + // Existing participant should still be there + expect(updatedReport?.participants?.[EXISTING_PARTICIPANT_ID]).toMatchObject({ + notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS, + role: CONST.REPORT.ROLE.ADMIN, + }); + }); + }); + describe('buildOptimisticAddCommentReportAction delegateAccountID forwarding', () => { const DELEGATE_ACCOUNT_ID = 999;