From ecf08c9b20ed1e8e687edf20b696abfc453b36f1 Mon Sep 17 00:00:00 2001 From: truph01 Date: Mon, 30 Mar 2026 17:35:08 +0700 Subject: [PATCH 1/8] fix: refactor getLastMessageTextForReport -> createOptionFromReport --- src/components/OptionListContextProvider.tsx | 4 +- .../Search/SearchFiltersChatsSelector.tsx | 4 +- .../Search/SearchRouter/SearchRouter.tsx | 4 +- src/libs/OptionsListUtils/index.ts | 12 +- tests/unit/OptionsListUtilsTest.tsx | 116 ++++++++++++++++++ 5 files changed, 134 insertions(+), 6 deletions(-) diff --git a/src/components/OptionListContextProvider.tsx b/src/components/OptionListContextProvider.tsx index aa8eb937d4a7a..58aaac14ba1a5 100644 --- a/src/components/OptionListContextProvider.tsx +++ b/src/components/OptionListContextProvider.tsx @@ -1,3 +1,4 @@ +import {sortedActionsSelector} from '@selectors/SortedReportActions'; import React, {createContext, useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; @@ -66,6 +67,7 @@ function OptionsListContextProvider({children}: OptionsListProviderProps) { const personalDetails = usePersonalDetails(); const prevPersonalDetails = usePrevious(personalDetails); const privateIsArchivedMap = usePrivateIsArchivedMap(); + const [sortedActions] = useOnyx(ONYXKEYS.DERIVED.SORTED_REPORT_ACTIONS, {selector: sortedActionsSelector}); const currentUserPersonalDetails = useCurrentUserPersonalDetails(); const currentUserAccountID = currentUserPersonalDetails.accountID; const hasInitialData = useMemo(() => Object.keys(personalDetails ?? {}).length > 0, [personalDetails]); @@ -245,7 +247,7 @@ function OptionsListContextProvider({children}: OptionsListProviderProps) { const policy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${report.policyID}`]; const newReportOption = createOptionFromReport(report, personalDetails, currentUserAccountID, privateIsArchived, policy, reportAttributes?.reports, { showPersonalDetails: true, - }); + }, undefined, undefined, sortedActions); const replaceIndex = options.reports.findIndex((option) => option.reportID === report.reportID); newReportOptions.push({ newReportOption, diff --git a/src/components/Search/SearchFiltersChatsSelector.tsx b/src/components/Search/SearchFiltersChatsSelector.tsx index 91cb47cad2aab..1e4fcc3dc84a7 100644 --- a/src/components/Search/SearchFiltersChatsSelector.tsx +++ b/src/components/Search/SearchFiltersChatsSelector.tsx @@ -1,4 +1,5 @@ import passthroughPolicyTagListSelector from '@selectors/PolicyTagList'; +import {sortedActionsSelector} from '@selectors/SortedReportActions'; import React, {useEffect, useState} from 'react'; import {usePersonalDetails} from '@components/OnyxListItemProvider'; import {useOptionsList} from '@components/OptionListContextProvider'; @@ -67,13 +68,14 @@ function SearchFiltersChatsSelector({initialReportIDs, onFiltersUpdate, isScreen const privateIsArchivedMap = usePrivateIsArchivedMap(); const [nvpDismissedProductTraining] = useOnyx(ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING); const [policyTags] = useOnyx(ONYXKEYS.COLLECTION.POLICY_TAGS, {selector: passthroughPolicyTagListSelector}); + const [sortedActions] = useOnyx(ONYXKEYS.DERIVED.SORTED_REPORT_ACTIONS, {selector: sortedActionsSelector}); const selectedOptions: OptionData[] = selectedReportIDs.map((id) => { const privateIsArchived = privateIsArchivedMap[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${id}`]; const reportData = reports?.[`${ONYXKEYS.COLLECTION.REPORT}${id}`]; const reportPolicy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${reportData?.policyID}`]; const report = getSelectedOptionData( - createOptionFromReport({...reportData, reportID: id}, personalDetails, currentUserAccountID, privateIsArchived, reportPolicy, reportAttributesDerived), + createOptionFromReport({...reportData, reportID: id}, personalDetails, currentUserAccountID, privateIsArchived, reportPolicy, reportAttributesDerived, undefined, undefined, undefined, sortedActions), ); const isReportArchived = !!privateIsArchived; const policy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${reportData?.policyID}`]; diff --git a/src/components/Search/SearchRouter/SearchRouter.tsx b/src/components/Search/SearchRouter/SearchRouter.tsx index 176066be2a9fd..52cea3c4d8808 100644 --- a/src/components/Search/SearchRouter/SearchRouter.tsx +++ b/src/components/Search/SearchRouter/SearchRouter.tsx @@ -1,4 +1,5 @@ import {hasSeenTourSelector} from '@selectors/Onboarding'; +import {sortedActionsSelector} from '@selectors/SortedReportActions'; import {deepEqual} from 'fast-equals'; import React, {useCallback, useEffect, useRef, useState} from 'react'; import type {TextInputProps} from 'react-native'; @@ -71,6 +72,7 @@ function SearchRouter({onRouterClose, shouldHideInputCaret, isSearchRouterDispla const [policies] = useOnyx(ONYXKEYS.COLLECTION.POLICY); const [personalAndWorkspaceCards] = useOnyx(ONYXKEYS.DERIVED.PERSONAL_AND_WORKSPACE_CARD_LIST); const [allFeeds] = useOnyx(ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER); + const [sortedActions] = useOnyx(ONYXKEYS.DERIVED.SORTED_REPORT_ACTIONS, {selector: sortedActionsSelector}); const privateIsArchivedMap = usePrivateIsArchivedMap(); const {shouldUseNarrowLayout} = useResponsiveLayout(); const listRef = useRef(null); @@ -112,7 +114,7 @@ function SearchRouter({onRouterClose, shouldHideInputCaret, isSearchRouterDispla const privateIsArchived = privateIsArchivedMap[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${contextualReportID}`]; const reportPolicy = policies?.[`${ONYXKEYS.COLLECTION.POLICY}${report.policyID}`]; - const option = createOptionFromReport(report, personalDetails, currentUserAccountID, privateIsArchived, reportPolicy, undefined, {showPersonalDetails: true}); + const option = createOptionFromReport(report, personalDetails, currentUserAccountID, privateIsArchived, reportPolicy, undefined, {showPersonalDetails: true}, undefined, undefined, sortedActions); reportForContextualSearch = option; } diff --git a/src/libs/OptionsListUtils/index.ts b/src/libs/OptionsListUtils/index.ts index 5213346e69ef0..69afed44708da 100644 --- a/src/libs/OptionsListUtils/index.ts +++ b/src/libs/OptionsListUtils/index.ts @@ -608,6 +608,8 @@ function getLastMessageTextForReport({ policyTags, currentUserLogin, conciergeReportID, + // eslint-disable-next-line @typescript-eslint/no-deprecated + sortedActions = deprecatedAllSortedReportActions, }: { translate: LocalizedTranslate; report: OnyxEntry; @@ -625,6 +627,7 @@ function getLastMessageTextForReport({ currentUserLogin?: string; // TODO: conciergeReportID will be required eventually. Refactor issue: https://github.com/Expensify/App/issues/66411 conciergeReportID?: string; + sortedActions?: Record; }): string { const reportID = report?.reportID; const canUserPerformWrite = canUserPerformWriteAction(report, isReportArchived); @@ -695,8 +698,7 @@ function getLastMessageTextForReport({ const iouReportID = iouReport?.reportID; const reportCache = iouReportID ? visibleReportActionsDataParam?.[iouReportID] : undefined; const visibleReportActionsForIOUReport = reportCache && Object.keys(reportCache).length > 0 ? visibleReportActionsDataParam : undefined; - // eslint-disable-next-line @typescript-eslint/no-deprecated - const iouReportActions = iouReportID ? deprecatedAllSortedReportActions[iouReportID] : undefined; + const iouReportActions = iouReportID ? sortedActions?.[iouReportID] : undefined; const canPerformWrite = canUserPerformWriteAction(report, isReportArchived); const lastIOUMoneyReportAction = iouReportID && iouReportActions @@ -962,6 +964,7 @@ type CreateOptionParams = { translate?: LocalizedTranslate; // TODO: conciergeReportID will be required eventually. Refactor issue: https://github.com/Expensify/App/issues/66411 conciergeReportID?: string; + sortedActions?: Record; }; /** @@ -980,6 +983,7 @@ function createOption({ visibleReportActionsData = {}, translate, conciergeReportID, + sortedActions, }: CreateOptionParams): SearchOptionData { const {showChatPreviewLine = false, forcePolicyNamePreview = false, showPersonalDetails = false, selected, isSelected, isDisabled} = config ?? {}; @@ -1063,6 +1067,7 @@ function createOption({ reportAttributesDerived, policyTags, conciergeReportID, + sortedActions, }); result.alternateText = showPersonalDetails && personalDetail?.login @@ -1652,12 +1657,13 @@ function createOptionFromReport( config?: PreviewConfig, policyTags?: OnyxEntry, visibleReportActionsData: VisibleReportActionsDerivedValue = {}, + sortedActions?: Record, ) { const accountIDs = getParticipantsAccountIDsForDisplay(report); return { item: report, - ...createOption({accountIDs, personalDetails, report, currentUserAccountID, privateIsArchived, policy, config, reportAttributesDerived, policyTags, visibleReportActionsData}), + ...createOption({accountIDs, personalDetails, report, currentUserAccountID, privateIsArchived, policy, config, reportAttributesDerived, policyTags, visibleReportActionsData, sortedActions}), }; } diff --git a/tests/unit/OptionsListUtilsTest.tsx b/tests/unit/OptionsListUtilsTest.tsx index 0f0286598fdee..9cbefe7f9f3fe 100644 --- a/tests/unit/OptionsListUtilsTest.tsx +++ b/tests/unit/OptionsListUtilsTest.tsx @@ -4070,6 +4070,87 @@ describe('OptionsListUtils', () => { expect(formattedMessage).toBe('$1.00 for A A A'); }); }); + describe('sortedActions parameter', () => { + it('should use sortedActions parameter instead of deprecated module variable for REPORT_PREVIEW', async () => { + const iouReport: Report = { + ...createRandomReport(10, undefined), + type: CONST.REPORT.TYPE.IOU, + isWaitingOnBankAccount: false, + currency: CONST.CURRENCY.USD, + total: 200, + unheldTotal: 200, + }; + const report: Report = { + ...createRandomReport(11, undefined), + isOwnPolicyExpenseChat: false, + }; + const reportPreviewAction: ReportAction = { + ...createRandomReportAction(1), + actionName: CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW, + childMoneyRequestCount: 1, + message: [{type: 'COMMENT', text: ''}], + originalMessage: { + linkedReportID: iouReport.reportID, + }, + shouldShow: true, + }; + const iouAction: ReportAction = { + ...createRandomReportAction(2), + reportID: iouReport.reportID, + actionName: CONST.REPORT.ACTIONS.TYPE.IOU, + message: [{type: 'COMMENT', text: ''}], + originalMessage: { + IOUTransactionID: 'txn1', + type: 'create', + }, + shouldShow: true, + }; + const transaction: Transaction = { + ...createRandomTransaction(0), + amount: 200, + currency: CONST.CURRENCY.USD, + merchant: '', + modifiedMerchant: '', + comment: {comment: 'test'}, + }; + + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`, iouReport); + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`, report); + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`, { + [reportPreviewAction.reportActionID]: reportPreviewAction, + }); + await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`, transaction); + await waitForBatchedUpdates(); + + const sortedActions: Record = { + [iouReport.reportID]: [iouAction], + }; + + const result = getLastMessageTextForReport({ + translate: translateLocal, + report, + lastActorDetails: null, + sortedActions, + }); + + expect(typeof result).toBe('string'); + }); + + it('should handle undefined sortedActions gracefully', () => { + const report: Report = { + ...createRandomReport(12, undefined), + }; + + const result = getLastMessageTextForReport({ + translate: translateLocal, + report, + lastActorDetails: null, + sortedActions: undefined, + }); + + expect(typeof result).toBe('string'); + }); + }); it('MOVED_TRANSACTION action', async () => { const mockIsSearchTopmostFullScreenRoute = jest.mocked(isSearchTopmostFullScreenRoute); mockIsSearchTopmostFullScreenRoute.mockReturnValue(false); @@ -6819,6 +6900,41 @@ describe('OptionsListUtils', () => { expect(result).toBeDefined(); expect(result.reportID).toBe('1'); }); + + it('should forward sortedActions parameter without errors', () => { + const report: Report = { + reportID: '1', + reportName: 'Report with sortedActions', + type: CONST.REPORT.TYPE.CHAT, + participants: { + [CURRENT_USER_ACCOUNT_ID]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS}, + 1: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS}, + }, + }; + + const sortedActions: Record = {}; + const result = createOptionFromReport(report, PERSONAL_DETAILS, CURRENT_USER_ACCOUNT_ID, undefined, undefined, undefined, undefined, undefined, undefined, sortedActions); + + expect(result).toBeDefined(); + expect(result.reportID).toBe('1'); + }); + + it('should work when sortedActions is undefined', () => { + const report: Report = { + reportID: '1', + reportName: 'Report without sortedActions', + type: CONST.REPORT.TYPE.CHAT, + participants: { + [CURRENT_USER_ACCOUNT_ID]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS}, + 1: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS}, + }, + }; + + const result = createOptionFromReport(report, PERSONAL_DETAILS, CURRENT_USER_ACCOUNT_ID, undefined, undefined, undefined, undefined, undefined, undefined, undefined); + + expect(result).toBeDefined(); + expect(result.reportID).toBe('1'); + }); }); describe('createFilteredOptionList', () => { From b121414124c417b0879f093a3678fb392b1065a7 Mon Sep 17 00:00:00 2001 From: truph01 Date: Mon, 30 Mar 2026 17:50:33 +0700 Subject: [PATCH 2/8] fix: refactor getLastMessageTextForReport -> getAlternateText -> createOptionFromReport --- .../Search/SearchFiltersChatsSelector.tsx | 2 +- src/libs/OptionsListUtils/index.ts | 3 ++ tests/unit/OptionsListUtilsTest.tsx | 34 +++++++++++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/components/Search/SearchFiltersChatsSelector.tsx b/src/components/Search/SearchFiltersChatsSelector.tsx index 1e4fcc3dc84a7..f22fdb27045b5 100644 --- a/src/components/Search/SearchFiltersChatsSelector.tsx +++ b/src/components/Search/SearchFiltersChatsSelector.tsx @@ -80,7 +80,7 @@ function SearchFiltersChatsSelector({initialReportIDs, onFiltersUpdate, isScreen const isReportArchived = !!privateIsArchived; const policy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${reportData?.policyID}`]; const reportPolicyTags = policyTags?.[`${ONYXKEYS.COLLECTION.POLICY_TAGS}${getNonEmptyStringOnyxID(report?.policyID)}`]; - const alternateText = getAlternateText(report, {}, isReportArchived, currentUserAccountID, policy, {}, undefined, undefined, reportAttributesDerived, reportPolicyTags); + const alternateText = getAlternateText(report, {}, isReportArchived, currentUserAccountID, policy, {}, undefined, undefined, reportAttributesDerived, reportPolicyTags, sortedActions); return {...report, alternateText}; }); diff --git a/src/libs/OptionsListUtils/index.ts b/src/libs/OptionsListUtils/index.ts index 69afed44708da..cdfa98e71f30b 100644 --- a/src/libs/OptionsListUtils/index.ts +++ b/src/libs/OptionsListUtils/index.ts @@ -437,6 +437,7 @@ function getAlternateText( translate?: LocalizedTranslate, reportAttributesDerived?: ReportAttributesDerivedValue['reports'], policyTags?: OnyxEntry, + sortedActions?: Record, ) { const report = getReportOrDraftReport(option.reportID); const isAdminRoom = reportUtilsIsAdminRoom(report); @@ -456,6 +457,7 @@ function getAlternateText( visibleReportActionsDataParam: visibleReportActionsData, reportAttributesDerived, policyTags, + sortedActions, }); const reportPrefix = getReportSubtitlePrefix(report); const formattedLastMessageTextWithPrefix = reportPrefix + formattedLastMessageText; @@ -1083,6 +1085,7 @@ function createOption({ translateFn, reportAttributesDerived, policyTags, + sortedActions, ); const computedReportName = getReportName(report, reportAttributesDerived); diff --git a/tests/unit/OptionsListUtilsTest.tsx b/tests/unit/OptionsListUtilsTest.tsx index 9cbefe7f9f3fe..6739a365babf8 100644 --- a/tests/unit/OptionsListUtilsTest.tsx +++ b/tests/unit/OptionsListUtilsTest.tsx @@ -20,6 +20,7 @@ import { createOptionList, filterAndOrderOptions, filterReports, + getAlternateText, filterSelfDMChat, filterWorkspaceChats, formatMemberForList, @@ -6937,6 +6938,39 @@ describe('OptionsListUtils', () => { }); }); + describe('getAlternateText', () => { + it('should forward sortedActions to getLastMessageTextForReport without errors', () => { + const option: OptionData = { + reportID: '1', + lastMessageText: '', + isMoneyRequestReport: false, + isThread: false, + isChatRoom: false, + isSelected: false, + }; + + const sortedActions: Record = {}; + const result = getAlternateText(option, {}, false, CURRENT_USER_ACCOUNT_ID, undefined, {}, {}, undefined, undefined, undefined, sortedActions); + + expect(typeof result).toBe('string'); + }); + + it('should work when sortedActions is undefined', () => { + const option: OptionData = { + reportID: '1', + lastMessageText: '', + isMoneyRequestReport: false, + isThread: false, + isChatRoom: false, + isSelected: false, + }; + + const result = getAlternateText(option, {}, false, CURRENT_USER_ACCOUNT_ID, undefined, {}, {}, undefined, undefined, undefined, undefined); + + expect(typeof result).toBe('string'); + }); + }); + describe('createFilteredOptionList', () => { it('should return report options limited by maxRecentReports', () => { const result = createFilteredOptionList(PERSONAL_DETAILS, REPORTS, CURRENT_USER_ACCOUNT_ID, undefined, {}, undefined, {maxRecentReports: 5}); From a77f2a3e5e33532a4eb233bceb5ddbf7769b0d78 Mon Sep 17 00:00:00 2001 From: truph01 Date: Mon, 30 Mar 2026 23:11:11 +0700 Subject: [PATCH 3/8] fix: refactor getSearchOptions --- src/components/Search/SearchAutocompleteList.tsx | 4 ++++ src/components/Search/SearchFiltersChatsSelector.tsx | 1 + src/hooks/useAutocompleteSuggestions.ts | 6 +++++- src/hooks/useSearchSelector.base.ts | 1 + src/libs/OptionsListUtils/index.ts | 4 ++++ src/pages/Share/ShareTab.tsx | 3 +++ 6 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/components/Search/SearchAutocompleteList.tsx b/src/components/Search/SearchAutocompleteList.tsx index 2af5a351b24f8..220ddfc2c35b3 100644 --- a/src/components/Search/SearchAutocompleteList.tsx +++ b/src/components/Search/SearchAutocompleteList.tsx @@ -1,3 +1,4 @@ +import {sortedActionsSelector} from '@selectors/SortedReportActions'; import type {ForwardedRef, RefObject} from 'react'; import React, {useContext, useEffect, useMemo, useRef, useState} from 'react'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; @@ -159,6 +160,7 @@ function SearchAutocompleteList({ const [loginList] = useOnyx(ONYXKEYS.LOGIN_LIST); const [policies = getEmptyObject>>()] = useOnyx(ONYXKEYS.COLLECTION.POLICY); const [visibleReportActionsData] = useOnyx(ONYXKEYS.DERIVED.VISIBLE_REPORT_ACTIONS); + const [sortedActions] = useOnyx(ONYXKEYS.DERIVED.SORTED_REPORT_ACTIONS, {selector: sortedActionsSelector}); const currentUserPersonalDetails = useCurrentUserPersonalDetails(); const currentUserEmail = currentUserPersonalDetails.email ?? ''; const currentUserAccountID = currentUserPersonalDetails.accountID; @@ -213,6 +215,7 @@ function SearchAutocompleteList({ currentUserEmail, policyCollection: policies, personalDetails, + sortedActions, }); })(); @@ -285,6 +288,7 @@ function SearchAutocompleteList({ loginList, policies, visibleReportActionsData, + sortedActions, currentUserAccountID, currentUserEmail, personalDetails, diff --git a/src/components/Search/SearchFiltersChatsSelector.tsx b/src/components/Search/SearchFiltersChatsSelector.tsx index f22fdb27045b5..e869474a26ab7 100644 --- a/src/components/Search/SearchFiltersChatsSelector.tsx +++ b/src/components/Search/SearchFiltersChatsSelector.tsx @@ -99,6 +99,7 @@ function SearchFiltersChatsSelector({initialReportIDs, onFiltersUpdate, isScreen currentUserEmail, personalDetails, policyCollection: allPolicies, + sortedActions, }); const chatOptions = filterAndOrderOptions(defaultOptions, cleanSearchTerm, countryCode, loginList, currentUserEmail, currentUserAccountID, personalDetails, { diff --git a/src/hooks/useAutocompleteSuggestions.ts b/src/hooks/useAutocompleteSuggestions.ts index 8eabc5be71abb..946bdc3c79030 100644 --- a/src/hooks/useAutocompleteSuggestions.ts +++ b/src/hooks/useAutocompleteSuggestions.ts @@ -20,7 +20,7 @@ import {getUserFriendlyKey, getUserFriendlyValue} from '@libs/SearchQueryUtils'; import {getDatePresets, getHasOptions} from '@libs/SearchUIUtils'; import CONST, {CONTINUATION_DETECTION_SEARCH_FILTER_KEYS} from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {Beta, CardFeeds, CardList, DismissedProductTraining, PersonalDetailsList, Policy} from '@src/types/onyx'; +import type {Beta, CardFeeds, CardList, DismissedProductTraining, PersonalDetailsList, Policy, ReportAction} from '@src/types/onyx'; import type {VisibleReportActionsDerivedValue} from '@src/types/onyx/DerivedValues'; import type {SearchDataTypes} from '@src/types/onyx/SearchResults'; import {useCurrencyListState} from './useCurrencyList'; @@ -47,6 +47,7 @@ type UseAutocompleteSuggestionsParams = { loginList: OnyxEntry>; policies: NonNullable>; visibleReportActionsData?: VisibleReportActionsDerivedValue; + sortedActions?: Record; currentUserAccountID: number; currentUserEmail: string; personalDetails: OnyxEntry; @@ -93,6 +94,7 @@ function useAutocompleteSuggestions({ loginList, policies, visibleReportActionsData, + sortedActions, currentUserAccountID, currentUserEmail, personalDetails, @@ -232,6 +234,7 @@ function useAutocompleteSuggestions({ currentUserAccountID, currentUserEmail, personalDetails, + sortedActions, }).personalDetails.filter((participant) => participant.text && !alreadyAutocompletedKeys.has(participant.text.toLowerCase())); return participants.map((participant) => ({ @@ -268,6 +271,7 @@ function useAutocompleteSuggestions({ currentUserAccountID, currentUserEmail, personalDetails, + sortedActions, }).recentReports.filter((chat) => { if (!chat.text) { return false; diff --git a/src/hooks/useSearchSelector.base.ts b/src/hooks/useSearchSelector.base.ts index 081200980c45e..fbb91cc456142 100644 --- a/src/hooks/useSearchSelector.base.ts +++ b/src/hooks/useSearchSelector.base.ts @@ -238,6 +238,7 @@ function useSearchSelectorBase({ currentUserAccountID, currentUserEmail, personalDetails, + sortedActions, }); case CONST.SEARCH_SELECTOR.SEARCH_CONTEXT_MEMBER_INVITE: return getValidOptions(optionsWithContacts, allPolicies, draftComments, nvpDismissedProductTraining, loginList, currentUserAccountID, currentUserEmail, { diff --git a/src/libs/OptionsListUtils/index.ts b/src/libs/OptionsListUtils/index.ts index cdfa98e71f30b..4af33cd02661f 100644 --- a/src/libs/OptionsListUtils/index.ts +++ b/src/libs/OptionsListUtils/index.ts @@ -2329,6 +2329,7 @@ function prepareReportOptionsForDisplay( undefined, reportAttributesDerived, reportPolicyTags, + sortedActions, ); const isSelected = isReportSelected(option, selectedOptions); @@ -2749,6 +2750,7 @@ type SearchOptionsConfig = { personalDetails?: OnyxEntry; reportAttributesDerived?: ReportAttributesDerivedValue['reports']; allPolicyTags?: OnyxCollection; + sortedActions?: Record; }; /** @@ -2777,6 +2779,7 @@ function getSearchOptions({ reportAttributesDerived, personalDetails, allPolicyTags, + sortedActions, }: SearchOptionsConfig): Options { const optionList = getValidOptions(options, policyCollection, draftComments, nvpDismissedProductTraining, loginList, currentUserAccountID, currentUserEmail, { betas, @@ -2803,6 +2806,7 @@ function getSearchOptions({ visibleReportActionsData, reportAttributesDerived, allPolicyTags, + sortedActions, }); return optionList; diff --git a/src/pages/Share/ShareTab.tsx b/src/pages/Share/ShareTab.tsx index 69c6211b1d933..0ac3fc0a15646 100644 --- a/src/pages/Share/ShareTab.tsx +++ b/src/pages/Share/ShareTab.tsx @@ -1,3 +1,4 @@ +import {sortedActionsSelector} from '@selectors/SortedReportActions'; import type {Ref} from 'react'; import React, {useEffect, useImperativeHandle, useRef} from 'react'; import {View} from 'react-native'; @@ -54,6 +55,7 @@ function ShareTab({ref}: ShareTabProps) { const [draftComments] = useOnyx(ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT); const [nvpDismissedProductTraining] = useOnyx(ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING); const [visibleReportActionsData] = useOnyx(ONYXKEYS.DERIVED.VISIBLE_REPORT_ACTIONS); + const [sortedActions] = useOnyx(ONYXKEYS.DERIVED.SORTED_REPORT_ACTIONS, {selector: sortedActionsSelector}); const currentUserPersonalDetails = useCurrentUserPersonalDetails(); const currentUserAccountID = currentUserPersonalDetails.accountID; const currentUserEmail = currentUserPersonalDetails.email ?? ''; @@ -92,6 +94,7 @@ function ShareTab({ref}: ShareTabProps) { currentUserEmail, policyCollection: allPolicies, personalDetails, + sortedActions, }) : defaultListOptions; From 8d2205f3a926a3498661757f45f35056b18a5cf3 Mon Sep 17 00:00:00 2001 From: truph01 Date: Mon, 30 Mar 2026 23:12:12 +0700 Subject: [PATCH 4/8] fix: prettier --- src/components/OptionListContextProvider.tsx | 17 +++++++++--- .../Search/SearchFiltersChatsSelector.tsx | 27 +++++++++++++++++-- .../Search/SearchRouter/SearchRouter.tsx | 13 ++++++++- src/libs/OptionsListUtils/index.ts | 14 +++++++++- tests/unit/OptionsListUtilsTest.tsx | 2 +- 5 files changed, 65 insertions(+), 8 deletions(-) diff --git a/src/components/OptionListContextProvider.tsx b/src/components/OptionListContextProvider.tsx index 58aaac14ba1a5..9555906b43fa6 100644 --- a/src/components/OptionListContextProvider.tsx +++ b/src/components/OptionListContextProvider.tsx @@ -245,9 +245,20 @@ function OptionsListContextProvider({children}: OptionsListProviderProps) { const privateIsArchived = privateIsArchivedMap[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report.reportID}`]; const policy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${report.policyID}`]; - const newReportOption = createOptionFromReport(report, personalDetails, currentUserAccountID, privateIsArchived, policy, reportAttributes?.reports, { - showPersonalDetails: true, - }, undefined, undefined, sortedActions); + const newReportOption = createOptionFromReport( + report, + personalDetails, + currentUserAccountID, + privateIsArchived, + policy, + reportAttributes?.reports, + { + showPersonalDetails: true, + }, + undefined, + undefined, + sortedActions, + ); const replaceIndex = options.reports.findIndex((option) => option.reportID === report.reportID); newReportOptions.push({ newReportOption, diff --git a/src/components/Search/SearchFiltersChatsSelector.tsx b/src/components/Search/SearchFiltersChatsSelector.tsx index e869474a26ab7..fff3704976585 100644 --- a/src/components/Search/SearchFiltersChatsSelector.tsx +++ b/src/components/Search/SearchFiltersChatsSelector.tsx @@ -75,12 +75,35 @@ function SearchFiltersChatsSelector({initialReportIDs, onFiltersUpdate, isScreen const reportData = reports?.[`${ONYXKEYS.COLLECTION.REPORT}${id}`]; const reportPolicy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${reportData?.policyID}`]; const report = getSelectedOptionData( - createOptionFromReport({...reportData, reportID: id}, personalDetails, currentUserAccountID, privateIsArchived, reportPolicy, reportAttributesDerived, undefined, undefined, undefined, sortedActions), + createOptionFromReport( + {...reportData, reportID: id}, + personalDetails, + currentUserAccountID, + privateIsArchived, + reportPolicy, + reportAttributesDerived, + undefined, + undefined, + undefined, + sortedActions, + ), ); const isReportArchived = !!privateIsArchived; const policy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${reportData?.policyID}`]; const reportPolicyTags = policyTags?.[`${ONYXKEYS.COLLECTION.POLICY_TAGS}${getNonEmptyStringOnyxID(report?.policyID)}`]; - const alternateText = getAlternateText(report, {}, isReportArchived, currentUserAccountID, policy, {}, undefined, undefined, reportAttributesDerived, reportPolicyTags, sortedActions); + const alternateText = getAlternateText( + report, + {}, + isReportArchived, + currentUserAccountID, + policy, + {}, + undefined, + undefined, + reportAttributesDerived, + reportPolicyTags, + sortedActions, + ); return {...report, alternateText}; }); diff --git a/src/components/Search/SearchRouter/SearchRouter.tsx b/src/components/Search/SearchRouter/SearchRouter.tsx index 52cea3c4d8808..31309f81304e2 100644 --- a/src/components/Search/SearchRouter/SearchRouter.tsx +++ b/src/components/Search/SearchRouter/SearchRouter.tsx @@ -114,7 +114,18 @@ function SearchRouter({onRouterClose, shouldHideInputCaret, isSearchRouterDispla const privateIsArchived = privateIsArchivedMap[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${contextualReportID}`]; const reportPolicy = policies?.[`${ONYXKEYS.COLLECTION.POLICY}${report.policyID}`]; - const option = createOptionFromReport(report, personalDetails, currentUserAccountID, privateIsArchived, reportPolicy, undefined, {showPersonalDetails: true}, undefined, undefined, sortedActions); + const option = createOptionFromReport( + report, + personalDetails, + currentUserAccountID, + privateIsArchived, + reportPolicy, + undefined, + {showPersonalDetails: true}, + undefined, + undefined, + sortedActions, + ); reportForContextualSearch = option; } diff --git a/src/libs/OptionsListUtils/index.ts b/src/libs/OptionsListUtils/index.ts index 4af33cd02661f..1d0dc8405fe79 100644 --- a/src/libs/OptionsListUtils/index.ts +++ b/src/libs/OptionsListUtils/index.ts @@ -1666,7 +1666,19 @@ function createOptionFromReport( return { item: report, - ...createOption({accountIDs, personalDetails, report, currentUserAccountID, privateIsArchived, policy, config, reportAttributesDerived, policyTags, visibleReportActionsData, sortedActions}), + ...createOption({ + accountIDs, + personalDetails, + report, + currentUserAccountID, + privateIsArchived, + policy, + config, + reportAttributesDerived, + policyTags, + visibleReportActionsData, + sortedActions, + }), }; } diff --git a/tests/unit/OptionsListUtilsTest.tsx b/tests/unit/OptionsListUtilsTest.tsx index 6739a365babf8..182cf5437ac26 100644 --- a/tests/unit/OptionsListUtilsTest.tsx +++ b/tests/unit/OptionsListUtilsTest.tsx @@ -20,11 +20,11 @@ import { createOptionList, filterAndOrderOptions, filterReports, - getAlternateText, filterSelfDMChat, filterWorkspaceChats, formatMemberForList, formatSectionsFromSearchTerm, + getAlternateText, getCurrentUserSearchTerms, getFilteredRecentAttendees, getIOUReportIDOfLastAction, From 1ba4a754347bb8d4207797181b68cd0219edf921 Mon Sep 17 00:00:00 2001 From: truph01 Date: Mon, 30 Mar 2026 23:30:56 +0700 Subject: [PATCH 5/8] fix: lint --- src/components/Search/SearchFiltersChatsSelector.tsx | 1 - src/libs/OptionsListUtils/index.ts | 4 ---- tests/unit/OptionsListUtilsTest.tsx | 6 ++++-- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/components/Search/SearchFiltersChatsSelector.tsx b/src/components/Search/SearchFiltersChatsSelector.tsx index fff3704976585..1bebad168f51e 100644 --- a/src/components/Search/SearchFiltersChatsSelector.tsx +++ b/src/components/Search/SearchFiltersChatsSelector.tsx @@ -95,7 +95,6 @@ function SearchFiltersChatsSelector({initialReportIDs, onFiltersUpdate, isScreen report, {}, isReportArchived, - currentUserAccountID, policy, {}, undefined, diff --git a/src/libs/OptionsListUtils/index.ts b/src/libs/OptionsListUtils/index.ts index 1d0dc8405fe79..2d0f5fe27a525 100644 --- a/src/libs/OptionsListUtils/index.ts +++ b/src/libs/OptionsListUtils/index.ts @@ -429,7 +429,6 @@ function getAlternateText( option: OptionData, {showChatPreviewLine = false, forcePolicyNamePreview = false}: PreviewConfig, isReportArchived: boolean | undefined, - currentUserAccountID: number, // We'll make it required in the next PR. Ref: https://github.com/Expensify/App/issues/66415 policy?: OnyxEntry, lastActorDetails: Partial | null = {}, @@ -976,7 +975,6 @@ function createOption({ accountIDs, personalDetails, report, - currentUserAccountID, privateIsArchived, policy, config, @@ -1078,7 +1076,6 @@ function createOption({ result, {showChatPreviewLine, forcePolicyNamePreview}, !!result.private_isArchived, - currentUserAccountID, policy, lastActorDetails, visibleReportActionsData, @@ -2334,7 +2331,6 @@ function prepareReportOptionsForDisplay( option, {showChatPreviewLine, forcePolicyNamePreview}, !!option.private_isArchived, - currentUserAccountID, policy, null, visibleReportActionsData, diff --git a/tests/unit/OptionsListUtilsTest.tsx b/tests/unit/OptionsListUtilsTest.tsx index 182cf5437ac26..1818401221637 100644 --- a/tests/unit/OptionsListUtilsTest.tsx +++ b/tests/unit/OptionsListUtilsTest.tsx @@ -6947,10 +6947,11 @@ describe('OptionsListUtils', () => { isThread: false, isChatRoom: false, isSelected: false, + keyForList: 'option-1', }; const sortedActions: Record = {}; - const result = getAlternateText(option, {}, false, CURRENT_USER_ACCOUNT_ID, undefined, {}, {}, undefined, undefined, undefined, sortedActions); + const result = getAlternateText(option, {}, false, undefined, {}, {}, undefined, undefined, undefined, sortedActions); expect(typeof result).toBe('string'); }); @@ -6963,9 +6964,10 @@ describe('OptionsListUtils', () => { isThread: false, isChatRoom: false, isSelected: false, + keyForList: 'option-1', }; - const result = getAlternateText(option, {}, false, CURRENT_USER_ACCOUNT_ID, undefined, {}, {}, undefined, undefined, undefined, undefined); + const result = getAlternateText(option, {}, false, undefined, {}, {}, undefined, undefined, undefined, undefined); expect(typeof result).toBe('string'); }); From 786f594285867b202bc77f67ae1a650f5bdfffe5 Mon Sep 17 00:00:00 2001 From: truph01 Date: Mon, 30 Mar 2026 23:34:29 +0700 Subject: [PATCH 6/8] fix: prettier --- .../Search/SearchFiltersChatsSelector.tsx | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/components/Search/SearchFiltersChatsSelector.tsx b/src/components/Search/SearchFiltersChatsSelector.tsx index 1bebad168f51e..a364525f2d0ce 100644 --- a/src/components/Search/SearchFiltersChatsSelector.tsx +++ b/src/components/Search/SearchFiltersChatsSelector.tsx @@ -91,18 +91,7 @@ function SearchFiltersChatsSelector({initialReportIDs, onFiltersUpdate, isScreen const isReportArchived = !!privateIsArchived; const policy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${reportData?.policyID}`]; const reportPolicyTags = policyTags?.[`${ONYXKEYS.COLLECTION.POLICY_TAGS}${getNonEmptyStringOnyxID(report?.policyID)}`]; - const alternateText = getAlternateText( - report, - {}, - isReportArchived, - policy, - {}, - undefined, - undefined, - reportAttributesDerived, - reportPolicyTags, - sortedActions, - ); + const alternateText = getAlternateText(report, {}, isReportArchived, policy, {}, undefined, undefined, reportAttributesDerived, reportPolicyTags, sortedActions); return {...report, alternateText}; }); From 027492970d3b12579629e50d804608ffc37141cb Mon Sep 17 00:00:00 2001 From: truph01 Date: Mon, 30 Mar 2026 23:55:39 +0700 Subject: [PATCH 7/8] fix: unit tests --- src/components/Search/SearchRouter/SearchRouter.tsx | 1 + tests/unit/OptionListContextProviderTest.tsx | 2 +- tests/unit/useSearchSelectorTest.tsx | 5 +++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/Search/SearchRouter/SearchRouter.tsx b/src/components/Search/SearchRouter/SearchRouter.tsx index 31309f81304e2..fe5f79775a414 100644 --- a/src/components/Search/SearchRouter/SearchRouter.tsx +++ b/src/components/Search/SearchRouter/SearchRouter.tsx @@ -190,6 +190,7 @@ function SearchRouter({onRouterClose, shouldHideInputCaret, isSearchRouterDispla currentUserAccountID, privateIsArchivedMap, policies, + sortedActions, ], ); diff --git a/tests/unit/OptionListContextProviderTest.tsx b/tests/unit/OptionListContextProviderTest.tsx index 76650bb527dd2..8638d726d106f 100644 --- a/tests/unit/OptionListContextProviderTest.tsx +++ b/tests/unit/OptionListContextProviderTest.tsx @@ -223,6 +223,6 @@ describe('OptionListContextProvider', () => { mockUsePersonalDetails.mockReturnValue(updatedPersonalDetails); rerender({shouldInitialize: false}); - expect(mockCreateOptionFromReport).toHaveBeenCalledWith(report, updatedPersonalDetails, expect.any(Number), 'true', undefined, undefined, {showPersonalDetails: true}); + expect(mockCreateOptionFromReport).toHaveBeenCalledWith(report, updatedPersonalDetails, expect.any(Number), 'true', undefined, undefined, {showPersonalDetails: true}, undefined, undefined, undefined); }); }); diff --git a/tests/unit/useSearchSelectorTest.tsx b/tests/unit/useSearchSelectorTest.tsx index 5d45fa237f93f..fdab5e53a7397 100644 --- a/tests/unit/useSearchSelectorTest.tsx +++ b/tests/unit/useSearchSelectorTest.tsx @@ -227,7 +227,7 @@ describe('useSearchSelector sortedActions integration', () => { expect(latestCallConfig?.sortedActions).toEqual(updatedData.sortedActions); }); - it('does not pass sortedActions to getSearchOptions for SEARCH context', async () => { + it('passes sortedActions to getSearchOptions for SEARCH context', async () => { const mockData = buildMockSortedActions(['1']); await act(async () => { @@ -246,6 +246,7 @@ describe('useSearchSelector sortedActions integration', () => { expect(mockGetSearchOptions).toHaveBeenCalled(); const lastSearchCall = mockGetSearchOptions.mock.calls.at(-1); const searchConfig = lastSearchCall?.[0]; - expect(searchConfig).not.toHaveProperty('sortedActions'); + expect(searchConfig).toHaveProperty('sortedActions'); + expect(searchConfig?.sortedActions).toEqual(mockData.sortedActions); }); }); From 36560847a7a31d7cc22a7480e08adfde79632979 Mon Sep 17 00:00:00 2001 From: truph01 Date: Mon, 30 Mar 2026 23:57:35 +0700 Subject: [PATCH 8/8] fix: prettier --- tests/unit/OptionListContextProviderTest.tsx | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/tests/unit/OptionListContextProviderTest.tsx b/tests/unit/OptionListContextProviderTest.tsx index 8638d726d106f..9464c4f4ef67e 100644 --- a/tests/unit/OptionListContextProviderTest.tsx +++ b/tests/unit/OptionListContextProviderTest.tsx @@ -223,6 +223,17 @@ describe('OptionListContextProvider', () => { mockUsePersonalDetails.mockReturnValue(updatedPersonalDetails); rerender({shouldInitialize: false}); - expect(mockCreateOptionFromReport).toHaveBeenCalledWith(report, updatedPersonalDetails, expect.any(Number), 'true', undefined, undefined, {showPersonalDetails: true}, undefined, undefined, undefined); + expect(mockCreateOptionFromReport).toHaveBeenCalledWith( + report, + updatedPersonalDetails, + expect.any(Number), + 'true', + undefined, + undefined, + {showPersonalDetails: true}, + undefined, + undefined, + undefined, + ); }); });