From 23547108f0e997d16deb00a57427becd99fafb45 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Tue, 3 Mar 2026 17:41:45 +0700 Subject: [PATCH 1/5] refactor getSortedSections getWelcomeMessage and getAlternateText to use policy from useOnyx --- .../Search/SearchFiltersChatsSelector.tsx | 3 +- src/libs/OptionsListUtils/index.ts | 6 + src/libs/ReportUtils.ts | 4 +- src/libs/SearchUIUtils.ts | 4 +- src/libs/SidebarUtils.ts | 2 +- tests/unit/OptionsListUtilsTest.tsx | 103 ++++++++++++++++++ 6 files changed, 115 insertions(+), 7 deletions(-) diff --git a/src/components/Search/SearchFiltersChatsSelector.tsx b/src/components/Search/SearchFiltersChatsSelector.tsx index 9f2cea9fae5bd..5e62965cdb562 100644 --- a/src/components/Search/SearchFiltersChatsSelector.tsx +++ b/src/components/Search/SearchFiltersChatsSelector.tsx @@ -73,7 +73,8 @@ function SearchFiltersChatsSelector({initialReportIDs, onFiltersUpdate, isScreen createOptionFromReport({...reportData, reportID: id}, personalDetails, currentUserAccountID, chatReport, privateIsArchived, reportAttributesDerived), ); const isReportArchived = !!privateIsArchived; - const alternateText = getAlternateText(report, {}, isReportArchived, currentUserAccountID, {}, undefined, undefined, reportAttributesDerived); + const policy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${reportData?.policyID}`]; + const alternateText = getAlternateText(report, {}, isReportArchived, currentUserAccountID, policy, {}, undefined, undefined, reportAttributesDerived); return {...report, alternateText}; }); diff --git a/src/libs/OptionsListUtils/index.ts b/src/libs/OptionsListUtils/index.ts index 91831ee3e5574..3202633e7abfa 100644 --- a/src/libs/OptionsListUtils/index.ts +++ b/src/libs/OptionsListUtils/index.ts @@ -430,6 +430,7 @@ function getAlternateText( {showChatPreviewLine = false, forcePolicyNamePreview = false}: PreviewConfig, isReportArchived: boolean | undefined, currentUserAccountID: number, + policy?: OnyxEntry, lastActorDetails: Partial | null = {}, visibleReportActionsData: VisibleReportActionsDerivedValue = {}, translate?: LocalizedTranslate, @@ -450,6 +451,7 @@ function getAlternateText( translate: translateFn, report, lastActorDetails, + policy, isReportArchived, chatReport, visibleReportActionsDataParam: visibleReportActionsData, @@ -1053,6 +1055,8 @@ function createOption( {showChatPreviewLine, forcePolicyNamePreview}, !!result.private_isArchived, currentUserAccountID, + // TODO: Remove this in the next PR that will refactor prepareReportOptionsForDisplay. Ref: https://github.com/Expensify/App/issues/66415 + undefined, lastActorDetails, visibleReportActionsData, translateFn, @@ -2252,6 +2256,8 @@ function prepareReportOptionsForDisplay( {showChatPreviewLine, forcePolicyNamePreview}, !!option.private_isArchived, currentUserAccountID, + // TODO: Remove this in the next PR that will refactor prepareReportOptionsForDisplay. Ref: https://github.com/Expensify/App/issues/66415 + undefined, null, visibleReportActionsData, undefined, diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index ed513b787bd01..b3f40a2e53b7a 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1066,14 +1066,12 @@ Onyx.connect({ }); let allPolicies: OnyxCollection; -let hasPolicies: boolean; let policiesArray: Policy[] = []; Onyx.connect({ key: ONYXKEYS.COLLECTION.POLICY, waitForCollectionCallback: true, callback: (value) => { allPolicies = value; - hasPolicies = !isEmptyObject(value); policiesArray = Object.values(value ?? {}).filter((policy): policy is Policy => !!policy); }, }); @@ -1412,7 +1410,7 @@ function getPolicyName({report, returnEmptyIfNotFound = false, policy, policies, const noPolicyFound = returnEmptyIfNotFound ? '' : unavailableTranslation; const parentReport = report ? getRootParentReport({report, reports}) : undefined; - if ((!report?.policyName && !parentReport?.policyName && !hasPolicies && isEmptyObject(policies)) || isEmptyObject(report)) { + if ((!report?.policyName && !parentReport?.policyName && isEmptyObject(policy) && isEmptyObject(policies) && isEmptyObject(allPolicies)) || isEmptyObject(report)) { return noPolicyFound; } const finalPolicy = (() => { diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts index d29144a21ba02..1538391728d3e 100644 --- a/src/libs/SearchUIUtils.ts +++ b/src/libs/SearchUIUtils.ts @@ -3055,8 +3055,8 @@ function getSortedTransactionData( const aIsUnreported = a.report?.type !== CONST.REPORT.TYPE.EXPENSE && a.report?.type !== CONST.REPORT.TYPE.INVOICE; const bIsUnreported = b.report?.type !== CONST.REPORT.TYPE.EXPENSE && b.report?.type !== CONST.REPORT.TYPE.INVOICE; - const aValue = !aIsUnreported ? getPolicyName({report: a.report}) : ''; - const bValue = !bIsUnreported ? getPolicyName({report: b.report}) : ''; + const aValue = !aIsUnreported ? getPolicyName({report: a.report, policy: a.policy}) : ''; + const bValue = !bIsUnreported ? getPolicyName({report: b.report, policy: b.policy}) : ''; return compareValues(aValue, bValue, sortOrder, sortBy, localeCompare); }); } diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 781381ecf1f30..b98321948b318 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -1210,7 +1210,7 @@ function getWelcomeMessage( } else { welcomeMessage.messageHtml = translate( 'reportActionsView.beginningOfChatHistoryPolicyExpenseChat', - getPolicyName({report}), + getPolicyName({report, policy}), getDisplayNameForParticipant({accountID: report?.ownerAccountID, formatPhoneNumber: formatPhoneNumberPhoneUtils}), ); welcomeMessage.messageText = Parser.htmlToText(welcomeMessage.messageHtml); diff --git a/tests/unit/OptionsListUtilsTest.tsx b/tests/unit/OptionsListUtilsTest.tsx index 655153408f9f7..94b1229a31a33 100644 --- a/tests/unit/OptionsListUtilsTest.tsx +++ b/tests/unit/OptionsListUtilsTest.tsx @@ -4323,6 +4323,7 @@ describe('OptionsListUtils', () => { translate: translateLocal, report, lastActorDetails: null, + policy: undefined, isReportArchived: false, chatReport: undefined, }); @@ -4347,6 +4348,7 @@ describe('OptionsListUtils', () => { translate: translateLocal, report, lastActorDetails: null, + policy: undefined, isReportArchived: false, chatReport: undefined, }); @@ -4372,6 +4374,7 @@ describe('OptionsListUtils', () => { translate: translateLocal, report, lastActorDetails: null, + policy: undefined, isReportArchived: false, chatReport: undefined, }); @@ -4397,6 +4400,7 @@ describe('OptionsListUtils', () => { translate: translateLocal, report, lastActorDetails: null, + policy: undefined, isReportArchived: false, chatReport: undefined, }); @@ -4419,6 +4423,7 @@ describe('OptionsListUtils', () => { translate: translateLocal, report, lastActorDetails: null, + policy: undefined, isReportArchived: false, chatReport: undefined, }); @@ -4440,6 +4445,7 @@ describe('OptionsListUtils', () => { translate: translateLocal, report, lastActorDetails: null, + policy: undefined, isReportArchived: false, chatReport: undefined, }); @@ -4460,6 +4466,7 @@ describe('OptionsListUtils', () => { translate: translateLocal, report, lastActorDetails: null, + policy: undefined, isReportArchived: false, chatReport: undefined, }); @@ -4480,6 +4487,7 @@ describe('OptionsListUtils', () => { translate: translateLocal, report, lastActorDetails: null, + policy: undefined, isReportArchived: false, chatReport: undefined, }); @@ -4500,6 +4508,7 @@ describe('OptionsListUtils', () => { translate: translateLocal, report, lastActorDetails: null, + policy: undefined, isReportArchived: false, chatReport: undefined, }); @@ -4520,6 +4529,7 @@ describe('OptionsListUtils', () => { translate: translateLocal, report, lastActorDetails: null, + policy: undefined, isReportArchived: false, chatReport: undefined, }); @@ -4540,6 +4550,7 @@ describe('OptionsListUtils', () => { translate: translateLocal, report, lastActorDetails: null, + policy: undefined, isReportArchived: false, chatReport: undefined, }); @@ -4564,6 +4575,7 @@ describe('OptionsListUtils', () => { translate: translateLocal, report, lastActorDetails: null, + policy: undefined, isReportArchived: false, chatReport: undefined, }); @@ -4590,6 +4602,7 @@ describe('OptionsListUtils', () => { translate: translateLocal, report, lastActorDetails: null, + policy: undefined, isReportArchived: false, chatReport: undefined, }); @@ -4607,6 +4620,7 @@ describe('OptionsListUtils', () => { translate: translateLocal, report, lastActorDetails: null, + policy: undefined, isReportArchived: false, chatReport: undefined, }); @@ -4636,6 +4650,7 @@ describe('OptionsListUtils', () => { translate: translateLocal, report, lastActorDetails: null, + policy: undefined, isReportArchived: false, chatReport: undefined, }); @@ -4674,6 +4689,7 @@ describe('OptionsListUtils', () => { report, translate: translateLocal, lastActorDetails: null, + policy: undefined, isReportArchived: false, chatReport: undefined, }); @@ -4757,6 +4773,7 @@ describe('OptionsListUtils', () => { translate: translateLocal, report, lastActorDetails: null, + policy: undefined, isReportArchived: false, chatReport: undefined, }); @@ -4786,12 +4803,96 @@ describe('OptionsListUtils', () => { translate: translateLocal, report, lastActorDetails: null, + policy: undefined, isReportArchived: false, chatReport: undefined, }); expect(lastMessage).toBe(translate(CONST.LOCALES.EN, 'iou.error.genericCreateFailureMessage')); }); }); + + describe('archived report with policy', () => { + it('should use the passed policy name for POLICY_DELETED archive reason', async () => { + const testPolicyID = 'archivePolicyTest'; + const policy: Policy = { + id: testPolicyID, + name: 'Test Workspace', + type: CONST.POLICY.TYPE.TEAM, + } as Policy; + const report: Report = { + ...createRandomReport(0, undefined), + policyID: testPolicyID, + type: CONST.REPORT.TYPE.CHAT, + }; + const closedAction = { + ...createRandomReportAction(1), + actionName: CONST.REPORT.ACTIONS.TYPE.CLOSED, + originalMessage: { + policyName: policy.name, + reason: CONST.REPORT.ARCHIVE_REASON.POLICY_DELETED, + }, + } as ReportAction; + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`, { + [closedAction.reportActionID]: closedAction, + }); + + const lastMessage = getLastMessageTextForReport({ + translate: translateLocal, + report, + lastActorDetails: null, + policy, + isReportArchived: true, + chatReport: undefined, + }); + + expect(lastMessage).toBe( + translateLocal('reportArchiveReasons.policyDeleted', { + policyName: policy.name, + }), + ); + }); + + it('should use the passed policy name for REMOVED_FROM_POLICY archive reason', async () => { + const testPolicyID = 'archivePolicyTest2'; + const policy: Policy = { + id: testPolicyID, + name: 'My Workspace', + type: CONST.POLICY.TYPE.TEAM, + } as Policy; + const report: Report = { + ...createRandomReport(0, undefined), + policyID: testPolicyID, + type: CONST.REPORT.TYPE.CHAT, + }; + const closedAction = { + ...createRandomReportAction(1), + actionName: CONST.REPORT.ACTIONS.TYPE.CLOSED, + originalMessage: { + policyName: policy.name, + reason: CONST.REPORT.ARCHIVE_REASON.REMOVED_FROM_POLICY, + }, + } as ReportAction; + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`, { + [closedAction.reportActionID]: closedAction, + }); + + const lastMessage = getLastMessageTextForReport({ + translate: translateLocal, + report, + lastActorDetails: null, + policy, + isReportArchived: true, + chatReport: undefined, + }); + + expect(lastMessage).toBe( + translateLocal('reportArchiveReasons.removedFromPolicy', { + displayName: 'Hidden', + policyName: policy.name, + }), + ); + }); + }); }); describe('getPersonalDetailSearchTerms', () => { @@ -6181,6 +6282,7 @@ describe('OptionsListUtils', () => { translate: jest.fn().mockReturnValue(''), report, lastActorDetails: null, + policy: undefined, isReportArchived: false, chatReport, }); @@ -6199,6 +6301,7 @@ describe('OptionsListUtils', () => { translate: jest.fn().mockReturnValue(''), report, lastActorDetails: null, + policy: undefined, isReportArchived: false, chatReport: undefined, }); From 9d2db251603672ae0d2e62e2e1ef42cf2c1d35d5 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Tue, 3 Mar 2026 18:37:50 +0700 Subject: [PATCH 2/5] add more tests --- tests/unit/ReportUtilsTest.ts | 39 +++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tests/unit/ReportUtilsTest.ts b/tests/unit/ReportUtilsTest.ts index 4e7a2316005a4..a2340f8a4f356 100644 --- a/tests/unit/ReportUtilsTest.ts +++ b/tests/unit/ReportUtilsTest.ts @@ -12957,6 +12957,45 @@ describe('ReportUtils', () => { }); expect(result).toBe('Report Fallback Name'); }); + + it('should return noPolicyFound when policy, policies, and allPolicies are all empty and report has no policyName', async () => { + // Clear all policies from Onyx so allPolicies is empty + await Onyx.clear(); + await waitForBatchedUpdates(); + + const report: Report = { + ...createRandomReport(1, undefined), + policyID: 'nonexistent', + policyName: undefined, + oldPolicyName: undefined, + }; + + const result = getPolicyName({report, returnEmptyIfNotFound: true}); + expect(result).toBe(''); + }); + + it('should not trigger early return when allPolicies has data even if policy and policies params are empty', async () => { + const onyxPolicy: Policy = { + ...createRandomPolicy(1), + id: 'guardTestPolicy', + name: 'Guard Test Policy', + }; + await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${onyxPolicy.id}`, onyxPolicy); + await waitForBatchedUpdates(); + + const report: Report = { + ...createRandomReport(1, undefined), + policyID: 'guardTestPolicy', + policyName: undefined, + }; + + // No policy or policies passed, but allPolicies has the policy via Onyx + const result = getPolicyName({report}); + expect(result).toBe('Guard Test Policy'); + + // Cleanup + await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${onyxPolicy.id}`, null); + }); }); describe('getBillableAndTaxTotal', () => { From 4dcf8077efba80bef34b2c47c0ea5792efbb2798 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Wed, 4 Mar 2026 11:47:44 +0700 Subject: [PATCH 3/5] ts fix --- tests/unit/OptionsListUtilsTest.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/unit/OptionsListUtilsTest.tsx b/tests/unit/OptionsListUtilsTest.tsx index dae8dfabf2285..1efab3f2b3459 100644 --- a/tests/unit/OptionsListUtilsTest.tsx +++ b/tests/unit/OptionsListUtilsTest.tsx @@ -4862,6 +4862,7 @@ describe('OptionsListUtils', () => { policy, isReportArchived: true, chatReport: undefined, + currentUserLogin: '', }); expect(lastMessage).toBe( @@ -4902,6 +4903,7 @@ describe('OptionsListUtils', () => { policy, isReportArchived: true, chatReport: undefined, + currentUserLogin: '', }); expect(lastMessage).toBe( From 62fbcac5a6f26732b3b52f43f34686924b7ab64e Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Mon, 9 Mar 2026 10:53:14 +0700 Subject: [PATCH 4/5] update code --- src/libs/ReportUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 15aea699e6db0..4ad1013d47fef 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1410,7 +1410,7 @@ function getPolicyName({report, returnEmptyIfNotFound = false, policy, policies, const noPolicyFound = returnEmptyIfNotFound ? '' : unavailableTranslation; const parentReport = report ? getRootParentReport({report, reports}) : undefined; - if ((!report?.policyName && !parentReport?.policyName && isEmptyObject(policy) && isEmptyObject(policies) && isEmptyObject(allPolicies)) || isEmptyObject(report)) { + if ((!report?.policyName && !parentReport?.policyName && isEmptyObject(policies) && isEmptyObject(allPolicies)) || isEmptyObject(report)) { return noPolicyFound; } const finalPolicy = (() => { From 74b5b51d0d53ff674a67bad161fa3772989ed54e Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Mon, 9 Mar 2026 23:54:36 +0700 Subject: [PATCH 5/5] add comments --- src/libs/OptionsListUtils/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/OptionsListUtils/index.ts b/src/libs/OptionsListUtils/index.ts index b0b5d19af176d..cb1ce83774cd3 100644 --- a/src/libs/OptionsListUtils/index.ts +++ b/src/libs/OptionsListUtils/index.ts @@ -431,6 +431,7 @@ function getAlternateText( {showChatPreviewLine = false, forcePolicyNamePreview = false}: PreviewConfig, isReportArchived: boolean | undefined, currentUserLogin: string, + // We'll make it required in the next PR. Ref: https://github.com/Expensify/App/issues/66415 policy?: OnyxEntry, lastActorDetails: Partial | null = {}, visibleReportActionsData: VisibleReportActionsDerivedValue = {},