diff --git a/src/components/Badge.tsx b/src/components/Badge.tsx
index 855f2805908e8..cca795cfa5e53 100644
--- a/src/components/Badge.tsx
+++ b/src/components/Badge.tsx
@@ -92,6 +92,10 @@ function Badge({
[styles.defaultBadge, styles.condensedBadge, styles.alignSelfCenter, styles.ml2, StyleUtils, success, error, environment, badgeStyles, isCondensed, isStrong],
);
+ if (!text && !icon) {
+ return null;
+ }
+
return (
{!!icon && (
-
+
)}
-
- {text}
-
+ {!!text && (
+
+ {text}
+
+ )}
);
}
diff --git a/src/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx
index 1165c5da5e505..378d98f906fca 100644
--- a/src/components/LHNOptionsList/OptionRowLHN.tsx
+++ b/src/components/LHNOptionsList/OptionRowLHN.tsx
@@ -450,7 +450,7 @@ function OptionRowLHN({
) : (
, policy: OnyxEntry,
isTimeRequest = false,
} = config;
const topmostReportId = Navigation.getTopmostReportId();
- const doesReportHaveViolations = shouldDisplayViolationsRBRInLHN(option.item, transactionViolations);
+ const doesReportHaveViolations = !!getViolatingReportIDForRBRInLHN(option.item, transactionViolations);
const shouldBeInOptionList = shouldReportBeInOptionList({
report: option.item,
diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts
index 6a63dc83c6e87..2bdc525869335 100644
--- a/src/libs/ReportUtils.ts
+++ b/src/libs/ReportUtils.ts
@@ -846,6 +846,7 @@ type OptionData = {
allReportErrors?: Errors;
brickRoadIndicator?: ValueOf | '' | null;
actionBadge?: ValueOf;
+ actionTargetReportActionID?: string;
tooltipText?: string | null;
alternateTextMaxLines?: number;
boldStyle?: boolean;
@@ -9091,67 +9092,51 @@ function shouldHideReport(
}
/**
- * Should we display a RBR on the LHN on this report due to violations?
+ * Returns the reportID of the first child expense report that has violations under the same policy,
+ * or undefined if none found. Used to find the REPORT_PREVIEW action to deep-link to.
*/
-function shouldDisplayViolationsRBRInLHN(report: OnyxEntry, transactionViolations: OnyxCollection): boolean {
+function getViolatingReportIDForRBRInLHN(report: OnyxEntry, transactionViolations: OnyxCollection): string | null {
// We only show the RBR in the highest level, which is the expense chat
if (!report || !isPolicyExpenseChat(report)) {
- return false;
+ return null;
}
// We only show the RBR to the submitter
if (!isCurrentUserSubmitter(report)) {
- return false;
+ return null;
}
if (!report.policyID || !reportsByPolicyID) {
- return false;
+ return null;
}
// If any report has a violation, then it should have a RBR
const potentialReports = Object.values(reportsByPolicyID[report.policyID] ?? {}) ?? [];
- return potentialReports.some((potentialReport) => {
- if (!potentialReport) {
- return false;
- }
- const policy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${potentialReport.policyID}`];
- const transactions = getReportTransactions(potentialReport.reportID);
+ const violatingReport = potentialReports
+ // eslint-disable-next-line rulesdir/prefer-locale-compare-from-context
+ .sort((a, b) => (a?.created ?? '').localeCompare(b?.created ?? ''))
+ .find((potentialReport) => {
+ if (!potentialReport) {
+ return false;
+ }
+ const policy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${potentialReport.policyID}`];
+ const transactions = getReportTransactions(potentialReport.reportID);
- // Allow both open and processing reports to show RBR for violations
- if (!isOpenOrProcessingReport(potentialReport)) {
- return false;
- }
+ // Allow both open and processing reports to show RBR for violations
+ if (!isOpenOrProcessingReport(potentialReport)) {
+ return false;
+ }
- return (
- !isInvoiceReport(potentialReport) &&
- ViolationsUtils.hasVisibleViolationsForUser(
- potentialReport,
- transactionViolations,
- currentUserEmail ?? '',
- currentUserAccountID ?? CONST.DEFAULT_NUMBER_ID,
- policy,
- transactions,
- ) &&
- (hasViolations(
- potentialReport.reportID,
- transactionViolations,
- currentUserAccountID ?? CONST.DEFAULT_NUMBER_ID,
- currentUserEmail ?? '',
- true,
- transactions,
- potentialReport,
- policy,
- ) ||
- hasWarningTypeViolations(
- potentialReport.reportID,
+ return (
+ !isInvoiceReport(potentialReport) &&
+ ViolationsUtils.hasVisibleViolationsForUser(
+ potentialReport,
transactionViolations,
- currentUserAccountID ?? CONST.DEFAULT_NUMBER_ID,
currentUserEmail ?? '',
- true,
- transactions,
- potentialReport,
+ currentUserAccountID ?? CONST.DEFAULT_NUMBER_ID,
policy,
- ) ||
- hasNoticeTypeViolations(
+ transactions,
+ ) &&
+ (hasViolations(
potentialReport.reportID,
transactionViolations,
currentUserAccountID ?? CONST.DEFAULT_NUMBER_ID,
@@ -9160,9 +9145,30 @@ function shouldDisplayViolationsRBRInLHN(report: OnyxEntry, transactionV
transactions,
potentialReport,
policy,
- ))
- );
- });
+ ) ||
+ hasWarningTypeViolations(
+ potentialReport.reportID,
+ transactionViolations,
+ currentUserAccountID ?? CONST.DEFAULT_NUMBER_ID,
+ currentUserEmail ?? '',
+ true,
+ transactions,
+ potentialReport,
+ policy,
+ ) ||
+ hasNoticeTypeViolations(
+ potentialReport.reportID,
+ transactionViolations,
+ currentUserAccountID ?? CONST.DEFAULT_NUMBER_ID,
+ currentUserEmail ?? '',
+ true,
+ transactions,
+ potentialReport,
+ policy,
+ ))
+ );
+ });
+ return violatingReport ? violatingReport.reportID : null;
}
/**
@@ -9393,7 +9399,7 @@ function hasReportErrorsOtherThanFailedReceipt(
let doesTransactionThreadReportHasViolations = false;
if (oneTransactionThreadReportID) {
const transactionReport = getReport(oneTransactionThreadReportID, allReports);
- doesTransactionThreadReportHasViolations = !!transactionReport && shouldDisplayViolationsRBRInLHN(transactionReport, transactionViolations);
+ doesTransactionThreadReportHasViolations = !!transactionReport && !!getViolatingReportIDForRBRInLHN(transactionReport, transactionViolations);
}
return (
doesTransactionThreadReportHasViolations ||
@@ -12741,16 +12747,17 @@ function generateReportAttributes({
transactionViolations: OnyxCollection;
isReportArchived: boolean;
actionBadge?: ValueOf;
+ actionTargetReportActionID?: string;
}) {
const reportActionsList = reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report?.reportID}`];
const parentReportActionsList = reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report?.parentReportID}`];
- const hasViolationsToDisplayInLHN = shouldDisplayViolationsRBRInLHN(report, transactionViolations);
+ const hasViolationsToDisplayInLHN = !!getViolatingReportIDForRBRInLHN(report, transactionViolations);
const hasAnyTypeOfViolations = hasViolationsToDisplayInLHN;
const reportErrors = getAllReportErrors(report, reportActionsList, isReportArchived);
const hasErrors = Object.entries(reportErrors ?? {}).length > 0;
const oneTransactionThreadReportID = getOneTransactionThreadReportID(report, chatReport, reportActionsList);
const parentReportAction = report?.parentReportActionID ? parentReportActionsList?.[report.parentReportActionID] : undefined;
- const {reason, actionBadge} = getReasonAndReportActionThatRequiresAttention(report, parentReportAction, isReportArchived) ?? {};
+ const {reason, actionBadge, reportAction} = getReasonAndReportActionThatRequiresAttention(report, parentReportAction, isReportArchived) ?? {};
return {
hasViolationsToDisplayInLHN,
@@ -12761,6 +12768,7 @@ function generateReportAttributes({
parentReportAction,
requiresAttention: !!reason,
actionBadge,
+ actionTargetReportActionID: reportAction?.reportActionID,
};
}
@@ -13273,6 +13281,7 @@ export {
getDisplayedReportID,
getTransactionsWithReceipts,
getUserDetailTooltipText,
+ getViolatingReportIDForRBRInLHN,
getWhisperDisplayNames,
getWorkspaceChats,
getWorkspaceIcon,
@@ -13413,7 +13422,6 @@ export {
shouldDisableRename,
shouldDisableThread,
shouldDisplayThreadReplies,
- shouldDisplayViolationsRBRInLHN,
shouldReportBeInOptionList,
shouldReportShowSubscript,
shouldShowFlagComment,
diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts
index b45ca22a91824..9dcb53dbb4153 100644
--- a/src/libs/SidebarUtils.ts
+++ b/src/libs/SidebarUtils.ts
@@ -56,6 +56,7 @@ import {
getIntegrationSyncFailedMessage,
getInvoiceCompanyNameUpdateMessage,
getInvoiceCompanyWebsiteUpdateMessage,
+ getIOUReportIDFromReportActionPreview,
getLastVisibleMessage,
getMessageOfOldDotReportAction,
getOriginalMessage,
@@ -151,6 +152,7 @@ import {
getReportParticipantsTitle,
getReportSubtitlePrefix,
getUnreportedTransactionMessage,
+ getViolatingReportIDForRBRInLHN,
getWorkspaceNameUpdatedMessage,
hasReportErrorsOtherThanFailedReceipt,
isAdminRoom,
@@ -182,7 +184,6 @@ import {
isUnread,
isUnreadWithMention,
isWorkspaceTaskReport,
- shouldDisplayViolationsRBRInLHN,
shouldReportBeInOptionList,
shouldReportShowSubscript,
} from './ReportUtils';
@@ -246,7 +247,7 @@ function shouldDisplayReportInLHN(
// Get report metadata and status
const parentReportAction = getReportAction(report?.parentReportID, report?.parentReportActionID);
- const doesReportHaveViolations = shouldDisplayViolationsRBRInLHN(report, transactionViolations);
+ const doesReportHaveViolations = !!getViolatingReportIDForRBRInLHN(report, transactionViolations);
const isHidden = isHiddenForCurrentUser(report);
const isFocused = report.reportID === currentReportId;
const chatReport = reports?.[`${ONYXKEYS.COLLECTION.REPORT}${report?.chatReportID}`];
@@ -627,20 +628,23 @@ function getReasonAndReportActionThatHasRedBrickRoad(
transactionViolations?: OnyxCollection,
isReportArchived = false,
): ReasonAndReportActionThatHasRedBrickRoad | null {
- const {reportAction} = getAllReportActionsErrorsAndReportActionThatRequiresAttention(report, reportActions, isReportArchived);
- const errors = reportErrors;
- const hasErrors = Object.keys(errors).length !== 0;
-
if (isReportArchived) {
return null;
}
- if (shouldDisplayViolationsRBRInLHN(report, transactionViolations)) {
+ const violatingReportID = getViolatingReportIDForRBRInLHN(report, transactionViolations);
+ if (violatingReportID) {
+ const reportPreviewAction = Object.values(reportActions ?? {}).find((action) => getIOUReportIDFromReportActionPreview(action) === violatingReportID);
return {
reason: CONST.RBR_REASONS.HAS_TRANSACTION_THREAD_VIOLATIONS,
+ reportAction: reportPreviewAction,
};
}
+ const {reportAction} = getAllReportActionsErrorsAndReportActionThatRequiresAttention(report, reportActions, isReportArchived);
+ const errors = reportErrors;
+ const hasErrors = Object.keys(errors).length !== 0;
+
if (hasErrors) {
return {
reason: CONST.RBR_REASONS.HAS_ERRORS,
@@ -657,19 +661,6 @@ function getReasonAndReportActionThatHasRedBrickRoad(
return getReceiptUploadErrorReason(report, chatReport, reportActions, transactions);
}
-function shouldShowRedBrickRoad(
- report: Report,
- chatReport: OnyxEntry,
- reportActions: OnyxEntry,
- hasViolations: boolean,
- reportErrors: Errors,
- transactions: OnyxCollection,
- transactionViolations?: OnyxCollection,
- isReportArchived = false,
-) {
- return !!getReasonAndReportActionThatHasRedBrickRoad(report, chatReport, reportActions, hasViolations, reportErrors, transactions, transactionViolations, isReportArchived);
-}
-
/**
* Gets all the data necessary for rendering an OptionRowLHN component
*/
@@ -792,6 +783,7 @@ function getOptionData({
result.pendingAction = report.pendingFields?.addWorkspaceRoom ?? report.pendingFields?.createChat;
result.brickRoadIndicator = reportAttributes?.brickRoadStatus;
result.actionBadge = reportAttributes?.actionBadge;
+ result.actionTargetReportActionID = reportAttributes?.actionTargetReportActionID;
result.ownerAccountID = report.ownerAccountID;
result.managerID = report.managerID;
result.reportID = report.reportID;
@@ -1360,7 +1352,6 @@ export default {
combineReportCategories,
getWelcomeMessage,
getReasonAndReportActionThatHasRedBrickRoad,
- shouldShowRedBrickRoad,
getReportsToDisplayInLHN,
updateReportsToDisplayInLHN,
shouldDisplayReportInLHN,
diff --git a/src/libs/actions/OnyxDerived/configs/reportAttributes.ts b/src/libs/actions/OnyxDerived/configs/reportAttributes.ts
index 2360aff7e04b3..bbf477c001c58 100644
--- a/src/libs/actions/OnyxDerived/configs/reportAttributes.ts
+++ b/src/libs/actions/OnyxDerived/configs/reportAttributes.ts
@@ -209,6 +209,7 @@ export default createOnyxDerivedValueConfig({
reportErrors,
oneTransactionThreadReportID,
actionBadge: actionGreenBadge,
+ actionTargetReportActionID: actionGreenTargetReportActionID,
} = generateReportAttributes({
report,
chatReport,
@@ -219,15 +220,28 @@ export default createOnyxDerivedValueConfig({
let brickRoadStatus;
let actionBadge;
+ let actionTargetReportActionID;
+ const reasonAndReportAction = SidebarUtils.getReasonAndReportActionThatHasRedBrickRoad(
+ report,
+ chatReport,
+ reportActionsList,
+ hasAnyViolations,
+ reportErrors,
+ transactions,
+ transactionViolations,
+ !!isReportArchived,
+ );
// if report has errors or violations, show red dot
- if (SidebarUtils.shouldShowRedBrickRoad(report, chatReport, reportActionsList, hasAnyViolations, reportErrors, transactions, transactionViolations, !!isReportArchived)) {
+ if (reasonAndReportAction) {
brickRoadStatus = CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR;
actionBadge = CONST.REPORT.ACTION_BADGE.FIX;
+ actionTargetReportActionID = reasonAndReportAction.reportAction?.reportActionID;
}
// if report does not have error, check if it should show green dot
if (brickRoadStatus !== CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR && requiresAttention) {
brickRoadStatus = CONST.BRICK_ROAD_INDICATOR_STATUS.INFO;
actionBadge = actionGreenBadge;
+ actionTargetReportActionID = actionGreenTargetReportActionID;
}
acc[report.reportID] = {
@@ -249,6 +263,7 @@ export default createOnyxDerivedValueConfig({
brickRoadStatus,
requiresAttention,
actionBadge,
+ actionTargetReportActionID,
reportErrors,
oneTransactionThreadReportID,
};
diff --git a/src/pages/Debug/Report/DebugReportPage.tsx b/src/pages/Debug/Report/DebugReportPage.tsx
index 2ba3169c34bb2..1c16568608515 100644
--- a/src/pages/Debug/Report/DebugReportPage.tsx
+++ b/src/pages/Debug/Report/DebugReportPage.tsx
@@ -21,7 +21,7 @@ import DebugTabNavigator from '@libs/Navigation/DebugTabNavigator';
import Navigation from '@libs/Navigation/Navigation';
import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';
import type {DebugParamList} from '@libs/Navigation/types';
-import {hasReportViolations, isReportOwner, shouldDisplayViolationsRBRInLHN} from '@libs/ReportUtils';
+import {getViolatingReportIDForRBRInLHN, hasReportViolations, isReportOwner} from '@libs/ReportUtils';
import DebugDetails from '@pages/Debug/DebugDetails';
import DebugJSON from '@pages/Debug/DebugJSON';
import NotFoundPage from '@pages/ErrorPage/NotFoundPage';
@@ -82,7 +82,7 @@ function DebugReportPage({
return [];
}
- const shouldDisplayViolations = shouldDisplayViolationsRBRInLHN(report, transactionViolations);
+ const shouldDisplayViolations = !!getViolatingReportIDForRBRInLHN(report, transactionViolations);
const shouldDisplayReportViolations = isReportOwner(report) && hasReportViolations(reportViolations);
const hasViolations = !!shouldDisplayViolations || shouldDisplayReportViolations;
const {reason: reasonGBR, reportAction: reportActionGBR} = DebugUtils.getReasonAndReportActionForGBRInLHNRow(report, isReportArchived) ?? {};
diff --git a/src/pages/inbox/sidebar/SidebarLinks.tsx b/src/pages/inbox/sidebar/SidebarLinks.tsx
index 4c1bdc22ff6be..d32c65ff60a45 100644
--- a/src/pages/inbox/sidebar/SidebarLinks.tsx
+++ b/src/pages/inbox/sidebar/SidebarLinks.tsx
@@ -6,12 +6,14 @@ import type {ValueOf} from 'type-fest';
import LHNOptionsList from '@components/LHNOptionsList/LHNOptionsList';
import OptionsListSkeletonView from '@components/OptionsListSkeletonView';
import useConfirmReadyToOpenApp from '@hooks/useConfirmReadyToOpenApp';
+import useEnvironment from '@hooks/useEnvironment';
import useOnyx from '@hooks/useOnyx';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useStyleUtils from '@hooks/useStyleUtils';
import useThemeStyles from '@hooks/useThemeStyles';
import {setSidebarLoaded} from '@libs/actions/App';
import Navigation from '@libs/Navigation/Navigation';
+import type {OptionData} from '@libs/ReportUtils';
import {cancelSpan} from '@libs/telemetry/activeSpans';
import type {SkeletonSpanReasonAttributes} from '@libs/telemetry/useSkeletonSpan';
import * as ReportActionContextMenu from '@pages/inbox/report/ContextMenu/ReportActionContextMenu';
@@ -35,6 +37,7 @@ type SidebarLinksProps = {
};
function SidebarLinks({insets, optionListItems, priorityMode = CONST.PRIORITY_MODE.DEFAULT, isActiveReport}: SidebarLinksProps) {
+ const {isProduction} = useEnvironment();
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
const {shouldUseNarrowLayout} = useResponsiveLayout();
@@ -51,7 +54,7 @@ function SidebarLinks({insets, optionListItems, priorityMode = CONST.PRIORITY_MO
* Show Report page with selected report id
*/
const showReportPage = useCallback(
- (option: Report) => {
+ (option: Report & Pick) => {
// Prevent opening Report page when clicking LHN row quickly after clicking FAB icon
// or when clicking the active LHN row on large screens
// or when continuously clicking different LHNs, only apply to small screen
@@ -70,9 +73,9 @@ function SidebarLinks({insets, optionListItems, priorityMode = CONST.PRIORITY_MO
cancelSpan(`${CONST.TELEMETRY.SPAN_OPEN_REPORT}_${option.reportID}`);
return;
}
- Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(option.reportID));
+ Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(option.reportID, isProduction ? undefined : option.actionTargetReportActionID));
},
- [shouldUseNarrowLayout, isActiveReport],
+ [shouldUseNarrowLayout, isActiveReport, isProduction],
);
const viewMode = priorityMode === CONST.PRIORITY_MODE.GSD ? CONST.OPTION_MODE.COMPACT : CONST.OPTION_MODE.DEFAULT;
diff --git a/src/types/onyx/DerivedValues.ts b/src/types/onyx/DerivedValues.ts
index 379cf0ce8a336..80ed1e688bc04 100644
--- a/src/types/onyx/DerivedValues.ts
+++ b/src/types/onyx/DerivedValues.ts
@@ -33,6 +33,10 @@ type ReportAttributes = {
* The action badge to display instead of the GBR/RBR dot (e.g. 'submit', 'approve', 'pay').
*/
actionBadge?: ValueOf;
+ /**
+ * The reportActionID that the action badge refers to, used for deep linking when the LHN row is pressed.
+ */
+ actionTargetReportActionID?: string;
/**
* The errors of the report.
*/
diff --git a/tests/unit/ReportUtilsTest.ts b/tests/unit/ReportUtilsTest.ts
index fd0ca838069f2..500c09a408e61 100644
--- a/tests/unit/ReportUtilsTest.ts
+++ b/tests/unit/ReportUtilsTest.ts
@@ -96,6 +96,7 @@ import {
getReportPreviewMessage,
getReportStatusTranslation,
getReportSubtitlePrefix,
+ getViolatingReportIDForRBRInLHN,
getWorkspaceIcon,
getWorkspaceNameUpdatedMessage,
hasActionWithErrorsForTransaction,
@@ -124,7 +125,6 @@ import {
shouldBlockSubmitDueToStrictPolicyRules,
shouldDisableRename,
shouldDisableThread,
- shouldDisplayViolationsRBRInLHN,
shouldEnableNegative,
shouldExcludeAncestorReportAction,
shouldHideSingleReportField,
@@ -11159,6 +11159,240 @@ describe('ReportUtils', () => {
});
});
+ describe('getViolatingReportIDForRBRInLHN', () => {
+ it('should return null for a non-policy-expense-chat report', async () => {
+ await Onyx.clear();
+
+ const regularChat: Report = {
+ ...createRegularChat(800, []),
+ ownerAccountID: currentUserAccountID,
+ policyID: 'policy-non-pec',
+ };
+
+ await Onyx.merge(ONYXKEYS.SESSION, {accountID: currentUserAccountID, email: currentUserEmail});
+ await waitForBatchedUpdates();
+
+ const result = getViolatingReportIDForRBRInLHN(regularChat, {});
+ expect(result).toBeNull();
+
+ await Onyx.clear();
+ });
+
+ it('should return null when current user is not the submitter of the policy expense chat', async () => {
+ await Onyx.clear();
+
+ const otherAccountID = 999;
+ const chatReport: Report = {
+ ...createPolicyExpenseChat(801, false),
+ ownerAccountID: otherAccountID,
+ policyID: 'policy-not-submitter',
+ };
+
+ await Onyx.merge(ONYXKEYS.SESSION, {accountID: currentUserAccountID, email: currentUserEmail});
+ await waitForBatchedUpdates();
+
+ const result = getViolatingReportIDForRBRInLHN(chatReport, {});
+ expect(result).toBeNull();
+
+ await Onyx.clear();
+ });
+
+ it('should return null when policy expense chat has no policyID', async () => {
+ await Onyx.clear();
+
+ const chatReport: Report = {
+ ...createPolicyExpenseChat(802),
+ ownerAccountID: currentUserAccountID,
+ policyID: undefined,
+ };
+
+ await Onyx.merge(ONYXKEYS.SESSION, {accountID: currentUserAccountID, email: currentUserEmail});
+ await waitForBatchedUpdates();
+
+ const result = getViolatingReportIDForRBRInLHN(chatReport, {});
+ expect(result).toBeNull();
+
+ await Onyx.clear();
+ });
+
+ it('should return the violating report ID for an open expense report with violations', async () => {
+ await Onyx.clear();
+
+ const policyID = 'policy-rbr-positive';
+ const chatReportID = 'chat-rbr-positive';
+ const expenseReportID = 'expense-rbr-positive';
+ const transactionID = 'transaction-rbr-positive';
+
+ const policyData: Policy = {
+ id: policyID,
+ name: 'RBR Positive Test Workspace',
+ type: CONST.POLICY.TYPE.TEAM,
+ role: CONST.POLICY.ROLE.ADMIN,
+ outputCurrency: CONST.CURRENCY.USD,
+ reimbursementChoice: CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_YES,
+ approvalMode: CONST.POLICY.APPROVAL_MODE.BASIC,
+ employeeList: {
+ [currentUserEmail]: {
+ role: CONST.POLICY.ROLE.ADMIN,
+ },
+ },
+ owner: currentUserEmail,
+ isPolicyExpenseChatEnabled: true,
+ };
+
+ const chatReport: Report = {
+ ...createPolicyExpenseChat(803),
+ reportID: chatReportID,
+ ownerAccountID: currentUserAccountID,
+ policyID,
+ iouReportID: expenseReportID,
+ hasOutstandingChildRequest: true,
+ };
+
+ const expenseReport: Report = {
+ ...createExpenseReport(804),
+ reportID: expenseReportID,
+ chatReportID,
+ ownerAccountID: currentUserAccountID,
+ managerID: 42,
+ policyID,
+ type: CONST.REPORT.TYPE.EXPENSE,
+ currency: CONST.CURRENCY.USD,
+ total: 5000,
+ stateNum: CONST.REPORT.STATE_NUM.OPEN,
+ statusNum: CONST.REPORT.STATUS_NUM.OPEN,
+ };
+
+ const baseTransaction = createRandomTransaction(803);
+ const transaction: Transaction = {
+ ...baseTransaction,
+ transactionID,
+ reportID: expenseReportID,
+ amount: 5000,
+ currency: CONST.CURRENCY.USD,
+ status: CONST.TRANSACTION.STATUS.POSTED,
+ reimbursable: true,
+ };
+
+ const transactionViolationsKey = `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}` as OnyxKey;
+ const transactionViolationsCollection: OnyxCollection = {
+ [transactionViolationsKey]: [
+ {
+ name: CONST.VIOLATIONS.MISSING_CATEGORY,
+ type: CONST.VIOLATION_TYPES.VIOLATION,
+ showInReview: true,
+ },
+ ],
+ };
+
+ await Onyx.merge(ONYXKEYS.SESSION, {accountID: currentUserAccountID, email: currentUserEmail});
+ await waitForBatchedUpdates();
+
+ await Promise.all([
+ Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, policyData),
+ Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`, chatReport),
+ Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${expenseReport.reportID}`, expenseReport),
+ Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`, transaction),
+ Onyx.merge(transactionViolationsKey, transactionViolationsCollection[transactionViolationsKey]),
+ ]);
+ await waitForBatchedUpdates();
+
+ const result = getViolatingReportIDForRBRInLHN(chatReport, transactionViolationsCollection);
+ expect(result).toBe(expenseReportID);
+
+ await Onyx.clear();
+ });
+
+ it('should return null when all expense reports in the policy are closed', async () => {
+ await Onyx.clear();
+
+ const policyID = 'policy-rbr-closed';
+ const chatReportID = 'chat-rbr-closed';
+ const expenseReportID = 'expense-rbr-closed';
+ const transactionID = 'transaction-rbr-closed';
+
+ const policyData: Policy = {
+ id: policyID,
+ name: 'RBR Closed Test Workspace',
+ type: CONST.POLICY.TYPE.TEAM,
+ role: CONST.POLICY.ROLE.ADMIN,
+ outputCurrency: CONST.CURRENCY.USD,
+ reimbursementChoice: CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_YES,
+ approvalMode: CONST.POLICY.APPROVAL_MODE.BASIC,
+ employeeList: {
+ [currentUserEmail]: {
+ role: CONST.POLICY.ROLE.ADMIN,
+ },
+ },
+ owner: currentUserEmail,
+ isPolicyExpenseChatEnabled: true,
+ };
+
+ const chatReport: Report = {
+ ...createPolicyExpenseChat(805),
+ reportID: chatReportID,
+ ownerAccountID: currentUserAccountID,
+ policyID,
+ iouReportID: expenseReportID,
+ };
+
+ // Closed/approved report — stateNum > 1, so it won't be in reportsByPolicyID
+ // and won't pass isOpenOrProcessingReport
+ const expenseReport: Report = {
+ ...createExpenseReport(806),
+ reportID: expenseReportID,
+ chatReportID,
+ ownerAccountID: currentUserAccountID,
+ managerID: 42,
+ policyID,
+ type: CONST.REPORT.TYPE.EXPENSE,
+ currency: CONST.CURRENCY.USD,
+ total: 5000,
+ stateNum: CONST.REPORT.STATE_NUM.APPROVED,
+ statusNum: CONST.REPORT.STATUS_NUM.APPROVED,
+ };
+
+ const baseTransaction = createRandomTransaction(805);
+ const transaction: Transaction = {
+ ...baseTransaction,
+ transactionID,
+ reportID: expenseReportID,
+ amount: 5000,
+ currency: CONST.CURRENCY.USD,
+ status: CONST.TRANSACTION.STATUS.POSTED,
+ reimbursable: true,
+ };
+
+ const transactionViolationsKey = `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}` as OnyxKey;
+ const transactionViolationsCollection: OnyxCollection = {
+ [transactionViolationsKey]: [
+ {
+ name: CONST.VIOLATIONS.MISSING_CATEGORY,
+ type: CONST.VIOLATION_TYPES.VIOLATION,
+ showInReview: true,
+ },
+ ],
+ };
+
+ await Onyx.merge(ONYXKEYS.SESSION, {accountID: currentUserAccountID, email: currentUserEmail});
+ await waitForBatchedUpdates();
+
+ await Promise.all([
+ Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, policyData),
+ Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`, chatReport),
+ Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${expenseReport.reportID}`, expenseReport),
+ Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`, transaction),
+ Onyx.merge(transactionViolationsKey, transactionViolationsCollection[transactionViolationsKey]),
+ ]);
+ await waitForBatchedUpdates();
+
+ const result = getViolatingReportIDForRBRInLHN(chatReport, transactionViolationsCollection);
+ expect(result).toBeNull();
+
+ await Onyx.clear();
+ });
+ });
+
it('should surface a GBR for admin with held expenses requiring approval or payment and avoid showing an RBR', async () => {
await Onyx.clear();
@@ -11255,7 +11489,7 @@ describe('ReportUtils', () => {
]);
await waitForBatchedUpdates();
- const shouldShowRBR = shouldDisplayViolationsRBRInLHN(chatReport, transactionViolationsCollection);
+ const shouldShowRBR = !!getViolatingReportIDForRBRInLHN(chatReport, transactionViolationsCollection);
expect(shouldShowRBR).toBe(false);
const reason = reasonForReportToBeInOptionList({
@@ -11370,7 +11604,7 @@ describe('ReportUtils', () => {
]);
await waitForBatchedUpdates();
- const shouldShowRBR = shouldDisplayViolationsRBRInLHN(chatReport, transactionViolationsCollection);
+ const shouldShowRBR = !!getViolatingReportIDForRBRInLHN(chatReport, transactionViolationsCollection);
expect(shouldShowRBR).toBe(false);
await Onyx.clear();
});
diff --git a/tests/unit/SidebarUtilsTest.ts b/tests/unit/SidebarUtilsTest.ts
index 6f55a745dbd15..109efae48ecda 100644
--- a/tests/unit/SidebarUtilsTest.ts
+++ b/tests/unit/SidebarUtilsTest.ts
@@ -533,6 +533,166 @@ describe('SidebarUtils', () => {
await Onyx.clear();
});
+
+ it('returns correct reason when report has transaction thread notice type violation', async () => {
+ const MOCK_REPORT: Report = {
+ reportID: '1',
+ ownerAccountID: 12345,
+ chatType: CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT,
+ stateNum: CONST.REPORT.STATE_NUM.OPEN,
+ statusNum: CONST.REPORT.STATUS_NUM.OPEN,
+ policyID: '6',
+ };
+
+ const MOCK_REPORTS: ReportCollectionDataSet = {
+ [`${ONYXKEYS.COLLECTION.REPORT}${MOCK_REPORT.reportID}` as const]: MOCK_REPORT,
+ };
+
+ const MOCK_REPORT_ACTIONS: ReportActions = {
+ // eslint-disable-next-line @typescript-eslint/naming-convention
+ '1': {
+ reportActionID: '1',
+ actionName: CONST.REPORT.ACTIONS.TYPE.IOU,
+ actorAccountID: 12345,
+ created: '2024-08-08 18:20:44.171',
+ },
+ };
+
+ const MOCK_TRANSACTION = {
+ transactionID: '1',
+ amount: 10,
+ modifiedAmount: 10,
+ reportID: MOCK_REPORT.reportID,
+ };
+
+ const MOCK_TRANSACTIONS = {
+ [`${ONYXKEYS.COLLECTION.TRANSACTION}${MOCK_TRANSACTION.transactionID}` as const]: MOCK_TRANSACTION,
+ } as OnyxCollection;
+
+ const MOCK_TRANSACTION_VIOLATIONS: TransactionViolationsCollectionDataSet = {
+ [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${MOCK_TRANSACTION.transactionID}` as const]: [
+ {
+ type: CONST.VIOLATION_TYPES.NOTICE,
+ name: CONST.VIOLATIONS.MODIFIED_AMOUNT,
+ showInReview: true,
+ },
+ ],
+ };
+
+ await act(async () => {
+ await Onyx.multiSet({
+ ...MOCK_REPORTS,
+ ...MOCK_TRANSACTION_VIOLATIONS,
+ [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${MOCK_REPORT.reportID}` as const]: MOCK_REPORT_ACTIONS,
+ [ONYXKEYS.SESSION]: {
+ accountID: 12345,
+ },
+ [`${ONYXKEYS.COLLECTION.TRANSACTION}${MOCK_TRANSACTION.transactionID}` as const]: MOCK_TRANSACTION,
+ });
+ });
+
+ const {result: isReportArchived} = renderHook(() => useReportIsArchived(MOCK_REPORT?.reportID));
+ const result = SidebarUtils.getReasonAndReportActionThatHasRedBrickRoad(
+ MOCK_REPORT,
+ chatReportR14932,
+ MOCK_REPORT_ACTIONS,
+ false,
+ {},
+ MOCK_TRANSACTIONS,
+ MOCK_TRANSACTION_VIOLATIONS as OnyxCollection,
+ isReportArchived.current,
+ );
+
+ expect(result).not.toBeNull();
+ expect(result?.reason).toBe(CONST.RBR_REASONS.HAS_TRANSACTION_THREAD_VIOLATIONS);
+ });
+
+ it('returns correct reason when submitter has held expenses even if outstanding tasks trigger GBR', async () => {
+ const policyID = generateReportID();
+ const expenseChatID = generateReportID();
+ const expenseReportID = generateReportID();
+ const holdReportActionID = generateReportID();
+
+ const policyExpenseChat: Report = {
+ reportID: expenseChatID,
+ chatType: CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT,
+ type: CONST.REPORT.TYPE.CHAT,
+ ownerAccountID: 12345,
+ policyID,
+ hasOutstandingChildRequest: true,
+ stateNum: CONST.REPORT.STATE_NUM.OPEN,
+ statusNum: CONST.REPORT.STATUS_NUM.OPEN,
+ };
+
+ const expenseReport: Report = {
+ reportID: expenseReportID,
+ chatReportID: expenseChatID,
+ type: CONST.REPORT.TYPE.EXPENSE,
+ ownerAccountID: 12345,
+ managerID: 12345,
+ policyID,
+ stateNum: CONST.REPORT.STATE_NUM.OPEN,
+ statusNum: CONST.REPORT.STATUS_NUM.OPEN,
+ };
+
+ const baseTransaction = createRandomTransaction(700);
+ const transactionID = generateTransactionID();
+ const transaction: Transaction = {
+ ...baseTransaction,
+ transactionID,
+ reportID: expenseReport.reportID,
+ amount: 12345,
+ currency: CONST.CURRENCY.USD,
+ status: CONST.TRANSACTION.STATUS.POSTED,
+ comment: {
+ ...(baseTransaction.comment ?? {}),
+ hold: holdReportActionID,
+ },
+ };
+
+ const transactionKey = `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}` as const;
+ const transactionViolationsKey = `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}` as const;
+ const transactionViolations: OnyxCollection = {
+ [transactionViolationsKey]: [
+ {
+ name: CONST.VIOLATIONS.HOLD,
+ type: CONST.VIOLATION_TYPES.VIOLATION,
+ showInReview: true,
+ },
+ ],
+ };
+
+ await act(async () => {
+ await Onyx.multiSet({
+ [ONYXKEYS.SESSION]: {
+ accountID: 12345,
+ },
+ [`${ONYXKEYS.COLLECTION.REPORT}${policyExpenseChat.reportID}`]: policyExpenseChat,
+ [`${ONYXKEYS.COLLECTION.REPORT}${expenseReport.reportID}`]: expenseReport,
+ [transactionKey]: transaction,
+ [transactionViolationsKey]: transactionViolations[transactionViolationsKey],
+ } as unknown as OnyxMultiSetInput);
+ });
+
+ await waitForBatchedUpdatesWithAct();
+
+ const requiresAttention = getReasonAndReportActionThatRequiresAttention(policyExpenseChat);
+ expect(requiresAttention?.reason).toBe(CONST.REQUIRES_ATTENTION_REASONS.HAS_CHILD_REPORT_AWAITING_ACTION);
+
+ const {reason} =
+ SidebarUtils.getReasonAndReportActionThatHasRedBrickRoad(
+ policyExpenseChat,
+ policyExpenseChat,
+ {} as OnyxEntry,
+ true,
+ {},
+ {[transactionKey]: transaction},
+ transactionViolations,
+ false,
+ ) ?? {};
+
+ expect(reason).toBe(CONST.RBR_REASONS.HAS_TRANSACTION_THREAD_VIOLATIONS);
+ });
});
describe('shouldDisplayReportInLHN', () => {
@@ -712,500 +872,6 @@ describe('SidebarUtils', () => {
});
});
- describe('shouldShowRedBrickRoad', () => {
- it('returns true when report has transaction thread violations', async () => {
- const MOCK_REPORT: Report = {
- reportID: '1',
- ownerAccountID: 12345,
- chatType: CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT,
- stateNum: CONST.REPORT.STATE_NUM.OPEN,
- statusNum: CONST.REPORT.STATUS_NUM.OPEN,
- policyID: '6',
- };
-
- const MOCK_REPORTS: ReportCollectionDataSet = {
- [`${ONYXKEYS.COLLECTION.REPORT}${MOCK_REPORT.reportID}` as const]: MOCK_REPORT,
- };
-
- const MOCK_REPORT_ACTIONS: ReportActions = {
- // eslint-disable-next-line @typescript-eslint/naming-convention
- '1': {
- reportActionID: '1',
- actionName: CONST.REPORT.ACTIONS.TYPE.IOU,
- actorAccountID: 12345,
- created: '2024-08-08 18:20:44.171',
- },
- };
-
- const MOCK_TRANSACTION = {
- transactionID: '1',
- amount: 10,
- modifiedAmount: 10,
- reportID: MOCK_REPORT.reportID,
- };
-
- const MOCK_TRANSACTIONS = {
- [`${ONYXKEYS.COLLECTION.TRANSACTION}${MOCK_TRANSACTION.transactionID}` as const]: MOCK_TRANSACTION,
- } as OnyxCollection;
-
- const MOCK_TRANSACTION_VIOLATIONS: TransactionViolationsCollectionDataSet = {
- [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${MOCK_TRANSACTION.transactionID}` as const]: [
- {
- type: CONST.VIOLATION_TYPES.VIOLATION,
- name: CONST.VIOLATIONS.MISSING_CATEGORY,
- showInReview: true,
- },
- ],
- };
-
- await act(async () => {
- await Onyx.multiSet({
- ...MOCK_REPORTS,
- ...MOCK_TRANSACTION_VIOLATIONS,
- [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${MOCK_REPORT.reportID}` as const]: MOCK_REPORT_ACTIONS,
- [ONYXKEYS.SESSION]: {
- accountID: 12345,
- },
- [`${ONYXKEYS.COLLECTION.TRANSACTION}${MOCK_TRANSACTION.transactionID}` as const]: MOCK_TRANSACTION,
- });
- });
-
- // Simulate how components determined if a report is archived by using this hook
- const {result: isReportArchived} = renderHook(() => useReportIsArchived(MOCK_REPORT?.reportID));
- const result = SidebarUtils.shouldShowRedBrickRoad(
- MOCK_REPORT,
- chatReportR14932,
- MOCK_REPORT_ACTIONS,
- false,
- {},
- MOCK_TRANSACTIONS,
- MOCK_TRANSACTION_VIOLATIONS as OnyxCollection,
- isReportArchived.current,
- );
-
- expect(result).toBe(true);
- });
-
- it('returns true when report has transaction thread notice type violation', async () => {
- const MOCK_REPORT: Report = {
- reportID: '1',
- ownerAccountID: 12345,
- chatType: CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT,
- stateNum: CONST.REPORT.STATE_NUM.OPEN,
- statusNum: CONST.REPORT.STATUS_NUM.OPEN,
- policyID: '6',
- };
-
- const MOCK_REPORTS: ReportCollectionDataSet = {
- [`${ONYXKEYS.COLLECTION.REPORT}${MOCK_REPORT.reportID}` as const]: MOCK_REPORT,
- };
-
- const MOCK_REPORT_ACTIONS: ReportActions = {
- // eslint-disable-next-line @typescript-eslint/naming-convention
- '1': {
- reportActionID: '1',
- actionName: CONST.REPORT.ACTIONS.TYPE.IOU,
- actorAccountID: 12345,
- created: '2024-08-08 18:20:44.171',
- },
- };
-
- const MOCK_TRANSACTION = {
- transactionID: '1',
- amount: 10,
- modifiedAmount: 10,
- reportID: MOCK_REPORT.reportID,
- };
-
- const MOCK_TRANSACTIONS = {
- [`${ONYXKEYS.COLLECTION.TRANSACTION}${MOCK_TRANSACTION.transactionID}` as const]: MOCK_TRANSACTION,
- } as OnyxCollection;
-
- const MOCK_TRANSACTION_VIOLATIONS: TransactionViolationsCollectionDataSet = {
- [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${MOCK_TRANSACTION.transactionID}` as const]: [
- {
- type: CONST.VIOLATION_TYPES.NOTICE,
- name: CONST.VIOLATIONS.MODIFIED_AMOUNT,
- showInReview: true,
- },
- ],
- };
-
- await act(async () => {
- await Onyx.multiSet({
- ...MOCK_REPORTS,
- ...MOCK_TRANSACTION_VIOLATIONS,
- [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${MOCK_REPORT.reportID}` as const]: MOCK_REPORT_ACTIONS,
- [ONYXKEYS.SESSION]: {
- accountID: 12345,
- },
- [`${ONYXKEYS.COLLECTION.TRANSACTION}${MOCK_TRANSACTION.transactionID}` as const]: MOCK_TRANSACTION,
- });
- });
-
- const {result: isReportArchived} = renderHook(() => useReportIsArchived(MOCK_REPORT?.reportID));
- const result = SidebarUtils.shouldShowRedBrickRoad(
- MOCK_REPORT,
- chatReportR14932,
- MOCK_REPORT_ACTIONS,
- false,
- {},
- MOCK_TRANSACTIONS,
- MOCK_TRANSACTION_VIOLATIONS as OnyxCollection,
- isReportArchived.current,
- );
-
- expect(result).toBe(true);
- });
-
- it('returns true when submitter has held expenses even if outstanding tasks trigger GBR', async () => {
- const policyID = generateReportID();
- const expenseChatID = generateReportID();
- const expenseReportID = generateReportID();
- const holdReportActionID = generateReportID();
-
- const policyExpenseChat: Report = {
- reportID: expenseChatID,
- chatType: CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT,
- type: CONST.REPORT.TYPE.CHAT,
- ownerAccountID: 12345,
- policyID,
- hasOutstandingChildRequest: true,
- stateNum: CONST.REPORT.STATE_NUM.OPEN,
- statusNum: CONST.REPORT.STATUS_NUM.OPEN,
- };
-
- const expenseReport: Report = {
- reportID: expenseReportID,
- chatReportID: expenseChatID,
- type: CONST.REPORT.TYPE.EXPENSE,
- ownerAccountID: 12345,
- managerID: 12345,
- policyID,
- stateNum: CONST.REPORT.STATE_NUM.OPEN,
- statusNum: CONST.REPORT.STATUS_NUM.OPEN,
- };
-
- const baseTransaction = createRandomTransaction(700);
- const transactionID = generateTransactionID();
- const transaction: Transaction = {
- ...baseTransaction,
- transactionID,
- reportID: expenseReport.reportID,
- amount: 12345,
- currency: CONST.CURRENCY.USD,
- status: CONST.TRANSACTION.STATUS.POSTED,
- comment: {
- ...(baseTransaction.comment ?? {}),
- hold: holdReportActionID,
- },
- };
-
- const transactionKey = `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}` as const;
- const transactionViolationsKey = `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}` as const;
- const transactionViolations: OnyxCollection = {
- [transactionViolationsKey]: [
- {
- name: CONST.VIOLATIONS.HOLD,
- type: CONST.VIOLATION_TYPES.VIOLATION,
- showInReview: true,
- },
- ],
- };
-
- await act(async () => {
- await Onyx.multiSet({
- [ONYXKEYS.SESSION]: {
- accountID: 12345,
- },
- [`${ONYXKEYS.COLLECTION.REPORT}${policyExpenseChat.reportID}`]: policyExpenseChat,
- [`${ONYXKEYS.COLLECTION.REPORT}${expenseReport.reportID}`]: expenseReport,
- [transactionKey]: transaction,
- [transactionViolationsKey]: transactionViolations[transactionViolationsKey],
- } as unknown as OnyxMultiSetInput);
- });
-
- await waitForBatchedUpdatesWithAct();
-
- const requiresAttention = getReasonAndReportActionThatRequiresAttention(policyExpenseChat);
- expect(requiresAttention?.reason).toBe(CONST.REQUIRES_ATTENTION_REASONS.HAS_CHILD_REPORT_AWAITING_ACTION);
-
- const {reason} =
- SidebarUtils.getReasonAndReportActionThatHasRedBrickRoad(
- policyExpenseChat,
- policyExpenseChat,
- {} as OnyxEntry,
- true,
- {},
- {[transactionKey]: transaction},
- transactionViolations,
- false,
- ) ?? {};
-
- expect(reason).toBe(CONST.RBR_REASONS.HAS_TRANSACTION_THREAD_VIOLATIONS);
-
- const {result: isReportArchived} = renderHook(() => useReportIsArchived(policyExpenseChat.reportID));
- const hasRedBrickRoad = SidebarUtils.shouldShowRedBrickRoad(
- policyExpenseChat,
- policyExpenseChat,
- {} as OnyxEntry,
- true,
- {},
- {[transactionKey]: transaction},
- transactionViolations as OnyxCollection,
- isReportArchived.current,
- );
-
- expect(hasRedBrickRoad).toBe(true);
- });
-
- it('returns true when report has errors', () => {
- const MOCK_REPORT: Report = {
- reportID: '1',
- errorFields: {
- someField: {
- error: 'Some error occurred',
- },
- },
- };
- const MOCK_REPORT_ACTIONS: OnyxEntry = {};
- const MOCK_TRANSACTIONS = {};
- const MOCK_TRANSACTION_VIOLATIONS: OnyxCollection = {};
- const reportErrors = getAllReportErrors(MOCK_REPORT, MOCK_REPORT_ACTIONS);
- const {result: isReportArchived} = renderHook(() => useReportIsArchived(MOCK_REPORT?.reportID));
- const result = SidebarUtils.shouldShowRedBrickRoad(
- MOCK_REPORT,
- chatReportR14932,
- MOCK_REPORT_ACTIONS,
- false,
- reportErrors,
- MOCK_TRANSACTIONS,
- MOCK_TRANSACTION_VIOLATIONS,
- isReportArchived.current,
- );
-
- expect(result).toBe(true);
- });
-
- it('returns true when report has violations', () => {
- const MOCK_REPORT: Report = {
- reportID: '1',
- };
- const MOCK_REPORT_ACTIONS: OnyxEntry = {};
- const MOCK_TRANSACTIONS = {};
- const MOCK_TRANSACTION_VIOLATIONS: OnyxCollection = {};
-
- const {result: isReportArchived} = renderHook(() => useReportIsArchived(MOCK_REPORT?.reportID));
- const result = SidebarUtils.shouldShowRedBrickRoad(
- MOCK_REPORT,
- chatReportR14932,
- MOCK_REPORT_ACTIONS,
- true,
- {},
- MOCK_TRANSACTIONS,
- MOCK_TRANSACTION_VIOLATIONS,
- isReportArchived.current,
- );
-
- expect(result).toBe(true);
- });
-
- it('returns true when report has report action errors', () => {
- const MOCK_REPORT: Report = {
- reportID: '1',
- };
- const MOCK_REPORT_ACTIONS: OnyxEntry = {
- // eslint-disable-next-line @typescript-eslint/naming-convention
- '1': {
- reportActionID: '1',
- actionName: CONST.REPORT.ACTIONS.TYPE.CREATED,
- actorAccountID: 12345,
- created: '2024-08-08 18:20:44.171',
- message: [
- {
- type: '',
- text: '',
- },
- ],
- errors: {
- someError: 'Some error occurred',
- },
- },
- };
- const MOCK_TRANSACTIONS = {};
- const MOCK_TRANSACTION_VIOLATIONS: OnyxCollection = {};
- const reportErrors = getAllReportErrors(MOCK_REPORT, MOCK_REPORT_ACTIONS);
- const {result: isReportArchived} = renderHook(() => useReportIsArchived(MOCK_REPORT?.reportID));
- const result = SidebarUtils.shouldShowRedBrickRoad(
- MOCK_REPORT,
- chatReportR14932,
- MOCK_REPORT_ACTIONS,
- false,
- reportErrors,
- MOCK_TRANSACTIONS,
- MOCK_TRANSACTION_VIOLATIONS,
- isReportArchived.current,
- );
-
- expect(result).toBe(true);
- });
-
- it('returns true when report has export errors', () => {
- const MOCK_REPORT: Report = {
- reportID: '1',
- errorFields: {
- export: {
- error: 'Some error occurred',
- },
- },
- };
- const MOCK_REPORT_ACTIONS: OnyxEntry = {};
- const MOCK_TRANSACTIONS = {};
- const MOCK_TRANSACTION_VIOLATIONS: OnyxCollection = {};
- const reportErrors = getAllReportErrors(MOCK_REPORT, MOCK_REPORT_ACTIONS);
- const {result: isReportArchived} = renderHook(() => useReportIsArchived(MOCK_REPORT?.reportID));
- const result = SidebarUtils.shouldShowRedBrickRoad(
- MOCK_REPORT,
- chatReportR14932,
- MOCK_REPORT_ACTIONS,
- false,
- reportErrors,
- MOCK_TRANSACTIONS,
- MOCK_TRANSACTION_VIOLATIONS,
- isReportArchived.current,
- );
-
- expect(result).toBe(true);
- });
-
- it('returns false when report has no errors', () => {
- const MOCK_REPORT: Report = {
- reportID: '1',
- };
- const MOCK_REPORT_ACTIONS: OnyxEntry = {};
- const MOCK_TRANSACTIONS = {};
- const MOCK_TRANSACTION_VIOLATIONS: OnyxCollection = {};
-
- const {result: isReportArchived} = renderHook(() => useReportIsArchived(MOCK_REPORT?.reportID));
- const result = SidebarUtils.shouldShowRedBrickRoad(
- MOCK_REPORT,
- chatReportR14932,
- MOCK_REPORT_ACTIONS,
- false,
- {},
- MOCK_TRANSACTIONS,
- MOCK_TRANSACTION_VIOLATIONS,
- isReportArchived.current,
- );
-
- expect(result).toBe(false);
- });
-
- it('shows RBR when copilot with full access pays a report but deposit and withdrawal accounts are the same', () => {
- const copilotAccountID = 99999;
- const ownerAccountID = 12345;
-
- const MOCK_EXPENSE_REPORT: Report = {
- reportID: '1',
- type: CONST.REPORT.TYPE.EXPENSE,
- ownerAccountID,
- managerID: copilotAccountID,
- statusNum: CONST.REPORT.STATUS_NUM.REIMBURSED,
- stateNum: CONST.REPORT.STATE_NUM.APPROVED,
- };
-
- const MOCK_PAY_ACTION: ReportAction = {
- reportActionID: '1',
- actionName: CONST.REPORT.ACTIONS.TYPE.IOU,
- actorAccountID: copilotAccountID,
- created: '2024-08-08 18:20:44.171',
- delegateAccountID: copilotAccountID,
- message: [
- {
- type: 'COMMENT',
- text: '',
- },
- ],
- originalMessage: {
- type: CONST.IOU.REPORT_ACTION_TYPE.PAY,
- IOUReportID: MOCK_EXPENSE_REPORT.reportID,
- amount: 10000,
- currency: CONST.CURRENCY.USD,
- paymentType: CONST.IOU.PAYMENT_TYPE.ELSEWHERE,
- } as OriginalMessageIOU,
- errors: {
- [`${Date.now()}`]: CONST.ERROR.BANK_ACCOUNT_SAME_DEPOSIT_AND_WITHDRAWAL_ERROR,
- },
- };
-
- const MOCK_REPORT_ACTIONS: OnyxEntry = {
- // eslint-disable-next-line @typescript-eslint/naming-convention
- '1': MOCK_PAY_ACTION,
- };
- const MOCK_TRANSACTIONS = {};
- const MOCK_TRANSACTION_VIOLATIONS: OnyxCollection = {};
- const reportErrors = getAllReportErrors(MOCK_EXPENSE_REPORT, MOCK_REPORT_ACTIONS);
- const {result: isReportArchived} = renderHook(() => useReportIsArchived(MOCK_EXPENSE_REPORT?.reportID));
-
- const result = SidebarUtils.shouldShowRedBrickRoad(
- MOCK_EXPENSE_REPORT,
- chatReportR14932,
- MOCK_REPORT_ACTIONS,
- false,
- reportErrors,
- MOCK_TRANSACTIONS,
- MOCK_TRANSACTION_VIOLATIONS,
- isReportArchived.current,
- );
- expect(result).toBe(true);
-
- const {reason, reportAction} =
- SidebarUtils.getReasonAndReportActionThatHasRedBrickRoad(
- MOCK_EXPENSE_REPORT,
- chatReportR14932,
- MOCK_REPORT_ACTIONS,
- false,
- reportErrors,
- MOCK_TRANSACTIONS,
- MOCK_TRANSACTION_VIOLATIONS,
- isReportArchived.current,
- ) ?? {};
- expect(reason).toBe(CONST.RBR_REASONS.HAS_ERRORS);
- expect(reportAction).toMatchObject(MOCK_PAY_ACTION);
- });
-
- it('returns false when report is archived', () => {
- const MOCK_REPORT: Report = {
- reportID: '5',
- errorFields: {
- export: {
- error: 'Some error occurred',
- },
- },
- };
- // This report with reportID 5 is already archived from previous tests
- // where we set reportNameValuePairs with private_isArchived
- const MOCK_REPORT_ACTIONS: OnyxEntry = {};
- const MOCK_TRANSACTIONS = {};
- const MOCK_TRANSACTION_VIOLATIONS: OnyxCollection = {};
-
- // Simulate how components determined if a report is archived by using this hook
- const {result: isReportArchived} = renderHook(() => useReportIsArchived(MOCK_REPORT?.reportID));
- const result = SidebarUtils.shouldShowRedBrickRoad(
- MOCK_REPORT,
- chatReportR14932,
- MOCK_REPORT_ACTIONS,
- false,
- {},
- MOCK_TRANSACTIONS,
- MOCK_TRANSACTION_VIOLATIONS,
- isReportArchived.current,
- );
-
- expect(result).toBe(false);
- });
- });
-
describe('getWelcomeMessage', () => {
const MOCK_CONCIERGE_REPORT_ID = 'concierge-report-id';
diff --git a/tests/unit/navigateAfterOnboardingTest.ts b/tests/unit/navigateAfterOnboardingTest.ts
index 600cedbd58968..cc85bda5fbae4 100644
--- a/tests/unit/navigateAfterOnboardingTest.ts
+++ b/tests/unit/navigateAfterOnboardingTest.ts
@@ -38,7 +38,7 @@ jest.mock('@libs/ReportUtils', () => ({
generateReportAttributes: jest.requireActual('@libs/ReportUtils').generateReportAttributes,
getAllReportActionsErrorsAndReportActionThatRequiresAttention: jest.requireActual('@libs/ReportUtils').getAllReportActionsErrorsAndReportActionThatRequiresAttention,
getAllReportErrors: jest.requireActual('@libs/ReportUtils').getAllReportErrors,
- shouldDisplayViolationsRBRInLHN: jest.requireActual('@libs/ReportUtils').shouldDisplayViolationsRBRInLHN,
+ getViolatingReportIDForRBRInLHN: jest.requireActual('@libs/ReportUtils').getViolatingReportIDForRBRInLHN,
generateIsEmptyReport: jest.requireActual('@libs/ReportUtils').generateIsEmptyReport,
isExpenseReport: jest.requireActual('@libs/ReportUtils').isExpenseReport,
isSelfDM: jest.requireActual('@libs/ReportUtils').isSelfDM,