diff --git a/src/components/Search/SearchFiltersChatsSelector.tsx b/src/components/Search/SearchFiltersChatsSelector.tsx index 1cdbbc341c29..c1297480b3c7 100644 --- a/src/components/Search/SearchFiltersChatsSelector.tsx +++ b/src/components/Search/SearchFiltersChatsSelector.tsx @@ -66,7 +66,7 @@ function SearchFiltersChatsSelector({initialReportIDs, onFiltersUpdate, isScreen }, [areOptionsInitialized, isScreenTransitionEnd, options]); const chatOptions = useMemo(() => { - return OptionsListUtils.filterOptions(defaultOptions, cleanSearchTerm, { + return OptionsListUtils.filterAndOrderOptions(defaultOptions, cleanSearchTerm, { selectedOptions, excludeLogins: CONST.EXPENSIFY_EMAILS, maxRecentReportsToShow: 0, diff --git a/src/components/Search/SearchFiltersParticipantsSelector.tsx b/src/components/Search/SearchFiltersParticipantsSelector.tsx index fc692c01f824..6fec134a608a 100644 --- a/src/components/Search/SearchFiltersParticipantsSelector.tsx +++ b/src/components/Search/SearchFiltersParticipantsSelector.tsx @@ -54,7 +54,7 @@ function SearchFiltersParticipantsSelector({initialAccountIDs, onFiltersUpdate}: return defaultListOptions; } - return OptionsListUtils.getOptions( + return OptionsListUtils.getValidOptions( { reports: options.reports, personalDetails: options.personalDetails, @@ -62,13 +62,12 @@ function SearchFiltersParticipantsSelector({initialAccountIDs, onFiltersUpdate}: { selectedOptions, excludeLogins: CONST.EXPENSIFY_EMAILS, - maxRecentReportsToShow: 0, }, ); }, [areOptionsInitialized, options.personalDetails, options.reports, selectedOptions]); const chatOptions = useMemo(() => { - return OptionsListUtils.filterOptions(defaultOptions, cleanSearchTerm, { + return OptionsListUtils.filterAndOrderOptions(defaultOptions, cleanSearchTerm, { selectedOptions, excludeLogins: CONST.EXPENSIFY_EMAILS, maxRecentReportsToShow: CONST.IOU.MAX_RECENT_REPORTS_TO_SHOW, diff --git a/src/components/Search/SearchRouter/SearchRouter.tsx b/src/components/Search/SearchRouter/SearchRouter.tsx index 3ee0874ff4fe..02b81717a28b 100644 --- a/src/components/Search/SearchRouter/SearchRouter.tsx +++ b/src/components/Search/SearchRouter/SearchRouter.tsx @@ -90,7 +90,7 @@ function SearchRouter({onRouterClose, shouldHideInputCaret}: SearchRouterProps) } Timing.start(CONST.TIMING.SEARCH_FILTER_OPTIONS); - const newOptions = OptionsListUtils.filterOptions(searchOptions, debouncedInputValue, {sortByReportTypeInSearch: true, preferChatroomsOverThreads: true}); + const newOptions = OptionsListUtils.filterAndOrderOptions(searchOptions, debouncedInputValue, {sortByReportTypeInSearch: true, preferChatroomsOverThreads: true}); Timing.end(CONST.TIMING.SEARCH_FILTER_OPTIONS); return { diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index 9e5ec3b18729..8fa8898bfec6 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -1,7 +1,6 @@ /* eslint-disable no-continue */ import {Str} from 'expensify-common'; import lodashOrderBy from 'lodash/orderBy'; -import lodashSortBy from 'lodash/sortBy'; import Onyx from 'react-native-onyx'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import type {SetNonNullable} from 'type-fest'; @@ -93,13 +92,11 @@ type GetOptionsConfig = { reportActions?: ReportActions; betas?: OnyxEntry; selectedOptions?: Option[]; - maxRecentReportsToShow?: number; excludeLogins?: string[]; includeMultipleParticipantReports?: boolean; includeRecentReports?: boolean; includeSelfDM?: boolean; showChatPreviewLine?: boolean; - sortPersonalDetailsByAlphaAsc?: boolean; forcePolicyNamePreview?: boolean; includeOwnedWorkspaceChats?: boolean; includeThreads?: boolean; @@ -153,12 +150,16 @@ type Options = { type PreviewConfig = {showChatPreviewLine?: boolean; forcePolicyNamePreview?: boolean; showPersonalDetails?: boolean}; -type FilterOptionsConfig = Pick & { +type FilterOptionsConfig = Pick & { + /* When sortByReportTypeInSearch flag is true, recentReports will include the personalDetails options as well. */ + sortByReportTypeInSearch?: boolean; + maxRecentReportsToShow?: number; +}; + +type OrderOptionsConfig = { preferChatroomsOverThreads?: boolean; preferPolicyExpenseChat?: boolean; preferRecentExpenseReports?: boolean; - /* When sortByReportTypeInSearch flag is true, recentReports will include the personalDetails options as well. */ - sortByReportTypeInSearch?: boolean; }; /** @@ -640,6 +641,7 @@ function createOption( policyID: undefined, isOptimisticPersonalDetail: false, lastMessageText: '', + lastVisibleActionCreated: undefined, }; const personalDetailMap = getPersonalDetailsForAccountIDs(accountIDs, personalDetails); @@ -676,6 +678,7 @@ function createOption( result.policyID = report.policyID; result.isSelfDM = ReportUtils.isSelfDM(report); result.notificationPreference = ReportUtils.getReportNotificationPreference(report); + result.lastVisibleActionCreated = report.lastVisibleActionCreated; const visibleParticipantAccountIDs = ReportUtils.getParticipantsAccountIDsForDisplay(report, true); @@ -917,20 +920,26 @@ function createOptionFromReport(report: Report, personalDetails: OnyxEntry personalDetail.text?.toLowerCase()], 'asc'); +} + /** - * Options need to be sorted in the specific order + * Report Options need to be sorted in the specific order * @param options - list of options to be sorted * @param searchValue - search string * @returns a sorted list of options */ -function orderOptions( +function orderReportOptions( options: ReportUtils.OptionData[], searchValue: string | undefined, - {preferChatroomsOverThreads = false, preferPolicyExpenseChat = false, preferRecentExpenseReports = false} = {}, + {preferChatroomsOverThreads = false, preferPolicyExpenseChat = false, preferRecentExpenseReports = false}: OrderOptionsConfig = {}, ) { return lodashOrderBy( options, [ + (option) => option?.lastVisibleActionCreated ?? 0, (option) => { if (option.isPolicyExpenseChat && preferPolicyExpenseChat && option.policyID === activePolicyID) { return 0; @@ -965,10 +974,20 @@ function orderOptions( preferRecentExpenseReports ? (option) => option?.lastIOUCreationDate ?? '' : '', preferRecentExpenseReports ? (option) => option?.isPolicyExpenseChat : 0, ], - ['asc', 'desc', 'desc'], + ['desc', 'asc', 'desc', 'desc'], ); } +function orderOptions(options: Pick, searchValue?: string, config?: OrderOptionsConfig) { + const orderedReportOptions = orderReportOptions(options.recentReports, searchValue, config); + const orderedPersonalDetailsOptions = orderPersonalDetailsOptions(options.personalDetails); + + return { + recentReports: orderedReportOptions, + personalDetails: orderedPersonalDetailsOptions, + }; +} + function canCreateOptimisticPersonalDetailOption({ recentReportOptions, personalDetailsOptions, @@ -1043,20 +1062,18 @@ function getUserToInviteOption({ } /** - * filter options based on specific conditions + * Options are reports and personal details. This function filters out the options that are not valid to be displayed. */ -function getOptions( +function getValidOptions( options: OptionList, { reportActions = {}, betas = [], selectedOptions = [], - maxRecentReportsToShow = 0, excludeLogins = [], includeMultipleParticipantReports = false, includeRecentReports = true, showChatPreviewLine = false, - sortPersonalDetailsByAlphaAsc = true, forcePolicyNamePreview = false, includeOwnedWorkspaceChats = false, includeThreads = false, @@ -1096,25 +1113,11 @@ function getOptions( }); }); - // Sorting the reports works like this: - // - Order everything by the last message timestamp (descending) - // - When searching, self DM is put at the top - // - All archived reports should remain at the bottom - const orderedReportOptions = lodashSortBy(filteredReportOptions, (option) => { - const report = option.item; - if (option.private_isArchived) { - return CONST.DATE.UNIX_EPOCH; - } - - return report?.lastVisibleActionCreated; - }); - orderedReportOptions.reverse(); - - const allReportOptions = orderedReportOptions.filter((option) => { + const allReportOptions = filteredReportOptions.filter((option) => { const report = option.item; if (!report) { - return; + return false; } const isThread = option.isThread; @@ -1126,51 +1129,46 @@ function getOptions( const accountIDs = ReportUtils.getParticipantsAccountIDsForDisplay(report); if (isPolicyExpenseChat && report.isOwnPolicyExpenseChat && !includeOwnedWorkspaceChats) { - return; + return false; } // When passing includeP2P false we are trying to hide features from users that are not ready for P2P and limited to workspace chats only. if (!includeP2P && !isPolicyExpenseChat) { - return; + return false; } if (isSelfDM && !includeSelfDM) { - return; + return false; } if (isThread && !includeThreads) { - return; + return false; } if (isTaskReport && !includeTasks) { - return; + return false; } if (isMoneyRequestReport && !includeMoneyRequests) { - return; + return false; } // In case user needs to add credit bank account, don't allow them to submit an expense from the workspace. if (includeOwnedWorkspaceChats && ReportUtils.hasIOUWaitingOnCurrentUserBankAccount(report)) { - return; + return false; } if ((!accountIDs || accountIDs.length === 0) && !isChatRoom) { - return; + return false; } - return option; + return true; }); const havingLoginPersonalDetails = includeP2P ? options.personalDetails.filter((detail) => !!detail?.login && !!detail.accountID && !detail?.isOptimisticPersonalDetail && (includeDomainEmail || !Str.isDomainEmail(detail.login))) : []; - let allPersonalDetailsOptions = havingLoginPersonalDetails; - - if (sortPersonalDetailsByAlphaAsc) { - // PersonalDetails should be ordered Alphabetically by default - https://github.com/Expensify/App/issues/8220#issuecomment-1104009435 - allPersonalDetailsOptions = lodashOrderBy(allPersonalDetailsOptions, [(personalDetail) => personalDetail.text?.toLowerCase()], 'asc'); - } + const allPersonalDetailsOptions = havingLoginPersonalDetails; const optionsToExclude: Option[] = [{login: CONST.EMAIL.NOTIFICATIONS}]; @@ -1198,11 +1196,6 @@ function getOptions( */ reportOption.alternateText = getAlternateText(reportOption, {showChatPreviewLine, forcePolicyNamePreview}); - // Stop adding options to the recentReports array when we reach the maxRecentReportsToShow value - if (recentReportOptions.length > 0 && recentReportOptions.length === maxRecentReportsToShow) { - break; - } - // Skip notifications@expensify.com if (reportOption.login === CONST.EMAIL.NOTIFICATIONS) { continue; @@ -1317,10 +1310,9 @@ function getOptions( function getSearchOptions(options: OptionList, betas: Beta[] = [], isUsedInChatFinder = true): Options { Timing.start(CONST.TIMING.LOAD_SEARCH_OPTIONS); Performance.markStart(CONST.TIMING.LOAD_SEARCH_OPTIONS); - const optionList = getOptions(options, { + const optionList = getValidOptions(options, { betas, includeMultipleParticipantReports: true, - maxRecentReportsToShow: 0, // Unlimited showChatPreviewLine: isUsedInChatFinder, includeP2P: true, forcePolicyNamePreview: true, @@ -1338,7 +1330,7 @@ function getSearchOptions(options: OptionList, betas: Beta[] = [], isUsedInChatF } function getShareLogOptions(options: OptionList, betas: Beta[] = []): Options { - return getOptions(options, { + return getValidOptions(options, { betas, includeMultipleParticipantReports: true, includeP2P: true, @@ -1384,7 +1376,7 @@ function getAttendeeOptions( includeInvoiceRooms = false, action: IOUAction | undefined = undefined, ) { - return getOptions( + return getValidOptions( {reports, personalDetails}, { betas, @@ -1395,7 +1387,6 @@ function getAttendeeOptions( includeP2P, canInviteUser, includeSelectedOptions: false, - maxRecentReportsToShow: 0, includeSelfDM: false, includeInvoiceRooms, action, @@ -1417,12 +1408,11 @@ function getShareDestinationOptions( includeOwnedWorkspaceChats = true, excludeUnknownUsers = true, ) { - return getOptions( + return getValidOptions( {reports, personalDetails}, { betas, selectedOptions, - maxRecentReportsToShow: 0, // Unlimited includeMultipleParticipantReports: true, showChatPreviewLine: true, forcePolicyNamePreview: true, @@ -1474,17 +1464,23 @@ function getMemberInviteOptions( reports: Array> = [], includeRecentReports = false, ): Options { - return getOptions( + const options = getValidOptions( {reports, personalDetails}, { betas, includeP2P: true, excludeLogins, - sortPersonalDetailsByAlphaAsc: true, includeSelectedOptions, includeRecentReports, }, ); + + const orderedOptions = orderOptions(options); + return { + ...options, + personalDetails: orderedOptions.personalDetails, + recentReports: orderedOptions.recentReports, + }; } /** @@ -1617,108 +1613,149 @@ function filteredPersonalDetailsOfRecentReports(recentReports: ReportUtils.Optio return personalDetails.filter((personalDetail) => !excludedLogins.has(personalDetail.login)); } -/** - * Filters options based on the search input value - */ function filterOptions(options: Options, searchInputValue: string, config?: FilterOptionsConfig): Options { - const { - sortByReportTypeInSearch = false, - canInviteUser = true, - maxRecentReportsToShow = 0, - excludeLogins = [], - preferChatroomsOverThreads = false, - preferPolicyExpenseChat = false, - preferRecentExpenseReports = false, - } = config ?? {}; - if (searchInputValue.trim() === '' && maxRecentReportsToShow > 0) { - const recentReports = options.recentReports.slice(0, maxRecentReportsToShow); - const personalDetails = filteredPersonalDetailsOfRecentReports(recentReports, options.personalDetails); - return { - ...options, - recentReports, - personalDetails, - }; - } - const parsedPhoneNumber = PhoneNumber.parsePhoneNumber(LoginUtils.appendCountryCode(Str.removeSMSDomain(searchInputValue))); const searchValue = parsedPhoneNumber.possible && parsedPhoneNumber.number?.e164 ? parsedPhoneNumber.number.e164 : searchInputValue.toLowerCase(); const searchTerms = searchValue ? searchValue.split(' ') : []; - const optionsToExclude: Option[] = [{login: CONST.EMAIL.NOTIFICATIONS}]; + const recentReports = filterReports(options.recentReports, searchTerms); + const personalDetails = filterPersonalDetails(options.personalDetails, searchTerms); - excludeLogins.forEach((login) => { - optionsToExclude.push({login}); - }); + const {canInviteUser = true, excludeLogins = []} = config ?? {}; + let userToInvite = null; + if (canInviteUser) { + if (recentReports.length === 0 && personalDetails.length === 0) { + const optionsToExclude: Option[] = [{login: CONST.EMAIL.NOTIFICATIONS}]; - const matchResults = searchTerms.reduceRight((items, term) => { - const recentReports = filterArrayByMatch(items.recentReports, term, (item) => { - const values: string[] = []; - if (item.text) { - values.push(item.text); - } + excludeLogins.forEach((login) => { + optionsToExclude.push({login}); + }); - if (item.login) { - values.push(item.login); - values.push(item.login.replace(CONST.EMAIL_SEARCH_REGEX, '')); - } + userToInvite = getUserToInviteOption({ + searchValue, + selectedOptions: config?.selectedOptions, + optionsToExclude, + }); + } + } + + const currentUserOption = filterCurrentUserOption(options.currentUserOption, searchTerms); + + return { + personalDetails, + recentReports, + userToInvite, + currentUserOption, + }; +} - if (item.isThread) { - if (item.alternateText) { - values.push(item.alternateText); +/** + * Filters options based on the search input value + */ +function filterReports(reports: ReportUtils.OptionData[], searchTerms: string[]): ReportUtils.OptionData[] { + // We search eventually for multiple whitespace separated search terms. + // We start with the search term at the end, and then narrow down those filtered search results with the next search term. + // We repeat (reduce) this until all search terms have been used: + const filteredReports = searchTerms.reduceRight( + (items, term) => + filterArrayByMatch(items, term, (item) => { + const values: string[] = []; + if (item.text) { + values.push(item.text); } - } else if (!!item.isChatRoom || !!item.isPolicyExpenseChat) { - if (item.subtitle) { - values.push(item.subtitle); + + if (item.login) { + values.push(item.login); + values.push(item.login.replace(CONST.EMAIL_SEARCH_REGEX, '')); } - } - return uniqFast(values); - }); - const personalDetails = filterArrayByMatch(items.personalDetails, term, (item) => uniqFast(getPersonalDetailSearchTerms(item))); + if (item.isThread) { + if (item.alternateText) { + values.push(item.alternateText); + } + } else if (!!item.isChatRoom || !!item.isPolicyExpenseChat) { + if (item.subtitle) { + values.push(item.subtitle); + } + } - const currentUserOptionSearchText = items.currentUserOption ? uniqFast(getCurrentUserSearchTerms(items.currentUserOption)).join(' ') : ''; + return uniqFast(values); + }), + // We start from all unfiltered reports: + reports, + ); + + return filteredReports; +} + +function filterPersonalDetails(personalDetails: ReportUtils.OptionData[], searchTerms: string[]): ReportUtils.OptionData[] { + return searchTerms.reduceRight( + (items, term) => + filterArrayByMatch(items, term, (item) => { + const values = getPersonalDetailSearchTerms(item); + return uniqFast(values); + }), + personalDetails, + ); +} + +function filterCurrentUserOption(currentUserOption: ReportUtils.OptionData | null | undefined, searchTerms: string[]): ReportUtils.OptionData | null | undefined { + return searchTerms.reduceRight((item, term) => { + if (!item) { + return null; + } + + const currentUserOptionSearchText = uniqFast(getCurrentUserSearchTerms(item)).join(' '); + return isSearchStringMatch(term, currentUserOptionSearchText) ? item : null; + }, currentUserOption); +} + +type FilterAndOrderConfig = FilterOptionsConfig & OrderOptionsConfig; +function filterAndOrderOptions(options: Options, searchInputValue: string, config: FilterAndOrderConfig = {}): Options { + const {maxRecentReportsToShow = 0, sortByReportTypeInSearch = false} = config; + + // Fast route: there's no search input value and we're limiting the number of recent reports to show + if (searchInputValue.trim().length === 0 && maxRecentReportsToShow > 0) { + const {personalDetails, recentReports} = orderOptions(options, searchInputValue, config); + const limitedRecentReports = recentReports.slice(0, maxRecentReportsToShow); + const filteredPersonalDetails = filteredPersonalDetailsOfRecentReports(limitedRecentReports, personalDetails); - const currentUserOption = isSearchStringMatch(term, currentUserOptionSearchText) ? items.currentUserOption : null; return { - recentReports: recentReports ?? [], - personalDetails: personalDetails ?? [], - userToInvite: null, - currentUserOption, + ...options, + personalDetails: filteredPersonalDetails, + recentReports: limitedRecentReports, }; - }, options); - - const {recentReports, personalDetails} = matchResults; + } - const personalDetailsWithoutDMs = filteredPersonalDetailsOfRecentReports(recentReports, personalDetails); + const filteredOptions = filterOptions(options, searchInputValue, config); + const personalDetailsWithoutDMs = filteredPersonalDetailsOfRecentReports(filteredOptions.recentReports, filteredOptions.personalDetails); + // sortByReportTypeInSearch option will show the personal details as part of the recent reports let filteredPersonalDetails: ReportUtils.OptionData[] = personalDetailsWithoutDMs; - let filteredRecentReports: ReportUtils.OptionData[] = recentReports; + let filteredRecentReports: ReportUtils.OptionData[] = filteredOptions.recentReports; if (sortByReportTypeInSearch) { - filteredRecentReports = recentReports.concat(personalDetailsWithoutDMs); + filteredRecentReports = filteredOptions.recentReports.concat(personalDetailsWithoutDMs); filteredPersonalDetails = []; } - let userToInvite = null; - if (canInviteUser) { - if (recentReports.length === 0 && personalDetails.length === 0) { - userToInvite = getUserToInviteOption({ - searchValue, - selectedOptions: config?.selectedOptions, - optionsToExclude, - }); - } - } + const orderedOptions = orderOptions( + { + recentReports: filteredRecentReports, + personalDetails: filteredPersonalDetails, + }, + searchInputValue, + config, + ); - if (maxRecentReportsToShow > 0 && recentReports.length > maxRecentReportsToShow) { - recentReports.splice(maxRecentReportsToShow); + let orderedReports = orderedOptions.recentReports; + if (config?.maxRecentReportsToShow) { + orderedReports = orderedReports.slice(0, config.maxRecentReportsToShow); } - const sortedRecentReports = orderOptions(filteredRecentReports, searchValue, {preferChatroomsOverThreads, preferPolicyExpenseChat, preferRecentExpenseReports}); return { - personalDetails: filteredPersonalDetails, - recentReports: sortedRecentReports, - userToInvite, - currentUserOption: matchResults.currentUserOption, + ...filteredOptions, + recentReports: orderedReports, + personalDetails: orderedOptions.personalDetails, }; } @@ -1744,7 +1781,7 @@ export { getAvatarsForAccountIDs, isCurrentUser, isPersonalDetailsReady, - getOptions, + getValidOptions, getSearchOptions, getShareDestinationOptions, getMemberInviteOptions, @@ -1769,7 +1806,10 @@ export { getShareLogOptions, filterOptions, filteredPersonalDetailsOfRecentReports, + orderReportOptions, + orderPersonalDetailsOptions, orderOptions, + filterAndOrderOptions, createOptionList, createOptionFromReport, getReportOption, diff --git a/src/pages/InviteReportParticipantsPage.tsx b/src/pages/InviteReportParticipantsPage.tsx index 2b2f7bbe25b8..d8c5f8c7585b 100644 --- a/src/pages/InviteReportParticipantsPage.tsx +++ b/src/pages/InviteReportParticipantsPage.tsx @@ -70,7 +70,7 @@ function InviteReportParticipantsPage({betas, report, didScreenTransitionEnd}: I }, [areOptionsInitialized, betas, excludedUsers, options.personalDetails, options.reports]); const inviteOptions = useMemo( - () => OptionsListUtils.filterOptions(defaultOptions, debouncedSearchTerm, {excludeLogins: excludedUsers}), + () => OptionsListUtils.filterAndOrderOptions(defaultOptions, debouncedSearchTerm, {excludeLogins: excludedUsers}), [debouncedSearchTerm, defaultOptions, excludedUsers], ); diff --git a/src/pages/NewChatPage.tsx b/src/pages/NewChatPage.tsx index 286dc75cb4f3..6728a5181b63 100755 --- a/src/pages/NewChatPage.tsx +++ b/src/pages/NewChatPage.tsx @@ -48,7 +48,7 @@ function useOptions() { }); const defaultOptions = useMemo(() => { - const filteredOptions = OptionsListUtils.getOptions( + const filteredOptions = OptionsListUtils.getValidOptions( { reports: listOptions.reports ?? [], personalDetails: listOptions.personalDetails ?? [], @@ -56,7 +56,6 @@ function useOptions() { { betas: betas ?? [], selectedOptions, - maxRecentReportsToShow: 0, includeSelfDM: true, }, ); @@ -64,7 +63,7 @@ function useOptions() { }, [betas, listOptions.personalDetails, listOptions.reports, selectedOptions]); const options = useMemo(() => { - const filteredOptions = OptionsListUtils.filterOptions(defaultOptions, debouncedSearchTerm, { + const filteredOptions = OptionsListUtils.filterAndOrderOptions(defaultOptions, debouncedSearchTerm, { selectedOptions, maxRecentReportsToShow: CONST.IOU.MAX_RECENT_REPORTS_TO_SHOW, }); diff --git a/src/pages/RoomInvitePage.tsx b/src/pages/RoomInvitePage.tsx index 88e84809224a..feec7175cc8b 100644 --- a/src/pages/RoomInvitePage.tsx +++ b/src/pages/RoomInvitePage.tsx @@ -102,7 +102,7 @@ function RoomInvitePage({ if (debouncedSearchTerm.trim() === '') { return defaultOptions; } - const filteredOptions = OptionsListUtils.filterOptions(defaultOptions, debouncedSearchTerm, {excludeLogins: excludedUsers}); + const filteredOptions = OptionsListUtils.filterAndOrderOptions(defaultOptions, debouncedSearchTerm, {excludeLogins: excludedUsers}); return filteredOptions; }, [debouncedSearchTerm, defaultOptions, excludedUsers]); diff --git a/src/pages/iou/request/MoneyRequestAttendeeSelector.tsx b/src/pages/iou/request/MoneyRequestAttendeeSelector.tsx index e27be90ce7fc..a94fd2e461fd 100644 --- a/src/pages/iou/request/MoneyRequestAttendeeSelector.tsx +++ b/src/pages/iou/request/MoneyRequestAttendeeSelector.tsx @@ -86,7 +86,7 @@ function MoneyRequestAttendeeSelector({attendees = [], onFinish, onAttendeesAdde action, ); if (isPaidGroupPolicy) { - optionList.recentReports = OptionsListUtils.orderOptions(optionList.recentReports, searchTerm, { + optionList.recentReports = OptionsListUtils.orderReportOptions(optionList.recentReports, searchTerm, { preferChatroomsOverThreads: true, preferPolicyExpenseChat: !!action, preferRecentExpenseReports: action === CONST.IOU.ACTION.CREATE, @@ -122,7 +122,7 @@ function MoneyRequestAttendeeSelector({attendees = [], onFinish, onAttendeesAdde headerMessage: '', }; } - const newOptions = OptionsListUtils.filterOptions(defaultOptions, debouncedSearchTerm, { + const newOptions = OptionsListUtils.filterAndOrderOptions(defaultOptions, debouncedSearchTerm, { excludeLogins: CONST.EXPENSIFY_EMAILS, maxRecentReportsToShow: CONST.IOU.MAX_RECENT_REPORTS_TO_SHOW, preferPolicyExpenseChat: isPaidGroupPolicy, diff --git a/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx b/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx index 26b8e3373767..3c9c165261ce 100644 --- a/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx +++ b/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx @@ -105,7 +105,7 @@ function MoneyRequestParticipantsSelector({ }; } - const optionList = OptionsListUtils.getOptions( + const optionList = OptionsListUtils.getValidOptions( { reports: options.reports, personalDetails: options.personalDetails, @@ -124,7 +124,6 @@ function MoneyRequestParticipantsSelector({ canInviteUser: !isCategorizeOrShareAction, includeInvoiceRooms: iouType === CONST.IOU.TYPE.INVOICE, action, - maxRecentReportsToShow: 0, }, ); @@ -142,7 +141,7 @@ function MoneyRequestParticipantsSelector({ }; } - const newOptions = OptionsListUtils.filterOptions(defaultOptions, debouncedSearchTerm, { + const newOptions = OptionsListUtils.filterAndOrderOptions(defaultOptions, debouncedSearchTerm, { // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing canInviteUser: !isCategorizeOrShareAction, selectedOptions: participants as Participant[], diff --git a/src/pages/settings/AboutPage/ShareLogList/BaseShareLogList.tsx b/src/pages/settings/AboutPage/ShareLogList/BaseShareLogList.tsx index 2b79d441e686..47b418179049 100644 --- a/src/pages/settings/AboutPage/ShareLogList/BaseShareLogList.tsx +++ b/src/pages/settings/AboutPage/ShareLogList/BaseShareLogList.tsx @@ -56,7 +56,7 @@ function BaseShareLogList({onAttachLogToReport}: BaseShareLogListProps) { return defaultOptions; } - const filteredOptions = OptionsListUtils.filterOptions(defaultOptions, debouncedSearchValue, { + const filteredOptions = OptionsListUtils.filterAndOrderOptions(defaultOptions, debouncedSearchValue, { preferChatroomsOverThreads: true, sortByReportTypeInSearch: true, }); diff --git a/src/pages/settings/Security/AddDelegate/AddDelegatePage.tsx b/src/pages/settings/Security/AddDelegate/AddDelegatePage.tsx index efaf6d5e8480..201d7c95df0f 100644 --- a/src/pages/settings/Security/AddDelegate/AddDelegatePage.tsx +++ b/src/pages/settings/Security/AddDelegate/AddDelegatePage.tsx @@ -27,7 +27,7 @@ function useOptions() { const existingDelegates = useMemo(() => account?.delegatedAccess?.delegates?.map((delegate) => delegate.email) ?? [], [account?.delegatedAccess?.delegates]); const defaultOptions = useMemo(() => { - const {recentReports, personalDetails, userToInvite, currentUserOption} = OptionsListUtils.getOptions( + const {recentReports, personalDetails, userToInvite, currentUserOption} = OptionsListUtils.getValidOptions( { reports: optionsList.reports, personalDetails: optionsList.personalDetails, @@ -35,7 +35,6 @@ function useOptions() { { betas, excludeLogins: [...CONST.EXPENSIFY_EMAILS, ...existingDelegates], - maxRecentReportsToShow: 0, }, ); @@ -56,7 +55,7 @@ function useOptions() { }, [optionsList.reports, optionsList.personalDetails, betas, existingDelegates, isLoading]); const options = useMemo(() => { - const filteredOptions = OptionsListUtils.filterOptions(defaultOptions, debouncedSearchValue.trim(), { + const filteredOptions = OptionsListUtils.filterAndOrderOptions(defaultOptions, debouncedSearchValue.trim(), { excludeLogins: [...CONST.EXPENSIFY_EMAILS, ...existingDelegates], maxRecentReportsToShow: CONST.IOU.MAX_RECENT_REPORTS_TO_SHOW, }); diff --git a/src/pages/tasks/TaskAssigneeSelectorModal.tsx b/src/pages/tasks/TaskAssigneeSelectorModal.tsx index a13475c3f15d..d0da5b673b76 100644 --- a/src/pages/tasks/TaskAssigneeSelectorModal.tsx +++ b/src/pages/tasks/TaskAssigneeSelectorModal.tsx @@ -40,7 +40,7 @@ function useOptions() { const {options: optionsList, areOptionsInitialized} = useOptionsList(); const defaultOptions = useMemo(() => { - const {recentReports, personalDetails, userToInvite, currentUserOption} = OptionsListUtils.getOptions( + const {recentReports, personalDetails, userToInvite, currentUserOption} = OptionsListUtils.getValidOptions( { reports: optionsList.reports, personalDetails: optionsList.personalDetails, @@ -48,7 +48,6 @@ function useOptions() { { betas, excludeLogins: CONST.EXPENSIFY_EMAILS, - maxRecentReportsToShow: 0, }, ); @@ -69,7 +68,7 @@ function useOptions() { }, [optionsList.reports, optionsList.personalDetails, betas, isLoading]); const options = useMemo(() => { - const filteredOptions = OptionsListUtils.filterOptions(defaultOptions, debouncedSearchValue.trim(), { + const filteredOptions = OptionsListUtils.filterAndOrderOptions(defaultOptions, debouncedSearchValue.trim(), { excludeLogins: CONST.EXPENSIFY_EMAILS, maxRecentReportsToShow: CONST.IOU.MAX_RECENT_REPORTS_TO_SHOW, }); diff --git a/src/pages/tasks/TaskShareDestinationSelectorModal.tsx b/src/pages/tasks/TaskShareDestinationSelectorModal.tsx index 9b85337aedcd..543afb4ba002 100644 --- a/src/pages/tasks/TaskShareDestinationSelectorModal.tsx +++ b/src/pages/tasks/TaskShareDestinationSelectorModal.tsx @@ -82,7 +82,7 @@ function TaskShareDestinationSelectorModal() { if (debouncedSearchValue.trim() === '') { return defaultOptions; } - const filteredReports = OptionsListUtils.filterOptions(defaultOptions, debouncedSearchValue.trim(), { + const filteredReports = OptionsListUtils.filterAndOrderOptions(defaultOptions, debouncedSearchValue.trim(), { excludeLogins: CONST.EXPENSIFY_EMAILS, canInviteUser: false, }); diff --git a/src/pages/workspace/WorkspaceInvitePage.tsx b/src/pages/workspace/WorkspaceInvitePage.tsx index d2ab6199cb90..eb0953a11ffb 100644 --- a/src/pages/workspace/WorkspaceInvitePage.tsx +++ b/src/pages/workspace/WorkspaceInvitePage.tsx @@ -86,7 +86,7 @@ function WorkspaceInvitePage({route, policy}: WorkspaceInvitePageProps) { }, [areOptionsInitialized, betas, excludedUsers, options.personalDetails]); const inviteOptions = useMemo( - () => OptionsListUtils.filterOptions(defaultOptions, debouncedSearchTerm, {excludeLogins: excludedUsers}), + () => OptionsListUtils.filterAndOrderOptions(defaultOptions, debouncedSearchTerm, {excludeLogins: excludedUsers}), [debouncedSearchTerm, defaultOptions, excludedUsers], ); diff --git a/tests/perf-test/OptionsListUtils.perf-test.ts b/tests/perf-test/OptionsListUtils.perf-test.ts index 19629c602477..11a756041a3b 100644 --- a/tests/perf-test/OptionsListUtils.perf-test.ts +++ b/tests/perf-test/OptionsListUtils.perf-test.ts @@ -108,8 +108,8 @@ describe('OptionsListUtils', () => { test('[OptionsListUtils] getFilteredOptions with search value', async () => { await waitForBatchedUpdates(); await measureFunction(() => { - const formattedOptions = OptionsListUtils.getOptions({reports: options.reports, personalDetails: options.personalDetails}, {betas: mockedBetas}); - OptionsListUtils.filterOptions(formattedOptions, SEARCH_VALUE); + const formattedOptions = OptionsListUtils.getValidOptions({reports: options.reports, personalDetails: options.personalDetails}, {betas: mockedBetas}); + OptionsListUtils.filterAndOrderOptions(formattedOptions, SEARCH_VALUE); }); }); diff --git a/tests/unit/OptionsListUtilsTest.ts b/tests/unit/OptionsListUtilsTest.ts index cad6c010bb67..52cdfd1a3bdc 100644 --- a/tests/unit/OptionsListUtilsTest.ts +++ b/tests/unit/OptionsListUtilsTest.ts @@ -412,14 +412,13 @@ describe('OptionsListUtils', () => { expect(results.recentReports.length).toBe(Object.values(OPTIONS.reports).length); }); - it('getOptions()', () => { - const MAX_RECENT_REPORTS = 5; - + it('orderOptions()', () => { // When we call getOptions() with no search value - let results = OptionsListUtils.getOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {maxRecentReportsToShow: MAX_RECENT_REPORTS}); - - // We should expect maximum of 5 recent reports to be returned - expect(results.recentReports.length).toBe(MAX_RECENT_REPORTS); + let results: Pick = OptionsListUtils.getValidOptions({ + reports: OPTIONS.reports, + personalDetails: OPTIONS.personalDetails, + }); + results = OptionsListUtils.orderOptions(results); // We should expect all personalDetails except the currently logged in user to be returned // Filtering of personalDetails that have reports is done in filterOptions @@ -442,31 +441,29 @@ describe('OptionsListUtils', () => { expect(personalDetailWithExistingReport?.reportID).toBe('2'); // When we only pass personal details - results = OptionsListUtils.getOptions({personalDetails: OPTIONS.personalDetails, reports: []}); + results = OptionsListUtils.getValidOptions({personalDetails: OPTIONS.personalDetails, reports: []}); + results = OptionsListUtils.orderOptions(results); // We should expect personal details sorted alphabetically expect(results.personalDetails.at(0)?.text).toBe('Black Panther'); expect(results.personalDetails.at(1)?.text).toBe('Black Widow'); expect(results.personalDetails.at(2)?.text).toBe('Captain America'); expect(results.personalDetails.at(3)?.text).toBe('Invisible Woman'); + }); + it('getOptions()', () => { // When we don't include personal detail to the result - results = OptionsListUtils.getOptions( - { - personalDetails: [], - reports: [], - }, - { - maxRecentReportsToShow: 0, - }, - ); + let results = OptionsListUtils.getValidOptions({ + personalDetails: [], + reports: [], + }); // Then no personal detail options will be returned expect(results.personalDetails.length).toBe(0); // Test for Concierge's existence in chat options - results = OptionsListUtils.getOptions({reports: OPTIONS_WITH_CONCIERGE.reports, personalDetails: OPTIONS_WITH_CONCIERGE.personalDetails}); + results = OptionsListUtils.getValidOptions({reports: OPTIONS_WITH_CONCIERGE.reports, personalDetails: OPTIONS_WITH_CONCIERGE.personalDetails}); // Concierge is included in the results by default. We should expect all the personalDetails to show // (minus the currently logged in user) @@ -475,7 +472,7 @@ describe('OptionsListUtils', () => { expect(results.recentReports).toEqual(expect.arrayContaining([expect.objectContaining({login: 'concierge@expensify.com'})])); // Test by excluding Concierge from the results - results = OptionsListUtils.getOptions( + results = OptionsListUtils.getValidOptions( { reports: OPTIONS_WITH_CONCIERGE.reports, personalDetails: OPTIONS_WITH_CONCIERGE.personalDetails, @@ -491,7 +488,7 @@ describe('OptionsListUtils', () => { expect(results.personalDetails).not.toEqual(expect.arrayContaining([expect.objectContaining({login: 'concierge@expensify.com'})])); // Test by excluding Chronos from the results - results = OptionsListUtils.getOptions({reports: OPTIONS_WITH_CHRONOS.reports, personalDetails: OPTIONS_WITH_CHRONOS.personalDetails}, {excludeLogins: [CONST.EMAIL.CHRONOS]}); + results = OptionsListUtils.getValidOptions({reports: OPTIONS_WITH_CHRONOS.reports, personalDetails: OPTIONS_WITH_CHRONOS.personalDetails}, {excludeLogins: [CONST.EMAIL.CHRONOS]}); // All the personalDetails should be returned minus the currently logged in user and Concierge // Filtering of personalDetails that have reports is done in filterOptions @@ -499,7 +496,7 @@ describe('OptionsListUtils', () => { expect(results.personalDetails).not.toEqual(expect.arrayContaining([expect.objectContaining({login: 'chronos@expensify.com'})])); // Test by excluding Receipts from the results - results = OptionsListUtils.getOptions( + results = OptionsListUtils.getValidOptions( { reports: OPTIONS_WITH_RECEIPTS.reports, personalDetails: OPTIONS_WITH_RECEIPTS.personalDetails, @@ -517,46 +514,34 @@ describe('OptionsListUtils', () => { it('getOptions() for group Chat', () => { // When we call getOptions() with no search value - let results = OptionsListUtils.getOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}); + let results = OptionsListUtils.getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}); // We should expect all the personalDetails to show except the currently logged in user // Filtering of personalDetails that have reports is done in filterOptions expect(results.personalDetails.length).toBe(Object.values(OPTIONS.personalDetails).length - 1); - // All personal details including those that have reports should be returned - // We should expect personal details sorted alphabetically - expect(results.personalDetails.at(0)?.text).toBe('Black Panther'); - expect(results.personalDetails.at(1)?.text).toBe('Black Widow'); - expect(results.personalDetails.at(2)?.text).toBe('Captain America'); - expect(results.personalDetails.at(3)?.text).toBe('Invisible Woman'); - expect(results.personalDetails.at(4)?.text).toBe('Mister Fantastic'); - expect(results.personalDetails.at(5)?.text).toBe('Mr Sinister'); - expect(results.personalDetails.at(6)?.text).toBe('Spider-Man'); - expect(results.personalDetails.at(7)?.text).toBe('The Incredible Hulk'); - expect(results.personalDetails.at(8)?.text).toBe('Thor'); - // And none of our personalDetails should include any of the users with recent reports const reportLogins = results.recentReports.map((reportOption) => reportOption.login); const personalDetailsOverlapWithReports = results.personalDetails.every((personalDetailOption) => reportLogins.includes(personalDetailOption.login)); expect(personalDetailsOverlapWithReports).toBe(false); - // When we provide no selected options to getOptions() - results = OptionsListUtils.getOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {maxRecentReportsToShow: 5}); + // When we limit getOptions() + results = OptionsListUtils.getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}); // Then one of our older report options (not in our five most recent) should appear in the personalDetails // but not in recentReports - expect(results.recentReports.every((option) => option.login !== 'peterparker@expensify.com')).toBe(true); - expect(results.personalDetails.every((option) => option.login !== 'peterparker@expensify.com')).toBe(false); + // expect(results.recentReports.every((option) => option.login !== 'peterparker@expensify.com')).toBe(true); + // expect(results.personalDetails.every((option) => option.login !== 'peterparker@expensify.com')).toBe(false); // When we provide a "selected" option to getOptions() - results = OptionsListUtils.getOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {excludeLogins: ['peterparker@expensify.com']}); + results = OptionsListUtils.getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {excludeLogins: ['peterparker@expensify.com']}); // Then the option should not appear anywhere in either list expect(results.recentReports.every((option) => option.login !== 'peterparker@expensify.com')).toBe(true); expect(results.personalDetails.every((option) => option.login !== 'peterparker@expensify.com')).toBe(true); // Test Concierge's existence in new group options - results = OptionsListUtils.getOptions({reports: OPTIONS_WITH_CONCIERGE.reports, personalDetails: OPTIONS_WITH_CONCIERGE.personalDetails}); + results = OptionsListUtils.getValidOptions({reports: OPTIONS_WITH_CONCIERGE.reports, personalDetails: OPTIONS_WITH_CONCIERGE.personalDetails}); // Concierge is included in the results by default. We should expect all the personalDetails to show // (minus the currently logged in user) @@ -565,7 +550,7 @@ describe('OptionsListUtils', () => { expect(results.recentReports).toEqual(expect.arrayContaining([expect.objectContaining({login: 'concierge@expensify.com'})])); // Test by excluding Concierge from the results - results = OptionsListUtils.getOptions( + results = OptionsListUtils.getValidOptions( { reports: OPTIONS_WITH_CONCIERGE.reports, personalDetails: OPTIONS_WITH_CONCIERGE.personalDetails, @@ -583,7 +568,7 @@ describe('OptionsListUtils', () => { expect(results.recentReports).not.toEqual(expect.arrayContaining([expect.objectContaining({login: 'concierge@expensify.com'})])); // Test by excluding Chronos from the results - results = OptionsListUtils.getOptions({reports: OPTIONS_WITH_CHRONOS.reports, personalDetails: OPTIONS_WITH_CHRONOS.personalDetails}, {excludeLogins: [CONST.EMAIL.CHRONOS]}); + results = OptionsListUtils.getValidOptions({reports: OPTIONS_WITH_CHRONOS.reports, personalDetails: OPTIONS_WITH_CHRONOS.personalDetails}, {excludeLogins: [CONST.EMAIL.CHRONOS]}); // We should expect all the personalDetails to show (minus // the currently logged in user and Concierge) @@ -593,7 +578,7 @@ describe('OptionsListUtils', () => { expect(results.recentReports).not.toEqual(expect.arrayContaining([expect.objectContaining({login: 'chronos@expensify.com'})])); // Test by excluding Receipts from the results - results = OptionsListUtils.getOptions( + results = OptionsListUtils.getValidOptions( { reports: OPTIONS_WITH_RECEIPTS.reports, personalDetails: OPTIONS_WITH_RECEIPTS.personalDetails, @@ -676,7 +661,7 @@ describe('OptionsListUtils', () => { describe('filterOptions', () => { it('should return all options when search is empty', () => { const options = OptionsListUtils.getSearchOptions(OPTIONS, [CONST.BETAS.ALL]); - const filteredOptions = OptionsListUtils.filterOptions(options, ''); + const filteredOptions = OptionsListUtils.filterAndOrderOptions(options, ''); expect(filteredOptions.recentReports.length + filteredOptions.personalDetails.length).toBe(12); }); @@ -685,8 +670,15 @@ describe('OptionsListUtils', () => { const searchText = 'man'; const options = OptionsListUtils.getSearchOptions(OPTIONS, [CONST.BETAS.ALL]); - const filteredOptions = OptionsListUtils.filterOptions(options, searchText, {sortByReportTypeInSearch: true}); + const filteredOptions = OptionsListUtils.filterAndOrderOptions(options, searchText, {sortByReportTypeInSearch: true}); + + // When sortByReportTypeInSearch is true, we expect all options to be part of the recentReports list: + expect(filteredOptions.personalDetails.length).toBe(0); + + // Expect to only find reports that matched our search text: expect(filteredOptions.recentReports.length).toBe(4); + + // This items should be ordered by most recent action (and other criteria such as whether they are archived): expect(filteredOptions.recentReports.at(0)?.text).toBe('Invisible Woman'); expect(filteredOptions.recentReports.at(1)?.text).toBe('Spider-Man'); expect(filteredOptions.recentReports.at(2)?.text).toBe('Black Widow'); @@ -697,7 +689,7 @@ describe('OptionsListUtils', () => { const searchText = 'mistersinister@marauders.com'; const options = OptionsListUtils.getSearchOptions(OPTIONS, [CONST.BETAS.ALL]); - const filteredOptions = OptionsListUtils.filterOptions(options, searchText); + const filteredOptions = OptionsListUtils.filterAndOrderOptions(options, searchText); expect(filteredOptions.recentReports.length).toBe(1); expect(filteredOptions.recentReports.at(0)?.text).toBe('Mr Sinister'); @@ -706,7 +698,7 @@ describe('OptionsListUtils', () => { it('should find archived chats', () => { const searchText = 'Archived'; const options = OptionsListUtils.getSearchOptions(OPTIONS, [CONST.BETAS.ALL]); - const filteredOptions = OptionsListUtils.filterOptions(options, searchText); + const filteredOptions = OptionsListUtils.filterAndOrderOptions(options, searchText); expect(filteredOptions.recentReports.length).toBe(1); expect(!!filteredOptions.recentReports.at(0)?.private_isArchived).toBe(true); @@ -717,7 +709,7 @@ describe('OptionsListUtils', () => { const OPTIONS_WITH_PERIODS = OptionsListUtils.createOptionList(PERSONAL_DETAILS_WITH_PERIODS, REPORTS); const options = OptionsListUtils.getSearchOptions(OPTIONS_WITH_PERIODS, [CONST.BETAS.ALL]); - const filteredOptions = OptionsListUtils.filterOptions(options, searchText, {sortByReportTypeInSearch: true}); + const filteredOptions = OptionsListUtils.filterAndOrderOptions(options, searchText, {sortByReportTypeInSearch: true}); expect(filteredOptions.recentReports.length).toBe(1); expect(filteredOptions.recentReports.at(0)?.login).toBe('barry.allen@expensify.com'); @@ -727,7 +719,7 @@ describe('OptionsListUtils', () => { const searchText = 'avengers'; const options = OptionsListUtils.getSearchOptions(OPTIONS_WITH_WORKSPACE_ROOM, [CONST.BETAS.ALL]); - const filteredOptions = OptionsListUtils.filterOptions(options, searchText); + const filteredOptions = OptionsListUtils.filterAndOrderOptions(options, searchText); expect(filteredOptions.recentReports.length).toBe(1); expect(filteredOptions.recentReports.at(0)?.subtitle).toBe('Avengers Room'); @@ -737,7 +729,7 @@ describe('OptionsListUtils', () => { const searchText = 'reedrichards@expensify.com'; const options = OptionsListUtils.getSearchOptions(OPTIONS, [CONST.BETAS.ALL]); - const filteredOptions = OptionsListUtils.filterOptions(options, searchText); + const filteredOptions = OptionsListUtils.filterAndOrderOptions(options, searchText); expect(filteredOptions.recentReports.length).toBe(1); expect(filteredOptions.recentReports.at(0)?.login).toBe(searchText); @@ -748,7 +740,7 @@ describe('OptionsListUtils', () => { const OPTIONS_WITH_CHATROOMS = OptionsListUtils.createOptionList(PERSONAL_DETAILS, REPORTS_WITH_CHAT_ROOM); const options = OptionsListUtils.getSearchOptions(OPTIONS_WITH_CHATROOMS, [CONST.BETAS.ALL]); - const filterOptions = OptionsListUtils.filterOptions(options, searchText); + const filterOptions = OptionsListUtils.filterAndOrderOptions(options, searchText); expect(filterOptions.recentReports.length).toBe(2); expect(filterOptions.recentReports.at(1)?.isChatRoom).toBe(true); @@ -758,7 +750,7 @@ describe('OptionsListUtils', () => { const searchText = 'fantastic'; const options = OptionsListUtils.getSearchOptions(OPTIONS); - const filteredOptions = OptionsListUtils.filterOptions(options, searchText); + const filteredOptions = OptionsListUtils.filterAndOrderOptions(options, searchText); expect(filteredOptions.recentReports.length).toBe(2); expect(filteredOptions.recentReports.at(0)?.text).toBe('Mister Fantastic'); @@ -769,7 +761,7 @@ describe('OptionsListUtils', () => { const searchText = 'test@email.com'; const options = OptionsListUtils.getSearchOptions(OPTIONS); - const filteredOptions = OptionsListUtils.filterOptions(options, searchText); + const filteredOptions = OptionsListUtils.filterAndOrderOptions(options, searchText); expect(filteredOptions.userToInvite?.login).toBe(searchText); }); @@ -777,8 +769,8 @@ describe('OptionsListUtils', () => { it('should not return any results if the search value is on an exluded logins list', () => { const searchText = 'admin@expensify.com'; - const options = OptionsListUtils.getOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {excludeLogins: CONST.EXPENSIFY_EMAILS}); - const filterOptions = OptionsListUtils.filterOptions(options, searchText, {excludeLogins: CONST.EXPENSIFY_EMAILS}); + const options = OptionsListUtils.getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {excludeLogins: CONST.EXPENSIFY_EMAILS}); + const filterOptions = OptionsListUtils.filterAndOrderOptions(options, searchText, {excludeLogins: CONST.EXPENSIFY_EMAILS}); expect(filterOptions.recentReports.length).toBe(0); }); @@ -786,7 +778,7 @@ describe('OptionsListUtils', () => { const searchText = 'test@email.com'; const options = OptionsListUtils.getSearchOptions(OPTIONS); - const filteredOptions = OptionsListUtils.filterOptions(options, searchText, {excludeLogins: CONST.EXPENSIFY_EMAILS}); + const filteredOptions = OptionsListUtils.filterAndOrderOptions(options, searchText, {excludeLogins: CONST.EXPENSIFY_EMAILS}); expect(filteredOptions.userToInvite?.login).toBe(searchText); }); @@ -795,7 +787,7 @@ describe('OptionsListUtils', () => { const searchText = ''; const options = OptionsListUtils.getSearchOptions(OPTIONS); - const filteredOptions = OptionsListUtils.filterOptions(options, searchText, {maxRecentReportsToShow: 2}); + const filteredOptions = OptionsListUtils.filterAndOrderOptions(options, searchText, {maxRecentReportsToShow: 2}); expect(filteredOptions.recentReports.length).toBe(2); }); @@ -804,20 +796,20 @@ describe('OptionsListUtils', () => { const searchText = 'natasharomanoff@expensify.com'; const options = OptionsListUtils.getSearchOptions(OPTIONS, [CONST.BETAS.ALL]); - const filteredOptions = OptionsListUtils.filterOptions(options, searchText); + const filteredOptions = OptionsListUtils.filterAndOrderOptions(options, searchText); expect(filteredOptions.personalDetails.length).toBe(1); expect(filteredOptions.userToInvite).toBe(null); }); it('should not return any options if search value does not match any personal details (getMemberInviteOptions)', () => { const options = OptionsListUtils.getMemberInviteOptions(OPTIONS.personalDetails, []); - const filteredOptions = OptionsListUtils.filterOptions(options, 'magneto'); + const filteredOptions = OptionsListUtils.filterAndOrderOptions(options, 'magneto'); expect(filteredOptions.personalDetails.length).toBe(0); }); it('should return one personal detail if search value matches an email (getMemberInviteOptions)', () => { const options = OptionsListUtils.getMemberInviteOptions(OPTIONS.personalDetails, []); - const filteredOptions = OptionsListUtils.filterOptions(options, 'peterparker@expensify.com'); + const filteredOptions = OptionsListUtils.filterAndOrderOptions(options, 'peterparker@expensify.com'); expect(filteredOptions.personalDetails.length).toBe(1); expect(filteredOptions.personalDetails.at(0)?.text).toBe('Spider-Man'); @@ -833,7 +825,7 @@ describe('OptionsListUtils', () => { return filtered; }, []); const options = OptionsListUtils.getShareDestinationOptions(filteredReports, OPTIONS.personalDetails, []); - const filteredOptions = OptionsListUtils.filterOptions(options, 'mutants'); + const filteredOptions = OptionsListUtils.filterAndOrderOptions(options, 'mutants'); expect(filteredOptions.recentReports.length).toBe(0); }); @@ -849,7 +841,7 @@ describe('OptionsListUtils', () => { }, []); const options = OptionsListUtils.getShareDestinationOptions(filteredReportsWithWorkspaceRooms, OPTIONS.personalDetails, []); - const filteredOptions = OptionsListUtils.filterOptions(options, 'Avengers Room'); + const filteredOptions = OptionsListUtils.filterAndOrderOptions(options, 'Avengers Room'); expect(filteredOptions.recentReports.length).toBe(1); }); @@ -865,14 +857,14 @@ describe('OptionsListUtils', () => { }, []); const options = OptionsListUtils.getShareDestinationOptions(filteredReportsWithWorkspaceRooms, OPTIONS.personalDetails, []); - const filteredOptions = OptionsListUtils.filterOptions(options, 'Mutants Lair'); + const filteredOptions = OptionsListUtils.filterAndOrderOptions(options, 'Mutants Lair'); expect(filteredOptions.recentReports.length).toBe(0); }); it('should show the option from personal details when searching for personal detail with no existing report (getOptions)', () => { - const options = OptionsListUtils.getOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}); - const filteredOptions = OptionsListUtils.filterOptions(options, 'hulk'); + const options = OptionsListUtils.getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}); + const filteredOptions = OptionsListUtils.filterAndOrderOptions(options, 'hulk'); expect(filteredOptions.recentReports.length).toBe(0); @@ -880,20 +872,9 @@ describe('OptionsListUtils', () => { expect(filteredOptions.personalDetails.at(0)?.login).toBe('brucebanner@expensify.com'); }); - it('should return all matching reports and personal details (getOptions)', () => { - const options = OptionsListUtils.getOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {maxRecentReportsToShow: 5}); - const filteredOptions = OptionsListUtils.filterOptions(options, '.com'); - - expect(filteredOptions.recentReports.at(0)?.text).toBe('Captain America'); - - // We expect that only personal details that are not in the reports are included here - expect(filteredOptions.personalDetails.length).toBe(4); - expect(filteredOptions.personalDetails.at(0)?.login).toBe('natasharomanoff@expensify.com'); - }); - it('should not return any options or user to invite if there are no search results and the string does not match a potential email or phone (getOptions)', () => { - const options = OptionsListUtils.getOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}); - const filteredOptions = OptionsListUtils.filterOptions(options, 'marc@expensify'); + const options = OptionsListUtils.getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}); + const filteredOptions = OptionsListUtils.filterAndOrderOptions(options, 'marc@expensify'); expect(filteredOptions.recentReports.length).toBe(0); expect(filteredOptions.personalDetails.length).toBe(0); @@ -901,8 +882,8 @@ describe('OptionsListUtils', () => { }); it('should not return any options but should return an user to invite if no matching options exist and the search value is a potential email (getOptions)', () => { - const options = OptionsListUtils.getOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}); - const filteredOptions = OptionsListUtils.filterOptions(options, 'marc@expensify.com'); + const options = OptionsListUtils.getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}); + const filteredOptions = OptionsListUtils.filterAndOrderOptions(options, 'marc@expensify.com'); expect(filteredOptions.recentReports.length).toBe(0); expect(filteredOptions.personalDetails.length).toBe(0); @@ -910,16 +891,16 @@ describe('OptionsListUtils', () => { }); it('should return user to invite when search term has a period with options for it that do not contain the period (getOptions)', () => { - const options = OptionsListUtils.getOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}); - const filteredOptions = OptionsListUtils.filterOptions(options, 'peter.parker@expensify.com'); + const options = OptionsListUtils.getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}); + const filteredOptions = OptionsListUtils.filterAndOrderOptions(options, 'peter.parker@expensify.com'); expect(filteredOptions.recentReports.length).toBe(0); expect(filteredOptions.userToInvite).not.toBe(null); }); it('should not return options but should return an user to invite if no matching options exist and the search value is a potential phone number (getOptions)', () => { - const options = OptionsListUtils.getOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}); - const filteredOptions = OptionsListUtils.filterOptions(options, '5005550006'); + const options = OptionsListUtils.getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}); + const filteredOptions = OptionsListUtils.filterAndOrderOptions(options, '5005550006'); expect(filteredOptions.recentReports.length).toBe(0); expect(filteredOptions.personalDetails.length).toBe(0); @@ -928,8 +909,8 @@ describe('OptionsListUtils', () => { }); it('should not return options but should return an user to invite if no matching options exist and the search value is a potential phone number with country code added (getOptions)', () => { - const options = OptionsListUtils.getOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}); - const filteredOptions = OptionsListUtils.filterOptions(options, '+15005550006'); + const options = OptionsListUtils.getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}); + const filteredOptions = OptionsListUtils.filterAndOrderOptions(options, '+15005550006'); expect(filteredOptions.recentReports.length).toBe(0); expect(filteredOptions.personalDetails.length).toBe(0); @@ -938,8 +919,8 @@ describe('OptionsListUtils', () => { }); it('should not return options but should return an user to invite if no matching options exist and the search value is a potential phone number with special characters added (getOptions)', () => { - const options = OptionsListUtils.getOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}); - const filteredOptions = OptionsListUtils.filterOptions(options, '+1 (800)324-3233'); + const options = OptionsListUtils.getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}); + const filteredOptions = OptionsListUtils.filterAndOrderOptions(options, '+1 (800)324-3233'); expect(filteredOptions.recentReports.length).toBe(0); expect(filteredOptions.personalDetails.length).toBe(0); @@ -948,8 +929,8 @@ describe('OptionsListUtils', () => { }); it('should not return any options or user to invite if contact number contains alphabet characters (getOptions)', () => { - const options = OptionsListUtils.getOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}); - const filteredOptions = OptionsListUtils.filterOptions(options, '998243aaaa'); + const options = OptionsListUtils.getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}); + const filteredOptions = OptionsListUtils.filterAndOrderOptions(options, '998243aaaa'); expect(filteredOptions.recentReports.length).toBe(0); expect(filteredOptions.personalDetails.length).toBe(0); @@ -957,25 +938,25 @@ describe('OptionsListUtils', () => { }); it('should not return any options if search value does not match any personal details (getOptions)', () => { - const options = OptionsListUtils.getOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}); - const filteredOptions = OptionsListUtils.filterOptions(options, 'magneto'); + const options = OptionsListUtils.getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}); + const filteredOptions = OptionsListUtils.filterAndOrderOptions(options, 'magneto'); expect(filteredOptions.personalDetails.length).toBe(0); }); it('should return one recent report and no personal details if a search value provides an email (getOptions)', () => { - const options = OptionsListUtils.getOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}); - const filteredOptions = OptionsListUtils.filterOptions(options, 'peterparker@expensify.com', {sortByReportTypeInSearch: true}); + const options = OptionsListUtils.getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}); + const filteredOptions = OptionsListUtils.filterAndOrderOptions(options, 'peterparker@expensify.com', {sortByReportTypeInSearch: true}); expect(filteredOptions.recentReports.length).toBe(1); expect(filteredOptions.recentReports.at(0)?.text).toBe('Spider-Man'); expect(filteredOptions.personalDetails.length).toBe(0); }); it('should return all matching reports and personal details (getOptions)', () => { - const options = OptionsListUtils.getOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {maxRecentReportsToShow: 5}); - const filteredOptions = OptionsListUtils.filterOptions(options, '.com'); + const options = OptionsListUtils.getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}); + const filteredOptions = OptionsListUtils.filterAndOrderOptions(options, '.com', {maxRecentReportsToShow: 5}); - expect(filteredOptions.personalDetails.length).toBe(4); + expect(filteredOptions.personalDetails.length).toBe(2); expect(filteredOptions.recentReports.length).toBe(5); expect(filteredOptions.personalDetails.at(0)?.login).toBe('natasharomanoff@expensify.com'); expect(filteredOptions.recentReports.at(0)?.text).toBe('Captain America'); @@ -985,7 +966,7 @@ describe('OptionsListUtils', () => { it('should return matching option when searching (getSearchOptions)', () => { const options = OptionsListUtils.getSearchOptions(OPTIONS); - const filteredOptions = OptionsListUtils.filterOptions(options, 'spider'); + const filteredOptions = OptionsListUtils.filterAndOrderOptions(options, 'spider'); expect(filteredOptions.recentReports.length).toBe(1); expect(filteredOptions.recentReports.at(0)?.text).toBe('Spider-Man'); @@ -993,7 +974,7 @@ describe('OptionsListUtils', () => { it('should return latest lastVisibleActionCreated item on top when search value matches multiple items (getSearchOptions)', () => { const options = OptionsListUtils.getSearchOptions(OPTIONS); - const filteredOptions = OptionsListUtils.filterOptions(options, 'fantastic'); + const filteredOptions = OptionsListUtils.filterAndOrderOptions(options, 'fantastic'); expect(filteredOptions.recentReports.length).toBe(2); expect(filteredOptions.recentReports.at(0)?.text).toBe('Mister Fantastic'); @@ -1004,7 +985,7 @@ describe('OptionsListUtils', () => { .then(() => { const OPTIONS_WITH_PERIODS = OptionsListUtils.createOptionList(PERSONAL_DETAILS_WITH_PERIODS, REPORTS); const results = OptionsListUtils.getSearchOptions(OPTIONS_WITH_PERIODS); - const filteredResults = OptionsListUtils.filterOptions(results, 'barry.allen@expensify.com', {sortByReportTypeInSearch: true}); + const filteredResults = OptionsListUtils.filterAndOrderOptions(results, 'barry.allen@expensify.com', {sortByReportTypeInSearch: true}); expect(filteredResults.recentReports.length).toBe(1); expect(filteredResults.recentReports.at(0)?.text).toBe('The Flash');