From 7b1a5ffaa408db211c890b5ad63d0fc90c9833ab Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Thu, 22 Jan 2026 17:53:53 +0700 Subject: [PATCH 1/7] refactor getPolicyExpenseReportOption --- .../Search/SearchFiltersChatsSelector.tsx | 4 + .../SearchFiltersParticipantsSelector.tsx | 5 +- src/libs/OptionsListUtils/index.ts | 16 ++- src/pages/NewChatPage.tsx | 3 + src/pages/iou/SplitBillDetailsPage.tsx | 4 +- .../MoneyRequestAccountantSelector.tsx | 7 +- .../request/MoneyRequestAttendeeSelector.tsx | 7 +- .../MoneyRequestParticipantsSelector.tsx | 7 +- src/selectors/ReportNameValuePairs.ts | 7 +- tests/perf-test/OptionsListUtils.perf-test.ts | 3 +- tests/unit/OptionsListUtilsTest.tsx | 114 ++++++++++++++++++ 11 files changed, 167 insertions(+), 10 deletions(-) diff --git a/src/components/Search/SearchFiltersChatsSelector.tsx b/src/components/Search/SearchFiltersChatsSelector.tsx index 2d8f438aeb86c..8d1544984baca 100644 --- a/src/components/Search/SearchFiltersChatsSelector.tsx +++ b/src/components/Search/SearchFiltersChatsSelector.tsx @@ -9,6 +9,7 @@ import useArchivedReportsIdSet from '@hooks/useArchivedReportsIdSet'; import useDebouncedState from '@hooks/useDebouncedState'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; +import usePrivateIsArchivedMap from '@hooks/usePrivateIsArchivedMap'; import useScreenWrapperTransitionStatus from '@hooks/useScreenWrapperTransitionStatus'; import {canUseTouchScreen} from '@libs/DeviceCapabilities'; import {createOptionFromReport, filterAndOrderOptions, formatSectionsFromSearchTerm, getAlternateText, getSearchOptions} from '@libs/OptionsListUtils'; @@ -60,6 +61,7 @@ function SearchFiltersChatsSelector({initialReportIDs, onFiltersUpdate, isScreen const [draftComments] = useOnyx(ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, {canBeMissing: true}); const archivedReportsIdSet = useArchivedReportsIdSet(); const [nvpDismissedProductTraining] = useOnyx(ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING, {canBeMissing: true}); + const privateIsArchivedMap = usePrivateIsArchivedMap(); const selectedOptions = useMemo(() => { return selectedReportIDs.map((id) => { @@ -95,6 +97,7 @@ function SearchFiltersChatsSelector({initialReportIDs, onFiltersUpdate, isScreen selectedOptions, chatOptions.recentReports, chatOptions.personalDetails, + privateIsArchivedMap[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${selectedOptions.at(0)?.reportID}`], personalDetails, false, undefined, @@ -131,6 +134,7 @@ function SearchFiltersChatsSelector({initialReportIDs, onFiltersUpdate, isScreen selectedOptions, selectedReportIDs, translate, + privateIsArchivedMap, ]); useEffect(() => { diff --git a/src/components/Search/SearchFiltersParticipantsSelector.tsx b/src/components/Search/SearchFiltersParticipantsSelector.tsx index d4bd61d90bc99..44f49cf245e9c 100644 --- a/src/components/Search/SearchFiltersParticipantsSelector.tsx +++ b/src/components/Search/SearchFiltersParticipantsSelector.tsx @@ -7,6 +7,7 @@ import SelectionList from '@components/SelectionListWithSections'; import UserSelectionListItem from '@components/SelectionListWithSections/Search/UserSelectionListItem'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; +import usePrivateIsArchivedMap from '@hooks/usePrivateIsArchivedMap'; import useScreenWrapperTransitionStatus from '@hooks/useScreenWrapperTransitionStatus'; import {canUseTouchScreen} from '@libs/DeviceCapabilities'; import memoize from '@libs/memoize'; @@ -90,6 +91,7 @@ function SearchFiltersParticipantsSelector({initialAccountIDs, onFiltersUpdate, const [nvpDismissedProductTraining] = useOnyx(ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING, {canBeMissing: true}); const [allPolicies] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {canBeMissing: true}); const [recentAttendees] = useOnyx(ONYXKEYS.NVP_RECENT_ATTENDEES, {canBeMissing: true}); + const privateIsArchivedMap = usePrivateIsArchivedMap(); // Transform raw recentAttendees into Option[] format for use with getValidOptions (only for attendee filter) const recentAttendeeLists = useMemo( @@ -190,6 +192,7 @@ function SearchFiltersParticipantsSelector({initialAccountIDs, onFiltersUpdate, selectedOptions, chatOptions.recentReports, chatOptions.personalDetails, + privateIsArchivedMap[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${selectedOptions.at(0)?.reportID}`], personalDetails, true, undefined, @@ -249,7 +252,7 @@ function SearchFiltersParticipantsSelector({initialAccountIDs, onFiltersUpdate, sections: newSections, headerMessage: message, }; - }, [areOptionsInitialized, cleanSearchTerm, selectedOptions, chatOptions, personalDetails, reportAttributesDerived, translate, formatPhoneNumber]); + }, [areOptionsInitialized, cleanSearchTerm, selectedOptions, chatOptions, personalDetails, reportAttributesDerived, translate, formatPhoneNumber, privateIsArchivedMap]); const resetChanges = useCallback(() => { setSelectedOptions([]); diff --git a/src/libs/OptionsListUtils/index.ts b/src/libs/OptionsListUtils/index.ts index 80f76b73e6165..3d36bd93a1454 100644 --- a/src/libs/OptionsListUtils/index.ts +++ b/src/libs/OptionsListUtils/index.ts @@ -1102,7 +1102,11 @@ function getReportDisplayOption( /** * Get the option for a policy expense report. */ -function getPolicyExpenseReportOption(participant: Participant | SearchOptionData, reportAttributesDerived?: ReportAttributesDerivedValue['reports']): SearchOptionData { +function getPolicyExpenseReportOption( + participant: Participant | SearchOptionData, + privateIsArchived: string | undefined, + reportAttributesDerived?: ReportAttributesDerivedValue['reports'], +): SearchOptionData { const expenseReport = reportUtilsIsPolicyExpenseChat(participant) ? getReportOrDraftReport(participant.reportID) : null; const visibleParticipantAccountIDs = Object.entries(expenseReport?.participants ?? {}) @@ -1118,6 +1122,7 @@ function getPolicyExpenseReportOption(participant: Participant | SearchOptionDat forcePolicyNamePreview: false, }, reportAttributesDerived, + privateIsArchived, ); // Update text & alternateText because createOption returns workspace name only if report is owned by the user @@ -2628,6 +2633,7 @@ function formatSectionsFromSearchTerm( selectedOptions: SearchOptionData[], filteredRecentReports: SearchOptionData[], filteredPersonalDetails: SearchOptionData[], + privateIsArchived: string | undefined, personalDetails: OnyxEntry = {}, shouldGetOptionDetails = false, filteredWorkspaceChats: SearchOptionData[] = [], @@ -2643,7 +2649,9 @@ function formatSectionsFromSearchTerm( data: shouldGetOptionDetails ? selectedOptions.map((participant) => { const isReportPolicyExpenseChat = participant.isPolicyExpenseChat ?? false; - return isReportPolicyExpenseChat ? getPolicyExpenseReportOption(participant, reportAttributesDerived) : getParticipantsOption(participant, personalDetails); + return isReportPolicyExpenseChat + ? getPolicyExpenseReportOption(participant, privateIsArchived, reportAttributesDerived) + : getParticipantsOption(participant, personalDetails); }) : selectedOptions, shouldShow: selectedOptions.length > 0, @@ -2669,7 +2677,9 @@ function formatSectionsFromSearchTerm( data: shouldGetOptionDetails ? selectedParticipantsWithoutDetails.map((participant) => { const isReportPolicyExpenseChat = participant.isPolicyExpenseChat ?? false; - return isReportPolicyExpenseChat ? getPolicyExpenseReportOption(participant, reportAttributesDerived) : getParticipantsOption(participant, personalDetails); + return isReportPolicyExpenseChat + ? getPolicyExpenseReportOption(participant, privateIsArchived, reportAttributesDerived) + : getParticipantsOption(participant, personalDetails); }) : selectedParticipantsWithoutDetails, shouldShow: selectedParticipantsWithoutDetails.length > 0, diff --git a/src/pages/NewChatPage.tsx b/src/pages/NewChatPage.tsx index e60006e53a29b..f863d32d20953 100755 --- a/src/pages/NewChatPage.tsx +++ b/src/pages/NewChatPage.tsx @@ -22,6 +22,7 @@ import useFilteredOptions from '@hooks/useFilteredOptions'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useOnyx from '@hooks/useOnyx'; +import usePrivateIsArchivedMap from '@hooks/usePrivateIsArchivedMap'; import useSafeAreaInsets from '@hooks/useSafeAreaInsets'; import useSingleExecution from '@hooks/useSingleExecution'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -228,6 +229,7 @@ function NewChatPage({ref}: NewChatPageProps) { const {top} = useSafeAreaInsets(); const [isSearchingForReports] = useOnyx(ONYXKEYS.IS_SEARCHING_FOR_REPORTS, {initWithStoredValues: false, canBeMissing: true}); const [reportAttributesDerived] = useOnyx(ONYXKEYS.DERIVED.REPORT_ATTRIBUTES, {canBeMissing: true, selector: reportsSelector}); + const privateIsArchivedMap = usePrivateIsArchivedMap(); const selectionListRef = useRef(null); const {singleExecution} = useSingleExecution(); @@ -259,6 +261,7 @@ function NewChatPage({ref}: NewChatPageProps) { selectedOptions as OptionData[], recentReports, personalDetails, + privateIsArchivedMap[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${(selectedOptions as OptionData[]).at(0)?.reportID}`], undefined, undefined, undefined, diff --git a/src/pages/iou/SplitBillDetailsPage.tsx b/src/pages/iou/SplitBillDetailsPage.tsx index 1ddde7fcde8ec..726a753aa6fae 100644 --- a/src/pages/iou/SplitBillDetailsPage.tsx +++ b/src/pages/iou/SplitBillDetailsPage.tsx @@ -1,4 +1,5 @@ import reportsSelector from '@selectors/Attributes'; +import {privateIsArchivedSelector} from '@selectors/ReportNameValuePairs'; import React, {useCallback, useState} from 'react'; import {View} from 'react-native'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; @@ -56,6 +57,7 @@ function SplitBillDetailsPage({route, report, reportAction}: SplitBillDetailsPag const [quickAction] = useOnyx(ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE, {canBeMissing: true}); const [session] = useOnyx(ONYXKEYS.SESSION, {canBeMissing: false}); const [reportAttributesDerived] = useOnyx(ONYXKEYS.DERIVED.REPORT_ATTRIBUTES, {canBeMissing: true, selector: reportsSelector}); + const [privateIsArchived] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${reportID}`, {canBeMissing: true, selector: privateIsArchivedSelector}); // In case this is workspace split expense, we manually add the workspace as the second participant of the split expense // because we don't save any accountID in the report action's originalMessage other than the payee's accountID @@ -63,7 +65,7 @@ function SplitBillDetailsPage({route, report, reportAction}: SplitBillDetailsPag if (isPolicyExpenseChat(report)) { participants = [ getParticipantsOption({accountID: participantAccountIDs.at(0), selected: true, reportID: ''}, personalDetails), - getPolicyExpenseReportOption({...report, selected: true, reportID}, reportAttributesDerived), + getPolicyExpenseReportOption({...report, selected: true, reportID}, privateIsArchived, reportAttributesDerived), ]; } else { participants = participantAccountIDs.map((accountID) => getParticipantsOption({accountID, selected: true, reportID: ''}, personalDetails)); diff --git a/src/pages/iou/request/MoneyRequestAccountantSelector.tsx b/src/pages/iou/request/MoneyRequestAccountantSelector.tsx index 031cc9b2c76ed..972d87b372bf2 100644 --- a/src/pages/iou/request/MoneyRequestAccountantSelector.tsx +++ b/src/pages/iou/request/MoneyRequestAccountantSelector.tsx @@ -12,6 +12,7 @@ import useDebouncedState from '@hooks/useDebouncedState'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useOnyx from '@hooks/useOnyx'; +import usePrivateIsArchivedMap from '@hooks/usePrivateIsArchivedMap'; import useScreenWrapperTransitionStatus from '@hooks/useScreenWrapperTransitionStatus'; import {canUseTouchScreen} from '@libs/DeviceCapabilities'; import memoize from '@libs/memoize'; @@ -67,6 +68,7 @@ function MoneyRequestAccountantSelector({onFinish, onAccountantSelected, iouType const [nvpDismissedProductTraining] = useOnyx(ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING, {canBeMissing: true}); const [loginList] = useOnyx(ONYXKEYS.LOGIN_LIST, {canBeMissing: true}); const [allPolicies] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {canBeMissing: true}); + const privateIsArchivedMap = usePrivateIsArchivedMap(); useEffect(() => { searchInServer(debouncedSearchTerm.trim()); @@ -148,6 +150,7 @@ function MoneyRequestAccountantSelector({onFinish, onAccountantSelected, iouType [], chatOptions.recentReports, chatOptions.personalDetails, + privateIsArchivedMap[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${chatOptions.recentReports.at(0)?.reportID}`], personalDetails, true, undefined, @@ -178,7 +181,8 @@ function MoneyRequestAccountantSelector({onFinish, onAccountantSelected, iouType title: undefined, data: [chatOptions.userToInvite].map((participant) => { const isPolicyExpenseChat = participant?.isPolicyExpenseChat ?? false; - return isPolicyExpenseChat ? getPolicyExpenseReportOption(participant, reportAttributesDerived) : getParticipantsOption(participant, personalDetails); + const privateIsArchived = privateIsArchivedMap[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${participant?.reportID}`]; + return isPolicyExpenseChat ? getPolicyExpenseReportOption(participant, privateIsArchived, reportAttributesDerived) : getParticipantsOption(participant, personalDetails); }), shouldShow: true, }); @@ -205,6 +209,7 @@ function MoneyRequestAccountantSelector({onFinish, onAccountantSelected, iouType translate, loginList, countryCode, + privateIsArchivedMap, ]); const selectAccountant = useCallback( diff --git a/src/pages/iou/request/MoneyRequestAttendeeSelector.tsx b/src/pages/iou/request/MoneyRequestAttendeeSelector.tsx index e55b95eed5b29..c7176ba89dbd0 100644 --- a/src/pages/iou/request/MoneyRequestAttendeeSelector.tsx +++ b/src/pages/iou/request/MoneyRequestAttendeeSelector.tsx @@ -14,6 +14,7 @@ import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useOnyx from '@hooks/useOnyx'; import usePolicy from '@hooks/usePolicy'; +import usePrivateIsArchivedMap from '@hooks/usePrivateIsArchivedMap'; import useScreenWrapperTransitionStatus from '@hooks/useScreenWrapperTransitionStatus'; import useSearchSelector from '@hooks/useSearchSelector'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -69,6 +70,7 @@ function MoneyRequestAttendeeSelector({attendees = [], onFinish, onAttendeesAdde const [reportAttributesDerived] = useOnyx(ONYXKEYS.DERIVED.REPORT_ATTRIBUTES, {canBeMissing: true, selector: reportsSelector}); const offlineMessage: string = isOffline ? `${translate('common.youAppearToBeOffline')} ${translate('search.resultsAreLimited')}` : ''; const [loginList] = useOnyx(ONYXKEYS.LOGIN_LIST, {canBeMissing: true}); + const privateIsArchivedMap = usePrivateIsArchivedMap(); const isPaidGroupPolicy = useMemo(() => isPaidGroupPolicyFn(policy), [policy]); @@ -209,6 +211,7 @@ function MoneyRequestAttendeeSelector({attendees = [], onFinish, onAttendeesAdde initialSelectedOptions, orderedAvailableOptions.recentReports, orderedAvailableOptions.personalDetails, + privateIsArchivedMap[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${initialSelectedOptions.at(0)?.reportID}`], personalDetails, true, undefined, @@ -249,7 +252,8 @@ function MoneyRequestAttendeeSelector({attendees = [], onFinish, onAttendeesAdde title: undefined, data: [orderedAvailableOptions.userToInvite].map((participant) => { const isPolicyExpenseChat = participant?.isPolicyExpenseChat ?? false; - return isPolicyExpenseChat ? getPolicyExpenseReportOption(participant, reportAttributesDerived) : getParticipantsOption(participant, personalDetails); + const privateIsArchived = privateIsArchivedMap[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${participant?.reportID}`]; + return isPolicyExpenseChat ? getPolicyExpenseReportOption(participant, privateIsArchived, reportAttributesDerived) : getParticipantsOption(participant, personalDetails); }) as OptionData[], shouldShow: true, }); @@ -278,6 +282,7 @@ function MoneyRequestAttendeeSelector({attendees = [], onFinish, onAttendeesAdde loginList, countryCode, translate, + privateIsArchivedMap, ]); const optionLength = useMemo(() => { diff --git a/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx b/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx index 83fc46c0e8a6b..5b891177849e8 100644 --- a/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx +++ b/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx @@ -26,6 +26,7 @@ import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useOnyx from '@hooks/useOnyx'; import usePreferredPolicy from '@hooks/usePreferredPolicy'; +import usePrivateIsArchivedMap from '@hooks/usePrivateIsArchivedMap'; import useScreenWrapperTransitionStatus from '@hooks/useScreenWrapperTransitionStatus'; import useSearchSelector from '@hooks/useSearchSelector'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -121,6 +122,7 @@ function MoneyRequestParticipantsSelector({ const [reportAttributesDerived] = useOnyx(ONYXKEYS.DERIVED.REPORT_ATTRIBUTES, {canBeMissing: true, selector: reportsSelector}); const [countryCode = CONST.DEFAULT_COUNTRY_CODE] = useOnyx(ONYXKEYS.COUNTRY_CODE, {canBeMissing: false}); const [loginList] = useOnyx(ONYXKEYS.LOGIN_LIST, {canBeMissing: true}); + const privateIsArchivedMap = usePrivateIsArchivedMap(); const [textInputAutoFocus, setTextInputAutoFocus] = useState(!isNative); const selectionListRef = useRef(null); @@ -287,6 +289,7 @@ function MoneyRequestParticipantsSelector({ participants.map((participant) => ({...participant, reportID: participant.reportID})) as OptionData[], [], [], + privateIsArchivedMap[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${participants.at(0)?.reportID}`], personalDetails, true, undefined, @@ -338,7 +341,8 @@ function MoneyRequestParticipantsSelector({ title: undefined, data: [availableOptions.userToInvite].map((participant) => { const isPolicyExpenseChat = participant?.isPolicyExpenseChat ?? false; - return isPolicyExpenseChat ? getPolicyExpenseReportOption(participant, reportAttributesDerived) : getParticipantsOption(participant, personalDetails); + const privateIsArchived = privateIsArchivedMap[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${participant?.reportID}`]; + return isPolicyExpenseChat ? getPolicyExpenseReportOption(participant, privateIsArchived, reportAttributesDerived) : getParticipantsOption(participant, personalDetails); }), shouldShow: true, }); @@ -368,6 +372,7 @@ function MoneyRequestParticipantsSelector({ isPerDiemRequest, showImportContacts, inputHelperText, + privateIsArchivedMap, ]); /** diff --git a/src/selectors/ReportNameValuePairs.ts b/src/selectors/ReportNameValuePairs.ts index a33ccc3893139..a8be618103e1d 100644 --- a/src/selectors/ReportNameValuePairs.ts +++ b/src/selectors/ReportNameValuePairs.ts @@ -43,5 +43,10 @@ const privateIsArchivedMapSelector = (all: OnyxCollection) return map; }; -export {createReportNameValuePairsSelector, archivedReportsIdSetSelector, privateIsArchivedMapSelector}; +/** + * Selector that extracts only the private_isArchived value from a single ReportNameValuePairs entry + */ +const privateIsArchivedSelector = (reportNameValuePairs: OnyxEntry): string | undefined => reportNameValuePairs?.private_isArchived; + +export {createReportNameValuePairsSelector, archivedReportsIdSetSelector, privateIsArchivedMapSelector, privateIsArchivedSelector}; export type {PrivateIsArchivedMap}; diff --git a/tests/perf-test/OptionsListUtils.perf-test.ts b/tests/perf-test/OptionsListUtils.perf-test.ts index e6867e5337a58..f3d770c219442 100644 --- a/tests/perf-test/OptionsListUtils.perf-test.ts +++ b/tests/perf-test/OptionsListUtils.perf-test.ts @@ -227,6 +227,7 @@ describe('OptionsListUtils', () => { Object.values(selectedOptions), Object.values(filteredRecentReports), Object.values(filteredPersonalDetails), + undefined, mockedPersonalDetails, true, ), @@ -239,6 +240,6 @@ describe('OptionsListUtils', () => { const mockedPersonalDetails = getMockedPersonalDetails(PERSONAL_DETAILS_COUNT); await waitForBatchedUpdates(); - await measureFunction(() => formatSectionsFromSearchTerm('', Object.values(selectedOptions), [], [], mockedPersonalDetails, true)); + await measureFunction(() => formatSectionsFromSearchTerm('', Object.values(selectedOptions), [], [], undefined, mockedPersonalDetails, true)); }); }); diff --git a/tests/unit/OptionsListUtilsTest.tsx b/tests/unit/OptionsListUtilsTest.tsx index f0d67978153c4..87448218cc572 100644 --- a/tests/unit/OptionsListUtilsTest.tsx +++ b/tests/unit/OptionsListUtilsTest.tsx @@ -20,13 +20,16 @@ import { filterSelfDMChat, filterWorkspaceChats, formatMemberForList, + formatSectionsFromSearchTerm, getCurrentUserSearchTerms, getFilteredRecentAttendees, getLastActorDisplayName, getLastActorDisplayNameFromLastVisibleActions, getLastMessageTextForReport, getMemberInviteOptions, + getParticipantsOption, getPersonalDetailSearchTerms, + getPolicyExpenseReportOption, getReportDisplayOption, getReportOption, getSearchOptions, @@ -3978,4 +3981,115 @@ describe('OptionsListUtils', () => { expect(johnSmith).toBeDefined(); }); }); + + describe('formatSectionsFromSearchTerm()', () => { + it('should return selected options when search term is empty', () => { + const selectedOptions = [ + { + accountID: 1, + login: 'user1@example.com', + reportID: 'report1', + isPolicyExpenseChat: false, + } as OptionData, + ]; + + const result = formatSectionsFromSearchTerm('', selectedOptions, [], [], undefined, {}, false, [], undefined); + + expect(result.section.shouldShow).toBe(true); + expect(result.section.data).toHaveLength(1); + expect(result.section.data[0]).toEqual(selectedOptions[0]); + }); + + it('should filter selected options based on search term', () => { + const selectedOptions = [ + { + accountID: 1, + login: 'user1@example.com', + displayName: 'User One', + reportID: 'report1', + isPolicyExpenseChat: false, + participantsList: [{displayName: 'User One'}], + } as OptionData, + { + accountID: 2, + login: 'user2@example.com', + displayName: 'User Two', + reportID: 'report2', + isPolicyExpenseChat: false, + participantsList: [{displayName: 'User Two'}], + } as OptionData, + ]; + + const result = formatSectionsFromSearchTerm('user one', selectedOptions, [], [], undefined, {}, false, [], undefined); + + expect(result.section.data).toHaveLength(1); + expect((result.section.data[0] as OptionData).accountID).toBe(1); + }); + + it('should pass privateIsArchived correctly', () => { + const selectedOptions = [ + { + accountID: 1, + login: 'user1@example.com', + reportID: 'report1', + isPolicyExpenseChat: true, + } as OptionData, + ]; + const privateIsArchived = '2024-01-01'; + + const result = formatSectionsFromSearchTerm('', selectedOptions, [], [], privateIsArchived, {}, true, [], undefined); + + expect(result.section.shouldShow).toBe(true); + }); + + it('should not show section when no selected options', () => { + const result = formatSectionsFromSearchTerm('', [], [], [], undefined, {}, false, [], undefined); + + expect(result.section.shouldShow).toBe(false); + expect(result.section.data).toHaveLength(0); + }); + }); + + describe('getPolicyExpenseReportOption()', () => { + it('should return an option with correct properties', () => { + const participant = { + reportID: 'report123', + isPolicyExpenseChat: true, + selected: true, + } as Participant; + + const result = getPolicyExpenseReportOption(participant, undefined, undefined); + + expect(result).toBeDefined(); + expect(result.isSelected).toBe(true); + expect(result.selected).toBe(true); + }); + + it('should handle archived reports with privateIsArchived', () => { + const participant = { + reportID: 'report123', + isPolicyExpenseChat: true, + selected: false, + } as Participant; + const privateIsArchived = '2024-01-01'; + + const result = getPolicyExpenseReportOption(participant, privateIsArchived, undefined); + + expect(result).toBeDefined(); + expect(result.isSelected).toBe(false); + }); + + it('should handle undefined privateIsArchived', () => { + const participant = { + reportID: 'report456', + isPolicyExpenseChat: true, + selected: true, + } as Participant; + + const result = getPolicyExpenseReportOption(participant, undefined, undefined); + + expect(result).toBeDefined(); + expect(result.isSelected).toBe(true); + }); + }); }); From 72e62cb6d12faa247a18bff3532559a496d87705 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Sun, 25 Jan 2026 23:56:07 +0700 Subject: [PATCH 2/7] update comments --- src/components/Search/SearchFiltersChatsSelector.tsx | 2 +- src/components/Search/SearchFiltersParticipantsSelector.tsx | 2 +- src/libs/OptionsListUtils/index.ts | 4 +++- src/pages/NewChatPage.tsx | 2 +- src/pages/iou/request/MoneyRequestAccountantSelector.tsx | 2 +- src/pages/iou/request/MoneyRequestAttendeeSelector.tsx | 2 +- src/pages/iou/request/MoneyRequestParticipantsSelector.tsx | 2 +- tests/perf-test/OptionsListUtils.perf-test.ts | 4 ++-- 8 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/components/Search/SearchFiltersChatsSelector.tsx b/src/components/Search/SearchFiltersChatsSelector.tsx index 8d1544984baca..dad3fcb09a650 100644 --- a/src/components/Search/SearchFiltersChatsSelector.tsx +++ b/src/components/Search/SearchFiltersChatsSelector.tsx @@ -97,7 +97,7 @@ function SearchFiltersChatsSelector({initialReportIDs, onFiltersUpdate, isScreen selectedOptions, chatOptions.recentReports, chatOptions.personalDetails, - privateIsArchivedMap[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${selectedOptions.at(0)?.reportID}`], + privateIsArchivedMap, personalDetails, false, undefined, diff --git a/src/components/Search/SearchFiltersParticipantsSelector.tsx b/src/components/Search/SearchFiltersParticipantsSelector.tsx index ab6fb5d77821f..d7564a2b9f447 100644 --- a/src/components/Search/SearchFiltersParticipantsSelector.tsx +++ b/src/components/Search/SearchFiltersParticipantsSelector.tsx @@ -116,7 +116,7 @@ function SearchFiltersParticipantsSelector({initialAccountIDs, onFiltersUpdate}: selectedOptions, chatOptions.recentReports, chatOptions.personalDetails, - privateIsArchivedMap[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${selectedOptions.at(0)?.reportID}`], + privateIsArchivedMap, personalDetails, true, undefined, diff --git a/src/libs/OptionsListUtils/index.ts b/src/libs/OptionsListUtils/index.ts index 933d0e699de8f..34e87b1f3a183 100644 --- a/src/libs/OptionsListUtils/index.ts +++ b/src/libs/OptionsListUtils/index.ts @@ -2620,7 +2620,7 @@ function formatSectionsFromSearchTerm( selectedOptions: SearchOptionData[], filteredRecentReports: SearchOptionData[], filteredPersonalDetails: SearchOptionData[], - privateIsArchived: string | undefined, + privateIsArchivedMap: Record, personalDetails: OnyxEntry = {}, shouldGetOptionDetails = false, filteredWorkspaceChats: SearchOptionData[] = [], @@ -2636,6 +2636,7 @@ function formatSectionsFromSearchTerm( data: shouldGetOptionDetails ? selectedOptions.map((participant) => { const isReportPolicyExpenseChat = participant.isPolicyExpenseChat ?? false; + const privateIsArchived = privateIsArchivedMap[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${participant.reportID}`]; return isReportPolicyExpenseChat ? getPolicyExpenseReportOption(participant, privateIsArchived, personalDetails, reportAttributesDerived) : getParticipantsOption(participant, personalDetails); @@ -2664,6 +2665,7 @@ function formatSectionsFromSearchTerm( data: shouldGetOptionDetails ? selectedParticipantsWithoutDetails.map((participant) => { const isReportPolicyExpenseChat = participant.isPolicyExpenseChat ?? false; + const privateIsArchived = privateIsArchivedMap[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${participant.reportID}`]; return isReportPolicyExpenseChat ? getPolicyExpenseReportOption(participant, privateIsArchived, personalDetails, reportAttributesDerived) : getParticipantsOption(participant, personalDetails); diff --git a/src/pages/NewChatPage.tsx b/src/pages/NewChatPage.tsx index 061277a7d70e2..883793a96200c 100755 --- a/src/pages/NewChatPage.tsx +++ b/src/pages/NewChatPage.tsx @@ -265,7 +265,7 @@ function NewChatPage({ref}: NewChatPageProps) { selectedOptions as OptionData[], recentReports, personalDetails, - privateIsArchivedMap[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${selectedOptions.at(0)?.reportID}`], + privateIsArchivedMap, allPersonalDetails, undefined, undefined, diff --git a/src/pages/iou/request/MoneyRequestAccountantSelector.tsx b/src/pages/iou/request/MoneyRequestAccountantSelector.tsx index c3cea80142646..8e659b85cfec4 100644 --- a/src/pages/iou/request/MoneyRequestAccountantSelector.tsx +++ b/src/pages/iou/request/MoneyRequestAccountantSelector.tsx @@ -152,7 +152,7 @@ function MoneyRequestAccountantSelector({onFinish, onAccountantSelected, iouType [], chatOptions.recentReports, chatOptions.personalDetails, - privateIsArchivedMap[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${chatOptions.recentReports.at(0)?.reportID}`], + privateIsArchivedMap, personalDetails, true, undefined, diff --git a/src/pages/iou/request/MoneyRequestAttendeeSelector.tsx b/src/pages/iou/request/MoneyRequestAttendeeSelector.tsx index ab6f9f40dd57f..a59bf6639b3a3 100644 --- a/src/pages/iou/request/MoneyRequestAttendeeSelector.tsx +++ b/src/pages/iou/request/MoneyRequestAttendeeSelector.tsx @@ -209,7 +209,7 @@ function MoneyRequestAttendeeSelector({attendees = [], onFinish, onAttendeesAdde initialSelectedOptions, orderedAvailableOptions.recentReports, orderedAvailableOptions.personalDetails, - privateIsArchivedMap[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${initialSelectedOptions.at(0)?.reportID}`], + privateIsArchivedMap, personalDetails, true, undefined, diff --git a/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx b/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx index 605d7b80593fe..b6d8bf9029bcb 100644 --- a/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx +++ b/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx @@ -289,7 +289,7 @@ function MoneyRequestParticipantsSelector({ participants.map((participant) => ({...participant, reportID: participant.reportID})) as OptionData[], [], [], - privateIsArchivedMap[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${participants.at(0)?.reportID}`], + privateIsArchivedMap, personalDetails, true, undefined, diff --git a/tests/perf-test/OptionsListUtils.perf-test.ts b/tests/perf-test/OptionsListUtils.perf-test.ts index f3d770c219442..7b7ef8e4c1b24 100644 --- a/tests/perf-test/OptionsListUtils.perf-test.ts +++ b/tests/perf-test/OptionsListUtils.perf-test.ts @@ -227,7 +227,7 @@ describe('OptionsListUtils', () => { Object.values(selectedOptions), Object.values(filteredRecentReports), Object.values(filteredPersonalDetails), - undefined, + {}, mockedPersonalDetails, true, ), @@ -240,6 +240,6 @@ describe('OptionsListUtils', () => { const mockedPersonalDetails = getMockedPersonalDetails(PERSONAL_DETAILS_COUNT); await waitForBatchedUpdates(); - await measureFunction(() => formatSectionsFromSearchTerm('', Object.values(selectedOptions), [], [], undefined, mockedPersonalDetails, true)); + await measureFunction(() => formatSectionsFromSearchTerm('', Object.values(selectedOptions), [], [], {}, mockedPersonalDetails, true)); }); }); From e320f100662c388461d5cbfa4a719cbbf1b1c055 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Wed, 4 Feb 2026 15:20:05 +0700 Subject: [PATCH 3/7] add more tests --- tests/unit/OptionsListUtilsTest.tsx | 345 +++++++++++++++++++++++++++- 1 file changed, 344 insertions(+), 1 deletion(-) diff --git a/tests/unit/OptionsListUtilsTest.tsx b/tests/unit/OptionsListUtilsTest.tsx index 266b3585e8055..16d9176f4235f 100644 --- a/tests/unit/OptionsListUtilsTest.tsx +++ b/tests/unit/OptionsListUtilsTest.tsx @@ -10,7 +10,7 @@ import useReportIsArchived from '@hooks/useReportIsArchived'; import DateUtils from '@libs/DateUtils'; import {translate} from '@libs/Localize'; import isSearchTopmostFullScreenRoute from '@libs/Navigation/helpers/isSearchTopmostFullScreenRoute'; -import type {OptionList, Options, SearchOption} from '@libs/OptionsListUtils'; +import type {OptionList, Options, SearchOption, SearchOptionData} from '@libs/OptionsListUtils'; import { canCreateOptimisticPersonalDetailOption, createOption, @@ -20,6 +20,7 @@ import { filterSelfDMChat, filterWorkspaceChats, formatMemberForList, + formatSectionsFromSearchTerm, getCurrentUserSearchTerms, getLastActorDisplayName, getLastActorDisplayNameFromLastVisibleActions, @@ -4926,6 +4927,348 @@ describe('OptionsListUtils', () => { }); }); + describe('getPolicyExpenseReportOption with privateIsArchived', () => { + it('should set private_isArchived on the option when privateIsArchived is provided', async () => { + const reportID = '301'; + const testPolicyID = 'policy301'; + const ownerAccountID = 2001; + const archivedTimestamp = DateUtils.getDBTime(); + + const report: Report = { + reportID, + reportName: 'Archived Policy Expense', + type: CONST.REPORT.TYPE.CHAT, + chatType: CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT, + policyID: testPolicyID, + ownerAccountID, + participants: { + [ownerAccountID]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS}, + }, + }; + + const policy: Policy = { + id: testPolicyID, + name: 'Archived Workspace', + type: CONST.POLICY.TYPE.TEAM, + owner: 'owner@test.com', + role: 'user', + outputCurrency: 'USD', + isPolicyExpenseChatEnabled: true, + }; + + const testPersonalDetails = { + [ownerAccountID]: { + accountID: ownerAccountID, + displayName: 'Archived Owner', + login: 'archivedowner@test.com', + }, + }; + + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, report); + await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${testPolicyID}`, policy); + await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, testPersonalDetails); + await waitForBatchedUpdates(); + + const participant = { + reportID, + policyID: testPolicyID, + isPolicyExpenseChat: true, + selected: false, + }; + + const option = getPolicyExpenseReportOption(participant, archivedTimestamp, CURRENT_USER_ACCOUNT_ID, testPersonalDetails); + + expect(option).toBeDefined(); + expect(option.private_isArchived).toBe(archivedTimestamp); + expect(option.text).toBe('Archived Workspace'); + }); + + it('should set private_isArchived to undefined when privateIsArchived is not provided', async () => { + const reportID = '302'; + const testPolicyID = 'policy302'; + const ownerAccountID = 2002; + + const report: Report = { + reportID, + reportName: 'Active Policy Expense', + type: CONST.REPORT.TYPE.CHAT, + chatType: CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT, + policyID: testPolicyID, + ownerAccountID, + participants: { + [ownerAccountID]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS}, + }, + }; + + const policy: Policy = { + id: testPolicyID, + name: 'Active Workspace', + type: CONST.POLICY.TYPE.TEAM, + owner: 'owner@test.com', + role: 'user', + outputCurrency: 'USD', + isPolicyExpenseChatEnabled: true, + }; + + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, report); + await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${testPolicyID}`, policy); + await waitForBatchedUpdates(); + + const participant = { + reportID, + policyID: testPolicyID, + isPolicyExpenseChat: true, + }; + + const option = getPolicyExpenseReportOption(participant, undefined, CURRENT_USER_ACCOUNT_ID, {}); + + expect(option).toBeDefined(); + expect(option.private_isArchived).toBeUndefined(); + }); + }); + + describe('formatSectionsFromSearchTerm', () => { + const formatTestPolicyID = 'policyFormat1'; + const formatOwnerAccountID = 3001; + const formatMemberAccountID = 3002; + const formatReportID1 = '401'; + const formatReportID2 = '402'; + + const formatPolicy: Policy = { + id: formatTestPolicyID, + name: 'Format Test Workspace', + type: CONST.POLICY.TYPE.TEAM, + owner: 'formatowner@test.com', + role: 'admin', + outputCurrency: 'USD', + isPolicyExpenseChatEnabled: true, + }; + + const formatPersonalDetails = { + [formatOwnerAccountID]: { + accountID: formatOwnerAccountID, + displayName: 'Format Owner', + login: 'formatowner@test.com', + }, + [formatMemberAccountID]: { + accountID: formatMemberAccountID, + displayName: 'Format Member', + login: 'formatmember@test.com', + }, + }; + + beforeAll(async () => { + const report1: Report = { + reportID: formatReportID1, + reportName: 'Archived Format Chat', + type: CONST.REPORT.TYPE.CHAT, + chatType: CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT, + policyID: formatTestPolicyID, + ownerAccountID: formatOwnerAccountID, + participants: { + [formatOwnerAccountID]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS}, + }, + }; + + const report2: Report = { + reportID: formatReportID2, + reportName: 'Active Format Chat', + type: CONST.REPORT.TYPE.CHAT, + chatType: CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT, + policyID: formatTestPolicyID, + ownerAccountID: formatMemberAccountID, + participants: { + [formatMemberAccountID]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS}, + }, + }; + + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${formatReportID1}`, report1); + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${formatReportID2}`, report2); + await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${formatTestPolicyID}`, formatPolicy); + await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, formatPersonalDetails); + await waitForBatchedUpdates(); + }); + + it('should pass privateIsArchived from map to policy expense options when searchTerm is empty and shouldGetOptionDetails is true', () => { + const archivedTimestamp = DateUtils.getDBTime(); + const privateIsArchivedMap: Record = { + [`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${formatReportID1}`]: archivedTimestamp, + }; + + const selectedOptions: SearchOptionData[] = [ + { + reportID: formatReportID1, + isPolicyExpenseChat: true, + selected: true, + text: 'Format Test Workspace', + alternateText: '', + isSelected: true, + } as SearchOptionData, + ]; + + const result = formatSectionsFromSearchTerm('', selectedOptions, [], [], privateIsArchivedMap, CURRENT_USER_ACCOUNT_ID, formatPersonalDetails, true); + + expect(result.section.shouldShow).toBe(true); + expect(result.section.data).toHaveLength(1); + + const option = result.section.data.at(0) as SearchOptionData; + expect(option.private_isArchived).toBe(archivedTimestamp); + }); + + it('should not set private_isArchived when report is not in the archived map', () => { + const privateIsArchivedMap: Record = {}; + + const selectedOptions: SearchOptionData[] = [ + { + reportID: formatReportID2, + isPolicyExpenseChat: true, + selected: true, + text: 'Format Test Workspace', + alternateText: '', + isSelected: true, + } as SearchOptionData, + ]; + + const result = formatSectionsFromSearchTerm('', selectedOptions, [], [], privateIsArchivedMap, CURRENT_USER_ACCOUNT_ID, formatPersonalDetails, true); + + expect(result.section.shouldShow).toBe(true); + expect(result.section.data).toHaveLength(1); + + const option = result.section.data.at(0) as SearchOptionData; + expect(option.private_isArchived).toBeUndefined(); + }); + + it('should handle mix of archived and non-archived policy expense chats', () => { + const archivedTimestamp = DateUtils.getDBTime(); + const privateIsArchivedMap: Record = { + [`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${formatReportID1}`]: archivedTimestamp, + }; + + const selectedOptions: SearchOptionData[] = [ + { + reportID: formatReportID1, + isPolicyExpenseChat: true, + selected: true, + text: 'Archived Workspace', + alternateText: '', + isSelected: true, + } as SearchOptionData, + { + reportID: formatReportID2, + isPolicyExpenseChat: true, + selected: true, + text: 'Active Workspace', + alternateText: '', + isSelected: true, + } as SearchOptionData, + ]; + + const result = formatSectionsFromSearchTerm('', selectedOptions, [], [], privateIsArchivedMap, CURRENT_USER_ACCOUNT_ID, formatPersonalDetails, true); + + expect(result.section.shouldShow).toBe(true); + expect(result.section.data).toHaveLength(2); + + const archivedOption = result.section.data.at(0) as SearchOptionData; + const activeOption = result.section.data.at(1) as SearchOptionData; + expect(archivedOption.private_isArchived).toBe(archivedTimestamp); + expect(activeOption.private_isArchived).toBeUndefined(); + }); + + it('should not transform options when shouldGetOptionDetails is false', () => { + const archivedTimestamp = DateUtils.getDBTime(); + const privateIsArchivedMap: Record = { + [`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${formatReportID1}`]: archivedTimestamp, + }; + + const selectedOptions: SearchOptionData[] = [ + { + reportID: formatReportID1, + isPolicyExpenseChat: true, + selected: true, + text: 'Format Test Workspace', + alternateText: '', + isSelected: true, + } as SearchOptionData, + ]; + + const result = formatSectionsFromSearchTerm('', selectedOptions, [], [], privateIsArchivedMap, CURRENT_USER_ACCOUNT_ID, formatPersonalDetails, false); + + expect(result.section.shouldShow).toBe(true); + expect(result.section.data).toHaveLength(1); + // When shouldGetOptionDetails is false, the original selectedOptions are returned unchanged + expect(result.section.data.at(0)).toBe(selectedOptions.at(0)); + }); + + it('should pass privateIsArchived from map when searchTerm matches and participant is not in filtered lists', () => { + const archivedTimestamp = DateUtils.getDBTime(); + const privateIsArchivedMap: Record = { + [`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${formatReportID1}`]: archivedTimestamp, + }; + + const selectedOptions: SearchOptionData[] = [ + { + reportID: formatReportID1, + accountID: formatOwnerAccountID, + isPolicyExpenseChat: true, + selected: true, + text: 'Format Test Workspace', + alternateText: '', + isSelected: true, + login: 'formatowner@test.com', + displayName: 'Format Owner', + } as SearchOptionData, + ]; + + // Pass empty filtered lists so the selected option is not deduplicated + const result = formatSectionsFromSearchTerm('format', selectedOptions, [], [], privateIsArchivedMap, CURRENT_USER_ACCOUNT_ID, formatPersonalDetails, true); + + expect(result.section.shouldShow).toBe(true); + expect(result.section.data).toHaveLength(1); + + const option = result.section.data.at(0) as SearchOptionData; + expect(option.private_isArchived).toBe(archivedTimestamp); + }); + + it('should handle non-policy expense chat participants without privateIsArchived', () => { + const archivedTimestamp = DateUtils.getDBTime(); + const privateIsArchivedMap: Record = { + [`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${formatReportID1}`]: archivedTimestamp, + }; + + const selectedOptions: SearchOptionData[] = [ + { + reportID: formatReportID1, + accountID: formatOwnerAccountID, + isPolicyExpenseChat: false, + selected: true, + text: 'Format Owner', + alternateText: '', + isSelected: true, + login: 'formatowner@test.com', + displayName: 'Format Owner', + } as SearchOptionData, + ]; + + const result = formatSectionsFromSearchTerm('', selectedOptions, [], [], privateIsArchivedMap, CURRENT_USER_ACCOUNT_ID, formatPersonalDetails, true); + + expect(result.section.shouldShow).toBe(true); + expect(result.section.data).toHaveLength(1); + + // Non-policy expense chats go through getParticipantsOption, not getPolicyExpenseReportOption + // so private_isArchived is not set + const option = result.section.data.at(0); + expect(option).toBeDefined(); + expect((option as SearchOptionData).private_isArchived).toBeUndefined(); + }); + + it('should return empty section when no selectedOptions are provided', () => { + const result = formatSectionsFromSearchTerm('', [], [], [], {}, CURRENT_USER_ACCOUNT_ID, formatPersonalDetails, true); + + expect(result.section.shouldShow).toBe(false); + expect(result.section.data).toHaveLength(0); + }); + }); + describe('getUserToInviteOption', () => { it('should not return userToInvite for plain text name when shouldAcceptName is false', () => { const result = getUserToInviteOption({ From 1a941ba13ec46b18a62034a337e38605936380be Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Wed, 4 Feb 2026 15:27:57 +0700 Subject: [PATCH 4/7] add sentryLabel --- src/CONST/index.ts | 3 +++ src/pages/NewChatPage.tsx | 1 + 2 files changed, 4 insertions(+) diff --git a/src/CONST/index.ts b/src/CONST/index.ts index c65871151723c..9fe14aa14c1ba 100755 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -8167,6 +8167,9 @@ const CONST = { VIEW_BUTTON: 'ReportPreview-ViewButton', ADD_EXPENSE_BUTTON: 'ReportPreview-AddExpenseButton', }, + NEW_CHAT: { + PARTICIPANT_TOGGLE: 'NewChat-ParticipantToggle', + }, TRANSACTION_PREVIEW: { CARD: 'TransactionPreview-Card', }, diff --git a/src/pages/NewChatPage.tsx b/src/pages/NewChatPage.tsx index b3d702f46fe3b..98bfd4c45fb5d 100755 --- a/src/pages/NewChatPage.tsx +++ b/src/pages/NewChatPage.tsx @@ -413,6 +413,7 @@ function NewChatPage({ref}: NewChatPageProps) { accessibilityLabel={item.text ?? ''} accessibilityState={{checked: item.isSelected}} style={[styles.flexRow, styles.alignItemsCenter, styles.ml5, styles.optionSelectCircle]} + sentryLabel={CONST.SENTRY_LABEL.NEW_CHAT.PARTICIPANT_TOGGLE} > Date: Sat, 28 Feb 2026 10:53:00 +0700 Subject: [PATCH 5/7] update test --- tests/unit/OptionsListUtilsTest.tsx | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/tests/unit/OptionsListUtilsTest.tsx b/tests/unit/OptionsListUtilsTest.tsx index a4d0561ef213c..7d79a82d84b23 100644 --- a/tests/unit/OptionsListUtilsTest.tsx +++ b/tests/unit/OptionsListUtilsTest.tsx @@ -6185,7 +6185,7 @@ describe('OptionsListUtils', () => { selected: false, }; - const option = getPolicyExpenseReportOption(participant, undefined, archivedTimestamp, CURRENT_USER_ACCOUNT_ID, testPersonalDetails); + const option = getPolicyExpenseReportOption(participant, archivedTimestamp, CURRENT_USER_ACCOUNT_ID, testPersonalDetails, report, undefined); expect(option).toBeDefined(); expect(option.private_isArchived).toBe(archivedTimestamp); @@ -6229,7 +6229,7 @@ describe('OptionsListUtils', () => { isPolicyExpenseChat: true, }; - const option = getPolicyExpenseReportOption(participant, undefined, undefined, CURRENT_USER_ACCOUNT_ID, {}); + const option = getPolicyExpenseReportOption(participant, undefined, CURRENT_USER_ACCOUNT_ID, {}, report, undefined); expect(option).toBeDefined(); expect(option.private_isArchived).toBeUndefined(); @@ -6317,7 +6317,6 @@ describe('OptionsListUtils', () => { const result = formatSectionsFromSearchTerm('', selectedOptions, [], [], privateIsArchivedMap, CURRENT_USER_ACCOUNT_ID, formatPersonalDetails, true); - expect(result.section.shouldShow).toBe(true); expect(result.section.data).toHaveLength(1); const option = result.section.data.at(0) as SearchOptionData; @@ -6340,7 +6339,6 @@ describe('OptionsListUtils', () => { const result = formatSectionsFromSearchTerm('', selectedOptions, [], [], privateIsArchivedMap, CURRENT_USER_ACCOUNT_ID, formatPersonalDetails, true); - expect(result.section.shouldShow).toBe(true); expect(result.section.data).toHaveLength(1); const option = result.section.data.at(0) as SearchOptionData; @@ -6374,7 +6372,6 @@ describe('OptionsListUtils', () => { const result = formatSectionsFromSearchTerm('', selectedOptions, [], [], privateIsArchivedMap, CURRENT_USER_ACCOUNT_ID, formatPersonalDetails, true); - expect(result.section.shouldShow).toBe(true); expect(result.section.data).toHaveLength(2); const archivedOption = result.section.data.at(0) as SearchOptionData; @@ -6402,7 +6399,6 @@ describe('OptionsListUtils', () => { const result = formatSectionsFromSearchTerm('', selectedOptions, [], [], privateIsArchivedMap, CURRENT_USER_ACCOUNT_ID, formatPersonalDetails, false); - expect(result.section.shouldShow).toBe(true); expect(result.section.data).toHaveLength(1); // When shouldGetOptionDetails is false, the original selectedOptions are returned unchanged expect(result.section.data.at(0)).toBe(selectedOptions.at(0)); @@ -6431,7 +6427,6 @@ describe('OptionsListUtils', () => { // Pass empty filtered lists so the selected option is not deduplicated const result = formatSectionsFromSearchTerm('format', selectedOptions, [], [], privateIsArchivedMap, CURRENT_USER_ACCOUNT_ID, formatPersonalDetails, true); - expect(result.section.shouldShow).toBe(true); expect(result.section.data).toHaveLength(1); const option = result.section.data.at(0) as SearchOptionData; @@ -6460,7 +6455,6 @@ describe('OptionsListUtils', () => { const result = formatSectionsFromSearchTerm('', selectedOptions, [], [], privateIsArchivedMap, CURRENT_USER_ACCOUNT_ID, formatPersonalDetails, true); - expect(result.section.shouldShow).toBe(true); expect(result.section.data).toHaveLength(1); // Non-policy expense chats go through getParticipantsOption, not getPolicyExpenseReportOption @@ -6473,7 +6467,6 @@ describe('OptionsListUtils', () => { it('should return empty section when no selectedOptions are provided', () => { const result = formatSectionsFromSearchTerm('', [], [], [], {}, CURRENT_USER_ACCOUNT_ID, formatPersonalDetails, true); - expect(result.section.shouldShow).toBe(false); expect(result.section.data).toHaveLength(0); }); }); @@ -6780,7 +6773,7 @@ describe('OptionsListUtils', () => { }; // When we call getPolicyExpenseReportOption with report passed directly - const option = getPolicyExpenseReportOption(participant, CURRENT_USER_ACCOUNT_ID, PERSONAL_DETAILS, report, undefined); + const option = getPolicyExpenseReportOption(participant, undefined, CURRENT_USER_ACCOUNT_ID, PERSONAL_DETAILS, report, undefined); // Then the option should be created successfully expect(option).toBeDefined(); From 3539376a9b5317cc10c77faa5f7f405420983a8c Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Thu, 5 Mar 2026 17:33:45 +0700 Subject: [PATCH 6/7] update expenseReport --- src/libs/OptionsListUtils/index.ts | 4 ++-- src/pages/iou/request/MoneyRequestAccountantSelector.tsx | 2 +- src/pages/iou/request/MoneyRequestAttendeeSelector.tsx | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libs/OptionsListUtils/index.ts b/src/libs/OptionsListUtils/index.ts index cfe17c6935d12..5db0dfdd9ffae 100644 --- a/src/libs/OptionsListUtils/index.ts +++ b/src/libs/OptionsListUtils/index.ts @@ -2930,7 +2930,7 @@ function formatSectionsFromSearchTerm( // TODO: This allReports usage is temporary and will be removed once the full Onyx.connect() refactor is complete (https://github.com/Expensify/App/issues/66378) const expenseReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${participant.reportID}`]; const chatReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${expenseReport?.chatReportID}`]; - const privateIsArchived = privateIsArchivedMap[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${participant.reportID}`]; + const privateIsArchived = privateIsArchivedMap[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${expenseReport?.reportID}`]; return getPolicyExpenseReportOption(participant, privateIsArchived, currentUserAccountID, personalDetails, expenseReport, chatReport, reportAttributesDerived); } return getParticipantsOption(participant, personalDetails); @@ -2963,7 +2963,7 @@ function formatSectionsFromSearchTerm( // TODO: This allReports usage is temporary and will be removed once the full Onyx.connect() refactor is complete (https://github.com/Expensify/App/issues/66378) const expenseReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${participant.reportID}`]; const chatReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${expenseReport?.chatReportID}`]; - const privateIsArchived = privateIsArchivedMap[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${participant.reportID}`]; + const privateIsArchived = privateIsArchivedMap[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${expenseReport?.reportID}`]; return getPolicyExpenseReportOption(participant, privateIsArchived, currentUserAccountID, personalDetails, expenseReport, chatReport, reportAttributesDerived); } return getParticipantsOption(participant, personalDetails); diff --git a/src/pages/iou/request/MoneyRequestAccountantSelector.tsx b/src/pages/iou/request/MoneyRequestAccountantSelector.tsx index aca817d383c1e..bae64a517856d 100644 --- a/src/pages/iou/request/MoneyRequestAccountantSelector.tsx +++ b/src/pages/iou/request/MoneyRequestAccountantSelector.tsx @@ -194,7 +194,7 @@ function MoneyRequestAccountantSelector({onFinish, onAccountantSelected, iouType newSections.push({ data: [chatOptions.userToInvite].map((participant) => { const isPolicyExpenseChat = participant?.isPolicyExpenseChat ?? false; - const privateIsArchived = privateIsArchivedMap[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${participant?.reportID}`]; + const privateIsArchived = privateIsArchivedMap[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${userToInviteExpenseReport?.reportID}`]; return isPolicyExpenseChat ? getPolicyExpenseReportOption( participant, diff --git a/src/pages/iou/request/MoneyRequestAttendeeSelector.tsx b/src/pages/iou/request/MoneyRequestAttendeeSelector.tsx index 909e91b24c0c4..06280a108e786 100644 --- a/src/pages/iou/request/MoneyRequestAttendeeSelector.tsx +++ b/src/pages/iou/request/MoneyRequestAttendeeSelector.tsx @@ -255,7 +255,7 @@ function MoneyRequestAttendeeSelector({attendees = [], onFinish, onAttendeesAdde title: undefined, data: [orderedAvailableOptions.userToInvite].map((participant) => { const isPolicyExpenseChat = participant?.isPolicyExpenseChat ?? false; - const privateIsArchived = privateIsArchivedMap[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${participant?.reportID}`]; + const privateIsArchived = privateIsArchivedMap[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${userToInviteExpenseReport?.reportID}`]; return isPolicyExpenseChat ? getPolicyExpenseReportOption( participant, From 82992fb83eab02fc9274fb500b9a87b62841f6b0 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Thu, 5 Mar 2026 18:12:49 +0700 Subject: [PATCH 7/7] update expenseReport --- src/pages/iou/request/MoneyRequestParticipantsSelector.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx b/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx index 7708d6f54782f..1c26f10540478 100644 --- a/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx +++ b/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx @@ -349,7 +349,7 @@ function MoneyRequestParticipantsSelector({ title: undefined, data: [availableOptions.userToInvite].map((participant) => { const isPolicyExpenseChat = participant?.isPolicyExpenseChat ?? false; - const privateIsArchived = privateIsArchivedMap[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${participant?.reportID}`]; + const privateIsArchived = privateIsArchivedMap[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${userToInviteExpenseReport?.reportID}`]; return isPolicyExpenseChat ? getPolicyExpenseReportOption( participant,