diff --git a/package.json b/package.json index f8a9610f47c4..d3f62bb9e592 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "test:debug": "TZ=utc NODE_OPTIONS='--inspect-brk --experimental-vm-modules' jest --runInBand", "perf-test": "NODE_OPTIONS=--experimental-vm-modules npx reassure", "typecheck": "NODE_OPTIONS=--max_old_space_size=8192 tsc", - "lint": "NODE_OPTIONS=--max_old_space_size=8192 eslint . --max-warnings=112 --cache --cache-location=node_modules/.cache/eslint --cache-strategy content --concurrency=auto", + "lint": "NODE_OPTIONS=--max_old_space_size=8192 eslint . --max-warnings=111 --cache --cache-location=node_modules/.cache/eslint --cache-strategy content --concurrency=auto", "lint-changed": "NODE_OPTIONS=--max_old_space_size=8192 ./scripts/lintChanged.sh", "check-lazy-loading": "ts-node scripts/checkLazyLoading.ts", "lint-watch": "npx eslint-watch --watch --changed", diff --git a/src/components/Search/FilterDropdowns/UserSelectPopup.tsx b/src/components/Search/FilterDropdowns/UserSelectPopup.tsx index 951fe77c4c20..9f1ce5560040 100644 --- a/src/components/Search/FilterDropdowns/UserSelectPopup.tsx +++ b/src/components/Search/FilterDropdowns/UserSelectPopup.tsx @@ -62,6 +62,7 @@ function UserSelectPopup({value, closeOverlay, onChange, isSearchable}: UserSele const [accountID] = useOnyx(ONYXKEYS.SESSION, {canBeMissing: true, selector: accountIDSelector}); const shouldFocusInputOnScreenFocus = canFocusInputOnScreenFocus(); const [countryCode = CONST.DEFAULT_COUNTRY_CODE] = useOnyx(ONYXKEYS.COUNTRY_CODE, {canBeMissing: false}); + const [loginList] = useOnyx(ONYXKEYS.LOGIN_LIST, {canBeMissing: true}); const [searchTerm, setSearchTerm] = useState(''); const [isSearchingForReports] = useOnyx(ONYXKEYS.IS_SEARCHING_FOR_REPORTS, {initWithStoredValues: false, canBeMissing: true}); const [draftComments] = useOnyx(ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, {canBeMissing: true}); @@ -98,21 +99,22 @@ function UserSelectPopup({value, closeOverlay, onChange, isSearchable}: UserSele }, draftComments, nvpDismissedProductTraining, + loginList, { excludeLogins: CONST.EXPENSIFY_EMAILS_OBJECT, includeCurrentUser: true, }, countryCode, ); - }, [options.reports, options.personalDetails, draftComments, nvpDismissedProductTraining, countryCode]); + }, [options.reports, options.personalDetails, draftComments, nvpDismissedProductTraining, loginList, countryCode]); const filteredOptions = useMemo(() => { - return filterAndOrderOptions(optionsList, cleanSearchTerm, countryCode, { + return filterAndOrderOptions(optionsList, cleanSearchTerm, countryCode, loginList, { excludeLogins: CONST.EXPENSIFY_EMAILS_OBJECT, maxRecentReportsToShow: CONST.IOU.MAX_RECENT_REPORTS_TO_SHOW, canInviteUser: false, }); - }, [optionsList, cleanSearchTerm, countryCode]); + }, [optionsList, cleanSearchTerm, countryCode, loginList]); const listData = useMemo(() => { const personalDetailList = filteredOptions.personalDetails.map((participant) => ({ diff --git a/src/components/Search/SearchAutocompleteList.tsx b/src/components/Search/SearchAutocompleteList.tsx index c4df92a25159..8226ea321c45 100644 --- a/src/components/Search/SearchAutocompleteList.tsx +++ b/src/components/Search/SearchAutocompleteList.tsx @@ -191,6 +191,7 @@ function SearchAutocompleteList({ const [nvpDismissedProductTraining] = useOnyx(ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING, {canBeMissing: true}); const [recentSearches] = useOnyx(ONYXKEYS.RECENT_SEARCHES, {canBeMissing: true}); const [countryCode] = useOnyx(ONYXKEYS.COUNTRY_CODE, {canBeMissing: false}); + const [loginList] = useOnyx(ONYXKEYS.LOGIN_LIST, {canBeMissing: true}); const expensifyIcons = useMemoizedLazyExpensifyIcons(['History', 'MagnifyingGlass']); const {options, areOptionsInitialized} = useOptionsList(); @@ -213,8 +214,9 @@ function SearchAutocompleteList({ countryCode, shouldShowGBR: false, shouldUnreadBeBold: true, + loginList, }); - }, [areOptionsInitialized, options, draftComments, nvpDismissedProductTraining, betas, autocompleteQueryValue, countryCode]); + }, [areOptionsInitialized, options, draftComments, nvpDismissedProductTraining, betas, autocompleteQueryValue, countryCode, loginList]); const [isInitialRender, setIsInitialRender] = useState(true); const parsedQuery = useMemo(() => parseForAutocomplete(autocompleteQueryValue), [autocompleteQueryValue]); @@ -421,6 +423,7 @@ function SearchAutocompleteList({ includeRecentReports: false, includeCurrentUser: true, countryCode, + loginList, shouldShowGBR: true, }).personalDetails.filter((participant) => participant.text && !alreadyAutocompletedKeys.has(participant.text.toLowerCase())); @@ -445,6 +448,7 @@ function SearchAutocompleteList({ includeRecentReports: true, includeCurrentUser: false, countryCode, + loginList, shouldShowGBR: true, }).recentReports; @@ -609,6 +613,7 @@ function SearchAutocompleteList({ nvpDismissedProductTraining, betas, countryCode, + loginList, currentUserLogin, groupByAutocompleteList, statusAutocompleteList, diff --git a/src/components/Search/SearchFiltersChatsSelector.tsx b/src/components/Search/SearchFiltersChatsSelector.tsx index bf4ab4921099..2d8f438aeb86 100644 --- a/src/components/Search/SearchFiltersChatsSelector.tsx +++ b/src/components/Search/SearchFiltersChatsSelector.tsx @@ -50,6 +50,8 @@ function SearchFiltersChatsSelector({initialReportIDs, onFiltersUpdate, isScreen const [reports] = useOnyx(ONYXKEYS.COLLECTION.REPORT, {canBeMissing: true}); const [countryCode = CONST.DEFAULT_COUNTRY_CODE] = useOnyx(ONYXKEYS.COUNTRY_CODE, {canBeMissing: false}); + const [loginList] = useOnyx(ONYXKEYS.LOGIN_LIST, {canBeMissing: true}); + const [isSearchingForReports] = useOnyx(ONYXKEYS.IS_SEARCHING_FOR_REPORTS, {initWithStoredValues: false, canBeMissing: true}); const [reportAttributesDerived] = useOnyx(ONYXKEYS.DERIVED.REPORT_ATTRIBUTES, {canBeMissing: true, selector: reportsSelector}); const [selectedReportIDs, setSelectedReportIDs] = useState(initialReportIDs); @@ -72,15 +74,15 @@ function SearchFiltersChatsSelector({initialReportIDs, onFiltersUpdate, isScreen if (!areOptionsInitialized || !isScreenTransitionEnd) { return defaultListOptions; } - return getSearchOptions({options, draftComments, nvpDismissedProductTraining, betas: undefined, isUsedInChatFinder: false, countryCode}); - }, [areOptionsInitialized, draftComments, nvpDismissedProductTraining, isScreenTransitionEnd, options, countryCode]); + return getSearchOptions({options, draftComments, nvpDismissedProductTraining, betas: undefined, isUsedInChatFinder: false, countryCode, loginList}); + }, [areOptionsInitialized, isScreenTransitionEnd, options, draftComments, nvpDismissedProductTraining, countryCode, loginList]); const chatOptions = useMemo(() => { - return filterAndOrderOptions(defaultOptions, cleanSearchTerm, countryCode, { + return filterAndOrderOptions(defaultOptions, cleanSearchTerm, countryCode, loginList, { selectedOptions, excludeLogins: CONST.EXPENSIFY_EMAILS_OBJECT, }); - }, [defaultOptions, cleanSearchTerm, selectedOptions, countryCode]); + }, [defaultOptions, cleanSearchTerm, countryCode, loginList, selectedOptions]); const {sections, headerMessage} = useMemo(() => { const newSections: Section[] = []; diff --git a/src/components/Search/SearchFiltersParticipantsSelector.tsx b/src/components/Search/SearchFiltersParticipantsSelector.tsx index d9b3207dc9ee..aa20315d8a50 100644 --- a/src/components/Search/SearchFiltersParticipantsSelector.tsx +++ b/src/components/Search/SearchFiltersParticipantsSelector.tsx @@ -50,6 +50,7 @@ function SearchFiltersParticipantsSelector({initialAccountIDs, onFiltersUpdate}: const [isSearchingForReports] = useOnyx(ONYXKEYS.IS_SEARCHING_FOR_REPORTS, {canBeMissing: false, initWithStoredValues: false}); const [reportAttributesDerived] = useOnyx(ONYXKEYS.DERIVED.REPORT_ATTRIBUTES, {canBeMissing: true, selector: reportsSelector}); const [countryCode = CONST.DEFAULT_COUNTRY_CODE] = useOnyx(ONYXKEYS.COUNTRY_CODE, {canBeMissing: false}); + const [loginList] = useOnyx(ONYXKEYS.LOGIN_LIST, {canBeMissing: true}); const [selectedOptions, setSelectedOptions] = useState([]); const [searchTerm, setSearchTerm] = useState(''); const cleanSearchTerm = useMemo(() => searchTerm.trim().toLowerCase(), [searchTerm]); @@ -67,20 +68,21 @@ function SearchFiltersParticipantsSelector({initialAccountIDs, onFiltersUpdate}: }, draftComments, nvpDismissedProductTraining, + loginList, { excludeLogins: CONST.EXPENSIFY_EMAILS_OBJECT, includeCurrentUser: true, }, countryCode, ); - }, [areOptionsInitialized, draftComments, options.personalDetails, options.reports, nvpDismissedProductTraining, countryCode]); + }, [areOptionsInitialized, options.reports, options.personalDetails, draftComments, nvpDismissedProductTraining, loginList, countryCode]); const unselectedOptions = useMemo(() => { return filterSelectedOptions(defaultOptions, new Set(selectedOptions.map((option) => option.accountID))); }, [defaultOptions, selectedOptions]); const chatOptions = useMemo(() => { - const filteredOptions = filterAndOrderOptions(unselectedOptions, cleanSearchTerm, countryCode, { + const filteredOptions = filterAndOrderOptions(unselectedOptions, cleanSearchTerm, countryCode, loginList, { selectedOptions, excludeLogins: CONST.EXPENSIFY_EMAILS_OBJECT, maxRecentReportsToShow: CONST.IOU.MAX_RECENT_REPORTS_TO_SHOW, @@ -95,7 +97,7 @@ function SearchFiltersParticipantsSelector({initialAccountIDs, onFiltersUpdate}: } return filteredOptions; - }, [unselectedOptions, cleanSearchTerm, selectedOptions, countryCode]); + }, [unselectedOptions, cleanSearchTerm, countryCode, loginList, selectedOptions]); const {sections, headerMessage} = useMemo(() => { const newSections: Section[] = []; diff --git a/src/hooks/useContactImport.ts b/src/hooks/useContactImport.ts index 67f1d5ed9456..1893e0d1272a 100644 --- a/src/hooks/useContactImport.ts +++ b/src/hooks/useContactImport.ts @@ -32,14 +32,15 @@ function useContactImport(): UseContactImportResult { const [contacts, setContacts] = useState>>([]); const {localeCompare} = useLocalize(); const [countryCode = CONST.DEFAULT_COUNTRY_CODE] = useOnyx(ONYXKEYS.COUNTRY_CODE, {canBeMissing: false}); + const [loginList] = useOnyx(ONYXKEYS.LOGIN_LIST, {canBeMissing: true}); const importAndSaveContacts = useCallback(() => { contactImport().then(({contactList, permissionStatus}: ContactImportResult) => { setContactPermissionState(permissionStatus); - const usersFromContact = getContacts(contactList, localeCompare, countryCode); + const usersFromContact = getContacts(contactList, localeCompare, countryCode, loginList); setContacts(usersFromContact); }); - }, [localeCompare, countryCode]); + }, [localeCompare, countryCode, loginList]); useContactPermissions({ importAndSaveContacts, diff --git a/src/hooks/useSearchSelector.base.ts b/src/hooks/useSearchSelector.base.ts index 2295ba794f5c..7196fa0a9ea5 100644 --- a/src/hooks/useSearchSelector.base.ts +++ b/src/hooks/useSearchSelector.base.ts @@ -161,6 +161,7 @@ function useSearchSelectorBase({ const [selectedOptions, setSelectedOptions] = useState(initialSelected ?? []); const [maxResults, setMaxResults] = useState(maxResultsPerPage); const [countryCode = CONST.DEFAULT_COUNTRY_CODE] = useOnyx(ONYXKEYS.COUNTRY_CODE, {canBeMissing: false}); + const [loginList] = useOnyx(ONYXKEYS.LOGIN_LIST, {canBeMissing: true}); const [draftComments] = useOnyx(ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, {canBeMissing: true}); const [nvpDismissedProductTraining] = useOnyx(ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING, {canBeMissing: true}); @@ -193,9 +194,10 @@ function useSearchSelectorBase({ maxResults, includeUserToInvite, countryCode, + loginList, }); case CONST.SEARCH_SELECTOR.SEARCH_CONTEXT_MEMBER_INVITE: - return getValidOptions(optionsWithContacts, draftComments, nvpDismissedProductTraining, { + return getValidOptions(optionsWithContacts, draftComments, nvpDismissedProductTraining, loginList, { betas: betas ?? [], includeP2P: true, includeSelectedOptions: false, @@ -207,7 +209,7 @@ function useSearchSelectorBase({ includeUserToInvite, }); case CONST.SEARCH_SELECTOR.SEARCH_CONTEXT_GENERAL: - return getValidOptions(optionsWithContacts, draftComments, nvpDismissedProductTraining, { + return getValidOptions(optionsWithContacts, draftComments, nvpDismissedProductTraining, loginList, { ...getValidOptionsConfig, betas: betas ?? [], searchString: computedSearchTerm, @@ -221,6 +223,7 @@ function useSearchSelectorBase({ optionsWithContacts, draftComments, nvpDismissedProductTraining, + loginList, { betas, includeMultipleParticipantReports: true, @@ -237,7 +240,7 @@ function useSearchSelectorBase({ countryCode, ); case CONST.SEARCH_SELECTOR.SEARCH_CONTEXT_SHARE_DESTINATION: - return getValidOptions(optionsWithContacts, draftComments, nvpDismissedProductTraining, { + return getValidOptions(optionsWithContacts, draftComments, nvpDismissedProductTraining, loginList, { betas, selectedOptions, includeMultipleParticipantReports: true, @@ -255,7 +258,7 @@ function useSearchSelectorBase({ includeUserToInvite, }); case CONST.SEARCH_SELECTOR.SEARCH_CONTEXT_ATTENDEES: - return getValidOptions(optionsWithContacts, draftComments, nvpDismissedProductTraining, { + return getValidOptions(optionsWithContacts, draftComments, nvpDismissedProductTraining, loginList, { ...getValidOptionsConfig, betas: betas ?? [], includeP2P: true, @@ -283,6 +286,7 @@ function useSearchSelectorBase({ maxResults, includeUserToInvite, countryCode, + loginList, excludeLogins, includeRecentReports, maxRecentReportsToShow, diff --git a/src/libs/ContactUtils.ts b/src/libs/ContactUtils.ts index f5945491a4ce..dc562f996524 100644 --- a/src/libs/ContactUtils.ts +++ b/src/libs/ContactUtils.ts @@ -1,6 +1,7 @@ +import type {OnyxEntry} from 'react-native-onyx'; import type {LocaleContextProps} from '@components/LocaleContextProvider'; import CONST from '@src/CONST'; -import type {PersonalDetails} from '@src/types/onyx'; +import type {Login, PersonalDetails} from '@src/types/onyx'; import type {DeviceContact, StringHolder} from './ContactImport/types'; import {getUserToInviteContactOption} from './OptionsListUtils'; import type {SearchOption} from './OptionsListUtils'; @@ -28,7 +29,12 @@ function sortEmailObjects(emails: StringHolder[], localeCompare: LocaleContextPr }); } -const getContacts = (deviceContacts: DeviceContact[] | [], localeCompare: LocaleContextProps['localeCompare'], countryCode: number): Array> => { +const getContacts = ( + deviceContacts: DeviceContact[] | [], + localeCompare: LocaleContextProps['localeCompare'], + countryCode: number, + loginList: OnyxEntry, +): Array> => { return deviceContacts .map((contact) => { const email = sortEmailObjects(contact?.emailAddresses ?? [], localeCompare)?.at(0) ?? ''; @@ -48,6 +54,7 @@ const getContacts = (deviceContacts: DeviceContact[] | [], localeCompare: Locale phone: phoneNumber, avatar: avatarSource, countryCode, + loginList, }); }) .filter((contact): contact is SearchOption => contact !== null); diff --git a/src/libs/OptionsListUtils/index.ts b/src/libs/OptionsListUtils/index.ts index 404d7a4d021a..42439305b3da 100644 --- a/src/libs/OptionsListUtils/index.ts +++ b/src/libs/OptionsListUtils/index.ts @@ -198,12 +198,6 @@ Onyx.connect({ }, }); -let loginList: OnyxEntry; -Onyx.connect({ - key: ONYXKEYS.LOGIN_LIST, - callback: (value) => (loginList = isEmptyObject(value) ? {} : value), -}); - let allPersonalDetails: OnyxEntry; Onyx.connect({ key: ONYXKEYS.PERSONAL_DETAILS_LIST, @@ -1074,7 +1068,7 @@ function getPolicyExpenseReportOption(participant: Participant | SearchOptionDat * Note: We can't migrate this off of using logins because this is used to check if you're trying to start a chat with * yourself or a different user, and people won't be starting new chats via accountID usually. */ -function isCurrentUser(userDetails: PersonalDetails): boolean { +function isCurrentUser(userDetails: PersonalDetails, loginList: OnyxEntry): boolean { if (!userDetails) { return false; } @@ -1643,13 +1637,14 @@ function getUserToInviteOption({ showChatPreviewLine = false, shouldAcceptName = false, countryCode = CONST.DEFAULT_COUNTRY_CODE, + loginList = {}, }: GetUserToInviteConfig): SearchOptionData | null { if (!searchValue) { return null; } const parsedPhoneNumber = parsePhoneNumber(appendCountryCode(Str.removeSMSDomain(searchValue), countryCode)); - const isCurrentUserLogin = isCurrentUser({login: searchValue} as PersonalDetails); + const isCurrentUserLogin = isCurrentUser({login: searchValue} as PersonalDetails, loginList); const isInSelectedOption = selectedOptions.some((option) => 'login' in option && option.login === searchValue); const isValidEmail = Str.isValidEmail(searchValue) && !Str.isDomainEmail(searchValue) && !Str.endsWith(searchValue, CONST.SMS.DOMAIN); const isValidPhoneNumber = parsedPhoneNumber.possible && Str.isValidE164Phone(getPhoneNumberWithoutSpecialChars(parsedPhoneNumber.number?.input ?? '')); @@ -1701,6 +1696,7 @@ function getUserToInviteContactOption({ phone = '', avatar = '', countryCode = CONST.DEFAULT_COUNTRY_CODE, + loginList = {}, }: GetUserToInviteConfig): SearchOption | null { // If email is provided, use it as the primary identifier // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing @@ -1721,7 +1717,7 @@ function getUserToInviteContactOption({ const login = email ? effectiveSearchValue : (sanitizedPhoneLogin ?? searchValue); const normalizedLoginToExclude = addSMSDomainIfPhoneNumber(login).toLowerCase(); - const isCurrentUserLogin = isCurrentUser({login} as PersonalDetails); + const isCurrentUserLogin = isCurrentUser({login} as PersonalDetails, loginList); const isInSelectedOption = selectedOptions.some((option) => 'login' in option && option.login === login); const isInOptionToExclude = optionsToExclude.findIndex((optionToExclude) => 'login' in optionToExclude && optionToExclude.login === normalizedLoginToExclude) !== -1; @@ -2077,6 +2073,7 @@ function getValidOptions( options: OptionList, draftComments: OnyxCollection | undefined, nvpDismissedProductTraining: OnyxEntry, + loginList: OnyxEntry, { excludeLogins = {}, includeSelectedOptions = false, @@ -2273,6 +2270,7 @@ function getValidOptions( userToInvite = filterUserToInvite( {currentUserOption: currentUserRef.current, recentReports: recentReportOptions, personalDetails: personalDetailsOptions}, searchString ?? '', + loginList, countryCode, { excludeLogins: loginsToExclude, @@ -2305,6 +2303,7 @@ type SearchOptionsConfig = { countryCode?: number; shouldShowGBR?: boolean; shouldUnreadBeBold?: boolean; + loginList: OnyxEntry; }; /** @@ -2325,6 +2324,7 @@ function getSearchOptions({ countryCode = CONST.DEFAULT_COUNTRY_CODE, shouldShowGBR = false, shouldUnreadBeBold = false, + loginList, }: SearchOptionsConfig): Options { Timing.start(CONST.TIMING.LOAD_SEARCH_OPTIONS); Performance.markStart(CONST.TIMING.LOAD_SEARCH_OPTIONS); @@ -2333,6 +2333,7 @@ function getSearchOptions({ options, draftComments, nvpDismissedProductTraining, + loginList, { betas, includeRecentReports, @@ -2447,6 +2448,7 @@ function formatMemberForList(member: SearchOptionData): MemberForList { function getMemberInviteOptions( personalDetails: Array>, nvpDismissedProductTraining: OnyxEntry, + loginList: OnyxEntry, betas: Beta[] = [], excludeLogins: Record = {}, includeSelectedOptions = false, @@ -2456,6 +2458,7 @@ function getMemberInviteOptions( {personalDetails, reports: []}, undefined, nvpDismissedProductTraining, + loginList, { betas, includeP2P: true, @@ -2694,6 +2697,7 @@ function filterCurrentUserOption(currentUserOption: SearchOptionData | null | un function filterUserToInvite( options: Omit, searchValue: string, + loginList: OnyxEntry, countryCode: number = CONST.DEFAULT_COUNTRY_CODE, config?: FilterUserToInviteConfig, ): SearchOptionData | null { @@ -2721,6 +2725,7 @@ function filterUserToInvite( searchValue, loginsToExclude, countryCode, + loginList, ...config, }); } @@ -2755,7 +2760,7 @@ function filterSelfDMChat(report: SearchOptionData, searchTerms: string[]): Sear return isMatch ? report : undefined; } -function filterOptions(options: Options, searchInputValue: string, countryCode: number, config?: FilterUserToInviteConfig): Options { +function filterOptions(options: Options, searchInputValue: string, countryCode: number, loginList: OnyxEntry, config?: FilterUserToInviteConfig): Options { const trimmedSearchInput = searchInputValue.trim(); // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing @@ -2773,6 +2778,7 @@ function filterOptions(options: Options, searchInputValue: string, countryCode: currentUserOption, }, searchValue, + loginList, countryCode, config, ); @@ -2828,10 +2834,10 @@ function combineOrderingOfReportsAndPersonalDetails( * Filters and orders the options based on the search input value. * Note that personal details that are part of the recent reports will always be shown as part of the recent reports (ie. DMs). */ -function filterAndOrderOptions(options: Options, searchInputValue: string, countryCode: number, config: FilterAndOrderConfig = {}): Options { +function filterAndOrderOptions(options: Options, searchInputValue: string, countryCode: number, loginList: OnyxEntry, config: FilterAndOrderConfig = {}): Options { let filterResult = options; if (searchInputValue.trim().length > 0) { - filterResult = filterOptions(options, searchInputValue, countryCode, config); + filterResult = filterOptions(options, searchInputValue, countryCode, loginList, config); } const orderedOptions = combineOrderingOfReportsAndPersonalDetails(filterResult, searchInputValue, config); diff --git a/src/libs/OptionsListUtils/types.ts b/src/libs/OptionsListUtils/types.ts index c13b856af8bf..814cc47f115e 100644 --- a/src/libs/OptionsListUtils/types.ts +++ b/src/libs/OptionsListUtils/types.ts @@ -2,7 +2,7 @@ import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import type {OptionData} from '@libs/ReportUtils'; import type {AvatarSource} from '@libs/UserAvatarUtils'; import type {IOUAction} from '@src/CONST'; -import type {Beta, PersonalDetails, Report, ReportActions, TransactionViolation} from '@src/types/onyx'; +import type {Beta, Login, PersonalDetails, Report, ReportActions, TransactionViolation} from '@src/types/onyx'; import type {Icon, PendingAction} from '@src/types/onyx/OnyxCommon'; /** @@ -204,6 +204,7 @@ type GetUserToInviteConfig = { shouldAcceptName?: boolean; optionsToExclude?: GetOptionsConfig['selectedOptions']; countryCode?: number; + loginList: OnyxEntry; } & Pick; type MemberForList = { diff --git a/src/pages/NewChatPage.tsx b/src/pages/NewChatPage.tsx index c1c7f51f602b..52e05cf17f1d 100755 --- a/src/pages/NewChatPage.tsx +++ b/src/pages/NewChatPage.tsx @@ -61,6 +61,7 @@ function useOptions() { const [betas] = useOnyx(ONYXKEYS.BETAS, {canBeMissing: true}); const [newGroupDraft] = useOnyx(ONYXKEYS.NEW_GROUP_CHAT_DRAFT, {canBeMissing: true}); const [countryCode = CONST.DEFAULT_COUNTRY_CODE] = useOnyx(ONYXKEYS.COUNTRY_CODE, {canBeMissing: false}); + const [loginList] = useOnyx(ONYXKEYS.LOGIN_LIST, {canBeMissing: true}); const personalData = useCurrentUserPersonalDetails(); const focusTimeoutRef = useRef(null); const [didScreenTransitionEnd, setDidScreenTransitionEnd] = useState(false); @@ -95,6 +96,7 @@ function useOptions() { }, draftComments, nvpDismissedProductTraining, + loginList, { betas: betas ?? [], includeSelfDM: true, @@ -106,7 +108,7 @@ function useOptions() { const areOptionsInitialized = !isLoading; - const options = filterAndOrderOptions(unselectedOptions, debouncedSearchTerm, countryCode, { + const options = filterAndOrderOptions(unselectedOptions, debouncedSearchTerm, countryCode, loginList, { selectedOptions, maxRecentReportsToShow: CONST.IOU.MAX_RECENT_REPORTS_TO_SHOW, }); @@ -151,6 +153,7 @@ function useOptions() { personalDetails.find((option) => option.accountID === participant.accountID) ?? getUserToInviteOption({ searchValue: participant?.login, + loginList, }); if (participantOption) { result.push({ diff --git a/src/pages/RoomInvitePage.tsx b/src/pages/RoomInvitePage.tsx index 4ffe4a2b646f..570a97741823 100644 --- a/src/pages/RoomInvitePage.tsx +++ b/src/pages/RoomInvitePage.tsx @@ -61,6 +61,7 @@ function RoomInvitePage({ const {translate} = useLocalize(); const [userSearchPhrase] = useOnyx(ONYXKEYS.ROOM_MEMBERS_USER_SEARCH_PHRASE, {canBeMissing: true}); const [countryCode = CONST.DEFAULT_COUNTRY_CODE] = useOnyx(ONYXKEYS.COUNTRY_CODE, {canBeMissing: false}); + const [loginList] = useOnyx(ONYXKEYS.LOGIN_LIST, {canBeMissing: true}); const [searchTerm, debouncedSearchTerm, setSearchTerm] = useDebouncedState(userSearchPhrase ?? ''); const [selectedOptions, setSelectedOptions] = useState([]); const [isSearchingForReports] = useOnyx(ONYXKEYS.IS_SEARCHING_FOR_REPORTS, {initWithStoredValues: false, canBeMissing: true}); @@ -91,7 +92,7 @@ function RoomInvitePage({ return {recentReports: [], personalDetails: [], userToInvite: null, currentUserOption: null}; } - const inviteOptions = getMemberInviteOptions(options.personalDetails, nvpDismissedProductTraining, betas ?? [], excludedUsers); + const inviteOptions = getMemberInviteOptions(options.personalDetails, nvpDismissedProductTraining, loginList, betas ?? [], excludedUsers); // Update selectedOptions with the latest personalDetails information const detailsMap: Record = {}; for (const detail of inviteOptions.personalDetails) { @@ -112,16 +113,16 @@ function RoomInvitePage({ recentReports: [], currentUserOption: null, }; - }, [areOptionsInitialized, betas, excludedUsers, options.personalDetails, selectedOptions, nvpDismissedProductTraining]); + }, [areOptionsInitialized, betas, excludedUsers, loginList, nvpDismissedProductTraining, options.personalDetails, selectedOptions]); const inviteOptions = useMemo(() => { if (debouncedSearchTerm.trim() === '') { return defaultOptions; } - const filteredOptions = filterAndOrderOptions(defaultOptions, debouncedSearchTerm, countryCode, {excludeLogins: excludedUsers}); + const filteredOptions = filterAndOrderOptions(defaultOptions, debouncedSearchTerm, countryCode, loginList, {excludeLogins: excludedUsers}); return filteredOptions; - }, [debouncedSearchTerm, defaultOptions, excludedUsers, countryCode]); + }, [debouncedSearchTerm, defaultOptions, countryCode, loginList, excludedUsers]); const sections = useMemo(() => { const sectionsArr: Sections = []; diff --git a/src/pages/Share/ShareTab.tsx b/src/pages/Share/ShareTab.tsx index 4e262c56169b..99eab562ba82 100644 --- a/src/pages/Share/ShareTab.tsx +++ b/src/pages/Share/ShareTab.tsx @@ -47,6 +47,7 @@ function ShareTab({ref}: ShareTabProps) { const [betas] = useOnyx(ONYXKEYS.BETAS, {canBeMissing: true}); const selectionListRef = useRef(null); const [countryCode = CONST.DEFAULT_COUNTRY_CODE] = useOnyx(ONYXKEYS.COUNTRY_CODE, {canBeMissing: false}); + const [loginList] = useOnyx(ONYXKEYS.LOGIN_LIST, {canBeMissing: true}); const [draftComments] = useOnyx(ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, {canBeMissing: true}); const [nvpDismissedProductTraining] = useOnyx(ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING, {canBeMissing: true}); @@ -76,8 +77,9 @@ function ShareTab({ref}: ShareTabProps) { maxResults: 20, includeUserToInvite: true, countryCode, + loginList, }); - }, [areOptionsInitialized, betas, draftComments, nvpDismissedProductTraining, options, textInputValue, countryCode]); + }, [areOptionsInitialized, options, draftComments, nvpDismissedProductTraining, betas, textInputValue, countryCode, loginList]); const recentReportsOptions = useMemo(() => { if (textInputValue.trim() === '') { diff --git a/src/pages/iou/request/MoneyRequestAccountantSelector.tsx b/src/pages/iou/request/MoneyRequestAccountantSelector.tsx index 678f7f2ec075..cbcbe5739374 100644 --- a/src/pages/iou/request/MoneyRequestAccountantSelector.tsx +++ b/src/pages/iou/request/MoneyRequestAccountantSelector.tsx @@ -65,6 +65,7 @@ function MoneyRequestAccountantSelector({onFinish, onAccountantSelected, iouType const [reportAttributesDerived] = useOnyx(ONYXKEYS.DERIVED.REPORT_ATTRIBUTES, {canBeMissing: true, selector: reportsSelector}); const [draftComments] = useOnyx(ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, {canBeMissing: true}); const [nvpDismissedProductTraining] = useOnyx(ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING, {canBeMissing: true}); + const [loginList] = useOnyx(ONYXKEYS.LOGIN_LIST, {canBeMissing: true}); useEffect(() => { searchInServer(debouncedSearchTerm.trim()); @@ -82,6 +83,7 @@ function MoneyRequestAccountantSelector({onFinish, onAccountantSelected, iouType }, draftComments, nvpDismissedProductTraining, + loginList, { betas, excludeLogins: CONST.EXPENSIFY_EMAILS_OBJECT, @@ -96,7 +98,7 @@ function MoneyRequestAccountantSelector({onFinish, onAccountantSelected, iouType ...optionList, ...orderedOptions, }; - }, [action, areOptionsInitialized, betas, didScreenTransitionEnd, draftComments, nvpDismissedProductTraining, options.personalDetails, options.reports, countryCode]); + }, [areOptionsInitialized, didScreenTransitionEnd, options.reports, options.personalDetails, draftComments, nvpDismissedProductTraining, loginList, betas, action, countryCode]); const chatOptions = useMemo(() => { if (!areOptionsInitialized) { @@ -108,12 +110,12 @@ function MoneyRequestAccountantSelector({onFinish, onAccountantSelected, iouType headerMessage: '', }; } - const newOptions = filterAndOrderOptions(defaultOptions, debouncedSearchTerm, countryCode, { + const newOptions = filterAndOrderOptions(defaultOptions, debouncedSearchTerm, countryCode, loginList, { excludeLogins: CONST.EXPENSIFY_EMAILS_OBJECT, maxRecentReportsToShow: CONST.IOU.MAX_RECENT_REPORTS_TO_SHOW, }); return newOptions; - }, [areOptionsInitialized, defaultOptions, debouncedSearchTerm, countryCode]); + }, [areOptionsInitialized, defaultOptions, debouncedSearchTerm, countryCode, loginList]); /** * Returns the sections needed for the OptionsSelector @@ -153,7 +155,10 @@ function MoneyRequestAccountantSelector({onFinish, onAccountantSelected, iouType if ( chatOptions.userToInvite && - !isCurrentUser({...chatOptions.userToInvite, accountID: chatOptions.userToInvite?.accountID ?? CONST.DEFAULT_NUMBER_ID, status: chatOptions.userToInvite?.status ?? undefined}) + !isCurrentUser( + {...chatOptions.userToInvite, accountID: chatOptions.userToInvite?.accountID ?? CONST.DEFAULT_NUMBER_ID, status: chatOptions.userToInvite?.status ?? undefined}, + loginList, + ) ) { newSections.push({ title: undefined, @@ -184,6 +189,7 @@ function MoneyRequestAccountantSelector({onFinish, onAccountantSelected, iouType personalDetails, reportAttributesDerived, translate, + loginList, countryCode, ]); diff --git a/src/pages/iou/request/MoneyRequestAttendeeSelector.tsx b/src/pages/iou/request/MoneyRequestAttendeeSelector.tsx index 10585c59aa3a..36c8b99b1f8a 100644 --- a/src/pages/iou/request/MoneyRequestAttendeeSelector.tsx +++ b/src/pages/iou/request/MoneyRequestAttendeeSelector.tsx @@ -68,6 +68,7 @@ function MoneyRequestAttendeeSelector({attendees = [], onFinish, onAttendeesAdde const [isSearchingForReports] = useOnyx(ONYXKEYS.IS_SEARCHING_FOR_REPORTS, {initWithStoredValues: false, canBeMissing: true}); const [reportAttributesDerived] = useOnyx(ONYXKEYS.DERIVED.REPORT_ATTRIBUTES, {canBeMissing: true, selector: reportsSelector}); const offlineMessage: string = isOffline ? `${translate('common.youAppearToBeOffline')} ${translate('search.resultsAreLimited')}` : ''; + const [loginList] = useOnyx(ONYXKEYS.LOGIN_LIST, {canBeMissing: true}); const isPaidGroupPolicy = useMemo(() => isPaidGroupPolicyFn(policy), [policy]); @@ -239,11 +240,14 @@ function MoneyRequestAttendeeSelector({attendees = [], onFinish, onAttendeesAdde if ( orderedAvailableOptions.userToInvite && - !isCurrentUser({ - ...orderedAvailableOptions.userToInvite, - accountID: orderedAvailableOptions.userToInvite?.accountID ?? CONST.DEFAULT_NUMBER_ID, - status: orderedAvailableOptions.userToInvite?.status ?? undefined, - }) + !isCurrentUser( + { + ...orderedAvailableOptions.userToInvite, + accountID: orderedAvailableOptions.userToInvite?.accountID ?? CONST.DEFAULT_NUMBER_ID, + status: orderedAvailableOptions.userToInvite?.status ?? undefined, + }, + loginList, + ) ) { newSections.push({ title: undefined, @@ -267,15 +271,16 @@ function MoneyRequestAttendeeSelector({attendees = [], onFinish, onAttendeesAdde }, [ areOptionsInitialized, didScreenTransitionEnd, + searchTerm, + attendees, orderedAvailableOptions.recentReports, orderedAvailableOptions.personalDetails, orderedAvailableOptions.userToInvite, - searchTerm, - attendees, personalDetails, - translate, reportAttributesDerived, + loginList, countryCode, + translate, ]); const optionLength = useMemo(() => { diff --git a/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx b/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx index 97f73818dfa8..e067b69653df 100644 --- a/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx +++ b/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx @@ -120,6 +120,7 @@ function MoneyRequestParticipantsSelector({ const [currentUserLogin] = useOnyx(ONYXKEYS.SESSION, {canBeMissing: true, selector: emailSelector}); const [reportAttributesDerived] = useOnyx(ONYXKEYS.DERIVED.REPORT_ATTRIBUTES, {canBeMissing: true, selector: reportsSelector}); const [countryCode = CONST.DEFAULT_COUNTRY_CODE] = useOnyx(ONYXKEYS.COUNTRY_CODE, {canBeMissing: false}); + const [loginList] = useOnyx(ONYXKEYS.LOGIN_LIST, {canBeMissing: true}); const [textInputAutoFocus, setTextInputAutoFocus] = useState(!isNative); const selectionListRef = useRef(null); @@ -324,11 +325,14 @@ function MoneyRequestParticipantsSelector({ if ( !isWorkspacesOnly && availableOptions.userToInvite && - !isCurrentUser({ - ...availableOptions.userToInvite, - accountID: availableOptions.userToInvite?.accountID ?? CONST.DEFAULT_NUMBER_ID, - status: availableOptions.userToInvite?.status ?? undefined, - }) && + !isCurrentUser( + { + ...availableOptions.userToInvite, + accountID: availableOptions.userToInvite?.accountID ?? CONST.DEFAULT_NUMBER_ID, + status: availableOptions.userToInvite?.status ?? undefined, + }, + loginList, + ) && !isPerDiemRequest ) { newSections.push({ @@ -361,6 +365,7 @@ function MoneyRequestParticipantsSelector({ availableOptions.recentReports, availableOptions.personalDetails, isWorkspacesOnly, + loginList, isPerDiemRequest, showImportContacts, inputHelperText, diff --git a/src/pages/tasks/TaskAssigneeSelectorModal.tsx b/src/pages/tasks/TaskAssigneeSelectorModal.tsx index 52878177dd4a..0bc735cd1dc7 100644 --- a/src/pages/tasks/TaskAssigneeSelectorModal.tsx +++ b/src/pages/tasks/TaskAssigneeSelectorModal.tsx @@ -45,6 +45,7 @@ function TaskAssigneeSelectorModal() { const [isSearchingForReports] = useOnyx(ONYXKEYS.IS_SEARCHING_FOR_REPORTS, {initWithStoredValues: false, canBeMissing: true}); const [countryCode = CONST.DEFAULT_COUNTRY_CODE] = useOnyx(ONYXKEYS.COUNTRY_CODE, {canBeMissing: false}); const currentUserPersonalDetails = useCurrentUserPersonalDetails(); + const [loginList] = useOnyx(ONYXKEYS.LOGIN_LIST, {canBeMissing: true}); const {searchTerm, debouncedSearchTerm, setSearchTerm, availableOptions, areOptionsInitialized} = useSearchSelector({ selectionMode: CONST.SEARCH_SELECTOR.SELECTION_MODE_SINGLE, @@ -169,7 +170,7 @@ function TaskAssigneeSelectorModal() { assigneePersonalDetails, report.reportID, undefined, // passing null as report because for editing task the report will be task details report page not the actual report where task was created - isCurrentUser({...option, accountID: option?.accountID ?? CONST.DEFAULT_NUMBER_ID, login: option?.login ?? ''}), + isCurrentUser({...option, accountID: option?.accountID ?? CONST.DEFAULT_NUMBER_ID, login: option?.login ?? ''}, loginList), ); // Pass through the selected assignee editTaskAssignee( @@ -195,7 +196,7 @@ function TaskAssigneeSelectorModal() { assigneePersonalDetails, task?.shareDestination ?? '', undefined, // passing null as report is null in this condition - isCurrentUser({...option, accountID: option?.accountID ?? CONST.DEFAULT_NUMBER_ID, login: option?.login ?? undefined}), + isCurrentUser({...option, accountID: option?.accountID ?? CONST.DEFAULT_NUMBER_ID, login: option?.login ?? undefined}, loginList), ); // eslint-disable-next-line @typescript-eslint/no-deprecated InteractionManager.runAfterInteractions(() => { @@ -203,7 +204,7 @@ function TaskAssigneeSelectorModal() { }); } }, - [report, currentUserPersonalDetails.accountID, task?.shareDestination, backTo, hasOutstandingChildTask, allPersonalDetails, parentReport], + [allPersonalDetails, report, currentUserPersonalDetails.accountID, loginList, parentReport, hasOutstandingChildTask, task?.shareDestination, backTo], ); const handleBackButtonPress = useCallback(() => Navigation.goBack(!route.params?.reportID ? ROUTES.NEW_TASK.getRoute(backTo) : backTo), [route.params, backTo]); diff --git a/tests/perf-test/OptionsListUtils.perf-test.ts b/tests/perf-test/OptionsListUtils.perf-test.ts index e9ff5807ca1d..8b23f320a2aa 100644 --- a/tests/perf-test/OptionsListUtils.perf-test.ts +++ b/tests/perf-test/OptionsListUtils.perf-test.ts @@ -90,6 +90,8 @@ const ValidOptionsConfig = { includeOwnedWorkspaceChats: true, }; +const loginList = {}; + /* GetOption is the private function and is never called directly, we are testing the functions which call getOption with different params */ describe('OptionsListUtils', () => { beforeAll(() => { @@ -110,22 +112,22 @@ describe('OptionsListUtils', () => { /* Testing getSearchOptions */ test('[OptionsListUtils] getSearchOptions', async () => { await waitForBatchedUpdates(); - await measureFunction(() => getSearchOptions({options, betas: mockedBetas, draftComments: {}, nvpDismissedProductTraining})); + await measureFunction(() => getSearchOptions({options, betas: mockedBetas, draftComments: {}, nvpDismissedProductTraining, loginList})); }); /* Testing getFilteredOptions */ test('[OptionsListUtils] getFilteredOptions with search value', async () => { await waitForBatchedUpdates(); - const formattedOptions = getValidOptions({reports: options.reports, personalDetails: options.personalDetails}, {}, nvpDismissedProductTraining, ValidOptionsConfig); + const formattedOptions = getValidOptions({reports: options.reports, personalDetails: options.personalDetails}, {}, nvpDismissedProductTraining, loginList, ValidOptionsConfig); await measureFunction(() => { - filterAndOrderOptions(formattedOptions, SEARCH_VALUE, COUNTRY_CODE); + filterAndOrderOptions(formattedOptions, SEARCH_VALUE, COUNTRY_CODE, loginList); }); }); test('[OptionsListUtils] getFilteredOptions with empty search value', async () => { await waitForBatchedUpdates(); - const formattedOptions = getValidOptions({reports: options.reports, personalDetails: options.personalDetails}, {}, nvpDismissedProductTraining, ValidOptionsConfig); + const formattedOptions = getValidOptions({reports: options.reports, personalDetails: options.personalDetails}, {}, nvpDismissedProductTraining, loginList, ValidOptionsConfig); await measureFunction(() => { - filterAndOrderOptions(formattedOptions, '', COUNTRY_CODE); + filterAndOrderOptions(formattedOptions, '', COUNTRY_CODE, loginList); }); }); @@ -133,7 +135,7 @@ describe('OptionsListUtils', () => { test('[OptionsListUtils] getShareDestinationOptions', async () => { await waitForBatchedUpdates(); await measureFunction(() => - getValidOptions({reports: options.reports, personalDetails: options.personalDetails}, {}, nvpDismissedProductTraining, { + getValidOptions({reports: options.reports, personalDetails: options.personalDetails}, {}, nvpDismissedProductTraining, loginList, { betas: mockedBetas, includeMultipleParticipantReports: true, showChatPreviewLine: true, @@ -153,7 +155,7 @@ describe('OptionsListUtils', () => { /* Testing getMemberInviteOptions */ test('[OptionsListUtils] getMemberInviteOptions', async () => { await waitForBatchedUpdates(); - await measureFunction(() => getMemberInviteOptions(options.personalDetails, nvpDismissedProductTraining, mockedBetas)); + await measureFunction(() => getMemberInviteOptions(options.personalDetails, nvpDismissedProductTraining, loginList, mockedBetas)); }); test('[OptionsListUtils] worst case scenario with a search term that matches a subset of selectedOptions, filteredRecentReports, and filteredPersonalDetails', async () => { diff --git a/tests/unit/OptionsListUtilsTest.tsx b/tests/unit/OptionsListUtilsTest.tsx index c682965fcafa..e4dfa68ebdf3 100644 --- a/tests/unit/OptionsListUtilsTest.tsx +++ b/tests/unit/OptionsListUtilsTest.tsx @@ -589,6 +589,8 @@ describe('OptionsListUtils', () => { }, ]; + const loginList = {}; + const reportNameValuePairs = { private_isArchived: DateUtils.getDBTime(), }; @@ -638,7 +640,7 @@ describe('OptionsListUtils', () => { it('should return all options when no search value is provided', () => { // Given a set of options // When we call getSearchOptions with all betas - const results = getSearchOptions({options: OPTIONS, draftComments: {}, nvpDismissedProductTraining, betas: [CONST.BETAS.ALL]}); + const results = getSearchOptions({options: OPTIONS, draftComments: {}, nvpDismissedProductTraining, loginList, betas: [CONST.BETAS.ALL]}); // Then all personal details (including those that have reports) should be returned expect(results.personalDetails.length).toBe(10); @@ -662,6 +664,7 @@ describe('OptionsListUtils', () => { includeUserToInvite: false, includeRecentReports: true, includeCurrentUser: true, + loginList, }); // Then the current user should be included in personalDetails @@ -688,6 +691,7 @@ describe('OptionsListUtils', () => { maxResults: undefined, includeUserToInvite: false, includeRecentReports: true, + loginList, }); // Then the current user should not be included in personalDetails @@ -710,6 +714,7 @@ describe('OptionsListUtils', () => { }, {}, nvpDismissedProductTraining, + loginList, ); // When we call orderOptions() results = orderOptions(results); @@ -741,7 +746,12 @@ describe('OptionsListUtils', () => { it('should sort personal details options alphabetically when only personal details are provided', () => { // Given a set of personalDetails and an empty reports array - let results: Pick = getValidOptions({personalDetails: OPTIONS.personalDetails, reports: []}, {}, nvpDismissedProductTraining); + let results: Pick = getValidOptions( + {personalDetails: OPTIONS.personalDetails, reports: []}, + {}, + nvpDismissedProductTraining, + loginList, + ); // When we call orderOptions() results = orderOptions(results); @@ -768,7 +778,7 @@ describe('OptionsListUtils', () => { it('should return empty options when no reports or personal details are provided', () => { // Given empty arrays of reports and personalDetails // When we call getValidOptions() - const results = getValidOptions({reports: [], personalDetails: []}, {}, nvpDismissedProductTraining); + const results = getValidOptions({reports: [], personalDetails: []}, {}, nvpDismissedProductTraining, loginList); // Then the result should be empty expect(results.personalDetails).toEqual([]); @@ -782,7 +792,7 @@ describe('OptionsListUtils', () => { it('should include Concierge by default in results', () => { // Given a set of reports and personalDetails that includes Concierge // When we call getValidOptions() - const results = getValidOptions({reports: OPTIONS_WITH_CONCIERGE.reports, personalDetails: OPTIONS_WITH_CONCIERGE.personalDetails}, {}, nvpDismissedProductTraining); + const results = getValidOptions({reports: OPTIONS_WITH_CONCIERGE.reports, personalDetails: OPTIONS_WITH_CONCIERGE.personalDetails}, {}, nvpDismissedProductTraining, loginList); // Then the result should include all personalDetails except the currently logged in user expect(results.personalDetails.length).toBe(Object.values(OPTIONS_WITH_CONCIERGE.personalDetails).length - 1); @@ -800,6 +810,7 @@ describe('OptionsListUtils', () => { }, {}, nvpDismissedProductTraining, + loginList, { excludeLogins: {[CONST.EMAIL.CONCIERGE]: true}, }, @@ -814,7 +825,7 @@ describe('OptionsListUtils', () => { it('should exclude Chronos when excludedLogins is specified', () => { // Given a set of reports and personalDetails that includes Chronos and a config object that excludes Chronos // When we call getValidOptions() - const results = getValidOptions({reports: OPTIONS_WITH_CHRONOS.reports, personalDetails: OPTIONS_WITH_CHRONOS.personalDetails}, {}, nvpDismissedProductTraining, { + const results = getValidOptions({reports: OPTIONS_WITH_CHRONOS.reports, personalDetails: OPTIONS_WITH_CHRONOS.personalDetails}, {}, nvpDismissedProductTraining, loginList, { excludeLogins: {[CONST.EMAIL.CHRONOS]: true}, }); @@ -834,6 +845,7 @@ describe('OptionsListUtils', () => { }, {}, nvpDismissedProductTraining, + loginList, { excludeLogins: {[CONST.EMAIL.RECEIPTS]: true}, }, @@ -857,6 +869,7 @@ describe('OptionsListUtils', () => { timestamp: DateUtils.getDBTime(new Date().valueOf()), }, }, + loginList, { includeP2P: true, canShowManagerMcTest: true, @@ -873,11 +886,17 @@ describe('OptionsListUtils', () => { it('should exclude Manager McTest from results if flag is set to false', () => { // Given a set of reports and personalDetails that includes Manager McTest and a config object that excludes Manager McTest // When we call getValidOptions() - const result = getValidOptions({reports: OPTIONS_WITH_MANAGER_MCTEST.reports, personalDetails: OPTIONS_WITH_MANAGER_MCTEST.personalDetails}, {}, nvpDismissedProductTraining, { - includeP2P: true, - canShowManagerMcTest: false, - betas: [CONST.BETAS.NEWDOT_MANAGER_MCTEST], - }); + const result = getValidOptions( + {reports: OPTIONS_WITH_MANAGER_MCTEST.reports, personalDetails: OPTIONS_WITH_MANAGER_MCTEST.personalDetails}, + {}, + nvpDismissedProductTraining, + loginList, + { + includeP2P: true, + canShowManagerMcTest: false, + betas: [CONST.BETAS.NEWDOT_MANAGER_MCTEST], + }, + ); // Then the result should include all personalDetails except the currently logged in user and Manager McTest expect(result.personalDetails.length).toBe(Object.values(OPTIONS_WITH_MANAGER_MCTEST.personalDetails).length - 2); @@ -901,6 +920,7 @@ describe('OptionsListUtils', () => { {reports: OPTIONS_WITH_MANAGER_MCTEST.reports, personalDetails: OPTIONS_WITH_MANAGER_MCTEST.personalDetails}, {}, nvpDismissedProductTraining, + loginList, {includeP2P: true, canShowManagerMcTest: true, betas: [CONST.BETAS.NEWDOT_MANAGER_MCTEST]}, ); @@ -946,7 +966,7 @@ describe('OptionsListUtils', () => { notificationPreference: 'hidden', }; // When we call getValidOptions with includeMultipleParticipantReports set to true - const results = getValidOptions({reports: [adminRoom], personalDetails: OPTIONS.personalDetails}, {}, nvpDismissedProductTraining, { + const results = getValidOptions({reports: [adminRoom], personalDetails: OPTIONS.personalDetails}, {}, nvpDismissedProductTraining, loginList, { includeMultipleParticipantReports: true, }); const adminRoomOption = results.recentReports.find((report) => report.reportID === '1455140530846319'); @@ -991,7 +1011,7 @@ describe('OptionsListUtils', () => { notificationPreference: 'hidden', brickRoadIndicator: CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR, }; - const results = getValidOptions({reports: [workspaceChat], personalDetails: []}, {}, nvpDismissedProductTraining, { + const results = getValidOptions({reports: [workspaceChat], personalDetails: []}, {}, nvpDismissedProductTraining, loginList, { includeMultipleParticipantReports: true, showRBR: true, }); @@ -1034,7 +1054,7 @@ describe('OptionsListUtils', () => { notificationPreference: 'hidden', brickRoadIndicator: CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR, }; - const results = getValidOptions({reports: [workspaceChat], personalDetails: []}, {}, nvpDismissedProductTraining, { + const results = getValidOptions({reports: [workspaceChat], personalDetails: []}, {}, nvpDismissedProductTraining, loginList, { includeMultipleParticipantReports: true, showRBR: false, }); @@ -1081,7 +1101,7 @@ describe('OptionsListUtils', () => { isBold: false, }; - const results = getValidOptions({reports: [inputOption], personalDetails: []}, {}, nvpDismissedProductTraining, { + const results = getValidOptions({reports: [inputOption], personalDetails: []}, {}, nvpDismissedProductTraining, loginList, { includeRecentReports: true, shouldUnreadBeBold: true, includeMultipleParticipantReports: true, @@ -1096,7 +1116,7 @@ describe('OptionsListUtils', () => { it('should include all reports by default', () => { // Given a set of reports and personalDetails that includes workspace rooms // When we call getValidOptions() - const results = getValidOptions(OPTIONS_WITH_WORKSPACE_ROOM, {}, nvpDismissedProductTraining, { + const results = getValidOptions(OPTIONS_WITH_WORKSPACE_ROOM, {}, nvpDismissedProductTraining, loginList, { includeRecentReports: true, includeMultipleParticipantReports: true, includeP2P: true, @@ -1113,7 +1133,7 @@ describe('OptionsListUtils', () => { it('should exclude users with recent reports from personalDetails', () => { // Given a set of reports and personalDetails // When we call getValidOptions with no search value - const results = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}, nvpDismissedProductTraining); + const results = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}, nvpDismissedProductTraining, loginList); const reportLogins = new Set(results.recentReports.map((reportOption) => reportOption.login)); const personalDetailsOverlapWithReports = results.personalDetails.every((personalDetailOption) => reportLogins.has(personalDetailOption.login)); @@ -1126,7 +1146,7 @@ describe('OptionsListUtils', () => { it('should exclude selected options', () => { // Given a set of reports and personalDetails // When we call getValidOptions with excludeLogins param - const results = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}, nvpDismissedProductTraining, { + const results = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}, nvpDismissedProductTraining, loginList, { excludeLogins: {'peterparker@expensify.com': true}, }); @@ -1138,7 +1158,7 @@ describe('OptionsListUtils', () => { it('should include Concierge in the results by default', () => { // Given a set of report and personalDetails that include Concierge // When we call getValidOptions() - const results = getValidOptions({reports: OPTIONS_WITH_CONCIERGE.reports, personalDetails: OPTIONS_WITH_CONCIERGE.personalDetails}, {}, nvpDismissedProductTraining); + const results = getValidOptions({reports: OPTIONS_WITH_CONCIERGE.reports, personalDetails: OPTIONS_WITH_CONCIERGE.personalDetails}, {}, nvpDismissedProductTraining, loginList); // Then the result should include all personalDetails except the currently logged in user expect(results.personalDetails.length).toBe(Object.values(OPTIONS_WITH_CONCIERGE.personalDetails).length - 1); @@ -1156,6 +1176,7 @@ describe('OptionsListUtils', () => { }, {}, nvpDismissedProductTraining, + loginList, { excludeLogins: {[CONST.EMAIL.CONCIERGE]: true}, }, @@ -1171,7 +1192,7 @@ describe('OptionsListUtils', () => { it('should exclude Chronos from the results when it is specified in excludedLogins', () => { // given a set of reports and personalDetails that includes Chronos // When we call getValidOptions() with excludeLogins param - const results = getValidOptions({reports: OPTIONS_WITH_CHRONOS.reports, personalDetails: OPTIONS_WITH_CHRONOS.personalDetails}, {}, nvpDismissedProductTraining, { + const results = getValidOptions({reports: OPTIONS_WITH_CHRONOS.reports, personalDetails: OPTIONS_WITH_CHRONOS.personalDetails}, {}, nvpDismissedProductTraining, loginList, { excludeLogins: {[CONST.EMAIL.CHRONOS]: true}, }); @@ -1192,6 +1213,7 @@ describe('OptionsListUtils', () => { }, {}, nvpDismissedProductTraining, + loginList, { excludeLogins: {[CONST.EMAIL.RECEIPTS]: true}, }, @@ -1208,7 +1230,7 @@ describe('OptionsListUtils', () => { // Given a set of reports and personalDetails with multiple reports // When we call getValidOptions with maxRecentReportElements set to 2 const maxRecentReports = 2; - const results = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}, nvpDismissedProductTraining, { + const results = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}, nvpDismissedProductTraining, loginList, { maxRecentReportElements: maxRecentReports, }); @@ -1219,8 +1241,10 @@ describe('OptionsListUtils', () => { it('should show all reports when maxRecentReportElements is not specified', () => { // Given a set of reports and personalDetails // When we call getValidOptions without maxRecentReportElements - const resultsWithoutLimit = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}, nvpDismissedProductTraining); - const resultsWithLimit = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}, nvpDismissedProductTraining, {maxRecentReportElements: 2}); + const resultsWithoutLimit = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}, nvpDismissedProductTraining, loginList); + const resultsWithLimit = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}, nvpDismissedProductTraining, loginList, { + maxRecentReportElements: 2, + }); // Then the results without limit should have more or equal reports expect(resultsWithoutLimit.recentReports.length).toBeGreaterThanOrEqual(resultsWithLimit.recentReports.length); @@ -1229,8 +1253,10 @@ describe('OptionsListUtils', () => { it('should not affect personalDetails count when maxRecentReportElements is specified', () => { // Given a set of reports and personalDetails // When we call getValidOptions with and without maxRecentReportElements - const resultsWithoutLimit = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}, nvpDismissedProductTraining); - const resultsWithLimit = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}, nvpDismissedProductTraining, {maxRecentReportElements: 2}); + const resultsWithoutLimit = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}, nvpDismissedProductTraining, loginList); + const resultsWithLimit = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}, nvpDismissedProductTraining, loginList, { + maxRecentReportElements: 2, + }); // Then personalDetails should remain the same regardless of maxRecentReportElements expect(resultsWithLimit.personalDetails.length).toBe(resultsWithoutLimit.personalDetails.length); @@ -1241,7 +1267,7 @@ describe('OptionsListUtils', () => { // When we call getValidOptions with both maxElements and maxRecentReportElements const maxRecentReports = 3; const maxTotalElements = 10; - const results = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}, nvpDismissedProductTraining, { + const results = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}, nvpDismissedProductTraining, loginList, { maxElements: maxTotalElements, maxRecentReportElements: maxRecentReports, }); @@ -1266,7 +1292,7 @@ describe('OptionsListUtils', () => { }, []); // When we call getValidOptions for share destination with an empty search value - const results = getValidOptions({reports: filteredReports, personalDetails: OPTIONS.personalDetails}, {}, nvpDismissedProductTraining, { + const results = getValidOptions({reports: filteredReports, personalDetails: OPTIONS.personalDetails}, {}, nvpDismissedProductTraining, loginList, { betas: [], includeMultipleParticipantReports: true, showChatPreviewLine: true, @@ -1298,7 +1324,7 @@ describe('OptionsListUtils', () => { }, []); // When we call getValidOptions for share destination with an empty search value - const results = getValidOptions({reports: filteredReportsWithWorkspaceRooms, personalDetails: OPTIONS.personalDetails}, {}, nvpDismissedProductTraining, { + const results = getValidOptions({reports: filteredReportsWithWorkspaceRooms, personalDetails: OPTIONS.personalDetails}, {}, nvpDismissedProductTraining, loginList, { betas: [], includeMultipleParticipantReports: true, showChatPreviewLine: true, @@ -1322,7 +1348,7 @@ describe('OptionsListUtils', () => { it('should sort personal details alphabetically', () => { // Given a set of personalDetails // When we call getMemberInviteOptions - const results = getMemberInviteOptions(OPTIONS.personalDetails, nvpDismissedProductTraining, []); + const results = getMemberInviteOptions(OPTIONS.personalDetails, nvpDismissedProductTraining, loginList, []); // Then personal details should be sorted alphabetically expect(results.personalDetails.at(0)?.text).toBe('Black Panther'); @@ -1506,9 +1532,9 @@ describe('OptionsListUtils', () => { it('should return all options when search is empty', () => { // Given a set of options // When we call getSearchOptions with all betas - const options = getSearchOptions({options: OPTIONS, draftComments: {}, nvpDismissedProductTraining, betas: [CONST.BETAS.ALL]}); + const options = getSearchOptions({options: OPTIONS, draftComments: {}, nvpDismissedProductTraining, loginList, betas: [CONST.BETAS.ALL]}); // When we pass the returned options to filterAndOrderOptions with an empty search value - const filteredOptions = filterAndOrderOptions(options, '', COUNTRY_CODE); + const filteredOptions = filterAndOrderOptions(options, '', COUNTRY_CODE, loginList); // Then all options should be returned expect(filteredOptions.recentReports.length + filteredOptions.personalDetails.length).toBe(14); @@ -1518,9 +1544,9 @@ describe('OptionsListUtils', () => { const searchText = 'man'; // Given a set of options // When we call getSearchOptions with all betas - const options = getSearchOptions({options: OPTIONS, draftComments: {}, nvpDismissedProductTraining, betas: [CONST.BETAS.ALL]}); + const options = getSearchOptions({options: OPTIONS, draftComments: {}, nvpDismissedProductTraining, loginList, betas: [CONST.BETAS.ALL]}); // When we pass the returned options to filterAndOrderOptions with a search value and sortByReportTypeInSearch param - const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE, {sortByReportTypeInSearch: true}); + const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE, loginList, {sortByReportTypeInSearch: true}); // Then we expect all options to be part of the recentReports list and reports should be first: expect(filteredOptions.personalDetails.length).toBe(0); @@ -1537,9 +1563,9 @@ describe('OptionsListUtils', () => { const searchText = 'mistersinister@marauders.com'; // Given a set of options // When we call getSearchOptions with all betas - const options = getSearchOptions({options: OPTIONS, draftComments: {}, nvpDismissedProductTraining, betas: [CONST.BETAS.ALL]}); + const options = getSearchOptions({options: OPTIONS, draftComments: {}, nvpDismissedProductTraining, loginList, betas: [CONST.BETAS.ALL]}); // When we pass the returned options to filterAndOrderOptions with a search value - const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE); + const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE, loginList); // Then only one report should be returned expect(filteredOptions.recentReports.length).toBe(1); @@ -1551,9 +1577,9 @@ describe('OptionsListUtils', () => { const searchText = 'Archived'; // Given a set of options // When we call getSearchOptions with all betas - const options = getSearchOptions({options: OPTIONS, draftComments: {}, nvpDismissedProductTraining, betas: [CONST.BETAS.ALL]}); + const options = getSearchOptions({options: OPTIONS, draftComments: {}, nvpDismissedProductTraining, loginList, betas: [CONST.BETAS.ALL]}); // When we pass the returned options to filterAndOrderOptions with a search value - const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE); + const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE, loginList); // Then only one report should be returned expect(filteredOptions.recentReports.length).toBe(1); @@ -1567,9 +1593,9 @@ describe('OptionsListUtils', () => { // Given a set of options created from PERSONAL_DETAILS_WITH_PERIODS const OPTIONS_WITH_PERIODS = createOptionList(PERSONAL_DETAILS_WITH_PERIODS, REPORTS); // When we call getSearchOptions with all betas - const options = getSearchOptions({options: OPTIONS_WITH_PERIODS, draftComments: {}, nvpDismissedProductTraining, betas: [CONST.BETAS.ALL]}); + const options = getSearchOptions({options: OPTIONS_WITH_PERIODS, draftComments: {}, nvpDismissedProductTraining, loginList, betas: [CONST.BETAS.ALL]}); // When we pass the returned options to filterAndOrderOptions with a search value and sortByReportTypeInSearch param - const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE, {sortByReportTypeInSearch: true}); + const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE, loginList, {sortByReportTypeInSearch: true}); // Then only one report should be returned expect(filteredOptions.recentReports.length).toBe(1); @@ -1581,9 +1607,9 @@ describe('OptionsListUtils', () => { const searchText = 'avengers'; // Given a set of options with workspace rooms // When we call getSearchOptions with all betas - const options = getSearchOptions({options: OPTIONS_WITH_WORKSPACE_ROOM, draftComments: {}, nvpDismissedProductTraining, betas: [CONST.BETAS.ALL]}); + const options = getSearchOptions({options: OPTIONS_WITH_WORKSPACE_ROOM, draftComments: {}, nvpDismissedProductTraining, loginList, betas: [CONST.BETAS.ALL]}); // When we pass the returned options to filterAndOrderOptions with a search value - const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE); + const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE, loginList); // Then only one report should be returned expect(filteredOptions.recentReports.length).toBe(1); @@ -1594,9 +1620,9 @@ describe('OptionsListUtils', () => { it('should put exact match by login on the top of the list', () => { const searchText = 'reedrichards@expensify.com'; // Given a set of options with all betas - const options = getSearchOptions({options: OPTIONS, draftComments: {}, nvpDismissedProductTraining, betas: [CONST.BETAS.ALL]}); + const options = getSearchOptions({options: OPTIONS, draftComments: {}, nvpDismissedProductTraining, loginList, betas: [CONST.BETAS.ALL]}); // When we pass the returned options to filterAndOrderOptions with a search value - const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE); + const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE, loginList); // Then only one report should be returned expect(filteredOptions.recentReports.length).toBe(1); @@ -1609,9 +1635,9 @@ describe('OptionsListUtils', () => { // Given a set of options with chat rooms const OPTIONS_WITH_CHAT_ROOMS = createOptionList(PERSONAL_DETAILS, REPORTS_WITH_CHAT_ROOM); // When we call getSearchOptions with all betas - const options = getSearchOptions({options: OPTIONS_WITH_CHAT_ROOMS, draftComments: {}, nvpDismissedProductTraining, betas: [CONST.BETAS.ALL]}); + const options = getSearchOptions({options: OPTIONS_WITH_CHAT_ROOMS, draftComments: {}, nvpDismissedProductTraining, loginList, betas: [CONST.BETAS.ALL]}); // When we pass the returned options to filterAndOrderOptions with a search value - const filterOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE); + const filterOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE, loginList); // Then only two reports should be returned expect(filterOptions.recentReports.length).toBe(2); @@ -1623,9 +1649,9 @@ describe('OptionsListUtils', () => { renderLocaleContextProvider(); const searchText = 'fantastic'; // Given a set of options - const options = getSearchOptions({options: OPTIONS, draftComments: {}, nvpDismissedProductTraining}); + const options = getSearchOptions({options: OPTIONS, draftComments: {}, nvpDismissedProductTraining, loginList}); // When we call filterAndOrderOptions with a search value - const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE); + const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE, loginList); // Then only three reports should be returned expect(filteredOptions.recentReports.length).toBe(3); @@ -1638,9 +1664,9 @@ describe('OptionsListUtils', () => { it('should return the user to invite when the search value is a valid, non-existent email', () => { const searchText = 'test@email.com'; // Given a set of options - const options = getSearchOptions({options: OPTIONS, draftComments: {}, nvpDismissedProductTraining}); + const options = getSearchOptions({options: OPTIONS, draftComments: {}, loginList, nvpDismissedProductTraining}); // When we call filterAndOrderOptions with a search value - const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE); + const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE, loginList); // Then the user to invite should be returned expect(filteredOptions.userToInvite?.login).toBe(searchText); @@ -1649,11 +1675,11 @@ describe('OptionsListUtils', () => { it('should not return any results if the search value is on an excluded logins list', () => { const searchText = 'admin@expensify.com'; // Given a set of options with excluded logins list - const options = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}, nvpDismissedProductTraining, { + const options = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}, nvpDismissedProductTraining, loginList, { excludeLogins: CONST.EXPENSIFY_EMAILS_OBJECT, }); // When we call filterAndOrderOptions with a search value and excluded logins list - const filterOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE, {excludeLogins: CONST.EXPENSIFY_EMAILS_OBJECT}); + const filterOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE, loginList, {excludeLogins: CONST.EXPENSIFY_EMAILS_OBJECT}); // Then no personal details should be returned expect(filterOptions.recentReports.length).toBe(0); @@ -1662,9 +1688,9 @@ describe('OptionsListUtils', () => { it('should return the user to invite when the search value is a valid, non-existent email and the user is not excluded', () => { const searchText = 'test@email.com'; // Given a set of options - const options = getSearchOptions({options: OPTIONS, draftComments: {}, nvpDismissedProductTraining}); + const options = getSearchOptions({options: OPTIONS, draftComments: {}, loginList, nvpDismissedProductTraining}); // When we call filterAndOrderOptions with a search value and excludeLogins - const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE, {excludeLogins: CONST.EXPENSIFY_EMAILS_OBJECT}); + const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE, loginList, {excludeLogins: CONST.EXPENSIFY_EMAILS_OBJECT}); // Then the user to invite should be returned expect(filteredOptions.userToInvite?.login).toBe(searchText); @@ -1673,16 +1699,16 @@ describe('OptionsListUtils', () => { it('should return limited amount of recent reports if the limit is set', () => { const searchText = ''; // Given a set of options - const options = getSearchOptions({options: OPTIONS, draftComments: {}, nvpDismissedProductTraining}); + const options = getSearchOptions({options: OPTIONS, draftComments: {}, loginList, nvpDismissedProductTraining}); // When we call filterAndOrderOptions with a search value and maxRecentReportsToShow set to 2 - const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE, {maxRecentReportsToShow: 2}); + const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE, loginList, {maxRecentReportsToShow: 2}); // Then only two reports should be returned expect(filteredOptions.recentReports.length).toBe(2); // Note: in the past maxRecentReportsToShow: 0 would return all recent reports, this has changed, and is expected to return none now // When we call filterAndOrderOptions with a search value and maxRecentReportsToShow set to 0 - const limitToZeroOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE, {maxRecentReportsToShow: 0}); + const limitToZeroOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE, loginList, {maxRecentReportsToShow: 0}); // Then no reports should be returned expect(limitToZeroOptions.recentReports.length).toBe(0); @@ -1691,9 +1717,9 @@ describe('OptionsListUtils', () => { it('should not return any user to invite if email exists on the personal details list', () => { const searchText = 'natasharomanoff@expensify.com'; // Given a set of options with all betas - const options = getSearchOptions({options: OPTIONS, draftComments: {}, nvpDismissedProductTraining, betas: [CONST.BETAS.ALL]}); + const options = getSearchOptions({options: OPTIONS, draftComments: {}, nvpDismissedProductTraining, loginList, betas: [CONST.BETAS.ALL]}); // When we call filterAndOrderOptions with a search value - const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE); + const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE, loginList); // Then there should be one matching result expect(filteredOptions.personalDetails.length).toBe(1); @@ -1703,9 +1729,9 @@ describe('OptionsListUtils', () => { it('should not return any options if search value does not match any personal details (getMemberInviteOptions)', () => { // Given a set of options - const options = getMemberInviteOptions(OPTIONS.personalDetails, nvpDismissedProductTraining, []); + const options = getMemberInviteOptions(OPTIONS.personalDetails, nvpDismissedProductTraining, loginList, []); // When we call filterAndOrderOptions with a search value that does not match any personal details - const filteredOptions = filterAndOrderOptions(options, 'magneto', COUNTRY_CODE); + const filteredOptions = filterAndOrderOptions(options, 'magneto', COUNTRY_CODE, loginList); // Then no personal details should be returned expect(filteredOptions.personalDetails.length).toBe(0); @@ -1713,9 +1739,9 @@ describe('OptionsListUtils', () => { it('should return one personal detail if search value matches an email (getMemberInviteOptions)', () => { // Given a set of options - const options = getMemberInviteOptions(OPTIONS.personalDetails, nvpDismissedProductTraining, []); + const options = getMemberInviteOptions(OPTIONS.personalDetails, nvpDismissedProductTraining, loginList, []); // When we call filterAndOrderOptions with a search value that matches an email - const filteredOptions = filterAndOrderOptions(options, 'peterparker@expensify.com', COUNTRY_CODE); + const filteredOptions = filterAndOrderOptions(options, 'peterparker@expensify.com', COUNTRY_CODE, loginList); // Then one personal detail should be returned expect(filteredOptions.personalDetails.length).toBe(1); @@ -1733,7 +1759,7 @@ describe('OptionsListUtils', () => { return filtered; }, []); // When we call getValidOptions for share destination with the filteredReports - const options = getValidOptions({reports: filteredReports, personalDetails: OPTIONS.personalDetails}, {}, nvpDismissedProductTraining, { + const options = getValidOptions({reports: filteredReports, personalDetails: OPTIONS.personalDetails}, {}, nvpDismissedProductTraining, loginList, { betas: [], includeMultipleParticipantReports: true, showChatPreviewLine: true, @@ -1748,7 +1774,7 @@ describe('OptionsListUtils', () => { includeUserToInvite: false, }); // When we pass the returned options to filterAndOrderOptions with a search value that does not match the group chat name - const filteredOptions = filterAndOrderOptions(options, 'mutants', COUNTRY_CODE); + const filteredOptions = filterAndOrderOptions(options, 'mutants', COUNTRY_CODE, loginList); // Then no recent reports should be returned expect(filteredOptions.recentReports.length).toBe(0); @@ -1766,7 +1792,7 @@ describe('OptionsListUtils', () => { }, []); // When we call getValidOptions for share destination with the filteredReports - const options = getValidOptions({reports: filteredReportsWithWorkspaceRooms, personalDetails: OPTIONS.personalDetails}, {}, nvpDismissedProductTraining, { + const options = getValidOptions({reports: filteredReportsWithWorkspaceRooms, personalDetails: OPTIONS.personalDetails}, {}, nvpDismissedProductTraining, loginList, { betas: [], includeMultipleParticipantReports: true, showChatPreviewLine: true, @@ -1781,7 +1807,7 @@ describe('OptionsListUtils', () => { includeUserToInvite: false, }); // When we pass the returned options to filterAndOrderOptions with a search value that matches the group chat name - const filteredOptions = filterAndOrderOptions(options, 'Avengers Room', COUNTRY_CODE); + const filteredOptions = filterAndOrderOptions(options, 'Avengers Room', COUNTRY_CODE, loginList); // Then one recent report should be returned expect(filteredOptions.recentReports.length).toBe(1); @@ -1799,7 +1825,7 @@ describe('OptionsListUtils', () => { }, []); // When we call getValidOptions for share destination with the filteredReports - const options = getValidOptions({reports: filteredReportsWithWorkspaceRooms, personalDetails: OPTIONS.personalDetails}, {}, nvpDismissedProductTraining, { + const options = getValidOptions({reports: filteredReportsWithWorkspaceRooms, personalDetails: OPTIONS.personalDetails}, {}, nvpDismissedProductTraining, loginList, { betas: [], includeMultipleParticipantReports: true, showChatPreviewLine: true, @@ -1814,7 +1840,7 @@ describe('OptionsListUtils', () => { includeUserToInvite: false, }); // When we pass the returned options to filterAndOrderOptions with a search value that does not match the group chat name - const filteredOptions = filterAndOrderOptions(options, 'Mutants Lair', COUNTRY_CODE); + const filteredOptions = filterAndOrderOptions(options, 'Mutants Lair', COUNTRY_CODE, loginList); // Then no recent reports should be returned expect(filteredOptions.recentReports.length).toBe(0); @@ -1822,9 +1848,9 @@ describe('OptionsListUtils', () => { it('should show the option from personal details when searching for personal detail with no existing report', () => { // Given a set of options - const options = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}, nvpDismissedProductTraining); + const options = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}, nvpDismissedProductTraining, loginList); // When we call filterAndOrderOptions with a search value that matches a personal detail with no existing report - const filteredOptions = filterAndOrderOptions(options, 'hulk', COUNTRY_CODE); + const filteredOptions = filterAndOrderOptions(options, 'hulk', COUNTRY_CODE, loginList); // Then no recent reports should be returned expect(filteredOptions.recentReports.length).toBe(0); @@ -1836,9 +1862,9 @@ describe('OptionsListUtils', () => { 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', () => { // Given a set of options - const options = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}, nvpDismissedProductTraining); + const options = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}, nvpDismissedProductTraining, loginList); // When we call filterAndOrderOptions with a search value that does not match any personal details or reports - const filteredOptions = filterAndOrderOptions(options, 'marc@expensify', COUNTRY_CODE); + const filteredOptions = filterAndOrderOptions(options, 'marc@expensify', COUNTRY_CODE, loginList); // Then no recent reports or personal details should be returned expect(filteredOptions.recentReports.length).toBe(0); @@ -1849,9 +1875,9 @@ 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', () => { // Given a set of options - const options = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}, nvpDismissedProductTraining); + const options = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}, nvpDismissedProductTraining, loginList); // When we call filterAndOrderOptions with a search value that does not match any personal details or reports - const filteredOptions = filterAndOrderOptions(options, 'marc@expensify.com', COUNTRY_CODE); + const filteredOptions = filterAndOrderOptions(options, 'marc@expensify.com', COUNTRY_CODE, loginList); // Then no recent reports or personal details should be returned expect(filteredOptions.recentReports.length).toBe(0); @@ -1862,9 +1888,9 @@ describe('OptionsListUtils', () => { it('should return user to invite when search term has a period with options for it that do not contain the period', () => { // Given a set of options - const options = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}, nvpDismissedProductTraining); + const options = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}, nvpDismissedProductTraining, loginList); // When we call filterAndOrderOptions with a search value that does not match any personal details or reports but matches user to invite - const filteredOptions = filterAndOrderOptions(options, 'peter.parker@expensify.com', COUNTRY_CODE); + const filteredOptions = filterAndOrderOptions(options, 'peter.parker@expensify.com', COUNTRY_CODE, loginList); // Then no recent reports should be returned expect(filteredOptions.recentReports.length).toBe(0); @@ -1874,9 +1900,9 @@ describe('OptionsListUtils', () => { it('should return user which has displayName with accent mark when search value without accent mark', () => { // Given a set of options - const options = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}, nvpDismissedProductTraining); + const options = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}, nvpDismissedProductTraining, loginList); // When we call filterAndOrderOptions with a search value without accent mark - const filteredOptions = filterAndOrderOptions(options, 'Timothee', COUNTRY_CODE); + const filteredOptions = filterAndOrderOptions(options, 'Timothee', COUNTRY_CODE, loginList); // Then one personalDetails with accent mark should be returned expect(filteredOptions.personalDetails.length).toBe(1); @@ -1884,9 +1910,9 @@ 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', () => { // Given a set of options - const options = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}, nvpDismissedProductTraining); + const options = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}, nvpDismissedProductTraining, loginList); // When we call filterAndOrderOptions with a search value that does not match any personal details or reports but matches user to invite - const filteredOptions = filterAndOrderOptions(options, '5005550006', COUNTRY_CODE); + const filteredOptions = filterAndOrderOptions(options, '5005550006', COUNTRY_CODE, loginList); // Then no recent reports or personal details should be returned expect(filteredOptions.recentReports.length).toBe(0); @@ -1899,9 +1925,9 @@ 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', () => { // Given a set of options - const options = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}, nvpDismissedProductTraining); + const options = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}, nvpDismissedProductTraining, loginList); // When we call filterAndOrderOptions with a search value that does not match any personal details or reports but matches user to invite - const filteredOptions = filterAndOrderOptions(options, '+15005550006', COUNTRY_CODE); + const filteredOptions = filterAndOrderOptions(options, '+15005550006', COUNTRY_CODE, loginList); // Then no recent reports or personal details should be returned expect(filteredOptions.recentReports.length).toBe(0); @@ -1914,9 +1940,9 @@ 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', () => { // Given a set of options - const options = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}, nvpDismissedProductTraining); + const options = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}, nvpDismissedProductTraining, loginList); // When we call filterAndOrderOptions with a search value that does not match any personal details or reports but matches user to invite - const filteredOptions = filterAndOrderOptions(options, '+1 (800)324-3233', COUNTRY_CODE); + const filteredOptions = filterAndOrderOptions(options, '+1 (800)324-3233', COUNTRY_CODE, loginList); // Then no recent reports or personal details should be returned expect(filteredOptions.recentReports.length).toBe(0); @@ -1929,9 +1955,9 @@ describe('OptionsListUtils', () => { it('should not return any options or user to invite if contact number contains alphabet characters', () => { // Given a set of options - const options = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}, nvpDismissedProductTraining); + const options = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}, nvpDismissedProductTraining, loginList); // When we call filterAndOrderOptions with a search value that does not match any personal details or reports - const filteredOptions = filterAndOrderOptions(options, '998243aaaa', COUNTRY_CODE); + const filteredOptions = filterAndOrderOptions(options, '998243aaaa', COUNTRY_CODE, loginList); // Then no recent reports or personal details should be returned expect(filteredOptions.recentReports.length).toBe(0); @@ -1942,9 +1968,9 @@ describe('OptionsListUtils', () => { it('should not return any options if search value does not match any personal details', () => { // Given a set of options - const options = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}, nvpDismissedProductTraining); + const options = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}, nvpDismissedProductTraining, loginList); // When we call filterAndOrderOptions with a search value that does not match any personal details - const filteredOptions = filterAndOrderOptions(options, 'magneto', COUNTRY_CODE); + const filteredOptions = filterAndOrderOptions(options, 'magneto', COUNTRY_CODE, loginList); // Then no personal details should be returned expect(filteredOptions.personalDetails.length).toBe(0); @@ -1952,9 +1978,9 @@ describe('OptionsListUtils', () => { it('should return one recent report and no personal details if a search value provides an email', () => { // Given a set of options - const options = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}, nvpDismissedProductTraining); + const options = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}, nvpDismissedProductTraining, loginList); // When we call filterAndOrderOptions with a search value that matches an email - const filteredOptions = filterAndOrderOptions(options, 'peterparker@expensify.com', COUNTRY_CODE, {sortByReportTypeInSearch: true}); + const filteredOptions = filterAndOrderOptions(options, 'peterparker@expensify.com', COUNTRY_CODE, loginList, {sortByReportTypeInSearch: true}); // Then one recent report should be returned expect(filteredOptions.recentReports.length).toBe(1); @@ -1966,9 +1992,9 @@ describe('OptionsListUtils', () => { it('should return all matching reports and personal details', () => { // Given a set of options - const options = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}, nvpDismissedProductTraining); + const options = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}, nvpDismissedProductTraining, loginList); // When we call filterAndOrderOptions with a search value that matches both reports and personal details and maxRecentReportsToShow param - const filteredOptions = filterAndOrderOptions(options, '.com', COUNTRY_CODE, {maxRecentReportsToShow: 5}); + const filteredOptions = filterAndOrderOptions(options, '.com', COUNTRY_CODE, loginList, {maxRecentReportsToShow: 5}); // Then there should be 4 matching personal details expect(filteredOptions.personalDetails.length).toBe(5); @@ -1983,9 +2009,9 @@ describe('OptionsListUtils', () => { it('should return matching option when searching (getSearchOptions)', () => { // Given a set of options - const options = getSearchOptions({options: OPTIONS, draftComments: {}, nvpDismissedProductTraining}); + const options = getSearchOptions({options: OPTIONS, draftComments: {}, nvpDismissedProductTraining, loginList}); // When we call filterAndOrderOptions with a search value that matches a personal detail - const filteredOptions = filterAndOrderOptions(options, 'spider', COUNTRY_CODE); + const filteredOptions = filterAndOrderOptions(options, 'spider', COUNTRY_CODE, loginList); // Then one personal detail should be returned expect(filteredOptions.recentReports.length).toBe(1); @@ -1995,9 +2021,9 @@ describe('OptionsListUtils', () => { it('should return latest lastVisibleActionCreated item on top when search value matches multiple items (getSearchOptions)', () => { // Given a set of options - const options = getSearchOptions({options: OPTIONS, draftComments: {}, nvpDismissedProductTraining}); + const options = getSearchOptions({options: OPTIONS, draftComments: {}, nvpDismissedProductTraining, loginList}); // When we call filterAndOrderOptions with a search value that matches multiple items - const filteredOptions = filterAndOrderOptions(options, 'fantastic', COUNTRY_CODE); + const filteredOptions = filterAndOrderOptions(options, 'fantastic', COUNTRY_CODE, loginList); // Then only three reports should be returned expect(filteredOptions.recentReports.length).toBe(3); @@ -2012,9 +2038,9 @@ describe('OptionsListUtils', () => { // Given a set of options with periods const OPTIONS_WITH_PERIODS = createOptionList(PERSONAL_DETAILS_WITH_PERIODS, REPORTS); // When we call getSearchOptions - const results = getSearchOptions({options: OPTIONS_WITH_PERIODS, draftComments: {}, nvpDismissedProductTraining}); + const results = getSearchOptions({options: OPTIONS_WITH_PERIODS, draftComments: {}, nvpDismissedProductTraining, loginList}); // When we pass the returned options to filterAndOrderOptions with a search value - const filteredResults = filterAndOrderOptions(results, 'barry.allen@expensify.com', COUNTRY_CODE, {sortByReportTypeInSearch: true}); + const filteredResults = filterAndOrderOptions(results, 'barry.allen@expensify.com', COUNTRY_CODE, loginList, {sortByReportTypeInSearch: true}); // Then only one report should be returned expect(filteredResults.recentReports.length).toBe(1); @@ -2030,9 +2056,9 @@ describe('OptionsListUtils', () => { OPTIONS.personalDetails = OPTIONS.personalDetails.flatMap((obj) => [obj, {...obj}]); // Given a set of options - const options = getSearchOptions({options: OPTIONS, draftComments: {}, nvpDismissedProductTraining, betas: [CONST.BETAS.ALL]}); + const options = getSearchOptions({options: OPTIONS, draftComments: {}, nvpDismissedProductTraining, loginList, betas: [CONST.BETAS.ALL]}); // When we call filterAndOrderOptions with a an empty search value - const filteredOptions = filterAndOrderOptions(options, '', COUNTRY_CODE); + const filteredOptions = filterAndOrderOptions(options, '', COUNTRY_CODE, loginList); const matchingEntries = filteredOptions.personalDetails.filter((detail) => detail.login === login); // Then there should be 2 unique login entries @@ -2046,9 +2072,9 @@ describe('OptionsListUtils', () => { const OPTIONS_WITH_SELF_DM = createOptionList(PERSONAL_DETAILS, REPORTS_WITH_SELF_DM); // Given a set of options with self dm and all betas - const options = getSearchOptions({options: OPTIONS_WITH_SELF_DM, draftComments: {}, nvpDismissedProductTraining, betas: [CONST.BETAS.ALL]}); + const options = getSearchOptions({options: OPTIONS_WITH_SELF_DM, draftComments: {}, nvpDismissedProductTraining, loginList, betas: [CONST.BETAS.ALL]}); // When we call filterAndOrderOptions with a search value - const filteredOptions = filterAndOrderOptions(options, searchTerm, COUNTRY_CODE); + const filteredOptions = filterAndOrderOptions(options, searchTerm, COUNTRY_CODE, loginList); // Then the self dm should be on top. expect(filteredOptions.recentReports.at(0)?.isSelfDM).toBe(true);