diff --git a/src/hooks/useOptimisticNextStep.ts b/src/hooks/useOptimisticNextStep.ts index 5a80fae71dab..697812faf066 100644 --- a/src/hooks/useOptimisticNextStep.ts +++ b/src/hooks/useOptimisticNextStep.ts @@ -85,7 +85,7 @@ function useOptimisticNextStep(reportID: string | undefined) { } return acc; }, {}); - const {errors} = getAllReportActionsErrorsAndReportActionThatRequiresAttention(moneyRequestReport, reportActionsObject, reportTransactions); + const {errors} = getAllReportActionsErrorsAndReportActionThatRequiresAttention(moneyRequestReport, reportActionsObject, reportTransactions, accountID); if (errors?.dewSubmitFailed) { optimisticNextStep = buildOptimisticNextStepForDynamicExternalWorkflowSubmitError(theme.danger); diff --git a/src/libs/DebugUtils.ts b/src/libs/DebugUtils.ts index 590a2e6dddee..5d715ffccfec 100644 --- a/src/libs/DebugUtils.ts +++ b/src/libs/DebugUtils.ts @@ -1556,6 +1556,7 @@ function getReasonAndReportActionForRBRInLHNRow( hasViolations: boolean, reportErrors: Errors, isOffline: boolean, + currentUserAccountID: number, isArchivedReport = false, ): RBRReasonAndReportAction | null { const {reason, reportAction} = @@ -1567,6 +1568,7 @@ function getReasonAndReportActionForRBRInLHNRow( reportErrors, transactions, isOffline, + currentUserAccountID, transactionViolations, isArchivedReport, ) ?? {}; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 9f35983d5118..adcac9787e86 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -5581,8 +5581,7 @@ function getReportActionWithMissingSmartscanFields( iouReport: OnyxEntry, iouReportID: string | undefined, iouReportTransactions: OnyxCollection, - // TODO: Remove optional (?) once all callers pass currentUserAccountID. Refactor issue: https://github.com/Expensify/App/issues/66408 - currentUserAccountID?: number, + currentUserAccountID: number, ): ReportAction | undefined { const reportActions = Object.values(getAllReportActions(iouReportID)); return reportActions.find((action) => { @@ -5608,8 +5607,7 @@ function shouldShowRBRForMissingSmartscanFields( iouReport: OnyxEntry, iouReportID: string | undefined, iouReportTransactions: OnyxCollection, - // TODO: Remove optional (?) once all callers pass currentUserAccountID. Refactor issue: https://github.com/Expensify/App/issues/66408 - currentUserAccountID?: number, + currentUserAccountID: number, ): boolean { return !!getReportActionWithMissingSmartscanFields(iouReport, iouReportID, iouReportTransactions, currentUserAccountID); } @@ -9670,10 +9668,9 @@ function getAllReportActionsErrorsAndReportActionThatRequiresAttention( report: OnyxEntry, reportActions: OnyxEntry, allTransactions: OnyxCollection, + currentUserAccountID: number, isReportArchived = false, reports?: OnyxCollection, - // TODO: Remove optional (?) once all callers pass currentUserAccountID. Refactor issue: https://github.com/Expensify/App/issues/66408 - currentUserAccountID?: number, ): ReportErrorsAndReportActionThatRequiresAttention { const reportActionsArray = Object.values(reportActions ?? {}).filter((action) => !isDeletedAction(action)); const reportActionErrors: ErrorFields = {}; @@ -9689,9 +9686,9 @@ function getAllReportActionsErrorsAndReportActionThatRequiresAttention( } } - if (!isReportArchived && hasSmartscanError(reportActionsArray, report, allTransactions, reports, currentUserAccountID)) { + if (!isReportArchived && hasSmartscanError(reportActionsArray, report, allTransactions, currentUserAccountID, reports)) { reportActionErrors.smartscan = getMicroSecondOnyxErrorWithTranslationKey('iou.error.genericSmartscanFailureMessage'); - reportAction = getReportActionWithSmartscanError(reportActionsArray, report, allTransactions, reports, currentUserAccountID); + reportAction = getReportActionWithSmartscanError(reportActionsArray, report, allTransactions, currentUserAccountID, reports); } if (!isReportArchived && isReportOwner(report) && report?.statusNum === CONST.REPORT.STATUS_NUM.OPEN) { @@ -9724,9 +9721,9 @@ function getAllReportErrors( report, reportActions, allTransactions, + currentUserAccountID, isReportArchived, reports, - currentUserAccountID, ); // All error objects related to the report. Each object in the sources contains error messages keyed by microtime @@ -11111,9 +11108,8 @@ function getReportActionWithSmartscanError( reportActions: ReportAction[], report: OnyxEntry, allTransactions: OnyxCollection, + currentUserAccountID: number, reports?: OnyxCollection, - // TODO: Remove optional (?) once all callers pass currentUserAccountID. Refactor issue: https://github.com/Expensify/App/issues/66408 - currentUserAccountID?: number, ): ReportAction | undefined { return reportActions.find((action) => { const isReportPreview = isReportPreviewAction(action); @@ -11144,11 +11140,10 @@ function hasSmartscanError( reportActions: ReportAction[], report: OnyxEntry, allTransactions: OnyxCollection, + currentUserAccountID: number, reports?: OnyxCollection, - // TODO: Remove optional (?) once all callers pass currentUserAccountID. Refactor issue: https://github.com/Expensify/App/issues/66408 - currentUserAccountID?: number, ): boolean { - return !!getReportActionWithSmartscanError(reportActions, report, allTransactions, reports, currentUserAccountID); + return !!getReportActionWithSmartscanError(reportActions, report, allTransactions, currentUserAccountID, reports); } function shouldAutoFocusOnKeyPress(event: KeyboardEvent): boolean { diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 2ce0c0284d63..2966a2151b75 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -755,6 +755,8 @@ type ReasonAndReportActionThatHasRedBrickRoad = { reportAction?: OnyxEntry; }; +// TODO: Refactor to use options object parameter to reduce parameter count +// eslint-disable-next-line @typescript-eslint/max-params function getReasonAndReportActionThatHasRedBrickRoad( report: Report, chatReport: OnyxEntry, @@ -763,6 +765,7 @@ function getReasonAndReportActionThatHasRedBrickRoad( reportErrors: Errors, transactions: OnyxCollection, isOffline: boolean, + currentUserAccountID: number, transactionViolations?: OnyxCollection, isReportArchived = false, reports?: OnyxCollection, @@ -780,7 +783,7 @@ function getReasonAndReportActionThatHasRedBrickRoad( }; } - const {reportAction} = getAllReportActionsErrorsAndReportActionThatRequiresAttention(report, reportActions, transactions, isReportArchived, reports); + const {reportAction} = getAllReportActionsErrorsAndReportActionThatRequiresAttention(report, reportActions, transactions, currentUserAccountID, isReportArchived, reports); const errors = reportErrors; const hasErrors = Object.keys(errors).length !== 0; diff --git a/src/libs/actions/OnyxDerived/configs/reportAttributes.ts b/src/libs/actions/OnyxDerived/configs/reportAttributes.ts index 9effe46e29f5..69da510a17a6 100644 --- a/src/libs/actions/OnyxDerived/configs/reportAttributes.ts +++ b/src/libs/actions/OnyxDerived/configs/reportAttributes.ts @@ -493,6 +493,7 @@ export default createOnyxDerivedValueConfig({ reportErrors, transactions, isOffline, + session?.accountID ?? CONST.DEFAULT_NUMBER_ID, transactionViolations, !!isReportArchived, reports, diff --git a/src/pages/Debug/Report/DebugReportPage.tsx b/src/pages/Debug/Report/DebugReportPage.tsx index 318f68818396..5fcf434f013b 100644 --- a/src/pages/Debug/Report/DebugReportPage.tsx +++ b/src/pages/Debug/Report/DebugReportPage.tsx @@ -106,6 +106,7 @@ function DebugReportPage({ hasViolations, reportAttributes?.reportErrors ?? {}, isOffline, + currentUserAccountID, isReportArchived, ) ?? {}; const hasRBR = !!reasonRBR; diff --git a/tests/unit/DebugUtilsTest.ts b/tests/unit/DebugUtilsTest.ts index 14e77ad3ea17..0e64fd3f0622 100644 --- a/tests/unit/DebugUtilsTest.ts +++ b/tests/unit/DebugUtilsTest.ts @@ -1249,6 +1249,7 @@ describe('DebugUtils', () => { false, {}, false, + RORY_ACCOUNT_ID, ) ?? {}; expect(reportAction).toBeUndefined(); }); @@ -1313,6 +1314,7 @@ describe('DebugUtils', () => { false, {}, false, + 12345, ) ?? {}; expect(reportAction).toBe(undefined); }); @@ -1389,6 +1391,7 @@ describe('DebugUtils', () => { false, reportErrors, false, + RORY_ACCOUNT_ID, ) ?? {}; expect(reportAction).toMatchObject(MOCK_CHAT_REPORT_ACTIONS['1']); }); @@ -1461,8 +1464,17 @@ describe('DebugUtils', () => { }; const reportErrors = getAllReportErrors(MOCK_CHAT_REPORT, MOCK_REPORT_ACTIONS, mockTransactions, RORY_ACCOUNT_ID); const {reportAction} = - DebugUtils.getReasonAndReportActionForRBRInLHNRow(MOCK_CHAT_REPORT, chatReportR14932, MOCK_REPORT_ACTIONS, mockTransactions, undefined, false, reportErrors, false) ?? - {}; + DebugUtils.getReasonAndReportActionForRBRInLHNRow( + MOCK_CHAT_REPORT, + chatReportR14932, + MOCK_REPORT_ACTIONS, + mockTransactions, + undefined, + false, + reportErrors, + false, + 12345, + ) ?? {}; expect(reportAction).toMatchObject(MOCK_REPORT_ACTIONS['3']); }); }); @@ -1521,6 +1533,7 @@ describe('DebugUtils', () => { false, reportErrors, false, + RORY_ACCOUNT_ID, ) ?? {}; expect(reportAction).toMatchObject(MOCK_REPORT_ACTIONS['1']); }); @@ -1549,8 +1562,17 @@ describe('DebugUtils', () => { const reportErrors = getAllReportErrors(mockedReport, mockedReportActions, sharedAllTransactions, RORY_ACCOUNT_ID); const {reason} = - DebugUtils.getReasonAndReportActionForRBRInLHNRow(mockedReport, chatReportR14932, mockedReportActions, sharedAllTransactions, undefined, false, reportErrors, false) ?? - {}; + DebugUtils.getReasonAndReportActionForRBRInLHNRow( + mockedReport, + chatReportR14932, + mockedReportActions, + sharedAllTransactions, + undefined, + false, + reportErrors, + false, + RORY_ACCOUNT_ID, + ) ?? {}; expect(reason).toBe('debug.reasonRBR.hasErrors'); }); it('returns correct reason when there are violations', () => { @@ -1566,6 +1588,7 @@ describe('DebugUtils', () => { true, {}, false, + RORY_ACCOUNT_ID, ) ?? {}; expect(reason).toBe('debug.reasonRBR.hasViolations'); }); @@ -1582,6 +1605,7 @@ describe('DebugUtils', () => { true, {}, false, + RORY_ACCOUNT_ID, true, ) ?? {}; expect(reason).toBe(undefined); @@ -1638,7 +1662,7 @@ describe('DebugUtils', () => { reportID: '1', }), }; - const {reason} = DebugUtils.getReasonAndReportActionForRBRInLHNRow(report, chatReportR14932, {}, violationTransactions, transactionViolations, false, {}, false) ?? {}; + const {reason} = DebugUtils.getReasonAndReportActionForRBRInLHNRow(report, chatReportR14932, {}, violationTransactions, transactionViolations, false, {}, false, 1234) ?? {}; expect(reason).toBe('debug.reasonRBR.hasTransactionThreadViolations'); }); it('forwards isOffline through to SidebarUtils so the live IOU transaction-thread receipt error surfaces only when isOffline=false excludes the deleted pending-delete action', () => { @@ -1707,6 +1731,7 @@ describe('DebugUtils', () => { false, {}, true, + 12345, ); const online = DebugUtils.getReasonAndReportActionForRBRInLHNRow( OFFLINE_EXPENSE_REPORT, @@ -1717,6 +1742,7 @@ describe('DebugUtils', () => { false, {}, false, + 12345, ); // Online: deleted pending-delete is skipped → 1 IOU thread → receipt error surfaces. diff --git a/tests/unit/ReportUtilsTest.ts b/tests/unit/ReportUtilsTest.ts index e109d6b54701..2a25130cc40c 100644 --- a/tests/unit/ReportUtilsTest.ts +++ b/tests/unit/ReportUtilsTest.ts @@ -12912,7 +12912,7 @@ describe('ReportUtils', () => { return waitForBatchedUpdates(); }); it("should return nothing when there's no actions required", () => { - expect(getAllReportActionsErrorsAndReportActionThatRequiresAttention(report, reportActions, allTransactions)).toEqual({ + expect(getAllReportActionsErrorsAndReportActionThatRequiresAttention(report, reportActions, allTransactions, currentUserAccountID)).toEqual({ errors: {}, reportAction: undefined, }); @@ -12934,7 +12934,7 @@ describe('ReportUtils', () => { [reportActionWithError.reportActionID]: reportActionWithError, }); await waitForBatchedUpdates(); - expect(getAllReportActionsErrorsAndReportActionThatRequiresAttention(report, reportActionsWithError, allTransactions)).toEqual({ + expect(getAllReportActionsErrorsAndReportActionThatRequiresAttention(report, reportActionsWithError, allTransactions, currentUserAccountID)).toEqual({ errors: { reportID: 'Error message', accountID: 'Error in accountID', @@ -12962,9 +12962,14 @@ describe('ReportUtils', () => { }; await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`, transaction); await waitForBatchedUpdates(); - const {errors, reportAction} = getAllReportActionsErrorsAndReportActionThatRequiresAttention(parentReport, reportActions, { - [`${ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`]: transaction, - }); + const {errors, reportAction} = getAllReportActionsErrorsAndReportActionThatRequiresAttention( + parentReport, + reportActions, + { + [`${ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`]: transaction, + }, + currentUserAccountID, + ); expect(Object.keys(errors)).toHaveLength(1); expect(Object.keys(errors).at(0)).toBe('smartscan'); expect(Object.keys(errors.smartscan ?? {})).toHaveLength(1); @@ -12994,6 +12999,7 @@ describe('ReportUtils', () => { report, reportActions, {[`${ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`]: transaction}, + currentUserAccountID, true, ); expect(Object.keys(errors)).toHaveLength(0); @@ -17303,7 +17309,7 @@ describe('ReportUtils', () => { [dewSubmitFailedAction.reportActionID]: dewSubmitFailedAction, }; - const {errors, reportAction} = getAllReportActionsErrorsAndReportActionThatRequiresAttention(report, reportActions, dewAllTransactions); + const {errors, reportAction} = getAllReportActionsErrorsAndReportActionThatRequiresAttention(report, reportActions, dewAllTransactions, currentUserAccountID); expect(errors?.dewSubmitFailed).toBeDefined(); expect(reportAction).toEqual(dewSubmitFailedAction); @@ -17332,7 +17338,7 @@ describe('ReportUtils', () => { [dewSubmitFailedAction.reportActionID]: dewSubmitFailedAction, }; - const {errors} = getAllReportActionsErrorsAndReportActionThatRequiresAttention(report, reportActions, dewAllTransactions); + const {errors} = getAllReportActionsErrorsAndReportActionThatRequiresAttention(report, reportActions, dewAllTransactions, currentUserAccountID); expect(errors?.dewSubmitFailed).toBeUndefined(); }); @@ -17367,7 +17373,7 @@ describe('ReportUtils', () => { [submittedAction.reportActionID]: submittedAction, }; - const {errors} = getAllReportActionsErrorsAndReportActionThatRequiresAttention(report, reportActions, dewAllTransactions); + const {errors} = getAllReportActionsErrorsAndReportActionThatRequiresAttention(report, reportActions, dewAllTransactions, currentUserAccountID); expect(errors?.dewSubmitFailed).toBeUndefined(); }); @@ -17420,7 +17426,7 @@ describe('ReportUtils', () => { [dewSubmitFailedAction.reportActionID]: dewSubmitFailedAction, }; - const {errors, reportAction} = getAllReportActionsErrorsAndReportActionThatRequiresAttention(report, reportActions, dewAllTransactions); + const {errors, reportAction} = getAllReportActionsErrorsAndReportActionThatRequiresAttention(report, reportActions, dewAllTransactions, currentUserAccountID); expect(errors?.dewSubmitFailed).toBeDefined(); expect(reportAction).toEqual(dewSubmitFailedAction); @@ -17447,7 +17453,7 @@ describe('ReportUtils', () => { [dewSubmitFailedAction.reportActionID]: dewSubmitFailedAction, }; - const {errors} = getAllReportActionsErrorsAndReportActionThatRequiresAttention(report, reportActions, dewAllTransactions); + const {errors} = getAllReportActionsErrorsAndReportActionThatRequiresAttention(report, reportActions, dewAllTransactions, currentUserAccountID); expect(errors?.dewSubmitFailed).toBeUndefined(); }); @@ -17473,7 +17479,7 @@ describe('ReportUtils', () => { [dewSubmitFailedAction.reportActionID]: dewSubmitFailedAction, }; - const {errors} = getAllReportActionsErrorsAndReportActionThatRequiresAttention(report, reportActions, dewAllTransactions, true); + const {errors} = getAllReportActionsErrorsAndReportActionThatRequiresAttention(report, reportActions, dewAllTransactions, currentUserAccountID, true); expect(errors?.dewSubmitFailed).toBeUndefined(); }); @@ -17544,7 +17550,7 @@ describe('ReportUtils', () => { [secondSubmittedAction.reportActionID]: secondSubmittedAction, }; - const {errors, reportAction} = getAllReportActionsErrorsAndReportActionThatRequiresAttention(report, reportActions, dewAllTransactions); + const {errors, reportAction} = getAllReportActionsErrorsAndReportActionThatRequiresAttention(report, reportActions, dewAllTransactions, currentUserAccountID); expect(errors?.dewSubmitFailed).toBeUndefined(); expect(reportAction).not.toEqual(dewSubmitFailedAction); @@ -20264,9 +20270,9 @@ describe('ReportUtils', () => { [`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]: transaction, }; - const result = getReportActionWithSmartscanError([reportPreviewAction], chatReport, allTransactions, reportsCollection, currentUserAccountID); + const result = getReportActionWithSmartscanError([reportPreviewAction], chatReport, allTransactions, currentUserAccountID, reportsCollection); expect(result).toBeUndefined(); - expect(hasSmartscanError([reportPreviewAction], chatReport, allTransactions, reportsCollection, currentUserAccountID)).toBe(false); + expect(hasSmartscanError([reportPreviewAction], chatReport, allTransactions, currentUserAccountID, reportsCollection)).toBe(false); }); it('should flag smartscan error when expense report has a missing merchant', async () => { @@ -20286,7 +20292,7 @@ describe('ReportUtils', () => { [`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]: transactionMissingMerchant, }; - expect(hasSmartscanError([reportPreviewAction], chatReport, allTransactions, reportsCollection, currentUserAccountID)).toBe(true); + expect(hasSmartscanError([reportPreviewAction], chatReport, allTransactions, currentUserAccountID, reportsCollection)).toBe(true); // Restore original transaction for subsequent tests await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, transaction); @@ -20317,7 +20323,7 @@ describe('ReportUtils', () => { [`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]: transactionMissingMerchant, }; - expect(hasSmartscanError([reportPreviewAction], chatReport, allTransactions, reportsCollection, currentUserAccountID)).toBe(false); + expect(hasSmartscanError([reportPreviewAction], chatReport, allTransactions, currentUserAccountID, reportsCollection)).toBe(false); // Restore await Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, transaction); @@ -20353,8 +20359,8 @@ describe('ReportUtils', () => { [`${ONYXKEYS.COLLECTION.TRANSACTION}${splitTransactionID}`]: splitTransaction, }; - expect(hasSmartscanError([splitAction], chatReport, allTransactions, undefined, currentUserAccountID)).toBe(true); - expect(getReportActionWithSmartscanError([splitAction], chatReport, allTransactions, undefined, currentUserAccountID)).toEqual(splitAction); + expect(hasSmartscanError([splitAction], chatReport, allTransactions, currentUserAccountID)).toBe(true); + expect(getReportActionWithSmartscanError([splitAction], chatReport, allTransactions, currentUserAccountID)).toEqual(splitAction); }); it('should NOT flag a split-bill action when its linked transaction has all required fields', () => { @@ -20384,8 +20390,8 @@ describe('ReportUtils', () => { [`${ONYXKEYS.COLLECTION.TRANSACTION}${splitTransactionID}`]: splitTransaction, }; - expect(hasSmartscanError([splitAction], chatReport, allTransactions, undefined, currentUserAccountID)).toBe(false); - expect(getReportActionWithSmartscanError([splitAction], chatReport, allTransactions, undefined, currentUserAccountID)).toBeUndefined(); + expect(hasSmartscanError([splitAction], chatReport, allTransactions, currentUserAccountID)).toBe(false); + expect(getReportActionWithSmartscanError([splitAction], chatReport, allTransactions, currentUserAccountID)).toBeUndefined(); }); }); diff --git a/tests/unit/SidebarUtilsTest.ts b/tests/unit/SidebarUtilsTest.ts index ecc2c389901c..066f57eeb9c2 100644 --- a/tests/unit/SidebarUtilsTest.ts +++ b/tests/unit/SidebarUtilsTest.ts @@ -10,7 +10,14 @@ import DateUtils from '@libs/DateUtils'; import {getLastActorDisplayName} from '@libs/OptionsListUtils'; import type * as PolicyUtils from '@libs/PolicyUtils'; import {getOriginalMessage, getReportActionMessageText} from '@libs/ReportActionsUtils'; -import {formatReportLastMessageText, generateReportID, getAllReportErrors, getReasonAndReportActionThatRequiresAttention, getReportPreviewReportActionMessage} from '@libs/ReportUtils'; +import { + buildOptimisticReportPreview, + formatReportLastMessageText, + generateReportID, + getAllReportErrors, + getReasonAndReportActionThatRequiresAttention, + getReportPreviewReportActionMessage, +} from '@libs/ReportUtils'; import SidebarUtils, {_buildSortKey, _categorizeReportsForLHN, _combineReportCategories, _sortCategorizedReports} from '@libs/SidebarUtils'; import initOnyxDerivedValues from '@userActions/OnyxDerived'; @@ -138,6 +145,7 @@ describe('SidebarUtils', () => { {}, MOCK_TRANSACTIONS, false, + CURRENT_USER_ACCOUNT_ID, MOCK_TRANSACTION_VIOLATIONS, isReportArchived.current, ) ?? {}; @@ -169,6 +177,7 @@ describe('SidebarUtils', () => { reportErrors, MOCK_TRANSACTIONS, false, + CURRENT_USER_ACCOUNT_ID, MOCK_TRANSACTION_VIOLATIONS, isReportArchived.current, ) ?? {}; @@ -195,6 +204,7 @@ describe('SidebarUtils', () => { {}, MOCK_TRANSACTIONS, false, + CURRENT_USER_ACCOUNT_ID, MOCK_TRANSACTION_VIOLATIONS, isReportArchived.current, ) ?? {}; @@ -238,6 +248,7 @@ describe('SidebarUtils', () => { reportErrors, MOCK_TRANSACTIONS, false, + CURRENT_USER_ACCOUNT_ID, MOCK_TRANSACTION_VIOLATIONS, isReportArchived.current, ) ?? {}; @@ -269,6 +280,7 @@ describe('SidebarUtils', () => { reportErrors, MOCK_TRANSACTIONS, false, + CURRENT_USER_ACCOUNT_ID, MOCK_TRANSACTION_VIOLATIONS, isReportArchived.current, ) ?? {}; @@ -312,6 +324,7 @@ describe('SidebarUtils', () => { reportErrors, MOCK_TRANSACTIONS, false, + CURRENT_USER_ACCOUNT_ID, MOCK_TRANSACTION_VIOLATIONS, isReportArchived.current, ) ?? {}; @@ -337,6 +350,7 @@ describe('SidebarUtils', () => { {}, MOCK_TRANSACTIONS, false, + CURRENT_USER_ACCOUNT_ID, MOCK_TRANSACTION_VIOLATIONS, isReportArchived.current, ); @@ -449,6 +463,7 @@ describe('SidebarUtils', () => { {}, MOCK_TRANSACTIONS, false, + CURRENT_USER_ACCOUNT_ID, MOCK_TRANSACTION_VIOLATIONS, isReportArchived.current, ); @@ -537,6 +552,7 @@ describe('SidebarUtils', () => { reportErrors, MOCK_TRANSACTIONS, false, + CURRENT_USER_ACCOUNT_ID, MOCK_TRANSACTION_VIOLATIONS, isReportArchived.current, ); @@ -615,6 +631,7 @@ describe('SidebarUtils', () => { {}, MOCK_TRANSACTIONS, false, + CURRENT_USER_ACCOUNT_ID, MOCK_TRANSACTION_VIOLATIONS, isReportArchived.current, ); @@ -708,6 +725,7 @@ describe('SidebarUtils', () => { {}, {[transactionKey]: transaction}, false, + CURRENT_USER_ACCOUNT_ID, transactionViolations, false, ) ?? {}; @@ -773,7 +791,18 @@ describe('SidebarUtils', () => { }; // When: called with isOffline=false — the pending-delete action is skipped, leaving the live one as the single thread. - const onlineResult = SidebarUtils.getReasonAndReportActionThatHasRedBrickRoad(MOCK_REPORT, MOCK_CHAT_REPORT, MOCK_REPORT_ACTIONS, false, {}, MOCK_TRANSACTIONS, false, {}, false); + const onlineResult = SidebarUtils.getReasonAndReportActionThatHasRedBrickRoad( + MOCK_REPORT, + MOCK_CHAT_REPORT, + MOCK_REPORT_ACTIONS, + false, + {}, + MOCK_TRANSACTIONS, + false, + CURRENT_USER_ACCOUNT_ID, + {}, + false, + ); expect(onlineResult?.reason).toBe(CONST.RBR_REASONS.HAS_ERRORS); }); @@ -835,11 +864,118 @@ describe('SidebarUtils', () => { }; // When: called with isOffline=true — the pending-delete action is included, making 2 IOU actions. - const offlineResult = SidebarUtils.getReasonAndReportActionThatHasRedBrickRoad(MOCK_REPORT, MOCK_CHAT_REPORT, MOCK_REPORT_ACTIONS, false, {}, MOCK_TRANSACTIONS, true, {}, false); + const offlineResult = SidebarUtils.getReasonAndReportActionThatHasRedBrickRoad( + MOCK_REPORT, + MOCK_CHAT_REPORT, + MOCK_REPORT_ACTIONS, + false, + {}, + MOCK_TRANSACTIONS, + true, + CURRENT_USER_ACCOUNT_ID, + {}, + false, + ); // Then: no single transaction thread is identified, so the receipt error is not surfaced via that path. expect(offlineResult).toBeNull(); }); + + it('forwards the passed currentUserAccountID to the smartscan detection instead of the module-level account ID', async () => { + // Given: an IOU report (owned by actorAccountID) that has a money-request action whose linked transaction is missing smartscan fields, + // surfaced in the chat report through a report-preview action. The smartscan RBR only applies to actions taken by the current user, + // so the reported action depends on which currentUserAccountID is passed in. + const actorAccountID = 777; + const chatReportID = '9001'; + const iouReportID = '9002'; + const transactionID = '9003'; + const iouActionID = '9004'; + + const chatReport: Report = {...LHNTestUtils.getFakeReport(), reportID: chatReportID, type: CONST.REPORT.TYPE.CHAT}; + const iouReport: Report = { + ...LHNTestUtils.getFakeReport(), + reportID: iouReportID, + type: CONST.REPORT.TYPE.IOU, + parentReportID: chatReportID, + ownerAccountID: actorAccountID, + stateNum: CONST.REPORT.STATE_NUM.OPEN, + statusNum: CONST.REPORT.STATUS_NUM.OPEN, + }; + const transaction: Transaction = { + ...createRandomTransaction(Number(transactionID)), + transactionID, + reportID: iouReportID, + amount: 0, + merchant: '', + modifiedMerchant: '', + }; + const iouAction: ReportAction = { + ...createRandomReportAction(Number(iouActionID)), + reportActionID: iouActionID, + reportID: iouReportID, + actionName: CONST.REPORT.ACTIONS.TYPE.IOU, + actorAccountID, + originalMessage: { + IOUTransactionID: transactionID, + type: CONST.IOU.REPORT_ACTION_TYPE.CREATE, + amount: 0, + currency: CONST.CURRENCY.USD, + comment: '', + participantAccountIDs: [actorAccountID], + }, + }; + const reportPreviewAction = buildOptimisticReportPreview(chatReport, iouReport, '', transaction); + + await act(async () => { + await Onyx.set(ONYXKEYS.SESSION, {accountID: 55555}); + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${chatReportID}` as const, chatReport); + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${iouReportID}` as const, iouReport); + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReportID}` as const, {[iouActionID]: iouAction}); + await Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}` as const, transaction); + }); + await waitForBatchedUpdatesWithAct(); + + const reportActions: ReportActions = {[reportPreviewAction.reportActionID]: reportPreviewAction}; + const transactions: OnyxCollection = {[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]: transaction}; + const reports: OnyxCollection = {[`${ONYXKEYS.COLLECTION.REPORT}${iouReportID}`]: iouReport}; + // Pre-populated non-empty errors so the HAS_ERRORS branch is taken in both calls; + // only the returned reportAction depends on the smartscan detection (and thus on currentUserAccountID). + const reportErrors = {someError: 'Some error occurred'}; + + // When: the passed currentUserAccountID matches the action's actor, the smartscan action is flagged as requiring attention. + const matchingResult = SidebarUtils.getReasonAndReportActionThatHasRedBrickRoad( + chatReport, + chatReport, + reportActions, + false, + reportErrors, + transactions, + false, + actorAccountID, + {}, + false, + reports, + ); + expect(matchingResult?.reason).toBe(CONST.RBR_REASONS.HAS_ERRORS); + expect(matchingResult?.reportAction?.reportActionID).toBe(reportPreviewAction.reportActionID); + + // When: the passed currentUserAccountID does NOT match, the action is not attributed to the current user, so no smartscan action is returned. + const nonMatchingResult = SidebarUtils.getReasonAndReportActionThatHasRedBrickRoad( + chatReport, + chatReport, + reportActions, + false, + reportErrors, + transactions, + false, + actorAccountID + 1, + {}, + false, + reports, + ); + expect(nonMatchingResult?.reason).toBe(CONST.RBR_REASONS.HAS_ERRORS); + expect(nonMatchingResult?.reportAction).toBeUndefined(); + }); }); describe('shouldDisplayReportInLHN', () => {