From 6e70549a7045a490aed21384967c5f5b52c4d557 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Thu, 23 Oct 2025 00:59:54 +0800 Subject: [PATCH 1/3] add some unit tests for getLastMessageTextForReport --- tests/unit/OptionsListUtilsTest.tsx | 204 +++++++++++++++++++++++++++- 1 file changed, 203 insertions(+), 1 deletion(-) diff --git a/tests/unit/OptionsListUtilsTest.tsx b/tests/unit/OptionsListUtilsTest.tsx index 33148be9323c..8a26e299d469 100644 --- a/tests/unit/OptionsListUtilsTest.tsx +++ b/tests/unit/OptionsListUtilsTest.tsx @@ -8,6 +8,8 @@ import {LocaleContextProvider} from '@components/LocaleContextProvider'; import OnyxListItemProvider from '@components/OnyxListItemProvider'; import useReportIsArchived from '@hooks/useReportIsArchived'; import DateUtils from '@libs/DateUtils'; +import {translateLocal} from '@libs/Localize'; +import isSearchTopmostFullScreenRoute from '@libs/Navigation/helpers/isSearchTopmostFullScreenRoute'; import type {OptionList, Options, SearchOption} from '@libs/OptionsListUtils'; import { canCreateOptimisticPersonalDetailOption, @@ -19,6 +21,7 @@ import { filterWorkspaceChats, formatMemberForList, getLastActorDisplayName, + getLastMessageTextForReport, getMemberInviteOptions, getSearchOptions, getSearchValueForPhoneOrEmail, @@ -30,13 +33,27 @@ import { recentReportComparator, sortAlphabetically, } from '@libs/OptionsListUtils'; -import {canCreateTaskInReport, canUserPerformWriteAction, isCanceledTaskReport, isExpensifyOnlyParticipantInReport} from '@libs/ReportUtils'; +import Parser from '@libs/Parser'; +import {getChangedApproverActionMessage} from '@libs/ReportActionsUtils'; +import { + canCreateTaskInReport, + canUserPerformWriteAction, + formatReportLastMessageText, + getMovedActionMessage, + getMovedTransactionMessage, + getReportPreviewMessage, + isCanceledTaskReport, + isExpensifyOnlyParticipantInReport, +} from '@libs/ReportUtils'; import type {OptionData} from '@libs/ReportUtils'; import initOnyxDerivedValues from '@userActions/OnyxDerived'; import CONST from '@src/CONST'; import IntlStore from '@src/languages/IntlStore'; import ONYXKEYS from '@src/ONYXKEYS'; import type {PersonalDetails, Policy, Report, ReportAction, Transaction} from '@src/types/onyx'; +import createRandomReportAction from '../utils/collections/reportActions'; +import {createRandomReport} from '../utils/collections/reports'; +import createRandomTransaction from '../utils/collections/transaction'; import {getFakeAdvancedReportAction} from '../utils/LHNTestUtils'; import {localeCompare} from '../utils/TestHelper'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; @@ -64,6 +81,8 @@ jest.mock('@src/libs/Navigation/Navigation', () => ({ getReportRHPActiveRoute: jest.fn(), })); +jest.mock('@libs/Navigation/helpers/isSearchTopmostFullScreenRoute', () => jest.fn()); + type PersonalDetailsList = Record; const renderLocaleContextProvider = () => { @@ -2412,4 +2431,187 @@ describe('OptionsListUtils', () => { expect(result.alternateText).toBe('Iron Man owes ₫34'); }); }); + + describe('getLastMessageTextForReport', () => { + describe('REPORT_PREVIEW action', () => { + it('should show report preview message for non-policy expense chat', async () => { + const report: Report = { + ...createRandomReport(0), + chatType: undefined, + isOwnPolicyExpenseChat: false, + }; + const iouReport: Report = { + ...createRandomReport(1), + chatType: undefined, + isOwnPolicyExpenseChat: false, + type: CONST.REPORT.TYPE.IOU, + isWaitingOnBankAccount: false, + }; + const reportPreviewAction: ReportAction = { + ...createRandomReportAction(1), + actionName: CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW, + childMoneyRequestCount: 1, + message: [{type: 'COMMENT', text: ''}], + originalMessage: { + linkedReportID: iouReport.reportID, + }, + shouldShow: true, + }; + const transaction: Transaction = { + ...createRandomTransaction(0), + amount: 100, + currency: CONST.CURRENCY.USD, + merchant: '', + modifiedMerchant: '', + comment: { + comment: 'A
A
A', + }, + }; + const iouAction: ReportAction = { + ...createRandomReportAction(2), + actionName: CONST.REPORT.ACTIONS.TYPE.IOU, + message: [{type: 'COMMENT', text: ''}], + originalMessage: { + IOUTransactionID: transaction.transactionID, + type: 'create', + }, + shouldShow: true, + }; + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`, iouReport); + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`, { + [reportPreviewAction.reportActionID]: reportPreviewAction, + }); + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`, { + [iouAction.reportActionID]: iouAction, + }); + await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`, transaction); + const lastMessage = getLastMessageTextForReport({report, lastActorDetails: null, isReportArchived: false}); + const reportPreviewMessage = getReportPreviewMessage(iouReport, iouAction, true, false, null, true, reportPreviewAction); + const expected = formatReportLastMessageText(Parser.htmlToText(reportPreviewMessage)); + expect(lastMessage).toBe(expected); + }); + }); + it('MOVED_TRANSACTION action', async () => { + const mockIsSearchTopmostFullScreenRoute = jest.mocked(isSearchTopmostFullScreenRoute); + mockIsSearchTopmostFullScreenRoute.mockReturnValue(false); + const report: Report = createRandomReport(0); + const report2: Report = { + ...createRandomReport(1), + reportName: 'Expense Report #123', + }; + const movedTransactionAction: ReportAction = { + ...createRandomReportAction(1), + actionName: CONST.REPORT.ACTIONS.TYPE.MOVED_TRANSACTION, + message: [{type: 'COMMENT', text: ''}], + originalMessage: { + toReportID: report2.reportID, + }, + }; + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${report2.reportID}`, report2); + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`, { + [movedTransactionAction.reportActionID]: movedTransactionAction, + }); + const lastMessage = getLastMessageTextForReport({report, lastActorDetails: null, isReportArchived: false}); + expect(lastMessage).toBe(Parser.htmlToText(getMovedTransactionMessage(report2))); + }); + describe('SUBMITTED action', () => { + it('should return automatic submitted message if submitted via harvesting', async () => { + const report: Report = createRandomReport(0); + const submittedAction: ReportAction = { + ...createRandomReportAction(1), + actionName: CONST.REPORT.ACTIONS.TYPE.SUBMITTED, + message: [{type: 'COMMENT', text: ''}], + originalMessage: { + amount: 1, + harvesting: true, + }, + }; + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`, { + [submittedAction.reportActionID]: submittedAction, + }); + const lastMessage = getLastMessageTextForReport({report, lastActorDetails: null, isReportArchived: false}); + expect(lastMessage).toBe(Parser.htmlToText(translateLocal('iou.automaticallySubmitted'))); + }); + }); + describe('APPROVED action', () => { + it('should return automatic approved message if approved automatically', async () => { + const report: Report = createRandomReport(0); + const approvedAction: ReportAction = { + ...createRandomReportAction(1), + actionName: CONST.REPORT.ACTIONS.TYPE.APPROVED, + message: [{type: 'COMMENT', text: ''}], + originalMessage: { + type: CONST.IOU.REPORT_ACTION_TYPE.APPROVE, + automaticAction: true, + }, + }; + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`, { + [approvedAction.reportActionID]: approvedAction, + }); + const lastMessage = getLastMessageTextForReport({report, lastActorDetails: null, isReportArchived: false}); + expect(lastMessage).toBe(Parser.htmlToText(translateLocal('iou.automaticallyApproved'))); + }); + }); + describe('FORWARDED action', () => { + it('should return automatic forwarded message if forwarded automatically', async () => { + const report: Report = createRandomReport(0); + const forwardedAction: ReportAction = { + ...createRandomReportAction(1), + actionName: CONST.REPORT.ACTIONS.TYPE.FORWARDED, + message: [{type: 'COMMENT', text: ''}], + originalMessage: { + type: CONST.IOU.REPORT_ACTION_TYPE.CREATE, + automaticAction: true, + }, + }; + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`, { + [forwardedAction.reportActionID]: forwardedAction, + }); + const lastMessage = getLastMessageTextForReport({report, lastActorDetails: null, isReportArchived: false}); + expect(lastMessage).toBe(Parser.htmlToText(translateLocal('iou.automaticallyForwarded'))); + }); + }); + it('TAKE_CONTROL action', async () => { + const report: Report = createRandomReport(0); + const takeControlAction: ReportAction = { + ...createRandomReportAction(1), + actionName: CONST.REPORT.ACTIONS.TYPE.TAKE_CONTROL, + message: [{type: 'COMMENT', text: ''}], + originalMessage: {}, + }; + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`, { + [takeControlAction.reportActionID]: takeControlAction, + }); + const lastMessage = getLastMessageTextForReport({report, lastActorDetails: null, isReportArchived: false}); + expect(lastMessage).toBe(Parser.htmlToText(getChangedApproverActionMessage(takeControlAction))); + }); + it('REROUTE action', async () => { + const report: Report = createRandomReport(0); + const rerouteAction: ReportAction = { + ...createRandomReportAction(1), + actionName: CONST.REPORT.ACTIONS.TYPE.REROUTE, + message: [{type: 'COMMENT', text: ''}], + originalMessage: {}, + }; + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`, { + [rerouteAction.reportActionID]: rerouteAction, + }); + const lastMessage = getLastMessageTextForReport({report, lastActorDetails: null, isReportArchived: false}); + expect(lastMessage).toBe(Parser.htmlToText(getChangedApproverActionMessage(rerouteAction))); + }); + it('MOVED action', async () => { + const report: Report = createRandomReport(0); + const movedAction: ReportAction = { + ...createRandomReportAction(1), + actionName: CONST.REPORT.ACTIONS.TYPE.MOVED, + message: [{type: 'COMMENT', text: ''}], + originalMessage: {}, + }; + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`, { + [movedAction.reportActionID]: movedAction, + }); + const lastMessage = getLastMessageTextForReport({report, lastActorDetails: null, isReportArchived: false}); + expect(lastMessage).toBe(Parser.htmlToText(getMovedActionMessage(movedAction, report))); + }); + }); }); From 0c4e6da54e89ca737ee2322670db693341ffd601 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Thu, 23 Oct 2025 12:22:05 +0800 Subject: [PATCH 2/3] lint --- tests/unit/OptionsListUtilsTest.tsx | 3602 +++++++++++++-------------- 1 file changed, 1801 insertions(+), 1801 deletions(-) diff --git a/tests/unit/OptionsListUtilsTest.tsx b/tests/unit/OptionsListUtilsTest.tsx index 8a26e299d469..e033bf8cb6c2 100644 --- a/tests/unit/OptionsListUtilsTest.tsx +++ b/tests/unit/OptionsListUtilsTest.tsx @@ -8,7 +8,7 @@ import {LocaleContextProvider} from '@components/LocaleContextProvider'; import OnyxListItemProvider from '@components/OnyxListItemProvider'; import useReportIsArchived from '@hooks/useReportIsArchived'; import DateUtils from '@libs/DateUtils'; -import {translateLocal} from '@libs/Localize'; +import {translate} from '@libs/Localize'; import isSearchTopmostFullScreenRoute from '@libs/Navigation/helpers/isSearchTopmostFullScreenRoute'; import type {OptionList, Options, SearchOption} from '@libs/OptionsListUtils'; import { @@ -634,1803 +634,1803 @@ describe('OptionsListUtils', () => { OPTIONS_WITH_MANAGER_MCTEST = createOptionList(PERSONAL_DETAILS_WITH_MANAGER_MCTEST); }); - describe('getSearchOptions()', () => { - 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: {}, betas: [CONST.BETAS.ALL]}); - - // Then all personal details (including those that have reports) should be returned - expect(results.personalDetails.length).toBe(10); - - // Then all of the reports should be shown including the archived rooms, except for the thread report with notificationPreferences hidden. - expect(results.recentReports.length).toBe(Object.values(OPTIONS.reports).length - 1); - }); - - it('should include current user when includeCurrentUser is true for type:chat from suggestions', () => { - // Given a set of options where the current user is Iron Man (accountID: 2) - // When we call getSearchOptions with includeCurrentUser set to true - const results = getSearchOptions({ - options: OPTIONS, - draftComments: {}, - betas: [CONST.BETAS.ALL], - isUsedInChatFinder: true, - includeReadOnly: true, - searchQuery: '', - maxResults: undefined, - includeUserToInvite: false, - includeRecentReports: true, - includeCurrentUser: true, - }); - - // Then the current user should be included in personalDetails - const currentUserOption = results.personalDetails.find((option) => option.login === 'tonystark@expensify.com'); - expect(currentUserOption).toBeDefined(); - expect(currentUserOption?.text).toBe('Iron Man'); - expect(currentUserOption?.accountID).toBe(2); - - // Then all personal details including the current user should be returned - expect(results.personalDetails.length).toBe(11); - }); - - it('should exclude current user when includeCurrentUser is false', () => { - // Given a set of options where the current user is Iron Man (accountID: 2) - // When we call getSearchOptions with includeCurrentUser set to false (default behavior) - const results = getSearchOptions({ - options: OPTIONS, - draftComments: {}, - betas: [CONST.BETAS.ALL], - isUsedInChatFinder: true, - includeReadOnly: true, - searchQuery: '', - maxResults: undefined, - includeUserToInvite: false, - includeRecentReports: true, - }); - - // Then the current user should not be included in personalDetails - const currentUserOption = results.personalDetails.find((option) => option.login === 'tonystark@expensify.com'); - expect(currentUserOption).toBeUndefined(); - - // Then all personal details except the current user should be returned - expect(results.personalDetails.length).toBe(10); - }); - }); - - describe('orderOptions()', () => { - it('should sort options alphabetically and preserves reportID for personal details with existing reports', () => { - // Given a set of reports and personalDetails - // When we call getValidOptions() - let results: Pick = getValidOptions( - { - reports: OPTIONS.reports, - personalDetails: OPTIONS.personalDetails, - }, - {}, - ); - // When we call orderOptions() - results = orderOptions(results); - - // Then all personalDetails except the currently logged in user should be returned - expect(results.personalDetails.length).toBe(Object.values(OPTIONS.personalDetails).length - 1); - - const expected = [ - 'Black Panther', - 'Black Widow', - 'Captain America', - 'Invisible Woman', - 'Mister Fantastic', - 'Mr Sinister', - 'Spider-Man', - 'The Incredible Hulk', - 'Thor', - 'Timothée', - ]; - const actual = results.personalDetails?.map((item) => item.text); - - // Then the results should be sorted alphabetically - expect(actual).toEqual(expected); - - const personalDetailWithExistingReport = results.personalDetails.find((personalDetail) => personalDetail.login === 'peterparker@expensify.com'); - // Then the result which has an existing report should also have the reportID attached - expect(personalDetailWithExistingReport?.reportID).toBe('2'); - }); - - 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: []}, {}); - // When we call orderOptions() - results = orderOptions(results); - - const expected = [ - 'Black Panther', - 'Black Widow', - 'Captain America', - 'Invisible Woman', - 'Mister Fantastic', - 'Mr Sinister', - 'Spider-Man', - 'The Incredible Hulk', - 'Thor', - 'Timothée', - ]; - const actual = results.personalDetails?.map((item) => item.text); - - // Then the results should be sorted alphabetically - expect(actual).toEqual(expected); - }); - }); - - describe('getValidOptions()', () => { - 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: []}, {}); - - // Then the result should be empty - expect(results.personalDetails).toEqual([]); - expect(results.recentReports).toEqual([]); - expect(results.currentUserOption).toBeUndefined(); - expect(results.userToInvite).toEqual(null); - expect(results.workspaceChats).toEqual([]); - expect(results.selfDMChat).toEqual(undefined); - }); - - 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}, {}); - - // 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); - // Then the result should include Concierge - expect(results.recentReports).toEqual(expect.arrayContaining([expect.objectContaining({login: 'concierge@expensify.com'})])); - }); - - it('should exclude Concierge when excludedLogins is specified', () => { - // Given a set of reports and personalDetails that includes Concierge and a config object that excludes Concierge - // When we call getValidOptions() - const results = getValidOptions( - { - reports: OPTIONS_WITH_CONCIERGE.reports, - personalDetails: OPTIONS_WITH_CONCIERGE.personalDetails, - }, - {}, - { - excludeLogins: {[CONST.EMAIL.CONCIERGE]: true}, - }, - ); - - // Then the result should include all personalDetails except the currently logged in user and Concierge - expect(results.personalDetails.length).toBe(Object.values(OPTIONS_WITH_CONCIERGE.personalDetails).length - 2); - // Then the result should not include Concierge - expect(results.personalDetails).not.toEqual(expect.arrayContaining([expect.objectContaining({login: 'concierge@expensify.com'})])); - }); - - 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}, - {}, - {excludeLogins: {[CONST.EMAIL.CHRONOS]: true}}, - ); - - // Then the result should include all personalDetails except the currently logged in user and Chronos - expect(results.personalDetails.length).toBe(Object.values(OPTIONS_WITH_CHRONOS.personalDetails).length - 2); - // Then the result should not include Chronos - expect(results.personalDetails).not.toEqual(expect.arrayContaining([expect.objectContaining({login: 'chronos@expensify.com'})])); - }); - - it('should exclude Receipts option from results when excludedLogins is specified', () => { - // Given a set of reports and personalDetails that includes receipts and a config object that excludes receipts - // When we call getValidOptions() - const results = getValidOptions( - { - reports: OPTIONS_WITH_RECEIPTS.reports, - personalDetails: OPTIONS_WITH_RECEIPTS.personalDetails, - }, - {}, - { - excludeLogins: {[CONST.EMAIL.RECEIPTS]: true}, - }, - ); - - // Then the result should include all personalDetails except the currently logged in user and receipts - expect(results.personalDetails.length).toBe(Object.values(OPTIONS_WITH_RECEIPTS.personalDetails).length - 2); - // Then the result should not include receipts - expect(results.personalDetails).not.toEqual(expect.arrayContaining([expect.objectContaining({login: 'receipts@expensify.com'})])); - }); - - it('should include Manager McTest in results by default', () => { - // Given a set of reports and personalDetails that includes Manager McTest - // When we call getValidOptions() - const result = getValidOptions( - {reports: OPTIONS_WITH_MANAGER_MCTEST.reports, personalDetails: OPTIONS_WITH_MANAGER_MCTEST.personalDetails}, - {}, - {includeP2P: true, canShowManagerMcTest: true, betas: [CONST.BETAS.NEWDOT_MANAGER_MCTEST]}, - ); - - // Then the result should include all personalDetails except the currently logged in user - expect(result.personalDetails.length).toBe(Object.values(OPTIONS_WITH_MANAGER_MCTEST.personalDetails).length - 1); - // Then the result should include Manager McTest - expect(result.personalDetails).toEqual(expect.arrayContaining([expect.objectContaining({login: CONST.EMAIL.MANAGER_MCTEST})])); - }); - - 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}, - {}, - {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); - // Then the result should not include Manager McTest - expect(result.personalDetails).not.toEqual(expect.arrayContaining([expect.objectContaining({login: CONST.EMAIL.MANAGER_MCTEST})])); - }); - - it('should exclude Manager McTest from results if user dismissed the tooltip', () => { - return waitForBatchedUpdates() - .then(() => - // Given that the user has dismissed the tooltip - Onyx.set(ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING, { - [CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.SCAN_TEST_TOOLTIP]: { - timestamp: DateUtils.getDBTime(new Date().valueOf()), - }, - }), - ) - .then(() => { - // When we call getValidOptions() - const optionsWhenUserAlreadySubmittedExpense = getValidOptions( - {reports: OPTIONS_WITH_MANAGER_MCTEST.reports, personalDetails: OPTIONS_WITH_MANAGER_MCTEST.personalDetails}, - {}, - {includeP2P: true, canShowManagerMcTest: true, betas: [CONST.BETAS.NEWDOT_MANAGER_MCTEST]}, - ); - - // Then the result should include all personalDetails except the currently logged in user and Manager McTest - expect(optionsWhenUserAlreadySubmittedExpense.personalDetails.length).toBe(Object.values(OPTIONS_WITH_MANAGER_MCTEST.personalDetails).length - 2); - // Then the result should not include Manager McTest - expect(optionsWhenUserAlreadySubmittedExpense.personalDetails).not.toEqual(expect.arrayContaining([expect.objectContaining({login: CONST.EMAIL.MANAGER_MCTEST})])); - }); - }); - - it('should keep admin rooms if specified', () => { - // Given an admin room report search option - const adminRoom: SearchOption = { - item: { - chatType: 'policyAdmins', - currency: 'USD', - errorFields: {}, - lastActionType: 'CREATED', - lastReadTime: '2025-03-21 07:25:46.279', - lastVisibleActionCreated: '2024-12-15 21:13:24.317', - lastVisibleActionLastModified: '2024-12-15 21:13:24.317', - ownerAccountID: 0, - permissions: ['read', 'write'], - policyID: '52A5ABD88FBBD18F', - policyName: "David's Playground", - reportID: '1455140530846319', - reportName: '#admins', - type: 'chat', - writeCapability: 'all', - }, - text: '#admins', - alternateText: "David's Playground", - allReportErrors: {}, - subtitle: "David's Playground", - participantsList: [], - reportID: '1455140530846319', - keyForList: '1455140530846319', - isDefaultRoom: true, - isChatRoom: true, - policyID: '52A5ABD88FBBD18F', - lastMessageText: '', - lastVisibleActionCreated: '2024-12-15 21:13:24.317', - notificationPreference: 'hidden', - }; - // When we call getValidOptions with includeMultipleParticipantReports set to true - const results = getValidOptions( - {reports: [adminRoom], personalDetails: OPTIONS.personalDetails}, - {}, - { - includeMultipleParticipantReports: true, - }, - ); - const adminRoomOption = results.recentReports.find((report) => report.reportID === '1455140530846319'); - - // Then the result should include the admin room - expect(adminRoomOption).toBeDefined(); - }); - - it('should include brickRoadIndicator if showRBR is true', () => { - const reportID = '1455140530846319'; - const workspaceChat: SearchOption = { - item: { - chatType: CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT, - currency: 'USD', - errorFields: {}, - lastActionType: 'CREATED', - lastReadTime: '2025-03-21 07:25:46.279', - lastVisibleActionCreated: '2024-12-15 21:13:24.317', - lastVisibleActionLastModified: '2024-12-15 21:13:24.317', - ownerAccountID: 0, - permissions: ['read', 'write'], - participants: {1: {notificationPreference: 'always'}}, - policyID: '52A5ABD88FBBD18F', - policyName: "A's Workspace", - reportID, - reportName: "A's Workspace chat", - type: 'chat', - writeCapability: 'all', - }, - text: "A's Workspace chat", - alternateText: "A's Workspace", - allReportErrors: {}, - subtitle: "A's Workspace", - participantsList: [], - reportID, - keyForList: '1455140530846319', - isDefaultRoom: true, - isChatRoom: true, - policyID: '52A5ABD88FBBD18F', - lastMessageText: '', - lastVisibleActionCreated: '2024-12-15 21:13:24.317', - notificationPreference: 'hidden', - brickRoadIndicator: CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR, - }; - const results = getValidOptions( - {reports: [workspaceChat], personalDetails: []}, - {}, - { - includeMultipleParticipantReports: true, - showRBR: true, - }, - ); - expect(results.recentReports.at(0)?.brickRoadIndicator).toBe(CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR); - }); - - it('should not include brickRoadIndicator if showRBR is false', () => { - const reportID = '1455140530846319'; - const workspaceChat: SearchOption = { - item: { - chatType: CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT, - currency: 'USD', - errorFields: {}, - lastActionType: 'CREATED', - lastReadTime: '2025-03-21 07:25:46.279', - lastVisibleActionCreated: '2024-12-15 21:13:24.317', - lastVisibleActionLastModified: '2024-12-15 21:13:24.317', - ownerAccountID: 0, - permissions: ['read', 'write'], - participants: {1: {notificationPreference: 'always'}}, - policyID: '52A5ABD88FBBD18F', - policyName: "A's Workspace", - reportID, - reportName: "A's Workspace chat", - type: 'chat', - writeCapability: 'all', - }, - text: "A's Workspace chat", - alternateText: "A's Workspace", - allReportErrors: {}, - subtitle: "A's Workspace", - participantsList: [], - reportID, - keyForList: '1455140530846319', - isDefaultRoom: true, - isChatRoom: true, - policyID: '52A5ABD88FBBD18F', - lastMessageText: '', - lastVisibleActionCreated: '2024-12-15 21:13:24.317', - notificationPreference: 'hidden', - brickRoadIndicator: CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR, - }; - const results = getValidOptions( - {reports: [workspaceChat], personalDetails: []}, - {}, - { - includeMultipleParticipantReports: true, - showRBR: false, - }, - ); - expect(results.recentReports.at(0)?.brickRoadIndicator).toBe(null); - }); - }); - - describe('getValidOptions() for chat room', () => { - 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, - {}, - { - includeRecentReports: true, - includeMultipleParticipantReports: true, - includeP2P: true, - includeOwnedWorkspaceChats: true, - }, - ); - - // Then the result should include all reports except the currently logged in user - expect(results.recentReports.length).toBe(OPTIONS_WITH_WORKSPACE_ROOM.reports.length - 1); - expect(results.recentReports).toEqual(expect.arrayContaining([expect.objectContaining({reportID: '14'})])); - }); - }); - - describe('getValidOptions() for group Chat', () => { - 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}, {}); - const reportLogins = results.recentReports.map((reportOption) => reportOption.login); - const personalDetailsOverlapWithReports = results.personalDetails.every((personalDetailOption) => reportLogins.includes(personalDetailOption.login)); - - // Then we should expect all the personalDetails to show except the currently logged in user - expect(results.personalDetails.length).toBe(Object.values(OPTIONS.personalDetails).length - 1); - // Then none of our personalDetails should include any of the users with recent reports - expect(personalDetailsOverlapWithReports).toBe(false); - }); - - 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}, {}, {excludeLogins: {'peterparker@expensify.com': true}}); - - // Then the option should not appear anywhere in either list - expect(results.recentReports.every((option) => option.login !== 'peterparker@expensify.com')).toBe(true); - expect(results.personalDetails.every((option) => option.login !== 'peterparker@expensify.com')).toBe(true); - }); - - 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}, {}); - - // 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); - // Then Concierge should be included in the results - expect(results.recentReports).toEqual(expect.arrayContaining([expect.objectContaining({login: 'concierge@expensify.com'})])); - }); - - it('should exclude Concierge from the results when it is specified in excludedLogins', () => { - // Given a set of reports and personalDetails that includes Concierge - // When we call getValidOptions with excludeLogins param - const results = getValidOptions( - { - reports: OPTIONS_WITH_CONCIERGE.reports, - personalDetails: OPTIONS_WITH_CONCIERGE.personalDetails, - }, - {}, - { - excludeLogins: {[CONST.EMAIL.CONCIERGE]: true}, - }, - ); - - // Then the result should include all personalDetails except the currently logged in user and Concierge - expect(results.personalDetails.length).toBe(Object.values(OPTIONS_WITH_CONCIERGE.personalDetails).length - 2); - // Then none of the results should include Concierge - expect(results.personalDetails).not.toEqual(expect.arrayContaining([expect.objectContaining({login: 'concierge@expensify.com'})])); - expect(results.recentReports).not.toEqual(expect.arrayContaining([expect.objectContaining({login: 'concierge@expensify.com'})])); - }); - - 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}, - {}, - {excludeLogins: {[CONST.EMAIL.CHRONOS]: true}}, - ); - - // Then the result should include all personalDetails except the currently logged in user and Chronos - expect(results.personalDetails.length).toBe(Object.values(OPTIONS_WITH_CHRONOS.personalDetails).length - 2); - // Then none of the results should include Chronos - expect(results.personalDetails).not.toEqual(expect.arrayContaining([expect.objectContaining({login: 'chronos@expensify.com'})])); - expect(results.recentReports).not.toEqual(expect.arrayContaining([expect.objectContaining({login: 'chronos@expensify.com'})])); - }); - - it('should exclude Receipts from the results when it is specified in excludedLogins', () => { - // Given a set of reports and personalDetails that includes receipts - // When we call getValidOptions() with excludeLogins param - const results = getValidOptions( - { - reports: OPTIONS_WITH_RECEIPTS.reports, - personalDetails: OPTIONS_WITH_RECEIPTS.personalDetails, - }, - {}, - { - excludeLogins: {[CONST.EMAIL.RECEIPTS]: true}, - }, - ); - - // Then the result should include all personalDetails except the currently logged in user and receipts - expect(results.personalDetails.length).toBe(Object.values(OPTIONS_WITH_RECEIPTS.personalDetails).length - 2); - // Then none of the results should include receipts - expect(results.personalDetails).not.toEqual(expect.arrayContaining([expect.objectContaining({login: 'receipts@expensify.com'})])); - expect(results.recentReports).not.toEqual(expect.arrayContaining([expect.objectContaining({login: 'receipts@expensify.com'})])); - }); - - it('should limit recent reports when maxRecentReportElements is specified', () => { - // 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}, {}, {maxRecentReportElements: maxRecentReports}); - - // Then the recent reports should be limited to the specified number - expect(results.recentReports.length).toBeLessThanOrEqual(maxRecentReports); - }); - - 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}, {}); - const resultsWithLimit = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}, {maxRecentReportElements: 2}); - - // Then the results without limit should have more or equal reports - expect(resultsWithoutLimit.recentReports.length).toBeGreaterThanOrEqual(resultsWithLimit.recentReports.length); - }); - - 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}, {}); - const resultsWithLimit = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}, {maxRecentReportElements: 2}); - - // Then personalDetails should remain the same regardless of maxRecentReportElements - expect(resultsWithLimit.personalDetails.length).toBe(resultsWithoutLimit.personalDetails.length); - }); - - it('should respect maxRecentReportElements when combined with maxElements', () => { - // Given a set of reports and personalDetails - // When we call getValidOptions with both maxElements and maxRecentReportElements - const maxRecentReports = 3; - const maxTotalElements = 10; - const results = getValidOptions( - {reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, - {}, - {maxElements: maxTotalElements, maxRecentReportElements: maxRecentReports}, - ); - - // Then recent reports should be limited by maxRecentReportElements - expect(results.recentReports.length).toBeLessThanOrEqual(maxRecentReports); - // Then the total number of options (reports + personalDetails) should not exceed maxElements - expect(results.recentReports.length + results.personalDetails.length).toBeLessThanOrEqual(maxTotalElements); - }); - }); - - describe('getShareDestinationsOptions()', () => { - it('should exclude archived rooms and hidden threads from share destinations', () => { - // 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) => { - const report = option.item; - const {result: isReportArchived} = renderHook(() => useReportIsArchived(report.reportID)); - if (canUserPerformWriteAction(report, isReportArchived.current) && canCreateTaskInReport(report) && !isCanceledTaskReport(report)) { - filtered.push(option); - } - return filtered; - }, []); - - // When we call getValidOptions for share destination with an empty search value - const results = getValidOptions( - {reports: filteredReports, personalDetails: OPTIONS.personalDetails}, - {}, - { - betas: [], - includeMultipleParticipantReports: true, - showChatPreviewLine: true, - forcePolicyNamePreview: true, - includeThreads: true, - includeMoneyRequests: true, - includeTasks: true, - excludeLogins: {}, - includeOwnedWorkspaceChats: true, - includeSelfDM: true, - searchString: '', - includeUserToInvite: false, - }, - ); - - // Then all the recent reports should be returned except the archived rooms and the hidden thread - expect(results.recentReports.length).toBe(Object.values(OPTIONS.reports).length - 2); - }); - - it('should include DMS, group chats, and workspace rooms in share destinations', () => { - // Given a set of filtered current Reports (as we do in the component) with workspace rooms before getting share destination options - const filteredReportsWithWorkspaceRooms = Object.values(OPTIONS_WITH_WORKSPACE_ROOM.reports).reduce((filtered, option) => { - const report = option.item; - const {result: isReportArchived} = renderHook(() => useReportIsArchived(report.reportID)); - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - if (canUserPerformWriteAction(report, isReportArchived.current) || isExpensifyOnlyParticipantInReport(report)) { - filtered.push(option); - } - return filtered; - }, []); - - // When we call getValidOptions for share destination with an empty search value - const results = getValidOptions( - {reports: filteredReportsWithWorkspaceRooms, personalDetails: OPTIONS.personalDetails}, - {}, - { - betas: [], - includeMultipleParticipantReports: true, - showChatPreviewLine: true, - forcePolicyNamePreview: true, - includeThreads: true, - includeMoneyRequests: true, - includeTasks: true, - excludeLogins: {}, - includeOwnedWorkspaceChats: true, - includeSelfDM: true, - searchString: '', - includeUserToInvite: false, - }, - ); - - // Then all recent reports should be returned except the archived rooms and the hidden thread - expect(results.recentReports.length).toBe(Object.values(OPTIONS_WITH_WORKSPACE_ROOM.reports).length - 2); - }); - }); - - describe('getShareLogOptions()', () => { - it('should not include read-only report', () => { - // Given a list of 11 report options with reportID of 10 is archived - // When we call getShareLogOptions - const results = getShareLogOptions(OPTIONS, {}, []); - - // Then the report with reportID of 10 should not be included on the list - expect(results.recentReports.length).toBe(10); - expect(results.recentReports.find((report) => report.reportID === '10')).toBeUndefined(); - }); - }); - - describe('getMemberInviteOptions()', () => { - it('should sort personal details alphabetically', () => { - // Given a set of personalDetails - // When we call getMemberInviteOptions - const results = getMemberInviteOptions(OPTIONS.personalDetails, []); - - // 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'); - }); - }); - - describe('getLastActorDisplayName()', () => { - it('should return correct display name', () => { - renderLocaleContextProvider(); - // Given two different personal details - // When we call getLastActorDisplayName - const result1 = getLastActorDisplayName(PERSONAL_DETAILS['2']); - const result2 = getLastActorDisplayName(PERSONAL_DETAILS['3']); - - // We should expect the display names to be the same as the personal details - expect(result1).toBe('You'); - expect(result2).toBe('Spider-Man'); - }); - }); - - describe('formatMemberForList()', () => { - it('should format members correctly', () => { - // Given a set of personal details - // When we call formatMemberForList - const formattedMembers = Object.values(PERSONAL_DETAILS).map((personalDetail) => formatMemberForList(personalDetail)); - - // Then the formatted members' order should be the same as the original PERSONAL_DETAILS array - expect(formattedMembers.at(0)?.text).toBe('Mister Fantastic'); - expect(formattedMembers.at(1)?.text).toBe('Iron Man'); - expect(formattedMembers.at(2)?.text).toBe('Spider-Man'); - - // Then only the first item should be selected - expect(formattedMembers.at(0)?.isSelected).toBe(true); - // Then all remaining items should be unselected - expect(formattedMembers.slice(1).every((personalDetail) => !personalDetail.isSelected)).toBe(true); - // Then all items should be enabled - expect(formattedMembers.every((personalDetail) => !personalDetail.isDisabled)).toBe(true); - }); - }); - - describe('filterAndOrderOptions()', () => { - 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: {}, betas: [CONST.BETAS.ALL]}); - // When we pass the returned options to filterAndOrderOptions with an empty search value - const filteredOptions = filterAndOrderOptions(options, '', COUNTRY_CODE); - - // Then all options should be returned - expect(filteredOptions.recentReports.length + filteredOptions.personalDetails.length).toBe(14); - }); - - it('should return filtered options in correct order', () => { - const searchText = 'man'; - // Given a set of options - // When we call getSearchOptions with all betas - const options = getSearchOptions({options: OPTIONS, draftComments: {}, 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}); - - // Then we expect all options to be part of the recentReports list and reports should be first: - expect(filteredOptions.personalDetails.length).toBe(0); - // Then returned reports should match the search text - expect(filteredOptions.recentReports.length).toBe(4); - // Then the returned reports should be ordered by most recent action (and other criteria such as whether they are archived) - expect(filteredOptions.recentReports.at(0)?.text).toBe('Invisible Woman'); // '2022-11-22 03:26:02.019' - expect(filteredOptions.recentReports.at(1)?.text).toBe('Spider-Man'); // '2022-11-22 03:26:02.016' - expect(filteredOptions.recentReports.at(2)?.text).toBe('Black Widow'); // This is a personal detail, which has no lastVisibleActionCreated, but matches the login - expect(filteredOptions.recentReports.at(3)?.text).toBe('Mister Fantastic, Invisible Woman'); // This again is a report with '2022-11-22 03:26:02.015' - }); - - it('should filter users by email', () => { - const searchText = 'mistersinister@marauders.com'; - // Given a set of options - // When we call getSearchOptions with all betas - const options = getSearchOptions({options: OPTIONS, draftComments: {}, betas: [CONST.BETAS.ALL]}); - // When we pass the returned options to filterAndOrderOptions with a search value - const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE); - - // Then only one report should be returned - expect(filteredOptions.recentReports.length).toBe(1); - // Then the returned report should match the search text - expect(filteredOptions.recentReports.at(0)?.text).toBe('Mr Sinister'); - }); - - it('should find archived chats', () => { - const searchText = 'Archived'; - // Given a set of options - // When we call getSearchOptions with all betas - const options = getSearchOptions({options: OPTIONS, draftComments: {}, betas: [CONST.BETAS.ALL]}); - // When we pass the returned options to filterAndOrderOptions with a search value - const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE); - - // Then only one report should be returned - expect(filteredOptions.recentReports.length).toBe(1); - // Then the returned report should match the search text - expect(!!filteredOptions.recentReports.at(0)?.private_isArchived).toBe(true); - }); - - it('should filter options by email if dot is skipped in the email', () => { - // cspell:disable-next-line - const searchText = 'barryallen'; - // 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: {}, 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}); - - // Then only one report should be returned - expect(filteredOptions.recentReports.length).toBe(1); - // Then the returned report should match the search text - expect(filteredOptions.recentReports.at(0)?.login).toBe('barry.allen@expensify.com'); - }); - - it('should include workspace rooms in the search results', () => { - 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: {}, betas: [CONST.BETAS.ALL]}); - // When we pass the returned options to filterAndOrderOptions with a search value - const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE); - - // Then only one report should be returned - expect(filteredOptions.recentReports.length).toBe(1); - // Then the returned report should match the search text - expect(filteredOptions.recentReports.at(0)?.subtitle).toBe('Avengers Room'); - }); - - 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: {}, betas: [CONST.BETAS.ALL]}); - // When we pass the returned options to filterAndOrderOptions with a search value - const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE); - - // Then only one report should be returned - expect(filteredOptions.recentReports.length).toBe(1); - // Then the returned report should match the search text - expect(filteredOptions.recentReports.at(0)?.login).toBe(searchText); - }); - - it('should prioritize options with matching display name over chat rooms', () => { - const searchText = 'spider'; - // 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: {}, betas: [CONST.BETAS.ALL]}); - // When we pass the returned options to filterAndOrderOptions with a search value - const filterOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE); - - // Then only two reports should be returned - expect(filterOptions.recentReports.length).toBe(2); - // Then the second report should match the search text - expect(filterOptions.recentReports.at(1)?.isChatRoom).toBe(true); - }); - - it('should put the item with latest lastVisibleActionCreated on top when search value match multiple items', () => { - renderLocaleContextProvider(); - const searchText = 'fantastic'; - // Given a set of options - const options = getSearchOptions({options: OPTIONS, draftComments: {}}); - // When we call filterAndOrderOptions with a search value - const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE); - - // Then only three reports should be returned - expect(filteredOptions.recentReports.length).toBe(3); - // Then the first report should match the search text - expect(filteredOptions.recentReports.at(0)?.text).toBe('Mister Fantastic'); - // Then the second report should match the search text - expect(filteredOptions.recentReports.at(1)?.text).toBe('Mister Fantastic, Invisible Woman'); - }); - - 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: {}}); - // When we call filterAndOrderOptions with a search value - const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE); - - // Then the user to invite should be returned - expect(filteredOptions.userToInvite?.login).toBe(searchText); - }); - - 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}, {}, {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}); - - // Then no personal details should be returned - expect(filterOptions.recentReports.length).toBe(0); - }); - - 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: {}}); - // When we call filterAndOrderOptions with a search value and excludeLogins - const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE, {excludeLogins: CONST.EXPENSIFY_EMAILS_OBJECT}); - - // Then the user to invite should be returned - expect(filteredOptions.userToInvite?.login).toBe(searchText); - }); - - 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: {}}); - // When we call filterAndOrderOptions with a search value and maxRecentReportsToShow set to 2 - const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE, {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}); - - // Then no reports should be returned - expect(limitToZeroOptions.recentReports.length).toBe(0); - }); - - 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: {}, betas: [CONST.BETAS.ALL]}); - // When we call filterAndOrderOptions with a search value - const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE); - - // Then there should be one matching result - expect(filteredOptions.personalDetails.length).toBe(1); - // Then the user to invite should be null - 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, []); - // When we call filterAndOrderOptions with a search value that does not match any personal details - const filteredOptions = filterAndOrderOptions(options, 'magneto', COUNTRY_CODE); - - // 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, []); - // When we call filterAndOrderOptions with a search value that matches an email - const filteredOptions = filterAndOrderOptions(options, 'peterparker@expensify.com', COUNTRY_CODE); - - // 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) => { - const report = option.item; - if (canUserPerformWriteAction(report, false) && canCreateTaskInReport(report) && !isCanceledTaskReport(report)) { - filtered.push(option); - } - return filtered; - }, []); - // When we call getValidOptions for share destination with the filteredReports - const options = getValidOptions( - {reports: filteredReports, personalDetails: OPTIONS.personalDetails}, - {}, - { - betas: [], - includeMultipleParticipantReports: true, - showChatPreviewLine: true, - forcePolicyNamePreview: true, - includeThreads: true, - includeMoneyRequests: true, - includeTasks: true, - excludeLogins: {}, - includeOwnedWorkspaceChats: true, - includeSelfDM: true, - searchString: '', - 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); - - // Then no recent reports should be returned - expect(filteredOptions.recentReports.length).toBe(0); - }); - - it('should return a workspace room when we search for a workspace room(getShareDestinationsOptions)', () => { - // Given a set of filtered current Reports (as we do in the component) before getting share destination options - const filteredReportsWithWorkspaceRooms = Object.values(OPTIONS_WITH_WORKSPACE_ROOM.reports).reduce((filtered, option) => { - const report = option.item; - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - if (canUserPerformWriteAction(report, false) || isExpensifyOnlyParticipantInReport(report)) { - filtered.push(option); - } - return filtered; - }, []); - - // When we call getValidOptions for share destination with the filteredReports - const options = getValidOptions( - {reports: filteredReportsWithWorkspaceRooms, personalDetails: OPTIONS.personalDetails}, - {}, - { - betas: [], - includeMultipleParticipantReports: true, - showChatPreviewLine: true, - forcePolicyNamePreview: true, - includeThreads: true, - includeMoneyRequests: true, - includeTasks: true, - excludeLogins: {}, - includeOwnedWorkspaceChats: true, - includeSelfDM: true, - searchString: '', - 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); - - // Then one recent report should be returned - expect(filteredOptions.recentReports.length).toBe(1); - }); - - it('should not show any results if searching for a non-existing workspace room(getShareDestinationOptions)', () => { - // Given a set of filtered current Reports (as we do in the component) before getting share destination options - const filteredReportsWithWorkspaceRooms = Object.values(OPTIONS_WITH_WORKSPACE_ROOM.reports).reduce((filtered, option) => { - const report = option.item; - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - if (canUserPerformWriteAction(report, false) || isExpensifyOnlyParticipantInReport(report)) { - filtered.push(option); - } - return filtered; - }, []); - - // When we call getValidOptions for share destination with the filteredReports - const options = getValidOptions( - {reports: filteredReportsWithWorkspaceRooms, personalDetails: OPTIONS.personalDetails}, - {}, - { - betas: [], - includeMultipleParticipantReports: true, - showChatPreviewLine: true, - forcePolicyNamePreview: true, - includeThreads: true, - includeMoneyRequests: true, - includeTasks: true, - excludeLogins: {}, - includeOwnedWorkspaceChats: true, - includeSelfDM: true, - searchString: '', - 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); - - // Then no recent reports should be returned - expect(filteredOptions.recentReports.length).toBe(0); - }); - - 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}, {}); - // When we call filterAndOrderOptions with a search value that matches a personal detail with no existing report - const filteredOptions = filterAndOrderOptions(options, 'hulk', COUNTRY_CODE); - - // Then no recent reports should be returned - expect(filteredOptions.recentReports.length).toBe(0); - // 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)?.login).toBe('brucebanner@expensify.com'); - }); - - it('should not return any options or user to invite if there are no search results and the string does not match a potential email or phone', () => { - // Given a set of options - const options = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}); - // 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); - - // Then no recent reports or personal details should be returned - expect(filteredOptions.recentReports.length).toBe(0); - expect(filteredOptions.personalDetails.length).toBe(0); - // Then no user to invite should be returned - expect(filteredOptions.userToInvite).toBe(null); - }); - - 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}, {}); - // 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); - - // Then no recent reports or personal details should be returned - expect(filteredOptions.recentReports.length).toBe(0); - expect(filteredOptions.personalDetails.length).toBe(0); - // Then an user to invite should be returned - expect(filteredOptions.userToInvite).not.toBe(null); - }); - - 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}, {}); - // 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); - - // Then no recent reports should be returned - expect(filteredOptions.recentReports.length).toBe(0); - // Then one user to invite should be returned - expect(filteredOptions.userToInvite).not.toBe(null); - }); - - 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}, {}); - // When we call filterAndOrderOptions with a search value without accent mark - const filteredOptions = filterAndOrderOptions(options, 'Timothee', COUNTRY_CODE); - - // Then one personalDetails with accent mark should be returned - expect(filteredOptions.personalDetails.length).toBe(1); - }); - - 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}, {}); - // 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); - - // Then no recent reports or personal details should be returned - expect(filteredOptions.recentReports.length).toBe(0); - expect(filteredOptions.personalDetails.length).toBe(0); - // Then one user to invite should be returned - expect(filteredOptions.userToInvite).not.toBe(null); - // Then the user to invite should match the search value - expect(filteredOptions.userToInvite?.login).toBe('+15005550006'); - }); - - 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}, {}); - // 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); - - // Then no recent reports or personal details should be returned - expect(filteredOptions.recentReports.length).toBe(0); - expect(filteredOptions.personalDetails.length).toBe(0); - // Then one user to invite should be returned - expect(filteredOptions.userToInvite).not.toBe(null); - // Then the user to invite should match the search value - expect(filteredOptions.userToInvite?.login).toBe('+15005550006'); - }); - - 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}, {}); - // 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); - - // Then no recent reports or personal details should be returned - expect(filteredOptions.recentReports.length).toBe(0); - expect(filteredOptions.personalDetails.length).toBe(0); - // Then one user to invite should be returned - expect(filteredOptions.userToInvite).not.toBe(null); - // Then the user to invite should match the search value - expect(filteredOptions.userToInvite?.login).toBe('+18003243233'); - }); - - 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}, {}); - // When we call filterAndOrderOptions with a search value that does not match any personal details or reports - const filteredOptions = filterAndOrderOptions(options, '998243aaaa', COUNTRY_CODE); - - // Then no recent reports or personal details should be returned - expect(filteredOptions.recentReports.length).toBe(0); - expect(filteredOptions.personalDetails.length).toBe(0); - // Then no user to invite should be returned - expect(filteredOptions.userToInvite).toBe(null); - }); - - 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}, {}); - // When we call filterAndOrderOptions with a search value that does not match any personal details - const filteredOptions = filterAndOrderOptions(options, 'magneto', COUNTRY_CODE); - - // Then no personal details should be returned - expect(filteredOptions.personalDetails.length).toBe(0); - }); - - 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}, {}); - // When we call filterAndOrderOptions with a search value that matches an email - const filteredOptions = filterAndOrderOptions(options, 'peterparker@expensify.com', COUNTRY_CODE, {sortByReportTypeInSearch: true}); - - // Then one recent report should be returned - expect(filteredOptions.recentReports.length).toBe(1); - // Then the returned recent report should match the search text - expect(filteredOptions.recentReports.at(0)?.text).toBe('Spider-Man'); - // Then no personal details should be returned - expect(filteredOptions.personalDetails.length).toBe(0); - }); - - it('should return all matching reports and personal details', () => { - // Given a set of options - const options = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}); - // 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}); - - // Then there should be 4 matching personal details - expect(filteredOptions.personalDetails.length).toBe(5); - // Then the first personal detail should match the search text - expect(filteredOptions.personalDetails.at(0)?.login).toBe('natasharomanoff@expensify.com'); - // Then there should be 5 matching recent reports - expect(filteredOptions.recentReports.length).toBe(5); - expect(filteredOptions.recentReports.at(0)?.text).toBe('Captain America'); - expect(filteredOptions.recentReports.at(1)?.text).toBe('Mr Sinister'); - expect(filteredOptions.recentReports.at(2)?.text).toBe('Black Panther'); - }); - - it('should return matching option when searching (getSearchOptions)', () => { - // Given a set of options - const options = getSearchOptions({options: OPTIONS, draftComments: {}}); - // When we call filterAndOrderOptions with a search value that matches a personal detail - const filteredOptions = filterAndOrderOptions(options, 'spider', COUNTRY_CODE); - - // Then one personal detail should be returned - expect(filteredOptions.recentReports.length).toBe(1); - // Then the returned personal detail should match the search text - expect(filteredOptions.recentReports.at(0)?.text).toBe('Spider-Man'); - }); - - 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: {}}); - // When we call filterAndOrderOptions with a search value that matches multiple items - const filteredOptions = filterAndOrderOptions(options, 'fantastic', COUNTRY_CODE); - - // Then only three reports should be returned - expect(filteredOptions.recentReports.length).toBe(3); - // Then the first report should match the search text - expect(filteredOptions.recentReports.at(0)?.text).toBe('Mister Fantastic'); - // Then the second report should match the search text - expect(filteredOptions.recentReports.at(1)?.text).toBe('Mister Fantastic, Invisible Woman'); - - return waitForBatchedUpdates() - .then(() => Onyx.set(ONYXKEYS.PERSONAL_DETAILS_LIST, PERSONAL_DETAILS_WITH_PERIODS)) - .then(() => { - // 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: {}}); - // When we pass the returned options to filterAndOrderOptions with a search value - const filteredResults = filterAndOrderOptions(results, 'barry.allen@expensify.com', COUNTRY_CODE, {sortByReportTypeInSearch: true}); - - // Then only one report should be returned - expect(filteredResults.recentReports.length).toBe(1); - // Then the returned report should match the search text - expect(filteredResults.recentReports.at(0)?.text).toBe('The Flash'); - }); - }); - - it('should filter out duplicated entries by login', () => { - const login = 'brucebanner@expensify.com'; - - // Duplicate personalDetails entries and reassign to OPTIONS - OPTIONS.personalDetails = OPTIONS.personalDetails.flatMap((obj) => [obj, {...obj}]); - - // Given a set of options - const options = getSearchOptions({options: OPTIONS, draftComments: {}, betas: [CONST.BETAS.ALL]}); - // When we call filterAndOrderOptions with a an empty search value - const filteredOptions = filterAndOrderOptions(options, '', COUNTRY_CODE); - const matchingEntries = filteredOptions.personalDetails.filter((detail) => detail.login === login); - - // Then there should be 2 unique login entries - expect(filteredOptions.personalDetails.length).toBe(3); - // Then there should be 1 matching entry - expect(matchingEntries.length).toBe(1); - }); - - it('should order self dm always on top if the search matches with the self dm login', () => { - const searchTerm = 'tonystark@expensify.com'; - 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: {}, betas: [CONST.BETAS.ALL]}); - // When we call filterAndOrderOptions with a search value - const filteredOptions = filterAndOrderOptions(options, searchTerm, COUNTRY_CODE); - - // Then the self dm should be on top. - expect(filteredOptions.recentReports.at(0)?.isSelfDM).toBe(true); - }); - }); - - describe('canCreateOptimisticPersonalDetailOption()', () => { - const VALID_EMAIL = 'valid@email.com'; - const currentUserEmail = 'tonystark@expensify.com'; - - it('should allow to create optimistic personal detail option if email is valid', () => { - const canCreate = canCreateOptimisticPersonalDetailOption({ - searchValue: VALID_EMAIL, - currentUserOption: { - login: currentUserEmail, - } as OptionData, - // Note: in the past this would check for the existence of the email in the personalDetails list, this has changed. - // We expect only filtered lists to be passed to this function, so we don't need to check for the existence of the email in the personalDetails list. - // This is a performance optimization. - personalDetailsOptions: [], - recentReportOptions: [], - }); - - expect(canCreate).toBe(true); - }); - - it('should not allow to create option if email is an email of current user', () => { - // Given a set of arguments with currentUserOption object - // When we call canCreateOptimisticPersonalDetailOption - const canCreate = canCreateOptimisticPersonalDetailOption({ - searchValue: currentUserEmail, - recentReportOptions: [], - personalDetailsOptions: [], - currentUserOption: { - login: currentUserEmail, - } as OptionData, - }); - - // Then the returned value should be false - expect(canCreate).toBe(false); - }); - - it('createOptionList() localization', () => { - renderLocaleContextProvider(); - // Given a set of reports and personal details - // When we call createOptionList and extract the reports - const reports = createOptionList(PERSONAL_DETAILS, REPORTS).reports; - - // Then the returned reports should match the expected values - expect(reports.at(10)?.subtitle).toBe(`Submits to Mister Fantastic`); - - return ( - waitForBatchedUpdates() - // When we set the preferred locale to Spanish - .then(() => Onyx.set(ONYXKEYS.NVP_PREFERRED_LOCALE, CONST.LOCALES.ES)) - .then(() => { - // When we call createOptionList again - const newReports = createOptionList(PERSONAL_DETAILS, REPORTS).reports; - // Then the returned reports should change to Spanish - // cspell:disable-next-line - expect(newReports.at(10)?.subtitle).toBe('Se envía a Mister Fantastic'); - }) - ); - }); - }); - - describe('filterWorkspaceChats()', () => { - it('should return an empty array if there are no expense chats', () => { - // Given an empty array of expense chats and no search terms - // When we call filterWorkspaceChats - const result = filterWorkspaceChats([], []); - - // Then the returned value should be an empty array - expect(result.length).toEqual(0); - }); - - it('should return all expense chats if there are no search terms', () => { - // Given a list of expense chats and no search terms - // When we call filterWorkspaceChats - const result = filterWorkspaceChats(WORKSPACE_CHATS, []); - - // Then the returned value should be the same as the input - expect(result).toEqual(WORKSPACE_CHATS); - // Then the length of the result should be equal to the length of the input - expect(result.length).toEqual(WORKSPACE_CHATS.length); - }); - - it('should filter multiple expense chats by search term', () => { - // Given a list of expense chats and one search term - // When we call filterWorkspaceChats - const result = filterWorkspaceChats(WORKSPACE_CHATS, ['Google']); - - // Then the returned value should should only include the matching expense chats - expect(result.length).toEqual(2); - }); - - it('should filter expense chat by exact name', () => { - // Given a list of expense chats and multiple search terms that reflect the exact name - // When we call filterWorkspaceChats - const result = filterWorkspaceChats(WORKSPACE_CHATS, ['Microsoft', 'Teams', 'Workspace']); - - // Then the returned value should should only include the matching expense chat - expect(result.length).toEqual(1); - }); - - it('should return an empty array if there are no matching expense chats', () => { - // Given a list of expense chats and a search term that does not match any expense chats - // When we call filterWorkspaceChats - const result = filterWorkspaceChats(WORKSPACE_CHATS, ['XYZ']); - - // Then the returned value should be an empty array - expect(result.length).toEqual(0); - }); - }); - - describe('orderWorkspaceOptions()', () => { - it('should put the default workspace on top of the list', () => { - // Given a list of expense chats - // When we call orderWorkspaceOptions - const result = orderWorkspaceOptions(WORKSPACE_CHATS); - - // Then the first item in the list should be the default workspace - expect(result.at(0)?.text).toEqual('Notion Workspace for Marketing'); - }); - }); - - describe('Alternative text', () => { - it("The text should not contain the last actor's name at prefix if the report is archived.", async () => { - renderLocaleContextProvider(); - // When we set the preferred locale to English and create an ADD_COMMENT report action - await Onyx.multiSet({ - [ONYXKEYS.NVP_PREFERRED_LOCALE]: CONST.LOCALES.EN, - [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}10` as const]: { - '1': getFakeAdvancedReportAction(CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT), - }, - }); - // When we call createOptionList - const reports = createOptionList(PERSONAL_DETAILS, REPORTS).reports; - const archivedReport = reports.find((report) => report.reportID === '10'); - - // Then the returned report should contain default archived reason - expect(archivedReport?.lastMessageText).toBe('This chat room has been archived.'); - }); - }); - - describe('filterSelfDMChat()', () => { - const REPORT = { - reportID: '1', - text: 'Google Workspace', - policyID: '11', - isPolicyExpenseChat: true, - }; - const LOGIN = 'johndoe@test.com'; - const ALTERNATE_TEXT = 'John William Doe'; - const SUBTITLE = 'Software Engineer'; - - it('should return the report when there are no search terms', () => { - // Given a report object - // When we call filterSelfDMChat with the report and no search terms - const result = filterSelfDMChat(REPORT, []); - - // Then the returned value should be the same as the input - expect(result?.reportID).toEqual(REPORT.reportID); - }); - - it('should return undefined, when the search term does not match the report', () => { - // Given a report object - // When we call filterSelfDMChat with the report and a search term that does not match the report - const result = filterSelfDMChat(REPORT, ['XYZ']); - - // Then the returned value should be undefined - expect(result).toBeUndefined(); - }); - - it('should filter report by text', () => { - // Given a report object - // When we call filterSelfDMChat with the report and search term that matches the report - const result = filterSelfDMChat(REPORT, ['Google']); - - // Then the returned value should be the same as the input - expect(result?.reportID).toEqual(REPORT.reportID); - }); - - it('should filter report by exact text', () => { - // Given a report object - // When we call filterSelfDMChat with the report and multiple search terms that match the report's exact name - const result = filterSelfDMChat(REPORT, ['Google', 'Workspace']); - - // Then the returned value should be the same as the input - expect(result?.reportID).toEqual(REPORT.reportID); - }); - - it('should filter report by login', () => { - // Given a report object - // When we call filterSelfDMChat with the report and a search term that matches the report's login - const result = filterSelfDMChat({...REPORT, login: LOGIN}, ['john']); - - // Then the returned value should be the same as the input - expect(result?.reportID).toEqual(REPORT.reportID); - }); - - it('should filter report by exact login', () => { - // Given a report object - // When we call filterSelfDMChat with the report and multiple search terms that match the report's exact login - const result = filterSelfDMChat({...REPORT, login: LOGIN}, [LOGIN]); - - // Then the returned value should be the same as the input - expect(result?.reportID).toEqual(REPORT.reportID); - }); - - it('should filter report by alternate text', () => { - // Given a report object - // When we call filterSelfDMChat with the report and a search term that matches the report's alternate text - const result = filterSelfDMChat({...REPORT, alternateText: ALTERNATE_TEXT, isThread: true}, ['William']); - - // Then the returned value should be the same as the input - expect(result?.reportID).toEqual(REPORT.reportID); - }); - - it('should filter report by exact alternate text', () => { - // Given a report object that is a thread - // When we call filterSelfDMChat with the report and multiple search terms that match the report's exact alternate text - const result = filterSelfDMChat({...REPORT, alternateText: ALTERNATE_TEXT, isThread: true}, ['John', 'William', 'Doe']); - - // Then the returned value should be the same as the input - expect(result?.reportID).toEqual(REPORT.reportID); - }); - - it('should filter report by alternate text if it is not a thread', () => { - // Given a report object that is not a thread - // When we call filterSelfDMChat with the report and a search term that matches the report's alternate text - const result = filterSelfDMChat({...REPORT, alternateText: ALTERNATE_TEXT, isThread: false}, ['William']); - - // Then the returned value should be undefined - expect(result?.reportID).toBeUndefined(); - }); - - it('should filter report by subtitle', () => { - // Given a report object - // When we call filterSelfDMChat with the report and a search term that matches the report's subtitle - const result = filterSelfDMChat({...REPORT, subtitle: SUBTITLE}, ['Software']); - - // Then the returned value should be the same as the input - expect(result?.reportID).toEqual(REPORT.reportID); - }); - - it('should filter report by exact subtitle', () => { - // Given a report object - // When we call filterSelfDMChat with the report and multiple search terms that match the report's exact subtitle - const result = filterSelfDMChat({...REPORT, subtitle: SUBTITLE}, ['Software', 'Engineer']); - - // Then the returned value should be the same as the input - expect(result?.reportID).toEqual(REPORT.reportID); - }); - - it('should not filter report by subtitle if it is not an expense chat nor a chat room', () => { - // Given a report object that is not an expense chat nor a chat room - // When we call filterSelfDMChat with the report and a search term that matches the report's subtitle - const result = filterSelfDMChat({...REPORT, subtitle: SUBTITLE, isPolicyExpenseChat: false, isChatRoom: false}, ['Software']); - - // Then the returned value should be undefined - expect(result).toBeUndefined(); - }); - - it('should filter report by subtitle if it is a chat room', () => { - // Given a report object that is not an expense chat but is a chat room - // When we call filterSelfDMChat with the report and a search term that matches the report's subtitle - const result = filterSelfDMChat({...REPORT, subtitle: SUBTITLE, isPolicyExpenseChat: false, isChatRoom: true}, ['Software']); - - // Then the returned value should be the same as the input - expect(result?.reportID).toEqual(REPORT.reportID); - }); - }); - - describe('filterReports()', () => { - it('should match a user with an accented name when searching using non-accented characters', () => { - // Given a report with accented characters in the text property - // cspell:disable-next-line - const reports = [{text: "Álex Timón D'artagnan Zo-e"} as OptionData]; - // Given a search term with non-accented characters - // cspell:disable-next-line - const searchTerms = ['Alex Timon Dartagnan Zoe']; - // When we call filterReports with the report and search terms - const filteredReports = filterReports(reports, searchTerms); - - // Then the returned value should match the search term - expect(filteredReports).toEqual(reports); - }); - }); - - describe('getMostRecentOptions()', () => { - it('returns the most recent options up to the specified limit', () => { - const options: OptionData[] = [ - {reportID: '1', lastVisibleActionCreated: '2022-01-01T10:00:00Z'} as OptionData, - {reportID: '2', lastVisibleActionCreated: '2022-01-01T12:00:00Z'} as OptionData, - {reportID: '3', lastVisibleActionCreated: '2022-01-01T09:00:00Z'} as OptionData, - {reportID: '4', lastVisibleActionCreated: '2022-01-01T13:00:00Z'} as OptionData, - ]; - const comparator = (option: OptionData) => option.lastVisibleActionCreated ?? ''; - const result = optionsOrderBy(options, comparator, 2); - expect(result.length).toBe(2); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - expect(result.at(0)!.reportID).toBe('4'); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - expect(result.at(1)!.reportID).toBe('2'); - }); - - it('returns all options if limit is greater than options length', () => { - const options: OptionData[] = [ - {reportID: '1', lastVisibleActionCreated: '2022-01-01T10:00:00Z'} as OptionData, - {reportID: '2', lastVisibleActionCreated: '2022-01-01T12:00:00Z'} as OptionData, - ]; - const comparator = (option: OptionData) => option.lastVisibleActionCreated ?? ''; - const result = optionsOrderBy(options, comparator, 5); - expect(result.length).toBe(2); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - expect(result.at(0)!.reportID).toBe('2'); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - expect(result.at(1)!.reportID).toBe('1'); - }); - - it('returns empty array if options is empty', () => { - const result = optionsOrderBy([], recentReportComparator, 3); - expect(result).toEqual([]); - }); - - it('applies filter function if provided', () => { - const options: OptionData[] = [ - {reportID: '1', lastVisibleActionCreated: '2022-01-01T10:00:00Z', isPinned: true} as OptionData, - {reportID: '2', lastVisibleActionCreated: '2022-01-01T12:00:00Z', isPinned: false} as OptionData, - {reportID: '3', lastVisibleActionCreated: '2022-01-01T09:00:00Z', isPinned: true} as OptionData, - ]; - const comparator = (option: OptionData) => option.lastVisibleActionCreated ?? ''; - const result = optionsOrderBy(options, comparator, 2, (option) => option.isPinned); - expect(result.length).toBe(2); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - expect(result.at(0)!.reportID).toBe('1'); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - expect(result.at(1)!.reportID).toBe('3'); - }); - - it('handles negative limit by returning empty array', () => { - const options: OptionData[] = [ - {reportID: '1', lastVisibleActionCreated: '2022-01-01T10:00:00Z'} as OptionData, - {reportID: '2', lastVisibleActionCreated: '2022-01-01T12:00:00Z'} as OptionData, - {reportID: '3', lastVisibleActionCreated: '2022-01-01T09:00:00Z'} as OptionData, - ]; - const comparator = (option: OptionData) => option.lastVisibleActionCreated ?? ''; - const result = optionsOrderBy(options, comparator, -1); - expect(result).toEqual([]); - }); - - it('handles negative limit with large absolute value', () => { - const options: OptionData[] = [ - {reportID: '1', lastVisibleActionCreated: '2022-01-01T10:00:00Z'} as OptionData, - {reportID: '2', lastVisibleActionCreated: '2022-01-01T12:00:00Z'} as OptionData, - ]; - const comparator = (option: OptionData) => option.lastVisibleActionCreated ?? ''; - const result = optionsOrderBy(options, comparator, -100); - expect(result).toEqual([]); - }); - - it('handles limit equal to zero', () => { - const options: OptionData[] = [ - {reportID: '1', lastVisibleActionCreated: '2022-01-01T10:00:00Z'} as OptionData, - {reportID: '2', lastVisibleActionCreated: '2022-01-01T12:00:00Z'} as OptionData, - ]; - const comparator = (option: OptionData) => option.lastVisibleActionCreated ?? ''; - const result = optionsOrderBy(options, comparator, 0); - expect(result).toEqual([]); - }); - }); - - describe('sortAlphabetically', () => { - it('should sort options alphabetically by text', () => { - const options: OptionData[] = [{text: 'Banana', reportID: '1'} as OptionData, {text: 'Apple', reportID: '2'} as OptionData, {text: 'Cherry', reportID: '3'} as OptionData]; - const sortedOptions = sortAlphabetically(options, 'text', localeCompare); - expect(sortedOptions.at(0)?.reportID).toBe('2'); - expect(sortedOptions.at(1)?.reportID).toBe('1'); - expect(sortedOptions.at(2)?.reportID).toBe('3'); - }); - - it('should handle empty array', () => { - const sortedOptions = sortAlphabetically([], 'abc', localeCompare); - expect(sortedOptions).toEqual([]); - }); - - it('should handle single option', () => { - const options: OptionData[] = [{text: 'Single', reportID: '1'} as OptionData]; - const sortedOptions = sortAlphabetically(options, 'text', localeCompare); - expect(sortedOptions.length).toBe(1); - expect(sortedOptions.at(0)?.text).toBe('Single'); - }); - }); - describe('getSearchValueForPhoneOrEmail', () => { - it('should return E164 format for valid phone number', () => { - const result = getSearchValueForPhoneOrEmail('+1 (234) 567-8901', 1); - expect(result).toBe('+12345678901'); - }); - - it('should return E164 format for valid international phone number', () => { - const result = getSearchValueForPhoneOrEmail('+44 20 8759 9036', 44); - expect(result).toBe('+442087599036'); - }); - - it('should return lowercase for email address', () => { - const result = getSearchValueForPhoneOrEmail('Test@Example.COM', 1); - expect(result).toBe('test@example.com'); - }); - - it('should handle SMS domain removal for valid phone number', () => { - const result = getSearchValueForPhoneOrEmail('+12345678901@expensify.sms', 1); - expect(result).toBe('+12345678901'); - }); - - it('should return empty string for empty input', () => { - const result = getSearchValueForPhoneOrEmail('', 1); - expect(result).toBe(''); - }); - }); - - describe('createOption', () => { - it('should return alternative text correctly when the last action is report preview action', async () => { - const report = { - chatType: '', - currency: 'USD', - description: '', - errorFields: {}, - hasOutstandingChildRequest: false, - hasOutstandingChildTask: false, - iouReportID: '456', - lastMessageHtml: '', - lastMessageText: '', - participants: { - '1': { - notificationPreference: 'always', - }, - '2': { - notificationPreference: 'always', - }, - }, - reportID: '123', - type: 'chat', - lastActorAccountID: 1, - } as unknown as Report; - - const reportPreviewAction = { - actionName: 'REPORTPREVIEW', - actorAccountID: 1, - childManagerAccountID: 2, - childOwnerAccountID: 1, - childReportID: '456', - childReportName: 'IOU', - created: '2025-10-02 06:50:36.302', - reportActionID: '12345678', - shouldShow: true, - message: [ - { - html: 'Iron Man owes ₫34', - text: 'Iron Man owes ₫34', - type: 'COMMENT', - whisperedTo: [], - }, - ], - } as unknown as ReportAction; - - const iouReport = { - chatReportID: '123', - currency: 'VND', - managerID: 2, - ownerAccountID: 1, - parentReportActionID: '12345678', - parentReportID: '123', - participants: { - '19960856': { - notificationPreference: '', - }, - '20669492': { - notificationPreference: '', - }, - }, - reportID: '456', - reportName: 'IOU', - total: 3400, - } as unknown as Report; - - const iouAction = { - actorAccountID: 1, - message: [ - { - type: 'COMMENT', - html: '₫34 expense', - text: '₫34 expense', - isEdited: false, - whisperedTo: [], - isDeletedParentAction: false, - deleted: '', - reactions: [], - }, - ], - originalMessage: { - IOUReportID: '456', - IOUTransactionID: '123456', - amount: 3400, - comment: '', - currency: 'VND', - participantAccountIDs: [1, 2], - }, - actionName: 'IOU', - reportActionID: '789', - } as unknown as ReportAction; - - const transaction = { - transactionID: '123456', - amount: 3400, - currency: 'VND', - reportID: '3993091505909230', - comment: { - comment: '', - }, - merchant: '(none)', - created: '2025-10-02', - category: '', - taxAmount: 0, - reimbursable: true, - } as unknown as Transaction; - - await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`, report); - await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`, {[reportPreviewAction.reportActionID]: reportPreviewAction}); - await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`, iouReport); - await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`, {[iouAction.reportActionID]: iouAction}); - await Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`, transaction); - await waitForBatchedUpdates(); - - const result = createOption([1, 2], PERSONAL_DETAILS, report, {showChatPreviewLine: true}); - - expect(result.alternateText).toBe('Iron Man owes ₫34'); - }); - }); + // describe('getSearchOptions()', () => { + // 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: {}, betas: [CONST.BETAS.ALL]}); + + // // Then all personal details (including those that have reports) should be returned + // expect(results.personalDetails.length).toBe(10); + + // // Then all of the reports should be shown including the archived rooms, except for the thread report with notificationPreferences hidden. + // expect(results.recentReports.length).toBe(Object.values(OPTIONS.reports).length - 1); + // }); + + // it('should include current user when includeCurrentUser is true for type:chat from suggestions', () => { + // // Given a set of options where the current user is Iron Man (accountID: 2) + // // When we call getSearchOptions with includeCurrentUser set to true + // const results = getSearchOptions({ + // options: OPTIONS, + // draftComments: {}, + // betas: [CONST.BETAS.ALL], + // isUsedInChatFinder: true, + // includeReadOnly: true, + // searchQuery: '', + // maxResults: undefined, + // includeUserToInvite: false, + // includeRecentReports: true, + // includeCurrentUser: true, + // }); + + // // Then the current user should be included in personalDetails + // const currentUserOption = results.personalDetails.find((option) => option.login === 'tonystark@expensify.com'); + // expect(currentUserOption).toBeDefined(); + // expect(currentUserOption?.text).toBe('Iron Man'); + // expect(currentUserOption?.accountID).toBe(2); + + // // Then all personal details including the current user should be returned + // expect(results.personalDetails.length).toBe(11); + // }); + + // it('should exclude current user when includeCurrentUser is false', () => { + // // Given a set of options where the current user is Iron Man (accountID: 2) + // // When we call getSearchOptions with includeCurrentUser set to false (default behavior) + // const results = getSearchOptions({ + // options: OPTIONS, + // draftComments: {}, + // betas: [CONST.BETAS.ALL], + // isUsedInChatFinder: true, + // includeReadOnly: true, + // searchQuery: '', + // maxResults: undefined, + // includeUserToInvite: false, + // includeRecentReports: true, + // }); + + // // Then the current user should not be included in personalDetails + // const currentUserOption = results.personalDetails.find((option) => option.login === 'tonystark@expensify.com'); + // expect(currentUserOption).toBeUndefined(); + + // // Then all personal details except the current user should be returned + // expect(results.personalDetails.length).toBe(10); + // }); + // }); + + // describe('orderOptions()', () => { + // it('should sort options alphabetically and preserves reportID for personal details with existing reports', () => { + // // Given a set of reports and personalDetails + // // When we call getValidOptions() + // let results: Pick = getValidOptions( + // { + // reports: OPTIONS.reports, + // personalDetails: OPTIONS.personalDetails, + // }, + // {}, + // ); + // // When we call orderOptions() + // results = orderOptions(results); + + // // Then all personalDetails except the currently logged in user should be returned + // expect(results.personalDetails.length).toBe(Object.values(OPTIONS.personalDetails).length - 1); + + // const expected = [ + // 'Black Panther', + // 'Black Widow', + // 'Captain America', + // 'Invisible Woman', + // 'Mister Fantastic', + // 'Mr Sinister', + // 'Spider-Man', + // 'The Incredible Hulk', + // 'Thor', + // 'Timothée', + // ]; + // const actual = results.personalDetails?.map((item) => item.text); + + // // Then the results should be sorted alphabetically + // expect(actual).toEqual(expected); + + // const personalDetailWithExistingReport = results.personalDetails.find((personalDetail) => personalDetail.login === 'peterparker@expensify.com'); + // // Then the result which has an existing report should also have the reportID attached + // expect(personalDetailWithExistingReport?.reportID).toBe('2'); + // }); + + // 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: []}, {}); + // // When we call orderOptions() + // results = orderOptions(results); + + // const expected = [ + // 'Black Panther', + // 'Black Widow', + // 'Captain America', + // 'Invisible Woman', + // 'Mister Fantastic', + // 'Mr Sinister', + // 'Spider-Man', + // 'The Incredible Hulk', + // 'Thor', + // 'Timothée', + // ]; + // const actual = results.personalDetails?.map((item) => item.text); + + // // Then the results should be sorted alphabetically + // expect(actual).toEqual(expected); + // }); + // }); + + // describe('getValidOptions()', () => { + // 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: []}, {}); + + // // Then the result should be empty + // expect(results.personalDetails).toEqual([]); + // expect(results.recentReports).toEqual([]); + // expect(results.currentUserOption).toBeUndefined(); + // expect(results.userToInvite).toEqual(null); + // expect(results.workspaceChats).toEqual([]); + // expect(results.selfDMChat).toEqual(undefined); + // }); + + // 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}, {}); + + // // 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); + // // Then the result should include Concierge + // expect(results.recentReports).toEqual(expect.arrayContaining([expect.objectContaining({login: 'concierge@expensify.com'})])); + // }); + + // it('should exclude Concierge when excludedLogins is specified', () => { + // // Given a set of reports and personalDetails that includes Concierge and a config object that excludes Concierge + // // When we call getValidOptions() + // const results = getValidOptions( + // { + // reports: OPTIONS_WITH_CONCIERGE.reports, + // personalDetails: OPTIONS_WITH_CONCIERGE.personalDetails, + // }, + // {}, + // { + // excludeLogins: {[CONST.EMAIL.CONCIERGE]: true}, + // }, + // ); + + // // Then the result should include all personalDetails except the currently logged in user and Concierge + // expect(results.personalDetails.length).toBe(Object.values(OPTIONS_WITH_CONCIERGE.personalDetails).length - 2); + // // Then the result should not include Concierge + // expect(results.personalDetails).not.toEqual(expect.arrayContaining([expect.objectContaining({login: 'concierge@expensify.com'})])); + // }); + + // 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}, + // {}, + // {excludeLogins: {[CONST.EMAIL.CHRONOS]: true}}, + // ); + + // // Then the result should include all personalDetails except the currently logged in user and Chronos + // expect(results.personalDetails.length).toBe(Object.values(OPTIONS_WITH_CHRONOS.personalDetails).length - 2); + // // Then the result should not include Chronos + // expect(results.personalDetails).not.toEqual(expect.arrayContaining([expect.objectContaining({login: 'chronos@expensify.com'})])); + // }); + + // it('should exclude Receipts option from results when excludedLogins is specified', () => { + // // Given a set of reports and personalDetails that includes receipts and a config object that excludes receipts + // // When we call getValidOptions() + // const results = getValidOptions( + // { + // reports: OPTIONS_WITH_RECEIPTS.reports, + // personalDetails: OPTIONS_WITH_RECEIPTS.personalDetails, + // }, + // {}, + // { + // excludeLogins: {[CONST.EMAIL.RECEIPTS]: true}, + // }, + // ); + + // // Then the result should include all personalDetails except the currently logged in user and receipts + // expect(results.personalDetails.length).toBe(Object.values(OPTIONS_WITH_RECEIPTS.personalDetails).length - 2); + // // Then the result should not include receipts + // expect(results.personalDetails).not.toEqual(expect.arrayContaining([expect.objectContaining({login: 'receipts@expensify.com'})])); + // }); + + // it('should include Manager McTest in results by default', () => { + // // Given a set of reports and personalDetails that includes Manager McTest + // // When we call getValidOptions() + // const result = getValidOptions( + // {reports: OPTIONS_WITH_MANAGER_MCTEST.reports, personalDetails: OPTIONS_WITH_MANAGER_MCTEST.personalDetails}, + // {}, + // {includeP2P: true, canShowManagerMcTest: true, betas: [CONST.BETAS.NEWDOT_MANAGER_MCTEST]}, + // ); + + // // Then the result should include all personalDetails except the currently logged in user + // expect(result.personalDetails.length).toBe(Object.values(OPTIONS_WITH_MANAGER_MCTEST.personalDetails).length - 1); + // // Then the result should include Manager McTest + // expect(result.personalDetails).toEqual(expect.arrayContaining([expect.objectContaining({login: CONST.EMAIL.MANAGER_MCTEST})])); + // }); + + // 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}, + // {}, + // {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); + // // Then the result should not include Manager McTest + // expect(result.personalDetails).not.toEqual(expect.arrayContaining([expect.objectContaining({login: CONST.EMAIL.MANAGER_MCTEST})])); + // }); + + // it('should exclude Manager McTest from results if user dismissed the tooltip', () => { + // return waitForBatchedUpdates() + // .then(() => + // // Given that the user has dismissed the tooltip + // Onyx.set(ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING, { + // [CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.SCAN_TEST_TOOLTIP]: { + // timestamp: DateUtils.getDBTime(new Date().valueOf()), + // }, + // }), + // ) + // .then(() => { + // // When we call getValidOptions() + // const optionsWhenUserAlreadySubmittedExpense = getValidOptions( + // {reports: OPTIONS_WITH_MANAGER_MCTEST.reports, personalDetails: OPTIONS_WITH_MANAGER_MCTEST.personalDetails}, + // {}, + // {includeP2P: true, canShowManagerMcTest: true, betas: [CONST.BETAS.NEWDOT_MANAGER_MCTEST]}, + // ); + + // // Then the result should include all personalDetails except the currently logged in user and Manager McTest + // expect(optionsWhenUserAlreadySubmittedExpense.personalDetails.length).toBe(Object.values(OPTIONS_WITH_MANAGER_MCTEST.personalDetails).length - 2); + // // Then the result should not include Manager McTest + // expect(optionsWhenUserAlreadySubmittedExpense.personalDetails).not.toEqual(expect.arrayContaining([expect.objectContaining({login: CONST.EMAIL.MANAGER_MCTEST})])); + // }); + // }); + + // it('should keep admin rooms if specified', () => { + // // Given an admin room report search option + // const adminRoom: SearchOption = { + // item: { + // chatType: 'policyAdmins', + // currency: 'USD', + // errorFields: {}, + // lastActionType: 'CREATED', + // lastReadTime: '2025-03-21 07:25:46.279', + // lastVisibleActionCreated: '2024-12-15 21:13:24.317', + // lastVisibleActionLastModified: '2024-12-15 21:13:24.317', + // ownerAccountID: 0, + // permissions: ['read', 'write'], + // policyID: '52A5ABD88FBBD18F', + // policyName: "David's Playground", + // reportID: '1455140530846319', + // reportName: '#admins', + // type: 'chat', + // writeCapability: 'all', + // }, + // text: '#admins', + // alternateText: "David's Playground", + // allReportErrors: {}, + // subtitle: "David's Playground", + // participantsList: [], + // reportID: '1455140530846319', + // keyForList: '1455140530846319', + // isDefaultRoom: true, + // isChatRoom: true, + // policyID: '52A5ABD88FBBD18F', + // lastMessageText: '', + // lastVisibleActionCreated: '2024-12-15 21:13:24.317', + // notificationPreference: 'hidden', + // }; + // // When we call getValidOptions with includeMultipleParticipantReports set to true + // const results = getValidOptions( + // {reports: [adminRoom], personalDetails: OPTIONS.personalDetails}, + // {}, + // { + // includeMultipleParticipantReports: true, + // }, + // ); + // const adminRoomOption = results.recentReports.find((report) => report.reportID === '1455140530846319'); + + // // Then the result should include the admin room + // expect(adminRoomOption).toBeDefined(); + // }); + + // it('should include brickRoadIndicator if showRBR is true', () => { + // const reportID = '1455140530846319'; + // const workspaceChat: SearchOption = { + // item: { + // chatType: CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT, + // currency: 'USD', + // errorFields: {}, + // lastActionType: 'CREATED', + // lastReadTime: '2025-03-21 07:25:46.279', + // lastVisibleActionCreated: '2024-12-15 21:13:24.317', + // lastVisibleActionLastModified: '2024-12-15 21:13:24.317', + // ownerAccountID: 0, + // permissions: ['read', 'write'], + // participants: {1: {notificationPreference: 'always'}}, + // policyID: '52A5ABD88FBBD18F', + // policyName: "A's Workspace", + // reportID, + // reportName: "A's Workspace chat", + // type: 'chat', + // writeCapability: 'all', + // }, + // text: "A's Workspace chat", + // alternateText: "A's Workspace", + // allReportErrors: {}, + // subtitle: "A's Workspace", + // participantsList: [], + // reportID, + // keyForList: '1455140530846319', + // isDefaultRoom: true, + // isChatRoom: true, + // policyID: '52A5ABD88FBBD18F', + // lastMessageText: '', + // lastVisibleActionCreated: '2024-12-15 21:13:24.317', + // notificationPreference: 'hidden', + // brickRoadIndicator: CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR, + // }; + // const results = getValidOptions( + // {reports: [workspaceChat], personalDetails: []}, + // {}, + // { + // includeMultipleParticipantReports: true, + // showRBR: true, + // }, + // ); + // expect(results.recentReports.at(0)?.brickRoadIndicator).toBe(CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR); + // }); + + // it('should not include brickRoadIndicator if showRBR is false', () => { + // const reportID = '1455140530846319'; + // const workspaceChat: SearchOption = { + // item: { + // chatType: CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT, + // currency: 'USD', + // errorFields: {}, + // lastActionType: 'CREATED', + // lastReadTime: '2025-03-21 07:25:46.279', + // lastVisibleActionCreated: '2024-12-15 21:13:24.317', + // lastVisibleActionLastModified: '2024-12-15 21:13:24.317', + // ownerAccountID: 0, + // permissions: ['read', 'write'], + // participants: {1: {notificationPreference: 'always'}}, + // policyID: '52A5ABD88FBBD18F', + // policyName: "A's Workspace", + // reportID, + // reportName: "A's Workspace chat", + // type: 'chat', + // writeCapability: 'all', + // }, + // text: "A's Workspace chat", + // alternateText: "A's Workspace", + // allReportErrors: {}, + // subtitle: "A's Workspace", + // participantsList: [], + // reportID, + // keyForList: '1455140530846319', + // isDefaultRoom: true, + // isChatRoom: true, + // policyID: '52A5ABD88FBBD18F', + // lastMessageText: '', + // lastVisibleActionCreated: '2024-12-15 21:13:24.317', + // notificationPreference: 'hidden', + // brickRoadIndicator: CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR, + // }; + // const results = getValidOptions( + // {reports: [workspaceChat], personalDetails: []}, + // {}, + // { + // includeMultipleParticipantReports: true, + // showRBR: false, + // }, + // ); + // expect(results.recentReports.at(0)?.brickRoadIndicator).toBe(null); + // }); + // }); + + // describe('getValidOptions() for chat room', () => { + // 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, + // {}, + // { + // includeRecentReports: true, + // includeMultipleParticipantReports: true, + // includeP2P: true, + // includeOwnedWorkspaceChats: true, + // }, + // ); + + // // Then the result should include all reports except the currently logged in user + // expect(results.recentReports.length).toBe(OPTIONS_WITH_WORKSPACE_ROOM.reports.length - 1); + // expect(results.recentReports).toEqual(expect.arrayContaining([expect.objectContaining({reportID: '14'})])); + // }); + // }); + + // describe('getValidOptions() for group Chat', () => { + // 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}, {}); + // const reportLogins = results.recentReports.map((reportOption) => reportOption.login); + // const personalDetailsOverlapWithReports = results.personalDetails.every((personalDetailOption) => reportLogins.includes(personalDetailOption.login)); + + // // Then we should expect all the personalDetails to show except the currently logged in user + // expect(results.personalDetails.length).toBe(Object.values(OPTIONS.personalDetails).length - 1); + // // Then none of our personalDetails should include any of the users with recent reports + // expect(personalDetailsOverlapWithReports).toBe(false); + // }); + + // 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}, {}, {excludeLogins: {'peterparker@expensify.com': true}}); + + // // Then the option should not appear anywhere in either list + // expect(results.recentReports.every((option) => option.login !== 'peterparker@expensify.com')).toBe(true); + // expect(results.personalDetails.every((option) => option.login !== 'peterparker@expensify.com')).toBe(true); + // }); + + // 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}, {}); + + // // 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); + // // Then Concierge should be included in the results + // expect(results.recentReports).toEqual(expect.arrayContaining([expect.objectContaining({login: 'concierge@expensify.com'})])); + // }); + + // it('should exclude Concierge from the results when it is specified in excludedLogins', () => { + // // Given a set of reports and personalDetails that includes Concierge + // // When we call getValidOptions with excludeLogins param + // const results = getValidOptions( + // { + // reports: OPTIONS_WITH_CONCIERGE.reports, + // personalDetails: OPTIONS_WITH_CONCIERGE.personalDetails, + // }, + // {}, + // { + // excludeLogins: {[CONST.EMAIL.CONCIERGE]: true}, + // }, + // ); + + // // Then the result should include all personalDetails except the currently logged in user and Concierge + // expect(results.personalDetails.length).toBe(Object.values(OPTIONS_WITH_CONCIERGE.personalDetails).length - 2); + // // Then none of the results should include Concierge + // expect(results.personalDetails).not.toEqual(expect.arrayContaining([expect.objectContaining({login: 'concierge@expensify.com'})])); + // expect(results.recentReports).not.toEqual(expect.arrayContaining([expect.objectContaining({login: 'concierge@expensify.com'})])); + // }); + + // 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}, + // {}, + // {excludeLogins: {[CONST.EMAIL.CHRONOS]: true}}, + // ); + + // // Then the result should include all personalDetails except the currently logged in user and Chronos + // expect(results.personalDetails.length).toBe(Object.values(OPTIONS_WITH_CHRONOS.personalDetails).length - 2); + // // Then none of the results should include Chronos + // expect(results.personalDetails).not.toEqual(expect.arrayContaining([expect.objectContaining({login: 'chronos@expensify.com'})])); + // expect(results.recentReports).not.toEqual(expect.arrayContaining([expect.objectContaining({login: 'chronos@expensify.com'})])); + // }); + + // it('should exclude Receipts from the results when it is specified in excludedLogins', () => { + // // Given a set of reports and personalDetails that includes receipts + // // When we call getValidOptions() with excludeLogins param + // const results = getValidOptions( + // { + // reports: OPTIONS_WITH_RECEIPTS.reports, + // personalDetails: OPTIONS_WITH_RECEIPTS.personalDetails, + // }, + // {}, + // { + // excludeLogins: {[CONST.EMAIL.RECEIPTS]: true}, + // }, + // ); + + // // Then the result should include all personalDetails except the currently logged in user and receipts + // expect(results.personalDetails.length).toBe(Object.values(OPTIONS_WITH_RECEIPTS.personalDetails).length - 2); + // // Then none of the results should include receipts + // expect(results.personalDetails).not.toEqual(expect.arrayContaining([expect.objectContaining({login: 'receipts@expensify.com'})])); + // expect(results.recentReports).not.toEqual(expect.arrayContaining([expect.objectContaining({login: 'receipts@expensify.com'})])); + // }); + + // it('should limit recent reports when maxRecentReportElements is specified', () => { + // // 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}, {}, {maxRecentReportElements: maxRecentReports}); + + // // Then the recent reports should be limited to the specified number + // expect(results.recentReports.length).toBeLessThanOrEqual(maxRecentReports); + // }); + + // 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}, {}); + // const resultsWithLimit = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}, {maxRecentReportElements: 2}); + + // // Then the results without limit should have more or equal reports + // expect(resultsWithoutLimit.recentReports.length).toBeGreaterThanOrEqual(resultsWithLimit.recentReports.length); + // }); + + // 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}, {}); + // const resultsWithLimit = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}, {maxRecentReportElements: 2}); + + // // Then personalDetails should remain the same regardless of maxRecentReportElements + // expect(resultsWithLimit.personalDetails.length).toBe(resultsWithoutLimit.personalDetails.length); + // }); + + // it('should respect maxRecentReportElements when combined with maxElements', () => { + // // Given a set of reports and personalDetails + // // When we call getValidOptions with both maxElements and maxRecentReportElements + // const maxRecentReports = 3; + // const maxTotalElements = 10; + // const results = getValidOptions( + // {reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, + // {}, + // {maxElements: maxTotalElements, maxRecentReportElements: maxRecentReports}, + // ); + + // // Then recent reports should be limited by maxRecentReportElements + // expect(results.recentReports.length).toBeLessThanOrEqual(maxRecentReports); + // // Then the total number of options (reports + personalDetails) should not exceed maxElements + // expect(results.recentReports.length + results.personalDetails.length).toBeLessThanOrEqual(maxTotalElements); + // }); + // }); + + // describe('getShareDestinationsOptions()', () => { + // it('should exclude archived rooms and hidden threads from share destinations', () => { + // // 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) => { + // const report = option.item; + // const {result: isReportArchived} = renderHook(() => useReportIsArchived(report.reportID)); + // if (canUserPerformWriteAction(report, isReportArchived.current) && canCreateTaskInReport(report) && !isCanceledTaskReport(report)) { + // filtered.push(option); + // } + // return filtered; + // }, []); + + // // When we call getValidOptions for share destination with an empty search value + // const results = getValidOptions( + // {reports: filteredReports, personalDetails: OPTIONS.personalDetails}, + // {}, + // { + // betas: [], + // includeMultipleParticipantReports: true, + // showChatPreviewLine: true, + // forcePolicyNamePreview: true, + // includeThreads: true, + // includeMoneyRequests: true, + // includeTasks: true, + // excludeLogins: {}, + // includeOwnedWorkspaceChats: true, + // includeSelfDM: true, + // searchString: '', + // includeUserToInvite: false, + // }, + // ); + + // // Then all the recent reports should be returned except the archived rooms and the hidden thread + // expect(results.recentReports.length).toBe(Object.values(OPTIONS.reports).length - 2); + // }); + + // it('should include DMS, group chats, and workspace rooms in share destinations', () => { + // // Given a set of filtered current Reports (as we do in the component) with workspace rooms before getting share destination options + // const filteredReportsWithWorkspaceRooms = Object.values(OPTIONS_WITH_WORKSPACE_ROOM.reports).reduce((filtered, option) => { + // const report = option.item; + // const {result: isReportArchived} = renderHook(() => useReportIsArchived(report.reportID)); + // // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + // if (canUserPerformWriteAction(report, isReportArchived.current) || isExpensifyOnlyParticipantInReport(report)) { + // filtered.push(option); + // } + // return filtered; + // }, []); + + // // When we call getValidOptions for share destination with an empty search value + // const results = getValidOptions( + // {reports: filteredReportsWithWorkspaceRooms, personalDetails: OPTIONS.personalDetails}, + // {}, + // { + // betas: [], + // includeMultipleParticipantReports: true, + // showChatPreviewLine: true, + // forcePolicyNamePreview: true, + // includeThreads: true, + // includeMoneyRequests: true, + // includeTasks: true, + // excludeLogins: {}, + // includeOwnedWorkspaceChats: true, + // includeSelfDM: true, + // searchString: '', + // includeUserToInvite: false, + // }, + // ); + + // // Then all recent reports should be returned except the archived rooms and the hidden thread + // expect(results.recentReports.length).toBe(Object.values(OPTIONS_WITH_WORKSPACE_ROOM.reports).length - 2); + // }); + // }); + + // describe('getShareLogOptions()', () => { + // it('should not include read-only report', () => { + // // Given a list of 11 report options with reportID of 10 is archived + // // When we call getShareLogOptions + // const results = getShareLogOptions(OPTIONS, {}, []); + + // // Then the report with reportID of 10 should not be included on the list + // expect(results.recentReports.length).toBe(10); + // expect(results.recentReports.find((report) => report.reportID === '10')).toBeUndefined(); + // }); + // }); + + // describe('getMemberInviteOptions()', () => { + // it('should sort personal details alphabetically', () => { + // // Given a set of personalDetails + // // When we call getMemberInviteOptions + // const results = getMemberInviteOptions(OPTIONS.personalDetails, []); + + // // 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'); + // }); + // }); + + // describe('getLastActorDisplayName()', () => { + // it('should return correct display name', () => { + // renderLocaleContextProvider(); + // // Given two different personal details + // // When we call getLastActorDisplayName + // const result1 = getLastActorDisplayName(PERSONAL_DETAILS['2']); + // const result2 = getLastActorDisplayName(PERSONAL_DETAILS['3']); + + // // We should expect the display names to be the same as the personal details + // expect(result1).toBe('You'); + // expect(result2).toBe('Spider-Man'); + // }); + // }); + + // describe('formatMemberForList()', () => { + // it('should format members correctly', () => { + // // Given a set of personal details + // // When we call formatMemberForList + // const formattedMembers = Object.values(PERSONAL_DETAILS).map((personalDetail) => formatMemberForList(personalDetail)); + + // // Then the formatted members' order should be the same as the original PERSONAL_DETAILS array + // expect(formattedMembers.at(0)?.text).toBe('Mister Fantastic'); + // expect(formattedMembers.at(1)?.text).toBe('Iron Man'); + // expect(formattedMembers.at(2)?.text).toBe('Spider-Man'); + + // // Then only the first item should be selected + // expect(formattedMembers.at(0)?.isSelected).toBe(true); + // // Then all remaining items should be unselected + // expect(formattedMembers.slice(1).every((personalDetail) => !personalDetail.isSelected)).toBe(true); + // // Then all items should be enabled + // expect(formattedMembers.every((personalDetail) => !personalDetail.isDisabled)).toBe(true); + // }); + // }); + + // describe('filterAndOrderOptions()', () => { + // 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: {}, betas: [CONST.BETAS.ALL]}); + // // When we pass the returned options to filterAndOrderOptions with an empty search value + // const filteredOptions = filterAndOrderOptions(options, '', COUNTRY_CODE); + + // // Then all options should be returned + // expect(filteredOptions.recentReports.length + filteredOptions.personalDetails.length).toBe(14); + // }); + + // it('should return filtered options in correct order', () => { + // const searchText = 'man'; + // // Given a set of options + // // When we call getSearchOptions with all betas + // const options = getSearchOptions({options: OPTIONS, draftComments: {}, 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}); + + // // Then we expect all options to be part of the recentReports list and reports should be first: + // expect(filteredOptions.personalDetails.length).toBe(0); + // // Then returned reports should match the search text + // expect(filteredOptions.recentReports.length).toBe(4); + // // Then the returned reports should be ordered by most recent action (and other criteria such as whether they are archived) + // expect(filteredOptions.recentReports.at(0)?.text).toBe('Invisible Woman'); // '2022-11-22 03:26:02.019' + // expect(filteredOptions.recentReports.at(1)?.text).toBe('Spider-Man'); // '2022-11-22 03:26:02.016' + // expect(filteredOptions.recentReports.at(2)?.text).toBe('Black Widow'); // This is a personal detail, which has no lastVisibleActionCreated, but matches the login + // expect(filteredOptions.recentReports.at(3)?.text).toBe('Mister Fantastic, Invisible Woman'); // This again is a report with '2022-11-22 03:26:02.015' + // }); + + // it('should filter users by email', () => { + // const searchText = 'mistersinister@marauders.com'; + // // Given a set of options + // // When we call getSearchOptions with all betas + // const options = getSearchOptions({options: OPTIONS, draftComments: {}, betas: [CONST.BETAS.ALL]}); + // // When we pass the returned options to filterAndOrderOptions with a search value + // const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE); + + // // Then only one report should be returned + // expect(filteredOptions.recentReports.length).toBe(1); + // // Then the returned report should match the search text + // expect(filteredOptions.recentReports.at(0)?.text).toBe('Mr Sinister'); + // }); + + // it('should find archived chats', () => { + // const searchText = 'Archived'; + // // Given a set of options + // // When we call getSearchOptions with all betas + // const options = getSearchOptions({options: OPTIONS, draftComments: {}, betas: [CONST.BETAS.ALL]}); + // // When we pass the returned options to filterAndOrderOptions with a search value + // const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE); + + // // Then only one report should be returned + // expect(filteredOptions.recentReports.length).toBe(1); + // // Then the returned report should match the search text + // expect(!!filteredOptions.recentReports.at(0)?.private_isArchived).toBe(true); + // }); + + // it('should filter options by email if dot is skipped in the email', () => { + // // cspell:disable-next-line + // const searchText = 'barryallen'; + // // 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: {}, 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}); + + // // Then only one report should be returned + // expect(filteredOptions.recentReports.length).toBe(1); + // // Then the returned report should match the search text + // expect(filteredOptions.recentReports.at(0)?.login).toBe('barry.allen@expensify.com'); + // }); + + // it('should include workspace rooms in the search results', () => { + // 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: {}, betas: [CONST.BETAS.ALL]}); + // // When we pass the returned options to filterAndOrderOptions with a search value + // const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE); + + // // Then only one report should be returned + // expect(filteredOptions.recentReports.length).toBe(1); + // // Then the returned report should match the search text + // expect(filteredOptions.recentReports.at(0)?.subtitle).toBe('Avengers Room'); + // }); + + // 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: {}, betas: [CONST.BETAS.ALL]}); + // // When we pass the returned options to filterAndOrderOptions with a search value + // const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE); + + // // Then only one report should be returned + // expect(filteredOptions.recentReports.length).toBe(1); + // // Then the returned report should match the search text + // expect(filteredOptions.recentReports.at(0)?.login).toBe(searchText); + // }); + + // it('should prioritize options with matching display name over chat rooms', () => { + // const searchText = 'spider'; + // // 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: {}, betas: [CONST.BETAS.ALL]}); + // // When we pass the returned options to filterAndOrderOptions with a search value + // const filterOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE); + + // // Then only two reports should be returned + // expect(filterOptions.recentReports.length).toBe(2); + // // Then the second report should match the search text + // expect(filterOptions.recentReports.at(1)?.isChatRoom).toBe(true); + // }); + + // it('should put the item with latest lastVisibleActionCreated on top when search value match multiple items', () => { + // renderLocaleContextProvider(); + // const searchText = 'fantastic'; + // // Given a set of options + // const options = getSearchOptions({options: OPTIONS, draftComments: {}}); + // // When we call filterAndOrderOptions with a search value + // const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE); + + // // Then only three reports should be returned + // expect(filteredOptions.recentReports.length).toBe(3); + // // Then the first report should match the search text + // expect(filteredOptions.recentReports.at(0)?.text).toBe('Mister Fantastic'); + // // Then the second report should match the search text + // expect(filteredOptions.recentReports.at(1)?.text).toBe('Mister Fantastic, Invisible Woman'); + // }); + + // 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: {}}); + // // When we call filterAndOrderOptions with a search value + // const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE); + + // // Then the user to invite should be returned + // expect(filteredOptions.userToInvite?.login).toBe(searchText); + // }); + + // 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}, {}, {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}); + + // // Then no personal details should be returned + // expect(filterOptions.recentReports.length).toBe(0); + // }); + + // 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: {}}); + // // When we call filterAndOrderOptions with a search value and excludeLogins + // const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE, {excludeLogins: CONST.EXPENSIFY_EMAILS_OBJECT}); + + // // Then the user to invite should be returned + // expect(filteredOptions.userToInvite?.login).toBe(searchText); + // }); + + // 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: {}}); + // // When we call filterAndOrderOptions with a search value and maxRecentReportsToShow set to 2 + // const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE, {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}); + + // // Then no reports should be returned + // expect(limitToZeroOptions.recentReports.length).toBe(0); + // }); + + // 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: {}, betas: [CONST.BETAS.ALL]}); + // // When we call filterAndOrderOptions with a search value + // const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE); + + // // Then there should be one matching result + // expect(filteredOptions.personalDetails.length).toBe(1); + // // Then the user to invite should be null + // 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, []); + // // When we call filterAndOrderOptions with a search value that does not match any personal details + // const filteredOptions = filterAndOrderOptions(options, 'magneto', COUNTRY_CODE); + + // // 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, []); + // // When we call filterAndOrderOptions with a search value that matches an email + // const filteredOptions = filterAndOrderOptions(options, 'peterparker@expensify.com', COUNTRY_CODE); + + // // 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) => { + // const report = option.item; + // if (canUserPerformWriteAction(report, false) && canCreateTaskInReport(report) && !isCanceledTaskReport(report)) { + // filtered.push(option); + // } + // return filtered; + // }, []); + // // When we call getValidOptions for share destination with the filteredReports + // const options = getValidOptions( + // {reports: filteredReports, personalDetails: OPTIONS.personalDetails}, + // {}, + // { + // betas: [], + // includeMultipleParticipantReports: true, + // showChatPreviewLine: true, + // forcePolicyNamePreview: true, + // includeThreads: true, + // includeMoneyRequests: true, + // includeTasks: true, + // excludeLogins: {}, + // includeOwnedWorkspaceChats: true, + // includeSelfDM: true, + // searchString: '', + // 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); + + // // Then no recent reports should be returned + // expect(filteredOptions.recentReports.length).toBe(0); + // }); + + // it('should return a workspace room when we search for a workspace room(getShareDestinationsOptions)', () => { + // // Given a set of filtered current Reports (as we do in the component) before getting share destination options + // const filteredReportsWithWorkspaceRooms = Object.values(OPTIONS_WITH_WORKSPACE_ROOM.reports).reduce((filtered, option) => { + // const report = option.item; + // // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + // if (canUserPerformWriteAction(report, false) || isExpensifyOnlyParticipantInReport(report)) { + // filtered.push(option); + // } + // return filtered; + // }, []); + + // // When we call getValidOptions for share destination with the filteredReports + // const options = getValidOptions( + // {reports: filteredReportsWithWorkspaceRooms, personalDetails: OPTIONS.personalDetails}, + // {}, + // { + // betas: [], + // includeMultipleParticipantReports: true, + // showChatPreviewLine: true, + // forcePolicyNamePreview: true, + // includeThreads: true, + // includeMoneyRequests: true, + // includeTasks: true, + // excludeLogins: {}, + // includeOwnedWorkspaceChats: true, + // includeSelfDM: true, + // searchString: '', + // 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); + + // // Then one recent report should be returned + // expect(filteredOptions.recentReports.length).toBe(1); + // }); + + // it('should not show any results if searching for a non-existing workspace room(getShareDestinationOptions)', () => { + // // Given a set of filtered current Reports (as we do in the component) before getting share destination options + // const filteredReportsWithWorkspaceRooms = Object.values(OPTIONS_WITH_WORKSPACE_ROOM.reports).reduce((filtered, option) => { + // const report = option.item; + // // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + // if (canUserPerformWriteAction(report, false) || isExpensifyOnlyParticipantInReport(report)) { + // filtered.push(option); + // } + // return filtered; + // }, []); + + // // When we call getValidOptions for share destination with the filteredReports + // const options = getValidOptions( + // {reports: filteredReportsWithWorkspaceRooms, personalDetails: OPTIONS.personalDetails}, + // {}, + // { + // betas: [], + // includeMultipleParticipantReports: true, + // showChatPreviewLine: true, + // forcePolicyNamePreview: true, + // includeThreads: true, + // includeMoneyRequests: true, + // includeTasks: true, + // excludeLogins: {}, + // includeOwnedWorkspaceChats: true, + // includeSelfDM: true, + // searchString: '', + // 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); + + // // Then no recent reports should be returned + // expect(filteredOptions.recentReports.length).toBe(0); + // }); + + // 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}, {}); + // // When we call filterAndOrderOptions with a search value that matches a personal detail with no existing report + // const filteredOptions = filterAndOrderOptions(options, 'hulk', COUNTRY_CODE); + + // // Then no recent reports should be returned + // expect(filteredOptions.recentReports.length).toBe(0); + // // 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)?.login).toBe('brucebanner@expensify.com'); + // }); + + // it('should not return any options or user to invite if there are no search results and the string does not match a potential email or phone', () => { + // // Given a set of options + // const options = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}); + // // 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); + + // // Then no recent reports or personal details should be returned + // expect(filteredOptions.recentReports.length).toBe(0); + // expect(filteredOptions.personalDetails.length).toBe(0); + // // Then no user to invite should be returned + // expect(filteredOptions.userToInvite).toBe(null); + // }); + + // 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}, {}); + // // 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); + + // // Then no recent reports or personal details should be returned + // expect(filteredOptions.recentReports.length).toBe(0); + // expect(filteredOptions.personalDetails.length).toBe(0); + // // Then an user to invite should be returned + // expect(filteredOptions.userToInvite).not.toBe(null); + // }); + + // 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}, {}); + // // 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); + + // // Then no recent reports should be returned + // expect(filteredOptions.recentReports.length).toBe(0); + // // Then one user to invite should be returned + // expect(filteredOptions.userToInvite).not.toBe(null); + // }); + + // 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}, {}); + // // When we call filterAndOrderOptions with a search value without accent mark + // const filteredOptions = filterAndOrderOptions(options, 'Timothee', COUNTRY_CODE); + + // // Then one personalDetails with accent mark should be returned + // expect(filteredOptions.personalDetails.length).toBe(1); + // }); + + // 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}, {}); + // // 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); + + // // Then no recent reports or personal details should be returned + // expect(filteredOptions.recentReports.length).toBe(0); + // expect(filteredOptions.personalDetails.length).toBe(0); + // // Then one user to invite should be returned + // expect(filteredOptions.userToInvite).not.toBe(null); + // // Then the user to invite should match the search value + // expect(filteredOptions.userToInvite?.login).toBe('+15005550006'); + // }); + + // 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}, {}); + // // 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); + + // // Then no recent reports or personal details should be returned + // expect(filteredOptions.recentReports.length).toBe(0); + // expect(filteredOptions.personalDetails.length).toBe(0); + // // Then one user to invite should be returned + // expect(filteredOptions.userToInvite).not.toBe(null); + // // Then the user to invite should match the search value + // expect(filteredOptions.userToInvite?.login).toBe('+15005550006'); + // }); + + // 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}, {}); + // // 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); + + // // Then no recent reports or personal details should be returned + // expect(filteredOptions.recentReports.length).toBe(0); + // expect(filteredOptions.personalDetails.length).toBe(0); + // // Then one user to invite should be returned + // expect(filteredOptions.userToInvite).not.toBe(null); + // // Then the user to invite should match the search value + // expect(filteredOptions.userToInvite?.login).toBe('+18003243233'); + // }); + + // 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}, {}); + // // When we call filterAndOrderOptions with a search value that does not match any personal details or reports + // const filteredOptions = filterAndOrderOptions(options, '998243aaaa', COUNTRY_CODE); + + // // Then no recent reports or personal details should be returned + // expect(filteredOptions.recentReports.length).toBe(0); + // expect(filteredOptions.personalDetails.length).toBe(0); + // // Then no user to invite should be returned + // expect(filteredOptions.userToInvite).toBe(null); + // }); + + // 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}, {}); + // // When we call filterAndOrderOptions with a search value that does not match any personal details + // const filteredOptions = filterAndOrderOptions(options, 'magneto', COUNTRY_CODE); + + // // Then no personal details should be returned + // expect(filteredOptions.personalDetails.length).toBe(0); + // }); + + // 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}, {}); + // // When we call filterAndOrderOptions with a search value that matches an email + // const filteredOptions = filterAndOrderOptions(options, 'peterparker@expensify.com', COUNTRY_CODE, {sortByReportTypeInSearch: true}); + + // // Then one recent report should be returned + // expect(filteredOptions.recentReports.length).toBe(1); + // // Then the returned recent report should match the search text + // expect(filteredOptions.recentReports.at(0)?.text).toBe('Spider-Man'); + // // Then no personal details should be returned + // expect(filteredOptions.personalDetails.length).toBe(0); + // }); + + // it('should return all matching reports and personal details', () => { + // // Given a set of options + // const options = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}); + // // 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}); + + // // Then there should be 4 matching personal details + // expect(filteredOptions.personalDetails.length).toBe(5); + // // Then the first personal detail should match the search text + // expect(filteredOptions.personalDetails.at(0)?.login).toBe('natasharomanoff@expensify.com'); + // // Then there should be 5 matching recent reports + // expect(filteredOptions.recentReports.length).toBe(5); + // expect(filteredOptions.recentReports.at(0)?.text).toBe('Captain America'); + // expect(filteredOptions.recentReports.at(1)?.text).toBe('Mr Sinister'); + // expect(filteredOptions.recentReports.at(2)?.text).toBe('Black Panther'); + // }); + + // it('should return matching option when searching (getSearchOptions)', () => { + // // Given a set of options + // const options = getSearchOptions({options: OPTIONS, draftComments: {}}); + // // When we call filterAndOrderOptions with a search value that matches a personal detail + // const filteredOptions = filterAndOrderOptions(options, 'spider', COUNTRY_CODE); + + // // Then one personal detail should be returned + // expect(filteredOptions.recentReports.length).toBe(1); + // // Then the returned personal detail should match the search text + // expect(filteredOptions.recentReports.at(0)?.text).toBe('Spider-Man'); + // }); + + // 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: {}}); + // // When we call filterAndOrderOptions with a search value that matches multiple items + // const filteredOptions = filterAndOrderOptions(options, 'fantastic', COUNTRY_CODE); + + // // Then only three reports should be returned + // expect(filteredOptions.recentReports.length).toBe(3); + // // Then the first report should match the search text + // expect(filteredOptions.recentReports.at(0)?.text).toBe('Mister Fantastic'); + // // Then the second report should match the search text + // expect(filteredOptions.recentReports.at(1)?.text).toBe('Mister Fantastic, Invisible Woman'); + + // return waitForBatchedUpdates() + // .then(() => Onyx.set(ONYXKEYS.PERSONAL_DETAILS_LIST, PERSONAL_DETAILS_WITH_PERIODS)) + // .then(() => { + // // 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: {}}); + // // When we pass the returned options to filterAndOrderOptions with a search value + // const filteredResults = filterAndOrderOptions(results, 'barry.allen@expensify.com', COUNTRY_CODE, {sortByReportTypeInSearch: true}); + + // // Then only one report should be returned + // expect(filteredResults.recentReports.length).toBe(1); + // // Then the returned report should match the search text + // expect(filteredResults.recentReports.at(0)?.text).toBe('The Flash'); + // }); + // }); + + // it('should filter out duplicated entries by login', () => { + // const login = 'brucebanner@expensify.com'; + + // // Duplicate personalDetails entries and reassign to OPTIONS + // OPTIONS.personalDetails = OPTIONS.personalDetails.flatMap((obj) => [obj, {...obj}]); + + // // Given a set of options + // const options = getSearchOptions({options: OPTIONS, draftComments: {}, betas: [CONST.BETAS.ALL]}); + // // When we call filterAndOrderOptions with a an empty search value + // const filteredOptions = filterAndOrderOptions(options, '', COUNTRY_CODE); + // const matchingEntries = filteredOptions.personalDetails.filter((detail) => detail.login === login); + + // // Then there should be 2 unique login entries + // expect(filteredOptions.personalDetails.length).toBe(3); + // // Then there should be 1 matching entry + // expect(matchingEntries.length).toBe(1); + // }); + + // it('should order self dm always on top if the search matches with the self dm login', () => { + // const searchTerm = 'tonystark@expensify.com'; + // 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: {}, betas: [CONST.BETAS.ALL]}); + // // When we call filterAndOrderOptions with a search value + // const filteredOptions = filterAndOrderOptions(options, searchTerm, COUNTRY_CODE); + + // // Then the self dm should be on top. + // expect(filteredOptions.recentReports.at(0)?.isSelfDM).toBe(true); + // }); + // }); + + // describe('canCreateOptimisticPersonalDetailOption()', () => { + // const VALID_EMAIL = 'valid@email.com'; + // const currentUserEmail = 'tonystark@expensify.com'; + + // it('should allow to create optimistic personal detail option if email is valid', () => { + // const canCreate = canCreateOptimisticPersonalDetailOption({ + // searchValue: VALID_EMAIL, + // currentUserOption: { + // login: currentUserEmail, + // } as OptionData, + // // Note: in the past this would check for the existence of the email in the personalDetails list, this has changed. + // // We expect only filtered lists to be passed to this function, so we don't need to check for the existence of the email in the personalDetails list. + // // This is a performance optimization. + // personalDetailsOptions: [], + // recentReportOptions: [], + // }); + + // expect(canCreate).toBe(true); + // }); + + // it('should not allow to create option if email is an email of current user', () => { + // // Given a set of arguments with currentUserOption object + // // When we call canCreateOptimisticPersonalDetailOption + // const canCreate = canCreateOptimisticPersonalDetailOption({ + // searchValue: currentUserEmail, + // recentReportOptions: [], + // personalDetailsOptions: [], + // currentUserOption: { + // login: currentUserEmail, + // } as OptionData, + // }); + + // // Then the returned value should be false + // expect(canCreate).toBe(false); + // }); + + // it('createOptionList() localization', () => { + // renderLocaleContextProvider(); + // // Given a set of reports and personal details + // // When we call createOptionList and extract the reports + // const reports = createOptionList(PERSONAL_DETAILS, REPORTS).reports; + + // // Then the returned reports should match the expected values + // expect(reports.at(10)?.subtitle).toBe(`Submits to Mister Fantastic`); + + // return ( + // waitForBatchedUpdates() + // // When we set the preferred locale to Spanish + // .then(() => Onyx.set(ONYXKEYS.NVP_PREFERRED_LOCALE, CONST.LOCALES.ES)) + // .then(() => { + // // When we call createOptionList again + // const newReports = createOptionList(PERSONAL_DETAILS, REPORTS).reports; + // // Then the returned reports should change to Spanish + // // cspell:disable-next-line + // expect(newReports.at(10)?.subtitle).toBe('Se envía a Mister Fantastic'); + // }) + // ); + // }); + // }); + + // describe('filterWorkspaceChats()', () => { + // it('should return an empty array if there are no expense chats', () => { + // // Given an empty array of expense chats and no search terms + // // When we call filterWorkspaceChats + // const result = filterWorkspaceChats([], []); + + // // Then the returned value should be an empty array + // expect(result.length).toEqual(0); + // }); + + // it('should return all expense chats if there are no search terms', () => { + // // Given a list of expense chats and no search terms + // // When we call filterWorkspaceChats + // const result = filterWorkspaceChats(WORKSPACE_CHATS, []); + + // // Then the returned value should be the same as the input + // expect(result).toEqual(WORKSPACE_CHATS); + // // Then the length of the result should be equal to the length of the input + // expect(result.length).toEqual(WORKSPACE_CHATS.length); + // }); + + // it('should filter multiple expense chats by search term', () => { + // // Given a list of expense chats and one search term + // // When we call filterWorkspaceChats + // const result = filterWorkspaceChats(WORKSPACE_CHATS, ['Google']); + + // // Then the returned value should should only include the matching expense chats + // expect(result.length).toEqual(2); + // }); + + // it('should filter expense chat by exact name', () => { + // // Given a list of expense chats and multiple search terms that reflect the exact name + // // When we call filterWorkspaceChats + // const result = filterWorkspaceChats(WORKSPACE_CHATS, ['Microsoft', 'Teams', 'Workspace']); + + // // Then the returned value should should only include the matching expense chat + // expect(result.length).toEqual(1); + // }); + + // it('should return an empty array if there are no matching expense chats', () => { + // // Given a list of expense chats and a search term that does not match any expense chats + // // When we call filterWorkspaceChats + // const result = filterWorkspaceChats(WORKSPACE_CHATS, ['XYZ']); + + // // Then the returned value should be an empty array + // expect(result.length).toEqual(0); + // }); + // }); + + // describe('orderWorkspaceOptions()', () => { + // it('should put the default workspace on top of the list', () => { + // // Given a list of expense chats + // // When we call orderWorkspaceOptions + // const result = orderWorkspaceOptions(WORKSPACE_CHATS); + + // // Then the first item in the list should be the default workspace + // expect(result.at(0)?.text).toEqual('Notion Workspace for Marketing'); + // }); + // }); + + // describe('Alternative text', () => { + // it("The text should not contain the last actor's name at prefix if the report is archived.", async () => { + // renderLocaleContextProvider(); + // // When we set the preferred locale to English and create an ADD_COMMENT report action + // await Onyx.multiSet({ + // [ONYXKEYS.NVP_PREFERRED_LOCALE]: CONST.LOCALES.EN, + // [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}10` as const]: { + // '1': getFakeAdvancedReportAction(CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT), + // }, + // }); + // // When we call createOptionList + // const reports = createOptionList(PERSONAL_DETAILS, REPORTS).reports; + // const archivedReport = reports.find((report) => report.reportID === '10'); + + // // Then the returned report should contain default archived reason + // expect(archivedReport?.lastMessageText).toBe('This chat room has been archived.'); + // }); + // }); + + // describe('filterSelfDMChat()', () => { + // const REPORT = { + // reportID: '1', + // text: 'Google Workspace', + // policyID: '11', + // isPolicyExpenseChat: true, + // }; + // const LOGIN = 'johndoe@test.com'; + // const ALTERNATE_TEXT = 'John William Doe'; + // const SUBTITLE = 'Software Engineer'; + + // it('should return the report when there are no search terms', () => { + // // Given a report object + // // When we call filterSelfDMChat with the report and no search terms + // const result = filterSelfDMChat(REPORT, []); + + // // Then the returned value should be the same as the input + // expect(result?.reportID).toEqual(REPORT.reportID); + // }); + + // it('should return undefined, when the search term does not match the report', () => { + // // Given a report object + // // When we call filterSelfDMChat with the report and a search term that does not match the report + // const result = filterSelfDMChat(REPORT, ['XYZ']); + + // // Then the returned value should be undefined + // expect(result).toBeUndefined(); + // }); + + // it('should filter report by text', () => { + // // Given a report object + // // When we call filterSelfDMChat with the report and search term that matches the report + // const result = filterSelfDMChat(REPORT, ['Google']); + + // // Then the returned value should be the same as the input + // expect(result?.reportID).toEqual(REPORT.reportID); + // }); + + // it('should filter report by exact text', () => { + // // Given a report object + // // When we call filterSelfDMChat with the report and multiple search terms that match the report's exact name + // const result = filterSelfDMChat(REPORT, ['Google', 'Workspace']); + + // // Then the returned value should be the same as the input + // expect(result?.reportID).toEqual(REPORT.reportID); + // }); + + // it('should filter report by login', () => { + // // Given a report object + // // When we call filterSelfDMChat with the report and a search term that matches the report's login + // const result = filterSelfDMChat({...REPORT, login: LOGIN}, ['john']); + + // // Then the returned value should be the same as the input + // expect(result?.reportID).toEqual(REPORT.reportID); + // }); + + // it('should filter report by exact login', () => { + // // Given a report object + // // When we call filterSelfDMChat with the report and multiple search terms that match the report's exact login + // const result = filterSelfDMChat({...REPORT, login: LOGIN}, [LOGIN]); + + // // Then the returned value should be the same as the input + // expect(result?.reportID).toEqual(REPORT.reportID); + // }); + + // it('should filter report by alternate text', () => { + // // Given a report object + // // When we call filterSelfDMChat with the report and a search term that matches the report's alternate text + // const result = filterSelfDMChat({...REPORT, alternateText: ALTERNATE_TEXT, isThread: true}, ['William']); + + // // Then the returned value should be the same as the input + // expect(result?.reportID).toEqual(REPORT.reportID); + // }); + + // it('should filter report by exact alternate text', () => { + // // Given a report object that is a thread + // // When we call filterSelfDMChat with the report and multiple search terms that match the report's exact alternate text + // const result = filterSelfDMChat({...REPORT, alternateText: ALTERNATE_TEXT, isThread: true}, ['John', 'William', 'Doe']); + + // // Then the returned value should be the same as the input + // expect(result?.reportID).toEqual(REPORT.reportID); + // }); + + // it('should filter report by alternate text if it is not a thread', () => { + // // Given a report object that is not a thread + // // When we call filterSelfDMChat with the report and a search term that matches the report's alternate text + // const result = filterSelfDMChat({...REPORT, alternateText: ALTERNATE_TEXT, isThread: false}, ['William']); + + // // Then the returned value should be undefined + // expect(result?.reportID).toBeUndefined(); + // }); + + // it('should filter report by subtitle', () => { + // // Given a report object + // // When we call filterSelfDMChat with the report and a search term that matches the report's subtitle + // const result = filterSelfDMChat({...REPORT, subtitle: SUBTITLE}, ['Software']); + + // // Then the returned value should be the same as the input + // expect(result?.reportID).toEqual(REPORT.reportID); + // }); + + // it('should filter report by exact subtitle', () => { + // // Given a report object + // // When we call filterSelfDMChat with the report and multiple search terms that match the report's exact subtitle + // const result = filterSelfDMChat({...REPORT, subtitle: SUBTITLE}, ['Software', 'Engineer']); + + // // Then the returned value should be the same as the input + // expect(result?.reportID).toEqual(REPORT.reportID); + // }); + + // it('should not filter report by subtitle if it is not an expense chat nor a chat room', () => { + // // Given a report object that is not an expense chat nor a chat room + // // When we call filterSelfDMChat with the report and a search term that matches the report's subtitle + // const result = filterSelfDMChat({...REPORT, subtitle: SUBTITLE, isPolicyExpenseChat: false, isChatRoom: false}, ['Software']); + + // // Then the returned value should be undefined + // expect(result).toBeUndefined(); + // }); + + // it('should filter report by subtitle if it is a chat room', () => { + // // Given a report object that is not an expense chat but is a chat room + // // When we call filterSelfDMChat with the report and a search term that matches the report's subtitle + // const result = filterSelfDMChat({...REPORT, subtitle: SUBTITLE, isPolicyExpenseChat: false, isChatRoom: true}, ['Software']); + + // // Then the returned value should be the same as the input + // expect(result?.reportID).toEqual(REPORT.reportID); + // }); + // }); + + // describe('filterReports()', () => { + // it('should match a user with an accented name when searching using non-accented characters', () => { + // // Given a report with accented characters in the text property + // // cspell:disable-next-line + // const reports = [{text: "Álex Timón D'artagnan Zo-e"} as OptionData]; + // // Given a search term with non-accented characters + // // cspell:disable-next-line + // const searchTerms = ['Alex Timon Dartagnan Zoe']; + // // When we call filterReports with the report and search terms + // const filteredReports = filterReports(reports, searchTerms); + + // // Then the returned value should match the search term + // expect(filteredReports).toEqual(reports); + // }); + // }); + + // describe('getMostRecentOptions()', () => { + // it('returns the most recent options up to the specified limit', () => { + // const options: OptionData[] = [ + // {reportID: '1', lastVisibleActionCreated: '2022-01-01T10:00:00Z'} as OptionData, + // {reportID: '2', lastVisibleActionCreated: '2022-01-01T12:00:00Z'} as OptionData, + // {reportID: '3', lastVisibleActionCreated: '2022-01-01T09:00:00Z'} as OptionData, + // {reportID: '4', lastVisibleActionCreated: '2022-01-01T13:00:00Z'} as OptionData, + // ]; + // const comparator = (option: OptionData) => option.lastVisibleActionCreated ?? ''; + // const result = optionsOrderBy(options, comparator, 2); + // expect(result.length).toBe(2); + // // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + // expect(result.at(0)!.reportID).toBe('4'); + // // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + // expect(result.at(1)!.reportID).toBe('2'); + // }); + + // it('returns all options if limit is greater than options length', () => { + // const options: OptionData[] = [ + // {reportID: '1', lastVisibleActionCreated: '2022-01-01T10:00:00Z'} as OptionData, + // {reportID: '2', lastVisibleActionCreated: '2022-01-01T12:00:00Z'} as OptionData, + // ]; + // const comparator = (option: OptionData) => option.lastVisibleActionCreated ?? ''; + // const result = optionsOrderBy(options, comparator, 5); + // expect(result.length).toBe(2); + // // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + // expect(result.at(0)!.reportID).toBe('2'); + // // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + // expect(result.at(1)!.reportID).toBe('1'); + // }); + + // it('returns empty array if options is empty', () => { + // const result = optionsOrderBy([], recentReportComparator, 3); + // expect(result).toEqual([]); + // }); + + // it('applies filter function if provided', () => { + // const options: OptionData[] = [ + // {reportID: '1', lastVisibleActionCreated: '2022-01-01T10:00:00Z', isPinned: true} as OptionData, + // {reportID: '2', lastVisibleActionCreated: '2022-01-01T12:00:00Z', isPinned: false} as OptionData, + // {reportID: '3', lastVisibleActionCreated: '2022-01-01T09:00:00Z', isPinned: true} as OptionData, + // ]; + // const comparator = (option: OptionData) => option.lastVisibleActionCreated ?? ''; + // const result = optionsOrderBy(options, comparator, 2, (option) => option.isPinned); + // expect(result.length).toBe(2); + // // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + // expect(result.at(0)!.reportID).toBe('1'); + // // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + // expect(result.at(1)!.reportID).toBe('3'); + // }); + + // it('handles negative limit by returning empty array', () => { + // const options: OptionData[] = [ + // {reportID: '1', lastVisibleActionCreated: '2022-01-01T10:00:00Z'} as OptionData, + // {reportID: '2', lastVisibleActionCreated: '2022-01-01T12:00:00Z'} as OptionData, + // {reportID: '3', lastVisibleActionCreated: '2022-01-01T09:00:00Z'} as OptionData, + // ]; + // const comparator = (option: OptionData) => option.lastVisibleActionCreated ?? ''; + // const result = optionsOrderBy(options, comparator, -1); + // expect(result).toEqual([]); + // }); + + // it('handles negative limit with large absolute value', () => { + // const options: OptionData[] = [ + // {reportID: '1', lastVisibleActionCreated: '2022-01-01T10:00:00Z'} as OptionData, + // {reportID: '2', lastVisibleActionCreated: '2022-01-01T12:00:00Z'} as OptionData, + // ]; + // const comparator = (option: OptionData) => option.lastVisibleActionCreated ?? ''; + // const result = optionsOrderBy(options, comparator, -100); + // expect(result).toEqual([]); + // }); + + // it('handles limit equal to zero', () => { + // const options: OptionData[] = [ + // {reportID: '1', lastVisibleActionCreated: '2022-01-01T10:00:00Z'} as OptionData, + // {reportID: '2', lastVisibleActionCreated: '2022-01-01T12:00:00Z'} as OptionData, + // ]; + // const comparator = (option: OptionData) => option.lastVisibleActionCreated ?? ''; + // const result = optionsOrderBy(options, comparator, 0); + // expect(result).toEqual([]); + // }); + // }); + + // describe('sortAlphabetically', () => { + // it('should sort options alphabetically by text', () => { + // const options: OptionData[] = [{text: 'Banana', reportID: '1'} as OptionData, {text: 'Apple', reportID: '2'} as OptionData, {text: 'Cherry', reportID: '3'} as OptionData]; + // const sortedOptions = sortAlphabetically(options, 'text', localeCompare); + // expect(sortedOptions.at(0)?.reportID).toBe('2'); + // expect(sortedOptions.at(1)?.reportID).toBe('1'); + // expect(sortedOptions.at(2)?.reportID).toBe('3'); + // }); + + // it('should handle empty array', () => { + // const sortedOptions = sortAlphabetically([], 'abc', localeCompare); + // expect(sortedOptions).toEqual([]); + // }); + + // it('should handle single option', () => { + // const options: OptionData[] = [{text: 'Single', reportID: '1'} as OptionData]; + // const sortedOptions = sortAlphabetically(options, 'text', localeCompare); + // expect(sortedOptions.length).toBe(1); + // expect(sortedOptions.at(0)?.text).toBe('Single'); + // }); + // }); + // describe('getSearchValueForPhoneOrEmail', () => { + // it('should return E164 format for valid phone number', () => { + // const result = getSearchValueForPhoneOrEmail('+1 (234) 567-8901', 1); + // expect(result).toBe('+12345678901'); + // }); + + // it('should return E164 format for valid international phone number', () => { + // const result = getSearchValueForPhoneOrEmail('+44 20 8759 9036', 44); + // expect(result).toBe('+442087599036'); + // }); + + // it('should return lowercase for email address', () => { + // const result = getSearchValueForPhoneOrEmail('Test@Example.COM', 1); + // expect(result).toBe('test@example.com'); + // }); + + // it('should handle SMS domain removal for valid phone number', () => { + // const result = getSearchValueForPhoneOrEmail('+12345678901@expensify.sms', 1); + // expect(result).toBe('+12345678901'); + // }); + + // it('should return empty string for empty input', () => { + // const result = getSearchValueForPhoneOrEmail('', 1); + // expect(result).toBe(''); + // }); + // }); + + // describe('createOption', () => { + // it('should return alternative text correctly when the last action is report preview action', async () => { + // const report = { + // chatType: '', + // currency: 'USD', + // description: '', + // errorFields: {}, + // hasOutstandingChildRequest: false, + // hasOutstandingChildTask: false, + // iouReportID: '456', + // lastMessageHtml: '', + // lastMessageText: '', + // participants: { + // '1': { + // notificationPreference: 'always', + // }, + // '2': { + // notificationPreference: 'always', + // }, + // }, + // reportID: '123', + // type: 'chat', + // lastActorAccountID: 1, + // } as unknown as Report; + + // const reportPreviewAction = { + // actionName: 'REPORTPREVIEW', + // actorAccountID: 1, + // childManagerAccountID: 2, + // childOwnerAccountID: 1, + // childReportID: '456', + // childReportName: 'IOU', + // created: '2025-10-02 06:50:36.302', + // reportActionID: '12345678', + // shouldShow: true, + // message: [ + // { + // html: 'Iron Man owes ₫34', + // text: 'Iron Man owes ₫34', + // type: 'COMMENT', + // whisperedTo: [], + // }, + // ], + // } as unknown as ReportAction; + + // const iouReport = { + // chatReportID: '123', + // currency: 'VND', + // managerID: 2, + // ownerAccountID: 1, + // parentReportActionID: '12345678', + // parentReportID: '123', + // participants: { + // '19960856': { + // notificationPreference: '', + // }, + // '20669492': { + // notificationPreference: '', + // }, + // }, + // reportID: '456', + // reportName: 'IOU', + // total: 3400, + // } as unknown as Report; + + // const iouAction = { + // actorAccountID: 1, + // message: [ + // { + // type: 'COMMENT', + // html: '₫34 expense', + // text: '₫34 expense', + // isEdited: false, + // whisperedTo: [], + // isDeletedParentAction: false, + // deleted: '', + // reactions: [], + // }, + // ], + // originalMessage: { + // IOUReportID: '456', + // IOUTransactionID: '123456', + // amount: 3400, + // comment: '', + // currency: 'VND', + // participantAccountIDs: [1, 2], + // }, + // actionName: 'IOU', + // reportActionID: '789', + // } as unknown as ReportAction; + + // const transaction = { + // transactionID: '123456', + // amount: 3400, + // currency: 'VND', + // reportID: '3993091505909230', + // comment: { + // comment: '', + // }, + // merchant: '(none)', + // created: '2025-10-02', + // category: '', + // taxAmount: 0, + // reimbursable: true, + // } as unknown as Transaction; + + // await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`, report); + // await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`, {[reportPreviewAction.reportActionID]: reportPreviewAction}); + // await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`, iouReport); + // await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`, {[iouAction.reportActionID]: iouAction}); + // await Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`, transaction); + // await waitForBatchedUpdates(); + + // const result = createOption([1, 2], PERSONAL_DETAILS, report, {showChatPreviewLine: true}); + + // expect(result.alternateText).toBe('Iron Man owes ₫34'); + // }); + // }); describe('getLastMessageTextForReport', () => { describe('REPORT_PREVIEW action', () => { @@ -2530,7 +2530,7 @@ describe('OptionsListUtils', () => { [submittedAction.reportActionID]: submittedAction, }); const lastMessage = getLastMessageTextForReport({report, lastActorDetails: null, isReportArchived: false}); - expect(lastMessage).toBe(Parser.htmlToText(translateLocal('iou.automaticallySubmitted'))); + expect(lastMessage).toBe(Parser.htmlToText(translate(CONST.LOCALES.EN, 'iou.automaticallySubmitted'))); }); }); describe('APPROVED action', () => { @@ -2549,7 +2549,7 @@ describe('OptionsListUtils', () => { [approvedAction.reportActionID]: approvedAction, }); const lastMessage = getLastMessageTextForReport({report, lastActorDetails: null, isReportArchived: false}); - expect(lastMessage).toBe(Parser.htmlToText(translateLocal('iou.automaticallyApproved'))); + expect(lastMessage).toBe(Parser.htmlToText(translate(CONST.LOCALES.EN, 'iou.automaticallyApproved'))); }); }); describe('FORWARDED action', () => { @@ -2568,7 +2568,7 @@ describe('OptionsListUtils', () => { [forwardedAction.reportActionID]: forwardedAction, }); const lastMessage = getLastMessageTextForReport({report, lastActorDetails: null, isReportArchived: false}); - expect(lastMessage).toBe(Parser.htmlToText(translateLocal('iou.automaticallyForwarded'))); + expect(lastMessage).toBe(Parser.htmlToText(translate(CONST.LOCALES.EN, 'iou.automaticallyForwarded'))); }); }); it('TAKE_CONTROL action', async () => { From eb38b56944c7b4f20d9fa8a2f3a7a9cd6da494ce Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Thu, 23 Oct 2025 12:45:58 +0800 Subject: [PATCH 3/3] uncomment --- tests/unit/OptionsListUtilsTest.tsx | 3594 +++++++++++++-------------- 1 file changed, 1797 insertions(+), 1797 deletions(-) diff --git a/tests/unit/OptionsListUtilsTest.tsx b/tests/unit/OptionsListUtilsTest.tsx index e033bf8cb6c2..18cc0e8a051b 100644 --- a/tests/unit/OptionsListUtilsTest.tsx +++ b/tests/unit/OptionsListUtilsTest.tsx @@ -634,1803 +634,1803 @@ describe('OptionsListUtils', () => { OPTIONS_WITH_MANAGER_MCTEST = createOptionList(PERSONAL_DETAILS_WITH_MANAGER_MCTEST); }); - // describe('getSearchOptions()', () => { - // 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: {}, betas: [CONST.BETAS.ALL]}); - - // // Then all personal details (including those that have reports) should be returned - // expect(results.personalDetails.length).toBe(10); - - // // Then all of the reports should be shown including the archived rooms, except for the thread report with notificationPreferences hidden. - // expect(results.recentReports.length).toBe(Object.values(OPTIONS.reports).length - 1); - // }); - - // it('should include current user when includeCurrentUser is true for type:chat from suggestions', () => { - // // Given a set of options where the current user is Iron Man (accountID: 2) - // // When we call getSearchOptions with includeCurrentUser set to true - // const results = getSearchOptions({ - // options: OPTIONS, - // draftComments: {}, - // betas: [CONST.BETAS.ALL], - // isUsedInChatFinder: true, - // includeReadOnly: true, - // searchQuery: '', - // maxResults: undefined, - // includeUserToInvite: false, - // includeRecentReports: true, - // includeCurrentUser: true, - // }); - - // // Then the current user should be included in personalDetails - // const currentUserOption = results.personalDetails.find((option) => option.login === 'tonystark@expensify.com'); - // expect(currentUserOption).toBeDefined(); - // expect(currentUserOption?.text).toBe('Iron Man'); - // expect(currentUserOption?.accountID).toBe(2); - - // // Then all personal details including the current user should be returned - // expect(results.personalDetails.length).toBe(11); - // }); - - // it('should exclude current user when includeCurrentUser is false', () => { - // // Given a set of options where the current user is Iron Man (accountID: 2) - // // When we call getSearchOptions with includeCurrentUser set to false (default behavior) - // const results = getSearchOptions({ - // options: OPTIONS, - // draftComments: {}, - // betas: [CONST.BETAS.ALL], - // isUsedInChatFinder: true, - // includeReadOnly: true, - // searchQuery: '', - // maxResults: undefined, - // includeUserToInvite: false, - // includeRecentReports: true, - // }); - - // // Then the current user should not be included in personalDetails - // const currentUserOption = results.personalDetails.find((option) => option.login === 'tonystark@expensify.com'); - // expect(currentUserOption).toBeUndefined(); - - // // Then all personal details except the current user should be returned - // expect(results.personalDetails.length).toBe(10); - // }); - // }); - - // describe('orderOptions()', () => { - // it('should sort options alphabetically and preserves reportID for personal details with existing reports', () => { - // // Given a set of reports and personalDetails - // // When we call getValidOptions() - // let results: Pick = getValidOptions( - // { - // reports: OPTIONS.reports, - // personalDetails: OPTIONS.personalDetails, - // }, - // {}, - // ); - // // When we call orderOptions() - // results = orderOptions(results); - - // // Then all personalDetails except the currently logged in user should be returned - // expect(results.personalDetails.length).toBe(Object.values(OPTIONS.personalDetails).length - 1); - - // const expected = [ - // 'Black Panther', - // 'Black Widow', - // 'Captain America', - // 'Invisible Woman', - // 'Mister Fantastic', - // 'Mr Sinister', - // 'Spider-Man', - // 'The Incredible Hulk', - // 'Thor', - // 'Timothée', - // ]; - // const actual = results.personalDetails?.map((item) => item.text); - - // // Then the results should be sorted alphabetically - // expect(actual).toEqual(expected); - - // const personalDetailWithExistingReport = results.personalDetails.find((personalDetail) => personalDetail.login === 'peterparker@expensify.com'); - // // Then the result which has an existing report should also have the reportID attached - // expect(personalDetailWithExistingReport?.reportID).toBe('2'); - // }); - - // 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: []}, {}); - // // When we call orderOptions() - // results = orderOptions(results); - - // const expected = [ - // 'Black Panther', - // 'Black Widow', - // 'Captain America', - // 'Invisible Woman', - // 'Mister Fantastic', - // 'Mr Sinister', - // 'Spider-Man', - // 'The Incredible Hulk', - // 'Thor', - // 'Timothée', - // ]; - // const actual = results.personalDetails?.map((item) => item.text); - - // // Then the results should be sorted alphabetically - // expect(actual).toEqual(expected); - // }); - // }); - - // describe('getValidOptions()', () => { - // 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: []}, {}); - - // // Then the result should be empty - // expect(results.personalDetails).toEqual([]); - // expect(results.recentReports).toEqual([]); - // expect(results.currentUserOption).toBeUndefined(); - // expect(results.userToInvite).toEqual(null); - // expect(results.workspaceChats).toEqual([]); - // expect(results.selfDMChat).toEqual(undefined); - // }); - - // 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}, {}); - - // // 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); - // // Then the result should include Concierge - // expect(results.recentReports).toEqual(expect.arrayContaining([expect.objectContaining({login: 'concierge@expensify.com'})])); - // }); - - // it('should exclude Concierge when excludedLogins is specified', () => { - // // Given a set of reports and personalDetails that includes Concierge and a config object that excludes Concierge - // // When we call getValidOptions() - // const results = getValidOptions( - // { - // reports: OPTIONS_WITH_CONCIERGE.reports, - // personalDetails: OPTIONS_WITH_CONCIERGE.personalDetails, - // }, - // {}, - // { - // excludeLogins: {[CONST.EMAIL.CONCIERGE]: true}, - // }, - // ); - - // // Then the result should include all personalDetails except the currently logged in user and Concierge - // expect(results.personalDetails.length).toBe(Object.values(OPTIONS_WITH_CONCIERGE.personalDetails).length - 2); - // // Then the result should not include Concierge - // expect(results.personalDetails).not.toEqual(expect.arrayContaining([expect.objectContaining({login: 'concierge@expensify.com'})])); - // }); - - // 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}, - // {}, - // {excludeLogins: {[CONST.EMAIL.CHRONOS]: true}}, - // ); - - // // Then the result should include all personalDetails except the currently logged in user and Chronos - // expect(results.personalDetails.length).toBe(Object.values(OPTIONS_WITH_CHRONOS.personalDetails).length - 2); - // // Then the result should not include Chronos - // expect(results.personalDetails).not.toEqual(expect.arrayContaining([expect.objectContaining({login: 'chronos@expensify.com'})])); - // }); - - // it('should exclude Receipts option from results when excludedLogins is specified', () => { - // // Given a set of reports and personalDetails that includes receipts and a config object that excludes receipts - // // When we call getValidOptions() - // const results = getValidOptions( - // { - // reports: OPTIONS_WITH_RECEIPTS.reports, - // personalDetails: OPTIONS_WITH_RECEIPTS.personalDetails, - // }, - // {}, - // { - // excludeLogins: {[CONST.EMAIL.RECEIPTS]: true}, - // }, - // ); - - // // Then the result should include all personalDetails except the currently logged in user and receipts - // expect(results.personalDetails.length).toBe(Object.values(OPTIONS_WITH_RECEIPTS.personalDetails).length - 2); - // // Then the result should not include receipts - // expect(results.personalDetails).not.toEqual(expect.arrayContaining([expect.objectContaining({login: 'receipts@expensify.com'})])); - // }); - - // it('should include Manager McTest in results by default', () => { - // // Given a set of reports and personalDetails that includes Manager McTest - // // When we call getValidOptions() - // const result = getValidOptions( - // {reports: OPTIONS_WITH_MANAGER_MCTEST.reports, personalDetails: OPTIONS_WITH_MANAGER_MCTEST.personalDetails}, - // {}, - // {includeP2P: true, canShowManagerMcTest: true, betas: [CONST.BETAS.NEWDOT_MANAGER_MCTEST]}, - // ); - - // // Then the result should include all personalDetails except the currently logged in user - // expect(result.personalDetails.length).toBe(Object.values(OPTIONS_WITH_MANAGER_MCTEST.personalDetails).length - 1); - // // Then the result should include Manager McTest - // expect(result.personalDetails).toEqual(expect.arrayContaining([expect.objectContaining({login: CONST.EMAIL.MANAGER_MCTEST})])); - // }); - - // 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}, - // {}, - // {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); - // // Then the result should not include Manager McTest - // expect(result.personalDetails).not.toEqual(expect.arrayContaining([expect.objectContaining({login: CONST.EMAIL.MANAGER_MCTEST})])); - // }); - - // it('should exclude Manager McTest from results if user dismissed the tooltip', () => { - // return waitForBatchedUpdates() - // .then(() => - // // Given that the user has dismissed the tooltip - // Onyx.set(ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING, { - // [CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.SCAN_TEST_TOOLTIP]: { - // timestamp: DateUtils.getDBTime(new Date().valueOf()), - // }, - // }), - // ) - // .then(() => { - // // When we call getValidOptions() - // const optionsWhenUserAlreadySubmittedExpense = getValidOptions( - // {reports: OPTIONS_WITH_MANAGER_MCTEST.reports, personalDetails: OPTIONS_WITH_MANAGER_MCTEST.personalDetails}, - // {}, - // {includeP2P: true, canShowManagerMcTest: true, betas: [CONST.BETAS.NEWDOT_MANAGER_MCTEST]}, - // ); - - // // Then the result should include all personalDetails except the currently logged in user and Manager McTest - // expect(optionsWhenUserAlreadySubmittedExpense.personalDetails.length).toBe(Object.values(OPTIONS_WITH_MANAGER_MCTEST.personalDetails).length - 2); - // // Then the result should not include Manager McTest - // expect(optionsWhenUserAlreadySubmittedExpense.personalDetails).not.toEqual(expect.arrayContaining([expect.objectContaining({login: CONST.EMAIL.MANAGER_MCTEST})])); - // }); - // }); - - // it('should keep admin rooms if specified', () => { - // // Given an admin room report search option - // const adminRoom: SearchOption = { - // item: { - // chatType: 'policyAdmins', - // currency: 'USD', - // errorFields: {}, - // lastActionType: 'CREATED', - // lastReadTime: '2025-03-21 07:25:46.279', - // lastVisibleActionCreated: '2024-12-15 21:13:24.317', - // lastVisibleActionLastModified: '2024-12-15 21:13:24.317', - // ownerAccountID: 0, - // permissions: ['read', 'write'], - // policyID: '52A5ABD88FBBD18F', - // policyName: "David's Playground", - // reportID: '1455140530846319', - // reportName: '#admins', - // type: 'chat', - // writeCapability: 'all', - // }, - // text: '#admins', - // alternateText: "David's Playground", - // allReportErrors: {}, - // subtitle: "David's Playground", - // participantsList: [], - // reportID: '1455140530846319', - // keyForList: '1455140530846319', - // isDefaultRoom: true, - // isChatRoom: true, - // policyID: '52A5ABD88FBBD18F', - // lastMessageText: '', - // lastVisibleActionCreated: '2024-12-15 21:13:24.317', - // notificationPreference: 'hidden', - // }; - // // When we call getValidOptions with includeMultipleParticipantReports set to true - // const results = getValidOptions( - // {reports: [adminRoom], personalDetails: OPTIONS.personalDetails}, - // {}, - // { - // includeMultipleParticipantReports: true, - // }, - // ); - // const adminRoomOption = results.recentReports.find((report) => report.reportID === '1455140530846319'); - - // // Then the result should include the admin room - // expect(adminRoomOption).toBeDefined(); - // }); - - // it('should include brickRoadIndicator if showRBR is true', () => { - // const reportID = '1455140530846319'; - // const workspaceChat: SearchOption = { - // item: { - // chatType: CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT, - // currency: 'USD', - // errorFields: {}, - // lastActionType: 'CREATED', - // lastReadTime: '2025-03-21 07:25:46.279', - // lastVisibleActionCreated: '2024-12-15 21:13:24.317', - // lastVisibleActionLastModified: '2024-12-15 21:13:24.317', - // ownerAccountID: 0, - // permissions: ['read', 'write'], - // participants: {1: {notificationPreference: 'always'}}, - // policyID: '52A5ABD88FBBD18F', - // policyName: "A's Workspace", - // reportID, - // reportName: "A's Workspace chat", - // type: 'chat', - // writeCapability: 'all', - // }, - // text: "A's Workspace chat", - // alternateText: "A's Workspace", - // allReportErrors: {}, - // subtitle: "A's Workspace", - // participantsList: [], - // reportID, - // keyForList: '1455140530846319', - // isDefaultRoom: true, - // isChatRoom: true, - // policyID: '52A5ABD88FBBD18F', - // lastMessageText: '', - // lastVisibleActionCreated: '2024-12-15 21:13:24.317', - // notificationPreference: 'hidden', - // brickRoadIndicator: CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR, - // }; - // const results = getValidOptions( - // {reports: [workspaceChat], personalDetails: []}, - // {}, - // { - // includeMultipleParticipantReports: true, - // showRBR: true, - // }, - // ); - // expect(results.recentReports.at(0)?.brickRoadIndicator).toBe(CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR); - // }); - - // it('should not include brickRoadIndicator if showRBR is false', () => { - // const reportID = '1455140530846319'; - // const workspaceChat: SearchOption = { - // item: { - // chatType: CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT, - // currency: 'USD', - // errorFields: {}, - // lastActionType: 'CREATED', - // lastReadTime: '2025-03-21 07:25:46.279', - // lastVisibleActionCreated: '2024-12-15 21:13:24.317', - // lastVisibleActionLastModified: '2024-12-15 21:13:24.317', - // ownerAccountID: 0, - // permissions: ['read', 'write'], - // participants: {1: {notificationPreference: 'always'}}, - // policyID: '52A5ABD88FBBD18F', - // policyName: "A's Workspace", - // reportID, - // reportName: "A's Workspace chat", - // type: 'chat', - // writeCapability: 'all', - // }, - // text: "A's Workspace chat", - // alternateText: "A's Workspace", - // allReportErrors: {}, - // subtitle: "A's Workspace", - // participantsList: [], - // reportID, - // keyForList: '1455140530846319', - // isDefaultRoom: true, - // isChatRoom: true, - // policyID: '52A5ABD88FBBD18F', - // lastMessageText: '', - // lastVisibleActionCreated: '2024-12-15 21:13:24.317', - // notificationPreference: 'hidden', - // brickRoadIndicator: CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR, - // }; - // const results = getValidOptions( - // {reports: [workspaceChat], personalDetails: []}, - // {}, - // { - // includeMultipleParticipantReports: true, - // showRBR: false, - // }, - // ); - // expect(results.recentReports.at(0)?.brickRoadIndicator).toBe(null); - // }); - // }); - - // describe('getValidOptions() for chat room', () => { - // 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, - // {}, - // { - // includeRecentReports: true, - // includeMultipleParticipantReports: true, - // includeP2P: true, - // includeOwnedWorkspaceChats: true, - // }, - // ); - - // // Then the result should include all reports except the currently logged in user - // expect(results.recentReports.length).toBe(OPTIONS_WITH_WORKSPACE_ROOM.reports.length - 1); - // expect(results.recentReports).toEqual(expect.arrayContaining([expect.objectContaining({reportID: '14'})])); - // }); - // }); - - // describe('getValidOptions() for group Chat', () => { - // 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}, {}); - // const reportLogins = results.recentReports.map((reportOption) => reportOption.login); - // const personalDetailsOverlapWithReports = results.personalDetails.every((personalDetailOption) => reportLogins.includes(personalDetailOption.login)); - - // // Then we should expect all the personalDetails to show except the currently logged in user - // expect(results.personalDetails.length).toBe(Object.values(OPTIONS.personalDetails).length - 1); - // // Then none of our personalDetails should include any of the users with recent reports - // expect(personalDetailsOverlapWithReports).toBe(false); - // }); - - // 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}, {}, {excludeLogins: {'peterparker@expensify.com': true}}); - - // // Then the option should not appear anywhere in either list - // expect(results.recentReports.every((option) => option.login !== 'peterparker@expensify.com')).toBe(true); - // expect(results.personalDetails.every((option) => option.login !== 'peterparker@expensify.com')).toBe(true); - // }); - - // 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}, {}); - - // // 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); - // // Then Concierge should be included in the results - // expect(results.recentReports).toEqual(expect.arrayContaining([expect.objectContaining({login: 'concierge@expensify.com'})])); - // }); - - // it('should exclude Concierge from the results when it is specified in excludedLogins', () => { - // // Given a set of reports and personalDetails that includes Concierge - // // When we call getValidOptions with excludeLogins param - // const results = getValidOptions( - // { - // reports: OPTIONS_WITH_CONCIERGE.reports, - // personalDetails: OPTIONS_WITH_CONCIERGE.personalDetails, - // }, - // {}, - // { - // excludeLogins: {[CONST.EMAIL.CONCIERGE]: true}, - // }, - // ); - - // // Then the result should include all personalDetails except the currently logged in user and Concierge - // expect(results.personalDetails.length).toBe(Object.values(OPTIONS_WITH_CONCIERGE.personalDetails).length - 2); - // // Then none of the results should include Concierge - // expect(results.personalDetails).not.toEqual(expect.arrayContaining([expect.objectContaining({login: 'concierge@expensify.com'})])); - // expect(results.recentReports).not.toEqual(expect.arrayContaining([expect.objectContaining({login: 'concierge@expensify.com'})])); - // }); - - // 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}, - // {}, - // {excludeLogins: {[CONST.EMAIL.CHRONOS]: true}}, - // ); - - // // Then the result should include all personalDetails except the currently logged in user and Chronos - // expect(results.personalDetails.length).toBe(Object.values(OPTIONS_WITH_CHRONOS.personalDetails).length - 2); - // // Then none of the results should include Chronos - // expect(results.personalDetails).not.toEqual(expect.arrayContaining([expect.objectContaining({login: 'chronos@expensify.com'})])); - // expect(results.recentReports).not.toEqual(expect.arrayContaining([expect.objectContaining({login: 'chronos@expensify.com'})])); - // }); - - // it('should exclude Receipts from the results when it is specified in excludedLogins', () => { - // // Given a set of reports and personalDetails that includes receipts - // // When we call getValidOptions() with excludeLogins param - // const results = getValidOptions( - // { - // reports: OPTIONS_WITH_RECEIPTS.reports, - // personalDetails: OPTIONS_WITH_RECEIPTS.personalDetails, - // }, - // {}, - // { - // excludeLogins: {[CONST.EMAIL.RECEIPTS]: true}, - // }, - // ); - - // // Then the result should include all personalDetails except the currently logged in user and receipts - // expect(results.personalDetails.length).toBe(Object.values(OPTIONS_WITH_RECEIPTS.personalDetails).length - 2); - // // Then none of the results should include receipts - // expect(results.personalDetails).not.toEqual(expect.arrayContaining([expect.objectContaining({login: 'receipts@expensify.com'})])); - // expect(results.recentReports).not.toEqual(expect.arrayContaining([expect.objectContaining({login: 'receipts@expensify.com'})])); - // }); - - // it('should limit recent reports when maxRecentReportElements is specified', () => { - // // 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}, {}, {maxRecentReportElements: maxRecentReports}); - - // // Then the recent reports should be limited to the specified number - // expect(results.recentReports.length).toBeLessThanOrEqual(maxRecentReports); - // }); - - // 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}, {}); - // const resultsWithLimit = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}, {maxRecentReportElements: 2}); - - // // Then the results without limit should have more or equal reports - // expect(resultsWithoutLimit.recentReports.length).toBeGreaterThanOrEqual(resultsWithLimit.recentReports.length); - // }); - - // 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}, {}); - // const resultsWithLimit = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}, {maxRecentReportElements: 2}); - - // // Then personalDetails should remain the same regardless of maxRecentReportElements - // expect(resultsWithLimit.personalDetails.length).toBe(resultsWithoutLimit.personalDetails.length); - // }); - - // it('should respect maxRecentReportElements when combined with maxElements', () => { - // // Given a set of reports and personalDetails - // // When we call getValidOptions with both maxElements and maxRecentReportElements - // const maxRecentReports = 3; - // const maxTotalElements = 10; - // const results = getValidOptions( - // {reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, - // {}, - // {maxElements: maxTotalElements, maxRecentReportElements: maxRecentReports}, - // ); - - // // Then recent reports should be limited by maxRecentReportElements - // expect(results.recentReports.length).toBeLessThanOrEqual(maxRecentReports); - // // Then the total number of options (reports + personalDetails) should not exceed maxElements - // expect(results.recentReports.length + results.personalDetails.length).toBeLessThanOrEqual(maxTotalElements); - // }); - // }); - - // describe('getShareDestinationsOptions()', () => { - // it('should exclude archived rooms and hidden threads from share destinations', () => { - // // 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) => { - // const report = option.item; - // const {result: isReportArchived} = renderHook(() => useReportIsArchived(report.reportID)); - // if (canUserPerformWriteAction(report, isReportArchived.current) && canCreateTaskInReport(report) && !isCanceledTaskReport(report)) { - // filtered.push(option); - // } - // return filtered; - // }, []); - - // // When we call getValidOptions for share destination with an empty search value - // const results = getValidOptions( - // {reports: filteredReports, personalDetails: OPTIONS.personalDetails}, - // {}, - // { - // betas: [], - // includeMultipleParticipantReports: true, - // showChatPreviewLine: true, - // forcePolicyNamePreview: true, - // includeThreads: true, - // includeMoneyRequests: true, - // includeTasks: true, - // excludeLogins: {}, - // includeOwnedWorkspaceChats: true, - // includeSelfDM: true, - // searchString: '', - // includeUserToInvite: false, - // }, - // ); - - // // Then all the recent reports should be returned except the archived rooms and the hidden thread - // expect(results.recentReports.length).toBe(Object.values(OPTIONS.reports).length - 2); - // }); - - // it('should include DMS, group chats, and workspace rooms in share destinations', () => { - // // Given a set of filtered current Reports (as we do in the component) with workspace rooms before getting share destination options - // const filteredReportsWithWorkspaceRooms = Object.values(OPTIONS_WITH_WORKSPACE_ROOM.reports).reduce((filtered, option) => { - // const report = option.item; - // const {result: isReportArchived} = renderHook(() => useReportIsArchived(report.reportID)); - // // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - // if (canUserPerformWriteAction(report, isReportArchived.current) || isExpensifyOnlyParticipantInReport(report)) { - // filtered.push(option); - // } - // return filtered; - // }, []); - - // // When we call getValidOptions for share destination with an empty search value - // const results = getValidOptions( - // {reports: filteredReportsWithWorkspaceRooms, personalDetails: OPTIONS.personalDetails}, - // {}, - // { - // betas: [], - // includeMultipleParticipantReports: true, - // showChatPreviewLine: true, - // forcePolicyNamePreview: true, - // includeThreads: true, - // includeMoneyRequests: true, - // includeTasks: true, - // excludeLogins: {}, - // includeOwnedWorkspaceChats: true, - // includeSelfDM: true, - // searchString: '', - // includeUserToInvite: false, - // }, - // ); - - // // Then all recent reports should be returned except the archived rooms and the hidden thread - // expect(results.recentReports.length).toBe(Object.values(OPTIONS_WITH_WORKSPACE_ROOM.reports).length - 2); - // }); - // }); - - // describe('getShareLogOptions()', () => { - // it('should not include read-only report', () => { - // // Given a list of 11 report options with reportID of 10 is archived - // // When we call getShareLogOptions - // const results = getShareLogOptions(OPTIONS, {}, []); - - // // Then the report with reportID of 10 should not be included on the list - // expect(results.recentReports.length).toBe(10); - // expect(results.recentReports.find((report) => report.reportID === '10')).toBeUndefined(); - // }); - // }); - - // describe('getMemberInviteOptions()', () => { - // it('should sort personal details alphabetically', () => { - // // Given a set of personalDetails - // // When we call getMemberInviteOptions - // const results = getMemberInviteOptions(OPTIONS.personalDetails, []); - - // // 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'); - // }); - // }); - - // describe('getLastActorDisplayName()', () => { - // it('should return correct display name', () => { - // renderLocaleContextProvider(); - // // Given two different personal details - // // When we call getLastActorDisplayName - // const result1 = getLastActorDisplayName(PERSONAL_DETAILS['2']); - // const result2 = getLastActorDisplayName(PERSONAL_DETAILS['3']); - - // // We should expect the display names to be the same as the personal details - // expect(result1).toBe('You'); - // expect(result2).toBe('Spider-Man'); - // }); - // }); - - // describe('formatMemberForList()', () => { - // it('should format members correctly', () => { - // // Given a set of personal details - // // When we call formatMemberForList - // const formattedMembers = Object.values(PERSONAL_DETAILS).map((personalDetail) => formatMemberForList(personalDetail)); - - // // Then the formatted members' order should be the same as the original PERSONAL_DETAILS array - // expect(formattedMembers.at(0)?.text).toBe('Mister Fantastic'); - // expect(formattedMembers.at(1)?.text).toBe('Iron Man'); - // expect(formattedMembers.at(2)?.text).toBe('Spider-Man'); - - // // Then only the first item should be selected - // expect(formattedMembers.at(0)?.isSelected).toBe(true); - // // Then all remaining items should be unselected - // expect(formattedMembers.slice(1).every((personalDetail) => !personalDetail.isSelected)).toBe(true); - // // Then all items should be enabled - // expect(formattedMembers.every((personalDetail) => !personalDetail.isDisabled)).toBe(true); - // }); - // }); - - // describe('filterAndOrderOptions()', () => { - // 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: {}, betas: [CONST.BETAS.ALL]}); - // // When we pass the returned options to filterAndOrderOptions with an empty search value - // const filteredOptions = filterAndOrderOptions(options, '', COUNTRY_CODE); - - // // Then all options should be returned - // expect(filteredOptions.recentReports.length + filteredOptions.personalDetails.length).toBe(14); - // }); - - // it('should return filtered options in correct order', () => { - // const searchText = 'man'; - // // Given a set of options - // // When we call getSearchOptions with all betas - // const options = getSearchOptions({options: OPTIONS, draftComments: {}, 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}); - - // // Then we expect all options to be part of the recentReports list and reports should be first: - // expect(filteredOptions.personalDetails.length).toBe(0); - // // Then returned reports should match the search text - // expect(filteredOptions.recentReports.length).toBe(4); - // // Then the returned reports should be ordered by most recent action (and other criteria such as whether they are archived) - // expect(filteredOptions.recentReports.at(0)?.text).toBe('Invisible Woman'); // '2022-11-22 03:26:02.019' - // expect(filteredOptions.recentReports.at(1)?.text).toBe('Spider-Man'); // '2022-11-22 03:26:02.016' - // expect(filteredOptions.recentReports.at(2)?.text).toBe('Black Widow'); // This is a personal detail, which has no lastVisibleActionCreated, but matches the login - // expect(filteredOptions.recentReports.at(3)?.text).toBe('Mister Fantastic, Invisible Woman'); // This again is a report with '2022-11-22 03:26:02.015' - // }); - - // it('should filter users by email', () => { - // const searchText = 'mistersinister@marauders.com'; - // // Given a set of options - // // When we call getSearchOptions with all betas - // const options = getSearchOptions({options: OPTIONS, draftComments: {}, betas: [CONST.BETAS.ALL]}); - // // When we pass the returned options to filterAndOrderOptions with a search value - // const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE); - - // // Then only one report should be returned - // expect(filteredOptions.recentReports.length).toBe(1); - // // Then the returned report should match the search text - // expect(filteredOptions.recentReports.at(0)?.text).toBe('Mr Sinister'); - // }); - - // it('should find archived chats', () => { - // const searchText = 'Archived'; - // // Given a set of options - // // When we call getSearchOptions with all betas - // const options = getSearchOptions({options: OPTIONS, draftComments: {}, betas: [CONST.BETAS.ALL]}); - // // When we pass the returned options to filterAndOrderOptions with a search value - // const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE); - - // // Then only one report should be returned - // expect(filteredOptions.recentReports.length).toBe(1); - // // Then the returned report should match the search text - // expect(!!filteredOptions.recentReports.at(0)?.private_isArchived).toBe(true); - // }); - - // it('should filter options by email if dot is skipped in the email', () => { - // // cspell:disable-next-line - // const searchText = 'barryallen'; - // // 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: {}, 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}); - - // // Then only one report should be returned - // expect(filteredOptions.recentReports.length).toBe(1); - // // Then the returned report should match the search text - // expect(filteredOptions.recentReports.at(0)?.login).toBe('barry.allen@expensify.com'); - // }); - - // it('should include workspace rooms in the search results', () => { - // 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: {}, betas: [CONST.BETAS.ALL]}); - // // When we pass the returned options to filterAndOrderOptions with a search value - // const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE); - - // // Then only one report should be returned - // expect(filteredOptions.recentReports.length).toBe(1); - // // Then the returned report should match the search text - // expect(filteredOptions.recentReports.at(0)?.subtitle).toBe('Avengers Room'); - // }); - - // 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: {}, betas: [CONST.BETAS.ALL]}); - // // When we pass the returned options to filterAndOrderOptions with a search value - // const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE); - - // // Then only one report should be returned - // expect(filteredOptions.recentReports.length).toBe(1); - // // Then the returned report should match the search text - // expect(filteredOptions.recentReports.at(0)?.login).toBe(searchText); - // }); - - // it('should prioritize options with matching display name over chat rooms', () => { - // const searchText = 'spider'; - // // 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: {}, betas: [CONST.BETAS.ALL]}); - // // When we pass the returned options to filterAndOrderOptions with a search value - // const filterOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE); - - // // Then only two reports should be returned - // expect(filterOptions.recentReports.length).toBe(2); - // // Then the second report should match the search text - // expect(filterOptions.recentReports.at(1)?.isChatRoom).toBe(true); - // }); - - // it('should put the item with latest lastVisibleActionCreated on top when search value match multiple items', () => { - // renderLocaleContextProvider(); - // const searchText = 'fantastic'; - // // Given a set of options - // const options = getSearchOptions({options: OPTIONS, draftComments: {}}); - // // When we call filterAndOrderOptions with a search value - // const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE); - - // // Then only three reports should be returned - // expect(filteredOptions.recentReports.length).toBe(3); - // // Then the first report should match the search text - // expect(filteredOptions.recentReports.at(0)?.text).toBe('Mister Fantastic'); - // // Then the second report should match the search text - // expect(filteredOptions.recentReports.at(1)?.text).toBe('Mister Fantastic, Invisible Woman'); - // }); - - // 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: {}}); - // // When we call filterAndOrderOptions with a search value - // const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE); - - // // Then the user to invite should be returned - // expect(filteredOptions.userToInvite?.login).toBe(searchText); - // }); - - // 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}, {}, {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}); - - // // Then no personal details should be returned - // expect(filterOptions.recentReports.length).toBe(0); - // }); - - // 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: {}}); - // // When we call filterAndOrderOptions with a search value and excludeLogins - // const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE, {excludeLogins: CONST.EXPENSIFY_EMAILS_OBJECT}); - - // // Then the user to invite should be returned - // expect(filteredOptions.userToInvite?.login).toBe(searchText); - // }); - - // 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: {}}); - // // When we call filterAndOrderOptions with a search value and maxRecentReportsToShow set to 2 - // const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE, {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}); - - // // Then no reports should be returned - // expect(limitToZeroOptions.recentReports.length).toBe(0); - // }); - - // 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: {}, betas: [CONST.BETAS.ALL]}); - // // When we call filterAndOrderOptions with a search value - // const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE); - - // // Then there should be one matching result - // expect(filteredOptions.personalDetails.length).toBe(1); - // // Then the user to invite should be null - // 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, []); - // // When we call filterAndOrderOptions with a search value that does not match any personal details - // const filteredOptions = filterAndOrderOptions(options, 'magneto', COUNTRY_CODE); - - // // 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, []); - // // When we call filterAndOrderOptions with a search value that matches an email - // const filteredOptions = filterAndOrderOptions(options, 'peterparker@expensify.com', COUNTRY_CODE); - - // // 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) => { - // const report = option.item; - // if (canUserPerformWriteAction(report, false) && canCreateTaskInReport(report) && !isCanceledTaskReport(report)) { - // filtered.push(option); - // } - // return filtered; - // }, []); - // // When we call getValidOptions for share destination with the filteredReports - // const options = getValidOptions( - // {reports: filteredReports, personalDetails: OPTIONS.personalDetails}, - // {}, - // { - // betas: [], - // includeMultipleParticipantReports: true, - // showChatPreviewLine: true, - // forcePolicyNamePreview: true, - // includeThreads: true, - // includeMoneyRequests: true, - // includeTasks: true, - // excludeLogins: {}, - // includeOwnedWorkspaceChats: true, - // includeSelfDM: true, - // searchString: '', - // 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); - - // // Then no recent reports should be returned - // expect(filteredOptions.recentReports.length).toBe(0); - // }); - - // it('should return a workspace room when we search for a workspace room(getShareDestinationsOptions)', () => { - // // Given a set of filtered current Reports (as we do in the component) before getting share destination options - // const filteredReportsWithWorkspaceRooms = Object.values(OPTIONS_WITH_WORKSPACE_ROOM.reports).reduce((filtered, option) => { - // const report = option.item; - // // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - // if (canUserPerformWriteAction(report, false) || isExpensifyOnlyParticipantInReport(report)) { - // filtered.push(option); - // } - // return filtered; - // }, []); - - // // When we call getValidOptions for share destination with the filteredReports - // const options = getValidOptions( - // {reports: filteredReportsWithWorkspaceRooms, personalDetails: OPTIONS.personalDetails}, - // {}, - // { - // betas: [], - // includeMultipleParticipantReports: true, - // showChatPreviewLine: true, - // forcePolicyNamePreview: true, - // includeThreads: true, - // includeMoneyRequests: true, - // includeTasks: true, - // excludeLogins: {}, - // includeOwnedWorkspaceChats: true, - // includeSelfDM: true, - // searchString: '', - // 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); - - // // Then one recent report should be returned - // expect(filteredOptions.recentReports.length).toBe(1); - // }); - - // it('should not show any results if searching for a non-existing workspace room(getShareDestinationOptions)', () => { - // // Given a set of filtered current Reports (as we do in the component) before getting share destination options - // const filteredReportsWithWorkspaceRooms = Object.values(OPTIONS_WITH_WORKSPACE_ROOM.reports).reduce((filtered, option) => { - // const report = option.item; - // // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - // if (canUserPerformWriteAction(report, false) || isExpensifyOnlyParticipantInReport(report)) { - // filtered.push(option); - // } - // return filtered; - // }, []); - - // // When we call getValidOptions for share destination with the filteredReports - // const options = getValidOptions( - // {reports: filteredReportsWithWorkspaceRooms, personalDetails: OPTIONS.personalDetails}, - // {}, - // { - // betas: [], - // includeMultipleParticipantReports: true, - // showChatPreviewLine: true, - // forcePolicyNamePreview: true, - // includeThreads: true, - // includeMoneyRequests: true, - // includeTasks: true, - // excludeLogins: {}, - // includeOwnedWorkspaceChats: true, - // includeSelfDM: true, - // searchString: '', - // 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); - - // // Then no recent reports should be returned - // expect(filteredOptions.recentReports.length).toBe(0); - // }); - - // 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}, {}); - // // When we call filterAndOrderOptions with a search value that matches a personal detail with no existing report - // const filteredOptions = filterAndOrderOptions(options, 'hulk', COUNTRY_CODE); - - // // Then no recent reports should be returned - // expect(filteredOptions.recentReports.length).toBe(0); - // // 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)?.login).toBe('brucebanner@expensify.com'); - // }); - - // it('should not return any options or user to invite if there are no search results and the string does not match a potential email or phone', () => { - // // Given a set of options - // const options = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}); - // // 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); - - // // Then no recent reports or personal details should be returned - // expect(filteredOptions.recentReports.length).toBe(0); - // expect(filteredOptions.personalDetails.length).toBe(0); - // // Then no user to invite should be returned - // expect(filteredOptions.userToInvite).toBe(null); - // }); - - // 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}, {}); - // // 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); - - // // Then no recent reports or personal details should be returned - // expect(filteredOptions.recentReports.length).toBe(0); - // expect(filteredOptions.personalDetails.length).toBe(0); - // // Then an user to invite should be returned - // expect(filteredOptions.userToInvite).not.toBe(null); - // }); - - // 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}, {}); - // // 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); - - // // Then no recent reports should be returned - // expect(filteredOptions.recentReports.length).toBe(0); - // // Then one user to invite should be returned - // expect(filteredOptions.userToInvite).not.toBe(null); - // }); - - // 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}, {}); - // // When we call filterAndOrderOptions with a search value without accent mark - // const filteredOptions = filterAndOrderOptions(options, 'Timothee', COUNTRY_CODE); - - // // Then one personalDetails with accent mark should be returned - // expect(filteredOptions.personalDetails.length).toBe(1); - // }); - - // 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}, {}); - // // 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); - - // // Then no recent reports or personal details should be returned - // expect(filteredOptions.recentReports.length).toBe(0); - // expect(filteredOptions.personalDetails.length).toBe(0); - // // Then one user to invite should be returned - // expect(filteredOptions.userToInvite).not.toBe(null); - // // Then the user to invite should match the search value - // expect(filteredOptions.userToInvite?.login).toBe('+15005550006'); - // }); - - // 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}, {}); - // // 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); - - // // Then no recent reports or personal details should be returned - // expect(filteredOptions.recentReports.length).toBe(0); - // expect(filteredOptions.personalDetails.length).toBe(0); - // // Then one user to invite should be returned - // expect(filteredOptions.userToInvite).not.toBe(null); - // // Then the user to invite should match the search value - // expect(filteredOptions.userToInvite?.login).toBe('+15005550006'); - // }); - - // 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}, {}); - // // 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); - - // // Then no recent reports or personal details should be returned - // expect(filteredOptions.recentReports.length).toBe(0); - // expect(filteredOptions.personalDetails.length).toBe(0); - // // Then one user to invite should be returned - // expect(filteredOptions.userToInvite).not.toBe(null); - // // Then the user to invite should match the search value - // expect(filteredOptions.userToInvite?.login).toBe('+18003243233'); - // }); - - // 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}, {}); - // // When we call filterAndOrderOptions with a search value that does not match any personal details or reports - // const filteredOptions = filterAndOrderOptions(options, '998243aaaa', COUNTRY_CODE); - - // // Then no recent reports or personal details should be returned - // expect(filteredOptions.recentReports.length).toBe(0); - // expect(filteredOptions.personalDetails.length).toBe(0); - // // Then no user to invite should be returned - // expect(filteredOptions.userToInvite).toBe(null); - // }); - - // 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}, {}); - // // When we call filterAndOrderOptions with a search value that does not match any personal details - // const filteredOptions = filterAndOrderOptions(options, 'magneto', COUNTRY_CODE); - - // // Then no personal details should be returned - // expect(filteredOptions.personalDetails.length).toBe(0); - // }); - - // 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}, {}); - // // When we call filterAndOrderOptions with a search value that matches an email - // const filteredOptions = filterAndOrderOptions(options, 'peterparker@expensify.com', COUNTRY_CODE, {sortByReportTypeInSearch: true}); - - // // Then one recent report should be returned - // expect(filteredOptions.recentReports.length).toBe(1); - // // Then the returned recent report should match the search text - // expect(filteredOptions.recentReports.at(0)?.text).toBe('Spider-Man'); - // // Then no personal details should be returned - // expect(filteredOptions.personalDetails.length).toBe(0); - // }); - - // it('should return all matching reports and personal details', () => { - // // Given a set of options - // const options = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}); - // // 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}); - - // // Then there should be 4 matching personal details - // expect(filteredOptions.personalDetails.length).toBe(5); - // // Then the first personal detail should match the search text - // expect(filteredOptions.personalDetails.at(0)?.login).toBe('natasharomanoff@expensify.com'); - // // Then there should be 5 matching recent reports - // expect(filteredOptions.recentReports.length).toBe(5); - // expect(filteredOptions.recentReports.at(0)?.text).toBe('Captain America'); - // expect(filteredOptions.recentReports.at(1)?.text).toBe('Mr Sinister'); - // expect(filteredOptions.recentReports.at(2)?.text).toBe('Black Panther'); - // }); - - // it('should return matching option when searching (getSearchOptions)', () => { - // // Given a set of options - // const options = getSearchOptions({options: OPTIONS, draftComments: {}}); - // // When we call filterAndOrderOptions with a search value that matches a personal detail - // const filteredOptions = filterAndOrderOptions(options, 'spider', COUNTRY_CODE); - - // // Then one personal detail should be returned - // expect(filteredOptions.recentReports.length).toBe(1); - // // Then the returned personal detail should match the search text - // expect(filteredOptions.recentReports.at(0)?.text).toBe('Spider-Man'); - // }); - - // 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: {}}); - // // When we call filterAndOrderOptions with a search value that matches multiple items - // const filteredOptions = filterAndOrderOptions(options, 'fantastic', COUNTRY_CODE); - - // // Then only three reports should be returned - // expect(filteredOptions.recentReports.length).toBe(3); - // // Then the first report should match the search text - // expect(filteredOptions.recentReports.at(0)?.text).toBe('Mister Fantastic'); - // // Then the second report should match the search text - // expect(filteredOptions.recentReports.at(1)?.text).toBe('Mister Fantastic, Invisible Woman'); - - // return waitForBatchedUpdates() - // .then(() => Onyx.set(ONYXKEYS.PERSONAL_DETAILS_LIST, PERSONAL_DETAILS_WITH_PERIODS)) - // .then(() => { - // // 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: {}}); - // // When we pass the returned options to filterAndOrderOptions with a search value - // const filteredResults = filterAndOrderOptions(results, 'barry.allen@expensify.com', COUNTRY_CODE, {sortByReportTypeInSearch: true}); - - // // Then only one report should be returned - // expect(filteredResults.recentReports.length).toBe(1); - // // Then the returned report should match the search text - // expect(filteredResults.recentReports.at(0)?.text).toBe('The Flash'); - // }); - // }); - - // it('should filter out duplicated entries by login', () => { - // const login = 'brucebanner@expensify.com'; - - // // Duplicate personalDetails entries and reassign to OPTIONS - // OPTIONS.personalDetails = OPTIONS.personalDetails.flatMap((obj) => [obj, {...obj}]); - - // // Given a set of options - // const options = getSearchOptions({options: OPTIONS, draftComments: {}, betas: [CONST.BETAS.ALL]}); - // // When we call filterAndOrderOptions with a an empty search value - // const filteredOptions = filterAndOrderOptions(options, '', COUNTRY_CODE); - // const matchingEntries = filteredOptions.personalDetails.filter((detail) => detail.login === login); - - // // Then there should be 2 unique login entries - // expect(filteredOptions.personalDetails.length).toBe(3); - // // Then there should be 1 matching entry - // expect(matchingEntries.length).toBe(1); - // }); - - // it('should order self dm always on top if the search matches with the self dm login', () => { - // const searchTerm = 'tonystark@expensify.com'; - // 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: {}, betas: [CONST.BETAS.ALL]}); - // // When we call filterAndOrderOptions with a search value - // const filteredOptions = filterAndOrderOptions(options, searchTerm, COUNTRY_CODE); - - // // Then the self dm should be on top. - // expect(filteredOptions.recentReports.at(0)?.isSelfDM).toBe(true); - // }); - // }); - - // describe('canCreateOptimisticPersonalDetailOption()', () => { - // const VALID_EMAIL = 'valid@email.com'; - // const currentUserEmail = 'tonystark@expensify.com'; - - // it('should allow to create optimistic personal detail option if email is valid', () => { - // const canCreate = canCreateOptimisticPersonalDetailOption({ - // searchValue: VALID_EMAIL, - // currentUserOption: { - // login: currentUserEmail, - // } as OptionData, - // // Note: in the past this would check for the existence of the email in the personalDetails list, this has changed. - // // We expect only filtered lists to be passed to this function, so we don't need to check for the existence of the email in the personalDetails list. - // // This is a performance optimization. - // personalDetailsOptions: [], - // recentReportOptions: [], - // }); - - // expect(canCreate).toBe(true); - // }); - - // it('should not allow to create option if email is an email of current user', () => { - // // Given a set of arguments with currentUserOption object - // // When we call canCreateOptimisticPersonalDetailOption - // const canCreate = canCreateOptimisticPersonalDetailOption({ - // searchValue: currentUserEmail, - // recentReportOptions: [], - // personalDetailsOptions: [], - // currentUserOption: { - // login: currentUserEmail, - // } as OptionData, - // }); - - // // Then the returned value should be false - // expect(canCreate).toBe(false); - // }); - - // it('createOptionList() localization', () => { - // renderLocaleContextProvider(); - // // Given a set of reports and personal details - // // When we call createOptionList and extract the reports - // const reports = createOptionList(PERSONAL_DETAILS, REPORTS).reports; - - // // Then the returned reports should match the expected values - // expect(reports.at(10)?.subtitle).toBe(`Submits to Mister Fantastic`); - - // return ( - // waitForBatchedUpdates() - // // When we set the preferred locale to Spanish - // .then(() => Onyx.set(ONYXKEYS.NVP_PREFERRED_LOCALE, CONST.LOCALES.ES)) - // .then(() => { - // // When we call createOptionList again - // const newReports = createOptionList(PERSONAL_DETAILS, REPORTS).reports; - // // Then the returned reports should change to Spanish - // // cspell:disable-next-line - // expect(newReports.at(10)?.subtitle).toBe('Se envía a Mister Fantastic'); - // }) - // ); - // }); - // }); - - // describe('filterWorkspaceChats()', () => { - // it('should return an empty array if there are no expense chats', () => { - // // Given an empty array of expense chats and no search terms - // // When we call filterWorkspaceChats - // const result = filterWorkspaceChats([], []); - - // // Then the returned value should be an empty array - // expect(result.length).toEqual(0); - // }); - - // it('should return all expense chats if there are no search terms', () => { - // // Given a list of expense chats and no search terms - // // When we call filterWorkspaceChats - // const result = filterWorkspaceChats(WORKSPACE_CHATS, []); - - // // Then the returned value should be the same as the input - // expect(result).toEqual(WORKSPACE_CHATS); - // // Then the length of the result should be equal to the length of the input - // expect(result.length).toEqual(WORKSPACE_CHATS.length); - // }); - - // it('should filter multiple expense chats by search term', () => { - // // Given a list of expense chats and one search term - // // When we call filterWorkspaceChats - // const result = filterWorkspaceChats(WORKSPACE_CHATS, ['Google']); - - // // Then the returned value should should only include the matching expense chats - // expect(result.length).toEqual(2); - // }); - - // it('should filter expense chat by exact name', () => { - // // Given a list of expense chats and multiple search terms that reflect the exact name - // // When we call filterWorkspaceChats - // const result = filterWorkspaceChats(WORKSPACE_CHATS, ['Microsoft', 'Teams', 'Workspace']); - - // // Then the returned value should should only include the matching expense chat - // expect(result.length).toEqual(1); - // }); - - // it('should return an empty array if there are no matching expense chats', () => { - // // Given a list of expense chats and a search term that does not match any expense chats - // // When we call filterWorkspaceChats - // const result = filterWorkspaceChats(WORKSPACE_CHATS, ['XYZ']); - - // // Then the returned value should be an empty array - // expect(result.length).toEqual(0); - // }); - // }); - - // describe('orderWorkspaceOptions()', () => { - // it('should put the default workspace on top of the list', () => { - // // Given a list of expense chats - // // When we call orderWorkspaceOptions - // const result = orderWorkspaceOptions(WORKSPACE_CHATS); - - // // Then the first item in the list should be the default workspace - // expect(result.at(0)?.text).toEqual('Notion Workspace for Marketing'); - // }); - // }); - - // describe('Alternative text', () => { - // it("The text should not contain the last actor's name at prefix if the report is archived.", async () => { - // renderLocaleContextProvider(); - // // When we set the preferred locale to English and create an ADD_COMMENT report action - // await Onyx.multiSet({ - // [ONYXKEYS.NVP_PREFERRED_LOCALE]: CONST.LOCALES.EN, - // [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}10` as const]: { - // '1': getFakeAdvancedReportAction(CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT), - // }, - // }); - // // When we call createOptionList - // const reports = createOptionList(PERSONAL_DETAILS, REPORTS).reports; - // const archivedReport = reports.find((report) => report.reportID === '10'); - - // // Then the returned report should contain default archived reason - // expect(archivedReport?.lastMessageText).toBe('This chat room has been archived.'); - // }); - // }); - - // describe('filterSelfDMChat()', () => { - // const REPORT = { - // reportID: '1', - // text: 'Google Workspace', - // policyID: '11', - // isPolicyExpenseChat: true, - // }; - // const LOGIN = 'johndoe@test.com'; - // const ALTERNATE_TEXT = 'John William Doe'; - // const SUBTITLE = 'Software Engineer'; - - // it('should return the report when there are no search terms', () => { - // // Given a report object - // // When we call filterSelfDMChat with the report and no search terms - // const result = filterSelfDMChat(REPORT, []); - - // // Then the returned value should be the same as the input - // expect(result?.reportID).toEqual(REPORT.reportID); - // }); - - // it('should return undefined, when the search term does not match the report', () => { - // // Given a report object - // // When we call filterSelfDMChat with the report and a search term that does not match the report - // const result = filterSelfDMChat(REPORT, ['XYZ']); - - // // Then the returned value should be undefined - // expect(result).toBeUndefined(); - // }); - - // it('should filter report by text', () => { - // // Given a report object - // // When we call filterSelfDMChat with the report and search term that matches the report - // const result = filterSelfDMChat(REPORT, ['Google']); - - // // Then the returned value should be the same as the input - // expect(result?.reportID).toEqual(REPORT.reportID); - // }); - - // it('should filter report by exact text', () => { - // // Given a report object - // // When we call filterSelfDMChat with the report and multiple search terms that match the report's exact name - // const result = filterSelfDMChat(REPORT, ['Google', 'Workspace']); - - // // Then the returned value should be the same as the input - // expect(result?.reportID).toEqual(REPORT.reportID); - // }); - - // it('should filter report by login', () => { - // // Given a report object - // // When we call filterSelfDMChat with the report and a search term that matches the report's login - // const result = filterSelfDMChat({...REPORT, login: LOGIN}, ['john']); - - // // Then the returned value should be the same as the input - // expect(result?.reportID).toEqual(REPORT.reportID); - // }); - - // it('should filter report by exact login', () => { - // // Given a report object - // // When we call filterSelfDMChat with the report and multiple search terms that match the report's exact login - // const result = filterSelfDMChat({...REPORT, login: LOGIN}, [LOGIN]); - - // // Then the returned value should be the same as the input - // expect(result?.reportID).toEqual(REPORT.reportID); - // }); - - // it('should filter report by alternate text', () => { - // // Given a report object - // // When we call filterSelfDMChat with the report and a search term that matches the report's alternate text - // const result = filterSelfDMChat({...REPORT, alternateText: ALTERNATE_TEXT, isThread: true}, ['William']); - - // // Then the returned value should be the same as the input - // expect(result?.reportID).toEqual(REPORT.reportID); - // }); - - // it('should filter report by exact alternate text', () => { - // // Given a report object that is a thread - // // When we call filterSelfDMChat with the report and multiple search terms that match the report's exact alternate text - // const result = filterSelfDMChat({...REPORT, alternateText: ALTERNATE_TEXT, isThread: true}, ['John', 'William', 'Doe']); - - // // Then the returned value should be the same as the input - // expect(result?.reportID).toEqual(REPORT.reportID); - // }); - - // it('should filter report by alternate text if it is not a thread', () => { - // // Given a report object that is not a thread - // // When we call filterSelfDMChat with the report and a search term that matches the report's alternate text - // const result = filterSelfDMChat({...REPORT, alternateText: ALTERNATE_TEXT, isThread: false}, ['William']); - - // // Then the returned value should be undefined - // expect(result?.reportID).toBeUndefined(); - // }); - - // it('should filter report by subtitle', () => { - // // Given a report object - // // When we call filterSelfDMChat with the report and a search term that matches the report's subtitle - // const result = filterSelfDMChat({...REPORT, subtitle: SUBTITLE}, ['Software']); - - // // Then the returned value should be the same as the input - // expect(result?.reportID).toEqual(REPORT.reportID); - // }); - - // it('should filter report by exact subtitle', () => { - // // Given a report object - // // When we call filterSelfDMChat with the report and multiple search terms that match the report's exact subtitle - // const result = filterSelfDMChat({...REPORT, subtitle: SUBTITLE}, ['Software', 'Engineer']); - - // // Then the returned value should be the same as the input - // expect(result?.reportID).toEqual(REPORT.reportID); - // }); - - // it('should not filter report by subtitle if it is not an expense chat nor a chat room', () => { - // // Given a report object that is not an expense chat nor a chat room - // // When we call filterSelfDMChat with the report and a search term that matches the report's subtitle - // const result = filterSelfDMChat({...REPORT, subtitle: SUBTITLE, isPolicyExpenseChat: false, isChatRoom: false}, ['Software']); - - // // Then the returned value should be undefined - // expect(result).toBeUndefined(); - // }); - - // it('should filter report by subtitle if it is a chat room', () => { - // // Given a report object that is not an expense chat but is a chat room - // // When we call filterSelfDMChat with the report and a search term that matches the report's subtitle - // const result = filterSelfDMChat({...REPORT, subtitle: SUBTITLE, isPolicyExpenseChat: false, isChatRoom: true}, ['Software']); - - // // Then the returned value should be the same as the input - // expect(result?.reportID).toEqual(REPORT.reportID); - // }); - // }); - - // describe('filterReports()', () => { - // it('should match a user with an accented name when searching using non-accented characters', () => { - // // Given a report with accented characters in the text property - // // cspell:disable-next-line - // const reports = [{text: "Álex Timón D'artagnan Zo-e"} as OptionData]; - // // Given a search term with non-accented characters - // // cspell:disable-next-line - // const searchTerms = ['Alex Timon Dartagnan Zoe']; - // // When we call filterReports with the report and search terms - // const filteredReports = filterReports(reports, searchTerms); - - // // Then the returned value should match the search term - // expect(filteredReports).toEqual(reports); - // }); - // }); - - // describe('getMostRecentOptions()', () => { - // it('returns the most recent options up to the specified limit', () => { - // const options: OptionData[] = [ - // {reportID: '1', lastVisibleActionCreated: '2022-01-01T10:00:00Z'} as OptionData, - // {reportID: '2', lastVisibleActionCreated: '2022-01-01T12:00:00Z'} as OptionData, - // {reportID: '3', lastVisibleActionCreated: '2022-01-01T09:00:00Z'} as OptionData, - // {reportID: '4', lastVisibleActionCreated: '2022-01-01T13:00:00Z'} as OptionData, - // ]; - // const comparator = (option: OptionData) => option.lastVisibleActionCreated ?? ''; - // const result = optionsOrderBy(options, comparator, 2); - // expect(result.length).toBe(2); - // // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - // expect(result.at(0)!.reportID).toBe('4'); - // // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - // expect(result.at(1)!.reportID).toBe('2'); - // }); - - // it('returns all options if limit is greater than options length', () => { - // const options: OptionData[] = [ - // {reportID: '1', lastVisibleActionCreated: '2022-01-01T10:00:00Z'} as OptionData, - // {reportID: '2', lastVisibleActionCreated: '2022-01-01T12:00:00Z'} as OptionData, - // ]; - // const comparator = (option: OptionData) => option.lastVisibleActionCreated ?? ''; - // const result = optionsOrderBy(options, comparator, 5); - // expect(result.length).toBe(2); - // // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - // expect(result.at(0)!.reportID).toBe('2'); - // // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - // expect(result.at(1)!.reportID).toBe('1'); - // }); - - // it('returns empty array if options is empty', () => { - // const result = optionsOrderBy([], recentReportComparator, 3); - // expect(result).toEqual([]); - // }); - - // it('applies filter function if provided', () => { - // const options: OptionData[] = [ - // {reportID: '1', lastVisibleActionCreated: '2022-01-01T10:00:00Z', isPinned: true} as OptionData, - // {reportID: '2', lastVisibleActionCreated: '2022-01-01T12:00:00Z', isPinned: false} as OptionData, - // {reportID: '3', lastVisibleActionCreated: '2022-01-01T09:00:00Z', isPinned: true} as OptionData, - // ]; - // const comparator = (option: OptionData) => option.lastVisibleActionCreated ?? ''; - // const result = optionsOrderBy(options, comparator, 2, (option) => option.isPinned); - // expect(result.length).toBe(2); - // // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - // expect(result.at(0)!.reportID).toBe('1'); - // // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - // expect(result.at(1)!.reportID).toBe('3'); - // }); - - // it('handles negative limit by returning empty array', () => { - // const options: OptionData[] = [ - // {reportID: '1', lastVisibleActionCreated: '2022-01-01T10:00:00Z'} as OptionData, - // {reportID: '2', lastVisibleActionCreated: '2022-01-01T12:00:00Z'} as OptionData, - // {reportID: '3', lastVisibleActionCreated: '2022-01-01T09:00:00Z'} as OptionData, - // ]; - // const comparator = (option: OptionData) => option.lastVisibleActionCreated ?? ''; - // const result = optionsOrderBy(options, comparator, -1); - // expect(result).toEqual([]); - // }); - - // it('handles negative limit with large absolute value', () => { - // const options: OptionData[] = [ - // {reportID: '1', lastVisibleActionCreated: '2022-01-01T10:00:00Z'} as OptionData, - // {reportID: '2', lastVisibleActionCreated: '2022-01-01T12:00:00Z'} as OptionData, - // ]; - // const comparator = (option: OptionData) => option.lastVisibleActionCreated ?? ''; - // const result = optionsOrderBy(options, comparator, -100); - // expect(result).toEqual([]); - // }); - - // it('handles limit equal to zero', () => { - // const options: OptionData[] = [ - // {reportID: '1', lastVisibleActionCreated: '2022-01-01T10:00:00Z'} as OptionData, - // {reportID: '2', lastVisibleActionCreated: '2022-01-01T12:00:00Z'} as OptionData, - // ]; - // const comparator = (option: OptionData) => option.lastVisibleActionCreated ?? ''; - // const result = optionsOrderBy(options, comparator, 0); - // expect(result).toEqual([]); - // }); - // }); - - // describe('sortAlphabetically', () => { - // it('should sort options alphabetically by text', () => { - // const options: OptionData[] = [{text: 'Banana', reportID: '1'} as OptionData, {text: 'Apple', reportID: '2'} as OptionData, {text: 'Cherry', reportID: '3'} as OptionData]; - // const sortedOptions = sortAlphabetically(options, 'text', localeCompare); - // expect(sortedOptions.at(0)?.reportID).toBe('2'); - // expect(sortedOptions.at(1)?.reportID).toBe('1'); - // expect(sortedOptions.at(2)?.reportID).toBe('3'); - // }); - - // it('should handle empty array', () => { - // const sortedOptions = sortAlphabetically([], 'abc', localeCompare); - // expect(sortedOptions).toEqual([]); - // }); - - // it('should handle single option', () => { - // const options: OptionData[] = [{text: 'Single', reportID: '1'} as OptionData]; - // const sortedOptions = sortAlphabetically(options, 'text', localeCompare); - // expect(sortedOptions.length).toBe(1); - // expect(sortedOptions.at(0)?.text).toBe('Single'); - // }); - // }); - // describe('getSearchValueForPhoneOrEmail', () => { - // it('should return E164 format for valid phone number', () => { - // const result = getSearchValueForPhoneOrEmail('+1 (234) 567-8901', 1); - // expect(result).toBe('+12345678901'); - // }); - - // it('should return E164 format for valid international phone number', () => { - // const result = getSearchValueForPhoneOrEmail('+44 20 8759 9036', 44); - // expect(result).toBe('+442087599036'); - // }); - - // it('should return lowercase for email address', () => { - // const result = getSearchValueForPhoneOrEmail('Test@Example.COM', 1); - // expect(result).toBe('test@example.com'); - // }); - - // it('should handle SMS domain removal for valid phone number', () => { - // const result = getSearchValueForPhoneOrEmail('+12345678901@expensify.sms', 1); - // expect(result).toBe('+12345678901'); - // }); - - // it('should return empty string for empty input', () => { - // const result = getSearchValueForPhoneOrEmail('', 1); - // expect(result).toBe(''); - // }); - // }); - - // describe('createOption', () => { - // it('should return alternative text correctly when the last action is report preview action', async () => { - // const report = { - // chatType: '', - // currency: 'USD', - // description: '', - // errorFields: {}, - // hasOutstandingChildRequest: false, - // hasOutstandingChildTask: false, - // iouReportID: '456', - // lastMessageHtml: '', - // lastMessageText: '', - // participants: { - // '1': { - // notificationPreference: 'always', - // }, - // '2': { - // notificationPreference: 'always', - // }, - // }, - // reportID: '123', - // type: 'chat', - // lastActorAccountID: 1, - // } as unknown as Report; - - // const reportPreviewAction = { - // actionName: 'REPORTPREVIEW', - // actorAccountID: 1, - // childManagerAccountID: 2, - // childOwnerAccountID: 1, - // childReportID: '456', - // childReportName: 'IOU', - // created: '2025-10-02 06:50:36.302', - // reportActionID: '12345678', - // shouldShow: true, - // message: [ - // { - // html: 'Iron Man owes ₫34', - // text: 'Iron Man owes ₫34', - // type: 'COMMENT', - // whisperedTo: [], - // }, - // ], - // } as unknown as ReportAction; - - // const iouReport = { - // chatReportID: '123', - // currency: 'VND', - // managerID: 2, - // ownerAccountID: 1, - // parentReportActionID: '12345678', - // parentReportID: '123', - // participants: { - // '19960856': { - // notificationPreference: '', - // }, - // '20669492': { - // notificationPreference: '', - // }, - // }, - // reportID: '456', - // reportName: 'IOU', - // total: 3400, - // } as unknown as Report; - - // const iouAction = { - // actorAccountID: 1, - // message: [ - // { - // type: 'COMMENT', - // html: '₫34 expense', - // text: '₫34 expense', - // isEdited: false, - // whisperedTo: [], - // isDeletedParentAction: false, - // deleted: '', - // reactions: [], - // }, - // ], - // originalMessage: { - // IOUReportID: '456', - // IOUTransactionID: '123456', - // amount: 3400, - // comment: '', - // currency: 'VND', - // participantAccountIDs: [1, 2], - // }, - // actionName: 'IOU', - // reportActionID: '789', - // } as unknown as ReportAction; - - // const transaction = { - // transactionID: '123456', - // amount: 3400, - // currency: 'VND', - // reportID: '3993091505909230', - // comment: { - // comment: '', - // }, - // merchant: '(none)', - // created: '2025-10-02', - // category: '', - // taxAmount: 0, - // reimbursable: true, - // } as unknown as Transaction; - - // await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`, report); - // await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`, {[reportPreviewAction.reportActionID]: reportPreviewAction}); - // await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`, iouReport); - // await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`, {[iouAction.reportActionID]: iouAction}); - // await Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`, transaction); - // await waitForBatchedUpdates(); - - // const result = createOption([1, 2], PERSONAL_DETAILS, report, {showChatPreviewLine: true}); - - // expect(result.alternateText).toBe('Iron Man owes ₫34'); - // }); - // }); + describe('getSearchOptions()', () => { + 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: {}, betas: [CONST.BETAS.ALL]}); + + // Then all personal details (including those that have reports) should be returned + expect(results.personalDetails.length).toBe(10); + + // Then all of the reports should be shown including the archived rooms, except for the thread report with notificationPreferences hidden. + expect(results.recentReports.length).toBe(Object.values(OPTIONS.reports).length - 1); + }); + + it('should include current user when includeCurrentUser is true for type:chat from suggestions', () => { + // Given a set of options where the current user is Iron Man (accountID: 2) + // When we call getSearchOptions with includeCurrentUser set to true + const results = getSearchOptions({ + options: OPTIONS, + draftComments: {}, + betas: [CONST.BETAS.ALL], + isUsedInChatFinder: true, + includeReadOnly: true, + searchQuery: '', + maxResults: undefined, + includeUserToInvite: false, + includeRecentReports: true, + includeCurrentUser: true, + }); + + // Then the current user should be included in personalDetails + const currentUserOption = results.personalDetails.find((option) => option.login === 'tonystark@expensify.com'); + expect(currentUserOption).toBeDefined(); + expect(currentUserOption?.text).toBe('Iron Man'); + expect(currentUserOption?.accountID).toBe(2); + + // Then all personal details including the current user should be returned + expect(results.personalDetails.length).toBe(11); + }); + + it('should exclude current user when includeCurrentUser is false', () => { + // Given a set of options where the current user is Iron Man (accountID: 2) + // When we call getSearchOptions with includeCurrentUser set to false (default behavior) + const results = getSearchOptions({ + options: OPTIONS, + draftComments: {}, + betas: [CONST.BETAS.ALL], + isUsedInChatFinder: true, + includeReadOnly: true, + searchQuery: '', + maxResults: undefined, + includeUserToInvite: false, + includeRecentReports: true, + }); + + // Then the current user should not be included in personalDetails + const currentUserOption = results.personalDetails.find((option) => option.login === 'tonystark@expensify.com'); + expect(currentUserOption).toBeUndefined(); + + // Then all personal details except the current user should be returned + expect(results.personalDetails.length).toBe(10); + }); + }); + + describe('orderOptions()', () => { + it('should sort options alphabetically and preserves reportID for personal details with existing reports', () => { + // Given a set of reports and personalDetails + // When we call getValidOptions() + let results: Pick = getValidOptions( + { + reports: OPTIONS.reports, + personalDetails: OPTIONS.personalDetails, + }, + {}, + ); + // When we call orderOptions() + results = orderOptions(results); + + // Then all personalDetails except the currently logged in user should be returned + expect(results.personalDetails.length).toBe(Object.values(OPTIONS.personalDetails).length - 1); + + const expected = [ + 'Black Panther', + 'Black Widow', + 'Captain America', + 'Invisible Woman', + 'Mister Fantastic', + 'Mr Sinister', + 'Spider-Man', + 'The Incredible Hulk', + 'Thor', + 'Timothée', + ]; + const actual = results.personalDetails?.map((item) => item.text); + + // Then the results should be sorted alphabetically + expect(actual).toEqual(expected); + + const personalDetailWithExistingReport = results.personalDetails.find((personalDetail) => personalDetail.login === 'peterparker@expensify.com'); + // Then the result which has an existing report should also have the reportID attached + expect(personalDetailWithExistingReport?.reportID).toBe('2'); + }); + + 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: []}, {}); + // When we call orderOptions() + results = orderOptions(results); + + const expected = [ + 'Black Panther', + 'Black Widow', + 'Captain America', + 'Invisible Woman', + 'Mister Fantastic', + 'Mr Sinister', + 'Spider-Man', + 'The Incredible Hulk', + 'Thor', + 'Timothée', + ]; + const actual = results.personalDetails?.map((item) => item.text); + + // Then the results should be sorted alphabetically + expect(actual).toEqual(expected); + }); + }); + + describe('getValidOptions()', () => { + 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: []}, {}); + + // Then the result should be empty + expect(results.personalDetails).toEqual([]); + expect(results.recentReports).toEqual([]); + expect(results.currentUserOption).toBeUndefined(); + expect(results.userToInvite).toEqual(null); + expect(results.workspaceChats).toEqual([]); + expect(results.selfDMChat).toEqual(undefined); + }); + + 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}, {}); + + // 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); + // Then the result should include Concierge + expect(results.recentReports).toEqual(expect.arrayContaining([expect.objectContaining({login: 'concierge@expensify.com'})])); + }); + + it('should exclude Concierge when excludedLogins is specified', () => { + // Given a set of reports and personalDetails that includes Concierge and a config object that excludes Concierge + // When we call getValidOptions() + const results = getValidOptions( + { + reports: OPTIONS_WITH_CONCIERGE.reports, + personalDetails: OPTIONS_WITH_CONCIERGE.personalDetails, + }, + {}, + { + excludeLogins: {[CONST.EMAIL.CONCIERGE]: true}, + }, + ); + + // Then the result should include all personalDetails except the currently logged in user and Concierge + expect(results.personalDetails.length).toBe(Object.values(OPTIONS_WITH_CONCIERGE.personalDetails).length - 2); + // Then the result should not include Concierge + expect(results.personalDetails).not.toEqual(expect.arrayContaining([expect.objectContaining({login: 'concierge@expensify.com'})])); + }); + + 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}, + {}, + {excludeLogins: {[CONST.EMAIL.CHRONOS]: true}}, + ); + + // Then the result should include all personalDetails except the currently logged in user and Chronos + expect(results.personalDetails.length).toBe(Object.values(OPTIONS_WITH_CHRONOS.personalDetails).length - 2); + // Then the result should not include Chronos + expect(results.personalDetails).not.toEqual(expect.arrayContaining([expect.objectContaining({login: 'chronos@expensify.com'})])); + }); + + it('should exclude Receipts option from results when excludedLogins is specified', () => { + // Given a set of reports and personalDetails that includes receipts and a config object that excludes receipts + // When we call getValidOptions() + const results = getValidOptions( + { + reports: OPTIONS_WITH_RECEIPTS.reports, + personalDetails: OPTIONS_WITH_RECEIPTS.personalDetails, + }, + {}, + { + excludeLogins: {[CONST.EMAIL.RECEIPTS]: true}, + }, + ); + + // Then the result should include all personalDetails except the currently logged in user and receipts + expect(results.personalDetails.length).toBe(Object.values(OPTIONS_WITH_RECEIPTS.personalDetails).length - 2); + // Then the result should not include receipts + expect(results.personalDetails).not.toEqual(expect.arrayContaining([expect.objectContaining({login: 'receipts@expensify.com'})])); + }); + + it('should include Manager McTest in results by default', () => { + // Given a set of reports and personalDetails that includes Manager McTest + // When we call getValidOptions() + const result = getValidOptions( + {reports: OPTIONS_WITH_MANAGER_MCTEST.reports, personalDetails: OPTIONS_WITH_MANAGER_MCTEST.personalDetails}, + {}, + {includeP2P: true, canShowManagerMcTest: true, betas: [CONST.BETAS.NEWDOT_MANAGER_MCTEST]}, + ); + + // Then the result should include all personalDetails except the currently logged in user + expect(result.personalDetails.length).toBe(Object.values(OPTIONS_WITH_MANAGER_MCTEST.personalDetails).length - 1); + // Then the result should include Manager McTest + expect(result.personalDetails).toEqual(expect.arrayContaining([expect.objectContaining({login: CONST.EMAIL.MANAGER_MCTEST})])); + }); + + 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}, + {}, + {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); + // Then the result should not include Manager McTest + expect(result.personalDetails).not.toEqual(expect.arrayContaining([expect.objectContaining({login: CONST.EMAIL.MANAGER_MCTEST})])); + }); + + it('should exclude Manager McTest from results if user dismissed the tooltip', () => { + return waitForBatchedUpdates() + .then(() => + // Given that the user has dismissed the tooltip + Onyx.set(ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING, { + [CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.SCAN_TEST_TOOLTIP]: { + timestamp: DateUtils.getDBTime(new Date().valueOf()), + }, + }), + ) + .then(() => { + // When we call getValidOptions() + const optionsWhenUserAlreadySubmittedExpense = getValidOptions( + {reports: OPTIONS_WITH_MANAGER_MCTEST.reports, personalDetails: OPTIONS_WITH_MANAGER_MCTEST.personalDetails}, + {}, + {includeP2P: true, canShowManagerMcTest: true, betas: [CONST.BETAS.NEWDOT_MANAGER_MCTEST]}, + ); + + // Then the result should include all personalDetails except the currently logged in user and Manager McTest + expect(optionsWhenUserAlreadySubmittedExpense.personalDetails.length).toBe(Object.values(OPTIONS_WITH_MANAGER_MCTEST.personalDetails).length - 2); + // Then the result should not include Manager McTest + expect(optionsWhenUserAlreadySubmittedExpense.personalDetails).not.toEqual(expect.arrayContaining([expect.objectContaining({login: CONST.EMAIL.MANAGER_MCTEST})])); + }); + }); + + it('should keep admin rooms if specified', () => { + // Given an admin room report search option + const adminRoom: SearchOption = { + item: { + chatType: 'policyAdmins', + currency: 'USD', + errorFields: {}, + lastActionType: 'CREATED', + lastReadTime: '2025-03-21 07:25:46.279', + lastVisibleActionCreated: '2024-12-15 21:13:24.317', + lastVisibleActionLastModified: '2024-12-15 21:13:24.317', + ownerAccountID: 0, + permissions: ['read', 'write'], + policyID: '52A5ABD88FBBD18F', + policyName: "David's Playground", + reportID: '1455140530846319', + reportName: '#admins', + type: 'chat', + writeCapability: 'all', + }, + text: '#admins', + alternateText: "David's Playground", + allReportErrors: {}, + subtitle: "David's Playground", + participantsList: [], + reportID: '1455140530846319', + keyForList: '1455140530846319', + isDefaultRoom: true, + isChatRoom: true, + policyID: '52A5ABD88FBBD18F', + lastMessageText: '', + lastVisibleActionCreated: '2024-12-15 21:13:24.317', + notificationPreference: 'hidden', + }; + // When we call getValidOptions with includeMultipleParticipantReports set to true + const results = getValidOptions( + {reports: [adminRoom], personalDetails: OPTIONS.personalDetails}, + {}, + { + includeMultipleParticipantReports: true, + }, + ); + const adminRoomOption = results.recentReports.find((report) => report.reportID === '1455140530846319'); + + // Then the result should include the admin room + expect(adminRoomOption).toBeDefined(); + }); + + it('should include brickRoadIndicator if showRBR is true', () => { + const reportID = '1455140530846319'; + const workspaceChat: SearchOption = { + item: { + chatType: CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT, + currency: 'USD', + errorFields: {}, + lastActionType: 'CREATED', + lastReadTime: '2025-03-21 07:25:46.279', + lastVisibleActionCreated: '2024-12-15 21:13:24.317', + lastVisibleActionLastModified: '2024-12-15 21:13:24.317', + ownerAccountID: 0, + permissions: ['read', 'write'], + participants: {1: {notificationPreference: 'always'}}, + policyID: '52A5ABD88FBBD18F', + policyName: "A's Workspace", + reportID, + reportName: "A's Workspace chat", + type: 'chat', + writeCapability: 'all', + }, + text: "A's Workspace chat", + alternateText: "A's Workspace", + allReportErrors: {}, + subtitle: "A's Workspace", + participantsList: [], + reportID, + keyForList: '1455140530846319', + isDefaultRoom: true, + isChatRoom: true, + policyID: '52A5ABD88FBBD18F', + lastMessageText: '', + lastVisibleActionCreated: '2024-12-15 21:13:24.317', + notificationPreference: 'hidden', + brickRoadIndicator: CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR, + }; + const results = getValidOptions( + {reports: [workspaceChat], personalDetails: []}, + {}, + { + includeMultipleParticipantReports: true, + showRBR: true, + }, + ); + expect(results.recentReports.at(0)?.brickRoadIndicator).toBe(CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR); + }); + + it('should not include brickRoadIndicator if showRBR is false', () => { + const reportID = '1455140530846319'; + const workspaceChat: SearchOption = { + item: { + chatType: CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT, + currency: 'USD', + errorFields: {}, + lastActionType: 'CREATED', + lastReadTime: '2025-03-21 07:25:46.279', + lastVisibleActionCreated: '2024-12-15 21:13:24.317', + lastVisibleActionLastModified: '2024-12-15 21:13:24.317', + ownerAccountID: 0, + permissions: ['read', 'write'], + participants: {1: {notificationPreference: 'always'}}, + policyID: '52A5ABD88FBBD18F', + policyName: "A's Workspace", + reportID, + reportName: "A's Workspace chat", + type: 'chat', + writeCapability: 'all', + }, + text: "A's Workspace chat", + alternateText: "A's Workspace", + allReportErrors: {}, + subtitle: "A's Workspace", + participantsList: [], + reportID, + keyForList: '1455140530846319', + isDefaultRoom: true, + isChatRoom: true, + policyID: '52A5ABD88FBBD18F', + lastMessageText: '', + lastVisibleActionCreated: '2024-12-15 21:13:24.317', + notificationPreference: 'hidden', + brickRoadIndicator: CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR, + }; + const results = getValidOptions( + {reports: [workspaceChat], personalDetails: []}, + {}, + { + includeMultipleParticipantReports: true, + showRBR: false, + }, + ); + expect(results.recentReports.at(0)?.brickRoadIndicator).toBe(null); + }); + }); + + describe('getValidOptions() for chat room', () => { + 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, + {}, + { + includeRecentReports: true, + includeMultipleParticipantReports: true, + includeP2P: true, + includeOwnedWorkspaceChats: true, + }, + ); + + // Then the result should include all reports except the currently logged in user + expect(results.recentReports.length).toBe(OPTIONS_WITH_WORKSPACE_ROOM.reports.length - 1); + expect(results.recentReports).toEqual(expect.arrayContaining([expect.objectContaining({reportID: '14'})])); + }); + }); + + describe('getValidOptions() for group Chat', () => { + 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}, {}); + const reportLogins = results.recentReports.map((reportOption) => reportOption.login); + const personalDetailsOverlapWithReports = results.personalDetails.every((personalDetailOption) => reportLogins.includes(personalDetailOption.login)); + + // Then we should expect all the personalDetails to show except the currently logged in user + expect(results.personalDetails.length).toBe(Object.values(OPTIONS.personalDetails).length - 1); + // Then none of our personalDetails should include any of the users with recent reports + expect(personalDetailsOverlapWithReports).toBe(false); + }); + + 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}, {}, {excludeLogins: {'peterparker@expensify.com': true}}); + + // Then the option should not appear anywhere in either list + expect(results.recentReports.every((option) => option.login !== 'peterparker@expensify.com')).toBe(true); + expect(results.personalDetails.every((option) => option.login !== 'peterparker@expensify.com')).toBe(true); + }); + + 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}, {}); + + // 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); + // Then Concierge should be included in the results + expect(results.recentReports).toEqual(expect.arrayContaining([expect.objectContaining({login: 'concierge@expensify.com'})])); + }); + + it('should exclude Concierge from the results when it is specified in excludedLogins', () => { + // Given a set of reports and personalDetails that includes Concierge + // When we call getValidOptions with excludeLogins param + const results = getValidOptions( + { + reports: OPTIONS_WITH_CONCIERGE.reports, + personalDetails: OPTIONS_WITH_CONCIERGE.personalDetails, + }, + {}, + { + excludeLogins: {[CONST.EMAIL.CONCIERGE]: true}, + }, + ); + + // Then the result should include all personalDetails except the currently logged in user and Concierge + expect(results.personalDetails.length).toBe(Object.values(OPTIONS_WITH_CONCIERGE.personalDetails).length - 2); + // Then none of the results should include Concierge + expect(results.personalDetails).not.toEqual(expect.arrayContaining([expect.objectContaining({login: 'concierge@expensify.com'})])); + expect(results.recentReports).not.toEqual(expect.arrayContaining([expect.objectContaining({login: 'concierge@expensify.com'})])); + }); + + 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}, + {}, + {excludeLogins: {[CONST.EMAIL.CHRONOS]: true}}, + ); + + // Then the result should include all personalDetails except the currently logged in user and Chronos + expect(results.personalDetails.length).toBe(Object.values(OPTIONS_WITH_CHRONOS.personalDetails).length - 2); + // Then none of the results should include Chronos + expect(results.personalDetails).not.toEqual(expect.arrayContaining([expect.objectContaining({login: 'chronos@expensify.com'})])); + expect(results.recentReports).not.toEqual(expect.arrayContaining([expect.objectContaining({login: 'chronos@expensify.com'})])); + }); + + it('should exclude Receipts from the results when it is specified in excludedLogins', () => { + // Given a set of reports and personalDetails that includes receipts + // When we call getValidOptions() with excludeLogins param + const results = getValidOptions( + { + reports: OPTIONS_WITH_RECEIPTS.reports, + personalDetails: OPTIONS_WITH_RECEIPTS.personalDetails, + }, + {}, + { + excludeLogins: {[CONST.EMAIL.RECEIPTS]: true}, + }, + ); + + // Then the result should include all personalDetails except the currently logged in user and receipts + expect(results.personalDetails.length).toBe(Object.values(OPTIONS_WITH_RECEIPTS.personalDetails).length - 2); + // Then none of the results should include receipts + expect(results.personalDetails).not.toEqual(expect.arrayContaining([expect.objectContaining({login: 'receipts@expensify.com'})])); + expect(results.recentReports).not.toEqual(expect.arrayContaining([expect.objectContaining({login: 'receipts@expensify.com'})])); + }); + + it('should limit recent reports when maxRecentReportElements is specified', () => { + // 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}, {}, {maxRecentReportElements: maxRecentReports}); + + // Then the recent reports should be limited to the specified number + expect(results.recentReports.length).toBeLessThanOrEqual(maxRecentReports); + }); + + 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}, {}); + const resultsWithLimit = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}, {maxRecentReportElements: 2}); + + // Then the results without limit should have more or equal reports + expect(resultsWithoutLimit.recentReports.length).toBeGreaterThanOrEqual(resultsWithLimit.recentReports.length); + }); + + 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}, {}); + const resultsWithLimit = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}, {maxRecentReportElements: 2}); + + // Then personalDetails should remain the same regardless of maxRecentReportElements + expect(resultsWithLimit.personalDetails.length).toBe(resultsWithoutLimit.personalDetails.length); + }); + + it('should respect maxRecentReportElements when combined with maxElements', () => { + // Given a set of reports and personalDetails + // When we call getValidOptions with both maxElements and maxRecentReportElements + const maxRecentReports = 3; + const maxTotalElements = 10; + const results = getValidOptions( + {reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, + {}, + {maxElements: maxTotalElements, maxRecentReportElements: maxRecentReports}, + ); + + // Then recent reports should be limited by maxRecentReportElements + expect(results.recentReports.length).toBeLessThanOrEqual(maxRecentReports); + // Then the total number of options (reports + personalDetails) should not exceed maxElements + expect(results.recentReports.length + results.personalDetails.length).toBeLessThanOrEqual(maxTotalElements); + }); + }); + + describe('getShareDestinationsOptions()', () => { + it('should exclude archived rooms and hidden threads from share destinations', () => { + // 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) => { + const report = option.item; + const {result: isReportArchived} = renderHook(() => useReportIsArchived(report.reportID)); + if (canUserPerformWriteAction(report, isReportArchived.current) && canCreateTaskInReport(report) && !isCanceledTaskReport(report)) { + filtered.push(option); + } + return filtered; + }, []); + + // When we call getValidOptions for share destination with an empty search value + const results = getValidOptions( + {reports: filteredReports, personalDetails: OPTIONS.personalDetails}, + {}, + { + betas: [], + includeMultipleParticipantReports: true, + showChatPreviewLine: true, + forcePolicyNamePreview: true, + includeThreads: true, + includeMoneyRequests: true, + includeTasks: true, + excludeLogins: {}, + includeOwnedWorkspaceChats: true, + includeSelfDM: true, + searchString: '', + includeUserToInvite: false, + }, + ); + + // Then all the recent reports should be returned except the archived rooms and the hidden thread + expect(results.recentReports.length).toBe(Object.values(OPTIONS.reports).length - 2); + }); + + it('should include DMS, group chats, and workspace rooms in share destinations', () => { + // Given a set of filtered current Reports (as we do in the component) with workspace rooms before getting share destination options + const filteredReportsWithWorkspaceRooms = Object.values(OPTIONS_WITH_WORKSPACE_ROOM.reports).reduce((filtered, option) => { + const report = option.item; + const {result: isReportArchived} = renderHook(() => useReportIsArchived(report.reportID)); + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + if (canUserPerformWriteAction(report, isReportArchived.current) || isExpensifyOnlyParticipantInReport(report)) { + filtered.push(option); + } + return filtered; + }, []); + + // When we call getValidOptions for share destination with an empty search value + const results = getValidOptions( + {reports: filteredReportsWithWorkspaceRooms, personalDetails: OPTIONS.personalDetails}, + {}, + { + betas: [], + includeMultipleParticipantReports: true, + showChatPreviewLine: true, + forcePolicyNamePreview: true, + includeThreads: true, + includeMoneyRequests: true, + includeTasks: true, + excludeLogins: {}, + includeOwnedWorkspaceChats: true, + includeSelfDM: true, + searchString: '', + includeUserToInvite: false, + }, + ); + + // Then all recent reports should be returned except the archived rooms and the hidden thread + expect(results.recentReports.length).toBe(Object.values(OPTIONS_WITH_WORKSPACE_ROOM.reports).length - 2); + }); + }); + + describe('getShareLogOptions()', () => { + it('should not include read-only report', () => { + // Given a list of 11 report options with reportID of 10 is archived + // When we call getShareLogOptions + const results = getShareLogOptions(OPTIONS, {}, []); + + // Then the report with reportID of 10 should not be included on the list + expect(results.recentReports.length).toBe(10); + expect(results.recentReports.find((report) => report.reportID === '10')).toBeUndefined(); + }); + }); + + describe('getMemberInviteOptions()', () => { + it('should sort personal details alphabetically', () => { + // Given a set of personalDetails + // When we call getMemberInviteOptions + const results = getMemberInviteOptions(OPTIONS.personalDetails, []); + + // 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'); + }); + }); + + describe('getLastActorDisplayName()', () => { + it('should return correct display name', () => { + renderLocaleContextProvider(); + // Given two different personal details + // When we call getLastActorDisplayName + const result1 = getLastActorDisplayName(PERSONAL_DETAILS['2']); + const result2 = getLastActorDisplayName(PERSONAL_DETAILS['3']); + + // We should expect the display names to be the same as the personal details + expect(result1).toBe('You'); + expect(result2).toBe('Spider-Man'); + }); + }); + + describe('formatMemberForList()', () => { + it('should format members correctly', () => { + // Given a set of personal details + // When we call formatMemberForList + const formattedMembers = Object.values(PERSONAL_DETAILS).map((personalDetail) => formatMemberForList(personalDetail)); + + // Then the formatted members' order should be the same as the original PERSONAL_DETAILS array + expect(formattedMembers.at(0)?.text).toBe('Mister Fantastic'); + expect(formattedMembers.at(1)?.text).toBe('Iron Man'); + expect(formattedMembers.at(2)?.text).toBe('Spider-Man'); + + // Then only the first item should be selected + expect(formattedMembers.at(0)?.isSelected).toBe(true); + // Then all remaining items should be unselected + expect(formattedMembers.slice(1).every((personalDetail) => !personalDetail.isSelected)).toBe(true); + // Then all items should be enabled + expect(formattedMembers.every((personalDetail) => !personalDetail.isDisabled)).toBe(true); + }); + }); + + describe('filterAndOrderOptions()', () => { + 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: {}, betas: [CONST.BETAS.ALL]}); + // When we pass the returned options to filterAndOrderOptions with an empty search value + const filteredOptions = filterAndOrderOptions(options, '', COUNTRY_CODE); + + // Then all options should be returned + expect(filteredOptions.recentReports.length + filteredOptions.personalDetails.length).toBe(14); + }); + + it('should return filtered options in correct order', () => { + const searchText = 'man'; + // Given a set of options + // When we call getSearchOptions with all betas + const options = getSearchOptions({options: OPTIONS, draftComments: {}, 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}); + + // Then we expect all options to be part of the recentReports list and reports should be first: + expect(filteredOptions.personalDetails.length).toBe(0); + // Then returned reports should match the search text + expect(filteredOptions.recentReports.length).toBe(4); + // Then the returned reports should be ordered by most recent action (and other criteria such as whether they are archived) + expect(filteredOptions.recentReports.at(0)?.text).toBe('Invisible Woman'); // '2022-11-22 03:26:02.019' + expect(filteredOptions.recentReports.at(1)?.text).toBe('Spider-Man'); // '2022-11-22 03:26:02.016' + expect(filteredOptions.recentReports.at(2)?.text).toBe('Black Widow'); // This is a personal detail, which has no lastVisibleActionCreated, but matches the login + expect(filteredOptions.recentReports.at(3)?.text).toBe('Mister Fantastic, Invisible Woman'); // This again is a report with '2022-11-22 03:26:02.015' + }); + + it('should filter users by email', () => { + const searchText = 'mistersinister@marauders.com'; + // Given a set of options + // When we call getSearchOptions with all betas + const options = getSearchOptions({options: OPTIONS, draftComments: {}, betas: [CONST.BETAS.ALL]}); + // When we pass the returned options to filterAndOrderOptions with a search value + const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE); + + // Then only one report should be returned + expect(filteredOptions.recentReports.length).toBe(1); + // Then the returned report should match the search text + expect(filteredOptions.recentReports.at(0)?.text).toBe('Mr Sinister'); + }); + + it('should find archived chats', () => { + const searchText = 'Archived'; + // Given a set of options + // When we call getSearchOptions with all betas + const options = getSearchOptions({options: OPTIONS, draftComments: {}, betas: [CONST.BETAS.ALL]}); + // When we pass the returned options to filterAndOrderOptions with a search value + const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE); + + // Then only one report should be returned + expect(filteredOptions.recentReports.length).toBe(1); + // Then the returned report should match the search text + expect(!!filteredOptions.recentReports.at(0)?.private_isArchived).toBe(true); + }); + + it('should filter options by email if dot is skipped in the email', () => { + // cspell:disable-next-line + const searchText = 'barryallen'; + // 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: {}, 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}); + + // Then only one report should be returned + expect(filteredOptions.recentReports.length).toBe(1); + // Then the returned report should match the search text + expect(filteredOptions.recentReports.at(0)?.login).toBe('barry.allen@expensify.com'); + }); + + it('should include workspace rooms in the search results', () => { + 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: {}, betas: [CONST.BETAS.ALL]}); + // When we pass the returned options to filterAndOrderOptions with a search value + const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE); + + // Then only one report should be returned + expect(filteredOptions.recentReports.length).toBe(1); + // Then the returned report should match the search text + expect(filteredOptions.recentReports.at(0)?.subtitle).toBe('Avengers Room'); + }); + + 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: {}, betas: [CONST.BETAS.ALL]}); + // When we pass the returned options to filterAndOrderOptions with a search value + const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE); + + // Then only one report should be returned + expect(filteredOptions.recentReports.length).toBe(1); + // Then the returned report should match the search text + expect(filteredOptions.recentReports.at(0)?.login).toBe(searchText); + }); + + it('should prioritize options with matching display name over chat rooms', () => { + const searchText = 'spider'; + // 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: {}, betas: [CONST.BETAS.ALL]}); + // When we pass the returned options to filterAndOrderOptions with a search value + const filterOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE); + + // Then only two reports should be returned + expect(filterOptions.recentReports.length).toBe(2); + // Then the second report should match the search text + expect(filterOptions.recentReports.at(1)?.isChatRoom).toBe(true); + }); + + it('should put the item with latest lastVisibleActionCreated on top when search value match multiple items', () => { + renderLocaleContextProvider(); + const searchText = 'fantastic'; + // Given a set of options + const options = getSearchOptions({options: OPTIONS, draftComments: {}}); + // When we call filterAndOrderOptions with a search value + const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE); + + // Then only three reports should be returned + expect(filteredOptions.recentReports.length).toBe(3); + // Then the first report should match the search text + expect(filteredOptions.recentReports.at(0)?.text).toBe('Mister Fantastic'); + // Then the second report should match the search text + expect(filteredOptions.recentReports.at(1)?.text).toBe('Mister Fantastic, Invisible Woman'); + }); + + 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: {}}); + // When we call filterAndOrderOptions with a search value + const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE); + + // Then the user to invite should be returned + expect(filteredOptions.userToInvite?.login).toBe(searchText); + }); + + 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}, {}, {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}); + + // Then no personal details should be returned + expect(filterOptions.recentReports.length).toBe(0); + }); + + 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: {}}); + // When we call filterAndOrderOptions with a search value and excludeLogins + const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE, {excludeLogins: CONST.EXPENSIFY_EMAILS_OBJECT}); + + // Then the user to invite should be returned + expect(filteredOptions.userToInvite?.login).toBe(searchText); + }); + + 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: {}}); + // When we call filterAndOrderOptions with a search value and maxRecentReportsToShow set to 2 + const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE, {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}); + + // Then no reports should be returned + expect(limitToZeroOptions.recentReports.length).toBe(0); + }); + + 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: {}, betas: [CONST.BETAS.ALL]}); + // When we call filterAndOrderOptions with a search value + const filteredOptions = filterAndOrderOptions(options, searchText, COUNTRY_CODE); + + // Then there should be one matching result + expect(filteredOptions.personalDetails.length).toBe(1); + // Then the user to invite should be null + 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, []); + // When we call filterAndOrderOptions with a search value that does not match any personal details + const filteredOptions = filterAndOrderOptions(options, 'magneto', COUNTRY_CODE); + + // 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, []); + // When we call filterAndOrderOptions with a search value that matches an email + const filteredOptions = filterAndOrderOptions(options, 'peterparker@expensify.com', COUNTRY_CODE); + + // 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) => { + const report = option.item; + if (canUserPerformWriteAction(report, false) && canCreateTaskInReport(report) && !isCanceledTaskReport(report)) { + filtered.push(option); + } + return filtered; + }, []); + // When we call getValidOptions for share destination with the filteredReports + const options = getValidOptions( + {reports: filteredReports, personalDetails: OPTIONS.personalDetails}, + {}, + { + betas: [], + includeMultipleParticipantReports: true, + showChatPreviewLine: true, + forcePolicyNamePreview: true, + includeThreads: true, + includeMoneyRequests: true, + includeTasks: true, + excludeLogins: {}, + includeOwnedWorkspaceChats: true, + includeSelfDM: true, + searchString: '', + 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); + + // Then no recent reports should be returned + expect(filteredOptions.recentReports.length).toBe(0); + }); + + it('should return a workspace room when we search for a workspace room(getShareDestinationsOptions)', () => { + // Given a set of filtered current Reports (as we do in the component) before getting share destination options + const filteredReportsWithWorkspaceRooms = Object.values(OPTIONS_WITH_WORKSPACE_ROOM.reports).reduce((filtered, option) => { + const report = option.item; + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + if (canUserPerformWriteAction(report, false) || isExpensifyOnlyParticipantInReport(report)) { + filtered.push(option); + } + return filtered; + }, []); + + // When we call getValidOptions for share destination with the filteredReports + const options = getValidOptions( + {reports: filteredReportsWithWorkspaceRooms, personalDetails: OPTIONS.personalDetails}, + {}, + { + betas: [], + includeMultipleParticipantReports: true, + showChatPreviewLine: true, + forcePolicyNamePreview: true, + includeThreads: true, + includeMoneyRequests: true, + includeTasks: true, + excludeLogins: {}, + includeOwnedWorkspaceChats: true, + includeSelfDM: true, + searchString: '', + 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); + + // Then one recent report should be returned + expect(filteredOptions.recentReports.length).toBe(1); + }); + + it('should not show any results if searching for a non-existing workspace room(getShareDestinationOptions)', () => { + // Given a set of filtered current Reports (as we do in the component) before getting share destination options + const filteredReportsWithWorkspaceRooms = Object.values(OPTIONS_WITH_WORKSPACE_ROOM.reports).reduce((filtered, option) => { + const report = option.item; + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + if (canUserPerformWriteAction(report, false) || isExpensifyOnlyParticipantInReport(report)) { + filtered.push(option); + } + return filtered; + }, []); + + // When we call getValidOptions for share destination with the filteredReports + const options = getValidOptions( + {reports: filteredReportsWithWorkspaceRooms, personalDetails: OPTIONS.personalDetails}, + {}, + { + betas: [], + includeMultipleParticipantReports: true, + showChatPreviewLine: true, + forcePolicyNamePreview: true, + includeThreads: true, + includeMoneyRequests: true, + includeTasks: true, + excludeLogins: {}, + includeOwnedWorkspaceChats: true, + includeSelfDM: true, + searchString: '', + 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); + + // Then no recent reports should be returned + expect(filteredOptions.recentReports.length).toBe(0); + }); + + 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}, {}); + // When we call filterAndOrderOptions with a search value that matches a personal detail with no existing report + const filteredOptions = filterAndOrderOptions(options, 'hulk', COUNTRY_CODE); + + // Then no recent reports should be returned + expect(filteredOptions.recentReports.length).toBe(0); + // 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)?.login).toBe('brucebanner@expensify.com'); + }); + + it('should not return any options or user to invite if there are no search results and the string does not match a potential email or phone', () => { + // Given a set of options + const options = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}); + // 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); + + // Then no recent reports or personal details should be returned + expect(filteredOptions.recentReports.length).toBe(0); + expect(filteredOptions.personalDetails.length).toBe(0); + // Then no user to invite should be returned + expect(filteredOptions.userToInvite).toBe(null); + }); + + 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}, {}); + // 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); + + // Then no recent reports or personal details should be returned + expect(filteredOptions.recentReports.length).toBe(0); + expect(filteredOptions.personalDetails.length).toBe(0); + // Then an user to invite should be returned + expect(filteredOptions.userToInvite).not.toBe(null); + }); + + 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}, {}); + // 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); + + // Then no recent reports should be returned + expect(filteredOptions.recentReports.length).toBe(0); + // Then one user to invite should be returned + expect(filteredOptions.userToInvite).not.toBe(null); + }); + + 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}, {}); + // When we call filterAndOrderOptions with a search value without accent mark + const filteredOptions = filterAndOrderOptions(options, 'Timothee', COUNTRY_CODE); + + // Then one personalDetails with accent mark should be returned + expect(filteredOptions.personalDetails.length).toBe(1); + }); + + 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}, {}); + // 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); + + // Then no recent reports or personal details should be returned + expect(filteredOptions.recentReports.length).toBe(0); + expect(filteredOptions.personalDetails.length).toBe(0); + // Then one user to invite should be returned + expect(filteredOptions.userToInvite).not.toBe(null); + // Then the user to invite should match the search value + expect(filteredOptions.userToInvite?.login).toBe('+15005550006'); + }); + + 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}, {}); + // 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); + + // Then no recent reports or personal details should be returned + expect(filteredOptions.recentReports.length).toBe(0); + expect(filteredOptions.personalDetails.length).toBe(0); + // Then one user to invite should be returned + expect(filteredOptions.userToInvite).not.toBe(null); + // Then the user to invite should match the search value + expect(filteredOptions.userToInvite?.login).toBe('+15005550006'); + }); + + 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}, {}); + // 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); + + // Then no recent reports or personal details should be returned + expect(filteredOptions.recentReports.length).toBe(0); + expect(filteredOptions.personalDetails.length).toBe(0); + // Then one user to invite should be returned + expect(filteredOptions.userToInvite).not.toBe(null); + // Then the user to invite should match the search value + expect(filteredOptions.userToInvite?.login).toBe('+18003243233'); + }); + + 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}, {}); + // When we call filterAndOrderOptions with a search value that does not match any personal details or reports + const filteredOptions = filterAndOrderOptions(options, '998243aaaa', COUNTRY_CODE); + + // Then no recent reports or personal details should be returned + expect(filteredOptions.recentReports.length).toBe(0); + expect(filteredOptions.personalDetails.length).toBe(0); + // Then no user to invite should be returned + expect(filteredOptions.userToInvite).toBe(null); + }); + + 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}, {}); + // When we call filterAndOrderOptions with a search value that does not match any personal details + const filteredOptions = filterAndOrderOptions(options, 'magneto', COUNTRY_CODE); + + // Then no personal details should be returned + expect(filteredOptions.personalDetails.length).toBe(0); + }); + + 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}, {}); + // When we call filterAndOrderOptions with a search value that matches an email + const filteredOptions = filterAndOrderOptions(options, 'peterparker@expensify.com', COUNTRY_CODE, {sortByReportTypeInSearch: true}); + + // Then one recent report should be returned + expect(filteredOptions.recentReports.length).toBe(1); + // Then the returned recent report should match the search text + expect(filteredOptions.recentReports.at(0)?.text).toBe('Spider-Man'); + // Then no personal details should be returned + expect(filteredOptions.personalDetails.length).toBe(0); + }); + + it('should return all matching reports and personal details', () => { + // Given a set of options + const options = getValidOptions({reports: OPTIONS.reports, personalDetails: OPTIONS.personalDetails}, {}); + // 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}); + + // Then there should be 4 matching personal details + expect(filteredOptions.personalDetails.length).toBe(5); + // Then the first personal detail should match the search text + expect(filteredOptions.personalDetails.at(0)?.login).toBe('natasharomanoff@expensify.com'); + // Then there should be 5 matching recent reports + expect(filteredOptions.recentReports.length).toBe(5); + expect(filteredOptions.recentReports.at(0)?.text).toBe('Captain America'); + expect(filteredOptions.recentReports.at(1)?.text).toBe('Mr Sinister'); + expect(filteredOptions.recentReports.at(2)?.text).toBe('Black Panther'); + }); + + it('should return matching option when searching (getSearchOptions)', () => { + // Given a set of options + const options = getSearchOptions({options: OPTIONS, draftComments: {}}); + // When we call filterAndOrderOptions with a search value that matches a personal detail + const filteredOptions = filterAndOrderOptions(options, 'spider', COUNTRY_CODE); + + // Then one personal detail should be returned + expect(filteredOptions.recentReports.length).toBe(1); + // Then the returned personal detail should match the search text + expect(filteredOptions.recentReports.at(0)?.text).toBe('Spider-Man'); + }); + + 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: {}}); + // When we call filterAndOrderOptions with a search value that matches multiple items + const filteredOptions = filterAndOrderOptions(options, 'fantastic', COUNTRY_CODE); + + // Then only three reports should be returned + expect(filteredOptions.recentReports.length).toBe(3); + // Then the first report should match the search text + expect(filteredOptions.recentReports.at(0)?.text).toBe('Mister Fantastic'); + // Then the second report should match the search text + expect(filteredOptions.recentReports.at(1)?.text).toBe('Mister Fantastic, Invisible Woman'); + + return waitForBatchedUpdates() + .then(() => Onyx.set(ONYXKEYS.PERSONAL_DETAILS_LIST, PERSONAL_DETAILS_WITH_PERIODS)) + .then(() => { + // 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: {}}); + // When we pass the returned options to filterAndOrderOptions with a search value + const filteredResults = filterAndOrderOptions(results, 'barry.allen@expensify.com', COUNTRY_CODE, {sortByReportTypeInSearch: true}); + + // Then only one report should be returned + expect(filteredResults.recentReports.length).toBe(1); + // Then the returned report should match the search text + expect(filteredResults.recentReports.at(0)?.text).toBe('The Flash'); + }); + }); + + it('should filter out duplicated entries by login', () => { + const login = 'brucebanner@expensify.com'; + + // Duplicate personalDetails entries and reassign to OPTIONS + OPTIONS.personalDetails = OPTIONS.personalDetails.flatMap((obj) => [obj, {...obj}]); + + // Given a set of options + const options = getSearchOptions({options: OPTIONS, draftComments: {}, betas: [CONST.BETAS.ALL]}); + // When we call filterAndOrderOptions with a an empty search value + const filteredOptions = filterAndOrderOptions(options, '', COUNTRY_CODE); + const matchingEntries = filteredOptions.personalDetails.filter((detail) => detail.login === login); + + // Then there should be 2 unique login entries + expect(filteredOptions.personalDetails.length).toBe(3); + // Then there should be 1 matching entry + expect(matchingEntries.length).toBe(1); + }); + + it('should order self dm always on top if the search matches with the self dm login', () => { + const searchTerm = 'tonystark@expensify.com'; + 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: {}, betas: [CONST.BETAS.ALL]}); + // When we call filterAndOrderOptions with a search value + const filteredOptions = filterAndOrderOptions(options, searchTerm, COUNTRY_CODE); + + // Then the self dm should be on top. + expect(filteredOptions.recentReports.at(0)?.isSelfDM).toBe(true); + }); + }); + + describe('canCreateOptimisticPersonalDetailOption()', () => { + const VALID_EMAIL = 'valid@email.com'; + const currentUserEmail = 'tonystark@expensify.com'; + + it('should allow to create optimistic personal detail option if email is valid', () => { + const canCreate = canCreateOptimisticPersonalDetailOption({ + searchValue: VALID_EMAIL, + currentUserOption: { + login: currentUserEmail, + } as OptionData, + // Note: in the past this would check for the existence of the email in the personalDetails list, this has changed. + // We expect only filtered lists to be passed to this function, so we don't need to check for the existence of the email in the personalDetails list. + // This is a performance optimization. + personalDetailsOptions: [], + recentReportOptions: [], + }); + + expect(canCreate).toBe(true); + }); + + it('should not allow to create option if email is an email of current user', () => { + // Given a set of arguments with currentUserOption object + // When we call canCreateOptimisticPersonalDetailOption + const canCreate = canCreateOptimisticPersonalDetailOption({ + searchValue: currentUserEmail, + recentReportOptions: [], + personalDetailsOptions: [], + currentUserOption: { + login: currentUserEmail, + } as OptionData, + }); + + // Then the returned value should be false + expect(canCreate).toBe(false); + }); + + it('createOptionList() localization', () => { + renderLocaleContextProvider(); + // Given a set of reports and personal details + // When we call createOptionList and extract the reports + const reports = createOptionList(PERSONAL_DETAILS, REPORTS).reports; + + // Then the returned reports should match the expected values + expect(reports.at(10)?.subtitle).toBe(`Submits to Mister Fantastic`); + + return ( + waitForBatchedUpdates() + // When we set the preferred locale to Spanish + .then(() => Onyx.set(ONYXKEYS.NVP_PREFERRED_LOCALE, CONST.LOCALES.ES)) + .then(() => { + // When we call createOptionList again + const newReports = createOptionList(PERSONAL_DETAILS, REPORTS).reports; + // Then the returned reports should change to Spanish + // cspell:disable-next-line + expect(newReports.at(10)?.subtitle).toBe('Se envía a Mister Fantastic'); + }) + ); + }); + }); + + describe('filterWorkspaceChats()', () => { + it('should return an empty array if there are no expense chats', () => { + // Given an empty array of expense chats and no search terms + // When we call filterWorkspaceChats + const result = filterWorkspaceChats([], []); + + // Then the returned value should be an empty array + expect(result.length).toEqual(0); + }); + + it('should return all expense chats if there are no search terms', () => { + // Given a list of expense chats and no search terms + // When we call filterWorkspaceChats + const result = filterWorkspaceChats(WORKSPACE_CHATS, []); + + // Then the returned value should be the same as the input + expect(result).toEqual(WORKSPACE_CHATS); + // Then the length of the result should be equal to the length of the input + expect(result.length).toEqual(WORKSPACE_CHATS.length); + }); + + it('should filter multiple expense chats by search term', () => { + // Given a list of expense chats and one search term + // When we call filterWorkspaceChats + const result = filterWorkspaceChats(WORKSPACE_CHATS, ['Google']); + + // Then the returned value should should only include the matching expense chats + expect(result.length).toEqual(2); + }); + + it('should filter expense chat by exact name', () => { + // Given a list of expense chats and multiple search terms that reflect the exact name + // When we call filterWorkspaceChats + const result = filterWorkspaceChats(WORKSPACE_CHATS, ['Microsoft', 'Teams', 'Workspace']); + + // Then the returned value should should only include the matching expense chat + expect(result.length).toEqual(1); + }); + + it('should return an empty array if there are no matching expense chats', () => { + // Given a list of expense chats and a search term that does not match any expense chats + // When we call filterWorkspaceChats + const result = filterWorkspaceChats(WORKSPACE_CHATS, ['XYZ']); + + // Then the returned value should be an empty array + expect(result.length).toEqual(0); + }); + }); + + describe('orderWorkspaceOptions()', () => { + it('should put the default workspace on top of the list', () => { + // Given a list of expense chats + // When we call orderWorkspaceOptions + const result = orderWorkspaceOptions(WORKSPACE_CHATS); + + // Then the first item in the list should be the default workspace + expect(result.at(0)?.text).toEqual('Notion Workspace for Marketing'); + }); + }); + + describe('Alternative text', () => { + it("The text should not contain the last actor's name at prefix if the report is archived.", async () => { + renderLocaleContextProvider(); + // When we set the preferred locale to English and create an ADD_COMMENT report action + await Onyx.multiSet({ + [ONYXKEYS.NVP_PREFERRED_LOCALE]: CONST.LOCALES.EN, + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}10` as const]: { + '1': getFakeAdvancedReportAction(CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT), + }, + }); + // When we call createOptionList + const reports = createOptionList(PERSONAL_DETAILS, REPORTS).reports; + const archivedReport = reports.find((report) => report.reportID === '10'); + + // Then the returned report should contain default archived reason + expect(archivedReport?.lastMessageText).toBe('This chat room has been archived.'); + }); + }); + + describe('filterSelfDMChat()', () => { + const REPORT = { + reportID: '1', + text: 'Google Workspace', + policyID: '11', + isPolicyExpenseChat: true, + }; + const LOGIN = 'johndoe@test.com'; + const ALTERNATE_TEXT = 'John William Doe'; + const SUBTITLE = 'Software Engineer'; + + it('should return the report when there are no search terms', () => { + // Given a report object + // When we call filterSelfDMChat with the report and no search terms + const result = filterSelfDMChat(REPORT, []); + + // Then the returned value should be the same as the input + expect(result?.reportID).toEqual(REPORT.reportID); + }); + + it('should return undefined, when the search term does not match the report', () => { + // Given a report object + // When we call filterSelfDMChat with the report and a search term that does not match the report + const result = filterSelfDMChat(REPORT, ['XYZ']); + + // Then the returned value should be undefined + expect(result).toBeUndefined(); + }); + + it('should filter report by text', () => { + // Given a report object + // When we call filterSelfDMChat with the report and search term that matches the report + const result = filterSelfDMChat(REPORT, ['Google']); + + // Then the returned value should be the same as the input + expect(result?.reportID).toEqual(REPORT.reportID); + }); + + it('should filter report by exact text', () => { + // Given a report object + // When we call filterSelfDMChat with the report and multiple search terms that match the report's exact name + const result = filterSelfDMChat(REPORT, ['Google', 'Workspace']); + + // Then the returned value should be the same as the input + expect(result?.reportID).toEqual(REPORT.reportID); + }); + + it('should filter report by login', () => { + // Given a report object + // When we call filterSelfDMChat with the report and a search term that matches the report's login + const result = filterSelfDMChat({...REPORT, login: LOGIN}, ['john']); + + // Then the returned value should be the same as the input + expect(result?.reportID).toEqual(REPORT.reportID); + }); + + it('should filter report by exact login', () => { + // Given a report object + // When we call filterSelfDMChat with the report and multiple search terms that match the report's exact login + const result = filterSelfDMChat({...REPORT, login: LOGIN}, [LOGIN]); + + // Then the returned value should be the same as the input + expect(result?.reportID).toEqual(REPORT.reportID); + }); + + it('should filter report by alternate text', () => { + // Given a report object + // When we call filterSelfDMChat with the report and a search term that matches the report's alternate text + const result = filterSelfDMChat({...REPORT, alternateText: ALTERNATE_TEXT, isThread: true}, ['William']); + + // Then the returned value should be the same as the input + expect(result?.reportID).toEqual(REPORT.reportID); + }); + + it('should filter report by exact alternate text', () => { + // Given a report object that is a thread + // When we call filterSelfDMChat with the report and multiple search terms that match the report's exact alternate text + const result = filterSelfDMChat({...REPORT, alternateText: ALTERNATE_TEXT, isThread: true}, ['John', 'William', 'Doe']); + + // Then the returned value should be the same as the input + expect(result?.reportID).toEqual(REPORT.reportID); + }); + + it('should filter report by alternate text if it is not a thread', () => { + // Given a report object that is not a thread + // When we call filterSelfDMChat with the report and a search term that matches the report's alternate text + const result = filterSelfDMChat({...REPORT, alternateText: ALTERNATE_TEXT, isThread: false}, ['William']); + + // Then the returned value should be undefined + expect(result?.reportID).toBeUndefined(); + }); + + it('should filter report by subtitle', () => { + // Given a report object + // When we call filterSelfDMChat with the report and a search term that matches the report's subtitle + const result = filterSelfDMChat({...REPORT, subtitle: SUBTITLE}, ['Software']); + + // Then the returned value should be the same as the input + expect(result?.reportID).toEqual(REPORT.reportID); + }); + + it('should filter report by exact subtitle', () => { + // Given a report object + // When we call filterSelfDMChat with the report and multiple search terms that match the report's exact subtitle + const result = filterSelfDMChat({...REPORT, subtitle: SUBTITLE}, ['Software', 'Engineer']); + + // Then the returned value should be the same as the input + expect(result?.reportID).toEqual(REPORT.reportID); + }); + + it('should not filter report by subtitle if it is not an expense chat nor a chat room', () => { + // Given a report object that is not an expense chat nor a chat room + // When we call filterSelfDMChat with the report and a search term that matches the report's subtitle + const result = filterSelfDMChat({...REPORT, subtitle: SUBTITLE, isPolicyExpenseChat: false, isChatRoom: false}, ['Software']); + + // Then the returned value should be undefined + expect(result).toBeUndefined(); + }); + + it('should filter report by subtitle if it is a chat room', () => { + // Given a report object that is not an expense chat but is a chat room + // When we call filterSelfDMChat with the report and a search term that matches the report's subtitle + const result = filterSelfDMChat({...REPORT, subtitle: SUBTITLE, isPolicyExpenseChat: false, isChatRoom: true}, ['Software']); + + // Then the returned value should be the same as the input + expect(result?.reportID).toEqual(REPORT.reportID); + }); + }); + + describe('filterReports()', () => { + it('should match a user with an accented name when searching using non-accented characters', () => { + // Given a report with accented characters in the text property + // cspell:disable-next-line + const reports = [{text: "Álex Timón D'artagnan Zo-e"} as OptionData]; + // Given a search term with non-accented characters + // cspell:disable-next-line + const searchTerms = ['Alex Timon Dartagnan Zoe']; + // When we call filterReports with the report and search terms + const filteredReports = filterReports(reports, searchTerms); + + // Then the returned value should match the search term + expect(filteredReports).toEqual(reports); + }); + }); + + describe('getMostRecentOptions()', () => { + it('returns the most recent options up to the specified limit', () => { + const options: OptionData[] = [ + {reportID: '1', lastVisibleActionCreated: '2022-01-01T10:00:00Z'} as OptionData, + {reportID: '2', lastVisibleActionCreated: '2022-01-01T12:00:00Z'} as OptionData, + {reportID: '3', lastVisibleActionCreated: '2022-01-01T09:00:00Z'} as OptionData, + {reportID: '4', lastVisibleActionCreated: '2022-01-01T13:00:00Z'} as OptionData, + ]; + const comparator = (option: OptionData) => option.lastVisibleActionCreated ?? ''; + const result = optionsOrderBy(options, comparator, 2); + expect(result.length).toBe(2); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + expect(result.at(0)!.reportID).toBe('4'); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + expect(result.at(1)!.reportID).toBe('2'); + }); + + it('returns all options if limit is greater than options length', () => { + const options: OptionData[] = [ + {reportID: '1', lastVisibleActionCreated: '2022-01-01T10:00:00Z'} as OptionData, + {reportID: '2', lastVisibleActionCreated: '2022-01-01T12:00:00Z'} as OptionData, + ]; + const comparator = (option: OptionData) => option.lastVisibleActionCreated ?? ''; + const result = optionsOrderBy(options, comparator, 5); + expect(result.length).toBe(2); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + expect(result.at(0)!.reportID).toBe('2'); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + expect(result.at(1)!.reportID).toBe('1'); + }); + + it('returns empty array if options is empty', () => { + const result = optionsOrderBy([], recentReportComparator, 3); + expect(result).toEqual([]); + }); + + it('applies filter function if provided', () => { + const options: OptionData[] = [ + {reportID: '1', lastVisibleActionCreated: '2022-01-01T10:00:00Z', isPinned: true} as OptionData, + {reportID: '2', lastVisibleActionCreated: '2022-01-01T12:00:00Z', isPinned: false} as OptionData, + {reportID: '3', lastVisibleActionCreated: '2022-01-01T09:00:00Z', isPinned: true} as OptionData, + ]; + const comparator = (option: OptionData) => option.lastVisibleActionCreated ?? ''; + const result = optionsOrderBy(options, comparator, 2, (option) => option.isPinned); + expect(result.length).toBe(2); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + expect(result.at(0)!.reportID).toBe('1'); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + expect(result.at(1)!.reportID).toBe('3'); + }); + + it('handles negative limit by returning empty array', () => { + const options: OptionData[] = [ + {reportID: '1', lastVisibleActionCreated: '2022-01-01T10:00:00Z'} as OptionData, + {reportID: '2', lastVisibleActionCreated: '2022-01-01T12:00:00Z'} as OptionData, + {reportID: '3', lastVisibleActionCreated: '2022-01-01T09:00:00Z'} as OptionData, + ]; + const comparator = (option: OptionData) => option.lastVisibleActionCreated ?? ''; + const result = optionsOrderBy(options, comparator, -1); + expect(result).toEqual([]); + }); + + it('handles negative limit with large absolute value', () => { + const options: OptionData[] = [ + {reportID: '1', lastVisibleActionCreated: '2022-01-01T10:00:00Z'} as OptionData, + {reportID: '2', lastVisibleActionCreated: '2022-01-01T12:00:00Z'} as OptionData, + ]; + const comparator = (option: OptionData) => option.lastVisibleActionCreated ?? ''; + const result = optionsOrderBy(options, comparator, -100); + expect(result).toEqual([]); + }); + + it('handles limit equal to zero', () => { + const options: OptionData[] = [ + {reportID: '1', lastVisibleActionCreated: '2022-01-01T10:00:00Z'} as OptionData, + {reportID: '2', lastVisibleActionCreated: '2022-01-01T12:00:00Z'} as OptionData, + ]; + const comparator = (option: OptionData) => option.lastVisibleActionCreated ?? ''; + const result = optionsOrderBy(options, comparator, 0); + expect(result).toEqual([]); + }); + }); + + describe('sortAlphabetically', () => { + it('should sort options alphabetically by text', () => { + const options: OptionData[] = [{text: 'Banana', reportID: '1'} as OptionData, {text: 'Apple', reportID: '2'} as OptionData, {text: 'Cherry', reportID: '3'} as OptionData]; + const sortedOptions = sortAlphabetically(options, 'text', localeCompare); + expect(sortedOptions.at(0)?.reportID).toBe('2'); + expect(sortedOptions.at(1)?.reportID).toBe('1'); + expect(sortedOptions.at(2)?.reportID).toBe('3'); + }); + + it('should handle empty array', () => { + const sortedOptions = sortAlphabetically([], 'abc', localeCompare); + expect(sortedOptions).toEqual([]); + }); + + it('should handle single option', () => { + const options: OptionData[] = [{text: 'Single', reportID: '1'} as OptionData]; + const sortedOptions = sortAlphabetically(options, 'text', localeCompare); + expect(sortedOptions.length).toBe(1); + expect(sortedOptions.at(0)?.text).toBe('Single'); + }); + }); + describe('getSearchValueForPhoneOrEmail', () => { + it('should return E164 format for valid phone number', () => { + const result = getSearchValueForPhoneOrEmail('+1 (234) 567-8901', 1); + expect(result).toBe('+12345678901'); + }); + + it('should return E164 format for valid international phone number', () => { + const result = getSearchValueForPhoneOrEmail('+44 20 8759 9036', 44); + expect(result).toBe('+442087599036'); + }); + + it('should return lowercase for email address', () => { + const result = getSearchValueForPhoneOrEmail('Test@Example.COM', 1); + expect(result).toBe('test@example.com'); + }); + + it('should handle SMS domain removal for valid phone number', () => { + const result = getSearchValueForPhoneOrEmail('+12345678901@expensify.sms', 1); + expect(result).toBe('+12345678901'); + }); + + it('should return empty string for empty input', () => { + const result = getSearchValueForPhoneOrEmail('', 1); + expect(result).toBe(''); + }); + }); + + describe('createOption', () => { + it('should return alternative text correctly when the last action is report preview action', async () => { + const report = { + chatType: '', + currency: 'USD', + description: '', + errorFields: {}, + hasOutstandingChildRequest: false, + hasOutstandingChildTask: false, + iouReportID: '456', + lastMessageHtml: '', + lastMessageText: '', + participants: { + '1': { + notificationPreference: 'always', + }, + '2': { + notificationPreference: 'always', + }, + }, + reportID: '123', + type: 'chat', + lastActorAccountID: 1, + } as unknown as Report; + + const reportPreviewAction = { + actionName: 'REPORTPREVIEW', + actorAccountID: 1, + childManagerAccountID: 2, + childOwnerAccountID: 1, + childReportID: '456', + childReportName: 'IOU', + created: '2025-10-02 06:50:36.302', + reportActionID: '12345678', + shouldShow: true, + message: [ + { + html: 'Iron Man owes ₫34', + text: 'Iron Man owes ₫34', + type: 'COMMENT', + whisperedTo: [], + }, + ], + } as unknown as ReportAction; + + const iouReport = { + chatReportID: '123', + currency: 'VND', + managerID: 2, + ownerAccountID: 1, + parentReportActionID: '12345678', + parentReportID: '123', + participants: { + '19960856': { + notificationPreference: '', + }, + '20669492': { + notificationPreference: '', + }, + }, + reportID: '456', + reportName: 'IOU', + total: 3400, + } as unknown as Report; + + const iouAction = { + actorAccountID: 1, + message: [ + { + type: 'COMMENT', + html: '₫34 expense', + text: '₫34 expense', + isEdited: false, + whisperedTo: [], + isDeletedParentAction: false, + deleted: '', + reactions: [], + }, + ], + originalMessage: { + IOUReportID: '456', + IOUTransactionID: '123456', + amount: 3400, + comment: '', + currency: 'VND', + participantAccountIDs: [1, 2], + }, + actionName: 'IOU', + reportActionID: '789', + } as unknown as ReportAction; + + const transaction = { + transactionID: '123456', + amount: 3400, + currency: 'VND', + reportID: '3993091505909230', + comment: { + comment: '', + }, + merchant: '(none)', + created: '2025-10-02', + category: '', + taxAmount: 0, + reimbursable: true, + } as unknown as Transaction; + + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`, report); + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`, {[reportPreviewAction.reportActionID]: reportPreviewAction}); + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`, iouReport); + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`, {[iouAction.reportActionID]: iouAction}); + await Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`, transaction); + await waitForBatchedUpdates(); + + const result = createOption([1, 2], PERSONAL_DETAILS, report, {showChatPreviewLine: true}); + + expect(result.alternateText).toBe('Iron Man owes ₫34'); + }); + }); describe('getLastMessageTextForReport', () => { describe('REPORT_PREVIEW action', () => {