From 0c994b21875ac9e771347fcfec43cf5cf068c50d Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Mon, 16 Mar 2026 14:53:15 +0530 Subject: [PATCH] Migrated UserSelectPopup.tsx from useOptionsList to usePersonalDetailOptions --- src/libs/OptionsListUtils/index.ts | 30 -- src/pages/RoomInvitePage.tsx | 237 +++++++-------- tests/perf-test/OptionsListUtils.perf-test.ts | 21 +- tests/unit/OptionsListUtilsTest.tsx | 274 ------------------ 4 files changed, 107 insertions(+), 455 deletions(-) diff --git a/src/libs/OptionsListUtils/index.ts b/src/libs/OptionsListUtils/index.ts index 2530ec6561898..41d1b95299ad5 100644 --- a/src/libs/OptionsListUtils/index.ts +++ b/src/libs/OptionsListUtils/index.ts @@ -2851,35 +2851,6 @@ function formatMemberForList(member: SearchOptionData): MemberForList { }; } -/** - * Build the options for the Workspace Member Invite view - * This method will be removed. See https://github.com/Expensify/App/issues/66615 for more information. - */ -function getMemberInviteOptions( - personalDetails: Array>, - nvpDismissedProductTraining: OnyxEntry, - loginList: OnyxEntry, - currentUserAccountID: number, - currentUserEmail: string, - personalDetailsCollection: OnyxEntry, - betas: Beta[] = [], - excludeLogins: Record = {}, - includeSelectedOptions = false, - countryCode: number = CONST.DEFAULT_COUNTRY_CODE, -): Options { - return getValidOptions({personalDetails, reports: []}, undefined, undefined, nvpDismissedProductTraining, loginList, currentUserAccountID, currentUserEmail, { - betas, - includeP2P: true, - excludeLogins, - includeSelectedOptions, - includeRecentReports: false, - searchString: '', - maxElements: undefined, - personalDetails: personalDetailsCollection, - countryCode, - }); -} - /** * Helper method that returns the text to be used for the header's message and title (if any) */ @@ -3423,7 +3394,6 @@ export { getLastActorDisplayNameFromLastVisibleActions, getLastMessageTextForReport, getManagerMcTestParticipant, - getMemberInviteOptions, getParticipantsOption, getPersonalDetailSearchTerms, getPersonalDetailsForAccountIDs, diff --git a/src/pages/RoomInvitePage.tsx b/src/pages/RoomInvitePage.tsx index 0c76ae5c5cdb5..1b1e0f5541caf 100644 --- a/src/pages/RoomInvitePage.tsx +++ b/src/pages/RoomInvitePage.tsx @@ -1,12 +1,10 @@ -import {Str} from 'expensify-common'; +import {pendingChatMembersSelector} from '@selectors/ReportMetaData'; import React, {useEffect, useState} from 'react'; import type {SectionListData} from 'react-native'; import {View} from 'react-native'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import FormAlertWithSubmitButton from '@components/FormAlertWithSubmitButton'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; -import {usePersonalDetails} from '@components/OnyxListItemProvider'; -import {useOptionsList} from '@components/OptionListContextProvider'; import ScreenWrapper from '@components/ScreenWrapper'; import InviteMemberListItem from '@components/SelectionList/ListItem/InviteMemberListItem'; import SelectionListWithSections from '@components/SelectionList/SelectionListWithSections'; @@ -18,6 +16,7 @@ import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails' import useDebouncedState from '@hooks/useDebouncedState'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; +import usePersonalDetailOptions from '@hooks/usePersonalDetailOptions'; import useReportAttributes from '@hooks/useReportAttributes'; import useReportIsArchived from '@hooks/useReportIsArchived'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -27,20 +26,18 @@ import {READ_COMMANDS} from '@libs/API/types'; import {canUseTouchScreen} from '@libs/DeviceCapabilities'; import HttpUtils from '@libs/HttpUtils'; import {appendCountryCode} from '@libs/LoginUtils'; +import memoize from '@libs/memoize'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import type {RoomMembersNavigatorParamList} from '@libs/Navigation/types'; -import type {MemberForList} from '@libs/OptionsListUtils'; -import {filterAndOrderOptions, formatMemberForList, getHeaderMessage, getMemberInviteOptions} from '@libs/OptionsListUtils'; -import Parser from '@libs/Parser'; +import type {OptionData} from '@libs/PersonalDetailOptionsListUtils'; +import {getHeaderMessage, getValidOptions} from '@libs/PersonalDetailOptionsListUtils'; import {getLoginsByAccountIDs} from '@libs/PersonalDetailsUtils'; import {addSMSDomainIfPhoneNumber, parsePhoneNumber} from '@libs/PhoneNumber'; import type {MemberEmailsToAccountIDs} from '@libs/PolicyUtils'; import {isPolicyEmployee as isPolicyEmployeeUtil} from '@libs/PolicyUtils'; -import {getReportAction} from '@libs/ReportActionsUtils'; import {getReportName} from '@libs/ReportNameUtils'; -import type {OptionData} from '@libs/ReportUtils'; -import {isHiddenForCurrentUser, isPolicyExpenseChat} from '@libs/ReportUtils'; +import {getParticipantsAccountIDsForDisplay, isPolicyExpenseChat} from '@libs/ReportUtils'; import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -50,13 +47,22 @@ import {isEmptyObject} from '@src/types/utils/EmptyObject'; import type {WithReportOrNotFoundProps} from './inbox/report/withReportOrNotFound'; import withReportOrNotFound from './inbox/report/withReportOrNotFound'; +const defaultListOptions = { + userToInvite: null, + recentOptions: [], + personalDetails: [], + selectedOptions: [], +}; + +const memoizedGetValidOptions = memoize(getValidOptions, {maxSize: 5, monitoringName: 'RoomInvitePage.getValidOptions'}); + type RoomInvitePageProps = WithReportOrNotFoundProps & WithNavigationTransitionEndProps & PlatformStackScreenProps; -type Sections = Array>>; +type MembersSection = SectionListData>; function RoomInvitePage({ - betas, report, policy, + didScreenTransitionEnd, route: { params: {backTo}, }, @@ -64,162 +70,132 @@ function RoomInvitePage({ const styles = useThemeStyles(); const reportAttributes = useReportAttributes(); const {translate, formatPhoneNumber} = useLocalize(); + const {options} = usePersonalDetailOptions({enabled: didScreenTransitionEnd}); + const areOptionsInitialized = (options?.length ?? 0) > 0; const [userSearchPhrase] = useOnyx(ONYXKEYS.ROOM_MEMBERS_USER_SEARCH_PHRASE); const [countryCode = CONST.DEFAULT_COUNTRY_CODE] = useOnyx(ONYXKEYS.COUNTRY_CODE); + const [reportMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}${report?.reportID}`, {selector: pendingChatMembersSelector}); const [loginList] = useOnyx(ONYXKEYS.LOGIN_LIST); const currentUserPersonalDetails = useCurrentUserPersonalDetails(); - const currentUserAccountID = currentUserPersonalDetails.accountID; const currentUserEmail = currentUserPersonalDetails.email ?? ''; const [searchTerm, debouncedSearchTerm, setSearchTerm] = useDebouncedState(userSearchPhrase ?? ''); - const [selectedOptions, setSelectedOptions] = useState([]); + const [selectedLogins, setSelectedLogins] = useState>(new Set()); + const [extraOptions, setExtraOptions] = useState([]); const [isSearchingForReports] = useOnyx(ONYXKEYS.IS_SEARCHING_FOR_REPORTS, {initWithStoredValues: false}); const isReportArchived = useReportIsArchived(report.reportID); - const [nvpDismissedProductTraining] = useOnyx(ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING); - const {options, areOptionsInitialized} = useOptionsList(); - const allPersonalDetails = usePersonalDetails(); + const loginToAccountIDMap = (() => { + const map: Record = {}; + for (const option of extraOptions) { + const login = option.login; + if (login) { + map[login] = option.accountID; + } + } + for (const option of options ?? []) { + const login = option.login; + if (login) { + map[login] = option.accountID; + } + } + return map; + })(); + + const transformedOptions = + options?.map((option) => ({ + ...option, + isSelected: selectedLogins.has(option.login ?? ''), + })) ?? []; // Any existing participants and Expensify emails should not be eligible for invitation const excludedUsers: Record = { ...CONST.EXPENSIFY_EMAILS_OBJECT, }; - const visibleParticipantAccountIDs = Object.entries(report.participants ?? {}) - .filter(([, participant]) => participant && !isHiddenForCurrentUser(participant.notificationPreference)) - .map(([accountID]) => Number(accountID)); - for (const participant of getLoginsByAccountIDs(visibleParticipantAccountIDs)) { - const smsDomain = addSMSDomainIfPhoneNumber(participant); + const participantsAccountIDs = getParticipantsAccountIDsForDisplay(report, false, true, undefined, reportMetadata); + const loginsByAccountIDs = getLoginsByAccountIDs(participantsAccountIDs); + for (const login of loginsByAccountIDs) { + const smsDomain = addSMSDomainIfPhoneNumber(login); excludedUsers[smsDomain] = true; } - const getDefaultOptions = () => { - if (!areOptionsInitialized) { - return {recentReports: [], personalDetails: [], userToInvite: null, currentUserOption: null}; - } - - const inviteOptions = getMemberInviteOptions( - options.personalDetails, - nvpDismissedProductTraining, - loginList, - currentUserAccountID, - currentUserEmail, - allPersonalDetails, - betas ?? [], - excludedUsers, - false, - countryCode, - ); - // Update selectedOptions with the latest personalDetails information - const detailsMap: Record = {}; - for (const detail of inviteOptions.personalDetails) { - if (!detail.login) { - continue; - } - detailsMap[detail.login] = formatMemberForList(detail); - } - const newSelectedOptions: OptionData[] = []; - for (const option of selectedOptions) { - newSelectedOptions.push(option.login && option.login in detailsMap ? {...detailsMap[option.login], isSelected: true} : option); - } - - return { - userToInvite: inviteOptions.userToInvite, - personalDetails: inviteOptions.personalDetails, - selectedOptions: newSelectedOptions, - recentReports: [], - currentUserOption: null, - }; - }; - const defaultOptions = getDefaultOptions(); - - const inviteOptions = - debouncedSearchTerm.trim() === '' - ? defaultOptions - : filterAndOrderOptions(defaultOptions, debouncedSearchTerm, countryCode, loginList, currentUserEmail, currentUserAccountID, allPersonalDetails, { - excludeLogins: excludedUsers, - }); + const optionsList = !areOptionsInitialized + ? defaultListOptions + : memoizedGetValidOptions(transformedOptions, currentUserEmail, formatPhoneNumber, countryCode, loginList, { + excludeLogins: excludedUsers, + extraOptions, + includeRecentReports: false, + searchString: debouncedSearchTerm, + includeCurrentUser: false, + includeUserToInvite: true, + }); - const {personalDetails, userToInvite} = inviteOptions; - const sections: Sections = []; + const sections: MembersSection[] = []; if (areOptionsInitialized) { - // Filter all options that is a part of the search term or in the personal details - let filterSelectedOptions = selectedOptions; - if (debouncedSearchTerm !== '') { - filterSelectedOptions = selectedOptions.filter((option) => { - const accountID = option?.accountID; - const isOptionInPersonalDetails = personalDetails ? personalDetails.some((personalDetail) => accountID && personalDetail?.accountID === accountID) : false; - const parsedPhoneNumber = parsePhoneNumber(appendCountryCode(Str.removeSMSDomain(debouncedSearchTerm), countryCode)); - const searchValue = parsedPhoneNumber.possible && parsedPhoneNumber.number ? parsedPhoneNumber.number.e164 : debouncedSearchTerm.toLowerCase(); - const isPartOfSearchTerm = (option.text?.toLowerCase() ?? '').includes(searchValue) || (option.login?.toLowerCase() ?? '').includes(searchValue); - return isPartOfSearchTerm || isOptionInPersonalDetails; - }); - } - const filterSelectedOptionsFormatted = filterSelectedOptions.map((selectedOption) => formatMemberForList(selectedOption)); - - sections.push({ - title: undefined, - data: filterSelectedOptionsFormatted, - sectionIndex: 0, - }); - - // Filtering out selected users from the search results - const selectedLogins = new Set(selectedOptions.map(({login}) => login)); - const personalDetailsWithoutSelected = personalDetails ? personalDetails.filter(({login}) => !selectedLogins.has(login)) : []; - const personalDetailsFormatted = personalDetailsWithoutSelected.map((personalDetail) => formatMemberForList(personalDetail)); - const hasUnselectedUserToInvite = userToInvite && !selectedLogins.has(userToInvite.login); - - sections.push({ - title: translate('common.contacts'), - data: personalDetailsFormatted, - sectionIndex: 1, - }); - - if (hasUnselectedUserToInvite) { + if (optionsList.userToInvite) { sections.push({ title: undefined, - data: [formatMemberForList(userToInvite)], - sectionIndex: 2, + data: [optionsList.userToInvite], + sectionIndex: 0, }); + } else { + if (optionsList.selectedOptions.length > 0) { + sections.push({ + title: undefined, + data: optionsList.selectedOptions, + sectionIndex: 0, + }); + } + if (optionsList.personalDetails.length > 0) { + sections.push({ + title: translate('common.contacts'), + data: optionsList.personalDetails, + sectionIndex: optionsList.selectedOptions.length > 0 ? 1 : 0, + }); + } } } - const toggleOption = (option: MemberForList) => { - const isOptionInList = selectedOptions.some((selectedOption) => selectedOption.login === option.login); + const existingLogins = new Set(options?.map((option) => option.login ?? '')); - let newSelectedOptions: OptionData[]; - if (isOptionInList) { - newSelectedOptions = selectedOptions.filter((selectedOption) => selectedOption.login !== option.login); + const toggleOption = (option: OptionData) => { + const isSelected = selectedLogins.has(option.login ?? ''); + + if (isSelected) { + // If the option is selected, remove it from the selected logins + const isInExtraOption = extraOptions.some((extraOption) => extraOption.login === option.login); + if (isInExtraOption) { + setExtraOptions((prev) => prev.filter((extraOption) => extraOption.login !== option.login)); + } + setSelectedLogins((prev) => new Set([...prev].filter((login) => login !== option.login))); } else { - newSelectedOptions = [...selectedOptions, {...option, isSelected: true}]; + setSelectedLogins((prev) => new Set([...prev, option.login ?? ''])); + if (!existingLogins.has(option.login ?? '')) { + setExtraOptions((prev) => [...prev, {...option, isSelected: true}]); + } } - - setSelectedOptions(newSelectedOptions); }; // Non policy members should not be able to view the participants of a room const reportID = report?.reportID; const isPolicyEmployee = isPolicyEmployeeUtil(report?.policyID, policy); - const reportAction = getReportAction(report?.parentReportID, report?.parentReportActionID); - const shouldParserToHTML = reportAction?.actionName !== CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT; const backRoute = reportID && (!isPolicyEmployee || isReportArchived ? ROUTES.REPORT_WITH_ID_DETAILS.getRoute(reportID, backTo) : ROUTES.ROOM_MEMBERS.getRoute(reportID, backTo)); const reportName = getReportName(report, reportAttributes); const ancestors = useAncestors(report); + const validSelectedLogins = Array.from(selectedLogins).filter((login) => !excludedUsers[login]); + const inviteUsers = () => { HttpUtils.cancelPendingRequests(READ_COMMANDS.SEARCH_FOR_USERS); - if (selectedOptions.length === 0) { + if (validSelectedLogins.length === 0) { return; } const invitedEmailsToAccountIDs: MemberEmailsToAccountIDs = {}; - for (const option of selectedOptions) { - const login = option.login ?? ''; - const accountID = option.accountID; - if (!login.toLowerCase().trim() || !accountID) { - continue; - } - invitedEmailsToAccountIDs[login] = Number(accountID); + for (const login of validSelectedLogins) { + const accountID = loginToAccountIDMap[login] ?? CONST.DEFAULT_NUMBER_ID; + invitedEmailsToAccountIDs[login] = accountID; } if (report?.reportID) { if (isPolicyExpenseChat(report)) { @@ -237,18 +213,17 @@ function RoomInvitePage({ }; const getHeaderMessageText = () => { + if (sections.length > 0) { + return ''; + } const searchValue = debouncedSearchTerm.trim().toLowerCase(); - const expensifyEmails = CONST.EXPENSIFY_EMAILS; - if (!inviteOptions.userToInvite && expensifyEmails.includes(searchValue)) { + if (CONST.EXPENSIFY_EMAILS_OBJECT[searchValue]) { return translate('messages.errorMessageInvalidEmail'); } - if ( - !inviteOptions.userToInvite && - excludedUsers[parsePhoneNumber(appendCountryCode(searchValue, countryCode)).possible ? addSMSDomainIfPhoneNumber(appendCountryCode(searchValue, countryCode)) : searchValue] - ) { + if (excludedUsers[parsePhoneNumber(appendCountryCode(searchValue, countryCode)).possible ? addSMSDomainIfPhoneNumber(appendCountryCode(searchValue, countryCode)) : searchValue]) { return translate('messages.userIsAlreadyMember', {login: searchValue, name: reportName}); } - return getHeaderMessage((inviteOptions.personalDetails ?? []).length !== 0, !!inviteOptions.userToInvite, debouncedSearchTerm, countryCode); + return getHeaderMessage(translate, debouncedSearchTerm, countryCode); }; useEffect(() => { @@ -281,7 +256,7 @@ function RoomInvitePage({ > Navigation.goBack(backRoute)} /> { ); }); - /* Testing getMemberInviteOptions */ - test('[OptionsListUtils] getMemberInviteOptions', async () => { - await waitForBatchedUpdates(); - await measureFunction(() => - getMemberInviteOptions( - options.personalDetails, - nvpDismissedProductTraining, - loginList, - MOCK_CURRENT_USER_ACCOUNT_ID, - MOCK_CURRENT_USER_EMAIL, - personalDetails, - mockedBetas, - {}, - false, - COUNTRY_CODE, - ), - ); - }); - test('[OptionsListUtils] worst case scenario with a search term that matches a subset of selectedOptions, filteredRecentReports, and filteredPersonalDetails', async () => { const SELECTED_OPTION_TEXT = 'Selected Option'; const RECENT_REPORT_TEXT = 'Recent Report'; diff --git a/tests/unit/OptionsListUtilsTest.tsx b/tests/unit/OptionsListUtilsTest.tsx index c475e57a27784..501ce21b38f26 100644 --- a/tests/unit/OptionsListUtilsTest.tsx +++ b/tests/unit/OptionsListUtilsTest.tsx @@ -30,7 +30,6 @@ import { getLastActorDisplayName, getLastActorDisplayNameFromLastVisibleActions, getLastMessageTextForReport, - getMemberInviteOptions, getPersonalDetailSearchTerms, getPolicyExpenseReportOption, getReportDisplayOption, @@ -1921,214 +1920,6 @@ describe('OptionsListUtils', () => { }); }); - describe('getMemberInviteOptions()', () => { - it('should sort personal details alphabetically and return expected structure', () => { - // Given a set of personalDetails - // When we call getMemberInviteOptions - const results = getMemberInviteOptions( - OPTIONS.personalDetails, - nvpDismissedProductTraining, - loginList, - CURRENT_USER_ACCOUNT_ID, - CURRENT_USER_EMAIL, - PERSONAL_DETAILS, - [], - {}, - false, - COUNTRY_CODE, - ); - - // Then personal details should be 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'); - - // Then the results should contain expected structure - expect(results.personalDetails.length).toBeGreaterThan(0); - expect(results.recentReports).toEqual([]); - expect(results.currentUserOption).toBeUndefined(); - }); - - it('should exclude logins when excludeLogins is provided', () => { - // Given a set of personalDetails and excludeLogins - const excludeLogins = {'reedrichards@expensify.com': true}; - - // When we call getMemberInviteOptions with excludeLogins - const results = getMemberInviteOptions( - OPTIONS.personalDetails, - nvpDismissedProductTraining, - loginList, - CURRENT_USER_ACCOUNT_ID, - CURRENT_USER_EMAIL, - PERSONAL_DETAILS, - [], - excludeLogins, - false, - COUNTRY_CODE, - ); - - // Then the excluded login should not be in the results - const excludedUser = results.personalDetails.find((detail) => detail.login === 'reedrichards@expensify.com'); - expect(excludedUser).toBeUndefined(); - }); - - it('should handle undefined personalDetailsCollection gracefully', () => { - // Given a set of personalDetails and undefined personalDetailsCollection - // When we call getMemberInviteOptions with undefined personalDetailsCollection - const results = getMemberInviteOptions( - OPTIONS.personalDetails, - nvpDismissedProductTraining, - loginList, - CURRENT_USER_ACCOUNT_ID, - CURRENT_USER_EMAIL, - undefined, - [], - {}, - false, - COUNTRY_CODE, - ); - - // Then personal details should still be returned and sorted alphabetically - expect(results.personalDetails.length).toBeGreaterThan(0); - expect(results.personalDetails.at(0)?.text).toBe('Black Panther'); - }); - - it('should handle empty personalDetailsCollection gracefully', () => { - // Given a set of personalDetails and empty personalDetailsCollection - // When we call getMemberInviteOptions with empty personalDetailsCollection - const results = getMemberInviteOptions( - OPTIONS.personalDetails, - nvpDismissedProductTraining, - loginList, - CURRENT_USER_ACCOUNT_ID, - CURRENT_USER_EMAIL, - {}, - [], - {}, - false, - COUNTRY_CODE, - ); - - // Then personal details should still be returned and sorted alphabetically - expect(results.personalDetails.length).toBeGreaterThan(0); - expect(results.personalDetails.at(0)?.text).toBe('Black Panther'); - }); - - it('should handle null personalDetailsCollection gracefully', () => { - // Given a set of personalDetails and null personalDetailsCollection - // When we call getMemberInviteOptions with null personalDetailsCollection - const results = getMemberInviteOptions( - OPTIONS.personalDetails, - nvpDismissedProductTraining, - loginList, - CURRENT_USER_ACCOUNT_ID, - CURRENT_USER_EMAIL, - {}, - [], - {}, - false, - COUNTRY_CODE, - ); - - // Then personal details should still be returned and sorted alphabetically - expect(results.personalDetails.length).toBeGreaterThan(0); - expect(results.personalDetails.at(0)?.text).toBe('Black Panther'); - }); - - it('should use personalDetailsCollection when provided with partial data', () => { - // Given a subset of personalDetailsCollection with only some users - const partialPersonalDetails: PersonalDetailsList = { - '4': { - accountID: 4, - displayName: 'Black Panther', - login: 'tchalla@expensify.com', - keyForList: 'tchalla@expensify.com', - reportID: '1', - }, - '9': { - accountID: 9, - displayName: 'Black Widow', - login: 'natasharomanoff@expensify.com', - keyForList: 'natasharomanoff@expensify.com', - reportID: '', - }, - }; - - // When we call getMemberInviteOptions with a partial personalDetailsCollection - const results = getMemberInviteOptions( - OPTIONS.personalDetails, - nvpDismissedProductTraining, - loginList, - CURRENT_USER_ACCOUNT_ID, - CURRENT_USER_EMAIL, - partialPersonalDetails, - [], - {}, - false, - COUNTRY_CODE, - ); - - // Then results should still include all personal details from OPTIONS.personalDetails - // (personalDetailsCollection is used for lookup purposes, not filtering) - expect(results.personalDetails.length).toBeGreaterThan(0); - expect(results.personalDetails.at(0)?.text).toBe('Black Panther'); - }); - - it('should handle personalDetailsCollection with different display names', () => { - // Given a personalDetailsCollection with modified display names - const modifiedPersonalDetails: PersonalDetailsList = { - ...PERSONAL_DETAILS, - '4': { - ...PERSONAL_DETAILS['4'], - displayName: 'Black Panther Updated', // Modified display name - }, - }; - - // When we call getMemberInviteOptions with modified personalDetailsCollection - const results = getMemberInviteOptions( - OPTIONS.personalDetails, - nvpDismissedProductTraining, - loginList, - CURRENT_USER_ACCOUNT_ID, - CURRENT_USER_EMAIL, - modifiedPersonalDetails, - [], - {}, - false, - COUNTRY_CODE, - ); - - // Then personal details should still be returned - expect(results.personalDetails.length).toBeGreaterThan(0); - // The personalDetails in results come from OPTIONS.personalDetails, not personalDetailsCollection - expect(results.personalDetails.at(0)?.text).toBe('Black Panther'); - }); - - it('should exclude specified logins', () => { - // Given a set of personalDetails and logins to exclude - const excludeLogins = {'tchalla@expensify.com': true}; - - // When we call getMemberInviteOptions with excludeLogins - const results = getMemberInviteOptions( - OPTIONS.personalDetails, - nvpDismissedProductTraining, - loginList, - CURRENT_USER_ACCOUNT_ID, - CURRENT_USER_EMAIL, - PERSONAL_DETAILS, - [], - excludeLogins, - false, - COUNTRY_CODE, - ); - - // Then Black Panther should not be in the results - const blackPanther = results.personalDetails.find((detail) => detail.text === 'Black Panther'); - expect(blackPanther).toBeUndefined(); - }); - }); - describe('getLastActorDisplayName()', () => { it('should return correct display name', () => { renderLocaleContextProvider(); @@ -2670,50 +2461,6 @@ describe('OptionsListUtils', () => { expect(filteredOptions.userToInvite).toBe(null); }); - 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, - loginList, - CURRENT_USER_ACCOUNT_ID, - CURRENT_USER_EMAIL, - PERSONAL_DETAILS, - [], - {}, - false, - COUNTRY_CODE, - ); - // When we call filterAndOrderOptions with a search value that does not match any personal details - const filteredOptions = filterAndOrderOptions(options, 'magneto', COUNTRY_CODE, loginList, CURRENT_USER_EMAIL, CURRENT_USER_ACCOUNT_ID, PERSONAL_DETAILS); - - // Then no personal details should be returned - expect(filteredOptions.personalDetails.length).toBe(0); - }); - - it('should return one personal detail if search value matches an email (getMemberInviteOptions)', () => { - // Given a set of options - const options = getMemberInviteOptions( - OPTIONS.personalDetails, - nvpDismissedProductTraining, - loginList, - CURRENT_USER_ACCOUNT_ID, - CURRENT_USER_EMAIL, - PERSONAL_DETAILS, - [], - {}, - false, - COUNTRY_CODE, - ); - // When we call filterAndOrderOptions with a search value that matches an email - const filteredOptions = filterAndOrderOptions(options, 'peterparker@expensify.com', COUNTRY_CODE, loginList, CURRENT_USER_EMAIL, CURRENT_USER_ACCOUNT_ID, PERSONAL_DETAILS); - - // Then one personal detail should be returned - expect(filteredOptions.personalDetails.length).toBe(1); - // Then the returned personal detail should match the search text - expect(filteredOptions.personalDetails.at(0)?.text).toBe('Spider-Man'); - }); - it('should not show any recent reports if a search value does not match the group chat name (getShareDestinationsOptions)', () => { // Given a set of filtered current Reports (as we do in the component) before getting share destination options const filteredReports = Object.values(OPTIONS.reports).reduce((filtered, option) => { @@ -6707,27 +6454,6 @@ describe('OptionsListUtils', () => { expect(options.personalDetails).toBeDefined(); }); - it('getMemberInviteOptions should use reports parameter correctly', () => { - // When we call getMemberInviteOptions with the reports parameter - const results = getMemberInviteOptions( - OPTIONS.personalDetails, - nvpDismissedProductTraining, - loginList, - CURRENT_USER_ACCOUNT_ID, - CURRENT_USER_EMAIL, - PERSONAL_DETAILS, - [], - {}, - false, - COUNTRY_CODE, - ); - - // Then the function should complete without errors and return valid results - expect(results).toBeDefined(); - expect(results.personalDetails).toBeDefined(); - expect(results.recentReports).toEqual([]); - }); - it('getUserToInviteOption should use reports parameter correctly', () => { // Given a valid email search value and reports collection const result = getUserToInviteOption({