From 750f924b6149504b1ca9973b8b16b13613691272 Mon Sep 17 00:00:00 2001 From: Jakub Magiera Date: Tue, 20 Oct 2026 21:33:12 +0200 Subject: [PATCH 01/13] Add baseline search parity test for SearchRouter --- cspell.json | 1 + .../unit/OptionsListUtilsSearchParityTest.ts | 452 ++++ .../OptionsListUtilsSearchParityTest.ts.snap | 2086 +++++++++++++++++ .../searchParity/generateSearchFixture.ts | 688 ++++++ 4 files changed, 3227 insertions(+) create mode 100644 tests/unit/OptionsListUtilsSearchParityTest.ts create mode 100644 tests/unit/__snapshots__/OptionsListUtilsSearchParityTest.ts.snap create mode 100644 tests/utils/searchParity/generateSearchFixture.ts diff --git a/cspell.json b/cspell.json index 07b8cebe6eb5..480192a97354 100644 --- a/cspell.json +++ b/cspell.json @@ -1222,6 +1222,7 @@ "ignorePaths": [ ".github/actions/javascript/**/index.js", ".gitignore", + "tests/unit/__snapshots__/OptionsListUtilsSearchParityTest.ts.snap", "src/languages/de.ts", "src/languages/el.ts", "src/languages/es.ts", diff --git a/tests/unit/OptionsListUtilsSearchParityTest.ts b/tests/unit/OptionsListUtilsSearchParityTest.ts new file mode 100644 index 000000000000..b1892fb1898c --- /dev/null +++ b/tests/unit/OptionsListUtilsSearchParityTest.ts @@ -0,0 +1,452 @@ +import SEARCH_ROUTER_OPTIONS_CONFIG from '@components/Search/SearchRouter/searchRouterOptionsConfig'; + +// cspell:disable -- names and queries deliberately use diacritics, non-Latin scripts and misspelled forms +import type {OptionList, Options, SearchOptionData} from '@libs/OptionsListUtils'; +import { + clearAlternateTextCache, + clearFilteredOptionListCache, + combineOrderingOfReportsAndPersonalDetails, + createFilteredOptionList, + getSearchOptions, + hydrateContactOption, +} from '@libs/OptionsListUtils'; +import type {OptionsResult} from '@libs/OptionsListUtils/types'; + +import initOnyxDerivedValues from '@userActions/OnyxDerived'; + +import CONST from '@src/CONST'; +import IntlStore from '@src/languages/IntlStore'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {Report, Rule} from '@src/types/onyx'; + +import type {OnyxCollection} from 'react-native-onyx'; + +import Onyx from 'react-native-onyx'; + +import type {SearchFixture} from '../utils/searchParity/generateSearchFixture'; + +import {CURRENT_USER_ACCOUNT_ID, CURRENT_USER_EMAIL, generateSearchFixture, OWN_POLICY_ID, SEARCH_QUERIES, writeSearchFixtureToOnyx} from '../utils/searchParity/generateSearchFixture'; +import {convertToDisplayString, convertToDisplayStringWithoutCurrency, formatPhoneNumber, localeCompare, translateLocal} from '../utils/TestHelper'; +import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; + +jest.mock('@src/libs/Navigation/Navigation', () => ({ + navigate: jest.fn(), + getActiveRouteWithoutParams: jest.fn(() => ''), + dismissModal: jest.fn(), + goBack: jest.fn(), + getTopmostReportId: jest.fn(() => undefined), + setNavigationActionToMicrotaskQueue: jest.fn(), + isNavigationReady: jest.fn(() => Promise.resolve()), + getReportRHPActiveRoute: jest.fn(), +})); + +// Baseline for the search option pipeline the SearchRouter runs through (createFilteredOptionList to getSearchOptions). +// Only re-record the snapshot (jest -u) against the pre-migration search on main, so the migration never becomes its own baseline. +// The snapshot lists every option as " [flags] [alt: ...] [accID: ...]", so a changed result shows its options in the diff. +const fixture = generateSearchFixture(); + +/** Drops every cache the search keeps between calls, so the next build starts cold. */ +function clearSearchCaches() { + clearFilteredOptionListCache(); + clearAlternateTextCache(); +} + +/** + * Serializes options into " [flags] [alt: ...] [accID: ...]" lines to pin option ordering and content rather than rendering details. + * Invited users include their login when different from text to distinguish email from phone invites. + */ +function createOptionSerializer() { + /** Serializes one option into a " [flags] [alt: ...] [accID: ...]" line to pin option ordering and content rather than rendering details. */ + function serializeOption(option: SearchOptionData | null | undefined, {isUserToInvite = false}: {isUserToInvite?: boolean} = {}): string | null { + if (!option) { + return null; + } + const flags = [ + option.isSelected ? ' [selected]' : '', + option.isDisabled ? ' [disabled]' : '', + option.isBold ? ' [bold]' : '', + option.brickRoadIndicator === CONST.BRICK_ROAD_INDICATOR_STATUS.INFO ? ' [gbr]' : '', + option.brickRoadIndicator === CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR ? ' [rbr]' : '', + ].join(''); + const invitedLogin = isUserToInvite && option.login !== option.text ? ` ${option.login ?? ''}` : ''; + const altText = option.alternateText ? ` [alt: ${option.alternateText}]` : ''; + const accountID = option.accountID ? ` [accID: ${option.accountID}]` : ''; + return `${option.keyForList ?? ''} ${option.text ?? ''}${flags}${altText}${accountID}${invitedLogin}`; + } + + function serializeResult({options, hasMore}: OptionsResult | {options: Options; hasMore?: boolean}) { + return { + recentReports: options.recentReports.map((option) => serializeOption(option)), + personalDetails: options.personalDetails.map((option) => serializeOption(option)), + workspaceChats: options.workspaceChats?.map((option) => serializeOption(option)), + selfDMChat: serializeOption(options.selfDMChat), + currentUserOption: serializeOption(options.currentUserOption), + userToInvite: serializeOption(options.userToInvite, { + isUserToInvite: true, + }), + hasMore: !!hasMore, + }; + } + + /** Preserves pipeline ordering, but sorts unordered collections to ensure deterministic snapshots. */ + function serializeOptionList(optionList: OptionList, {isOrderMeaningful}: {isOrderMeaningful: boolean}) { + const sortIfUnordered = (references: Array) => (isOrderMeaningful ? references : [...references].sort()); + return { + reports: sortIfUnordered(optionList.reports.map((option) => serializeOption(option))), + // Shells are compared both as built and after hydration, so the shell/hydrator split stays invisible + personalDetails: optionList.personalDetails.map((shell) => `${shell.reportID} | ${shell.accountID} | ${shell.login ?? ''}`).sort(), + hydratedPersonalDetails: optionList.personalDetails.map((shell) => serializeOption(hydrateContactOption(shell))).sort(), + }; + } + + return {serializeOption, serializeResult, serializeOptionList}; +} + +/** Matches the readable serialization of a recorded value against the snapshot. */ +function expectToMatchGoldenMaster(value: unknown) { + expect(value).toMatchSnapshot(); +} + +/** + * Cap for the SearchRouter idle list. Production uses 100, which keeps all fixture reports. Setting this to 10 forces evictions. + */ +const SEARCH_ROUTER_IDLE_REPORTS_CAP = 10; + +const RULES: OnyxCollection = {}; + +type BuildOptionListConfig = { + isSearching: boolean; + maxRecentReports?: number; + deferContactsUntilSearch?: boolean; + includeP2P?: boolean; +}; + +/** Builds the list as useFilteredOptions does, keeping whatever search cached from earlier builds. */ +function buildOptionList(sourceFixture: SearchFixture, {isSearching, maxRecentReports, deferContactsUntilSearch, includeP2P = true}: BuildOptionListConfig): OptionList { + const {sortedActions, lastActions, transactionThreadIDs} = sourceFixture.derived.sortedReportActions; + return createFilteredOptionList( + sourceFixture.personalDetails, + sourceFixture.reports, + sourceFixture.derived.reportAttributes, + sourceFixture.privateIsArchivedMap, + sourceFixture.policies, + { + currentUserAccountID: CURRENT_USER_ACCOUNT_ID, + currentUserLogin: CURRENT_USER_EMAIL, + transactionThreadIDs, + lastActions, + dateFnsLocale: undefined, + convertToDisplayString, + conciergeReportID: sourceFixture.conciergeReportID, + includeP2P, + isSearching, + maxRecentReports, + deferContactsUntilSearch, + formatPhoneNumber, + }, + RULES, + undefined, + undefined, + undefined, + sortedActions, + ); +} + +function buildSearchRouterIdleOptionList(sourceFixture: SearchFixture, maxRecentReports: number): OptionList { + return buildOptionList(sourceFixture, { + isSearching: false, + maxRecentReports, + deferContactsUntilSearch: SEARCH_ROUTER_OPTIONS_CONFIG.deferContactsUntilSearch, + includeP2P: SEARCH_ROUTER_OPTIONS_CONFIG.includeP2P, + }); +} + +// Passed to combineOrderingOfReportsAndPersonalDetails. Only affects workspace chat ordering, which displayed passes do not show. +const ACTIVE_POLICY_ID = OWN_POLICY_ID; + +type SearchCallSite = (options: OptionList, query: string) => OptionsResult; + +/** + * Keeps getReportByID stable for identical fixture.reports references as useCallback(..., [allReports]) does in useFilteredOptions, + * so the preview cache is not invalidated between keystrokes when reports have not changed. + */ +const reportGettersByReports = new WeakMap Report | undefined>(); + +function getOrCreateReportByID(reports: SearchFixture['reports']) { + let getter = reportGettersByReports.get(reports); + if (!getter) { + getter = (reportID: string | undefined): Report | undefined => (reportID ? (reports[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] ?? undefined) : undefined); + reportGettersByReports.set(reports, getter); + } + return getter; +} + +/** + * Builds frozen copies of the production SearchRouter search configurations for the given fixture. + * Includes the screen-side ranking step (combineOrderingOfReportsAndPersonalDetails) while omitting pure styling steps. + */ +function createCallSites(sourceFixture: SearchFixture) { + const getReportByID = getOrCreateReportByID(sourceFixture.reports); + const {sortedActions, lastActions, transactionThreadIDs} = sourceFixture.derived.sortedReportActions; + const visibleReportActionsData = sourceFixture.derived.visibleReportActions; + + // Configuration every getSearchOptions screen passes + const baseSearchConfig = { + dateFnsLocale: undefined, + convertToDisplayString, + draftComments: sourceFixture.draftComments, + countryCode: CONST.DEFAULT_COUNTRY_CODE, + loginList: sourceFixture.loginList, + currentUserAccountID: CURRENT_USER_ACCOUNT_ID, + currentUserEmail: CURRENT_USER_EMAIL, + policyCollection: sourceFixture.policies, + personalDetails: sourceFixture.personalDetails, + sortedActions, + conciergeReportID: sourceFixture.conciergeReportID, + translate: translateLocal, + formatPhoneNumber, + getReportByID, + rules: RULES, + }; + // SearchAutocompleteList server pass + const serverPassConfig = { + ...baseSearchConfig, + visibleReportActionsData, + }; + // SearchAutocompleteList local pass + const commonSearchConfig = { + ...baseSearchConfig, + convertToDisplayStringWithoutCurrency, + currentUserLogin: CURRENT_USER_EMAIL, + visibleReportActionsData, + transactionThreadIDs, + lastActions, + reportAttributesDerived: sourceFixture.derived.reportAttributes, + localeCompare, + }; + + // SearchAutocompleteList's local pass, which its displayed pass then reorders + const runSearchRouterSearch = (options: OptionList, query: string) => + getSearchOptions({ + ...commonSearchConfig, + options, + isDefaultRoomsBetaEnabled: true, + isUsedInChatFinder: true, + includeReadOnly: true, + searchQuery: query, + maxResults: CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMOUNT_OF_SUGGESTIONS, + includeUserToInvite: true, + includeRecentReports: true, + includeCurrentUser: true, + shouldShowGBR: false, + shouldUnreadBeBold: true, + }); + + const searchCallSites = { + searchRouterLocal: (options, query) => runSearchRouterSearch(options, query), + // SearchAutocompleteList displayed pass: combines rankings, appends userToInvite and caps results + searchRouterDisplayed: (options, query) => { + const result = runSearchRouterSearch(options, query); + + if (!query.trim()) { + return { + options: { + ...result.options, + recentReports: result.options.recentReports.slice(0, CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMOUNT_OF_SUGGESTIONS), + }, + hasMore: result.hasMore, + }; + } + + // SearchAutocompleteList ranks by the raw autocomplete value, not the trimmed one + const ordered = combineOrderingOfReportsAndPersonalDetails(result.options, query, ACTIVE_POLICY_ID, { + sortByReportTypeInSearch: true, + preferChatRoomsOverThreads: true, + }); + const displayedReports = [...ordered.recentReports, ...ordered.personalDetails]; + if (result.options.userToInvite) { + displayedReports.push(result.options.userToInvite); + } + + return { + options: { + ...result.options, + recentReports: displayedReports.slice(0, CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMOUNT_OF_SUGGESTIONS), + personalDetails: ordered.personalDetails, + }, + hasMore: result.hasMore, + }; + }, + } satisfies Record; + + return {searchCallSites, serverPassConfig}; +} + +// Extracts call site names for test parameterization. Actual instances are created in beforeAll once Onyx derives report values. +const CALL_SITE_NAMES = Object.keys(createCallSites(fixture).searchCallSites); + +type OptionListKind = 'searching' | 'searchRouterIdle'; + +// Maps call sites to the option list used when query is blank (the small idle list, or the searching list for any other query). +const BLANK_QUERY_OPTION_LIST: Record = { + searchRouterLocal: 'searchRouterIdle', + searchRouterDisplayed: 'searchRouterIdle', +}; + +function getOptionListKind(callSite: string, query: string): OptionListKind { + if (query.trim()) { + return 'searching'; + } + const kind = BLANK_QUERY_OPTION_LIST[callSite]; + if (!kind) { + throw new Error(`Call site ${callSite} does not say which list it searches with a blank query`); + } + return kind; +} + +// Reports returned by the SearchAutocompleteList server pass in Auth ranking order, including a self-DM that the pass drops. +const SERVER_PASS_REPORT_NAMES = [ + '#admins', + '#announce', + '#vít-room', + 'Weekend Hiking', + 'Current User (you)', + 'Book flights', + 'Offsite expenses', + 'Thread about the offsite', + 'İsmail Yılmaz', + "Mary-Jane O'Brien (archived) ", +]; + +/** Finds a fixture report by its name, so a test never depends on the reportIDs the fixture assigns. */ +function getReportIDByName(sourceFixture: SearchFixture, reportName: string): string { + const reportID = Object.values(sourceFixture.reports).find((report) => report?.reportName === reportName)?.reportID; + if (!reportID) { + throw new Error(`No fixture report is named ${reportName}`); + } + return reportID; +} + +/** Computes every recorded pass over the fixture, starting from cold caches. */ +function computeGoldenMaster(sourceFixture: SearchFixture) { + const {serializeOption, serializeResult, serializeOptionList} = createOptionSerializer(); + const {searchCallSites, serverPassConfig} = createCallSites(sourceFixture); + clearSearchCaches(); + + const searching = buildOptionList(sourceFixture, {isSearching: true}); + const idle = buildOptionList(sourceFixture, {isSearching: false}); + const buildSearchRouterIdleList = (maxRecentReports: number) => buildSearchRouterIdleOptionList(sourceFixture, maxRecentReports); + const searchRouterIdle = buildSearchRouterIdleList(SEARCH_ROUTER_IDLE_REPORTS_CAP); + const searchRouterIdleAtProductionCap = buildSearchRouterIdleList(SEARCH_ROUTER_OPTIONS_CONFIG.maxRecentReports); + + const optionLists = { + // The searching list keeps the collection's iteration order, which the search ranks later, so only its content is recorded + searching: serializeOptionList(searching, {isOrderMeaningful: false}), + idle: serializeOptionList(idle, {isOrderMeaningful: true}), + searchRouterIdle: serializeOptionList(searchRouterIdle, { + isOrderMeaningful: true, + }), + searchRouterIdleAtProductionCap: serializeOptionList(searchRouterIdleAtProductionCap, {isOrderMeaningful: true}), + }; + + const optionListsByKind: Record = { + searching, + searchRouterIdle, + }; + const callSites = Object.fromEntries( + Object.entries(searchCallSites).map(([callSite, run]) => [ + callSite, + Object.fromEntries(SEARCH_QUERIES.map((query) => [query, serializeResult(run(optionListsByKind[getOptionListKind(callSite, query)], query))])), + ]), + ); + + const orderedServerReportIDs = SERVER_PASS_REPORT_NAMES.map((reportName) => getReportIDByName(sourceFixture, reportName)).slice( + 0, + CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMOUNT_OF_SUGGESTIONS, + ); + const serverReportIDs = new Set(orderedServerReportIDs); + // SearchAutocompleteList server pass reads derived attributes from Onyx because reportAttributesDerived is omitted + const serverPassOptions = getSearchOptions({ + ...serverPassConfig, + options: { + reports: searching.reports.filter((option) => serverReportIDs.has(option.reportID)), + personalDetails: [], + }, + isDefaultRoomsBetaEnabled: true, + isUsedInChatFinder: true, + includeReadOnly: true, + searchQuery: '', + maxResults: orderedServerReportIDs.length, + includeUserToInvite: false, + includeRecentReports: true, + includeCurrentUser: false, + shouldShowGBR: false, + shouldUnreadBeBold: true, + }).options; + // The pass then puts the rows back in Auth's order and drops the self-DM + const serverPassOptionsByReportID = new Map(serverPassOptions.recentReports.map((option) => [option.reportID, option])); + const serverPass = orderedServerReportIDs.map((reportID) => serverPassOptionsByReportID.get(reportID)).filter((option) => !!option && !option.isSelfDM); + + // When the idle cap hides reports and shows fewer rows than available slots, rebuild with all reports via loadAll + const totalReports = Object.keys(sourceFixture.reports).length; + const searchRouterBlankAtSmallCap = searchCallSites.searchRouterDisplayed(searchRouterIdle, ''); + const shouldLoadAllReports = + searchRouterBlankAtSmallCap.options.recentReports.length < CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMOUNT_OF_SUGGESTIONS && SEARCH_ROUTER_IDLE_REPORTS_CAP < totalReports; + const searchRouterIdleAfterLoadAll = shouldLoadAllReports ? buildSearchRouterIdleList(totalReports) : searchRouterIdle; + const runSearchRouterBlank = (optionList: OptionList) => ({ + local: serializeResult(searchCallSites.searchRouterLocal(optionList, '')), + displayed: serializeResult(searchCallSites.searchRouterDisplayed(optionList, '')), + }); + + const passes = { + searchRouterBlank: { + atProductionCap: runSearchRouterBlank(searchRouterIdleAtProductionCap), + afterLoadAll: { + didLoadAll: shouldLoadAllReports, + ...runSearchRouterBlank(searchRouterIdleAfterLoadAll), + }, + }, + serverPass: serverPass.map((option) => serializeOption(option)), + }; + + return {optionLists, callSites, passes}; +} + +describe('OptionsListUtils search parity (golden master)', () => { + let goldenMaster: ReturnType; + + beforeAll(async () => { + Onyx.init({keys: ONYXKEYS}); + await IntlStore.load(CONST.LOCALES.EN); + initOnyxDerivedValues(); + await waitForBatchedUpdates(); + // The fixture starts without derived values. Writing to Onyx derives them for subsequent reads. + fixture.derived = await writeSearchFixtureToOnyx(fixture); + goldenMaster = computeGoldenMaster(fixture); + }); + + beforeEach(() => { + clearSearchCaches(); + }); + + it('builds searching, idle and SearchRouter idle option lists that match the golden master', () => { + // Given the fixture account with every report type and contact quirk + // When the option list is built for search (every report), for the idle state (top-N, sorted) and for the SearchRouter idle state (small top-N, contacts deferred) + // Then every option keeps its identity, flags and, where it matters, its order + expectToMatchGoldenMaster(goldenMaster.optionLists); + }); + + it.each(CALL_SITE_NAMES)('call site %s matches the golden master for every query', (callSite) => { + // Given a production getSearchOptions config and the option list it searches (the full list, or its idle list while the query is blank) + // When every query in the matrix runs through it + // Then the sections and their order match the recorded golden master + expectToMatchGoldenMaster(goldenMaster.callSites[callSite]); + }); + + it('SearchRouter blank query and server-ID pass match the golden master', () => { + // Given the SearchRouter idle list at the production cap and after loading all reports, and the SearchAutocompleteList server pass (a fixed set of reports in Auth's order, no query) + // When they run through their production configs + // Then both match the recorded golden master + expectToMatchGoldenMaster(goldenMaster.passes); + }); +}); diff --git a/tests/unit/__snapshots__/OptionsListUtilsSearchParityTest.ts.snap b/tests/unit/__snapshots__/OptionsListUtilsSearchParityTest.ts.snap new file mode 100644 index 000000000000..3c941638e253 --- /dev/null +++ b/tests/unit/__snapshots__/OptionsListUtilsSearchParityTest.ts.snap @@ -0,0 +1,2086 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`OptionsListUtils search parity (golden master) SearchRouter blank query and server-ID pass match the golden master 1`] = ` +{ + "searchRouterBlank": { + "afterLoadAll": { + "didLoadAll": true, + "displayed": { + "currentUserOption": null, + "hasMore": true, + "personalDetails": [], + "recentReports": [ + "2004 Current User (you) [alt: This is your personal space. Use it for notes, tasks, drafts, and reminders.] [accID: 1]", + "2013 #orphan-room [alt: Gone Workspace • This chat room is for anything #orphan-room related.]", + "1124 Ada Cole [alt: This chat is with Ada Cole.] [accID: 124]", + "1103 Dan Ames [alt: This chat is with Dan Ames.] [accID: 103]", + "1019 No Login [alt: This chat is with No Login.] [accID: 19]", + "1123 Lou Byrd [alt: This chat is with Lou Byrd.] [accID: 123]", + "1102 Cyd Ames [alt: This chat is with Cyd Ames.] [accID: 102]", + "1018 Domain Admin [alt: This chat is with Domain Admin.] [accID: 18]", + "1121 Jon Byrd [alt: This chat is with Jon Byrd.] [accID: 121]", + "1100 Ada Ames [alt: This chat is with Ada Ames.] [accID: 100]", + "1120 Ivy Byrd [alt: This chat is with Ivy Byrd.] [accID: 120]", + "1015 Alex Smith Jr. [alt: This chat is with Alex Smith Jr..] [accID: 15]", + "2012 #vít-room [rbr] [alt: Acme Holdings • This chat room is for anything #vít-room related.]", + "1139 Dan Dunn [alt: This chat is with Dan Dunn.] [accID: 139]", + "1118 Gus Byrd [alt: This chat is with Gus Byrd.] [accID: 118]", + "1013 (500) 555-0006 [alt: This chat is with (500) 555-0006.] [accID: 13]", + "2007 Groß Straße [alt: Vít Workspace • This chat is for invoices between Groß Straße and Vít Workspace. Use the + button to send an invoice.]", + "1138 Cyd Dunn [alt: This chat is with Cyd Dunn.] [accID: 138]", + "1117 Fay Byrd [alt: This chat is with Fay Byrd.] [accID: 117]", + "1012 first.last+tag@x.com [alt: This chat is with first.last+tag@x.com.] [accID: 12]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "local": { + "currentUserOption": null, + "hasMore": true, + "personalDetails": [], + "recentReports": [ + "2004 Current User (you) [alt: This is your personal space. Use it for notes, tasks, drafts, and reminders.] [accID: 1]", + "2013 #orphan-room [alt: Gone Workspace • This chat room is for anything #orphan-room related.]", + "1124 Ada Cole [alt: This chat is with Ada Cole.] [accID: 124]", + "1103 Dan Ames [alt: This chat is with Dan Ames.] [accID: 103]", + "1019 No Login [alt: This chat is with No Login.] [accID: 19]", + "1123 Lou Byrd [alt: This chat is with Lou Byrd.] [accID: 123]", + "1102 Cyd Ames [alt: This chat is with Cyd Ames.] [accID: 102]", + "1018 Domain Admin [alt: This chat is with Domain Admin.] [accID: 18]", + "1121 Jon Byrd [alt: This chat is with Jon Byrd.] [accID: 121]", + "1100 Ada Ames [alt: This chat is with Ada Ames.] [accID: 100]", + "1120 Ivy Byrd [alt: This chat is with Ivy Byrd.] [accID: 120]", + "1015 Alex Smith Jr. [alt: This chat is with Alex Smith Jr..] [accID: 15]", + "2012 #vít-room [rbr] [alt: Acme Holdings • This chat room is for anything #vít-room related.]", + "1139 Dan Dunn [alt: This chat is with Dan Dunn.] [accID: 139]", + "1118 Gus Byrd [alt: This chat is with Gus Byrd.] [accID: 118]", + "1013 (500) 555-0006 [alt: This chat is with (500) 555-0006.] [accID: 13]", + "2007 Groß Straße [alt: Vít Workspace • This chat is for invoices between Groß Straße and Vít Workspace. Use the + button to send an invoice.]", + "1138 Cyd Dunn [alt: This chat is with Cyd Dunn.] [accID: 138]", + "1117 Fay Byrd [alt: This chat is with Fay Byrd.] [accID: 117]", + "1012 first.last+tag@x.com [alt: This chat is with first.last+tag@x.com.] [accID: 12]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + }, + "atProductionCap": { + "displayed": { + "currentUserOption": null, + "hasMore": true, + "personalDetails": [], + "recentReports": [ + "2004 Current User (you) [alt: This is your personal space. Use it for notes, tasks, drafts, and reminders.] [accID: 1]", + "2013 #orphan-room [alt: Gone Workspace • This chat room is for anything #orphan-room related.]", + "1124 Ada Cole [alt: This chat is with Ada Cole.] [accID: 124]", + "1103 Dan Ames [alt: This chat is with Dan Ames.] [accID: 103]", + "1019 No Login [alt: This chat is with No Login.] [accID: 19]", + "1123 Lou Byrd [alt: This chat is with Lou Byrd.] [accID: 123]", + "1102 Cyd Ames [alt: This chat is with Cyd Ames.] [accID: 102]", + "1018 Domain Admin [alt: This chat is with Domain Admin.] [accID: 18]", + "1121 Jon Byrd [alt: This chat is with Jon Byrd.] [accID: 121]", + "1100 Ada Ames [alt: This chat is with Ada Ames.] [accID: 100]", + "1120 Ivy Byrd [alt: This chat is with Ivy Byrd.] [accID: 120]", + "1015 Alex Smith Jr. [alt: This chat is with Alex Smith Jr..] [accID: 15]", + "2012 #vít-room [rbr] [alt: Acme Holdings • This chat room is for anything #vít-room related.]", + "1139 Dan Dunn [alt: This chat is with Dan Dunn.] [accID: 139]", + "1118 Gus Byrd [alt: This chat is with Gus Byrd.] [accID: 118]", + "1013 (500) 555-0006 [alt: This chat is with (500) 555-0006.] [accID: 13]", + "2007 Groß Straße [alt: Vít Workspace • This chat is for invoices between Groß Straße and Vít Workspace. Use the + button to send an invoice.]", + "1138 Cyd Dunn [alt: This chat is with Cyd Dunn.] [accID: 138]", + "1117 Fay Byrd [alt: This chat is with Fay Byrd.] [accID: 117]", + "1012 first.last+tag@x.com [alt: This chat is with first.last+tag@x.com.] [accID: 12]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "local": { + "currentUserOption": null, + "hasMore": true, + "personalDetails": [], + "recentReports": [ + "2004 Current User (you) [alt: This is your personal space. Use it for notes, tasks, drafts, and reminders.] [accID: 1]", + "2013 #orphan-room [alt: Gone Workspace • This chat room is for anything #orphan-room related.]", + "1124 Ada Cole [alt: This chat is with Ada Cole.] [accID: 124]", + "1103 Dan Ames [alt: This chat is with Dan Ames.] [accID: 103]", + "1019 No Login [alt: This chat is with No Login.] [accID: 19]", + "1123 Lou Byrd [alt: This chat is with Lou Byrd.] [accID: 123]", + "1102 Cyd Ames [alt: This chat is with Cyd Ames.] [accID: 102]", + "1018 Domain Admin [alt: This chat is with Domain Admin.] [accID: 18]", + "1121 Jon Byrd [alt: This chat is with Jon Byrd.] [accID: 121]", + "1100 Ada Ames [alt: This chat is with Ada Ames.] [accID: 100]", + "1120 Ivy Byrd [alt: This chat is with Ivy Byrd.] [accID: 120]", + "1015 Alex Smith Jr. [alt: This chat is with Alex Smith Jr..] [accID: 15]", + "2012 #vít-room [rbr] [alt: Acme Holdings • This chat room is for anything #vít-room related.]", + "1139 Dan Dunn [alt: This chat is with Dan Dunn.] [accID: 139]", + "1118 Gus Byrd [alt: This chat is with Gus Byrd.] [accID: 118]", + "1013 (500) 555-0006 [alt: This chat is with (500) 555-0006.] [accID: 13]", + "2007 Groß Straße [alt: Vít Workspace • This chat is for invoices between Groß Straße and Vít Workspace. Use the + button to send an invoice.]", + "1138 Cyd Dunn [alt: This chat is with Cyd Dunn.] [accID: 138]", + "1117 Fay Byrd [alt: This chat is with Fay Byrd.] [accID: 117]", + "1012 first.last+tag@x.com [alt: This chat is with first.last+tag@x.com.] [accID: 12]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + }, + }, + "serverPass": [ + "2010 #admins [alt: Vít Workspace • This chat is with Vít Workspace admin. Use it to chat about workspace setup and more.]", + "2011 #announce [alt: Vít Workspace • This chat is with everyone in Vít Workspace. Use it for the most important announcements.]", + "2012 #vít-room [rbr] [alt: Acme Holdings • This chat room is for anything #vít-room related.]", + "2003 Weekend Hiking [alt: This chat is with José Ñúñez, Mary-Jane O'Brien, and Vít Novák.]", + "2030 Book flights [alt: No activity yet]", + "2031 Offsite expenses [alt: No activity yet] [accID: 1]", + "2021 Thread about the offsite [bold] [alt: José Ñúñez: Count me in]", + "1004 İsmail Yılmaz [alt: Thanks!] [accID: 4]", + "2000 Mary-Jane O'Brien (archived) [alt: This chat room has been archived.] [accID: 11]", + ], +} +`; + +exports[`OptionsListUtils search parity (golden master) builds searching, idle and SearchRouter idle option lists that match the golden master 1`] = ` +{ + "idle": { + "hydratedPersonalDetails": [ + "1 Current User [alt: current.user@example.com] [accID: 1]", + "1003 Vít Novák [alt: vit.novak@example.com] [accID: 3]", + "1004 İsmail Yılmaz [alt: ismail@example.com] [accID: 4]", + "1006 Łukasz Wójcik [alt: lukasz@example.com] [accID: 6]", + "1007 Οδυσσεύς [alt: odysseus@example.com] [accID: 7]", + "1009 Ana​Maria [alt: ana.maria@example.com] [accID: 9]", + "101 Bea Ames [alt: bea.ames.1@example.com] [accID: 101]", + "1010 Bob Builder [alt: bob.builder@example.com] [accID: 10]", + "1012 first.last+tag@x.com [alt: first.last+tag@x.com] [accID: 12]", + "1013 (500) 555-0006 [alt: +15005550006@expensify.sms] [accID: 13]", + "1015 Alex Smith Jr. [alt: alex.smith2@example.com] [accID: 15]", + "1016 Expensify Notifications [alt: notifications@expensify.com] [accID: 16]", + "1018 Domain Admin [alt: admin@example.com] [accID: 18]", + "1019 No Login [accID: 19]", + "1021 Concierge [alt: concierge@expensify.com] [accID: 21]", + "104 Eve Ames [alt: eve.ames.4@example.com] [accID: 104]", + "107 Hal Ames [alt: hal.ames.7@example.com] [accID: 107]", + "110 Kim Ames [alt: kim.ames.10@example.com] [accID: 110]", + "1100 Ada Ames [alt: ada.ames.0@example.com] [accID: 100]", + "1102 Cyd Ames [alt: cyd.ames.2@example.com] [accID: 102]", + "1103 Dan Ames [alt: dan.ames.3@example.com] [accID: 103]", + "1105 Fay Ames [alt: fay.ames.5@example.com] [accID: 105]", + "1106 Gus Ames [alt: gus.ames.6@example.com] [accID: 106]", + "1108 Ivy Ames [alt: ivy.ames.8@example.com] [accID: 108]", + "1109 Jon Ames [alt: jon.ames.9@example.com] [accID: 109]", + "1111 Lou Ames [alt: lou.ames.11@example.com] [accID: 111]", + "1112 Ada Byrd [alt: ada.byrd.12@example.com] [accID: 112]", + "1114 Cyd Byrd [alt: cyd.byrd.14@example.com] [accID: 114]", + "1115 Dan Byrd [alt: dan.byrd.15@example.com] [accID: 115]", + "1117 Fay Byrd [alt: fay.byrd.17@example.com] [accID: 117]", + "1118 Gus Byrd [alt: gus.byrd.18@example.com] [accID: 118]", + "1120 Ivy Byrd [alt: ivy.byrd.20@example.com] [accID: 120]", + "1121 Jon Byrd [alt: jon.byrd.21@example.com] [accID: 121]", + "1123 Lou Byrd [alt: lou.byrd.23@example.com] [accID: 123]", + "1124 Ada Cole [alt: ada.cole.24@example.com] [accID: 124]", + "1126 Cyd Cole [alt: cyd.cole.26@example.com] [accID: 126]", + "1127 Dan Cole [alt: dan.cole.27@example.com] [accID: 127]", + "1129 Fay Cole [alt: fay.cole.29@example.com] [accID: 129]", + "113 Bea Byrd [alt: bea.byrd.13@example.com] [accID: 113]", + "1130 Gus Cole [alt: gus.cole.30@example.com] [accID: 130]", + "1132 Ivy Cole [alt: ivy.cole.32@example.com] [accID: 132]", + "1133 Jon Cole [alt: jon.cole.33@example.com] [accID: 133]", + "1135 Lou Cole [alt: lou.cole.35@example.com] [accID: 135]", + "1136 Ada Dunn [alt: ada.dunn.36@example.com] [accID: 136]", + "1138 Cyd Dunn [alt: cyd.dunn.38@example.com] [accID: 138]", + "1139 Dan Dunn [alt: dan.dunn.39@example.com] [accID: 139]", + "116 Eve Byrd [alt: eve.byrd.16@example.com] [accID: 116]", + "119 Hal Byrd [alt: hal.byrd.19@example.com] [accID: 119]", + "122 Kim Byrd [alt: kim.byrd.22@example.com] [accID: 122]", + "125 Bea Cole [alt: bea.cole.25@example.com] [accID: 125]", + "128 Eve Cole [alt: eve.cole.28@example.com] [accID: 128]", + "131 Hal Cole [alt: hal.cole.31@example.com] [accID: 131]", + "134 Kim Cole [alt: kim.cole.34@example.com] [accID: 134]", + "137 Bea Dunn [alt: bea.dunn.37@example.com] [accID: 137]", + "17 Account Executive [alt: Account Executive] [accID: 17]", + "20 René Dupont [alt: rene@example.com] [accID: 20]", + "2000 Mary-Jane O'Brien [alt: obrien@example.com] [accID: 11]", + "2001 Alex Smith [alt: alex.smith@example.com] [accID: 14]", + "2020 José Ñúñez [alt: jose.nunez@example.com] [accID: 2]", + "5 Groß Straße [alt: strasse@example.com] [accID: 5]", + "8 Zoë 👩‍💻 [alt: zoe@example.com] [accID: 8]", + ], + "personalDetails": [ + " | 1 | current.user@example.com", + " | 101 | bea.ames.1@example.com", + " | 104 | eve.ames.4@example.com", + " | 107 | hal.ames.7@example.com", + " | 110 | kim.ames.10@example.com", + " | 113 | bea.byrd.13@example.com", + " | 116 | eve.byrd.16@example.com", + " | 119 | hal.byrd.19@example.com", + " | 122 | kim.byrd.22@example.com", + " | 125 | bea.cole.25@example.com", + " | 128 | eve.cole.28@example.com", + " | 131 | hal.cole.31@example.com", + " | 134 | kim.cole.34@example.com", + " | 137 | bea.dunn.37@example.com", + " | 17 | Account Executive", + " | 20 | rene@example.com", + " | 5 | strasse@example.com", + " | 8 | zoe@example.com", + "1003 | 3 | vit.novak@example.com", + "1004 | 4 | ismail@example.com", + "1006 | 6 | lukasz@example.com", + "1007 | 7 | odysseus@example.com", + "1009 | 9 | ana.maria@example.com", + "1010 | 10 | bob.builder@example.com", + "1012 | 12 | first.last+tag@x.com", + "1013 | 13 | +15005550006@expensify.sms", + "1015 | 15 | alex.smith2@example.com", + "1016 | 16 | notifications@expensify.com", + "1018 | 18 | admin@example.com", + "1019 | 19 | ", + "1021 | 21 | concierge@expensify.com", + "1100 | 100 | ada.ames.0@example.com", + "1102 | 102 | cyd.ames.2@example.com", + "1103 | 103 | dan.ames.3@example.com", + "1105 | 105 | fay.ames.5@example.com", + "1106 | 106 | gus.ames.6@example.com", + "1108 | 108 | ivy.ames.8@example.com", + "1109 | 109 | jon.ames.9@example.com", + "1111 | 111 | lou.ames.11@example.com", + "1112 | 112 | ada.byrd.12@example.com", + "1114 | 114 | cyd.byrd.14@example.com", + "1115 | 115 | dan.byrd.15@example.com", + "1117 | 117 | fay.byrd.17@example.com", + "1118 | 118 | gus.byrd.18@example.com", + "1120 | 120 | ivy.byrd.20@example.com", + "1121 | 121 | jon.byrd.21@example.com", + "1123 | 123 | lou.byrd.23@example.com", + "1124 | 124 | ada.cole.24@example.com", + "1126 | 126 | cyd.cole.26@example.com", + "1127 | 127 | dan.cole.27@example.com", + "1129 | 129 | fay.cole.29@example.com", + "1130 | 130 | gus.cole.30@example.com", + "1132 | 132 | ivy.cole.32@example.com", + "1133 | 133 | jon.cole.33@example.com", + "1135 | 135 | lou.cole.35@example.com", + "1136 | 136 | ada.dunn.36@example.com", + "1138 | 138 | cyd.dunn.38@example.com", + "1139 | 139 | dan.dunn.39@example.com", + "2000 | 11 | obrien@example.com", + "2001 | 14 | alex.smith@example.com", + "2020 | 2 | jose.nunez@example.com", + ], + "reports": [ + "2004 Current User (you) [alt: current.user@example.com] [accID: 1]", + "2034 José Ñúñez", + "2001 Alex Smith [alt: alex.smith@example.com] [accID: 14]", + "2013 #orphan-room", + "1124 Ada Cole [alt: ada.cole.24@example.com] [accID: 124]", + "1103 Dan Ames [alt: dan.ames.3@example.com] [accID: 103]", + "1019 No Login [accID: 19]", + "1123 Lou Byrd [alt: lou.byrd.23@example.com] [accID: 123]", + "1102 Cyd Ames [alt: cyd.ames.2@example.com] [accID: 102]", + "1018 Domain Admin [alt: admin@example.com] [accID: 18]", + "1121 Jon Byrd [alt: jon.byrd.21@example.com] [accID: 121]", + "1100 Ada Ames [alt: ada.ames.0@example.com] [accID: 100]", + "1016 Expensify Notifications [alt: notifications@expensify.com] [accID: 16]", + "1120 Ivy Byrd [alt: ivy.byrd.20@example.com] [accID: 120]", + "1015 Alex Smith Jr. [alt: alex.smith2@example.com] [accID: 15]", + "2012 #vít-room [rbr]", + "2032 Empty submitted report [alt: Expense] [accID: 1]", + "1139 Dan Dunn [alt: dan.dunn.39@example.com] [accID: 139]", + "1118 Gus Byrd [alt: gus.byrd.18@example.com] [accID: 118]", + "1013 (500) 555-0006 [alt: (500) 555-0006] [accID: 13]", + "2007 Groß Straße", + "1138 Cyd Dunn [alt: cyd.dunn.38@example.com] [accID: 138]", + "1117 Fay Byrd [alt: fay.byrd.17@example.com] [accID: 117]", + "1012 first.last+tag@x.com [alt: first.last+tag@x.com] [accID: 12]", + "2031 Offsite expenses [alt: Expense] [accID: 1]", + "1136 Ada Dunn [alt: ada.dunn.36@example.com] [accID: 136]", + "1115 Dan Byrd [alt: dan.byrd.15@example.com] [accID: 115]", + "1010 Bob Builder [alt: bob.builder@example.com] [accID: 10]", + "2030 Book flights [alt: Task]", + "1135 Lou Cole [alt: lou.cole.35@example.com] [accID: 135]", + "1114 Cyd Byrd [alt: cyd.byrd.14@example.com] [accID: 114]", + "1009 Ana​Maria [alt: ana.maria@example.com] [accID: 9]", + "2011 #announce", + "2006 Groß Straße's expenses", + "2005 Current User's expenses [gbr]", + "1133 Jon Cole [alt: jon.cole.33@example.com] [accID: 133]", + "1112 Ada Byrd [alt: ada.byrd.12@example.com] [accID: 112]", + "1007 Οδυσσεύς [alt: odysseus@example.com] [accID: 7]", + "2002 [accID: 999]", + "1132 Ivy Cole [alt: ivy.cole.32@example.com] [accID: 132]", + "1111 Lou Ames [alt: lou.ames.11@example.com] [accID: 111]", + "1006 Łukasz Wójcik [alt: lukasz@example.com] [accID: 6]", + "2003 Weekend Hiking [alt: Group]", + "2026 Packing list [alt: Thread]", + "2025 Empty pinned thread [alt: Thread]", + "2024 Empty thread with a draft [alt: Thread]", + "2023 Empty thread [alt: Thread]", + "2022 Hidden thread [alt: Thread]", + "2021 Thread about the offsite [alt: Thread]", + "1130 Gus Cole [alt: gus.cole.30@example.com] [accID: 130]", + "1109 Jon Ames [alt: jon.ames.9@example.com] [accID: 109]", + "1004 İsmail Yılmaz [alt: ismail@example.com] [accID: 4]", + "2020 José Ñúñez [alt: jose.nunez@example.com] [accID: 2]", + "2010 #admins", + "1129 Fay Cole [alt: fay.cole.29@example.com] [accID: 129]", + "1108 Ivy Ames [alt: ivy.ames.8@example.com] [accID: 108]", + "1003 Vít Novák [alt: vit.novak@example.com] [accID: 3]", + "1127 Dan Cole [alt: dan.cole.27@example.com] [accID: 127]", + "1106 Gus Ames [alt: gus.ames.6@example.com] [accID: 106]", + "1126 Cyd Cole [alt: cyd.cole.26@example.com] [accID: 126]", + "1105 Fay Ames [alt: fay.ames.5@example.com] [accID: 105]", + "1021 Concierge [alt: concierge@expensify.com] [accID: 21]", + "2000 Mary-Jane O'Brien (archived) [alt: obrien@example.com] [accID: 11]", + ], + }, + "searchRouterIdle": { + "hydratedPersonalDetails": [], + "personalDetails": [], + "reports": [ + "2004 Current User (you) [alt: current.user@example.com] [accID: 1]", + "2034 José Ñúñez", + "2001 Alex Smith [alt: alex.smith@example.com] [accID: 14]", + "2013 #orphan-room", + "1124 Ada Cole [alt: ada.cole.24@example.com] [accID: 124]", + "1103 Dan Ames [alt: dan.ames.3@example.com] [accID: 103]", + "1019 No Login [accID: 19]", + "1123 Lou Byrd [alt: lou.byrd.23@example.com] [accID: 123]", + "1102 Cyd Ames [alt: cyd.ames.2@example.com] [accID: 102]", + "1018 Domain Admin [alt: admin@example.com] [accID: 18]", + ], + }, + "searchRouterIdleAtProductionCap": { + "hydratedPersonalDetails": [], + "personalDetails": [], + "reports": [ + "2004 Current User (you) [alt: current.user@example.com] [accID: 1]", + "2034 José Ñúñez", + "2001 Alex Smith [alt: alex.smith@example.com] [accID: 14]", + "2013 #orphan-room", + "1124 Ada Cole [alt: ada.cole.24@example.com] [accID: 124]", + "1103 Dan Ames [alt: dan.ames.3@example.com] [accID: 103]", + "1019 No Login [accID: 19]", + "1123 Lou Byrd [alt: lou.byrd.23@example.com] [accID: 123]", + "1102 Cyd Ames [alt: cyd.ames.2@example.com] [accID: 102]", + "1018 Domain Admin [alt: admin@example.com] [accID: 18]", + "1121 Jon Byrd [alt: jon.byrd.21@example.com] [accID: 121]", + "1100 Ada Ames [alt: ada.ames.0@example.com] [accID: 100]", + "1016 Expensify Notifications [alt: notifications@expensify.com] [accID: 16]", + "1120 Ivy Byrd [alt: ivy.byrd.20@example.com] [accID: 120]", + "1015 Alex Smith Jr. [alt: alex.smith2@example.com] [accID: 15]", + "2012 #vít-room [rbr]", + "2032 Empty submitted report [alt: Expense] [accID: 1]", + "1139 Dan Dunn [alt: dan.dunn.39@example.com] [accID: 139]", + "1118 Gus Byrd [alt: gus.byrd.18@example.com] [accID: 118]", + "1013 (500) 555-0006 [alt: (500) 555-0006] [accID: 13]", + "2007 Groß Straße", + "1138 Cyd Dunn [alt: cyd.dunn.38@example.com] [accID: 138]", + "1117 Fay Byrd [alt: fay.byrd.17@example.com] [accID: 117]", + "1012 first.last+tag@x.com [alt: first.last+tag@x.com] [accID: 12]", + "2031 Offsite expenses [alt: Expense] [accID: 1]", + "1136 Ada Dunn [alt: ada.dunn.36@example.com] [accID: 136]", + "1115 Dan Byrd [alt: dan.byrd.15@example.com] [accID: 115]", + "1010 Bob Builder [alt: bob.builder@example.com] [accID: 10]", + "2030 Book flights [alt: Task]", + "1135 Lou Cole [alt: lou.cole.35@example.com] [accID: 135]", + "1114 Cyd Byrd [alt: cyd.byrd.14@example.com] [accID: 114]", + "1009 Ana​Maria [alt: ana.maria@example.com] [accID: 9]", + "2011 #announce", + "2006 Groß Straße's expenses", + "2005 Current User's expenses [gbr]", + "1133 Jon Cole [alt: jon.cole.33@example.com] [accID: 133]", + "1112 Ada Byrd [alt: ada.byrd.12@example.com] [accID: 112]", + "1007 Οδυσσεύς [alt: odysseus@example.com] [accID: 7]", + "2002 [accID: 999]", + "1132 Ivy Cole [alt: ivy.cole.32@example.com] [accID: 132]", + "1111 Lou Ames [alt: lou.ames.11@example.com] [accID: 111]", + "1006 Łukasz Wójcik [alt: lukasz@example.com] [accID: 6]", + "2003 Weekend Hiking [alt: Group]", + "2026 Packing list [alt: Thread]", + "2025 Empty pinned thread [alt: Thread]", + "2024 Empty thread with a draft [alt: Thread]", + "2023 Empty thread [alt: Thread]", + "2022 Hidden thread [alt: Thread]", + "2021 Thread about the offsite [alt: Thread]", + "1130 Gus Cole [alt: gus.cole.30@example.com] [accID: 130]", + "1109 Jon Ames [alt: jon.ames.9@example.com] [accID: 109]", + "1004 İsmail Yılmaz [alt: ismail@example.com] [accID: 4]", + "2020 José Ñúñez [alt: jose.nunez@example.com] [accID: 2]", + "2010 #admins", + "1129 Fay Cole [alt: fay.cole.29@example.com] [accID: 129]", + "1108 Ivy Ames [alt: ivy.ames.8@example.com] [accID: 108]", + "1003 Vít Novák [alt: vit.novak@example.com] [accID: 3]", + "1127 Dan Cole [alt: dan.cole.27@example.com] [accID: 127]", + "1106 Gus Ames [alt: gus.ames.6@example.com] [accID: 106]", + "1126 Cyd Cole [alt: cyd.cole.26@example.com] [accID: 126]", + "1105 Fay Ames [alt: fay.ames.5@example.com] [accID: 105]", + "1021 Concierge [alt: concierge@expensify.com] [accID: 21]", + "2000 Mary-Jane O'Brien (archived) [alt: obrien@example.com] [accID: 11]", + ], + }, + "searching": { + "hydratedPersonalDetails": [ + "1 Current User [alt: current.user@example.com] [accID: 1]", + "1003 Vít Novák [alt: vit.novak@example.com] [accID: 3]", + "1004 İsmail Yılmaz [alt: ismail@example.com] [accID: 4]", + "1006 Łukasz Wójcik [alt: lukasz@example.com] [accID: 6]", + "1007 Οδυσσεύς [alt: odysseus@example.com] [accID: 7]", + "1009 Ana​Maria [alt: ana.maria@example.com] [accID: 9]", + "101 Bea Ames [alt: bea.ames.1@example.com] [accID: 101]", + "1010 Bob Builder [alt: bob.builder@example.com] [accID: 10]", + "1012 first.last+tag@x.com [alt: first.last+tag@x.com] [accID: 12]", + "1013 (500) 555-0006 [alt: +15005550006@expensify.sms] [accID: 13]", + "1015 Alex Smith Jr. [alt: alex.smith2@example.com] [accID: 15]", + "1016 Expensify Notifications [alt: notifications@expensify.com] [accID: 16]", + "1018 Domain Admin [alt: admin@example.com] [accID: 18]", + "1019 No Login [accID: 19]", + "1021 Concierge [alt: concierge@expensify.com] [accID: 21]", + "104 Eve Ames [alt: eve.ames.4@example.com] [accID: 104]", + "107 Hal Ames [alt: hal.ames.7@example.com] [accID: 107]", + "110 Kim Ames [alt: kim.ames.10@example.com] [accID: 110]", + "1100 Ada Ames [alt: ada.ames.0@example.com] [accID: 100]", + "1102 Cyd Ames [alt: cyd.ames.2@example.com] [accID: 102]", + "1103 Dan Ames [alt: dan.ames.3@example.com] [accID: 103]", + "1105 Fay Ames [alt: fay.ames.5@example.com] [accID: 105]", + "1106 Gus Ames [alt: gus.ames.6@example.com] [accID: 106]", + "1108 Ivy Ames [alt: ivy.ames.8@example.com] [accID: 108]", + "1109 Jon Ames [alt: jon.ames.9@example.com] [accID: 109]", + "1111 Lou Ames [alt: lou.ames.11@example.com] [accID: 111]", + "1112 Ada Byrd [alt: ada.byrd.12@example.com] [accID: 112]", + "1114 Cyd Byrd [alt: cyd.byrd.14@example.com] [accID: 114]", + "1115 Dan Byrd [alt: dan.byrd.15@example.com] [accID: 115]", + "1117 Fay Byrd [alt: fay.byrd.17@example.com] [accID: 117]", + "1118 Gus Byrd [alt: gus.byrd.18@example.com] [accID: 118]", + "1120 Ivy Byrd [alt: ivy.byrd.20@example.com] [accID: 120]", + "1121 Jon Byrd [alt: jon.byrd.21@example.com] [accID: 121]", + "1123 Lou Byrd [alt: lou.byrd.23@example.com] [accID: 123]", + "1124 Ada Cole [alt: ada.cole.24@example.com] [accID: 124]", + "1126 Cyd Cole [alt: cyd.cole.26@example.com] [accID: 126]", + "1127 Dan Cole [alt: dan.cole.27@example.com] [accID: 127]", + "1129 Fay Cole [alt: fay.cole.29@example.com] [accID: 129]", + "113 Bea Byrd [alt: bea.byrd.13@example.com] [accID: 113]", + "1130 Gus Cole [alt: gus.cole.30@example.com] [accID: 130]", + "1132 Ivy Cole [alt: ivy.cole.32@example.com] [accID: 132]", + "1133 Jon Cole [alt: jon.cole.33@example.com] [accID: 133]", + "1135 Lou Cole [alt: lou.cole.35@example.com] [accID: 135]", + "1136 Ada Dunn [alt: ada.dunn.36@example.com] [accID: 136]", + "1138 Cyd Dunn [alt: cyd.dunn.38@example.com] [accID: 138]", + "1139 Dan Dunn [alt: dan.dunn.39@example.com] [accID: 139]", + "116 Eve Byrd [alt: eve.byrd.16@example.com] [accID: 116]", + "119 Hal Byrd [alt: hal.byrd.19@example.com] [accID: 119]", + "122 Kim Byrd [alt: kim.byrd.22@example.com] [accID: 122]", + "125 Bea Cole [alt: bea.cole.25@example.com] [accID: 125]", + "128 Eve Cole [alt: eve.cole.28@example.com] [accID: 128]", + "131 Hal Cole [alt: hal.cole.31@example.com] [accID: 131]", + "134 Kim Cole [alt: kim.cole.34@example.com] [accID: 134]", + "137 Bea Dunn [alt: bea.dunn.37@example.com] [accID: 137]", + "17 Account Executive [alt: Account Executive] [accID: 17]", + "20 René Dupont [alt: rene@example.com] [accID: 20]", + "2000 Mary-Jane O'Brien [alt: obrien@example.com] [accID: 11]", + "2001 Alex Smith [alt: alex.smith@example.com] [accID: 14]", + "2020 José Ñúñez [alt: jose.nunez@example.com] [accID: 2]", + "5 Groß Straße [alt: strasse@example.com] [accID: 5]", + "8 Zoë 👩‍💻 [alt: zoe@example.com] [accID: 8]", + ], + "personalDetails": [ + " | 1 | current.user@example.com", + " | 101 | bea.ames.1@example.com", + " | 104 | eve.ames.4@example.com", + " | 107 | hal.ames.7@example.com", + " | 110 | kim.ames.10@example.com", + " | 113 | bea.byrd.13@example.com", + " | 116 | eve.byrd.16@example.com", + " | 119 | hal.byrd.19@example.com", + " | 122 | kim.byrd.22@example.com", + " | 125 | bea.cole.25@example.com", + " | 128 | eve.cole.28@example.com", + " | 131 | hal.cole.31@example.com", + " | 134 | kim.cole.34@example.com", + " | 137 | bea.dunn.37@example.com", + " | 17 | Account Executive", + " | 20 | rene@example.com", + " | 5 | strasse@example.com", + " | 8 | zoe@example.com", + "1003 | 3 | vit.novak@example.com", + "1004 | 4 | ismail@example.com", + "1006 | 6 | lukasz@example.com", + "1007 | 7 | odysseus@example.com", + "1009 | 9 | ana.maria@example.com", + "1010 | 10 | bob.builder@example.com", + "1012 | 12 | first.last+tag@x.com", + "1013 | 13 | +15005550006@expensify.sms", + "1015 | 15 | alex.smith2@example.com", + "1016 | 16 | notifications@expensify.com", + "1018 | 18 | admin@example.com", + "1019 | 19 | ", + "1021 | 21 | concierge@expensify.com", + "1100 | 100 | ada.ames.0@example.com", + "1102 | 102 | cyd.ames.2@example.com", + "1103 | 103 | dan.ames.3@example.com", + "1105 | 105 | fay.ames.5@example.com", + "1106 | 106 | gus.ames.6@example.com", + "1108 | 108 | ivy.ames.8@example.com", + "1109 | 109 | jon.ames.9@example.com", + "1111 | 111 | lou.ames.11@example.com", + "1112 | 112 | ada.byrd.12@example.com", + "1114 | 114 | cyd.byrd.14@example.com", + "1115 | 115 | dan.byrd.15@example.com", + "1117 | 117 | fay.byrd.17@example.com", + "1118 | 118 | gus.byrd.18@example.com", + "1120 | 120 | ivy.byrd.20@example.com", + "1121 | 121 | jon.byrd.21@example.com", + "1123 | 123 | lou.byrd.23@example.com", + "1124 | 124 | ada.cole.24@example.com", + "1126 | 126 | cyd.cole.26@example.com", + "1127 | 127 | dan.cole.27@example.com", + "1129 | 129 | fay.cole.29@example.com", + "1130 | 130 | gus.cole.30@example.com", + "1132 | 132 | ivy.cole.32@example.com", + "1133 | 133 | jon.cole.33@example.com", + "1135 | 135 | lou.cole.35@example.com", + "1136 | 136 | ada.dunn.36@example.com", + "1138 | 138 | cyd.dunn.38@example.com", + "1139 | 139 | dan.dunn.39@example.com", + "2000 | 11 | obrien@example.com", + "2001 | 14 | alex.smith@example.com", + "2020 | 2 | jose.nunez@example.com", + ], + "reports": [ + "1003 Vít Novák [alt: vit.novak@example.com] [accID: 3]", + "1004 İsmail Yılmaz [alt: ismail@example.com] [accID: 4]", + "1006 Łukasz Wójcik [alt: lukasz@example.com] [accID: 6]", + "1007 Οδυσσεύς [alt: odysseus@example.com] [accID: 7]", + "1009 Ana​Maria [alt: ana.maria@example.com] [accID: 9]", + "1010 Bob Builder [alt: bob.builder@example.com] [accID: 10]", + "1012 first.last+tag@x.com [alt: first.last+tag@x.com] [accID: 12]", + "1013 (500) 555-0006 [alt: (500) 555-0006] [accID: 13]", + "1015 Alex Smith Jr. [alt: alex.smith2@example.com] [accID: 15]", + "1016 Expensify Notifications [alt: notifications@expensify.com] [accID: 16]", + "1018 Domain Admin [alt: admin@example.com] [accID: 18]", + "1019 No Login [accID: 19]", + "1021 Concierge [alt: concierge@expensify.com] [accID: 21]", + "1100 Ada Ames [alt: ada.ames.0@example.com] [accID: 100]", + "1102 Cyd Ames [alt: cyd.ames.2@example.com] [accID: 102]", + "1103 Dan Ames [alt: dan.ames.3@example.com] [accID: 103]", + "1105 Fay Ames [alt: fay.ames.5@example.com] [accID: 105]", + "1106 Gus Ames [alt: gus.ames.6@example.com] [accID: 106]", + "1108 Ivy Ames [alt: ivy.ames.8@example.com] [accID: 108]", + "1109 Jon Ames [alt: jon.ames.9@example.com] [accID: 109]", + "1111 Lou Ames [alt: lou.ames.11@example.com] [accID: 111]", + "1112 Ada Byrd [alt: ada.byrd.12@example.com] [accID: 112]", + "1114 Cyd Byrd [alt: cyd.byrd.14@example.com] [accID: 114]", + "1115 Dan Byrd [alt: dan.byrd.15@example.com] [accID: 115]", + "1117 Fay Byrd [alt: fay.byrd.17@example.com] [accID: 117]", + "1118 Gus Byrd [alt: gus.byrd.18@example.com] [accID: 118]", + "1120 Ivy Byrd [alt: ivy.byrd.20@example.com] [accID: 120]", + "1121 Jon Byrd [alt: jon.byrd.21@example.com] [accID: 121]", + "1123 Lou Byrd [alt: lou.byrd.23@example.com] [accID: 123]", + "1124 Ada Cole [alt: ada.cole.24@example.com] [accID: 124]", + "1126 Cyd Cole [alt: cyd.cole.26@example.com] [accID: 126]", + "1127 Dan Cole [alt: dan.cole.27@example.com] [accID: 127]", + "1129 Fay Cole [alt: fay.cole.29@example.com] [accID: 129]", + "1130 Gus Cole [alt: gus.cole.30@example.com] [accID: 130]", + "1132 Ivy Cole [alt: ivy.cole.32@example.com] [accID: 132]", + "1133 Jon Cole [alt: jon.cole.33@example.com] [accID: 133]", + "1135 Lou Cole [alt: lou.cole.35@example.com] [accID: 135]", + "1136 Ada Dunn [alt: ada.dunn.36@example.com] [accID: 136]", + "1138 Cyd Dunn [alt: cyd.dunn.38@example.com] [accID: 138]", + "1139 Dan Dunn [alt: dan.dunn.39@example.com] [accID: 139]", + "2000 Mary-Jane O'Brien (archived) [alt: obrien@example.com] [accID: 11]", + "2001 Alex Smith [alt: alex.smith@example.com] [accID: 14]", + "2002 [accID: 999]", + "2003 Weekend Hiking [alt: Group]", + "2004 Current User (you) [alt: current.user@example.com] [accID: 1]", + "2005 Current User's expenses [gbr]", + "2006 Groß Straße's expenses", + "2007 Groß Straße", + "2010 #admins", + "2011 #announce", + "2012 #vít-room [rbr]", + "2013 #orphan-room", + "2020 José Ñúñez [alt: jose.nunez@example.com] [accID: 2]", + "2021 Thread about the offsite [alt: Thread]", + "2022 Hidden thread [alt: Thread]", + "2023 Empty thread [alt: Thread]", + "2024 Empty thread with a draft [alt: Thread]", + "2025 Empty pinned thread [alt: Thread]", + "2026 Packing list [alt: Thread]", + "2030 Book flights [alt: Task]", + "2031 Offsite expenses [alt: Expense] [accID: 1]", + "2032 Empty submitted report [alt: Expense] [accID: 1]", + "2034 José Ñúñez", + ], + }, +} +`; + +exports[`OptionsListUtils search parity (golden master) call site searchRouterDisplayed matches the golden master for every query 1`] = ` +{ + "": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "2004 Current User (you) [alt: This is your personal space. Use it for notes, tasks, drafts, and reminders.] [accID: 1]", + "2013 #orphan-room [alt: Gone Workspace • This chat room is for anything #orphan-room related.]", + "1124 Ada Cole [alt: This chat is with Ada Cole.] [accID: 124]", + "1103 Dan Ames [alt: This chat is with Dan Ames.] [accID: 103]", + "1019 No Login [alt: This chat is with No Login.] [accID: 19]", + "1123 Lou Byrd [alt: This chat is with Lou Byrd.] [accID: 123]", + "1102 Cyd Ames [alt: This chat is with Cyd Ames.] [accID: 102]", + "1018 Domain Admin [alt: This chat is with Domain Admin.] [accID: 18]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + " alex.smith@example.com ": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "2001 Alex Smith [alt: alex.smith@example.com] [accID: 14]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "(500) 555": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "1013 (500) 555-0006 [alt: This chat is with (500) 555-0006.] [accID: 13]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "*": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "+1 650 555 0100": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "1100537387 +1 650 555 0100 [alt: +1 650 555 0100] [accID: 1100537387]", + ], + "selfDMChat": null, + "userToInvite": "1100537387 +1 650 555 0100 [alt: +1 650 555 0100] [accID: 1100537387]", + "workspaceChats": [], + }, + ".*": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "5550006": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "1013 (500) 555-0006 [alt: This chat is with (500) 555-0006.] [accID: 13]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "@example": { + "currentUserOption": null, + "hasMore": true, + "personalDetails": [], + "recentReports": [ + "2004 Current User (you) [alt: This is your personal space. Use it for notes, tasks, drafts, and reminders.] [accID: 1]", + "2001 Alex Smith [alt: alex.smith@example.com] [accID: 14]", + "1124 Ada Cole [alt: This chat is with Ada Cole.] [accID: 124]", + "1103 Dan Ames [alt: This chat is with Dan Ames.] [accID: 103]", + "1123 Lou Byrd [alt: This chat is with Lou Byrd.] [accID: 123]", + "1102 Cyd Ames [alt: This chat is with Cyd Ames.] [accID: 102]", + "1018 Domain Admin [alt: This chat is with Domain Admin.] [accID: 18]", + "1121 Jon Byrd [alt: This chat is with Jon Byrd.] [accID: 121]", + "1100 Ada Ames [alt: This chat is with Ada Ames.] [accID: 100]", + "1120 Ivy Byrd [alt: This chat is with Ivy Byrd.] [accID: 120]", + "1015 Alex Smith Jr. [alt: This chat is with Alex Smith Jr..] [accID: 15]", + "1139 Dan Dunn [alt: This chat is with Dan Dunn.] [accID: 139]", + "1118 Gus Byrd [alt: This chat is with Gus Byrd.] [accID: 118]", + "1138 Cyd Dunn [alt: This chat is with Cyd Dunn.] [accID: 138]", + "1117 Fay Byrd [alt: This chat is with Fay Byrd.] [accID: 117]", + "2031 Offsite expenses [alt: No activity yet] [accID: 1]", + "1136 Ada Dunn [alt: This chat is with Ada Dunn.] [accID: 136]", + "1115 Dan Byrd [alt: This chat is with Dan Byrd.] [accID: 115]", + "1010 Bob Builder [bold] [alt: See you at the trailhead] [accID: 10]", + "1135 Lou Cole [alt: This chat is with Lou Cole.] [accID: 135]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "[": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "\\": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "a": { + "currentUserOption": null, + "hasMore": true, + "personalDetails": [], + "recentReports": [ + "2004 Current User (you) [alt: This is your personal space. Use it for notes, tasks, drafts, and reminders.] [accID: 1]", + "2001 Alex Smith [alt: alex.smith@example.com] [accID: 14]", + "1124 Ada Cole [alt: This chat is with Ada Cole.] [accID: 124]", + "1103 Dan Ames [alt: This chat is with Dan Ames.] [accID: 103]", + "1123 Lou Byrd [alt: This chat is with Lou Byrd.] [accID: 123]", + "1102 Cyd Ames [alt: This chat is with Cyd Ames.] [accID: 102]", + "1018 Domain Admin [alt: This chat is with Domain Admin.] [accID: 18]", + "1121 Jon Byrd [alt: This chat is with Jon Byrd.] [accID: 121]", + "1100 Ada Ames [alt: This chat is with Ada Ames.] [accID: 100]", + "1120 Ivy Byrd [alt: This chat is with Ivy Byrd.] [accID: 120]", + "1015 Alex Smith Jr. [alt: This chat is with Alex Smith Jr..] [accID: 15]", + "1139 Dan Dunn [alt: This chat is with Dan Dunn.] [accID: 139]", + "1118 Gus Byrd [alt: This chat is with Gus Byrd.] [accID: 118]", + "1138 Cyd Dunn [alt: This chat is with Cyd Dunn.] [accID: 138]", + "1117 Fay Byrd [alt: This chat is with Fay Byrd.] [accID: 117]", + "1012 first.last+tag@x.com [alt: This chat is with first.last+tag@x.com.] [accID: 12]", + "2031 Offsite expenses [alt: No activity yet] [accID: 1]", + "1136 Ada Dunn [alt: This chat is with Ada Dunn.] [accID: 136]", + "1112 Ada Byrd [alt: ada.byrd.12@example.com] [accID: 112]", + "2013 #orphan-room [alt: Gone Workspace • This chat room is for anything #orphan-room related.]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "acme": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "2012 #vít-room [rbr] [alt: Acme Holdings • This chat room is for anything #vít-room related.]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "ada byrd": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "1112 Ada Byrd [alt: This chat is with Ada Byrd.] [accID: 112]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "admins": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "2010 #admins [alt: Vít Workspace • This chat is with Vít Workspace admin. Use it to chat about workspace setup and more.]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "al": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "2001 Alex Smith [alt: alex.smith@example.com] [accID: 14]", + "1015 Alex Smith Jr. [alt: This chat is with Alex Smith Jr..] [accID: 15]", + "107 Hal Ames [alt: hal.ames.7@example.com] [accID: 107]", + "119 Hal Byrd [alt: hal.byrd.19@example.com] [accID: 119]", + "131 Hal Cole [alt: hal.cole.31@example.com] [accID: 131]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "alex smith": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "2001 Alex Smith [alt: alex.smith@example.com] [accID: 14]", + "1015 Alex Smith Jr. [alt: This chat is with Alex Smith Jr..] [accID: 15]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "anamaria": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "1009 Ana​Maria [alt: This chat is with Ana​Maria.] [accID: 9]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "bob builder": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "1010 Bob Builder [bold] [alt: See you at the trailhead] [accID: 10]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "cole lou": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "1135 Lou Cole [alt: This chat is with Lou Cole.] [accID: 135]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "concierge": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "1021 Concierge [alt: Concierge can answer questions, update expenses, and more.] [accID: 21]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "ex sm": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "2001 Alex Smith [alt: alex.smith@example.com] [accID: 14]", + "1015 Alex Smith Jr. [alt: This chat is with Alex Smith Jr..] [accID: 15]", + "1013 (500) 555-0006 [alt: This chat is with (500) 555-0006.] [accID: 13]", + "1004 İsmail Yılmaz [alt: Thanks!] [accID: 4]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "example.com": { + "currentUserOption": null, + "hasMore": true, + "personalDetails": [], + "recentReports": [ + "2004 Current User (you) [alt: This is your personal space. Use it for notes, tasks, drafts, and reminders.] [accID: 1]", + "2001 Alex Smith [alt: alex.smith@example.com] [accID: 14]", + "1124 Ada Cole [alt: This chat is with Ada Cole.] [accID: 124]", + "1103 Dan Ames [alt: This chat is with Dan Ames.] [accID: 103]", + "1123 Lou Byrd [alt: This chat is with Lou Byrd.] [accID: 123]", + "1102 Cyd Ames [alt: This chat is with Cyd Ames.] [accID: 102]", + "1018 Domain Admin [alt: This chat is with Domain Admin.] [accID: 18]", + "1121 Jon Byrd [alt: This chat is with Jon Byrd.] [accID: 121]", + "1100 Ada Ames [alt: This chat is with Ada Ames.] [accID: 100]", + "1120 Ivy Byrd [alt: This chat is with Ivy Byrd.] [accID: 120]", + "1015 Alex Smith Jr. [alt: This chat is with Alex Smith Jr..] [accID: 15]", + "1139 Dan Dunn [alt: This chat is with Dan Dunn.] [accID: 139]", + "1118 Gus Byrd [alt: This chat is with Gus Byrd.] [accID: 118]", + "1138 Cyd Dunn [alt: This chat is with Cyd Dunn.] [accID: 138]", + "1117 Fay Byrd [alt: This chat is with Fay Byrd.] [accID: 117]", + "2031 Offsite expenses [alt: No activity yet] [accID: 1]", + "1136 Ada Dunn [alt: This chat is with Ada Dunn.] [accID: 136]", + "1115 Dan Byrd [alt: This chat is with Dan Byrd.] [accID: 115]", + "1010 Bob Builder [bold] [alt: See you at the trailhead] [accID: 10]", + "1135 Lou Cole [alt: This chat is with Lou Cole.] [accID: 135]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "firstlast": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "1012 first.last+tag@x.com [alt: This chat is with first.last+tag@x.com.] [accID: 12]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "hiking": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "2003 Weekend Hiking [alt: This chat is with José Ñúñez, Mary-Jane O'Brien, and Vít Novák.]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "ikin": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "2003 Weekend Hiking [alt: This chat is with José Ñúñez, Mary-Jane O'Brien, and Vít Novák.]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "ismail": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "1004 İsmail Yılmaz [alt: Thanks!] [accID: 4]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "jr alex": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "1015 Alex Smith Jr. [alt: This chat is with Alex Smith Jr..] [accID: 15]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "last+tag": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "1012 first.last+tag@x.com [alt: This chat is with first.last+tag@x.com.] [accID: 12]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "lukasz": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "1006 Łukasz Wójcik [alt: This chat is with Łukasz Wójcik.] [accID: 6]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "mary-jane": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "2003 Weekend Hiking [alt: This chat is with José Ñúñez, Mary-Jane O'Brien, and Vít Novák.]", + "2000 Mary-Jane O'Brien (archived) [alt: This chat room has been archived.] [accID: 11]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "me": { + "currentUserOption": "1 Current User [alt: current.user@example.com] [accID: 1]", + "hasMore": true, + "personalDetails": [], + "recentReports": [ + "2004 Current User (you) [alt: This is your personal space. Use it for notes, tasks, drafts, and reminders.] [accID: 1]", + "1103 Dan Ames [alt: This chat is with Dan Ames.] [accID: 103]", + "1102 Cyd Ames [alt: This chat is with Cyd Ames.] [accID: 102]", + "1100 Ada Ames [alt: This chat is with Ada Ames.] [accID: 100]", + "1111 Lou Ames [alt: This chat is with Lou Ames.] [accID: 111]", + "1109 Jon Ames [alt: This chat is with Jon Ames.] [accID: 109]", + "1108 Ivy Ames [alt: This chat is with Ivy Ames.] [accID: 108]", + "1106 Gus Ames [alt: This chat is with Gus Ames.] [accID: 106]", + "1105 Fay Ames [alt: This chat is with Fay Ames.] [accID: 105]", + "101 Bea Ames [alt: bea.ames.1@example.com] [accID: 101]", + "104 Eve Ames [alt: eve.ames.4@example.com] [accID: 104]", + "107 Hal Ames [alt: hal.ames.7@example.com] [accID: 107]", + "2012 #vít-room [rbr] [alt: Acme Holdings • This chat room is for anything #vít-room related.]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "mith": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "2001 Alex Smith [alt: alex.smith@example.com] [accID: 14]", + "1015 Alex Smith Jr. [alt: This chat is with Alex Smith Jr..] [accID: 15]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "new.person@example.com": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "301623904 new.person@example.com [alt: new.person@example.com] [accID: 301623904]", + ], + "selfDMChat": null, + "userToInvite": "301623904 new.person@example.com [alt: new.person@example.com] [accID: 301623904]", + "workspaceChats": [], + }, + "nounce": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "2011 #announce [alt: Vít Workspace • This chat is with everyone in Vít Workspace. Use it for the most important announcements.]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "nunez": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "2020 José Ñúñez [bold] [alt: Packing list] [accID: 2]", + "2003 Weekend Hiking [alt: This chat is with José Ñúñez, Mary-Jane O'Brien, and Vít Novák.]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "obrien": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "2003 Weekend Hiking [alt: This chat is with José Ñúñez, Mary-Jane O'Brien, and Vít Novák.]", + "2000 Mary-Jane O'Brien (archived) [alt: This chat room has been archived.] [accID: 11]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "oldings": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "2012 #vít-room [rbr] [alt: Acme Holdings • This chat room is for anything #vít-room related.]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "orkspace": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "2006 Groß Straße's expenses [alt: Vít Workspace • This is where Groß Straße will submit expenses to Vít Workspace. Just use the + button.]", + "2005 Current User's expenses [alt: Vít Workspace • This is where Current User will submit expenses to Vít Workspace. Just use the + button.]", + "2013 #orphan-room [alt: Gone Workspace • This chat room is for anything #orphan-room related.]", + "2011 #announce [alt: Vít Workspace • This chat is with everyone in Vít Workspace. Use it for the most important announcements.]", + "2010 #admins [alt: Vít Workspace • This chat is with Vít Workspace admin. Use it to chat about workspace setup and more.]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "ovak": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "1003 Vít Novák [alt: This chat is with Vít Novák.] [accID: 3]", + "2003 Weekend Hiking [alt: This chat is with José Ñúñez, Mary-Jane O'Brien, and Vít Novák.]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "packing thread": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "2026 Packing list [bold] [alt: José Ñúñez: Tent and stove]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "rene": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "20 René Dupont [alt: rene@example.com] [accID: 20]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "smith jr": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "1015 Alex Smith Jr. [alt: This chat is with Alex Smith Jr..] [accID: 15]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "strasse": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "5 Groß Straße [alt: strasse@example.com] [accID: 5]", + "2006 Groß Straße's expenses [alt: Vít Workspace • This is where Groß Straße will submit expenses to Vít Workspace. Just use the + button.]", + "2007 Groß Straße [alt: Vít Workspace • This chat is for invoices between Groß Straße and Vít Workspace. Use the + button to send an invoice.]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "thread": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "2026 Packing list [bold] [alt: José Ñúñez: Tent and stove]", + "2025 Empty pinned thread [alt: No activity yet]", + "2024 Empty thread with a draft [alt: No activity yet]", + "2021 Thread about the offsite [bold] [alt: José Ñúñez: Count me in]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "to:al": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "vit": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "1003 Vít Novák [alt: This chat is with Vít Novák.] [accID: 3]", + "2006 Groß Straße's expenses [alt: Vít Workspace • This is where Groß Straße will submit expenses to Vít Workspace. Just use the + button.]", + "2005 Current User's expenses [alt: Vít Workspace • This is where Current User will submit expenses to Vít Workspace. Just use the + button.]", + "2003 Weekend Hiking [alt: This chat is with José Ñúñez, Mary-Jane O'Brien, and Vít Novák.]", + "2012 #vít-room [rbr] [alt: Acme Holdings • This chat room is for anything #vít-room related.]", + "2011 #announce [alt: Vít Workspace • This chat is with everyone in Vít Workspace. Use it for the most important announcements.]", + "2010 #admins [alt: Vít Workspace • This chat is with Vít Workspace admin. Use it to chat about workspace setup and more.]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "vit workspace": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "2006 Groß Straße's expenses [alt: Vít Workspace • This is where Groß Straße will submit expenses to Vít Workspace. Just use the + button.]", + "2005 Current User's expenses [alt: Vít Workspace • This is where Current User will submit expenses to Vít Workspace. Just use the + button.]", + "2011 #announce [alt: Vít Workspace • This chat is with everyone in Vít Workspace. Use it for the most important announcements.]", + "2010 #admins [alt: Vít Workspace • This chat is with Vít Workspace admin. Use it to chat about workspace setup and more.]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "workspace": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "2006 Groß Straße's expenses [alt: Vít Workspace • This is where Groß Straße will submit expenses to Vít Workspace. Just use the + button.]", + "2005 Current User's expenses [alt: Vít Workspace • This is where Current User will submit expenses to Vít Workspace. Just use the + button.]", + "2013 #orphan-room [alt: Gone Workspace • This chat room is for anything #orphan-room related.]", + "2011 #announce [alt: Vít Workspace • This chat is with everyone in Vít Workspace. Use it for the most important announcements.]", + "2010 #admins [alt: Vít Workspace • This chat is with Vít Workspace admin. Use it to chat about workspace setup and more.]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "x.com": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "1012 first.last+tag@x.com [alt: This chat is with first.last+tag@x.com.] [accID: 12]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "you": { + "currentUserOption": "1 Current User [alt: current.user@example.com] [accID: 1]", + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "2004 Current User (you) [alt: This is your personal space. Use it for notes, tasks, drafts, and reminders.] [accID: 1]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "zzzqqq": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "δυσσευς": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "1007 Οδυσσεύς [alt: This chat is with Οδυσσεύς.] [accID: 7]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "δυσσευσ": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "👩‍💻": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "8 Zoë 👩‍💻 [alt: zoe@example.com] [accID: 8]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, +} +`; + +exports[`OptionsListUtils search parity (golden master) call site searchRouterLocal matches the golden master for every query 1`] = ` +{ + "": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "2004 Current User (you) [alt: This is your personal space. Use it for notes, tasks, drafts, and reminders.] [accID: 1]", + "2013 #orphan-room [alt: Gone Workspace • This chat room is for anything #orphan-room related.]", + "1124 Ada Cole [alt: This chat is with Ada Cole.] [accID: 124]", + "1103 Dan Ames [alt: This chat is with Dan Ames.] [accID: 103]", + "1019 No Login [alt: This chat is with No Login.] [accID: 19]", + "1123 Lou Byrd [alt: This chat is with Lou Byrd.] [accID: 123]", + "1102 Cyd Ames [alt: This chat is with Cyd Ames.] [accID: 102]", + "1018 Domain Admin [alt: This chat is with Domain Admin.] [accID: 18]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + " alex.smith@example.com ": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [ + "2001 Alex Smith [alt: alex.smith@example.com] [accID: 14]", + ], + "recentReports": [], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "(500) 555": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [ + "1013 (500) 555-0006 [alt: +15005550006@expensify.sms] [accID: 13]", + ], + "recentReports": [ + "1013 (500) 555-0006 [alt: This chat is with (500) 555-0006.] [accID: 13]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "*": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "+1 650 555 0100": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [], + "selfDMChat": null, + "userToInvite": "1100537387 +1 650 555 0100 [alt: +1 650 555 0100] [accID: 1100537387]", + "workspaceChats": [], + }, + ".*": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "5550006": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [ + "1013 (500) 555-0006 [alt: +15005550006@expensify.sms] [accID: 13]", + ], + "recentReports": [ + "1013 (500) 555-0006 [alt: This chat is with (500) 555-0006.] [accID: 13]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "@example": { + "currentUserOption": null, + "hasMore": true, + "personalDetails": [ + "1100 Ada Ames [alt: ada.ames.0@example.com] [accID: 100]", + "1112 Ada Byrd [alt: ada.byrd.12@example.com] [accID: 112]", + "1124 Ada Cole [alt: ada.cole.24@example.com] [accID: 124]", + "1136 Ada Dunn [alt: ada.dunn.36@example.com] [accID: 136]", + "2001 Alex Smith [alt: alex.smith@example.com] [accID: 14]", + ], + "recentReports": [ + "2004 Current User (you) [alt: This is your personal space. Use it for notes, tasks, drafts, and reminders.] [accID: 1]", + "1124 Ada Cole [alt: This chat is with Ada Cole.] [accID: 124]", + "1103 Dan Ames [alt: This chat is with Dan Ames.] [accID: 103]", + "1123 Lou Byrd [alt: This chat is with Lou Byrd.] [accID: 123]", + "1102 Cyd Ames [alt: This chat is with Cyd Ames.] [accID: 102]", + "1018 Domain Admin [alt: This chat is with Domain Admin.] [accID: 18]", + "1121 Jon Byrd [alt: This chat is with Jon Byrd.] [accID: 121]", + "1100 Ada Ames [alt: This chat is with Ada Ames.] [accID: 100]", + "1120 Ivy Byrd [alt: This chat is with Ivy Byrd.] [accID: 120]", + "1015 Alex Smith Jr. [alt: This chat is with Alex Smith Jr..] [accID: 15]", + "1139 Dan Dunn [alt: This chat is with Dan Dunn.] [accID: 139]", + "1118 Gus Byrd [alt: This chat is with Gus Byrd.] [accID: 118]", + "1138 Cyd Dunn [alt: This chat is with Cyd Dunn.] [accID: 138]", + "1117 Fay Byrd [alt: This chat is with Fay Byrd.] [accID: 117]", + "2031 Offsite expenses [alt: No activity yet] [accID: 1]", + "1136 Ada Dunn [alt: This chat is with Ada Dunn.] [accID: 136]", + "1115 Dan Byrd [alt: This chat is with Dan Byrd.] [accID: 115]", + "1010 Bob Builder [bold] [alt: See you at the trailhead] [accID: 10]", + "1135 Lou Cole [alt: This chat is with Lou Cole.] [accID: 135]", + "1114 Cyd Byrd [alt: This chat is with Cyd Byrd.] [accID: 114]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "[": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "\\": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "a": { + "currentUserOption": null, + "hasMore": true, + "personalDetails": [ + "1100 Ada Ames [alt: ada.ames.0@example.com] [accID: 100]", + "1112 Ada Byrd [alt: ada.byrd.12@example.com] [accID: 112]", + "1124 Ada Cole [alt: ada.cole.24@example.com] [accID: 124]", + "1136 Ada Dunn [alt: ada.dunn.36@example.com] [accID: 136]", + "2001 Alex Smith [alt: alex.smith@example.com] [accID: 14]", + ], + "recentReports": [ + "2004 Current User (you) [alt: This is your personal space. Use it for notes, tasks, drafts, and reminders.] [accID: 1]", + "2013 #orphan-room [alt: Gone Workspace • This chat room is for anything #orphan-room related.]", + "1124 Ada Cole [alt: This chat is with Ada Cole.] [accID: 124]", + "1103 Dan Ames [alt: This chat is with Dan Ames.] [accID: 103]", + "1123 Lou Byrd [alt: This chat is with Lou Byrd.] [accID: 123]", + "1102 Cyd Ames [alt: This chat is with Cyd Ames.] [accID: 102]", + "1018 Domain Admin [alt: This chat is with Domain Admin.] [accID: 18]", + "1121 Jon Byrd [alt: This chat is with Jon Byrd.] [accID: 121]", + "1100 Ada Ames [alt: This chat is with Ada Ames.] [accID: 100]", + "1120 Ivy Byrd [alt: This chat is with Ivy Byrd.] [accID: 120]", + "1015 Alex Smith Jr. [alt: This chat is with Alex Smith Jr..] [accID: 15]", + "2012 #vít-room [rbr] [alt: Acme Holdings • This chat room is for anything #vít-room related.]", + "1139 Dan Dunn [alt: This chat is with Dan Dunn.] [accID: 139]", + "1118 Gus Byrd [alt: This chat is with Gus Byrd.] [accID: 118]", + "2007 Groß Straße [alt: Vít Workspace • This chat is for invoices between Groß Straße and Vít Workspace. Use the + button to send an invoice.]", + "1138 Cyd Dunn [alt: This chat is with Cyd Dunn.] [accID: 138]", + "1117 Fay Byrd [alt: This chat is with Fay Byrd.] [accID: 117]", + "1012 first.last+tag@x.com [alt: This chat is with first.last+tag@x.com.] [accID: 12]", + "2031 Offsite expenses [alt: No activity yet] [accID: 1]", + "1136 Ada Dunn [alt: This chat is with Ada Dunn.] [accID: 136]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "acme": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "2012 #vít-room [rbr] [alt: Acme Holdings • This chat room is for anything #vít-room related.]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "ada byrd": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [ + "1112 Ada Byrd [alt: ada.byrd.12@example.com] [accID: 112]", + ], + "recentReports": [ + "1112 Ada Byrd [alt: This chat is with Ada Byrd.] [accID: 112]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "admins": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "2010 #admins [alt: Vít Workspace • This chat is with Vít Workspace admin. Use it to chat about workspace setup and more.]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "al": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [ + "2001 Alex Smith [alt: alex.smith@example.com] [accID: 14]", + "1015 Alex Smith Jr. [alt: alex.smith2@example.com] [accID: 15]", + "107 Hal Ames [alt: hal.ames.7@example.com] [accID: 107]", + "119 Hal Byrd [alt: hal.byrd.19@example.com] [accID: 119]", + "131 Hal Cole [alt: hal.cole.31@example.com] [accID: 131]", + ], + "recentReports": [ + "1015 Alex Smith Jr. [alt: This chat is with Alex Smith Jr..] [accID: 15]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "alex smith": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [ + "2001 Alex Smith [alt: alex.smith@example.com] [accID: 14]", + "1015 Alex Smith Jr. [alt: alex.smith2@example.com] [accID: 15]", + ], + "recentReports": [ + "1015 Alex Smith Jr. [alt: This chat is with Alex Smith Jr..] [accID: 15]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "anamaria": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [ + "1009 Ana​Maria [alt: ana.maria@example.com] [accID: 9]", + ], + "recentReports": [ + "1009 Ana​Maria [alt: This chat is with Ana​Maria.] [accID: 9]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "bob builder": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [ + "1010 Bob Builder [alt: bob.builder@example.com] [accID: 10]", + ], + "recentReports": [ + "1010 Bob Builder [bold] [alt: See you at the trailhead] [accID: 10]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "cole lou": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [ + "1135 Lou Cole [alt: lou.cole.35@example.com] [accID: 135]", + ], + "recentReports": [ + "1135 Lou Cole [alt: This chat is with Lou Cole.] [accID: 135]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "concierge": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [ + "1021 Concierge [alt: concierge@expensify.com] [accID: 21]", + ], + "recentReports": [ + "1021 Concierge [alt: Concierge can answer questions, update expenses, and more.] [accID: 21]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "ex sm": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [ + "1013 (500) 555-0006 [alt: +15005550006@expensify.sms] [accID: 13]", + "2001 Alex Smith [alt: alex.smith@example.com] [accID: 14]", + "1015 Alex Smith Jr. [alt: alex.smith2@example.com] [accID: 15]", + "1004 İsmail Yılmaz [alt: ismail@example.com] [accID: 4]", + ], + "recentReports": [ + "1015 Alex Smith Jr. [alt: This chat is with Alex Smith Jr..] [accID: 15]", + "1013 (500) 555-0006 [alt: This chat is with (500) 555-0006.] [accID: 13]", + "1004 İsmail Yılmaz [alt: Thanks!] [accID: 4]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "example.com": { + "currentUserOption": null, + "hasMore": true, + "personalDetails": [ + "1100 Ada Ames [alt: ada.ames.0@example.com] [accID: 100]", + "1112 Ada Byrd [alt: ada.byrd.12@example.com] [accID: 112]", + "1124 Ada Cole [alt: ada.cole.24@example.com] [accID: 124]", + "1136 Ada Dunn [alt: ada.dunn.36@example.com] [accID: 136]", + "2001 Alex Smith [alt: alex.smith@example.com] [accID: 14]", + ], + "recentReports": [ + "2004 Current User (you) [alt: This is your personal space. Use it for notes, tasks, drafts, and reminders.] [accID: 1]", + "1124 Ada Cole [alt: This chat is with Ada Cole.] [accID: 124]", + "1103 Dan Ames [alt: This chat is with Dan Ames.] [accID: 103]", + "1123 Lou Byrd [alt: This chat is with Lou Byrd.] [accID: 123]", + "1102 Cyd Ames [alt: This chat is with Cyd Ames.] [accID: 102]", + "1018 Domain Admin [alt: This chat is with Domain Admin.] [accID: 18]", + "1121 Jon Byrd [alt: This chat is with Jon Byrd.] [accID: 121]", + "1100 Ada Ames [alt: This chat is with Ada Ames.] [accID: 100]", + "1120 Ivy Byrd [alt: This chat is with Ivy Byrd.] [accID: 120]", + "1015 Alex Smith Jr. [alt: This chat is with Alex Smith Jr..] [accID: 15]", + "1139 Dan Dunn [alt: This chat is with Dan Dunn.] [accID: 139]", + "1118 Gus Byrd [alt: This chat is with Gus Byrd.] [accID: 118]", + "1138 Cyd Dunn [alt: This chat is with Cyd Dunn.] [accID: 138]", + "1117 Fay Byrd [alt: This chat is with Fay Byrd.] [accID: 117]", + "2031 Offsite expenses [alt: No activity yet] [accID: 1]", + "1136 Ada Dunn [alt: This chat is with Ada Dunn.] [accID: 136]", + "1115 Dan Byrd [alt: This chat is with Dan Byrd.] [accID: 115]", + "1010 Bob Builder [bold] [alt: See you at the trailhead] [accID: 10]", + "1135 Lou Cole [alt: This chat is with Lou Cole.] [accID: 135]", + "1114 Cyd Byrd [alt: This chat is with Cyd Byrd.] [accID: 114]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "firstlast": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [ + "1012 first.last+tag@x.com [alt: first.last+tag@x.com] [accID: 12]", + ], + "recentReports": [ + "1012 first.last+tag@x.com [alt: This chat is with first.last+tag@x.com.] [accID: 12]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "hiking": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "2003 Weekend Hiking [alt: This chat is with José Ñúñez, Mary-Jane O'Brien, and Vít Novák.]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "ikin": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "2003 Weekend Hiking [alt: This chat is with José Ñúñez, Mary-Jane O'Brien, and Vít Novák.]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "ismail": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [ + "1004 İsmail Yılmaz [alt: ismail@example.com] [accID: 4]", + ], + "recentReports": [ + "1004 İsmail Yılmaz [alt: Thanks!] [accID: 4]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "jr alex": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [ + "1015 Alex Smith Jr. [alt: alex.smith2@example.com] [accID: 15]", + ], + "recentReports": [ + "1015 Alex Smith Jr. [alt: This chat is with Alex Smith Jr..] [accID: 15]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "last+tag": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [ + "1012 first.last+tag@x.com [alt: first.last+tag@x.com] [accID: 12]", + ], + "recentReports": [ + "1012 first.last+tag@x.com [alt: This chat is with first.last+tag@x.com.] [accID: 12]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "lukasz": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [ + "1006 Łukasz Wójcik [alt: lukasz@example.com] [accID: 6]", + ], + "recentReports": [ + "1006 Łukasz Wójcik [alt: This chat is with Łukasz Wójcik.] [accID: 6]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "mary-jane": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [ + "2000 Mary-Jane O'Brien [alt: obrien@example.com] [accID: 11]", + ], + "recentReports": [ + "2003 Weekend Hiking [alt: This chat is with José Ñúñez, Mary-Jane O'Brien, and Vít Novák.]", + "2000 Mary-Jane O'Brien (archived) [alt: This chat room has been archived.] [accID: 11]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "me": { + "currentUserOption": "1 Current User [alt: current.user@example.com] [accID: 1]", + "hasMore": true, + "personalDetails": [ + "1100 Ada Ames [alt: ada.ames.0@example.com] [accID: 100]", + "101 Bea Ames [alt: bea.ames.1@example.com] [accID: 101]", + "1 Current User [alt: current.user@example.com] [accID: 1]", + "1102 Cyd Ames [alt: cyd.ames.2@example.com] [accID: 102]", + "1103 Dan Ames [alt: dan.ames.3@example.com] [accID: 103]", + "104 Eve Ames [alt: eve.ames.4@example.com] [accID: 104]", + "1105 Fay Ames [alt: fay.ames.5@example.com] [accID: 105]", + "1106 Gus Ames [alt: gus.ames.6@example.com] [accID: 106]", + "107 Hal Ames [alt: hal.ames.7@example.com] [accID: 107]", + ], + "recentReports": [ + "2004 Current User (you) [alt: This is your personal space. Use it for notes, tasks, drafts, and reminders.] [accID: 1]", + "1103 Dan Ames [alt: This chat is with Dan Ames.] [accID: 103]", + "1102 Cyd Ames [alt: This chat is with Cyd Ames.] [accID: 102]", + "1100 Ada Ames [alt: This chat is with Ada Ames.] [accID: 100]", + "2012 #vít-room [rbr] [alt: Acme Holdings • This chat room is for anything #vít-room related.]", + "1111 Lou Ames [alt: This chat is with Lou Ames.] [accID: 111]", + "1109 Jon Ames [alt: This chat is with Jon Ames.] [accID: 109]", + "1108 Ivy Ames [alt: This chat is with Ivy Ames.] [accID: 108]", + "1106 Gus Ames [alt: This chat is with Gus Ames.] [accID: 106]", + "1105 Fay Ames [alt: This chat is with Fay Ames.] [accID: 105]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "mith": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [ + "2001 Alex Smith [alt: alex.smith@example.com] [accID: 14]", + "1015 Alex Smith Jr. [alt: alex.smith2@example.com] [accID: 15]", + ], + "recentReports": [ + "1015 Alex Smith Jr. [alt: This chat is with Alex Smith Jr..] [accID: 15]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "new.person@example.com": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [], + "selfDMChat": null, + "userToInvite": "301623904 new.person@example.com [alt: new.person@example.com] [accID: 301623904]", + "workspaceChats": [], + }, + "nounce": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "2011 #announce [alt: Vít Workspace • This chat is with everyone in Vít Workspace. Use it for the most important announcements.]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "nunez": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [ + "2020 José Ñúñez [alt: jose.nunez@example.com] [accID: 2]", + ], + "recentReports": [ + "2003 Weekend Hiking [alt: This chat is with José Ñúñez, Mary-Jane O'Brien, and Vít Novák.]", + "2020 José Ñúñez [bold] [alt: Packing list] [accID: 2]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "obrien": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [ + "2000 Mary-Jane O'Brien [alt: obrien@example.com] [accID: 11]", + ], + "recentReports": [ + "2003 Weekend Hiking [alt: This chat is with José Ñúñez, Mary-Jane O'Brien, and Vít Novák.]", + "2000 Mary-Jane O'Brien (archived) [alt: This chat room has been archived.] [accID: 11]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "oldings": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "2012 #vít-room [rbr] [alt: Acme Holdings • This chat room is for anything #vít-room related.]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "orkspace": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "2013 #orphan-room [alt: Gone Workspace • This chat room is for anything #orphan-room related.]", + "2011 #announce [alt: Vít Workspace • This chat is with everyone in Vít Workspace. Use it for the most important announcements.]", + "2006 Groß Straße's expenses [alt: Vít Workspace • This is where Groß Straße will submit expenses to Vít Workspace. Just use the + button.]", + "2005 Current User's expenses [alt: Vít Workspace • This is where Current User will submit expenses to Vít Workspace. Just use the + button.]", + "2010 #admins [alt: Vít Workspace • This chat is with Vít Workspace admin. Use it to chat about workspace setup and more.]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "ovak": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [ + "1003 Vít Novák [alt: vit.novak@example.com] [accID: 3]", + ], + "recentReports": [ + "2003 Weekend Hiking [alt: This chat is with José Ñúñez, Mary-Jane O'Brien, and Vít Novák.]", + "1003 Vít Novák [alt: This chat is with Vít Novák.] [accID: 3]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "packing thread": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "2026 Packing list [bold] [alt: José Ñúñez: Tent and stove]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "rene": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [ + "20 René Dupont [alt: rene@example.com] [accID: 20]", + ], + "recentReports": [], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "smith jr": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [ + "1015 Alex Smith Jr. [alt: alex.smith2@example.com] [accID: 15]", + ], + "recentReports": [ + "1015 Alex Smith Jr. [alt: This chat is with Alex Smith Jr..] [accID: 15]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "strasse": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [ + "5 Groß Straße [alt: strasse@example.com] [accID: 5]", + ], + "recentReports": [ + "2007 Groß Straße [alt: Vít Workspace • This chat is for invoices between Groß Straße and Vít Workspace. Use the + button to send an invoice.]", + "2006 Groß Straße's expenses [alt: Vít Workspace • This is where Groß Straße will submit expenses to Vít Workspace. Just use the + button.]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "thread": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "2026 Packing list [bold] [alt: José Ñúñez: Tent and stove]", + "2025 Empty pinned thread [alt: No activity yet]", + "2024 Empty thread with a draft [alt: No activity yet]", + "2021 Thread about the offsite [bold] [alt: José Ñúñez: Count me in]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "to:al": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "vit": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [ + "1003 Vít Novák [alt: vit.novak@example.com] [accID: 3]", + ], + "recentReports": [ + "2012 #vít-room [rbr] [alt: Acme Holdings • This chat room is for anything #vít-room related.]", + "2011 #announce [alt: Vít Workspace • This chat is with everyone in Vít Workspace. Use it for the most important announcements.]", + "2006 Groß Straße's expenses [alt: Vít Workspace • This is where Groß Straße will submit expenses to Vít Workspace. Just use the + button.]", + "2005 Current User's expenses [alt: Vít Workspace • This is where Current User will submit expenses to Vít Workspace. Just use the + button.]", + "2003 Weekend Hiking [alt: This chat is with José Ñúñez, Mary-Jane O'Brien, and Vít Novák.]", + "2010 #admins [alt: Vít Workspace • This chat is with Vít Workspace admin. Use it to chat about workspace setup and more.]", + "1003 Vít Novák [alt: This chat is with Vít Novák.] [accID: 3]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "vit workspace": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "2011 #announce [alt: Vít Workspace • This chat is with everyone in Vít Workspace. Use it for the most important announcements.]", + "2006 Groß Straße's expenses [alt: Vít Workspace • This is where Groß Straße will submit expenses to Vít Workspace. Just use the + button.]", + "2005 Current User's expenses [alt: Vít Workspace • This is where Current User will submit expenses to Vít Workspace. Just use the + button.]", + "2010 #admins [alt: Vít Workspace • This chat is with Vít Workspace admin. Use it to chat about workspace setup and more.]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "workspace": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "2013 #orphan-room [alt: Gone Workspace • This chat room is for anything #orphan-room related.]", + "2011 #announce [alt: Vít Workspace • This chat is with everyone in Vít Workspace. Use it for the most important announcements.]", + "2006 Groß Straße's expenses [alt: Vít Workspace • This is where Groß Straße will submit expenses to Vít Workspace. Just use the + button.]", + "2005 Current User's expenses [alt: Vít Workspace • This is where Current User will submit expenses to Vít Workspace. Just use the + button.]", + "2010 #admins [alt: Vít Workspace • This chat is with Vít Workspace admin. Use it to chat about workspace setup and more.]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "x.com": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [ + "1012 first.last+tag@x.com [alt: first.last+tag@x.com] [accID: 12]", + ], + "recentReports": [ + "1012 first.last+tag@x.com [alt: This chat is with first.last+tag@x.com.] [accID: 12]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "you": { + "currentUserOption": "1 Current User [alt: current.user@example.com] [accID: 1]", + "hasMore": false, + "personalDetails": [ + "1 Current User [alt: current.user@example.com] [accID: 1]", + ], + "recentReports": [ + "2004 Current User (you) [alt: This is your personal space. Use it for notes, tasks, drafts, and reminders.] [accID: 1]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "zzzqqq": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "δυσσευς": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [ + "1007 Οδυσσεύς [alt: odysseus@example.com] [accID: 7]", + ], + "recentReports": [ + "1007 Οδυσσεύς [alt: This chat is with Οδυσσεύς.] [accID: 7]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "δυσσευσ": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "👩‍💻": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [ + "8 Zoë 👩‍💻 [alt: zoe@example.com] [accID: 8]", + ], + "recentReports": [], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, +} +`; diff --git a/tests/utils/searchParity/generateSearchFixture.ts b/tests/utils/searchParity/generateSearchFixture.ts new file mode 100644 index 000000000000..9caf2a72fa5a --- /dev/null +++ b/tests/utils/searchParity/generateSearchFixture.ts @@ -0,0 +1,688 @@ +// cspell:disable -- names and queries deliberately use diacritics, non-Latin scripts and misspelled forms +/* eslint-disable @typescript-eslint/naming-convention -- logins and IDs are object keys throughout this file */ +import type {PrivateIsArchivedMap} from '@hooks/usePrivateIsArchivedMap'; + +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {Logins, PersonalDetails, PersonalDetailsList, Policy, Report, ReportActions} from '@src/types/onyx'; +import type {ReportAttributes, SortedReportActionsDerivedValue, VisibleReportActionsDerivedValue} from '@src/types/onyx/DerivedValues'; + +import type {OnyxCollection, OnyxMultiSetInput} from 'react-native-onyx'; + +import Onyx from 'react-native-onyx'; + +import getOnyxValue from '../getOnyxValue'; +import waitForBatchedUpdates from '../waitForBatchedUpdates'; + +const CURRENT_USER_ACCOUNT_ID = 1; +const CURRENT_USER_EMAIL = 'current.user@example.com'; +const OWN_POLICY_ID = 'A1B2C3D4E5F60001'; +const OTHER_POLICY_ID = 'A1B2C3D4E5F60002'; +const MISSING_POLICY_ID = 'A1B2C3D4E5F60003'; +/** A DM participant with no personal details, as after a partial personal details load. */ +const UNKNOWN_ACCOUNT_ID = 999; +const SESSION = { + accountID: CURRENT_USER_ACCOUNT_ID, + email: CURRENT_USER_EMAIL, +}; +const CONCIERGE_ACCOUNT_ID = 21; +/** Contacts and reports carry fixed IDs, so adding one never renumbers the others and churns the golden master. */ +const FIRST_FILLER_ACCOUNT_ID = 100; +/** A contact's generated 1:1 DM is DM_REPORT_ID_OFFSET + its accountID. */ +const DM_REPORT_ID_OFFSET = 1000; +/** Every reportID is above this, and a report's timestamp is derived from its distance to it. */ +const REPORT_ID_TIMESTAMP_ORIGIN = 1000; + +/** The values Onyx derives from the fixture, which the app passes to the search next to the raw collections. */ +type SearchFixtureDerivedData = { + reportAttributes: Record; + visibleReportActions: VisibleReportActionsDerivedValue; + sortedReportActions: SortedReportActionsDerivedValue; +}; + +type SearchFixture = { + personalDetails: PersonalDetailsList; + reports: NonNullable>; + reportActions: NonNullable>; + policies: NonNullable>; + privateIsArchivedMap: PrivateIsArchivedMap; + /** Empty until writeSearchFixtureToOnyx returns what Onyx derived. */ + derived: SearchFixtureDerivedData; + draftComments: NonNullable>; + loginList: Logins; + conciergeReportID: string; +}; + +const NAMED_CONTACTS: Array> = [ + { + accountID: CURRENT_USER_ACCOUNT_ID, + login: CURRENT_USER_EMAIL, + displayName: 'Current User', + firstName: 'Current', + lastName: 'User', + }, + { + accountID: 2, + login: 'jose.nunez@example.com', + displayName: 'Jos\u00e9 \u00d1\u00fa\u00f1ez', + }, + { + accountID: 3, + login: 'vit.novak@example.com', + displayName: 'V\u00edt Nov\u00e1k', + }, + { + accountID: 4, + login: 'ismail@example.com', + displayName: '\u0130smail Y\u0131lmaz', + }, + { + accountID: 5, + login: 'strasse@example.com', + displayName: 'Gro\u00df Stra\u00dfe', + }, + { + accountID: 6, + login: 'lukasz@example.com', + displayName: '\u0141ukasz W\u00f3jcik', + }, + { + accountID: 7, + login: 'odysseus@example.com', + displayName: '\u039f\u03b4\u03c5\u03c3\u03c3\u03b5\u03cd\u03c2', + }, + { + accountID: 8, + login: 'zoe@example.com', + displayName: 'Zo\u00eb \ud83d\udc69\u200d\ud83d\udcbb', + }, + { + accountID: 9, + login: 'ana.maria@example.com', + displayName: 'Ana\u200bMaria', + }, + { + accountID: 10, + login: 'bob.builder@example.com', + displayName: 'Bob\u00a0Builder', + }, + { + accountID: 11, + login: 'obrien@example.com', + displayName: "Mary-Jane O'Brien", + }, + {accountID: 12, login: 'first.last+tag@x.com', displayName: ''}, + {accountID: 13, login: '+15005550006@expensify.sms', displayName: ''}, + {accountID: 14, login: 'alex.smith@example.com', displayName: 'Alex Smith'}, + // Shares the "alex smith" prefix but not the full name, so the two contacts never tie when sorted by name + { + accountID: 15, + login: 'alex.smith2@example.com', + displayName: 'Alex Smith Jr.', + }, + { + accountID: 16, + login: CONST.EMAIL.NOTIFICATIONS, + displayName: 'Expensify Notifications', + }, + { + accountID: 17, + login: CONST.ACCOUNT_EXECUTIVE_LOGIN, + displayName: 'Account Executive', + }, + {accountID: 18, login: 'admin@example.com', displayName: 'Domain Admin'}, + {accountID: 19, login: undefined, displayName: 'No Login'}, + // Stored decomposed (NFD: "e" + combining acute), unlike every other name + { + accountID: 20, + login: 'rene@example.com', + displayName: 'Rene\u0301 Dupont', + }, + { + accountID: CONCIERGE_ACCOUNT_ID, + login: CONST.EMAIL.CONCIERGE, + displayName: 'Concierge', + }, +]; + +const SEARCH_QUERIES = [ + '', + 'a', + 'al', + 'alex smith', + 'ada byrd', + 'smith jr', + 'jr alex', + 'cole lou', + 'vit workspace', + 'ex sm', + 'vit', + 'nunez', + 'ismail', + 'strasse', + 'lukasz', + '\u03b4\u03c5\u03c3\u03c3\u03b5\u03c5\u03c2', + '\ud83d\udc69\u200d\ud83d\udcbb', + 'anamaria', + 'bob builder', + // Punctuation and phone digits, which a tokenizing search splits differently from a substring match + 'obrien', + 'mary-jane', + 'firstlast', + 'last+tag', + 'x.com', + '@example', + 'rene', + 'concierge', + 'acme', + 'example.com', + 'you', + 'me', + 'hiking', + 'thread', + '\u03b4\u03c5\u03c3\u03c3\u03b5\u03c5\u03c3', + 'admins', + 'workspace', + 'new.person@example.com', + '+1 650 555 0100', + '5550006', + '(500) 555', + // A padded login: the displayed passes rank by the raw query, which then matches no login exactly + ' alex.smith@example.com ', + 'to:al', + 'zzzqqq', + // Infixes, which a substring search finds and a word-prefix index does not: in a name, a room name, a workspace subtitle and a group participant's name + 'mith', + 'ikin', + 'nounce', + 'orkspace', + 'oldings', + 'ovak', + // Matches a thread only through its "Thread" alternate text + 'packing thread', + // Regular expression metacharacters, which a matcher must treat literally + '*', + '[', + '\\', + '.*', +]; + +const FIRST_NAMES = ['Ada', 'Bea', 'Cyd', 'Dan', 'Eve', 'Fay', 'Gus', 'Hal', 'Ivy', 'Jon', 'Kim', 'Lou']; +const LAST_NAMES = ['Ames', 'Byrd', 'Cole', 'Dunn', 'Eads', 'Ford', 'Gray', 'Hale']; +// Every filler gets a distinct "First Last" pair (up to FIRST_NAMES.length * LAST_NAMES.length), so no two contacts tie when sorted by name +const FILLER_CONTACT_COUNT = 40; + +/** The derived name of a DM with a nameless phone-number contact. */ +const FORMATTED_PHONE_LOGINS: Record = { + '+15005550006@expensify.sms': '(500) 555-0006', +}; + +/** Per-contact overrides of the generated 1:1 DM, keyed by accountID. */ +const DM_OVERRIDES: Record> = { + // Unread: the contact wrote last and the current user has not read it since + 10: { + lastActorAccountID: 10, + lastReadTime: '2024-01-01 00:00:00.000', + lastMessageText: 'See you at the trailhead', + }, + // Read: the current user wrote last and read everything + 4: { + lastActorAccountID: CURRENT_USER_ACCOUNT_ID, + lastReadTime: '2024-12-31 00:00:00.000', + lastMessageText: 'Thanks!', + }, +}; + +/** A unique timestamp per report: `day` sets the coarse recency, `sequence` (unique per report, below 3600) breaks every tie. */ +function timestamp(day: number, sequence: number): string { + const minutes = String(Math.floor(sequence / 60) % 60).padStart(2, '0'); + const seconds = String(sequence % 60).padStart(2, '0'); + return `2024-01-${String((day % 28) + 1).padStart(2, '0')} 10:${minutes}:${seconds}.000`; +} + +function buildPolicy(id: string, name: string, role: Policy['role']): Policy { + return { + id, + name, + type: CONST.POLICY.TYPE.TEAM, + role, + owner: CURRENT_USER_EMAIL, + ownerAccountID: CURRENT_USER_ACCOUNT_ID, + outputCurrency: CONST.CURRENCY.USD, + }; +} + +function participantsOf(accountIDs: number[], hiddenAccountIDs: number[] = []): Report['participants'] { + const participants: NonNullable = {}; + for (const accountID of accountIDs) { + participants[accountID] = { + notificationPreference: hiddenAccountIDs.includes(accountID) ? CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN : CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS, + }; + } + return participants; +} + +/** + * Builds a deterministic fixture covering every searchable report type and edge case (DMs, groups, rooms, threads, tasks, expenses, and status indicators). + * Reports have unique timestamps and contacts have unique names. + */ +function generateSearchFixture(): SearchFixture { + const personalDetails: PersonalDetailsList = {}; + const contactAccountIDs: number[] = []; + + for (const contact of NAMED_CONTACTS) { + personalDetails[contact.accountID] = contact; + contactAccountIDs.push(contact.accountID); + } + for (let index = 0; index < FILLER_CONTACT_COUNT; index++) { + const accountID = FIRST_FILLER_ACCOUNT_ID + index; + const firstName = FIRST_NAMES.at(index % FIRST_NAMES.length) ?? ''; + const lastName = LAST_NAMES.at(Math.floor(index / FIRST_NAMES.length) % LAST_NAMES.length) ?? ''; + personalDetails[accountID] = { + accountID, + login: `${firstName}.${lastName}.${index}@example.com`.toLowerCase(), + displayName: `${firstName} ${lastName}`, + firstName, + lastName, + }; + contactAccountIDs.push(accountID); + } + + const policies: SearchFixture['policies'] = { + [`${ONYXKEYS.COLLECTION.POLICY}${OWN_POLICY_ID}`]: buildPolicy(OWN_POLICY_ID, 'V\u00edt Workspace', CONST.POLICY.ROLE.ADMIN), + [`${ONYXKEYS.COLLECTION.POLICY}${OTHER_POLICY_ID}`]: buildPolicy(OTHER_POLICY_ID, 'Acme Holdings', CONST.POLICY.ROLE.USER), + }; + + const reports: SearchFixture['reports'] = {}; + const reportActions: SearchFixture['reportActions'] = {}; + const privateIsArchivedMap: Record = {}; + const draftComments: SearchFixture['draftComments'] = {}; + const dmReportIDByAccountID: Record = {}; + + // Real reports always carry a reportName (shouldReportBeInOptionList drops those that don't), so it defaults to the derived name. + const addReport = (reportIDNumber: number, report: Omit, reportName: string, day: number) => { + const reportID = String(reportIDNumber); + const reportKey = `${ONYXKEYS.COLLECTION.REPORT}${reportID}` as const; + if (reports[reportKey]) { + throw new Error(`Two fixture reports share the reportID ${reportID}`); + } + reports[reportKey] = { + reportID, + reportName, + lastVisibleActionCreated: timestamp(day, reportIDNumber - REPORT_ID_TIMESTAMP_ORIGIN), + ...report, + }; + return reportID; + }; + + // Assigns a 1:1 DM to roughly two-thirds of contacts, picked by accountID so adding a contact never moves another contact's DM. + // No contact receives multiple DMs to avoid collection order dependence. + for (const accountID of contactAccountIDs) { + if (accountID === CURRENT_USER_ACCOUNT_ID || accountID % 3 === 2) { + continue; + } + const contact = personalDetails[accountID]; + // A DM with a nameless contact is named after the contact's login, formatted when it is a phone number + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- an empty displayName must fall back too + const name = contact?.displayName || FORMATTED_PHONE_LOGINS[contact?.login ?? ''] || (contact?.login ?? ''); + dmReportIDByAccountID[accountID] = addReport( + DM_REPORT_ID_OFFSET + accountID, + { + type: CONST.REPORT.TYPE.CHAT, + participants: participantsOf([CURRENT_USER_ACCOUNT_ID, accountID]), + ...DM_OVERRIDES[accountID], + }, + name, + accountID % 21, + ); + } + + // The only DM with this contact, so the contact row has exactly one DM to point at. + const archivedDMReportID = addReport( + 2000, + { + type: CONST.REPORT.TYPE.CHAT, + participants: participantsOf([CURRENT_USER_ACCOUNT_ID, 11]), + }, + "Mary-Jane O'Brien (archived) ", + 25, + ); + privateIsArchivedMap[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${archivedDMReportID}`] = true; + + addReport( + 2001, + { + type: CONST.REPORT.TYPE.CHAT, + participants: participantsOf([CURRENT_USER_ACCOUNT_ID, 14], [CURRENT_USER_ACCOUNT_ID]), + }, + 'Alex Smith', + 26, + ); + + addReport( + 2002, + { + type: CONST.REPORT.TYPE.CHAT, + participants: participantsOf([CURRENT_USER_ACCOUNT_ID, UNKNOWN_ACCOUNT_ID]), + }, + '', + 6, + ); + + addReport( + 2003, + { + type: CONST.REPORT.TYPE.CHAT, + chatType: CONST.REPORT.CHAT_TYPE.GROUP, + reportName: 'Weekend Hiking', + participants: participantsOf([CURRENT_USER_ACCOUNT_ID, 2, 3, 11]), + }, + 'Weekend Hiking', + 5, + ); + + addReport( + 2004, + { + type: CONST.REPORT.TYPE.CHAT, + chatType: CONST.REPORT.CHAT_TYPE.SELF_DM, + ownerAccountID: CURRENT_USER_ACCOUNT_ID, + participants: participantsOf([CURRENT_USER_ACCOUNT_ID]), + }, + 'Current User (you)', + 1, + ); + + // GBR: the current user has something to do in their own workspace chat + addReport( + 2005, + { + type: CONST.REPORT.TYPE.CHAT, + chatType: CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT, + policyID: OWN_POLICY_ID, + policyName: 'V\u00edt Workspace', + isOwnPolicyExpenseChat: true, + ownerAccountID: CURRENT_USER_ACCOUNT_ID, + participants: participantsOf([CURRENT_USER_ACCOUNT_ID]), + hasOutstandingChildRequest: true, + }, + "Current User's expenses", + 7, + ); + + addReport( + 2006, + { + type: CONST.REPORT.TYPE.CHAT, + chatType: CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT, + policyID: OWN_POLICY_ID, + policyName: 'V\u00edt Workspace', + isOwnPolicyExpenseChat: false, + ownerAccountID: 5, + participants: participantsOf([CURRENT_USER_ACCOUNT_ID, 5]), + }, + "Gro\u00df Stra\u00dfe's expenses", + 7, + ); + + addReport( + 2007, + { + type: CONST.REPORT.TYPE.CHAT, + chatType: CONST.REPORT.CHAT_TYPE.INVOICE, + policyID: OWN_POLICY_ID, + policyName: 'V\u00edt Workspace', + invoiceReceiver: { + type: CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL, + accountID: 5, + }, + participants: participantsOf([CURRENT_USER_ACCOUNT_ID, 5]), + }, + 'Gro\u00df Stra\u00dfe', + 12, + ); + + const rooms = [ + [2010, CONST.REPORT.CHAT_TYPE.POLICY_ADMINS, '#admins', OWN_POLICY_ID, 3, {}], + [2011, CONST.REPORT.CHAT_TYPE.POLICY_ANNOUNCE, '#announce', OWN_POLICY_ID, 8, {}], + // RBR: the room has an error + [2012, CONST.REPORT.CHAT_TYPE.POLICY_ROOM, '#v\u00edt-room', OTHER_POLICY_ID, 14, {errorFields: {joinRoom: {joinRoom: 'Could not join the room'}}}], + [2013, CONST.REPORT.CHAT_TYPE.POLICY_ROOM, '#orphan-room', MISSING_POLICY_ID, 19, {}], + ] as const satisfies ReadonlyArray]>; + for (const [reportID, chatType, reportName, policyID, day, reportFields] of rooms) { + addReport( + reportID, + { + type: CONST.REPORT.TYPE.CHAT, + chatType, + reportName, + policyID, + policyName: policyID === MISSING_POLICY_ID ? 'Gone Workspace' : undefined, + participants: participantsOf([CURRENT_USER_ACCOUNT_ID, 2, 4]), + ...reportFields, + }, + reportName, + day, + ); + } + + // Parent chat containing comments from which subsequent threads originate + const threadParentReportID = addReport( + 2020, + { + type: CONST.REPORT.TYPE.CHAT, + participants: participantsOf([CURRENT_USER_ACCOUNT_ID, 2]), + }, + 'Jos\u00e9 \u00d1\u00fa\u00f1ez', + 3, + ); + const threadParentActions: ReportActions = {}; + reportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${threadParentReportID}`] = threadParentActions; + + // A thread is named after its parent comment, so every thread gets one in the parent DM. + const addThread = (reportIDNumber: number, reportName: string, report: Partial = {}) => { + const parentReportActionID = String(reportIDNumber); + const reportID = addReport( + reportIDNumber, + { + type: CONST.REPORT.TYPE.CHAT, + parentReportID: threadParentReportID, + parentReportActionID, + participants: participantsOf([CURRENT_USER_ACCOUNT_ID, 2]), + ...report, + }, + reportName, + 4, + ); + threadParentActions[parentReportActionID] = { + reportActionID: parentReportActionID, + actionName: CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT, + created: timestamp(3, reportIDNumber - REPORT_ID_TIMESTAMP_ORIGIN), + actorAccountID: 2, + childReportID: reportID, + message: [ + { + type: CONST.REPORT.MESSAGE.TYPE.COMMENT, + html: reportName, + text: reportName, + }, + ], + }; + return reportID; + }; + + const addReply = (threadReportID: string, text: string) => { + reportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${threadReportID}`] = { + '1': { + reportActionID: '1', + actionName: CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT, + created: reports[`${ONYXKEYS.COLLECTION.REPORT}${threadReportID}`]?.lastVisibleActionCreated ?? '', + actorAccountID: 2, + message: [{type: CONST.REPORT.MESSAGE.TYPE.COMMENT, html: text, text}], + }, + }; + }; + + // Thread with replies to verify that non-empty threads are kept in options + addReply(addThread(2021, 'Thread about the offsite'), 'Count me in'); + // Hidden for the current user: every call site drops it except the task share destination, which includes threads and never sets excludeHidden + addThread(2022, 'Hidden thread', { + participants: participantsOf([CURRENT_USER_ACCOUNT_ID, 2], [CURRENT_USER_ACCOUNT_ID]), + }); + // Empty threads are dropped by shouldReportBeInOptionList unless a draft or a pin keeps them + addThread(2023, 'Empty thread'); + const draftedThreadReportID = addThread(2024, 'Empty thread with a draft'); + draftComments[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${draftedThreadReportID}`] = 'unsent draft'; + addThread(2025, 'Empty pinned thread', {isPinned: true}); + // The only thread whose name lacks "thread", so a query reaches it through its "Thread" alternate text alone + addReply(addThread(2026, 'Packing list'), 'Tent and stove'); + + addReport( + 2030, + { + type: CONST.REPORT.TYPE.TASK, + reportName: 'Book flights', + parentReportID: threadParentReportID, + ownerAccountID: CURRENT_USER_ACCOUNT_ID, + participants: participantsOf([CURRENT_USER_ACCOUNT_ID, 2]), + }, + 'Book flights', + 9, + ); + + addReport( + 2031, + { + type: CONST.REPORT.TYPE.EXPENSE, + reportName: 'Offsite expenses', + policyID: OWN_POLICY_ID, + ownerAccountID: CURRENT_USER_ACCOUNT_ID, + participants: participantsOf([CURRENT_USER_ACCOUNT_ID]), + }, + 'Offsite expenses', + 11, + ); + + // Dropped by shouldReportBeInOptionList: an empty submitted expense report whose only action is CREATED. + const emptySubmittedReportID = addReport( + 2032, + { + type: CONST.REPORT.TYPE.EXPENSE, + reportName: 'Empty submitted report', + policyID: OWN_POLICY_ID, + ownerAccountID: CURRENT_USER_ACCOUNT_ID, + participants: participantsOf([CURRENT_USER_ACCOUNT_ID]), + total: 0, + stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, + statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED, + }, + 'Empty submitted report', + 13, + ); + reportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${emptySubmittedReportID}`] = { + '1': { + reportActionID: '1', + actionName: CONST.REPORT.ACTIONS.TYPE.CREATED, + created: timestamp(13, 0), + }, + }; + + // Dropped by processReport: neither a room nor any participants. + addReport(2033, {type: CONST.REPORT.TYPE.CHAT, participants: {}}, 'Empty', 2); + + // Dropped by shouldReportBeInOptionList: still loading, so the report itself has no reportName yet. + addReport( + 2034, + { + type: CONST.REPORT.TYPE.CHAT, + chatType: CONST.REPORT.CHAT_TYPE.POLICY_ROOM, + reportName: undefined, + policyID: OWN_POLICY_ID, + participants: participantsOf([CURRENT_USER_ACCOUNT_ID, 2]), + }, + '#loading-room', + 27, + ); + + const conciergeReportID = dmReportIDByAccountID[CONCIERGE_ACCOUNT_ID]; + if (!conciergeReportID) { + throw new Error('The fixture must give the Concierge contact a DM'); + } + + return { + personalDetails, + reports, + reportActions, + policies, + privateIsArchivedMap, + derived: { + reportAttributes: {}, + visibleReportActions: {}, + sortedReportActions: { + sortedActions: {}, + lastActions: {}, + transactionThreadIDs: {}, + }, + }, + draftComments, + conciergeReportID, + loginList: { + [CURRENT_USER_EMAIL]: { + created: '2024-01-01 00:00:00.000', + accountID: CURRENT_USER_ACCOUNT_ID, + partnerID: 1, + partnerUserID: CURRENT_USER_EMAIL, + lastLogin: '2024-01-01 00:00:00.000', + validatedDate: '2024-01-01 00:00:00.000', + }, + }, + }; +} + +/** Shared by every archived report, so an unchanged archive keeps its reference between writes. */ +const ARCHIVED_REPORT_NAME_VALUE_PAIRS = { + private_isArchived: '2024-01-27 10:00:00.000', +}; + +/** The account's Onyx keys and values, for every key the search reads back. */ +function getOnyxEntries(fixture: SearchFixture): Record { + return { + [ONYXKEYS.SESSION]: SESSION, + [ONYXKEYS.PERSONAL_DETAILS_LIST]: fixture.personalDetails, + [ONYXKEYS.LOGINS]: fixture.loginList, + ...fixture.reports, + ...fixture.reportActions, + ...fixture.policies, + ...fixture.draftComments, + // Mirrors privateIsArchivedMap so module-level archived reads agree with the map passed to createFilteredOptionList + ...Object.fromEntries(Object.keys(fixture.privateIsArchivedMap).map((key) => [key, ARCHIVED_REPORT_NAME_VALUE_PAIRS])), + }; +} + +async function writeOnyxEntries(entries: Record): Promise { + // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- the entries come from the fixture's typed collections, which Onyx's per-key input type cannot follow through Object.entries + await Onyx.multiSet(entries as OnyxMultiSetInput); + await waitForBatchedUpdates(); + const [reportAttributes, visibleReportActions, sortedReportActions] = await Promise.all([ + getOnyxValue(ONYXKEYS.DERIVED.REPORT_ATTRIBUTES), + getOnyxValue(ONYXKEYS.DERIVED.VISIBLE_REPORT_ACTIONS), + getOnyxValue(ONYXKEYS.DERIVED.RAM_ONLY_SORTED_REPORT_ACTIONS), + ]); + // Without derived values every report would silently fall back to defaults the app never computed + if (!reportAttributes?.reports || !visibleReportActions || !sortedReportActions) { + throw new Error('Onyx did not derive the report values; call initOnyxDerivedValues before writing the fixture'); + } + return { + reportAttributes: reportAttributes.reports, + visibleReportActions, + sortedReportActions, + }; +} + +/** Writes the whole account to Onyx and returns what Onyx derived from it. */ +function writeSearchFixtureToOnyx(fixture: SearchFixture): Promise { + return writeOnyxEntries(getOnyxEntries(fixture)); +} + +export {generateSearchFixture, writeSearchFixtureToOnyx, CURRENT_USER_ACCOUNT_ID, CURRENT_USER_EMAIL, OWN_POLICY_ID, SEARCH_QUERIES}; +export type {SearchFixture}; From 2289793a3208ba0dc4e3043610bfd7fac7b2afd9 Mon Sep 17 00:00:00 2001 From: Jakub Magiera Date: Wed, 21 Oct 2026 10:02:37 +0200 Subject: [PATCH 02/13] Clean up and deduplicate comments in search parity tests --- tests/unit/OptionsListUtilsSearchParityTest.ts | 9 +++------ tests/utils/searchParity/generateSearchFixture.ts | 1 - 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/tests/unit/OptionsListUtilsSearchParityTest.ts b/tests/unit/OptionsListUtilsSearchParityTest.ts index b1892fb1898c..2cb52ce5384f 100644 --- a/tests/unit/OptionsListUtilsSearchParityTest.ts +++ b/tests/unit/OptionsListUtilsSearchParityTest.ts @@ -40,9 +40,8 @@ jest.mock('@src/libs/Navigation/Navigation', () => ({ getReportRHPActiveRoute: jest.fn(), })); -// Baseline for the search option pipeline the SearchRouter runs through (createFilteredOptionList to getSearchOptions). -// Only re-record the snapshot (jest -u) against the pre-migration search on main, so the migration never becomes its own baseline. -// The snapshot lists every option as " [flags] [alt: ...] [accID: ...]", so a changed result shows its options in the diff. +// Baseline snapshot for the search option pipeline (createFilteredOptionList to getSearchOptions). +// Only re-record (jest -u) against pre-migration search on main so the migration never becomes its own baseline. const fixture = generateSearchFixture(); /** Drops every cache the search keeps between calls, so the next build starts cold. */ @@ -56,7 +55,6 @@ function clearSearchCaches() { * Invited users include their login when different from text to distinguish email from phone invites. */ function createOptionSerializer() { - /** Serializes one option into a " [flags] [alt: ...] [accID: ...]" line to pin option ordering and content rather than rendering details. */ function serializeOption(option: SearchOptionData | null | undefined, {isUserToInvite = false}: {isUserToInvite?: boolean} = {}): string | null { if (!option) { return null; @@ -102,7 +100,6 @@ function createOptionSerializer() { return {serializeOption, serializeResult, serializeOptionList}; } -/** Matches the readable serialization of a recorded value against the snapshot. */ function expectToMatchGoldenMaster(value: unknown) { expect(value).toMatchSnapshot(); } @@ -244,7 +241,7 @@ function createCallSites(sourceFixture: SearchFixture) { const searchCallSites = { searchRouterLocal: (options, query) => runSearchRouterSearch(options, query), - // SearchAutocompleteList displayed pass: combines rankings, appends userToInvite and caps results + // Replicates the displayed ranking and capping pass from SearchAutocompleteList searchRouterDisplayed: (options, query) => { const result = runSearchRouterSearch(options, query); diff --git a/tests/utils/searchParity/generateSearchFixture.ts b/tests/utils/searchParity/generateSearchFixture.ts index 9caf2a72fa5a..88c461ab11d3 100644 --- a/tests/utils/searchParity/generateSearchFixture.ts +++ b/tests/utils/searchParity/generateSearchFixture.ts @@ -322,7 +322,6 @@ function generateSearchFixture(): SearchFixture { continue; } const contact = personalDetails[accountID]; - // A DM with a nameless contact is named after the contact's login, formatted when it is a phone number // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- an empty displayName must fall back too const name = contact?.displayName || FORMATTED_PHONE_LOGINS[contact?.login ?? ''] || (contact?.login ?? ''); dmReportIDByAccountID[accountID] = addReport( From c87406c69447fb39fe51089836044089a471baf5 Mon Sep 17 00:00:00 2001 From: Jakub Magiera Date: Thu, 22 Oct 2026 21:25:15 +0200 Subject: [PATCH 03/13] Address review feedback: query normalization, typing, and snapshot cleanup --- .../unit/OptionsListUtilsSearchParityTest.ts | 27 +- .../OptionsListUtilsSearchParityTest.ts.snap | 537 +++++++----------- .../searchParity/generateSearchFixture.ts | 15 +- 3 files changed, 232 insertions(+), 347 deletions(-) diff --git a/tests/unit/OptionsListUtilsSearchParityTest.ts b/tests/unit/OptionsListUtilsSearchParityTest.ts index 2cb52ce5384f..c1698859fa7c 100644 --- a/tests/unit/OptionsListUtilsSearchParityTest.ts +++ b/tests/unit/OptionsListUtilsSearchParityTest.ts @@ -331,7 +331,6 @@ function computeGoldenMaster(sourceFixture: SearchFixture) { clearSearchCaches(); const searching = buildOptionList(sourceFixture, {isSearching: true}); - const idle = buildOptionList(sourceFixture, {isSearching: false}); const buildSearchRouterIdleList = (maxRecentReports: number) => buildSearchRouterIdleOptionList(sourceFixture, maxRecentReports); const searchRouterIdle = buildSearchRouterIdleList(SEARCH_ROUTER_IDLE_REPORTS_CAP); const searchRouterIdleAtProductionCap = buildSearchRouterIdleList(SEARCH_ROUTER_OPTIONS_CONFIG.maxRecentReports); @@ -339,7 +338,6 @@ function computeGoldenMaster(sourceFixture: SearchFixture) { const optionLists = { // The searching list keeps the collection's iteration order, which the search ranks later, so only its content is recorded searching: serializeOptionList(searching, {isOrderMeaningful: false}), - idle: serializeOptionList(idle, {isOrderMeaningful: true}), searchRouterIdle: serializeOptionList(searchRouterIdle, { isOrderMeaningful: true, }), @@ -384,25 +382,8 @@ function computeGoldenMaster(sourceFixture: SearchFixture) { const serverPassOptionsByReportID = new Map(serverPassOptions.recentReports.map((option) => [option.reportID, option])); const serverPass = orderedServerReportIDs.map((reportID) => serverPassOptionsByReportID.get(reportID)).filter((option) => !!option && !option.isSelfDM); - // When the idle cap hides reports and shows fewer rows than available slots, rebuild with all reports via loadAll - const totalReports = Object.keys(sourceFixture.reports).length; - const searchRouterBlankAtSmallCap = searchCallSites.searchRouterDisplayed(searchRouterIdle, ''); - const shouldLoadAllReports = - searchRouterBlankAtSmallCap.options.recentReports.length < CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMOUNT_OF_SUGGESTIONS && SEARCH_ROUTER_IDLE_REPORTS_CAP < totalReports; - const searchRouterIdleAfterLoadAll = shouldLoadAllReports ? buildSearchRouterIdleList(totalReports) : searchRouterIdle; - const runSearchRouterBlank = (optionList: OptionList) => ({ - local: serializeResult(searchCallSites.searchRouterLocal(optionList, '')), - displayed: serializeResult(searchCallSites.searchRouterDisplayed(optionList, '')), - }); - const passes = { - searchRouterBlank: { - atProductionCap: runSearchRouterBlank(searchRouterIdleAtProductionCap), - afterLoadAll: { - didLoadAll: shouldLoadAllReports, - ...runSearchRouterBlank(searchRouterIdleAfterLoadAll), - }, - }, + searchRouterBlankAtProductionCap: serializeResult(searchCallSites.searchRouterDisplayed(searchRouterIdleAtProductionCap, '')), serverPass: serverPass.map((option) => serializeOption(option)), }; @@ -426,9 +407,9 @@ describe('OptionsListUtils search parity (golden master)', () => { clearSearchCaches(); }); - it('builds searching, idle and SearchRouter idle option lists that match the golden master', () => { + it('builds searching and SearchRouter idle option lists that match the golden master', () => { // Given the fixture account with every report type and contact quirk - // When the option list is built for search (every report), for the idle state (top-N, sorted) and for the SearchRouter idle state (small top-N, contacts deferred) + // When the option list is built for search (every report) and for the SearchRouter idle state (top-N, contacts deferred) at the small cap and the production cap // Then every option keeps its identity, flags and, where it matters, its order expectToMatchGoldenMaster(goldenMaster.optionLists); }); @@ -441,7 +422,7 @@ describe('OptionsListUtils search parity (golden master)', () => { }); it('SearchRouter blank query and server-ID pass match the golden master', () => { - // Given the SearchRouter idle list at the production cap and after loading all reports, and the SearchAutocompleteList server pass (a fixed set of reports in Auth's order, no query) + // Given the SearchRouter idle list at the production cap and the SearchAutocompleteList server pass (a fixed set of reports in Auth's order, no query) // When they run through their production configs // Then both match the recorded golden master expectToMatchGoldenMaster(goldenMaster.passes); diff --git a/tests/unit/__snapshots__/OptionsListUtilsSearchParityTest.ts.snap b/tests/unit/__snapshots__/OptionsListUtilsSearchParityTest.ts.snap index 3c941638e253..fdea06894abb 100644 --- a/tests/unit/__snapshots__/OptionsListUtilsSearchParityTest.ts.snap +++ b/tests/unit/__snapshots__/OptionsListUtilsSearchParityTest.ts.snap @@ -2,132 +2,35 @@ exports[`OptionsListUtils search parity (golden master) SearchRouter blank query and server-ID pass match the golden master 1`] = ` { - "searchRouterBlank": { - "afterLoadAll": { - "didLoadAll": true, - "displayed": { - "currentUserOption": null, - "hasMore": true, - "personalDetails": [], - "recentReports": [ - "2004 Current User (you) [alt: This is your personal space. Use it for notes, tasks, drafts, and reminders.] [accID: 1]", - "2013 #orphan-room [alt: Gone Workspace • This chat room is for anything #orphan-room related.]", - "1124 Ada Cole [alt: This chat is with Ada Cole.] [accID: 124]", - "1103 Dan Ames [alt: This chat is with Dan Ames.] [accID: 103]", - "1019 No Login [alt: This chat is with No Login.] [accID: 19]", - "1123 Lou Byrd [alt: This chat is with Lou Byrd.] [accID: 123]", - "1102 Cyd Ames [alt: This chat is with Cyd Ames.] [accID: 102]", - "1018 Domain Admin [alt: This chat is with Domain Admin.] [accID: 18]", - "1121 Jon Byrd [alt: This chat is with Jon Byrd.] [accID: 121]", - "1100 Ada Ames [alt: This chat is with Ada Ames.] [accID: 100]", - "1120 Ivy Byrd [alt: This chat is with Ivy Byrd.] [accID: 120]", - "1015 Alex Smith Jr. [alt: This chat is with Alex Smith Jr..] [accID: 15]", - "2012 #vít-room [rbr] [alt: Acme Holdings • This chat room is for anything #vít-room related.]", - "1139 Dan Dunn [alt: This chat is with Dan Dunn.] [accID: 139]", - "1118 Gus Byrd [alt: This chat is with Gus Byrd.] [accID: 118]", - "1013 (500) 555-0006 [alt: This chat is with (500) 555-0006.] [accID: 13]", - "2007 Groß Straße [alt: Vít Workspace • This chat is for invoices between Groß Straße and Vít Workspace. Use the + button to send an invoice.]", - "1138 Cyd Dunn [alt: This chat is with Cyd Dunn.] [accID: 138]", - "1117 Fay Byrd [alt: This chat is with Fay Byrd.] [accID: 117]", - "1012 first.last+tag@x.com [alt: This chat is with first.last+tag@x.com.] [accID: 12]", - ], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], - }, - "local": { - "currentUserOption": null, - "hasMore": true, - "personalDetails": [], - "recentReports": [ - "2004 Current User (you) [alt: This is your personal space. Use it for notes, tasks, drafts, and reminders.] [accID: 1]", - "2013 #orphan-room [alt: Gone Workspace • This chat room is for anything #orphan-room related.]", - "1124 Ada Cole [alt: This chat is with Ada Cole.] [accID: 124]", - "1103 Dan Ames [alt: This chat is with Dan Ames.] [accID: 103]", - "1019 No Login [alt: This chat is with No Login.] [accID: 19]", - "1123 Lou Byrd [alt: This chat is with Lou Byrd.] [accID: 123]", - "1102 Cyd Ames [alt: This chat is with Cyd Ames.] [accID: 102]", - "1018 Domain Admin [alt: This chat is with Domain Admin.] [accID: 18]", - "1121 Jon Byrd [alt: This chat is with Jon Byrd.] [accID: 121]", - "1100 Ada Ames [alt: This chat is with Ada Ames.] [accID: 100]", - "1120 Ivy Byrd [alt: This chat is with Ivy Byrd.] [accID: 120]", - "1015 Alex Smith Jr. [alt: This chat is with Alex Smith Jr..] [accID: 15]", - "2012 #vít-room [rbr] [alt: Acme Holdings • This chat room is for anything #vít-room related.]", - "1139 Dan Dunn [alt: This chat is with Dan Dunn.] [accID: 139]", - "1118 Gus Byrd [alt: This chat is with Gus Byrd.] [accID: 118]", - "1013 (500) 555-0006 [alt: This chat is with (500) 555-0006.] [accID: 13]", - "2007 Groß Straße [alt: Vít Workspace • This chat is for invoices between Groß Straße and Vít Workspace. Use the + button to send an invoice.]", - "1138 Cyd Dunn [alt: This chat is with Cyd Dunn.] [accID: 138]", - "1117 Fay Byrd [alt: This chat is with Fay Byrd.] [accID: 117]", - "1012 first.last+tag@x.com [alt: This chat is with first.last+tag@x.com.] [accID: 12]", - ], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], - }, - }, - "atProductionCap": { - "displayed": { - "currentUserOption": null, - "hasMore": true, - "personalDetails": [], - "recentReports": [ - "2004 Current User (you) [alt: This is your personal space. Use it for notes, tasks, drafts, and reminders.] [accID: 1]", - "2013 #orphan-room [alt: Gone Workspace • This chat room is for anything #orphan-room related.]", - "1124 Ada Cole [alt: This chat is with Ada Cole.] [accID: 124]", - "1103 Dan Ames [alt: This chat is with Dan Ames.] [accID: 103]", - "1019 No Login [alt: This chat is with No Login.] [accID: 19]", - "1123 Lou Byrd [alt: This chat is with Lou Byrd.] [accID: 123]", - "1102 Cyd Ames [alt: This chat is with Cyd Ames.] [accID: 102]", - "1018 Domain Admin [alt: This chat is with Domain Admin.] [accID: 18]", - "1121 Jon Byrd [alt: This chat is with Jon Byrd.] [accID: 121]", - "1100 Ada Ames [alt: This chat is with Ada Ames.] [accID: 100]", - "1120 Ivy Byrd [alt: This chat is with Ivy Byrd.] [accID: 120]", - "1015 Alex Smith Jr. [alt: This chat is with Alex Smith Jr..] [accID: 15]", - "2012 #vít-room [rbr] [alt: Acme Holdings • This chat room is for anything #vít-room related.]", - "1139 Dan Dunn [alt: This chat is with Dan Dunn.] [accID: 139]", - "1118 Gus Byrd [alt: This chat is with Gus Byrd.] [accID: 118]", - "1013 (500) 555-0006 [alt: This chat is with (500) 555-0006.] [accID: 13]", - "2007 Groß Straße [alt: Vít Workspace • This chat is for invoices between Groß Straße and Vít Workspace. Use the + button to send an invoice.]", - "1138 Cyd Dunn [alt: This chat is with Cyd Dunn.] [accID: 138]", - "1117 Fay Byrd [alt: This chat is with Fay Byrd.] [accID: 117]", - "1012 first.last+tag@x.com [alt: This chat is with first.last+tag@x.com.] [accID: 12]", - ], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], - }, - "local": { - "currentUserOption": null, - "hasMore": true, - "personalDetails": [], - "recentReports": [ - "2004 Current User (you) [alt: This is your personal space. Use it for notes, tasks, drafts, and reminders.] [accID: 1]", - "2013 #orphan-room [alt: Gone Workspace • This chat room is for anything #orphan-room related.]", - "1124 Ada Cole [alt: This chat is with Ada Cole.] [accID: 124]", - "1103 Dan Ames [alt: This chat is with Dan Ames.] [accID: 103]", - "1019 No Login [alt: This chat is with No Login.] [accID: 19]", - "1123 Lou Byrd [alt: This chat is with Lou Byrd.] [accID: 123]", - "1102 Cyd Ames [alt: This chat is with Cyd Ames.] [accID: 102]", - "1018 Domain Admin [alt: This chat is with Domain Admin.] [accID: 18]", - "1121 Jon Byrd [alt: This chat is with Jon Byrd.] [accID: 121]", - "1100 Ada Ames [alt: This chat is with Ada Ames.] [accID: 100]", - "1120 Ivy Byrd [alt: This chat is with Ivy Byrd.] [accID: 120]", - "1015 Alex Smith Jr. [alt: This chat is with Alex Smith Jr..] [accID: 15]", - "2012 #vít-room [rbr] [alt: Acme Holdings • This chat room is for anything #vít-room related.]", - "1139 Dan Dunn [alt: This chat is with Dan Dunn.] [accID: 139]", - "1118 Gus Byrd [alt: This chat is with Gus Byrd.] [accID: 118]", - "1013 (500) 555-0006 [alt: This chat is with (500) 555-0006.] [accID: 13]", - "2007 Groß Straße [alt: Vít Workspace • This chat is for invoices between Groß Straße and Vít Workspace. Use the + button to send an invoice.]", - "1138 Cyd Dunn [alt: This chat is with Cyd Dunn.] [accID: 138]", - "1117 Fay Byrd [alt: This chat is with Fay Byrd.] [accID: 117]", - "1012 first.last+tag@x.com [alt: This chat is with first.last+tag@x.com.] [accID: 12]", - ], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], - }, - }, + "searchRouterBlankAtProductionCap": { + "currentUserOption": null, + "hasMore": true, + "personalDetails": [], + "recentReports": [ + "2004 Current User (you) [alt: This is your personal space. Use it for notes, tasks, drafts, and reminders.] [accID: 1]", + "2013 #orphan-room [alt: Gone Workspace • This chat room is for anything #orphan-room related.]", + "1124 Ada Cole [alt: This chat is with Ada Cole.] [accID: 124]", + "1103 Dan Ames [alt: This chat is with Dan Ames.] [accID: 103]", + "1019 No Login [alt: This chat is with No Login.] [accID: 19]", + "1123 Lou Byrd [alt: This chat is with Lou Byrd.] [accID: 123]", + "1102 Cyd Ames [alt: This chat is with Cyd Ames.] [accID: 102]", + "1018 Domain Admin [alt: This chat is with Domain Admin.] [accID: 18]", + "1121 Jon Byrd [alt: This chat is with Jon Byrd.] [accID: 121]", + "1100 Ada Ames [alt: This chat is with Ada Ames.] [accID: 100]", + "1120 Ivy Byrd [alt: This chat is with Ivy Byrd.] [accID: 120]", + "1015 Alex Smith Jr. [alt: This chat is with Alex Smith Jr..] [accID: 15]", + "2012 #vít-room [rbr] [alt: Acme Holdings • This chat room is for anything #vít-room related.]", + "1139 Dan Dunn [alt: This chat is with Dan Dunn.] [accID: 139]", + "1118 Gus Byrd [alt: This chat is with Gus Byrd.] [accID: 118]", + "1013 (500) 555-0006 [alt: This chat is with (500) 555-0006.] [accID: 13]", + "2007 Groß Straße [alt: Vít Workspace • This chat is for invoices between Groß Straße and Vít Workspace. Use the + button to send an invoice.]", + "1138 Cyd Dunn [alt: This chat is with Cyd Dunn.] [accID: 138]", + "1117 Fay Byrd [alt: This chat is with Fay Byrd.] [accID: 117]", + "1012 first.last+tag@x.com [alt: This chat is with first.last+tag@x.com.] [accID: 12]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], }, "serverPass": [ "2010 #admins [alt: Vít Workspace • This chat is with Vít Workspace admin. Use it to chat about workspace setup and more.]", @@ -143,201 +46,8 @@ exports[`OptionsListUtils search parity (golden master) SearchRouter blank query } `; -exports[`OptionsListUtils search parity (golden master) builds searching, idle and SearchRouter idle option lists that match the golden master 1`] = ` +exports[`OptionsListUtils search parity (golden master) builds searching and SearchRouter idle option lists that match the golden master 1`] = ` { - "idle": { - "hydratedPersonalDetails": [ - "1 Current User [alt: current.user@example.com] [accID: 1]", - "1003 Vít Novák [alt: vit.novak@example.com] [accID: 3]", - "1004 İsmail Yılmaz [alt: ismail@example.com] [accID: 4]", - "1006 Łukasz Wójcik [alt: lukasz@example.com] [accID: 6]", - "1007 Οδυσσεύς [alt: odysseus@example.com] [accID: 7]", - "1009 Ana​Maria [alt: ana.maria@example.com] [accID: 9]", - "101 Bea Ames [alt: bea.ames.1@example.com] [accID: 101]", - "1010 Bob Builder [alt: bob.builder@example.com] [accID: 10]", - "1012 first.last+tag@x.com [alt: first.last+tag@x.com] [accID: 12]", - "1013 (500) 555-0006 [alt: +15005550006@expensify.sms] [accID: 13]", - "1015 Alex Smith Jr. [alt: alex.smith2@example.com] [accID: 15]", - "1016 Expensify Notifications [alt: notifications@expensify.com] [accID: 16]", - "1018 Domain Admin [alt: admin@example.com] [accID: 18]", - "1019 No Login [accID: 19]", - "1021 Concierge [alt: concierge@expensify.com] [accID: 21]", - "104 Eve Ames [alt: eve.ames.4@example.com] [accID: 104]", - "107 Hal Ames [alt: hal.ames.7@example.com] [accID: 107]", - "110 Kim Ames [alt: kim.ames.10@example.com] [accID: 110]", - "1100 Ada Ames [alt: ada.ames.0@example.com] [accID: 100]", - "1102 Cyd Ames [alt: cyd.ames.2@example.com] [accID: 102]", - "1103 Dan Ames [alt: dan.ames.3@example.com] [accID: 103]", - "1105 Fay Ames [alt: fay.ames.5@example.com] [accID: 105]", - "1106 Gus Ames [alt: gus.ames.6@example.com] [accID: 106]", - "1108 Ivy Ames [alt: ivy.ames.8@example.com] [accID: 108]", - "1109 Jon Ames [alt: jon.ames.9@example.com] [accID: 109]", - "1111 Lou Ames [alt: lou.ames.11@example.com] [accID: 111]", - "1112 Ada Byrd [alt: ada.byrd.12@example.com] [accID: 112]", - "1114 Cyd Byrd [alt: cyd.byrd.14@example.com] [accID: 114]", - "1115 Dan Byrd [alt: dan.byrd.15@example.com] [accID: 115]", - "1117 Fay Byrd [alt: fay.byrd.17@example.com] [accID: 117]", - "1118 Gus Byrd [alt: gus.byrd.18@example.com] [accID: 118]", - "1120 Ivy Byrd [alt: ivy.byrd.20@example.com] [accID: 120]", - "1121 Jon Byrd [alt: jon.byrd.21@example.com] [accID: 121]", - "1123 Lou Byrd [alt: lou.byrd.23@example.com] [accID: 123]", - "1124 Ada Cole [alt: ada.cole.24@example.com] [accID: 124]", - "1126 Cyd Cole [alt: cyd.cole.26@example.com] [accID: 126]", - "1127 Dan Cole [alt: dan.cole.27@example.com] [accID: 127]", - "1129 Fay Cole [alt: fay.cole.29@example.com] [accID: 129]", - "113 Bea Byrd [alt: bea.byrd.13@example.com] [accID: 113]", - "1130 Gus Cole [alt: gus.cole.30@example.com] [accID: 130]", - "1132 Ivy Cole [alt: ivy.cole.32@example.com] [accID: 132]", - "1133 Jon Cole [alt: jon.cole.33@example.com] [accID: 133]", - "1135 Lou Cole [alt: lou.cole.35@example.com] [accID: 135]", - "1136 Ada Dunn [alt: ada.dunn.36@example.com] [accID: 136]", - "1138 Cyd Dunn [alt: cyd.dunn.38@example.com] [accID: 138]", - "1139 Dan Dunn [alt: dan.dunn.39@example.com] [accID: 139]", - "116 Eve Byrd [alt: eve.byrd.16@example.com] [accID: 116]", - "119 Hal Byrd [alt: hal.byrd.19@example.com] [accID: 119]", - "122 Kim Byrd [alt: kim.byrd.22@example.com] [accID: 122]", - "125 Bea Cole [alt: bea.cole.25@example.com] [accID: 125]", - "128 Eve Cole [alt: eve.cole.28@example.com] [accID: 128]", - "131 Hal Cole [alt: hal.cole.31@example.com] [accID: 131]", - "134 Kim Cole [alt: kim.cole.34@example.com] [accID: 134]", - "137 Bea Dunn [alt: bea.dunn.37@example.com] [accID: 137]", - "17 Account Executive [alt: Account Executive] [accID: 17]", - "20 René Dupont [alt: rene@example.com] [accID: 20]", - "2000 Mary-Jane O'Brien [alt: obrien@example.com] [accID: 11]", - "2001 Alex Smith [alt: alex.smith@example.com] [accID: 14]", - "2020 José Ñúñez [alt: jose.nunez@example.com] [accID: 2]", - "5 Groß Straße [alt: strasse@example.com] [accID: 5]", - "8 Zoë 👩‍💻 [alt: zoe@example.com] [accID: 8]", - ], - "personalDetails": [ - " | 1 | current.user@example.com", - " | 101 | bea.ames.1@example.com", - " | 104 | eve.ames.4@example.com", - " | 107 | hal.ames.7@example.com", - " | 110 | kim.ames.10@example.com", - " | 113 | bea.byrd.13@example.com", - " | 116 | eve.byrd.16@example.com", - " | 119 | hal.byrd.19@example.com", - " | 122 | kim.byrd.22@example.com", - " | 125 | bea.cole.25@example.com", - " | 128 | eve.cole.28@example.com", - " | 131 | hal.cole.31@example.com", - " | 134 | kim.cole.34@example.com", - " | 137 | bea.dunn.37@example.com", - " | 17 | Account Executive", - " | 20 | rene@example.com", - " | 5 | strasse@example.com", - " | 8 | zoe@example.com", - "1003 | 3 | vit.novak@example.com", - "1004 | 4 | ismail@example.com", - "1006 | 6 | lukasz@example.com", - "1007 | 7 | odysseus@example.com", - "1009 | 9 | ana.maria@example.com", - "1010 | 10 | bob.builder@example.com", - "1012 | 12 | first.last+tag@x.com", - "1013 | 13 | +15005550006@expensify.sms", - "1015 | 15 | alex.smith2@example.com", - "1016 | 16 | notifications@expensify.com", - "1018 | 18 | admin@example.com", - "1019 | 19 | ", - "1021 | 21 | concierge@expensify.com", - "1100 | 100 | ada.ames.0@example.com", - "1102 | 102 | cyd.ames.2@example.com", - "1103 | 103 | dan.ames.3@example.com", - "1105 | 105 | fay.ames.5@example.com", - "1106 | 106 | gus.ames.6@example.com", - "1108 | 108 | ivy.ames.8@example.com", - "1109 | 109 | jon.ames.9@example.com", - "1111 | 111 | lou.ames.11@example.com", - "1112 | 112 | ada.byrd.12@example.com", - "1114 | 114 | cyd.byrd.14@example.com", - "1115 | 115 | dan.byrd.15@example.com", - "1117 | 117 | fay.byrd.17@example.com", - "1118 | 118 | gus.byrd.18@example.com", - "1120 | 120 | ivy.byrd.20@example.com", - "1121 | 121 | jon.byrd.21@example.com", - "1123 | 123 | lou.byrd.23@example.com", - "1124 | 124 | ada.cole.24@example.com", - "1126 | 126 | cyd.cole.26@example.com", - "1127 | 127 | dan.cole.27@example.com", - "1129 | 129 | fay.cole.29@example.com", - "1130 | 130 | gus.cole.30@example.com", - "1132 | 132 | ivy.cole.32@example.com", - "1133 | 133 | jon.cole.33@example.com", - "1135 | 135 | lou.cole.35@example.com", - "1136 | 136 | ada.dunn.36@example.com", - "1138 | 138 | cyd.dunn.38@example.com", - "1139 | 139 | dan.dunn.39@example.com", - "2000 | 11 | obrien@example.com", - "2001 | 14 | alex.smith@example.com", - "2020 | 2 | jose.nunez@example.com", - ], - "reports": [ - "2004 Current User (you) [alt: current.user@example.com] [accID: 1]", - "2034 José Ñúñez", - "2001 Alex Smith [alt: alex.smith@example.com] [accID: 14]", - "2013 #orphan-room", - "1124 Ada Cole [alt: ada.cole.24@example.com] [accID: 124]", - "1103 Dan Ames [alt: dan.ames.3@example.com] [accID: 103]", - "1019 No Login [accID: 19]", - "1123 Lou Byrd [alt: lou.byrd.23@example.com] [accID: 123]", - "1102 Cyd Ames [alt: cyd.ames.2@example.com] [accID: 102]", - "1018 Domain Admin [alt: admin@example.com] [accID: 18]", - "1121 Jon Byrd [alt: jon.byrd.21@example.com] [accID: 121]", - "1100 Ada Ames [alt: ada.ames.0@example.com] [accID: 100]", - "1016 Expensify Notifications [alt: notifications@expensify.com] [accID: 16]", - "1120 Ivy Byrd [alt: ivy.byrd.20@example.com] [accID: 120]", - "1015 Alex Smith Jr. [alt: alex.smith2@example.com] [accID: 15]", - "2012 #vít-room [rbr]", - "2032 Empty submitted report [alt: Expense] [accID: 1]", - "1139 Dan Dunn [alt: dan.dunn.39@example.com] [accID: 139]", - "1118 Gus Byrd [alt: gus.byrd.18@example.com] [accID: 118]", - "1013 (500) 555-0006 [alt: (500) 555-0006] [accID: 13]", - "2007 Groß Straße", - "1138 Cyd Dunn [alt: cyd.dunn.38@example.com] [accID: 138]", - "1117 Fay Byrd [alt: fay.byrd.17@example.com] [accID: 117]", - "1012 first.last+tag@x.com [alt: first.last+tag@x.com] [accID: 12]", - "2031 Offsite expenses [alt: Expense] [accID: 1]", - "1136 Ada Dunn [alt: ada.dunn.36@example.com] [accID: 136]", - "1115 Dan Byrd [alt: dan.byrd.15@example.com] [accID: 115]", - "1010 Bob Builder [alt: bob.builder@example.com] [accID: 10]", - "2030 Book flights [alt: Task]", - "1135 Lou Cole [alt: lou.cole.35@example.com] [accID: 135]", - "1114 Cyd Byrd [alt: cyd.byrd.14@example.com] [accID: 114]", - "1009 Ana​Maria [alt: ana.maria@example.com] [accID: 9]", - "2011 #announce", - "2006 Groß Straße's expenses", - "2005 Current User's expenses [gbr]", - "1133 Jon Cole [alt: jon.cole.33@example.com] [accID: 133]", - "1112 Ada Byrd [alt: ada.byrd.12@example.com] [accID: 112]", - "1007 Οδυσσεύς [alt: odysseus@example.com] [accID: 7]", - "2002 [accID: 999]", - "1132 Ivy Cole [alt: ivy.cole.32@example.com] [accID: 132]", - "1111 Lou Ames [alt: lou.ames.11@example.com] [accID: 111]", - "1006 Łukasz Wójcik [alt: lukasz@example.com] [accID: 6]", - "2003 Weekend Hiking [alt: Group]", - "2026 Packing list [alt: Thread]", - "2025 Empty pinned thread [alt: Thread]", - "2024 Empty thread with a draft [alt: Thread]", - "2023 Empty thread [alt: Thread]", - "2022 Hidden thread [alt: Thread]", - "2021 Thread about the offsite [alt: Thread]", - "1130 Gus Cole [alt: gus.cole.30@example.com] [accID: 130]", - "1109 Jon Ames [alt: jon.ames.9@example.com] [accID: 109]", - "1004 İsmail Yılmaz [alt: ismail@example.com] [accID: 4]", - "2020 José Ñúñez [alt: jose.nunez@example.com] [accID: 2]", - "2010 #admins", - "1129 Fay Cole [alt: fay.cole.29@example.com] [accID: 129]", - "1108 Ivy Ames [alt: ivy.ames.8@example.com] [accID: 108]", - "1003 Vít Novák [alt: vit.novak@example.com] [accID: 3]", - "1127 Dan Cole [alt: dan.cole.27@example.com] [accID: 127]", - "1106 Gus Ames [alt: gus.ames.6@example.com] [accID: 106]", - "1126 Cyd Cole [alt: cyd.cole.26@example.com] [accID: 126]", - "1105 Fay Ames [alt: fay.ames.5@example.com] [accID: 105]", - "1021 Concierge [alt: concierge@expensify.com] [accID: 21]", - "2000 Mary-Jane O'Brien (archived) [alt: obrien@example.com] [accID: 11]", - ], - }, "searchRouterIdle": { "hydratedPersonalDetails": [], "personalDetails": [], @@ -681,6 +391,17 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterDi "userToInvite": "1100537387 +1 650 555 0100 [alt: +1 650 555 0100] [accID: 1100537387]", "workspaceChats": [], }, + "+15005550006": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "1013 (500) 555-0006 [alt: This chat is with (500) 555-0006.] [accID: 13]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, ".*": { "currentUserOption": null, "hasMore": false, @@ -731,6 +452,72 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterDi "userToInvite": null, "workspaceChats": [], }, + "CURRENT.USER@EXAMPLE.COM": { + "currentUserOption": "1 Current User [alt: current.user@example.com] [accID: 1]", + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "2004 Current User (you) [alt: This is your personal space. Use it for notes, tasks, drafts, and reminders.] [accID: 1]", + "2031 Offsite expenses [alt: No activity yet] [accID: 1]", + "2003 Weekend Hiking [alt: This chat is with José Ñúñez, Mary-Jane O'Brien, and Vít Novák.]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "Groß": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "5 Groß Straße [alt: strasse@example.com] [accID: 5]", + "2006 Groß Straße's expenses [alt: Vít Workspace • This is where Groß Straße will submit expenses to Vít Workspace. Just use the + button.]", + "2007 Groß Straße [alt: Vít Workspace • This chat is for invoices between Groß Straße and Vít Workspace. Use the + button to send an invoice.]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "JOSÉ": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "2020 José Ñúñez [bold] [alt: Packing list] [accID: 2]", + "2003 Weekend Hiking [alt: This chat is with José Ñúñez, Mary-Jane O'Brien, and Vít Novák.]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "René": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "20 René Dupont [alt: rene@example.com] [accID: 20]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "Vít": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "1003 Vít Novák [alt: This chat is with Vít Novák.] [accID: 3]", + "2006 Groß Straße's expenses [alt: Vít Workspace • This is where Groß Straße will submit expenses to Vít Workspace. Just use the + button.]", + "2005 Current User's expenses [alt: Vít Workspace • This is where Current User will submit expenses to Vít Workspace. Just use the + button.]", + "2003 Weekend Hiking [alt: This chat is with José Ñúñez, Mary-Jane O'Brien, and Vít Novák.]", + "2012 #vít-room [rbr] [alt: Acme Holdings • This chat room is for anything #vít-room related.]", + "2011 #announce [alt: Vít Workspace • This chat is with everyone in Vít Workspace. Use it for the most important announcements.]", + "2010 #admins [alt: Vít Workspace • This chat is with Vít Workspace admin. Use it to chat about workspace setup and more.]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, "[": { "currentUserOption": null, "hasMore": false, @@ -1281,6 +1068,17 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterDi "userToInvite": null, "workspaceChats": [], }, + "İSMAIL": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "1004 İsmail Yılmaz [alt: Thanks!] [accID: 4]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, "δυσσευς": { "currentUserOption": null, "hasMore": false, @@ -1377,6 +1175,19 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterLo "userToInvite": "1100537387 +1 650 555 0100 [alt: +1 650 555 0100] [accID: 1100537387]", "workspaceChats": [], }, + "+15005550006": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [ + "1013 (500) 555-0006 [alt: +15005550006@expensify.sms] [accID: 13]", + ], + "recentReports": [ + "1013 (500) 555-0006 [alt: This chat is with (500) 555-0006.] [accID: 13]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, ".*": { "currentUserOption": null, "hasMore": false, @@ -1435,6 +1246,79 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterLo "userToInvite": null, "workspaceChats": [], }, + "CURRENT.USER@EXAMPLE.COM": { + "currentUserOption": "1 Current User [alt: current.user@example.com] [accID: 1]", + "hasMore": false, + "personalDetails": [ + "1 Current User [alt: current.user@example.com] [accID: 1]", + ], + "recentReports": [ + "2004 Current User (you) [alt: This is your personal space. Use it for notes, tasks, drafts, and reminders.] [accID: 1]", + "2031 Offsite expenses [alt: No activity yet] [accID: 1]", + "2003 Weekend Hiking [alt: This chat is with José Ñúñez, Mary-Jane O'Brien, and Vít Novák.]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "Groß": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [ + "5 Groß Straße [alt: strasse@example.com] [accID: 5]", + ], + "recentReports": [ + "2007 Groß Straße [alt: Vít Workspace • This chat is for invoices between Groß Straße and Vít Workspace. Use the + button to send an invoice.]", + "2006 Groß Straße's expenses [alt: Vít Workspace • This is where Groß Straße will submit expenses to Vít Workspace. Just use the + button.]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "JOSÉ": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [ + "2020 José Ñúñez [alt: jose.nunez@example.com] [accID: 2]", + ], + "recentReports": [ + "2003 Weekend Hiking [alt: This chat is with José Ñúñez, Mary-Jane O'Brien, and Vít Novák.]", + "2020 José Ñúñez [bold] [alt: Packing list] [accID: 2]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "René": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [ + "20 René Dupont [alt: rene@example.com] [accID: 20]", + ], + "recentReports": [], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "Vít": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [ + "1003 Vít Novák [alt: vit.novak@example.com] [accID: 3]", + ], + "recentReports": [ + "2012 #vít-room [rbr] [alt: Acme Holdings • This chat room is for anything #vít-room related.]", + "2011 #announce [alt: Vít Workspace • This chat is with everyone in Vít Workspace. Use it for the most important announcements.]", + "2006 Groß Straße's expenses [alt: Vít Workspace • This is where Groß Straße will submit expenses to Vít Workspace. Just use the + button.]", + "2005 Current User's expenses [alt: Vít Workspace • This is where Current User will submit expenses to Vít Workspace. Just use the + button.]", + "2003 Weekend Hiking [alt: This chat is with José Ñúñez, Mary-Jane O'Brien, and Vít Novák.]", + "2010 #admins [alt: Vít Workspace • This chat is with Vít Workspace admin. Use it to chat about workspace setup and more.]", + "1003 Vít Novák [alt: This chat is with Vít Novák.] [accID: 3]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, "[": { "currentUserOption": null, "hasMore": false, @@ -2049,6 +1933,19 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterLo "userToInvite": null, "workspaceChats": [], }, + "İSMAIL": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [ + "1004 İsmail Yılmaz [alt: ismail@example.com] [accID: 4]", + ], + "recentReports": [ + "1004 İsmail Yılmaz [alt: Thanks!] [accID: 4]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, "δυσσευς": { "currentUserOption": null, "hasMore": false, diff --git a/tests/utils/searchParity/generateSearchFixture.ts b/tests/utils/searchParity/generateSearchFixture.ts index 88c461ab11d3..f02cab04e44f 100644 --- a/tests/utils/searchParity/generateSearchFixture.ts +++ b/tests/utils/searchParity/generateSearchFixture.ts @@ -205,6 +205,14 @@ const SEARCH_QUERIES = [ '[', '\\', '.*', + // Query-side normalization: case, diacritics (an NFC query against the NFD-stored "René"), ß, a dotted capital I, and an existing contact's phone and the own login, which must not invite + 'V\u00edt', + 'JOS\u00c9', + 'Gro\u00df', + 'Ren\u00e9', + '\u0130SMAIL', + '+15005550006', + 'CURRENT.USER@EXAMPLE.COM', ]; const FIRST_NAMES = ['Ada', 'Bea', 'Cyd', 'Dan', 'Eve', 'Fay', 'Gus', 'Hal', 'Ivy', 'Jon', 'Kim', 'Lou']; @@ -644,7 +652,7 @@ const ARCHIVED_REPORT_NAME_VALUE_PAIRS = { }; /** The account's Onyx keys and values, for every key the search reads back. */ -function getOnyxEntries(fixture: SearchFixture): Record { +function getOnyxEntries(fixture: SearchFixture): OnyxMultiSetInput { return { [ONYXKEYS.SESSION]: SESSION, [ONYXKEYS.PERSONAL_DETAILS_LIST]: fixture.personalDetails, @@ -658,9 +666,8 @@ function getOnyxEntries(fixture: SearchFixture): Record { }; } -async function writeOnyxEntries(entries: Record): Promise { - // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- the entries come from the fixture's typed collections, which Onyx's per-key input type cannot follow through Object.entries - await Onyx.multiSet(entries as OnyxMultiSetInput); +async function writeOnyxEntries(entries: OnyxMultiSetInput): Promise { + await Onyx.multiSet(entries); await waitForBatchedUpdates(); const [reportAttributes, visibleReportActions, sortedReportActions] = await Promise.all([ getOnyxValue(ONYXKEYS.DERIVED.REPORT_ATTRIBUTES), From a83b46e2367a0450013c8261778556ad7ec1b6a2 Mon Sep 17 00:00:00 2001 From: Jakub Magiera Date: Fri, 23 Oct 2026 10:25:56 +0200 Subject: [PATCH 04/13] Record SearchRouter results while a report is focused in search parity test --- .../unit/OptionsListUtilsSearchParityTest.ts | 33 ++++++- .../OptionsListUtilsSearchParityTest.ts.snap | 92 +++++++++++++++++++ 2 files changed, 122 insertions(+), 3 deletions(-) diff --git a/tests/unit/OptionsListUtilsSearchParityTest.ts b/tests/unit/OptionsListUtilsSearchParityTest.ts index c1698859fa7c..8f0854ab8db4 100644 --- a/tests/unit/OptionsListUtilsSearchParityTest.ts +++ b/tests/unit/OptionsListUtilsSearchParityTest.ts @@ -16,6 +16,7 @@ import initOnyxDerivedValues from '@userActions/OnyxDerived'; import CONST from '@src/CONST'; import IntlStore from '@src/languages/IntlStore'; +import Navigation from '@src/libs/Navigation/Navigation'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Report, Rule} from '@src/types/onyx'; @@ -324,6 +325,22 @@ function getReportIDByName(sourceFixture: SearchFixture, reportName: string): st return reportID; } +// Dropped from the list unless focused, because the focused report skips the checks that drop empty threads and empty submitted reports +const FOCUSED_REPORT_NAMES = ['Empty thread', 'Empty submitted report']; + +// Matches both focused reports, so the pass shows whether they come back while a query is typed +const FOCUSED_REPORT_QUERY = 'empty'; + +/** Runs with the report focused, as when Cmd+K opens over it, then goes back to nothing focused so no other pass sees it. */ +function withFocusedReport(reportID: string, run: () => T): T { + jest.mocked(Navigation.getTopmostReportId).mockReturnValue(reportID); + try { + return run(); + } finally { + jest.mocked(Navigation.getTopmostReportId).mockReturnValue(undefined); + } +} + /** Computes every recorded pass over the fixture, starting from cold caches. */ function computeGoldenMaster(sourceFixture: SearchFixture) { const {serializeOption, serializeResult, serializeOptionList} = createOptionSerializer(); @@ -385,6 +402,16 @@ function computeGoldenMaster(sourceFixture: SearchFixture) { const passes = { searchRouterBlankAtProductionCap: serializeResult(searchCallSites.searchRouterDisplayed(searchRouterIdleAtProductionCap, '')), serverPass: serverPass.map((option) => serializeOption(option)), + // Recorded after the passes above, which run with nothing focused + focusedReport: Object.fromEntries( + FOCUSED_REPORT_NAMES.map((reportName) => [ + reportName, + withFocusedReport(getReportIDByName(sourceFixture, reportName), () => ({ + blank: serializeResult(searchCallSites.searchRouterDisplayed(searchRouterIdleAtProductionCap, '')), + [FOCUSED_REPORT_QUERY]: serializeResult(searchCallSites.searchRouterDisplayed(searching, FOCUSED_REPORT_QUERY)), + })), + ]), + ), }; return {optionLists, callSites, passes}; @@ -422,9 +449,9 @@ describe('OptionsListUtils search parity (golden master)', () => { }); it('SearchRouter blank query and server-ID pass match the golden master', () => { - // Given the SearchRouter idle list at the production cap and the SearchAutocompleteList server pass (a fixed set of reports in Auth's order, no query) - // When they run through their production configs - // Then both match the recorded golden master + // Given the SearchRouter idle list at the production cap, the SearchAutocompleteList server pass (a fixed set of reports in Auth's order, no query) and the SearchRouter opened over a focused empty thread or empty submitted report + // When they run through their production configs, the focused ones for a blank query and for a query matching the focused report + // Then all match the recorded golden master, with the focused report shown only while it is focused expectToMatchGoldenMaster(goldenMaster.passes); }); }); diff --git a/tests/unit/__snapshots__/OptionsListUtilsSearchParityTest.ts.snap b/tests/unit/__snapshots__/OptionsListUtilsSearchParityTest.ts.snap index fdea06894abb..4e8bc3d80d0b 100644 --- a/tests/unit/__snapshots__/OptionsListUtilsSearchParityTest.ts.snap +++ b/tests/unit/__snapshots__/OptionsListUtilsSearchParityTest.ts.snap @@ -2,6 +2,98 @@ exports[`OptionsListUtils search parity (golden master) SearchRouter blank query and server-ID pass match the golden master 1`] = ` { + "focusedReport": { + "Empty submitted report": { + "blank": { + "currentUserOption": null, + "hasMore": true, + "personalDetails": [], + "recentReports": [ + "2004 Current User (you) [alt: This is your personal space. Use it for notes, tasks, drafts, and reminders.] [accID: 1]", + "2013 #orphan-room [alt: Gone Workspace • This chat room is for anything #orphan-room related.]", + "1124 Ada Cole [alt: This chat is with Ada Cole.] [accID: 124]", + "1103 Dan Ames [alt: This chat is with Dan Ames.] [accID: 103]", + "1019 No Login [alt: This chat is with No Login.] [accID: 19]", + "1123 Lou Byrd [alt: This chat is with Lou Byrd.] [accID: 123]", + "1102 Cyd Ames [alt: This chat is with Cyd Ames.] [accID: 102]", + "1018 Domain Admin [alt: This chat is with Domain Admin.] [accID: 18]", + "1121 Jon Byrd [alt: This chat is with Jon Byrd.] [accID: 121]", + "1100 Ada Ames [alt: This chat is with Ada Ames.] [accID: 100]", + "1120 Ivy Byrd [alt: This chat is with Ivy Byrd.] [accID: 120]", + "1015 Alex Smith Jr. [alt: This chat is with Alex Smith Jr..] [accID: 15]", + "2012 #vít-room [rbr] [alt: Acme Holdings • This chat room is for anything #vít-room related.]", + "2032 Empty submitted report [alt: No activity yet] [accID: 1]", + "1139 Dan Dunn [alt: This chat is with Dan Dunn.] [accID: 139]", + "1118 Gus Byrd [alt: This chat is with Gus Byrd.] [accID: 118]", + "1013 (500) 555-0006 [alt: This chat is with (500) 555-0006.] [accID: 13]", + "2007 Groß Straße [alt: Vít Workspace • This chat is for invoices between Groß Straße and Vít Workspace. Use the + button to send an invoice.]", + "1138 Cyd Dunn [alt: This chat is with Cyd Dunn.] [accID: 138]", + "1117 Fay Byrd [alt: This chat is with Fay Byrd.] [accID: 117]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "empty": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "2032 Empty submitted report [alt: No activity yet] [accID: 1]", + "2025 Empty pinned thread [alt: No activity yet]", + "2024 Empty thread with a draft [alt: No activity yet]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + }, + "Empty thread": { + "blank": { + "currentUserOption": null, + "hasMore": true, + "personalDetails": [], + "recentReports": [ + "2004 Current User (you) [alt: This is your personal space. Use it for notes, tasks, drafts, and reminders.] [accID: 1]", + "2013 #orphan-room [alt: Gone Workspace • This chat room is for anything #orphan-room related.]", + "1124 Ada Cole [alt: This chat is with Ada Cole.] [accID: 124]", + "1103 Dan Ames [alt: This chat is with Dan Ames.] [accID: 103]", + "1019 No Login [alt: This chat is with No Login.] [accID: 19]", + "1123 Lou Byrd [alt: This chat is with Lou Byrd.] [accID: 123]", + "1102 Cyd Ames [alt: This chat is with Cyd Ames.] [accID: 102]", + "1018 Domain Admin [alt: This chat is with Domain Admin.] [accID: 18]", + "1121 Jon Byrd [alt: This chat is with Jon Byrd.] [accID: 121]", + "1100 Ada Ames [alt: This chat is with Ada Ames.] [accID: 100]", + "1120 Ivy Byrd [alt: This chat is with Ivy Byrd.] [accID: 120]", + "1015 Alex Smith Jr. [alt: This chat is with Alex Smith Jr..] [accID: 15]", + "2012 #vít-room [rbr] [alt: Acme Holdings • This chat room is for anything #vít-room related.]", + "1139 Dan Dunn [alt: This chat is with Dan Dunn.] [accID: 139]", + "1118 Gus Byrd [alt: This chat is with Gus Byrd.] [accID: 118]", + "1013 (500) 555-0006 [alt: This chat is with (500) 555-0006.] [accID: 13]", + "2007 Groß Straße [alt: Vít Workspace • This chat is for invoices between Groß Straße and Vít Workspace. Use the + button to send an invoice.]", + "1138 Cyd Dunn [alt: This chat is with Cyd Dunn.] [accID: 138]", + "1117 Fay Byrd [alt: This chat is with Fay Byrd.] [accID: 117]", + "1012 first.last+tag@x.com [alt: This chat is with first.last+tag@x.com.] [accID: 12]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + "empty": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "2025 Empty pinned thread [alt: No activity yet]", + "2024 Empty thread with a draft [alt: No activity yet]", + "2023 Empty thread [alt: No activity yet]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, + }, + }, "searchRouterBlankAtProductionCap": { "currentUserOption": null, "hasMore": true, From 7d0cfd6d658b1c85e6af52bfd55dc5decd2d6b4e Mon Sep 17 00:00:00 2001 From: Jakub Magiera Date: Fri, 23 Oct 2026 10:28:22 +0200 Subject: [PATCH 05/13] Check that the search parity server pass drops rejected and unknown report IDs --- tests/unit/OptionsListUtilsSearchParityTest.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tests/unit/OptionsListUtilsSearchParityTest.ts b/tests/unit/OptionsListUtilsSearchParityTest.ts index 8f0854ab8db4..7306e9839d7b 100644 --- a/tests/unit/OptionsListUtilsSearchParityTest.ts +++ b/tests/unit/OptionsListUtilsSearchParityTest.ts @@ -302,20 +302,26 @@ function getOptionListKind(callSite: string, query: string): OptionListKind { return kind; } -// Reports returned by the SearchAutocompleteList server pass in Auth ranking order, including a self-DM that the pass drops. +// Reports returned by the SearchAutocompleteList server pass in Auth ranking order, including a self-DM that the pass drops and reports the canonical filter rejects. const SERVER_PASS_REPORT_NAMES = [ '#admins', '#announce', + 'Hidden thread', '#vít-room', 'Weekend Hiking', 'Current User (you)', + 'Empty thread', 'Book flights', 'Offsite expenses', + 'Empty submitted report', 'Thread about the offsite', 'İsmail Yılmaz', "Mary-Jane O'Brien (archived) ", ]; +// An ID Auth returns for a report the client has not loaded, which the pass drops because it has no local option. No fixture report uses it. +const UNKNOWN_SERVER_REPORT_ID = '999999'; + /** Finds a fixture report by its name, so a test never depends on the reportIDs the fixture assigns. */ function getReportIDByName(sourceFixture: SearchFixture, reportName: string): string { const reportID = Object.values(sourceFixture.reports).find((report) => report?.reportName === reportName)?.reportID; @@ -372,7 +378,7 @@ function computeGoldenMaster(sourceFixture: SearchFixture) { ]), ); - const orderedServerReportIDs = SERVER_PASS_REPORT_NAMES.map((reportName) => getReportIDByName(sourceFixture, reportName)).slice( + const orderedServerReportIDs = [...SERVER_PASS_REPORT_NAMES.map((reportName) => getReportIDByName(sourceFixture, reportName)), UNKNOWN_SERVER_REPORT_ID].slice( 0, CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMOUNT_OF_SUGGESTIONS, ); @@ -449,7 +455,7 @@ describe('OptionsListUtils search parity (golden master)', () => { }); it('SearchRouter blank query and server-ID pass match the golden master', () => { - // Given the SearchRouter idle list at the production cap, the SearchAutocompleteList server pass (a fixed set of reports in Auth's order, no query) and the SearchRouter opened over a focused empty thread or empty submitted report + // Given the SearchRouter idle list at the production cap, the SearchAutocompleteList server pass (a fixed set of reports in Auth's order, no query, including rejected and unknown IDs that it drops) and the SearchRouter opened over a focused empty thread or empty submitted report // When they run through their production configs, the focused ones for a blank query and for a query matching the focused report // Then all match the recorded golden master, with the focused report shown only while it is focused expectToMatchGoldenMaster(goldenMaster.passes); From db832b4d5de9d922b2979ed0bffec578b3626c6d Mon Sep 17 00:00:00 2001 From: Jakub Magiera Date: Fri, 23 Oct 2026 10:29:49 +0200 Subject: [PATCH 06/13] Pin archived state in search parity snapshot lines --- .../unit/OptionsListUtilsSearchParityTest.ts | 2 ++ .../OptionsListUtilsSearchParityTest.ts.snap | 20 +++++++++---------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/tests/unit/OptionsListUtilsSearchParityTest.ts b/tests/unit/OptionsListUtilsSearchParityTest.ts index 7306e9839d7b..3e45ec8f3e46 100644 --- a/tests/unit/OptionsListUtilsSearchParityTest.ts +++ b/tests/unit/OptionsListUtilsSearchParityTest.ts @@ -53,6 +53,7 @@ function clearSearchCaches() { /** * Serializes options into " [flags] [alt: ...] [accID: ...]" lines to pin option ordering and content rather than rendering details. + * Flags are [selected], [disabled], [bold], [gbr], [rbr] and [archived], where [archived] surfaces private_isArchived, which drives ranking and the archived preview text. * Invited users include their login when different from text to distinguish email from phone invites. */ function createOptionSerializer() { @@ -66,6 +67,7 @@ function createOptionSerializer() { option.isBold ? ' [bold]' : '', option.brickRoadIndicator === CONST.BRICK_ROAD_INDICATOR_STATUS.INFO ? ' [gbr]' : '', option.brickRoadIndicator === CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR ? ' [rbr]' : '', + option.private_isArchived ? ' [archived]' : '', ].join(''); const invitedLogin = isUserToInvite && option.login !== option.text ? ` ${option.login ?? ''}` : ''; const altText = option.alternateText ? ` [alt: ${option.alternateText}]` : ''; diff --git a/tests/unit/__snapshots__/OptionsListUtilsSearchParityTest.ts.snap b/tests/unit/__snapshots__/OptionsListUtilsSearchParityTest.ts.snap index 4e8bc3d80d0b..c11ab2d6d8e0 100644 --- a/tests/unit/__snapshots__/OptionsListUtilsSearchParityTest.ts.snap +++ b/tests/unit/__snapshots__/OptionsListUtilsSearchParityTest.ts.snap @@ -133,7 +133,7 @@ exports[`OptionsListUtils search parity (golden master) SearchRouter blank query "2031 Offsite expenses [alt: No activity yet] [accID: 1]", "2021 Thread about the offsite [bold] [alt: José Ñúñez: Count me in]", "1004 İsmail Yılmaz [alt: Thanks!] [accID: 4]", - "2000 Mary-Jane O'Brien (archived) [alt: This chat room has been archived.] [accID: 11]", + "2000 Mary-Jane O'Brien (archived) [archived] [alt: This chat room has been archived.] [accID: 11]", ], } `; @@ -222,7 +222,7 @@ exports[`OptionsListUtils search parity (golden master) builds searching and Sea "1126 Cyd Cole [alt: cyd.cole.26@example.com] [accID: 126]", "1105 Fay Ames [alt: fay.ames.5@example.com] [accID: 105]", "1021 Concierge [alt: concierge@expensify.com] [accID: 21]", - "2000 Mary-Jane O'Brien (archived) [alt: obrien@example.com] [accID: 11]", + "2000 Mary-Jane O'Brien (archived) [archived] [alt: obrien@example.com] [accID: 11]", ], }, "searching": { @@ -283,7 +283,7 @@ exports[`OptionsListUtils search parity (golden master) builds searching and Sea "137 Bea Dunn [alt: bea.dunn.37@example.com] [accID: 137]", "17 Account Executive [alt: Account Executive] [accID: 17]", "20 René Dupont [alt: rene@example.com] [accID: 20]", - "2000 Mary-Jane O'Brien [alt: obrien@example.com] [accID: 11]", + "2000 Mary-Jane O'Brien [archived] [alt: obrien@example.com] [accID: 11]", "2001 Alex Smith [alt: alex.smith@example.com] [accID: 14]", "2020 José Ñúñez [alt: jose.nunez@example.com] [accID: 2]", "5 Groß Straße [alt: strasse@example.com] [accID: 5]", @@ -393,7 +393,7 @@ exports[`OptionsListUtils search parity (golden master) builds searching and Sea "1136 Ada Dunn [alt: ada.dunn.36@example.com] [accID: 136]", "1138 Cyd Dunn [alt: cyd.dunn.38@example.com] [accID: 138]", "1139 Dan Dunn [alt: dan.dunn.39@example.com] [accID: 139]", - "2000 Mary-Jane O'Brien (archived) [alt: obrien@example.com] [accID: 11]", + "2000 Mary-Jane O'Brien (archived) [archived] [alt: obrien@example.com] [accID: 11]", "2001 Alex Smith [alt: alex.smith@example.com] [accID: 14]", "2002 [accID: 999]", "2003 Weekend Hiking [alt: Group]", @@ -889,7 +889,7 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterDi "personalDetails": [], "recentReports": [ "2003 Weekend Hiking [alt: This chat is with José Ñúñez, Mary-Jane O'Brien, and Vít Novák.]", - "2000 Mary-Jane O'Brien (archived) [alt: This chat room has been archived.] [accID: 11]", + "2000 Mary-Jane O'Brien (archived) [archived] [alt: This chat room has been archived.] [accID: 11]", ], "selfDMChat": null, "userToInvite": null, @@ -970,7 +970,7 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterDi "personalDetails": [], "recentReports": [ "2003 Weekend Hiking [alt: This chat is with José Ñúñez, Mary-Jane O'Brien, and Vít Novák.]", - "2000 Mary-Jane O'Brien (archived) [alt: This chat room has been archived.] [accID: 11]", + "2000 Mary-Jane O'Brien (archived) [archived] [alt: This chat room has been archived.] [accID: 11]", ], "selfDMChat": null, "userToInvite": null, @@ -1728,11 +1728,11 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterLo "currentUserOption": null, "hasMore": false, "personalDetails": [ - "2000 Mary-Jane O'Brien [alt: obrien@example.com] [accID: 11]", + "2000 Mary-Jane O'Brien [archived] [alt: obrien@example.com] [accID: 11]", ], "recentReports": [ "2003 Weekend Hiking [alt: This chat is with José Ñúñez, Mary-Jane O'Brien, and Vít Novák.]", - "2000 Mary-Jane O'Brien (archived) [alt: This chat room has been archived.] [accID: 11]", + "2000 Mary-Jane O'Brien (archived) [archived] [alt: This chat room has been archived.] [accID: 11]", ], "selfDMChat": null, "userToInvite": null, @@ -1820,11 +1820,11 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterLo "currentUserOption": null, "hasMore": false, "personalDetails": [ - "2000 Mary-Jane O'Brien [alt: obrien@example.com] [accID: 11]", + "2000 Mary-Jane O'Brien [archived] [alt: obrien@example.com] [accID: 11]", ], "recentReports": [ "2003 Weekend Hiking [alt: This chat is with José Ñúñez, Mary-Jane O'Brien, and Vít Novák.]", - "2000 Mary-Jane O'Brien (archived) [alt: This chat room has been archived.] [accID: 11]", + "2000 Mary-Jane O'Brien (archived) [archived] [alt: This chat room has been archived.] [accID: 11]", ], "selfDMChat": null, "userToInvite": null, From 051143e2b88c7f1adc879486788431db328f3a66 Mon Sep 17 00:00:00 2001 From: Jakub Magiera Date: Fri, 23 Oct 2026 10:31:19 +0200 Subject: [PATCH 07/13] Pin room and workspace chat subtitles in search parity snapshot lines --- .../unit/OptionsListUtilsSearchParityTest.ts | 6 +- .../OptionsListUtilsSearchParityTest.ts.snap | 204 +++++++++--------- 2 files changed, 106 insertions(+), 104 deletions(-) diff --git a/tests/unit/OptionsListUtilsSearchParityTest.ts b/tests/unit/OptionsListUtilsSearchParityTest.ts index 3e45ec8f3e46..9585c92f9902 100644 --- a/tests/unit/OptionsListUtilsSearchParityTest.ts +++ b/tests/unit/OptionsListUtilsSearchParityTest.ts @@ -52,9 +52,10 @@ function clearSearchCaches() { } /** - * Serializes options into " [flags] [alt: ...] [accID: ...]" lines to pin option ordering and content rather than rendering details. + * Serializes options into " [flags] [alt: ...] [sub: ...] [accID: ...]" lines to pin option ordering and content rather than rendering details. * Flags are [selected], [disabled], [bold], [gbr], [rbr] and [archived], where [archived] surfaces private_isArchived, which drives ranking and the archived preview text. * Invited users include their login when different from text to distinguish email from phone invites. + * [sub: ...] is only emitted for rooms and workspace chats, because that is what search matches for them. Fields the SearchRouter never reads (participantsList, icons, policyID, ownerAccountID, item) are deliberately left out. */ function createOptionSerializer() { function serializeOption(option: SearchOptionData | null | undefined, {isUserToInvite = false}: {isUserToInvite?: boolean} = {}): string | null { @@ -71,8 +72,9 @@ function createOptionSerializer() { ].join(''); const invitedLogin = isUserToInvite && option.login !== option.text ? ` ${option.login ?? ''}` : ''; const altText = option.alternateText ? ` [alt: ${option.alternateText}]` : ''; + const subtitle = (option.isChatRoom || option.isPolicyExpenseChat) && option.subtitle ? ` [sub: ${option.subtitle}]` : ''; const accountID = option.accountID ? ` [accID: ${option.accountID}]` : ''; - return `${option.keyForList ?? ''} ${option.text ?? ''}${flags}${altText}${accountID}${invitedLogin}`; + return `${option.keyForList ?? ''} ${option.text ?? ''}${flags}${altText}${subtitle}${accountID}${invitedLogin}`; } function serializeResult({options, hasMore}: OptionsResult | {options: Options; hasMore?: boolean}) { diff --git a/tests/unit/__snapshots__/OptionsListUtilsSearchParityTest.ts.snap b/tests/unit/__snapshots__/OptionsListUtilsSearchParityTest.ts.snap index c11ab2d6d8e0..d8277c1305bd 100644 --- a/tests/unit/__snapshots__/OptionsListUtilsSearchParityTest.ts.snap +++ b/tests/unit/__snapshots__/OptionsListUtilsSearchParityTest.ts.snap @@ -10,7 +10,7 @@ exports[`OptionsListUtils search parity (golden master) SearchRouter blank query "personalDetails": [], "recentReports": [ "2004 Current User (you) [alt: This is your personal space. Use it for notes, tasks, drafts, and reminders.] [accID: 1]", - "2013 #orphan-room [alt: Gone Workspace • This chat room is for anything #orphan-room related.]", + "2013 #orphan-room [alt: Gone Workspace • This chat room is for anything #orphan-room related.] [sub: Gone Workspace]", "1124 Ada Cole [alt: This chat is with Ada Cole.] [accID: 124]", "1103 Dan Ames [alt: This chat is with Dan Ames.] [accID: 103]", "1019 No Login [alt: This chat is with No Login.] [accID: 19]", @@ -21,12 +21,12 @@ exports[`OptionsListUtils search parity (golden master) SearchRouter blank query "1100 Ada Ames [alt: This chat is with Ada Ames.] [accID: 100]", "1120 Ivy Byrd [alt: This chat is with Ivy Byrd.] [accID: 120]", "1015 Alex Smith Jr. [alt: This chat is with Alex Smith Jr..] [accID: 15]", - "2012 #vít-room [rbr] [alt: Acme Holdings • This chat room is for anything #vít-room related.]", + "2012 #vít-room [rbr] [alt: Acme Holdings • This chat room is for anything #vít-room related.] [sub: Acme Holdings]", "2032 Empty submitted report [alt: No activity yet] [accID: 1]", "1139 Dan Dunn [alt: This chat is with Dan Dunn.] [accID: 139]", "1118 Gus Byrd [alt: This chat is with Gus Byrd.] [accID: 118]", "1013 (500) 555-0006 [alt: This chat is with (500) 555-0006.] [accID: 13]", - "2007 Groß Straße [alt: Vít Workspace • This chat is for invoices between Groß Straße and Vít Workspace. Use the + button to send an invoice.]", + "2007 Groß Straße [alt: Vít Workspace • This chat is for invoices between Groß Straße and Vít Workspace. Use the + button to send an invoice.] [sub: Invoices]", "1138 Cyd Dunn [alt: This chat is with Cyd Dunn.] [accID: 138]", "1117 Fay Byrd [alt: This chat is with Fay Byrd.] [accID: 117]", ], @@ -55,7 +55,7 @@ exports[`OptionsListUtils search parity (golden master) SearchRouter blank query "personalDetails": [], "recentReports": [ "2004 Current User (you) [alt: This is your personal space. Use it for notes, tasks, drafts, and reminders.] [accID: 1]", - "2013 #orphan-room [alt: Gone Workspace • This chat room is for anything #orphan-room related.]", + "2013 #orphan-room [alt: Gone Workspace • This chat room is for anything #orphan-room related.] [sub: Gone Workspace]", "1124 Ada Cole [alt: This chat is with Ada Cole.] [accID: 124]", "1103 Dan Ames [alt: This chat is with Dan Ames.] [accID: 103]", "1019 No Login [alt: This chat is with No Login.] [accID: 19]", @@ -66,11 +66,11 @@ exports[`OptionsListUtils search parity (golden master) SearchRouter blank query "1100 Ada Ames [alt: This chat is with Ada Ames.] [accID: 100]", "1120 Ivy Byrd [alt: This chat is with Ivy Byrd.] [accID: 120]", "1015 Alex Smith Jr. [alt: This chat is with Alex Smith Jr..] [accID: 15]", - "2012 #vít-room [rbr] [alt: Acme Holdings • This chat room is for anything #vít-room related.]", + "2012 #vít-room [rbr] [alt: Acme Holdings • This chat room is for anything #vít-room related.] [sub: Acme Holdings]", "1139 Dan Dunn [alt: This chat is with Dan Dunn.] [accID: 139]", "1118 Gus Byrd [alt: This chat is with Gus Byrd.] [accID: 118]", "1013 (500) 555-0006 [alt: This chat is with (500) 555-0006.] [accID: 13]", - "2007 Groß Straße [alt: Vít Workspace • This chat is for invoices between Groß Straße and Vít Workspace. Use the + button to send an invoice.]", + "2007 Groß Straße [alt: Vít Workspace • This chat is for invoices between Groß Straße and Vít Workspace. Use the + button to send an invoice.] [sub: Invoices]", "1138 Cyd Dunn [alt: This chat is with Cyd Dunn.] [accID: 138]", "1117 Fay Byrd [alt: This chat is with Fay Byrd.] [accID: 117]", "1012 first.last+tag@x.com [alt: This chat is with first.last+tag@x.com.] [accID: 12]", @@ -100,7 +100,7 @@ exports[`OptionsListUtils search parity (golden master) SearchRouter blank query "personalDetails": [], "recentReports": [ "2004 Current User (you) [alt: This is your personal space. Use it for notes, tasks, drafts, and reminders.] [accID: 1]", - "2013 #orphan-room [alt: Gone Workspace • This chat room is for anything #orphan-room related.]", + "2013 #orphan-room [alt: Gone Workspace • This chat room is for anything #orphan-room related.] [sub: Gone Workspace]", "1124 Ada Cole [alt: This chat is with Ada Cole.] [accID: 124]", "1103 Dan Ames [alt: This chat is with Dan Ames.] [accID: 103]", "1019 No Login [alt: This chat is with No Login.] [accID: 19]", @@ -111,11 +111,11 @@ exports[`OptionsListUtils search parity (golden master) SearchRouter blank query "1100 Ada Ames [alt: This chat is with Ada Ames.] [accID: 100]", "1120 Ivy Byrd [alt: This chat is with Ivy Byrd.] [accID: 120]", "1015 Alex Smith Jr. [alt: This chat is with Alex Smith Jr..] [accID: 15]", - "2012 #vít-room [rbr] [alt: Acme Holdings • This chat room is for anything #vít-room related.]", + "2012 #vít-room [rbr] [alt: Acme Holdings • This chat room is for anything #vít-room related.] [sub: Acme Holdings]", "1139 Dan Dunn [alt: This chat is with Dan Dunn.] [accID: 139]", "1118 Gus Byrd [alt: This chat is with Gus Byrd.] [accID: 118]", "1013 (500) 555-0006 [alt: This chat is with (500) 555-0006.] [accID: 13]", - "2007 Groß Straße [alt: Vít Workspace • This chat is for invoices between Groß Straße and Vít Workspace. Use the + button to send an invoice.]", + "2007 Groß Straße [alt: Vít Workspace • This chat is for invoices between Groß Straße and Vít Workspace. Use the + button to send an invoice.] [sub: Invoices]", "1138 Cyd Dunn [alt: This chat is with Cyd Dunn.] [accID: 138]", "1117 Fay Byrd [alt: This chat is with Fay Byrd.] [accID: 117]", "1012 first.last+tag@x.com [alt: This chat is with first.last+tag@x.com.] [accID: 12]", @@ -125,9 +125,9 @@ exports[`OptionsListUtils search parity (golden master) SearchRouter blank query "workspaceChats": [], }, "serverPass": [ - "2010 #admins [alt: Vít Workspace • This chat is with Vít Workspace admin. Use it to chat about workspace setup and more.]", - "2011 #announce [alt: Vít Workspace • This chat is with everyone in Vít Workspace. Use it for the most important announcements.]", - "2012 #vít-room [rbr] [alt: Acme Holdings • This chat room is for anything #vít-room related.]", + "2010 #admins [alt: Vít Workspace • This chat is with Vít Workspace admin. Use it to chat about workspace setup and more.] [sub: Vít Workspace]", + "2011 #announce [alt: Vít Workspace • This chat is with everyone in Vít Workspace. Use it for the most important announcements.] [sub: Vít Workspace]", + "2012 #vít-room [rbr] [alt: Acme Holdings • This chat room is for anything #vít-room related.] [sub: Acme Holdings]", "2003 Weekend Hiking [alt: This chat is with José Ñúñez, Mary-Jane O'Brien, and Vít Novák.]", "2030 Book flights [alt: No activity yet]", "2031 Offsite expenses [alt: No activity yet] [accID: 1]", @@ -145,9 +145,9 @@ exports[`OptionsListUtils search parity (golden master) builds searching and Sea "personalDetails": [], "reports": [ "2004 Current User (you) [alt: current.user@example.com] [accID: 1]", - "2034 José Ñúñez", + "2034 José Ñúñez [sub: Vít Workspace]", "2001 Alex Smith [alt: alex.smith@example.com] [accID: 14]", - "2013 #orphan-room", + "2013 #orphan-room [sub: Gone Workspace]", "1124 Ada Cole [alt: ada.cole.24@example.com] [accID: 124]", "1103 Dan Ames [alt: dan.ames.3@example.com] [accID: 103]", "1019 No Login [accID: 19]", @@ -161,9 +161,9 @@ exports[`OptionsListUtils search parity (golden master) builds searching and Sea "personalDetails": [], "reports": [ "2004 Current User (you) [alt: current.user@example.com] [accID: 1]", - "2034 José Ñúñez", + "2034 José Ñúñez [sub: Vít Workspace]", "2001 Alex Smith [alt: alex.smith@example.com] [accID: 14]", - "2013 #orphan-room", + "2013 #orphan-room [sub: Gone Workspace]", "1124 Ada Cole [alt: ada.cole.24@example.com] [accID: 124]", "1103 Dan Ames [alt: dan.ames.3@example.com] [accID: 103]", "1019 No Login [accID: 19]", @@ -175,12 +175,12 @@ exports[`OptionsListUtils search parity (golden master) builds searching and Sea "1016 Expensify Notifications [alt: notifications@expensify.com] [accID: 16]", "1120 Ivy Byrd [alt: ivy.byrd.20@example.com] [accID: 120]", "1015 Alex Smith Jr. [alt: alex.smith2@example.com] [accID: 15]", - "2012 #vít-room [rbr]", + "2012 #vít-room [rbr] [sub: Acme Holdings]", "2032 Empty submitted report [alt: Expense] [accID: 1]", "1139 Dan Dunn [alt: dan.dunn.39@example.com] [accID: 139]", "1118 Gus Byrd [alt: gus.byrd.18@example.com] [accID: 118]", "1013 (500) 555-0006 [alt: (500) 555-0006] [accID: 13]", - "2007 Groß Straße", + "2007 Groß Straße [sub: Invoices]", "1138 Cyd Dunn [alt: cyd.dunn.38@example.com] [accID: 138]", "1117 Fay Byrd [alt: fay.byrd.17@example.com] [accID: 117]", "1012 first.last+tag@x.com [alt: first.last+tag@x.com] [accID: 12]", @@ -192,9 +192,9 @@ exports[`OptionsListUtils search parity (golden master) builds searching and Sea "1135 Lou Cole [alt: lou.cole.35@example.com] [accID: 135]", "1114 Cyd Byrd [alt: cyd.byrd.14@example.com] [accID: 114]", "1009 Ana​Maria [alt: ana.maria@example.com] [accID: 9]", - "2011 #announce", - "2006 Groß Straße's expenses", - "2005 Current User's expenses [gbr]", + "2011 #announce [sub: Vít Workspace]", + "2006 Groß Straße's expenses [sub: Vít Workspace]", + "2005 Current User's expenses [gbr] [sub: Vít Workspace • Submits to Current User]", "1133 Jon Cole [alt: jon.cole.33@example.com] [accID: 133]", "1112 Ada Byrd [alt: ada.byrd.12@example.com] [accID: 112]", "1007 Οδυσσεύς [alt: odysseus@example.com] [accID: 7]", @@ -213,7 +213,7 @@ exports[`OptionsListUtils search parity (golden master) builds searching and Sea "1109 Jon Ames [alt: jon.ames.9@example.com] [accID: 109]", "1004 İsmail Yılmaz [alt: ismail@example.com] [accID: 4]", "2020 José Ñúñez [alt: jose.nunez@example.com] [accID: 2]", - "2010 #admins", + "2010 #admins [sub: Vít Workspace]", "1129 Fay Cole [alt: fay.cole.29@example.com] [accID: 129]", "1108 Ivy Ames [alt: ivy.ames.8@example.com] [accID: 108]", "1003 Vít Novák [alt: vit.novak@example.com] [accID: 3]", @@ -398,13 +398,13 @@ exports[`OptionsListUtils search parity (golden master) builds searching and Sea "2002 [accID: 999]", "2003 Weekend Hiking [alt: Group]", "2004 Current User (you) [alt: current.user@example.com] [accID: 1]", - "2005 Current User's expenses [gbr]", - "2006 Groß Straße's expenses", - "2007 Groß Straße", - "2010 #admins", - "2011 #announce", - "2012 #vít-room [rbr]", - "2013 #orphan-room", + "2005 Current User's expenses [gbr] [sub: Vít Workspace • Submits to Current User]", + "2006 Groß Straße's expenses [sub: Vít Workspace]", + "2007 Groß Straße [sub: Invoices]", + "2010 #admins [sub: Vít Workspace]", + "2011 #announce [sub: Vít Workspace]", + "2012 #vít-room [rbr] [sub: Acme Holdings]", + "2013 #orphan-room [sub: Gone Workspace]", "2020 José Ñúñez [alt: jose.nunez@example.com] [accID: 2]", "2021 Thread about the offsite [alt: Thread]", "2022 Hidden thread [alt: Thread]", @@ -415,7 +415,7 @@ exports[`OptionsListUtils search parity (golden master) builds searching and Sea "2030 Book flights [alt: Task]", "2031 Offsite expenses [alt: Expense] [accID: 1]", "2032 Empty submitted report [alt: Expense] [accID: 1]", - "2034 José Ñúñez", + "2034 José Ñúñez [sub: Vít Workspace]", ], }, } @@ -429,7 +429,7 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterDi "personalDetails": [], "recentReports": [ "2004 Current User (you) [alt: This is your personal space. Use it for notes, tasks, drafts, and reminders.] [accID: 1]", - "2013 #orphan-room [alt: Gone Workspace • This chat room is for anything #orphan-room related.]", + "2013 #orphan-room [alt: Gone Workspace • This chat room is for anything #orphan-room related.] [sub: Gone Workspace]", "1124 Ada Cole [alt: This chat is with Ada Cole.] [accID: 124]", "1103 Dan Ames [alt: This chat is with Dan Ames.] [accID: 103]", "1019 No Login [alt: This chat is with No Login.] [accID: 19]", @@ -563,8 +563,8 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterDi "personalDetails": [], "recentReports": [ "5 Groß Straße [alt: strasse@example.com] [accID: 5]", - "2006 Groß Straße's expenses [alt: Vít Workspace • This is where Groß Straße will submit expenses to Vít Workspace. Just use the + button.]", - "2007 Groß Straße [alt: Vít Workspace • This chat is for invoices between Groß Straße and Vít Workspace. Use the + button to send an invoice.]", + "2006 Groß Straße's expenses [alt: Vít Workspace • This is where Groß Straße will submit expenses to Vít Workspace. Just use the + button.] [sub: Vít Workspace]", + "2007 Groß Straße [alt: Vít Workspace • This chat is for invoices between Groß Straße and Vít Workspace. Use the + button to send an invoice.] [sub: Invoices]", ], "selfDMChat": null, "userToInvite": null, @@ -599,12 +599,12 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterDi "personalDetails": [], "recentReports": [ "1003 Vít Novák [alt: This chat is with Vít Novák.] [accID: 3]", - "2006 Groß Straße's expenses [alt: Vít Workspace • This is where Groß Straße will submit expenses to Vít Workspace. Just use the + button.]", - "2005 Current User's expenses [alt: Vít Workspace • This is where Current User will submit expenses to Vít Workspace. Just use the + button.]", + "2006 Groß Straße's expenses [alt: Vít Workspace • This is where Groß Straße will submit expenses to Vít Workspace. Just use the + button.] [sub: Vít Workspace]", + "2005 Current User's expenses [alt: Vít Workspace • This is where Current User will submit expenses to Vít Workspace. Just use the + button.] [sub: Vít Workspace • Submits to Current User]", "2003 Weekend Hiking [alt: This chat is with José Ñúñez, Mary-Jane O'Brien, and Vít Novák.]", - "2012 #vít-room [rbr] [alt: Acme Holdings • This chat room is for anything #vít-room related.]", - "2011 #announce [alt: Vít Workspace • This chat is with everyone in Vít Workspace. Use it for the most important announcements.]", - "2010 #admins [alt: Vít Workspace • This chat is with Vít Workspace admin. Use it to chat about workspace setup and more.]", + "2012 #vít-room [rbr] [alt: Acme Holdings • This chat room is for anything #vít-room related.] [sub: Acme Holdings]", + "2011 #announce [alt: Vít Workspace • This chat is with everyone in Vít Workspace. Use it for the most important announcements.] [sub: Vít Workspace]", + "2010 #admins [alt: Vít Workspace • This chat is with Vít Workspace admin. Use it to chat about workspace setup and more.] [sub: Vít Workspace]", ], "selfDMChat": null, "userToInvite": null, @@ -652,7 +652,7 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterDi "2031 Offsite expenses [alt: No activity yet] [accID: 1]", "1136 Ada Dunn [alt: This chat is with Ada Dunn.] [accID: 136]", "1112 Ada Byrd [alt: ada.byrd.12@example.com] [accID: 112]", - "2013 #orphan-room [alt: Gone Workspace • This chat room is for anything #orphan-room related.]", + "2013 #orphan-room [alt: Gone Workspace • This chat room is for anything #orphan-room related.] [sub: Gone Workspace]", ], "selfDMChat": null, "userToInvite": null, @@ -663,7 +663,7 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterDi "hasMore": false, "personalDetails": [], "recentReports": [ - "2012 #vít-room [rbr] [alt: Acme Holdings • This chat room is for anything #vít-room related.]", + "2012 #vít-room [rbr] [alt: Acme Holdings • This chat room is for anything #vít-room related.] [sub: Acme Holdings]", ], "selfDMChat": null, "userToInvite": null, @@ -685,7 +685,7 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterDi "hasMore": false, "personalDetails": [], "recentReports": [ - "2010 #admins [alt: Vít Workspace • This chat is with Vít Workspace admin. Use it to chat about workspace setup and more.]", + "2010 #admins [alt: Vít Workspace • This chat is with Vít Workspace admin. Use it to chat about workspace setup and more.] [sub: Vít Workspace]", ], "selfDMChat": null, "userToInvite": null, @@ -912,7 +912,7 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterDi "101 Bea Ames [alt: bea.ames.1@example.com] [accID: 101]", "104 Eve Ames [alt: eve.ames.4@example.com] [accID: 104]", "107 Hal Ames [alt: hal.ames.7@example.com] [accID: 107]", - "2012 #vít-room [rbr] [alt: Acme Holdings • This chat room is for anything #vít-room related.]", + "2012 #vít-room [rbr] [alt: Acme Holdings • This chat room is for anything #vít-room related.] [sub: Acme Holdings]", ], "selfDMChat": null, "userToInvite": null, @@ -946,7 +946,7 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterDi "hasMore": false, "personalDetails": [], "recentReports": [ - "2011 #announce [alt: Vít Workspace • This chat is with everyone in Vít Workspace. Use it for the most important announcements.]", + "2011 #announce [alt: Vít Workspace • This chat is with everyone in Vít Workspace. Use it for the most important announcements.] [sub: Vít Workspace]", ], "selfDMChat": null, "userToInvite": null, @@ -981,7 +981,7 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterDi "hasMore": false, "personalDetails": [], "recentReports": [ - "2012 #vít-room [rbr] [alt: Acme Holdings • This chat room is for anything #vít-room related.]", + "2012 #vít-room [rbr] [alt: Acme Holdings • This chat room is for anything #vít-room related.] [sub: Acme Holdings]", ], "selfDMChat": null, "userToInvite": null, @@ -992,11 +992,11 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterDi "hasMore": false, "personalDetails": [], "recentReports": [ - "2006 Groß Straße's expenses [alt: Vít Workspace • This is where Groß Straße will submit expenses to Vít Workspace. Just use the + button.]", - "2005 Current User's expenses [alt: Vít Workspace • This is where Current User will submit expenses to Vít Workspace. Just use the + button.]", - "2013 #orphan-room [alt: Gone Workspace • This chat room is for anything #orphan-room related.]", - "2011 #announce [alt: Vít Workspace • This chat is with everyone in Vít Workspace. Use it for the most important announcements.]", - "2010 #admins [alt: Vít Workspace • This chat is with Vít Workspace admin. Use it to chat about workspace setup and more.]", + "2006 Groß Straße's expenses [alt: Vít Workspace • This is where Groß Straße will submit expenses to Vít Workspace. Just use the + button.] [sub: Vít Workspace]", + "2005 Current User's expenses [alt: Vít Workspace • This is where Current User will submit expenses to Vít Workspace. Just use the + button.] [sub: Vít Workspace • Submits to Current User]", + "2013 #orphan-room [alt: Gone Workspace • This chat room is for anything #orphan-room related.] [sub: Gone Workspace]", + "2011 #announce [alt: Vít Workspace • This chat is with everyone in Vít Workspace. Use it for the most important announcements.] [sub: Vít Workspace]", + "2010 #admins [alt: Vít Workspace • This chat is with Vít Workspace admin. Use it to chat about workspace setup and more.] [sub: Vít Workspace]", ], "selfDMChat": null, "userToInvite": null, @@ -1053,8 +1053,8 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterDi "personalDetails": [], "recentReports": [ "5 Groß Straße [alt: strasse@example.com] [accID: 5]", - "2006 Groß Straße's expenses [alt: Vít Workspace • This is where Groß Straße will submit expenses to Vít Workspace. Just use the + button.]", - "2007 Groß Straße [alt: Vít Workspace • This chat is for invoices between Groß Straße and Vít Workspace. Use the + button to send an invoice.]", + "2006 Groß Straße's expenses [alt: Vít Workspace • This is where Groß Straße will submit expenses to Vít Workspace. Just use the + button.] [sub: Vít Workspace]", + "2007 Groß Straße [alt: Vít Workspace • This chat is for invoices between Groß Straße and Vít Workspace. Use the + button to send an invoice.] [sub: Invoices]", ], "selfDMChat": null, "userToInvite": null, @@ -1089,12 +1089,12 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterDi "personalDetails": [], "recentReports": [ "1003 Vít Novák [alt: This chat is with Vít Novák.] [accID: 3]", - "2006 Groß Straße's expenses [alt: Vít Workspace • This is where Groß Straße will submit expenses to Vít Workspace. Just use the + button.]", - "2005 Current User's expenses [alt: Vít Workspace • This is where Current User will submit expenses to Vít Workspace. Just use the + button.]", + "2006 Groß Straße's expenses [alt: Vít Workspace • This is where Groß Straße will submit expenses to Vít Workspace. Just use the + button.] [sub: Vít Workspace]", + "2005 Current User's expenses [alt: Vít Workspace • This is where Current User will submit expenses to Vít Workspace. Just use the + button.] [sub: Vít Workspace • Submits to Current User]", "2003 Weekend Hiking [alt: This chat is with José Ñúñez, Mary-Jane O'Brien, and Vít Novák.]", - "2012 #vít-room [rbr] [alt: Acme Holdings • This chat room is for anything #vít-room related.]", - "2011 #announce [alt: Vít Workspace • This chat is with everyone in Vít Workspace. Use it for the most important announcements.]", - "2010 #admins [alt: Vít Workspace • This chat is with Vít Workspace admin. Use it to chat about workspace setup and more.]", + "2012 #vít-room [rbr] [alt: Acme Holdings • This chat room is for anything #vít-room related.] [sub: Acme Holdings]", + "2011 #announce [alt: Vít Workspace • This chat is with everyone in Vít Workspace. Use it for the most important announcements.] [sub: Vít Workspace]", + "2010 #admins [alt: Vít Workspace • This chat is with Vít Workspace admin. Use it to chat about workspace setup and more.] [sub: Vít Workspace]", ], "selfDMChat": null, "userToInvite": null, @@ -1105,10 +1105,10 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterDi "hasMore": false, "personalDetails": [], "recentReports": [ - "2006 Groß Straße's expenses [alt: Vít Workspace • This is where Groß Straße will submit expenses to Vít Workspace. Just use the + button.]", - "2005 Current User's expenses [alt: Vít Workspace • This is where Current User will submit expenses to Vít Workspace. Just use the + button.]", - "2011 #announce [alt: Vít Workspace • This chat is with everyone in Vít Workspace. Use it for the most important announcements.]", - "2010 #admins [alt: Vít Workspace • This chat is with Vít Workspace admin. Use it to chat about workspace setup and more.]", + "2006 Groß Straße's expenses [alt: Vít Workspace • This is where Groß Straße will submit expenses to Vít Workspace. Just use the + button.] [sub: Vít Workspace]", + "2005 Current User's expenses [alt: Vít Workspace • This is where Current User will submit expenses to Vít Workspace. Just use the + button.] [sub: Vít Workspace • Submits to Current User]", + "2011 #announce [alt: Vít Workspace • This chat is with everyone in Vít Workspace. Use it for the most important announcements.] [sub: Vít Workspace]", + "2010 #admins [alt: Vít Workspace • This chat is with Vít Workspace admin. Use it to chat about workspace setup and more.] [sub: Vít Workspace]", ], "selfDMChat": null, "userToInvite": null, @@ -1119,11 +1119,11 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterDi "hasMore": false, "personalDetails": [], "recentReports": [ - "2006 Groß Straße's expenses [alt: Vít Workspace • This is where Groß Straße will submit expenses to Vít Workspace. Just use the + button.]", - "2005 Current User's expenses [alt: Vít Workspace • This is where Current User will submit expenses to Vít Workspace. Just use the + button.]", - "2013 #orphan-room [alt: Gone Workspace • This chat room is for anything #orphan-room related.]", - "2011 #announce [alt: Vít Workspace • This chat is with everyone in Vít Workspace. Use it for the most important announcements.]", - "2010 #admins [alt: Vít Workspace • This chat is with Vít Workspace admin. Use it to chat about workspace setup and more.]", + "2006 Groß Straße's expenses [alt: Vít Workspace • This is where Groß Straße will submit expenses to Vít Workspace. Just use the + button.] [sub: Vít Workspace]", + "2005 Current User's expenses [alt: Vít Workspace • This is where Current User will submit expenses to Vít Workspace. Just use the + button.] [sub: Vít Workspace • Submits to Current User]", + "2013 #orphan-room [alt: Gone Workspace • This chat room is for anything #orphan-room related.] [sub: Gone Workspace]", + "2011 #announce [alt: Vít Workspace • This chat is with everyone in Vít Workspace. Use it for the most important announcements.] [sub: Vít Workspace]", + "2010 #admins [alt: Vít Workspace • This chat is with Vít Workspace admin. Use it to chat about workspace setup and more.] [sub: Vít Workspace]", ], "selfDMChat": null, "userToInvite": null, @@ -1213,7 +1213,7 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterLo "personalDetails": [], "recentReports": [ "2004 Current User (you) [alt: This is your personal space. Use it for notes, tasks, drafts, and reminders.] [accID: 1]", - "2013 #orphan-room [alt: Gone Workspace • This chat room is for anything #orphan-room related.]", + "2013 #orphan-room [alt: Gone Workspace • This chat room is for anything #orphan-room related.] [sub: Gone Workspace]", "1124 Ada Cole [alt: This chat is with Ada Cole.] [accID: 124]", "1103 Dan Ames [alt: This chat is with Dan Ames.] [accID: 103]", "1019 No Login [alt: This chat is with No Login.] [accID: 19]", @@ -1360,8 +1360,8 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterLo "5 Groß Straße [alt: strasse@example.com] [accID: 5]", ], "recentReports": [ - "2007 Groß Straße [alt: Vít Workspace • This chat is for invoices between Groß Straße and Vít Workspace. Use the + button to send an invoice.]", - "2006 Groß Straße's expenses [alt: Vít Workspace • This is where Groß Straße will submit expenses to Vít Workspace. Just use the + button.]", + "2007 Groß Straße [alt: Vít Workspace • This chat is for invoices between Groß Straße and Vít Workspace. Use the + button to send an invoice.] [sub: Invoices]", + "2006 Groß Straße's expenses [alt: Vít Workspace • This is where Groß Straße will submit expenses to Vít Workspace. Just use the + button.] [sub: Vít Workspace]", ], "selfDMChat": null, "userToInvite": null, @@ -1399,12 +1399,12 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterLo "1003 Vít Novák [alt: vit.novak@example.com] [accID: 3]", ], "recentReports": [ - "2012 #vít-room [rbr] [alt: Acme Holdings • This chat room is for anything #vít-room related.]", - "2011 #announce [alt: Vít Workspace • This chat is with everyone in Vít Workspace. Use it for the most important announcements.]", - "2006 Groß Straße's expenses [alt: Vít Workspace • This is where Groß Straße will submit expenses to Vít Workspace. Just use the + button.]", - "2005 Current User's expenses [alt: Vít Workspace • This is where Current User will submit expenses to Vít Workspace. Just use the + button.]", + "2012 #vít-room [rbr] [alt: Acme Holdings • This chat room is for anything #vít-room related.] [sub: Acme Holdings]", + "2011 #announce [alt: Vít Workspace • This chat is with everyone in Vít Workspace. Use it for the most important announcements.] [sub: Vít Workspace]", + "2006 Groß Straße's expenses [alt: Vít Workspace • This is where Groß Straße will submit expenses to Vít Workspace. Just use the + button.] [sub: Vít Workspace]", + "2005 Current User's expenses [alt: Vít Workspace • This is where Current User will submit expenses to Vít Workspace. Just use the + button.] [sub: Vít Workspace • Submits to Current User]", "2003 Weekend Hiking [alt: This chat is with José Ñúñez, Mary-Jane O'Brien, and Vít Novák.]", - "2010 #admins [alt: Vít Workspace • This chat is with Vít Workspace admin. Use it to chat about workspace setup and more.]", + "2010 #admins [alt: Vít Workspace • This chat is with Vít Workspace admin. Use it to chat about workspace setup and more.] [sub: Vít Workspace]", "1003 Vít Novák [alt: This chat is with Vít Novák.] [accID: 3]", ], "selfDMChat": null, @@ -1441,7 +1441,7 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterLo ], "recentReports": [ "2004 Current User (you) [alt: This is your personal space. Use it for notes, tasks, drafts, and reminders.] [accID: 1]", - "2013 #orphan-room [alt: Gone Workspace • This chat room is for anything #orphan-room related.]", + "2013 #orphan-room [alt: Gone Workspace • This chat room is for anything #orphan-room related.] [sub: Gone Workspace]", "1124 Ada Cole [alt: This chat is with Ada Cole.] [accID: 124]", "1103 Dan Ames [alt: This chat is with Dan Ames.] [accID: 103]", "1123 Lou Byrd [alt: This chat is with Lou Byrd.] [accID: 123]", @@ -1451,10 +1451,10 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterLo "1100 Ada Ames [alt: This chat is with Ada Ames.] [accID: 100]", "1120 Ivy Byrd [alt: This chat is with Ivy Byrd.] [accID: 120]", "1015 Alex Smith Jr. [alt: This chat is with Alex Smith Jr..] [accID: 15]", - "2012 #vít-room [rbr] [alt: Acme Holdings • This chat room is for anything #vít-room related.]", + "2012 #vít-room [rbr] [alt: Acme Holdings • This chat room is for anything #vít-room related.] [sub: Acme Holdings]", "1139 Dan Dunn [alt: This chat is with Dan Dunn.] [accID: 139]", "1118 Gus Byrd [alt: This chat is with Gus Byrd.] [accID: 118]", - "2007 Groß Straße [alt: Vít Workspace • This chat is for invoices between Groß Straße and Vít Workspace. Use the + button to send an invoice.]", + "2007 Groß Straße [alt: Vít Workspace • This chat is for invoices between Groß Straße and Vít Workspace. Use the + button to send an invoice.] [sub: Invoices]", "1138 Cyd Dunn [alt: This chat is with Cyd Dunn.] [accID: 138]", "1117 Fay Byrd [alt: This chat is with Fay Byrd.] [accID: 117]", "1012 first.last+tag@x.com [alt: This chat is with first.last+tag@x.com.] [accID: 12]", @@ -1470,7 +1470,7 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterLo "hasMore": false, "personalDetails": [], "recentReports": [ - "2012 #vít-room [rbr] [alt: Acme Holdings • This chat room is for anything #vít-room related.]", + "2012 #vít-room [rbr] [alt: Acme Holdings • This chat room is for anything #vít-room related.] [sub: Acme Holdings]", ], "selfDMChat": null, "userToInvite": null, @@ -1494,7 +1494,7 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterLo "hasMore": false, "personalDetails": [], "recentReports": [ - "2010 #admins [alt: Vít Workspace • This chat is with Vít Workspace admin. Use it to chat about workspace setup and more.]", + "2010 #admins [alt: Vít Workspace • This chat is with Vít Workspace admin. Use it to chat about workspace setup and more.] [sub: Vít Workspace]", ], "selfDMChat": null, "userToInvite": null, @@ -1757,7 +1757,7 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterLo "1103 Dan Ames [alt: This chat is with Dan Ames.] [accID: 103]", "1102 Cyd Ames [alt: This chat is with Cyd Ames.] [accID: 102]", "1100 Ada Ames [alt: This chat is with Ada Ames.] [accID: 100]", - "2012 #vít-room [rbr] [alt: Acme Holdings • This chat room is for anything #vít-room related.]", + "2012 #vít-room [rbr] [alt: Acme Holdings • This chat room is for anything #vít-room related.] [sub: Acme Holdings]", "1111 Lou Ames [alt: This chat is with Lou Ames.] [accID: 111]", "1109 Jon Ames [alt: This chat is with Jon Ames.] [accID: 109]", "1108 Ivy Ames [alt: This chat is with Ivy Ames.] [accID: 108]", @@ -1796,7 +1796,7 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterLo "hasMore": false, "personalDetails": [], "recentReports": [ - "2011 #announce [alt: Vít Workspace • This chat is with everyone in Vít Workspace. Use it for the most important announcements.]", + "2011 #announce [alt: Vít Workspace • This chat is with everyone in Vít Workspace. Use it for the most important announcements.] [sub: Vít Workspace]", ], "selfDMChat": null, "userToInvite": null, @@ -1835,7 +1835,7 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterLo "hasMore": false, "personalDetails": [], "recentReports": [ - "2012 #vít-room [rbr] [alt: Acme Holdings • This chat room is for anything #vít-room related.]", + "2012 #vít-room [rbr] [alt: Acme Holdings • This chat room is for anything #vít-room related.] [sub: Acme Holdings]", ], "selfDMChat": null, "userToInvite": null, @@ -1846,11 +1846,11 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterLo "hasMore": false, "personalDetails": [], "recentReports": [ - "2013 #orphan-room [alt: Gone Workspace • This chat room is for anything #orphan-room related.]", - "2011 #announce [alt: Vít Workspace • This chat is with everyone in Vít Workspace. Use it for the most important announcements.]", - "2006 Groß Straße's expenses [alt: Vít Workspace • This is where Groß Straße will submit expenses to Vít Workspace. Just use the + button.]", - "2005 Current User's expenses [alt: Vít Workspace • This is where Current User will submit expenses to Vít Workspace. Just use the + button.]", - "2010 #admins [alt: Vít Workspace • This chat is with Vít Workspace admin. Use it to chat about workspace setup and more.]", + "2013 #orphan-room [alt: Gone Workspace • This chat room is for anything #orphan-room related.] [sub: Gone Workspace]", + "2011 #announce [alt: Vít Workspace • This chat is with everyone in Vít Workspace. Use it for the most important announcements.] [sub: Vít Workspace]", + "2006 Groß Straße's expenses [alt: Vít Workspace • This is where Groß Straße will submit expenses to Vít Workspace. Just use the + button.] [sub: Vít Workspace]", + "2005 Current User's expenses [alt: Vít Workspace • This is where Current User will submit expenses to Vít Workspace. Just use the + button.] [sub: Vít Workspace • Submits to Current User]", + "2010 #admins [alt: Vít Workspace • This chat is with Vít Workspace admin. Use it to chat about workspace setup and more.] [sub: Vít Workspace]", ], "selfDMChat": null, "userToInvite": null, @@ -1912,8 +1912,8 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterLo "5 Groß Straße [alt: strasse@example.com] [accID: 5]", ], "recentReports": [ - "2007 Groß Straße [alt: Vít Workspace • This chat is for invoices between Groß Straße and Vít Workspace. Use the + button to send an invoice.]", - "2006 Groß Straße's expenses [alt: Vít Workspace • This is where Groß Straße will submit expenses to Vít Workspace. Just use the + button.]", + "2007 Groß Straße [alt: Vít Workspace • This chat is for invoices between Groß Straße and Vít Workspace. Use the + button to send an invoice.] [sub: Invoices]", + "2006 Groß Straße's expenses [alt: Vít Workspace • This is where Groß Straße will submit expenses to Vít Workspace. Just use the + button.] [sub: Vít Workspace]", ], "selfDMChat": null, "userToInvite": null, @@ -1949,12 +1949,12 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterLo "1003 Vít Novák [alt: vit.novak@example.com] [accID: 3]", ], "recentReports": [ - "2012 #vít-room [rbr] [alt: Acme Holdings • This chat room is for anything #vít-room related.]", - "2011 #announce [alt: Vít Workspace • This chat is with everyone in Vít Workspace. Use it for the most important announcements.]", - "2006 Groß Straße's expenses [alt: Vít Workspace • This is where Groß Straße will submit expenses to Vít Workspace. Just use the + button.]", - "2005 Current User's expenses [alt: Vít Workspace • This is where Current User will submit expenses to Vít Workspace. Just use the + button.]", + "2012 #vít-room [rbr] [alt: Acme Holdings • This chat room is for anything #vít-room related.] [sub: Acme Holdings]", + "2011 #announce [alt: Vít Workspace • This chat is with everyone in Vít Workspace. Use it for the most important announcements.] [sub: Vít Workspace]", + "2006 Groß Straße's expenses [alt: Vít Workspace • This is where Groß Straße will submit expenses to Vít Workspace. Just use the + button.] [sub: Vít Workspace]", + "2005 Current User's expenses [alt: Vít Workspace • This is where Current User will submit expenses to Vít Workspace. Just use the + button.] [sub: Vít Workspace • Submits to Current User]", "2003 Weekend Hiking [alt: This chat is with José Ñúñez, Mary-Jane O'Brien, and Vít Novák.]", - "2010 #admins [alt: Vít Workspace • This chat is with Vít Workspace admin. Use it to chat about workspace setup and more.]", + "2010 #admins [alt: Vít Workspace • This chat is with Vít Workspace admin. Use it to chat about workspace setup and more.] [sub: Vít Workspace]", "1003 Vít Novák [alt: This chat is with Vít Novák.] [accID: 3]", ], "selfDMChat": null, @@ -1966,10 +1966,10 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterLo "hasMore": false, "personalDetails": [], "recentReports": [ - "2011 #announce [alt: Vít Workspace • This chat is with everyone in Vít Workspace. Use it for the most important announcements.]", - "2006 Groß Straße's expenses [alt: Vít Workspace • This is where Groß Straße will submit expenses to Vít Workspace. Just use the + button.]", - "2005 Current User's expenses [alt: Vít Workspace • This is where Current User will submit expenses to Vít Workspace. Just use the + button.]", - "2010 #admins [alt: Vít Workspace • This chat is with Vít Workspace admin. Use it to chat about workspace setup and more.]", + "2011 #announce [alt: Vít Workspace • This chat is with everyone in Vít Workspace. Use it for the most important announcements.] [sub: Vít Workspace]", + "2006 Groß Straße's expenses [alt: Vít Workspace • This is where Groß Straße will submit expenses to Vít Workspace. Just use the + button.] [sub: Vít Workspace]", + "2005 Current User's expenses [alt: Vít Workspace • This is where Current User will submit expenses to Vít Workspace. Just use the + button.] [sub: Vít Workspace • Submits to Current User]", + "2010 #admins [alt: Vít Workspace • This chat is with Vít Workspace admin. Use it to chat about workspace setup and more.] [sub: Vít Workspace]", ], "selfDMChat": null, "userToInvite": null, @@ -1980,11 +1980,11 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterLo "hasMore": false, "personalDetails": [], "recentReports": [ - "2013 #orphan-room [alt: Gone Workspace • This chat room is for anything #orphan-room related.]", - "2011 #announce [alt: Vít Workspace • This chat is with everyone in Vít Workspace. Use it for the most important announcements.]", - "2006 Groß Straße's expenses [alt: Vít Workspace • This is where Groß Straße will submit expenses to Vít Workspace. Just use the + button.]", - "2005 Current User's expenses [alt: Vít Workspace • This is where Current User will submit expenses to Vít Workspace. Just use the + button.]", - "2010 #admins [alt: Vít Workspace • This chat is with Vít Workspace admin. Use it to chat about workspace setup and more.]", + "2013 #orphan-room [alt: Gone Workspace • This chat room is for anything #orphan-room related.] [sub: Gone Workspace]", + "2011 #announce [alt: Vít Workspace • This chat is with everyone in Vít Workspace. Use it for the most important announcements.] [sub: Vít Workspace]", + "2006 Groß Straße's expenses [alt: Vít Workspace • This is where Groß Straße will submit expenses to Vít Workspace. Just use the + button.] [sub: Vít Workspace]", + "2005 Current User's expenses [alt: Vít Workspace • This is where Current User will submit expenses to Vít Workspace. Just use the + button.] [sub: Vít Workspace • Submits to Current User]", + "2010 #admins [alt: Vít Workspace • This chat is with Vít Workspace admin. Use it to chat about workspace setup and more.] [sub: Vít Workspace]", ], "selfDMChat": null, "userToInvite": null, From 26d3f16cea287ef165b49efb30b296d39d38b1c3 Mon Sep 17 00:00:00 2001 From: Jakub Magiera Date: Fri, 23 Oct 2026 12:14:38 +0200 Subject: [PATCH 08/13] Remove unused prefer-nullish-coalescing disable directive --- tests/utils/searchParity/generateSearchFixture.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/utils/searchParity/generateSearchFixture.ts b/tests/utils/searchParity/generateSearchFixture.ts index f02cab04e44f..7e30e074435c 100644 --- a/tests/utils/searchParity/generateSearchFixture.ts +++ b/tests/utils/searchParity/generateSearchFixture.ts @@ -330,7 +330,6 @@ function generateSearchFixture(): SearchFixture { continue; } const contact = personalDetails[accountID]; - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- an empty displayName must fall back too const name = contact?.displayName || FORMATTED_PHONE_LOGINS[contact?.login ?? ''] || (contact?.login ?? ''); dmReportIDByAccountID[accountID] = addReport( DM_REPORT_ID_OFFSET + accountID, From d72145e878e35d1147ab1783708d5526398e3e20 Mon Sep 17 00:00:00 2001 From: Jakub Magiera Date: Mon, 26 Oct 2026 22:56:20 +0100 Subject: [PATCH 09/13] Clean up search parity harness and record displayed rows --- .../unit/OptionsListUtilsSearchParityTest.ts | 146 ++--- .../OptionsListUtilsSearchParityTest.ts.snap | 613 +++++------------- .../searchParity/generateSearchFixture.ts | 1 + 3 files changed, 223 insertions(+), 537 deletions(-) diff --git a/tests/unit/OptionsListUtilsSearchParityTest.ts b/tests/unit/OptionsListUtilsSearchParityTest.ts index 9585c92f9902..69543060c8bd 100644 --- a/tests/unit/OptionsListUtilsSearchParityTest.ts +++ b/tests/unit/OptionsListUtilsSearchParityTest.ts @@ -1,7 +1,7 @@ +// cspell:disable -- names and queries deliberately use diacritics, non-Latin scripts and misspelled forms import SEARCH_ROUTER_OPTIONS_CONFIG from '@components/Search/SearchRouter/searchRouterOptionsConfig'; -// cspell:disable -- names and queries deliberately use diacritics, non-Latin scripts and misspelled forms -import type {OptionList, Options, SearchOptionData} from '@libs/OptionsListUtils'; +import type {OptionList, SearchOptionData} from '@libs/OptionsListUtils'; import { clearAlternateTextCache, clearFilteredOptionListCache, @@ -53,64 +53,66 @@ function clearSearchCaches() { /** * Serializes options into " [flags] [alt: ...] [sub: ...] [accID: ...]" lines to pin option ordering and content rather than rendering details. - * Flags are [selected], [disabled], [bold], [gbr], [rbr] and [archived], where [archived] surfaces private_isArchived, which drives ranking and the archived preview text. - * Invited users include their login when different from text to distinguish email from phone invites. - * [sub: ...] is only emitted for rooms and workspace chats, because that is what search matches for them. Fields the SearchRouter never reads (participantsList, icons, policyID, ownerAccountID, item) are deliberately left out. + * [sub: ...] is only emitted for rooms and workspace chats, because that is what search matches for them. + * Fields the SearchRouter never reads (participantsList, icons, policyID, ownerAccountID, item) are deliberately left out. */ -function createOptionSerializer() { - function serializeOption(option: SearchOptionData | null | undefined, {isUserToInvite = false}: {isUserToInvite?: boolean} = {}): string | null { - if (!option) { - return null; - } - const flags = [ - option.isSelected ? ' [selected]' : '', - option.isDisabled ? ' [disabled]' : '', - option.isBold ? ' [bold]' : '', - option.brickRoadIndicator === CONST.BRICK_ROAD_INDICATOR_STATUS.INFO ? ' [gbr]' : '', - option.brickRoadIndicator === CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR ? ' [rbr]' : '', - option.private_isArchived ? ' [archived]' : '', - ].join(''); - const invitedLogin = isUserToInvite && option.login !== option.text ? ` ${option.login ?? ''}` : ''; - const altText = option.alternateText ? ` [alt: ${option.alternateText}]` : ''; - const subtitle = (option.isChatRoom || option.isPolicyExpenseChat) && option.subtitle ? ` [sub: ${option.subtitle}]` : ''; - const accountID = option.accountID ? ` [accID: ${option.accountID}]` : ''; - return `${option.keyForList ?? ''} ${option.text ?? ''}${flags}${altText}${subtitle}${accountID}${invitedLogin}`; +function serializeOption(option: SearchOptionData | null | undefined, {isUserToInvite = false}: {isUserToInvite?: boolean} = {}): string | null { + if (!option) { + return null; } + const flags = [ + option.isSelected ? ' [selected]' : '', + option.isDisabled ? ' [disabled]' : '', + option.isBold ? ' [bold]' : '', + option.brickRoadIndicator === CONST.BRICK_ROAD_INDICATOR_STATUS.INFO ? ' [gbr]' : '', + option.brickRoadIndicator === CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR ? ' [rbr]' : '', + option.private_isArchived ? ' [archived]' : '', + ].join(''); + const invitedLogin = isUserToInvite && option.login !== option.text ? ` ${option.login ?? ''}` : ''; + const altText = option.alternateText ? ` [alt: ${option.alternateText}]` : ''; + const subtitle = (option.isChatRoom || option.isPolicyExpenseChat) && option.subtitle ? ` [sub: ${option.subtitle}]` : ''; + const accountID = option.accountID ? ` [accID: ${option.accountID}]` : ''; + return `${option.keyForList ?? ''} ${option.text ?? ''}${flags}${altText}${subtitle}${accountID}${invitedLogin}`; +} - function serializeResult({options, hasMore}: OptionsResult | {options: Options; hasMore?: boolean}) { - return { - recentReports: options.recentReports.map((option) => serializeOption(option)), - personalDetails: options.personalDetails.map((option) => serializeOption(option)), - workspaceChats: options.workspaceChats?.map((option) => serializeOption(option)), - selfDMChat: serializeOption(options.selfDMChat), - currentUserOption: serializeOption(options.currentUserOption), - userToInvite: serializeOption(options.userToInvite, { - isUserToInvite: true, - }), - hasMore: !!hasMore, - }; - } +/** What the screen shows: one flat list of rows, in order. */ +type DisplayedResult = {displayedRows: SearchOptionData[]; hasMore?: boolean}; - /** Preserves pipeline ordering, but sorts unordered collections to ensure deterministic snapshots. */ - function serializeOptionList(optionList: OptionList, {isOrderMeaningful}: {isOrderMeaningful: boolean}) { - const sortIfUnordered = (references: Array) => (isOrderMeaningful ? references : [...references].sort()); +function serializeResult(result: OptionsResult | DisplayedResult) { + if ('displayedRows' in result) { return { - reports: sortIfUnordered(optionList.reports.map((option) => serializeOption(option))), - // Shells are compared both as built and after hydration, so the shell/hydrator split stays invisible - personalDetails: optionList.personalDetails.map((shell) => `${shell.reportID} | ${shell.accountID} | ${shell.login ?? ''}`).sort(), - hydratedPersonalDetails: optionList.personalDetails.map((shell) => serializeOption(hydrateContactOption(shell))).sort(), + displayedRows: result.displayedRows.map((option) => serializeOption(option)), + hasMore: !!result.hasMore, }; } - - return {serializeOption, serializeResult, serializeOptionList}; + const {options, hasMore} = result; + return { + recentReports: options.recentReports.map((option) => serializeOption(option)), + personalDetails: options.personalDetails.map((option) => serializeOption(option)), + workspaceChats: options.workspaceChats?.map((option) => serializeOption(option)), + selfDMChat: serializeOption(options.selfDMChat), + currentUserOption: serializeOption(options.currentUserOption), + userToInvite: serializeOption(options.userToInvite, { + isUserToInvite: true, + }), + hasMore: !!hasMore, + }; } -function expectToMatchGoldenMaster(value: unknown) { - expect(value).toMatchSnapshot(); +/** Preserves pipeline ordering, but sorts unordered collections to ensure deterministic snapshots. */ +function serializeOptionList(optionList: OptionList, {isOrderMeaningful}: {isOrderMeaningful: boolean}) { + const sortIfUnordered = (references: Array) => (isOrderMeaningful ? references : [...references].sort()); + return { + reports: sortIfUnordered(optionList.reports.map((option) => serializeOption(option))), + // Shells are compared both as built and after hydration, so the shell/hydrator split stays invisible + personalDetails: optionList.personalDetails.map((shell) => `${shell.reportID} | ${shell.accountID} | ${shell.login ?? ''}`).sort(), + hydratedPersonalDetails: optionList.personalDetails.map((shell) => serializeOption(hydrateContactOption(shell))).sort(), + }; } /** - * Cap for the SearchRouter idle list. Production uses 100, which keeps all fixture reports. Setting this to 10 forces evictions. + * Cap for the SearchRouter idle list. The production cap (SEARCH_ROUTER_OPTIONS_CONFIG.maxRecentReports) keeps all fixture reports. Setting this to 10 forces evictions. + * The value is arbitrary: any cap below the fixture report count forces evictions, and 10 just keeps the recorded idle list short. No particular sort rule sits at the boundary. */ const SEARCH_ROUTER_IDLE_REPORTS_CAP = 10; @@ -166,7 +168,11 @@ function buildSearchRouterIdleOptionList(sourceFixture: SearchFixture, maxRecent // Passed to combineOrderingOfReportsAndPersonalDetails. Only affects workspace chat ordering, which displayed passes do not show. const ACTIVE_POLICY_ID = OWN_POLICY_ID; -type SearchCallSite = (options: OptionList, query: string) => OptionsResult; +type SearchCallSite = (options: OptionList, query: string) => OptionsResult | DisplayedResult; + +const CALL_SITE_NAMES = ['searchRouterLocal', 'searchRouterDisplayed'] as const; + +type SearchCallSiteName = (typeof CALL_SITE_NAMES)[number]; /** * Keeps getReportByID stable for identical fixture.reports references as useCallback(..., [allReports]) does in useFilteredOptions, @@ -252,10 +258,7 @@ function createCallSites(sourceFixture: SearchFixture) { if (!query.trim()) { return { - options: { - ...result.options, - recentReports: result.options.recentReports.slice(0, CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMOUNT_OF_SUGGESTIONS), - }, + displayedRows: result.options.recentReports.slice(0, CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMOUNT_OF_SUGGESTIONS), hasMore: result.hasMore, }; } @@ -271,11 +274,7 @@ function createCallSites(sourceFixture: SearchFixture) { } return { - options: { - ...result.options, - recentReports: displayedReports.slice(0, CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMOUNT_OF_SUGGESTIONS), - personalDetails: ordered.personalDetails, - }, + displayedRows: displayedReports.slice(0, CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMOUNT_OF_SUGGESTIONS), hasMore: result.hasMore, }; }, @@ -284,26 +283,16 @@ function createCallSites(sourceFixture: SearchFixture) { return {searchCallSites, serverPassConfig}; } -// Extracts call site names for test parameterization. Actual instances are created in beforeAll once Onyx derives report values. -const CALL_SITE_NAMES = Object.keys(createCallSites(fixture).searchCallSites); - type OptionListKind = 'searching' | 'searchRouterIdle'; // Maps call sites to the option list used when query is blank (the small idle list, or the searching list for any other query). -const BLANK_QUERY_OPTION_LIST: Record = { +const BLANK_QUERY_OPTION_LIST: Record = { searchRouterLocal: 'searchRouterIdle', searchRouterDisplayed: 'searchRouterIdle', }; -function getOptionListKind(callSite: string, query: string): OptionListKind { - if (query.trim()) { - return 'searching'; - } - const kind = BLANK_QUERY_OPTION_LIST[callSite]; - if (!kind) { - throw new Error(`Call site ${callSite} does not say which list it searches with a blank query`); - } - return kind; +function getOptionListKind(callSite: SearchCallSiteName, query: string): OptionListKind { + return query.trim() ? 'searching' : BLANK_QUERY_OPTION_LIST[callSite]; } // Reports returned by the SearchAutocompleteList server pass in Auth ranking order, including a self-DM that the pass drops and reports the canonical filter rejects. @@ -353,7 +342,6 @@ function withFocusedReport(reportID: string, run: () => T): T { /** Computes every recorded pass over the fixture, starting from cold caches. */ function computeGoldenMaster(sourceFixture: SearchFixture) { - const {serializeOption, serializeResult, serializeOptionList} = createOptionSerializer(); const {searchCallSites, serverPassConfig} = createCallSites(sourceFixture); clearSearchCaches(); @@ -376,9 +364,9 @@ function computeGoldenMaster(sourceFixture: SearchFixture) { searchRouterIdle, }; const callSites = Object.fromEntries( - Object.entries(searchCallSites).map(([callSite, run]) => [ + CALL_SITE_NAMES.map((callSite) => [ callSite, - Object.fromEntries(SEARCH_QUERIES.map((query) => [query, serializeResult(run(optionListsByKind[getOptionListKind(callSite, query)], query))])), + Object.fromEntries(SEARCH_QUERIES.map((query) => [query, serializeResult(searchCallSites[callSite](optionListsByKind[getOptionListKind(callSite, query)], query))])), ]), ); @@ -440,28 +428,24 @@ describe('OptionsListUtils search parity (golden master)', () => { goldenMaster = computeGoldenMaster(fixture); }); - beforeEach(() => { - clearSearchCaches(); - }); - it('builds searching and SearchRouter idle option lists that match the golden master', () => { // Given the fixture account with every report type and contact quirk // When the option list is built for search (every report) and for the SearchRouter idle state (top-N, contacts deferred) at the small cap and the production cap // Then every option keeps its identity, flags and, where it matters, its order - expectToMatchGoldenMaster(goldenMaster.optionLists); + expect(goldenMaster.optionLists).toMatchSnapshot(); }); it.each(CALL_SITE_NAMES)('call site %s matches the golden master for every query', (callSite) => { // Given a production getSearchOptions config and the option list it searches (the full list, or its idle list while the query is blank) // When every query in the matrix runs through it // Then the sections and their order match the recorded golden master - expectToMatchGoldenMaster(goldenMaster.callSites[callSite]); + expect(goldenMaster.callSites[callSite]).toMatchSnapshot(); }); it('SearchRouter blank query and server-ID pass match the golden master', () => { // Given the SearchRouter idle list at the production cap, the SearchAutocompleteList server pass (a fixed set of reports in Auth's order, no query, including rejected and unknown IDs that it drops) and the SearchRouter opened over a focused empty thread or empty submitted report // When they run through their production configs, the focused ones for a blank query and for a query matching the focused report // Then all match the recorded golden master, with the focused report shown only while it is focused - expectToMatchGoldenMaster(goldenMaster.passes); + expect(goldenMaster.passes).toMatchSnapshot(); }); }); diff --git a/tests/unit/__snapshots__/OptionsListUtilsSearchParityTest.ts.snap b/tests/unit/__snapshots__/OptionsListUtilsSearchParityTest.ts.snap index d8277c1305bd..3d36a5061e9d 100644 --- a/tests/unit/__snapshots__/OptionsListUtilsSearchParityTest.ts.snap +++ b/tests/unit/__snapshots__/OptionsListUtilsSearchParityTest.ts.snap @@ -5,10 +5,7 @@ exports[`OptionsListUtils search parity (golden master) SearchRouter blank query "focusedReport": { "Empty submitted report": { "blank": { - "currentUserOption": null, - "hasMore": true, - "personalDetails": [], - "recentReports": [ + "displayedRows": [ "2004 Current User (you) [alt: This is your personal space. Use it for notes, tasks, drafts, and reminders.] [accID: 1]", "2013 #orphan-room [alt: Gone Workspace • This chat room is for anything #orphan-room related.] [sub: Gone Workspace]", "1124 Ada Cole [alt: This chat is with Ada Cole.] [accID: 124]", @@ -30,30 +27,20 @@ exports[`OptionsListUtils search parity (golden master) SearchRouter blank query "1138 Cyd Dunn [alt: This chat is with Cyd Dunn.] [accID: 138]", "1117 Fay Byrd [alt: This chat is with Fay Byrd.] [accID: 117]", ], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], + "hasMore": true, }, "empty": { - "currentUserOption": null, - "hasMore": false, - "personalDetails": [], - "recentReports": [ + "displayedRows": [ "2032 Empty submitted report [alt: No activity yet] [accID: 1]", "2025 Empty pinned thread [alt: No activity yet]", "2024 Empty thread with a draft [alt: No activity yet]", ], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], + "hasMore": false, }, }, "Empty thread": { "blank": { - "currentUserOption": null, - "hasMore": true, - "personalDetails": [], - "recentReports": [ + "displayedRows": [ "2004 Current User (you) [alt: This is your personal space. Use it for notes, tasks, drafts, and reminders.] [accID: 1]", "2013 #orphan-room [alt: Gone Workspace • This chat room is for anything #orphan-room related.] [sub: Gone Workspace]", "1124 Ada Cole [alt: This chat is with Ada Cole.] [accID: 124]", @@ -75,30 +62,20 @@ exports[`OptionsListUtils search parity (golden master) SearchRouter blank query "1117 Fay Byrd [alt: This chat is with Fay Byrd.] [accID: 117]", "1012 first.last+tag@x.com [alt: This chat is with first.last+tag@x.com.] [accID: 12]", ], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], + "hasMore": true, }, "empty": { - "currentUserOption": null, - "hasMore": false, - "personalDetails": [], - "recentReports": [ + "displayedRows": [ "2025 Empty pinned thread [alt: No activity yet]", "2024 Empty thread with a draft [alt: No activity yet]", "2023 Empty thread [alt: No activity yet]", ], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], + "hasMore": false, }, }, }, "searchRouterBlankAtProductionCap": { - "currentUserOption": null, - "hasMore": true, - "personalDetails": [], - "recentReports": [ + "displayedRows": [ "2004 Current User (you) [alt: This is your personal space. Use it for notes, tasks, drafts, and reminders.] [accID: 1]", "2013 #orphan-room [alt: Gone Workspace • This chat room is for anything #orphan-room related.] [sub: Gone Workspace]", "1124 Ada Cole [alt: This chat is with Ada Cole.] [accID: 124]", @@ -120,9 +97,7 @@ exports[`OptionsListUtils search parity (golden master) SearchRouter blank query "1117 Fay Byrd [alt: This chat is with Fay Byrd.] [accID: 117]", "1012 first.last+tag@x.com [alt: This chat is with first.last+tag@x.com.] [accID: 12]", ], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], + "hasMore": true, }, "serverPass": [ "2010 #admins [alt: Vít Workspace • This chat is with Vít Workspace admin. Use it to chat about workspace setup and more.] [sub: Vít Workspace]", @@ -424,10 +399,20 @@ exports[`OptionsListUtils search parity (golden master) builds searching and Sea exports[`OptionsListUtils search parity (golden master) call site searchRouterDisplayed matches the golden master for every query 1`] = ` { "": { - "currentUserOption": null, + "displayedRows": [ + "2004 Current User (you) [alt: This is your personal space. Use it for notes, tasks, drafts, and reminders.] [accID: 1]", + "2013 #orphan-room [alt: Gone Workspace • This chat room is for anything #orphan-room related.] [sub: Gone Workspace]", + "1124 Ada Cole [alt: This chat is with Ada Cole.] [accID: 124]", + "1103 Dan Ames [alt: This chat is with Dan Ames.] [accID: 103]", + "1019 No Login [alt: This chat is with No Login.] [accID: 19]", + "1123 Lou Byrd [alt: This chat is with Lou Byrd.] [accID: 123]", + "1102 Cyd Ames [alt: This chat is with Cyd Ames.] [accID: 102]", + "1018 Domain Admin [alt: This chat is with Domain Admin.] [accID: 18]", + ], "hasMore": false, - "personalDetails": [], - "recentReports": [ + }, + " ": { + "displayedRows": [ "2004 Current User (you) [alt: This is your personal space. Use it for notes, tasks, drafts, and reminders.] [accID: 1]", "2013 #orphan-room [alt: Gone Workspace • This chat room is for anything #orphan-room related.] [sub: Gone Workspace]", "1124 Ada Cole [alt: This chat is with Ada Cole.] [accID: 124]", @@ -437,88 +422,48 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterDi "1102 Cyd Ames [alt: This chat is with Cyd Ames.] [accID: 102]", "1018 Domain Admin [alt: This chat is with Domain Admin.] [accID: 18]", ], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], + "hasMore": false, }, " alex.smith@example.com ": { - "currentUserOption": null, - "hasMore": false, - "personalDetails": [], - "recentReports": [ + "displayedRows": [ "2001 Alex Smith [alt: alex.smith@example.com] [accID: 14]", ], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], + "hasMore": false, }, "(500) 555": { - "currentUserOption": null, - "hasMore": false, - "personalDetails": [], - "recentReports": [ + "displayedRows": [ "1013 (500) 555-0006 [alt: This chat is with (500) 555-0006.] [accID: 13]", ], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], + "hasMore": false, }, "*": { - "currentUserOption": null, + "displayedRows": [], "hasMore": false, - "personalDetails": [], - "recentReports": [], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], }, "+1 650 555 0100": { - "currentUserOption": null, - "hasMore": false, - "personalDetails": [], - "recentReports": [ + "displayedRows": [ "1100537387 +1 650 555 0100 [alt: +1 650 555 0100] [accID: 1100537387]", ], - "selfDMChat": null, - "userToInvite": "1100537387 +1 650 555 0100 [alt: +1 650 555 0100] [accID: 1100537387]", - "workspaceChats": [], + "hasMore": false, }, "+15005550006": { - "currentUserOption": null, - "hasMore": false, - "personalDetails": [], - "recentReports": [ + "displayedRows": [ "1013 (500) 555-0006 [alt: This chat is with (500) 555-0006.] [accID: 13]", ], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], + "hasMore": false, }, ".*": { - "currentUserOption": null, + "displayedRows": [], "hasMore": false, - "personalDetails": [], - "recentReports": [], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], }, "5550006": { - "currentUserOption": null, - "hasMore": false, - "personalDetails": [], - "recentReports": [ + "displayedRows": [ "1013 (500) 555-0006 [alt: This chat is with (500) 555-0006.] [accID: 13]", ], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], + "hasMore": false, }, "@example": { - "currentUserOption": null, - "hasMore": true, - "personalDetails": [], - "recentReports": [ + "displayedRows": [ "2004 Current User (you) [alt: This is your personal space. Use it for notes, tasks, drafts, and reminders.] [accID: 1]", "2001 Alex Smith [alt: alex.smith@example.com] [accID: 14]", "1124 Ada Cole [alt: This chat is with Ada Cole.] [accID: 124]", @@ -540,64 +485,39 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterDi "1010 Bob Builder [bold] [alt: See you at the trailhead] [accID: 10]", "1135 Lou Cole [alt: This chat is with Lou Cole.] [accID: 135]", ], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], + "hasMore": true, }, "CURRENT.USER@EXAMPLE.COM": { - "currentUserOption": "1 Current User [alt: current.user@example.com] [accID: 1]", - "hasMore": false, - "personalDetails": [], - "recentReports": [ + "displayedRows": [ "2004 Current User (you) [alt: This is your personal space. Use it for notes, tasks, drafts, and reminders.] [accID: 1]", "2031 Offsite expenses [alt: No activity yet] [accID: 1]", "2003 Weekend Hiking [alt: This chat is with José Ñúñez, Mary-Jane O'Brien, and Vít Novák.]", ], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], + "hasMore": false, }, "Groß": { - "currentUserOption": null, - "hasMore": false, - "personalDetails": [], - "recentReports": [ + "displayedRows": [ "5 Groß Straße [alt: strasse@example.com] [accID: 5]", "2006 Groß Straße's expenses [alt: Vít Workspace • This is where Groß Straße will submit expenses to Vít Workspace. Just use the + button.] [sub: Vít Workspace]", "2007 Groß Straße [alt: Vít Workspace • This chat is for invoices between Groß Straße and Vít Workspace. Use the + button to send an invoice.] [sub: Invoices]", ], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], + "hasMore": false, }, "JOSÉ": { - "currentUserOption": null, - "hasMore": false, - "personalDetails": [], - "recentReports": [ + "displayedRows": [ "2020 José Ñúñez [bold] [alt: Packing list] [accID: 2]", "2003 Weekend Hiking [alt: This chat is with José Ñúñez, Mary-Jane O'Brien, and Vít Novák.]", ], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], + "hasMore": false, }, "René": { - "currentUserOption": null, - "hasMore": false, - "personalDetails": [], - "recentReports": [ + "displayedRows": [ "20 René Dupont [alt: rene@example.com] [accID: 20]", ], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], + "hasMore": false, }, "Vít": { - "currentUserOption": null, - "hasMore": false, - "personalDetails": [], - "recentReports": [ + "displayedRows": [ "1003 Vít Novák [alt: This chat is with Vít Novák.] [accID: 3]", "2006 Groß Straße's expenses [alt: Vít Workspace • This is where Groß Straße will submit expenses to Vít Workspace. Just use the + button.] [sub: Vít Workspace]", "2005 Current User's expenses [alt: Vít Workspace • This is where Current User will submit expenses to Vít Workspace. Just use the + button.] [sub: Vít Workspace • Submits to Current User]", @@ -606,33 +526,18 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterDi "2011 #announce [alt: Vít Workspace • This chat is with everyone in Vít Workspace. Use it for the most important announcements.] [sub: Vít Workspace]", "2010 #admins [alt: Vít Workspace • This chat is with Vít Workspace admin. Use it to chat about workspace setup and more.] [sub: Vít Workspace]", ], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], + "hasMore": false, }, "[": { - "currentUserOption": null, + "displayedRows": [], "hasMore": false, - "personalDetails": [], - "recentReports": [], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], }, "\\": { - "currentUserOption": null, + "displayedRows": [], "hasMore": false, - "personalDetails": [], - "recentReports": [], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], }, "a": { - "currentUserOption": null, - "hasMore": true, - "personalDetails": [], - "recentReports": [ + "displayedRows": [ "2004 Current User (you) [alt: This is your personal space. Use it for notes, tasks, drafts, and reminders.] [accID: 1]", "2001 Alex Smith [alt: alex.smith@example.com] [accID: 14]", "1124 Ada Cole [alt: This chat is with Ada Cole.] [accID: 124]", @@ -654,133 +559,78 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterDi "1112 Ada Byrd [alt: ada.byrd.12@example.com] [accID: 112]", "2013 #orphan-room [alt: Gone Workspace • This chat room is for anything #orphan-room related.] [sub: Gone Workspace]", ], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], + "hasMore": true, }, "acme": { - "currentUserOption": null, - "hasMore": false, - "personalDetails": [], - "recentReports": [ + "displayedRows": [ "2012 #vít-room [rbr] [alt: Acme Holdings • This chat room is for anything #vít-room related.] [sub: Acme Holdings]", ], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], + "hasMore": false, }, "ada byrd": { - "currentUserOption": null, - "hasMore": false, - "personalDetails": [], - "recentReports": [ + "displayedRows": [ "1112 Ada Byrd [alt: This chat is with Ada Byrd.] [accID: 112]", ], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], + "hasMore": false, }, "admins": { - "currentUserOption": null, - "hasMore": false, - "personalDetails": [], - "recentReports": [ + "displayedRows": [ "2010 #admins [alt: Vít Workspace • This chat is with Vít Workspace admin. Use it to chat about workspace setup and more.] [sub: Vít Workspace]", ], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], + "hasMore": false, }, "al": { - "currentUserOption": null, - "hasMore": false, - "personalDetails": [], - "recentReports": [ + "displayedRows": [ "2001 Alex Smith [alt: alex.smith@example.com] [accID: 14]", "1015 Alex Smith Jr. [alt: This chat is with Alex Smith Jr..] [accID: 15]", "107 Hal Ames [alt: hal.ames.7@example.com] [accID: 107]", "119 Hal Byrd [alt: hal.byrd.19@example.com] [accID: 119]", "131 Hal Cole [alt: hal.cole.31@example.com] [accID: 131]", ], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], + "hasMore": false, }, "alex smith": { - "currentUserOption": null, - "hasMore": false, - "personalDetails": [], - "recentReports": [ + "displayedRows": [ "2001 Alex Smith [alt: alex.smith@example.com] [accID: 14]", "1015 Alex Smith Jr. [alt: This chat is with Alex Smith Jr..] [accID: 15]", ], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], + "hasMore": false, }, "anamaria": { - "currentUserOption": null, - "hasMore": false, - "personalDetails": [], - "recentReports": [ + "displayedRows": [ "1009 Ana​Maria [alt: This chat is with Ana​Maria.] [accID: 9]", ], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], + "hasMore": false, }, "bob builder": { - "currentUserOption": null, - "hasMore": false, - "personalDetails": [], - "recentReports": [ + "displayedRows": [ "1010 Bob Builder [bold] [alt: See you at the trailhead] [accID: 10]", ], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], + "hasMore": false, }, "cole lou": { - "currentUserOption": null, - "hasMore": false, - "personalDetails": [], - "recentReports": [ + "displayedRows": [ "1135 Lou Cole [alt: This chat is with Lou Cole.] [accID: 135]", ], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], + "hasMore": false, }, "concierge": { - "currentUserOption": null, - "hasMore": false, - "personalDetails": [], - "recentReports": [ + "displayedRows": [ "1021 Concierge [alt: Concierge can answer questions, update expenses, and more.] [accID: 21]", ], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], + "hasMore": false, }, "ex sm": { - "currentUserOption": null, - "hasMore": false, - "personalDetails": [], - "recentReports": [ + "displayedRows": [ "2001 Alex Smith [alt: alex.smith@example.com] [accID: 14]", "1015 Alex Smith Jr. [alt: This chat is with Alex Smith Jr..] [accID: 15]", "1013 (500) 555-0006 [alt: This chat is with (500) 555-0006.] [accID: 13]", "1004 İsmail Yılmaz [alt: Thanks!] [accID: 4]", ], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], + "hasMore": false, }, "example.com": { - "currentUserOption": null, - "hasMore": true, - "personalDetails": [], - "recentReports": [ + "displayedRows": [ "2004 Current User (you) [alt: This is your personal space. Use it for notes, tasks, drafts, and reminders.] [accID: 1]", "2001 Alex Smith [alt: alex.smith@example.com] [accID: 14]", "1124 Ada Cole [alt: This chat is with Ada Cole.] [accID: 124]", @@ -802,104 +652,59 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterDi "1010 Bob Builder [bold] [alt: See you at the trailhead] [accID: 10]", "1135 Lou Cole [alt: This chat is with Lou Cole.] [accID: 135]", ], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], + "hasMore": true, }, "firstlast": { - "currentUserOption": null, - "hasMore": false, - "personalDetails": [], - "recentReports": [ + "displayedRows": [ "1012 first.last+tag@x.com [alt: This chat is with first.last+tag@x.com.] [accID: 12]", ], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], + "hasMore": false, }, "hiking": { - "currentUserOption": null, + "displayedRows": [ + "2003 Weekend Hiking [alt: This chat is with José Ñúñez, Mary-Jane O'Brien, and Vít Novák.]", + ], "hasMore": false, - "personalDetails": [], - "recentReports": [ + }, + "ikin": { + "displayedRows": [ "2003 Weekend Hiking [alt: This chat is with José Ñúñez, Mary-Jane O'Brien, and Vít Novák.]", ], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], - }, - "ikin": { - "currentUserOption": null, "hasMore": false, - "personalDetails": [], - "recentReports": [ - "2003 Weekend Hiking [alt: This chat is with José Ñúñez, Mary-Jane O'Brien, and Vít Novák.]", - ], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], }, "ismail": { - "currentUserOption": null, - "hasMore": false, - "personalDetails": [], - "recentReports": [ + "displayedRows": [ "1004 İsmail Yılmaz [alt: Thanks!] [accID: 4]", ], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], + "hasMore": false, }, "jr alex": { - "currentUserOption": null, - "hasMore": false, - "personalDetails": [], - "recentReports": [ + "displayedRows": [ "1015 Alex Smith Jr. [alt: This chat is with Alex Smith Jr..] [accID: 15]", ], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], + "hasMore": false, }, "last+tag": { - "currentUserOption": null, - "hasMore": false, - "personalDetails": [], - "recentReports": [ + "displayedRows": [ "1012 first.last+tag@x.com [alt: This chat is with first.last+tag@x.com.] [accID: 12]", ], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], + "hasMore": false, }, "lukasz": { - "currentUserOption": null, - "hasMore": false, - "personalDetails": [], - "recentReports": [ + "displayedRows": [ "1006 Łukasz Wójcik [alt: This chat is with Łukasz Wójcik.] [accID: 6]", ], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], + "hasMore": false, }, "mary-jane": { - "currentUserOption": null, - "hasMore": false, - "personalDetails": [], - "recentReports": [ + "displayedRows": [ "2003 Weekend Hiking [alt: This chat is with José Ñúñez, Mary-Jane O'Brien, and Vít Novák.]", "2000 Mary-Jane O'Brien (archived) [archived] [alt: This chat room has been archived.] [accID: 11]", ], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], + "hasMore": false, }, "me": { - "currentUserOption": "1 Current User [alt: current.user@example.com] [accID: 1]", - "hasMore": true, - "personalDetails": [], - "recentReports": [ + "displayedRows": [ "2004 Current User (you) [alt: This is your personal space. Use it for notes, tasks, drafts, and reminders.] [accID: 1]", "1103 Dan Ames [alt: This chat is with Dan Ames.] [accID: 103]", "1102 Cyd Ames [alt: This chat is with Cyd Ames.] [accID: 102]", @@ -914,180 +719,105 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterDi "107 Hal Ames [alt: hal.ames.7@example.com] [accID: 107]", "2012 #vít-room [rbr] [alt: Acme Holdings • This chat room is for anything #vít-room related.] [sub: Acme Holdings]", ], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], + "hasMore": true, }, "mith": { - "currentUserOption": null, - "hasMore": false, - "personalDetails": [], - "recentReports": [ + "displayedRows": [ "2001 Alex Smith [alt: alex.smith@example.com] [accID: 14]", "1015 Alex Smith Jr. [alt: This chat is with Alex Smith Jr..] [accID: 15]", ], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], + "hasMore": false, }, "new.person@example.com": { - "currentUserOption": null, - "hasMore": false, - "personalDetails": [], - "recentReports": [ + "displayedRows": [ "301623904 new.person@example.com [alt: new.person@example.com] [accID: 301623904]", ], - "selfDMChat": null, - "userToInvite": "301623904 new.person@example.com [alt: new.person@example.com] [accID: 301623904]", - "workspaceChats": [], + "hasMore": false, }, "nounce": { - "currentUserOption": null, - "hasMore": false, - "personalDetails": [], - "recentReports": [ + "displayedRows": [ "2011 #announce [alt: Vít Workspace • This chat is with everyone in Vít Workspace. Use it for the most important announcements.] [sub: Vít Workspace]", ], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], + "hasMore": false, }, "nunez": { - "currentUserOption": null, - "hasMore": false, - "personalDetails": [], - "recentReports": [ + "displayedRows": [ "2020 José Ñúñez [bold] [alt: Packing list] [accID: 2]", "2003 Weekend Hiking [alt: This chat is with José Ñúñez, Mary-Jane O'Brien, and Vít Novák.]", ], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], + "hasMore": false, }, "obrien": { - "currentUserOption": null, - "hasMore": false, - "personalDetails": [], - "recentReports": [ + "displayedRows": [ "2003 Weekend Hiking [alt: This chat is with José Ñúñez, Mary-Jane O'Brien, and Vít Novák.]", "2000 Mary-Jane O'Brien (archived) [archived] [alt: This chat room has been archived.] [accID: 11]", ], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], + "hasMore": false, }, "oldings": { - "currentUserOption": null, - "hasMore": false, - "personalDetails": [], - "recentReports": [ + "displayedRows": [ "2012 #vít-room [rbr] [alt: Acme Holdings • This chat room is for anything #vít-room related.] [sub: Acme Holdings]", ], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], + "hasMore": false, }, "orkspace": { - "currentUserOption": null, - "hasMore": false, - "personalDetails": [], - "recentReports": [ + "displayedRows": [ "2006 Groß Straße's expenses [alt: Vít Workspace • This is where Groß Straße will submit expenses to Vít Workspace. Just use the + button.] [sub: Vít Workspace]", "2005 Current User's expenses [alt: Vít Workspace • This is where Current User will submit expenses to Vít Workspace. Just use the + button.] [sub: Vít Workspace • Submits to Current User]", "2013 #orphan-room [alt: Gone Workspace • This chat room is for anything #orphan-room related.] [sub: Gone Workspace]", "2011 #announce [alt: Vít Workspace • This chat is with everyone in Vít Workspace. Use it for the most important announcements.] [sub: Vít Workspace]", "2010 #admins [alt: Vít Workspace • This chat is with Vít Workspace admin. Use it to chat about workspace setup and more.] [sub: Vít Workspace]", ], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], + "hasMore": false, }, "ovak": { - "currentUserOption": null, - "hasMore": false, - "personalDetails": [], - "recentReports": [ + "displayedRows": [ "1003 Vít Novák [alt: This chat is with Vít Novák.] [accID: 3]", "2003 Weekend Hiking [alt: This chat is with José Ñúñez, Mary-Jane O'Brien, and Vít Novák.]", ], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], + "hasMore": false, }, "packing thread": { - "currentUserOption": null, - "hasMore": false, - "personalDetails": [], - "recentReports": [ + "displayedRows": [ "2026 Packing list [bold] [alt: José Ñúñez: Tent and stove]", ], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], + "hasMore": false, }, "rene": { - "currentUserOption": null, - "hasMore": false, - "personalDetails": [], - "recentReports": [ + "displayedRows": [ "20 René Dupont [alt: rene@example.com] [accID: 20]", ], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], + "hasMore": false, }, "smith jr": { - "currentUserOption": null, - "hasMore": false, - "personalDetails": [], - "recentReports": [ + "displayedRows": [ "1015 Alex Smith Jr. [alt: This chat is with Alex Smith Jr..] [accID: 15]", ], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], + "hasMore": false, }, "strasse": { - "currentUserOption": null, - "hasMore": false, - "personalDetails": [], - "recentReports": [ + "displayedRows": [ "5 Groß Straße [alt: strasse@example.com] [accID: 5]", "2006 Groß Straße's expenses [alt: Vít Workspace • This is where Groß Straße will submit expenses to Vít Workspace. Just use the + button.] [sub: Vít Workspace]", "2007 Groß Straße [alt: Vít Workspace • This chat is for invoices between Groß Straße and Vít Workspace. Use the + button to send an invoice.] [sub: Invoices]", ], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], + "hasMore": false, }, "thread": { - "currentUserOption": null, - "hasMore": false, - "personalDetails": [], - "recentReports": [ + "displayedRows": [ "2026 Packing list [bold] [alt: José Ñúñez: Tent and stove]", "2025 Empty pinned thread [alt: No activity yet]", "2024 Empty thread with a draft [alt: No activity yet]", "2021 Thread about the offsite [bold] [alt: José Ñúñez: Count me in]", ], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], + "hasMore": false, }, "to:al": { - "currentUserOption": null, + "displayedRows": [], "hasMore": false, - "personalDetails": [], - "recentReports": [], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], }, "vit": { - "currentUserOption": null, - "hasMore": false, - "personalDetails": [], - "recentReports": [ + "displayedRows": [ "1003 Vít Novák [alt: This chat is with Vít Novák.] [accID: 3]", "2006 Groß Straße's expenses [alt: Vít Workspace • This is where Groß Straße will submit expenses to Vít Workspace. Just use the + button.] [sub: Vít Workspace]", "2005 Current User's expenses [alt: Vít Workspace • This is where Current User will submit expenses to Vít Workspace. Just use the + button.] [sub: Vít Workspace • Submits to Current User]", @@ -1096,111 +826,64 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterDi "2011 #announce [alt: Vít Workspace • This chat is with everyone in Vít Workspace. Use it for the most important announcements.] [sub: Vít Workspace]", "2010 #admins [alt: Vít Workspace • This chat is with Vít Workspace admin. Use it to chat about workspace setup and more.] [sub: Vít Workspace]", ], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], + "hasMore": false, }, "vit workspace": { - "currentUserOption": null, - "hasMore": false, - "personalDetails": [], - "recentReports": [ + "displayedRows": [ "2006 Groß Straße's expenses [alt: Vít Workspace • This is where Groß Straße will submit expenses to Vít Workspace. Just use the + button.] [sub: Vít Workspace]", "2005 Current User's expenses [alt: Vít Workspace • This is where Current User will submit expenses to Vít Workspace. Just use the + button.] [sub: Vít Workspace • Submits to Current User]", "2011 #announce [alt: Vít Workspace • This chat is with everyone in Vít Workspace. Use it for the most important announcements.] [sub: Vít Workspace]", "2010 #admins [alt: Vít Workspace • This chat is with Vít Workspace admin. Use it to chat about workspace setup and more.] [sub: Vít Workspace]", ], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], + "hasMore": false, }, "workspace": { - "currentUserOption": null, - "hasMore": false, - "personalDetails": [], - "recentReports": [ + "displayedRows": [ "2006 Groß Straße's expenses [alt: Vít Workspace • This is where Groß Straße will submit expenses to Vít Workspace. Just use the + button.] [sub: Vít Workspace]", "2005 Current User's expenses [alt: Vít Workspace • This is where Current User will submit expenses to Vít Workspace. Just use the + button.] [sub: Vít Workspace • Submits to Current User]", "2013 #orphan-room [alt: Gone Workspace • This chat room is for anything #orphan-room related.] [sub: Gone Workspace]", "2011 #announce [alt: Vít Workspace • This chat is with everyone in Vít Workspace. Use it for the most important announcements.] [sub: Vít Workspace]", "2010 #admins [alt: Vít Workspace • This chat is with Vít Workspace admin. Use it to chat about workspace setup and more.] [sub: Vít Workspace]", ], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], + "hasMore": false, }, "x.com": { - "currentUserOption": null, - "hasMore": false, - "personalDetails": [], - "recentReports": [ + "displayedRows": [ "1012 first.last+tag@x.com [alt: This chat is with first.last+tag@x.com.] [accID: 12]", ], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], + "hasMore": false, }, "you": { - "currentUserOption": "1 Current User [alt: current.user@example.com] [accID: 1]", - "hasMore": false, - "personalDetails": [], - "recentReports": [ + "displayedRows": [ "2004 Current User (you) [alt: This is your personal space. Use it for notes, tasks, drafts, and reminders.] [accID: 1]", ], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], + "hasMore": false, }, "zzzqqq": { - "currentUserOption": null, + "displayedRows": [], "hasMore": false, - "personalDetails": [], - "recentReports": [], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], }, "İSMAIL": { - "currentUserOption": null, - "hasMore": false, - "personalDetails": [], - "recentReports": [ + "displayedRows": [ "1004 İsmail Yılmaz [alt: Thanks!] [accID: 4]", ], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], + "hasMore": false, }, "δυσσευς": { - "currentUserOption": null, - "hasMore": false, - "personalDetails": [], - "recentReports": [ + "displayedRows": [ "1007 Οδυσσεύς [alt: This chat is with Οδυσσεύς.] [accID: 7]", ], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], + "hasMore": false, }, "δυσσευσ": { - "currentUserOption": null, + "displayedRows": [], "hasMore": false, - "personalDetails": [], - "recentReports": [], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], }, "👩‍💻": { - "currentUserOption": null, - "hasMore": false, - "personalDetails": [], - "recentReports": [ + "displayedRows": [ "8 Zoë 👩‍💻 [alt: zoe@example.com] [accID: 8]", ], - "selfDMChat": null, - "userToInvite": null, - "workspaceChats": [], + "hasMore": false, }, } `; @@ -1225,6 +908,24 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterLo "userToInvite": null, "workspaceChats": [], }, + " ": { + "currentUserOption": null, + "hasMore": false, + "personalDetails": [], + "recentReports": [ + "2004 Current User (you) [alt: This is your personal space. Use it for notes, tasks, drafts, and reminders.] [accID: 1]", + "2013 #orphan-room [alt: Gone Workspace • This chat room is for anything #orphan-room related.] [sub: Gone Workspace]", + "1124 Ada Cole [alt: This chat is with Ada Cole.] [accID: 124]", + "1103 Dan Ames [alt: This chat is with Dan Ames.] [accID: 103]", + "1019 No Login [alt: This chat is with No Login.] [accID: 19]", + "1123 Lou Byrd [alt: This chat is with Lou Byrd.] [accID: 123]", + "1102 Cyd Ames [alt: This chat is with Cyd Ames.] [accID: 102]", + "1018 Domain Admin [alt: This chat is with Domain Admin.] [accID: 18]", + ], + "selfDMChat": null, + "userToInvite": null, + "workspaceChats": [], + }, " alex.smith@example.com ": { "currentUserOption": null, "hasMore": false, diff --git a/tests/utils/searchParity/generateSearchFixture.ts b/tests/utils/searchParity/generateSearchFixture.ts index 7e30e074435c..723ae6bc4f3d 100644 --- a/tests/utils/searchParity/generateSearchFixture.ts +++ b/tests/utils/searchParity/generateSearchFixture.ts @@ -147,6 +147,7 @@ const NAMED_CONTACTS: Array Date: Mon, 26 Oct 2026 22:58:59 +0100 Subject: [PATCH 10/13] Name fixture IDs, use CONST system accounts and document untested search inputs --- .../unit/OptionsListUtilsSearchParityTest.ts | 19 +- .../OptionsListUtilsSearchParityTest.ts.snap | 22 +-- .../searchParity/generateSearchFixture.ts | 187 +++++++++++------- 3 files changed, 142 insertions(+), 86 deletions(-) diff --git a/tests/unit/OptionsListUtilsSearchParityTest.ts b/tests/unit/OptionsListUtilsSearchParityTest.ts index 69543060c8bd..df37aa1aa484 100644 --- a/tests/unit/OptionsListUtilsSearchParityTest.ts +++ b/tests/unit/OptionsListUtilsSearchParityTest.ts @@ -26,7 +26,7 @@ import Onyx from 'react-native-onyx'; import type {SearchFixture} from '../utils/searchParity/generateSearchFixture'; -import {CURRENT_USER_ACCOUNT_ID, CURRENT_USER_EMAIL, generateSearchFixture, OWN_POLICY_ID, SEARCH_QUERIES, writeSearchFixtureToOnyx} from '../utils/searchParity/generateSearchFixture'; +import {ACCOUNT_IDS, CURRENT_USER_EMAIL, generateSearchFixture, POLICY_IDS, SEARCH_QUERIES, writeSearchFixtureToOnyx} from '../utils/searchParity/generateSearchFixture'; import {convertToDisplayString, convertToDisplayStringWithoutCurrency, formatPhoneNumber, localeCompare, translateLocal} from '../utils/TestHelper'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; @@ -104,6 +104,7 @@ function serializeOptionList(optionList: OptionList, {isOrderMeaningful}: {isOrd const sortIfUnordered = (references: Array) => (isOrderMeaningful ? references : [...references].sort()); return { reports: sortIfUnordered(optionList.reports.map((option) => serializeOption(option))), + // Shells are compared both as built and after hydration, so the shell/hydrator split stays invisible personalDetails: optionList.personalDetails.map((shell) => `${shell.reportID} | ${shell.accountID} | ${shell.login ?? ''}`).sort(), hydratedPersonalDetails: optionList.personalDetails.map((shell) => serializeOption(hydrateContactOption(shell))).sort(), @@ -116,6 +117,8 @@ function serializeOptionList(optionList: OptionList, {isOrderMeaningful}: {isOrd */ const SEARCH_ROUTER_IDLE_REPORTS_CAP = 10; +// The fixture account has no intro selection (not a track-intent user), no Onyx rules and no policy tags, which is what SearchAutocompleteList reads for it. +// allPolicyTags is omitted because it only reaches modified-expense previews and the fixture has none, so changes to tag names, rules or the track-intent welcome text are not pinned here. const RULES: OnyxCollection = {}; type BuildOptionListConfig = { @@ -135,7 +138,7 @@ function buildOptionList(sourceFixture: SearchFixture, {isSearching, maxRecentRe sourceFixture.privateIsArchivedMap, sourceFixture.policies, { - currentUserAccountID: CURRENT_USER_ACCOUNT_ID, + currentUserAccountID: ACCOUNT_IDS.CURRENT_USER, currentUserLogin: CURRENT_USER_EMAIL, transactionThreadIDs, lastActions, @@ -166,7 +169,7 @@ function buildSearchRouterIdleOptionList(sourceFixture: SearchFixture, maxRecent } // Passed to combineOrderingOfReportsAndPersonalDetails. Only affects workspace chat ordering, which displayed passes do not show. -const ACTIVE_POLICY_ID = OWN_POLICY_ID; +const ACTIVE_POLICY_ID = POLICY_IDS.OWN; type SearchCallSite = (options: OptionList, query: string) => OptionsResult | DisplayedResult; @@ -205,7 +208,7 @@ function createCallSites(sourceFixture: SearchFixture) { draftComments: sourceFixture.draftComments, countryCode: CONST.DEFAULT_COUNTRY_CODE, loginList: sourceFixture.loginList, - currentUserAccountID: CURRENT_USER_ACCOUNT_ID, + currentUserAccountID: ACCOUNT_IDS.CURRENT_USER, currentUserEmail: CURRENT_USER_EMAIL, policyCollection: sourceFixture.policies, personalDetails: sourceFixture.personalDetails, @@ -215,12 +218,15 @@ function createCallSites(sourceFixture: SearchFixture) { formatPhoneNumber, getReportByID, rules: RULES, + isTrackIntentUser: false, }; + // SearchAutocompleteList server pass const serverPassConfig = { ...baseSearchConfig, visibleReportActionsData, }; + // SearchAutocompleteList local pass const commonSearchConfig = { ...baseSearchConfig, @@ -252,6 +258,7 @@ function createCallSites(sourceFixture: SearchFixture) { const searchCallSites = { searchRouterLocal: (options, query) => runSearchRouterSearch(options, query), + // Replicates the displayed ranking and capping pass from SearchAutocompleteList searchRouterDisplayed: (options, query) => { const result = runSearchRouterSearch(options, query); @@ -375,6 +382,7 @@ function computeGoldenMaster(sourceFixture: SearchFixture) { CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMOUNT_OF_SUGGESTIONS, ); const serverReportIDs = new Set(orderedServerReportIDs); + // SearchAutocompleteList server pass reads derived attributes from Onyx because reportAttributesDerived is omitted const serverPassOptions = getSearchOptions({ ...serverPassConfig, @@ -393,6 +401,7 @@ function computeGoldenMaster(sourceFixture: SearchFixture) { shouldShowGBR: false, shouldUnreadBeBold: true, }).options; + // The pass then puts the rows back in Auth's order and drops the self-DM const serverPassOptionsByReportID = new Map(serverPassOptions.recentReports.map((option) => [option.reportID, option])); const serverPass = orderedServerReportIDs.map((reportID) => serverPassOptionsByReportID.get(reportID)).filter((option) => !!option && !option.isSelfDM); @@ -400,6 +409,7 @@ function computeGoldenMaster(sourceFixture: SearchFixture) { const passes = { searchRouterBlankAtProductionCap: serializeResult(searchCallSites.searchRouterDisplayed(searchRouterIdleAtProductionCap, '')), serverPass: serverPass.map((option) => serializeOption(option)), + // Recorded after the passes above, which run with nothing focused focusedReport: Object.fromEntries( FOCUSED_REPORT_NAMES.map((reportName) => [ @@ -423,6 +433,7 @@ describe('OptionsListUtils search parity (golden master)', () => { await IntlStore.load(CONST.LOCALES.EN); initOnyxDerivedValues(); await waitForBatchedUpdates(); + // The fixture starts without derived values. Writing to Onyx derives them for subsequent reads. fixture.derived = await writeSearchFixtureToOnyx(fixture); goldenMaster = computeGoldenMaster(fixture); diff --git a/tests/unit/__snapshots__/OptionsListUtilsSearchParityTest.ts.snap b/tests/unit/__snapshots__/OptionsListUtilsSearchParityTest.ts.snap index 3d36a5061e9d..c61d42b1e0db 100644 --- a/tests/unit/__snapshots__/OptionsListUtilsSearchParityTest.ts.snap +++ b/tests/unit/__snapshots__/OptionsListUtilsSearchParityTest.ts.snap @@ -147,7 +147,7 @@ exports[`OptionsListUtils search parity (golden master) builds searching and Sea "1018 Domain Admin [alt: admin@example.com] [accID: 18]", "1121 Jon Byrd [alt: jon.byrd.21@example.com] [accID: 121]", "1100 Ada Ames [alt: ada.ames.0@example.com] [accID: 100]", - "1016 Expensify Notifications [alt: notifications@expensify.com] [accID: 16]", + "1016 Expensify Notifications [alt: notifications@expensify.com] [accID: 11665625]", "1120 Ivy Byrd [alt: ivy.byrd.20@example.com] [accID: 120]", "1015 Alex Smith Jr. [alt: alex.smith2@example.com] [accID: 15]", "2012 #vít-room [rbr] [sub: Acme Holdings]", @@ -196,7 +196,7 @@ exports[`OptionsListUtils search parity (golden master) builds searching and Sea "1106 Gus Ames [alt: gus.ames.6@example.com] [accID: 106]", "1126 Cyd Cole [alt: cyd.cole.26@example.com] [accID: 126]", "1105 Fay Ames [alt: fay.ames.5@example.com] [accID: 105]", - "1021 Concierge [alt: concierge@expensify.com] [accID: 21]", + "1021 Concierge [alt: concierge@expensify.com] [accID: -1]", "2000 Mary-Jane O'Brien (archived) [archived] [alt: obrien@example.com] [accID: 11]", ], }, @@ -213,10 +213,10 @@ exports[`OptionsListUtils search parity (golden master) builds searching and Sea "1012 first.last+tag@x.com [alt: first.last+tag@x.com] [accID: 12]", "1013 (500) 555-0006 [alt: +15005550006@expensify.sms] [accID: 13]", "1015 Alex Smith Jr. [alt: alex.smith2@example.com] [accID: 15]", - "1016 Expensify Notifications [alt: notifications@expensify.com] [accID: 16]", + "1016 Expensify Notifications [alt: notifications@expensify.com] [accID: 11665625]", "1018 Domain Admin [alt: admin@example.com] [accID: 18]", "1019 No Login [accID: 19]", - "1021 Concierge [alt: concierge@expensify.com] [accID: 21]", + "1021 Concierge [alt: concierge@expensify.com] [accID: -1]", "104 Eve Ames [alt: eve.ames.4@example.com] [accID: 104]", "107 Hal Ames [alt: hal.ames.7@example.com] [accID: 107]", "110 Kim Ames [alt: kim.ames.10@example.com] [accID: 110]", @@ -292,10 +292,10 @@ exports[`OptionsListUtils search parity (golden master) builds searching and Sea "1012 | 12 | first.last+tag@x.com", "1013 | 13 | +15005550006@expensify.sms", "1015 | 15 | alex.smith2@example.com", - "1016 | 16 | notifications@expensify.com", + "1016 | 11665625 | notifications@expensify.com", "1018 | 18 | admin@example.com", "1019 | 19 | ", - "1021 | 21 | concierge@expensify.com", + "1021 | -1 | concierge@expensify.com", "1100 | 100 | ada.ames.0@example.com", "1102 | 102 | cyd.ames.2@example.com", "1103 | 103 | dan.ames.3@example.com", @@ -337,10 +337,10 @@ exports[`OptionsListUtils search parity (golden master) builds searching and Sea "1012 first.last+tag@x.com [alt: first.last+tag@x.com] [accID: 12]", "1013 (500) 555-0006 [alt: (500) 555-0006] [accID: 13]", "1015 Alex Smith Jr. [alt: alex.smith2@example.com] [accID: 15]", - "1016 Expensify Notifications [alt: notifications@expensify.com] [accID: 16]", + "1016 Expensify Notifications [alt: notifications@expensify.com] [accID: 11665625]", "1018 Domain Admin [alt: admin@example.com] [accID: 18]", "1019 No Login [accID: 19]", - "1021 Concierge [alt: concierge@expensify.com] [accID: 21]", + "1021 Concierge [alt: concierge@expensify.com] [accID: -1]", "1100 Ada Ames [alt: ada.ames.0@example.com] [accID: 100]", "1102 Cyd Ames [alt: cyd.ames.2@example.com] [accID: 102]", "1103 Dan Ames [alt: dan.ames.3@example.com] [accID: 103]", @@ -616,7 +616,7 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterDi }, "concierge": { "displayedRows": [ - "1021 Concierge [alt: Concierge can answer questions, update expenses, and more.] [accID: 21]", + "1021 Concierge [alt: Concierge can answer questions, update expenses, and more.] [accID: -1]", ], "hasMore": false, }, @@ -1275,10 +1275,10 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterLo "currentUserOption": null, "hasMore": false, "personalDetails": [ - "1021 Concierge [alt: concierge@expensify.com] [accID: 21]", + "1021 Concierge [alt: concierge@expensify.com] [accID: -1]", ], "recentReports": [ - "1021 Concierge [alt: Concierge can answer questions, update expenses, and more.] [accID: 21]", + "1021 Concierge [alt: Concierge can answer questions, update expenses, and more.] [accID: -1]", ], "selfDMChat": null, "userToInvite": null, diff --git a/tests/utils/searchParity/generateSearchFixture.ts b/tests/utils/searchParity/generateSearchFixture.ts index 723ae6bc4f3d..93ccb12f262c 100644 --- a/tests/utils/searchParity/generateSearchFixture.ts +++ b/tests/utils/searchParity/generateSearchFixture.ts @@ -14,22 +14,46 @@ import Onyx from 'react-native-onyx'; import getOnyxValue from '../getOnyxValue'; import waitForBatchedUpdates from '../waitForBatchedUpdates'; -const CURRENT_USER_ACCOUNT_ID = 1; const CURRENT_USER_EMAIL = 'current.user@example.com'; -const OWN_POLICY_ID = 'A1B2C3D4E5F60001'; -const OTHER_POLICY_ID = 'A1B2C3D4E5F60002'; -const MISSING_POLICY_ID = 'A1B2C3D4E5F60003'; -/** A DM participant with no personal details, as after a partial personal details load. */ -const UNKNOWN_ACCOUNT_ID = 999; + +const POLICY_IDS = { + OWN: 'A1B2C3D4E5F60001', + OTHER: 'A1B2C3D4E5F60002', + MISSING: 'A1B2C3D4E5F60003', +} as const; + +const ACCOUNT_IDS = { + CURRENT_USER: 1, + UNKNOWN: 999, + CONCIERGE: CONST.ACCOUNT_ID.CONCIERGE, + NOTIFICATIONS: CONST.ACCOUNT_ID.NOTIFICATIONS, + JOSE_NUNEZ: 2, + VIT_NOVAK: 3, + ISMAIL_YILMAZ: 4, + GROSS_STRASSE: 5, + BOB_BUILDER: 10, + MARY_JANE_OBRIEN: 11, + ALEX_SMITH: 14, +} as const; + const SESSION = { - accountID: CURRENT_USER_ACCOUNT_ID, + accountID: ACCOUNT_IDS.CURRENT_USER, email: CURRENT_USER_EMAIL, }; -const CONCIERGE_ACCOUNT_ID = 21; + /** Contacts and reports carry fixed IDs, so adding one never renumbers the others and churns the golden master. */ const FIRST_FILLER_ACCOUNT_ID = 100; + /** A contact's generated 1:1 DM is DM_REPORT_ID_OFFSET + its accountID. */ const DM_REPORT_ID_OFFSET = 1000; + +/** The system accounts' real IDs would break the accountID-derived reportID, day and DM assignment, so their DMs are pinned instead. */ +const SYSTEM_DM_SLOTS: Record = { + // Empty (no report actions). Search drops it through the hard-excluded notifications login before the empty-notifications-DM rule in reasonForReportToBeInOptionList runs, so that rule is not pinned here + [ACCOUNT_IDS.NOTIFICATIONS]: {reportID: DM_REPORT_ID_OFFSET + 16, day: 16}, + [ACCOUNT_IDS.CONCIERGE]: {reportID: DM_REPORT_ID_OFFSET + 21, day: 0}, +}; + /** Every reportID is above this, and a report's timestamp is derived from its distance to it. */ const REPORT_ID_TIMESTAMP_ORIGIN = 1000; @@ -46,6 +70,7 @@ type SearchFixture = { reportActions: NonNullable>; policies: NonNullable>; privateIsArchivedMap: PrivateIsArchivedMap; + /** Empty until writeSearchFixtureToOnyx returns what Onyx derived. */ derived: SearchFixtureDerivedData; draftComments: NonNullable>; @@ -55,29 +80,29 @@ type SearchFixture = { const NAMED_CONTACTS: Array> = [ { - accountID: CURRENT_USER_ACCOUNT_ID, + accountID: ACCOUNT_IDS.CURRENT_USER, login: CURRENT_USER_EMAIL, displayName: 'Current User', firstName: 'Current', lastName: 'User', }, { - accountID: 2, + accountID: ACCOUNT_IDS.JOSE_NUNEZ, login: 'jose.nunez@example.com', displayName: 'Jos\u00e9 \u00d1\u00fa\u00f1ez', }, { - accountID: 3, + accountID: ACCOUNT_IDS.VIT_NOVAK, login: 'vit.novak@example.com', displayName: 'V\u00edt Nov\u00e1k', }, { - accountID: 4, + accountID: ACCOUNT_IDS.ISMAIL_YILMAZ, login: 'ismail@example.com', displayName: '\u0130smail Y\u0131lmaz', }, { - accountID: 5, + accountID: ACCOUNT_IDS.GROSS_STRASSE, login: 'strasse@example.com', displayName: 'Gro\u00df Stra\u00dfe', }, @@ -102,18 +127,19 @@ const NAMED_CONTACTS: Array = { /** Per-contact overrides of the generated 1:1 DM, keyed by accountID. */ const DM_OVERRIDES: Record> = { // Unread: the contact wrote last and the current user has not read it since - 10: { - lastActorAccountID: 10, + [ACCOUNT_IDS.BOB_BUILDER]: { + lastActorAccountID: ACCOUNT_IDS.BOB_BUILDER, lastReadTime: '2024-01-01 00:00:00.000', lastMessageText: 'See you at the trailhead', }, + // Read: the current user wrote last and read everything - 4: { - lastActorAccountID: CURRENT_USER_ACCOUNT_ID, + [ACCOUNT_IDS.ISMAIL_YILMAZ]: { + lastActorAccountID: ACCOUNT_IDS.CURRENT_USER, lastReadTime: '2024-12-31 00:00:00.000', lastMessageText: 'Thanks!', }, @@ -256,7 +291,7 @@ function buildPolicy(id: string, name: string, role: Policy['role']): Policy { type: CONST.POLICY.TYPE.TEAM, role, owner: CURRENT_USER_EMAIL, - ownerAccountID: CURRENT_USER_ACCOUNT_ID, + ownerAccountID: ACCOUNT_IDS.CURRENT_USER, outputCurrency: CONST.CURRENCY.USD, }; } @@ -298,8 +333,8 @@ function generateSearchFixture(): SearchFixture { } const policies: SearchFixture['policies'] = { - [`${ONYXKEYS.COLLECTION.POLICY}${OWN_POLICY_ID}`]: buildPolicy(OWN_POLICY_ID, 'V\u00edt Workspace', CONST.POLICY.ROLE.ADMIN), - [`${ONYXKEYS.COLLECTION.POLICY}${OTHER_POLICY_ID}`]: buildPolicy(OTHER_POLICY_ID, 'Acme Holdings', CONST.POLICY.ROLE.USER), + [`${ONYXKEYS.COLLECTION.POLICY}${POLICY_IDS.OWN}`]: buildPolicy(POLICY_IDS.OWN, 'V\u00edt Workspace', CONST.POLICY.ROLE.ADMIN), + [`${ONYXKEYS.COLLECTION.POLICY}${POLICY_IDS.OTHER}`]: buildPolicy(POLICY_IDS.OTHER, 'Acme Holdings', CONST.POLICY.ROLE.USER), }; const reports: SearchFixture['reports'] = {}; @@ -327,20 +362,21 @@ function generateSearchFixture(): SearchFixture { // Assigns a 1:1 DM to roughly two-thirds of contacts, picked by accountID so adding a contact never moves another contact's DM. // No contact receives multiple DMs to avoid collection order dependence. for (const accountID of contactAccountIDs) { - if (accountID === CURRENT_USER_ACCOUNT_ID || accountID % 3 === 2) { + const systemDMSlot = SYSTEM_DM_SLOTS[accountID]; + if (accountID === ACCOUNT_IDS.CURRENT_USER || (!systemDMSlot && accountID % 3 === 2)) { continue; } const contact = personalDetails[accountID]; const name = contact?.displayName || FORMATTED_PHONE_LOGINS[contact?.login ?? ''] || (contact?.login ?? ''); dmReportIDByAccountID[accountID] = addReport( - DM_REPORT_ID_OFFSET + accountID, + systemDMSlot?.reportID ?? DM_REPORT_ID_OFFSET + accountID, { type: CONST.REPORT.TYPE.CHAT, - participants: participantsOf([CURRENT_USER_ACCOUNT_ID, accountID]), + participants: participantsOf([ACCOUNT_IDS.CURRENT_USER, accountID]), ...DM_OVERRIDES[accountID], }, name, - accountID % 21, + systemDMSlot?.day ?? accountID % 21, ); } @@ -349,7 +385,7 @@ function generateSearchFixture(): SearchFixture { 2000, { type: CONST.REPORT.TYPE.CHAT, - participants: participantsOf([CURRENT_USER_ACCOUNT_ID, 11]), + participants: participantsOf([ACCOUNT_IDS.CURRENT_USER, ACCOUNT_IDS.MARY_JANE_OBRIEN]), }, "Mary-Jane O'Brien (archived) ", 25, @@ -360,7 +396,7 @@ function generateSearchFixture(): SearchFixture { 2001, { type: CONST.REPORT.TYPE.CHAT, - participants: participantsOf([CURRENT_USER_ACCOUNT_ID, 14], [CURRENT_USER_ACCOUNT_ID]), + participants: participantsOf([ACCOUNT_IDS.CURRENT_USER, ACCOUNT_IDS.ALEX_SMITH], [ACCOUNT_IDS.CURRENT_USER]), }, 'Alex Smith', 26, @@ -370,7 +406,7 @@ function generateSearchFixture(): SearchFixture { 2002, { type: CONST.REPORT.TYPE.CHAT, - participants: participantsOf([CURRENT_USER_ACCOUNT_ID, UNKNOWN_ACCOUNT_ID]), + participants: participantsOf([ACCOUNT_IDS.CURRENT_USER, ACCOUNT_IDS.UNKNOWN]), }, '', 6, @@ -382,7 +418,7 @@ function generateSearchFixture(): SearchFixture { type: CONST.REPORT.TYPE.CHAT, chatType: CONST.REPORT.CHAT_TYPE.GROUP, reportName: 'Weekend Hiking', - participants: participantsOf([CURRENT_USER_ACCOUNT_ID, 2, 3, 11]), + participants: participantsOf([ACCOUNT_IDS.CURRENT_USER, ACCOUNT_IDS.JOSE_NUNEZ, ACCOUNT_IDS.VIT_NOVAK, ACCOUNT_IDS.MARY_JANE_OBRIEN]), }, 'Weekend Hiking', 5, @@ -393,8 +429,8 @@ function generateSearchFixture(): SearchFixture { { type: CONST.REPORT.TYPE.CHAT, chatType: CONST.REPORT.CHAT_TYPE.SELF_DM, - ownerAccountID: CURRENT_USER_ACCOUNT_ID, - participants: participantsOf([CURRENT_USER_ACCOUNT_ID]), + ownerAccountID: ACCOUNT_IDS.CURRENT_USER, + participants: participantsOf([ACCOUNT_IDS.CURRENT_USER]), }, 'Current User (you)', 1, @@ -406,11 +442,11 @@ function generateSearchFixture(): SearchFixture { { type: CONST.REPORT.TYPE.CHAT, chatType: CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT, - policyID: OWN_POLICY_ID, + policyID: POLICY_IDS.OWN, policyName: 'V\u00edt Workspace', isOwnPolicyExpenseChat: true, - ownerAccountID: CURRENT_USER_ACCOUNT_ID, - participants: participantsOf([CURRENT_USER_ACCOUNT_ID]), + ownerAccountID: ACCOUNT_IDS.CURRENT_USER, + participants: participantsOf([ACCOUNT_IDS.CURRENT_USER]), hasOutstandingChildRequest: true, }, "Current User's expenses", @@ -422,11 +458,11 @@ function generateSearchFixture(): SearchFixture { { type: CONST.REPORT.TYPE.CHAT, chatType: CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT, - policyID: OWN_POLICY_ID, + policyID: POLICY_IDS.OWN, policyName: 'V\u00edt Workspace', isOwnPolicyExpenseChat: false, - ownerAccountID: 5, - participants: participantsOf([CURRENT_USER_ACCOUNT_ID, 5]), + ownerAccountID: ACCOUNT_IDS.GROSS_STRASSE, + participants: participantsOf([ACCOUNT_IDS.CURRENT_USER, ACCOUNT_IDS.GROSS_STRASSE]), }, "Gro\u00df Stra\u00dfe's expenses", 7, @@ -437,24 +473,25 @@ function generateSearchFixture(): SearchFixture { { type: CONST.REPORT.TYPE.CHAT, chatType: CONST.REPORT.CHAT_TYPE.INVOICE, - policyID: OWN_POLICY_ID, + policyID: POLICY_IDS.OWN, policyName: 'V\u00edt Workspace', invoiceReceiver: { type: CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL, - accountID: 5, + accountID: ACCOUNT_IDS.GROSS_STRASSE, }, - participants: participantsOf([CURRENT_USER_ACCOUNT_ID, 5]), + participants: participantsOf([ACCOUNT_IDS.CURRENT_USER, ACCOUNT_IDS.GROSS_STRASSE]), }, 'Gro\u00df Stra\u00dfe', 12, ); const rooms = [ - [2010, CONST.REPORT.CHAT_TYPE.POLICY_ADMINS, '#admins', OWN_POLICY_ID, 3, {}], - [2011, CONST.REPORT.CHAT_TYPE.POLICY_ANNOUNCE, '#announce', OWN_POLICY_ID, 8, {}], + [2010, CONST.REPORT.CHAT_TYPE.POLICY_ADMINS, '#admins', POLICY_IDS.OWN, 3, {}], + [2011, CONST.REPORT.CHAT_TYPE.POLICY_ANNOUNCE, '#announce', POLICY_IDS.OWN, 8, {}], + // RBR: the room has an error - [2012, CONST.REPORT.CHAT_TYPE.POLICY_ROOM, '#v\u00edt-room', OTHER_POLICY_ID, 14, {errorFields: {joinRoom: {joinRoom: 'Could not join the room'}}}], - [2013, CONST.REPORT.CHAT_TYPE.POLICY_ROOM, '#orphan-room', MISSING_POLICY_ID, 19, {}], + [2012, CONST.REPORT.CHAT_TYPE.POLICY_ROOM, '#v\u00edt-room', POLICY_IDS.OTHER, 14, {errorFields: {joinRoom: {joinRoom: 'Could not join the room'}}}], + [2013, CONST.REPORT.CHAT_TYPE.POLICY_ROOM, '#orphan-room', POLICY_IDS.MISSING, 19, {}], ] as const satisfies ReadonlyArray]>; for (const [reportID, chatType, reportName, policyID, day, reportFields] of rooms) { addReport( @@ -464,8 +501,8 @@ function generateSearchFixture(): SearchFixture { chatType, reportName, policyID, - policyName: policyID === MISSING_POLICY_ID ? 'Gone Workspace' : undefined, - participants: participantsOf([CURRENT_USER_ACCOUNT_ID, 2, 4]), + policyName: policyID === POLICY_IDS.MISSING ? 'Gone Workspace' : undefined, + participants: participantsOf([ACCOUNT_IDS.CURRENT_USER, ACCOUNT_IDS.JOSE_NUNEZ, ACCOUNT_IDS.ISMAIL_YILMAZ]), ...reportFields, }, reportName, @@ -478,7 +515,7 @@ function generateSearchFixture(): SearchFixture { 2020, { type: CONST.REPORT.TYPE.CHAT, - participants: participantsOf([CURRENT_USER_ACCOUNT_ID, 2]), + participants: participantsOf([ACCOUNT_IDS.CURRENT_USER, ACCOUNT_IDS.JOSE_NUNEZ]), }, 'Jos\u00e9 \u00d1\u00fa\u00f1ez', 3, @@ -495,7 +532,7 @@ function generateSearchFixture(): SearchFixture { type: CONST.REPORT.TYPE.CHAT, parentReportID: threadParentReportID, parentReportActionID, - participants: participantsOf([CURRENT_USER_ACCOUNT_ID, 2]), + participants: participantsOf([ACCOUNT_IDS.CURRENT_USER, ACCOUNT_IDS.JOSE_NUNEZ]), ...report, }, reportName, @@ -505,7 +542,7 @@ function generateSearchFixture(): SearchFixture { reportActionID: parentReportActionID, actionName: CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT, created: timestamp(3, reportIDNumber - REPORT_ID_TIMESTAMP_ORIGIN), - actorAccountID: 2, + actorAccountID: ACCOUNT_IDS.JOSE_NUNEZ, childReportID: reportID, message: [ { @@ -524,7 +561,7 @@ function generateSearchFixture(): SearchFixture { reportActionID: '1', actionName: CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT, created: reports[`${ONYXKEYS.COLLECTION.REPORT}${threadReportID}`]?.lastVisibleActionCreated ?? '', - actorAccountID: 2, + actorAccountID: ACCOUNT_IDS.JOSE_NUNEZ, message: [{type: CONST.REPORT.MESSAGE.TYPE.COMMENT, html: text, text}], }, }; @@ -532,15 +569,21 @@ function generateSearchFixture(): SearchFixture { // Thread with replies to verify that non-empty threads are kept in options addReply(addThread(2021, 'Thread about the offsite'), 'Count me in'); - // Hidden for the current user: every call site drops it except the task share destination, which includes threads and never sets excludeHidden - addThread(2022, 'Hidden thread', { - participants: participantsOf([CURRENT_USER_ACCOUNT_ID, 2], [CURRENT_USER_ACCOUNT_ID]), - }); + + // Hidden for the current user. It has a reply, so the empty-thread rule cannot drop it and only the excludeHidden check that every SearchRouter call site sets keeps it out + addReply( + addThread(2022, 'Hidden thread', { + participants: participantsOf([ACCOUNT_IDS.CURRENT_USER, ACCOUNT_IDS.JOSE_NUNEZ], [ACCOUNT_IDS.CURRENT_USER]), + }), + 'Muted for now', + ); + // Empty threads are dropped by shouldReportBeInOptionList unless a draft or a pin keeps them addThread(2023, 'Empty thread'); const draftedThreadReportID = addThread(2024, 'Empty thread with a draft'); draftComments[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${draftedThreadReportID}`] = 'unsent draft'; addThread(2025, 'Empty pinned thread', {isPinned: true}); + // The only thread whose name lacks "thread", so a query reaches it through its "Thread" alternate text alone addReply(addThread(2026, 'Packing list'), 'Tent and stove'); @@ -550,8 +593,8 @@ function generateSearchFixture(): SearchFixture { type: CONST.REPORT.TYPE.TASK, reportName: 'Book flights', parentReportID: threadParentReportID, - ownerAccountID: CURRENT_USER_ACCOUNT_ID, - participants: participantsOf([CURRENT_USER_ACCOUNT_ID, 2]), + ownerAccountID: ACCOUNT_IDS.CURRENT_USER, + participants: participantsOf([ACCOUNT_IDS.CURRENT_USER, ACCOUNT_IDS.JOSE_NUNEZ]), }, 'Book flights', 9, @@ -562,9 +605,9 @@ function generateSearchFixture(): SearchFixture { { type: CONST.REPORT.TYPE.EXPENSE, reportName: 'Offsite expenses', - policyID: OWN_POLICY_ID, - ownerAccountID: CURRENT_USER_ACCOUNT_ID, - participants: participantsOf([CURRENT_USER_ACCOUNT_ID]), + policyID: POLICY_IDS.OWN, + ownerAccountID: ACCOUNT_IDS.CURRENT_USER, + participants: participantsOf([ACCOUNT_IDS.CURRENT_USER]), }, 'Offsite expenses', 11, @@ -576,9 +619,9 @@ function generateSearchFixture(): SearchFixture { { type: CONST.REPORT.TYPE.EXPENSE, reportName: 'Empty submitted report', - policyID: OWN_POLICY_ID, - ownerAccountID: CURRENT_USER_ACCOUNT_ID, - participants: participantsOf([CURRENT_USER_ACCOUNT_ID]), + policyID: POLICY_IDS.OWN, + ownerAccountID: ACCOUNT_IDS.CURRENT_USER, + participants: participantsOf([ACCOUNT_IDS.CURRENT_USER]), total: 0, stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED, @@ -604,14 +647,14 @@ function generateSearchFixture(): SearchFixture { type: CONST.REPORT.TYPE.CHAT, chatType: CONST.REPORT.CHAT_TYPE.POLICY_ROOM, reportName: undefined, - policyID: OWN_POLICY_ID, - participants: participantsOf([CURRENT_USER_ACCOUNT_ID, 2]), + policyID: POLICY_IDS.OWN, + participants: participantsOf([ACCOUNT_IDS.CURRENT_USER, ACCOUNT_IDS.JOSE_NUNEZ]), }, '#loading-room', 27, ); - const conciergeReportID = dmReportIDByAccountID[CONCIERGE_ACCOUNT_ID]; + const conciergeReportID = dmReportIDByAccountID[ACCOUNT_IDS.CONCIERGE]; if (!conciergeReportID) { throw new Error('The fixture must give the Concierge contact a DM'); } @@ -636,7 +679,7 @@ function generateSearchFixture(): SearchFixture { loginList: { [CURRENT_USER_EMAIL]: { created: '2024-01-01 00:00:00.000', - accountID: CURRENT_USER_ACCOUNT_ID, + accountID: ACCOUNT_IDS.CURRENT_USER, partnerID: 1, partnerUserID: CURRENT_USER_EMAIL, lastLogin: '2024-01-01 00:00:00.000', @@ -661,6 +704,7 @@ function getOnyxEntries(fixture: SearchFixture): OnyxMultiSetInput { ...fixture.reportActions, ...fixture.policies, ...fixture.draftComments, + // Mirrors privateIsArchivedMap so module-level archived reads agree with the map passed to createFilteredOptionList ...Object.fromEntries(Object.keys(fixture.privateIsArchivedMap).map((key) => [key, ARCHIVED_REPORT_NAME_VALUE_PAIRS])), }; @@ -674,6 +718,7 @@ async function writeOnyxEntries(entries: OnyxMultiSetInput): Promise Date: Mon, 26 Oct 2026 16:33:45 +0100 Subject: [PATCH 11/13] Search blank queries on production-cap idle list and pin the hidden and empty thread rules --- .../unit/OptionsListUtilsSearchParityTest.ts | 40 +++---- .../OptionsListUtilsSearchParityTest.ts.snap | 106 +++++++++--------- 2 files changed, 67 insertions(+), 79 deletions(-) diff --git a/tests/unit/OptionsListUtilsSearchParityTest.ts b/tests/unit/OptionsListUtilsSearchParityTest.ts index df37aa1aa484..e61e84464b42 100644 --- a/tests/unit/OptionsListUtilsSearchParityTest.ts +++ b/tests/unit/OptionsListUtilsSearchParityTest.ts @@ -21,6 +21,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import type {Report, Rule} from '@src/types/onyx'; import type {OnyxCollection} from 'react-native-onyx'; +import type {TupleToUnion} from 'type-fest'; import Onyx from 'react-native-onyx'; @@ -112,7 +113,8 @@ function serializeOptionList(optionList: OptionList, {isOrderMeaningful}: {isOrd } /** - * Cap for the SearchRouter idle list. The production cap (SEARCH_ROUTER_OPTIONS_CONFIG.maxRecentReports) keeps all fixture reports. Setting this to 10 forces evictions. + * Cap for the SearchRouter idle list recorded in optionLists, which only exists to record evictions in createFilteredOptionList. No call site searches this list. + * The production cap (SEARCH_ROUTER_OPTIONS_CONFIG.maxRecentReports) keeps all fixture reports, while setting this to 10 forces evictions. * The value is arbitrary: any cap below the fixture report count forces evictions, and 10 just keeps the recorded idle list short. No particular sort rule sits at the boundary. */ const SEARCH_ROUTER_IDLE_REPORTS_CAP = 10; @@ -175,7 +177,7 @@ type SearchCallSite = (options: OptionList, query: string) => OptionsResult | Di const CALL_SITE_NAMES = ['searchRouterLocal', 'searchRouterDisplayed'] as const; -type SearchCallSiteName = (typeof CALL_SITE_NAMES)[number]; +type SearchCallSiteName = TupleToUnion; /** * Keeps getReportByID stable for identical fixture.reports references as useCallback(..., [allReports]) does in useFilteredOptions, @@ -285,23 +287,11 @@ function createCallSites(sourceFixture: SearchFixture) { hasMore: result.hasMore, }; }, - } satisfies Record; + } satisfies Record; return {searchCallSites, serverPassConfig}; } -type OptionListKind = 'searching' | 'searchRouterIdle'; - -// Maps call sites to the option list used when query is blank (the small idle list, or the searching list for any other query). -const BLANK_QUERY_OPTION_LIST: Record = { - searchRouterLocal: 'searchRouterIdle', - searchRouterDisplayed: 'searchRouterIdle', -}; - -function getOptionListKind(callSite: SearchCallSiteName, query: string): OptionListKind { - return query.trim() ? 'searching' : BLANK_QUERY_OPTION_LIST[callSite]; -} - // Reports returned by the SearchAutocompleteList server pass in Auth ranking order, including a self-DM that the pass drops and reports the canonical filter rejects. const SERVER_PASS_REPORT_NAMES = [ '#admins', @@ -334,6 +324,9 @@ function getReportIDByName(sourceFixture: SearchFixture, reportName: string): st // Dropped from the list unless focused, because the focused report skips the checks that drop empty threads and empty submitted reports const FOCUSED_REPORT_NAMES = ['Empty thread', 'Empty submitted report']; +// The thread ranks past the MAX_AMOUNT_OF_SUGGESTIONS rows the blank list shows, so its focused blank result equals the unfocused one and only the query is recorded for it +const FOCUSED_REPORT_NAME_WITH_BLANK = 'Empty submitted report'; + // Matches both focused reports, so the pass shows whether they come back while a query is typed const FOCUSED_REPORT_QUERY = 'empty'; @@ -366,14 +359,12 @@ function computeGoldenMaster(sourceFixture: SearchFixture) { searchRouterIdleAtProductionCap: serializeOptionList(searchRouterIdleAtProductionCap, {isOrderMeaningful: true}), }; - const optionListsByKind: Record = { - searching, - searchRouterIdle, - }; + // The SearchRouter starts at the production cap, and its loadAll effect expands a list that has fewer than 20 rows, so a blank query never searches the small idle list + const getCallSiteOptionList = (query: string) => (query.trim() ? searching : searchRouterIdleAtProductionCap); const callSites = Object.fromEntries( CALL_SITE_NAMES.map((callSite) => [ callSite, - Object.fromEntries(SEARCH_QUERIES.map((query) => [query, serializeResult(searchCallSites[callSite](optionListsByKind[getOptionListKind(callSite, query)], query))])), + Object.fromEntries(SEARCH_QUERIES.map((query) => [query, serializeResult(searchCallSites[callSite](getCallSiteOptionList(query), query))])), ]), ); @@ -407,7 +398,6 @@ function computeGoldenMaster(sourceFixture: SearchFixture) { const serverPass = orderedServerReportIDs.map((reportID) => serverPassOptionsByReportID.get(reportID)).filter((option) => !!option && !option.isSelfDM); const passes = { - searchRouterBlankAtProductionCap: serializeResult(searchCallSites.searchRouterDisplayed(searchRouterIdleAtProductionCap, '')), serverPass: serverPass.map((option) => serializeOption(option)), // Recorded after the passes above, which run with nothing focused @@ -415,7 +405,7 @@ function computeGoldenMaster(sourceFixture: SearchFixture) { FOCUSED_REPORT_NAMES.map((reportName) => [ reportName, withFocusedReport(getReportIDByName(sourceFixture, reportName), () => ({ - blank: serializeResult(searchCallSites.searchRouterDisplayed(searchRouterIdleAtProductionCap, '')), + ...(reportName === FOCUSED_REPORT_NAME_WITH_BLANK ? {blank: serializeResult(searchCallSites.searchRouterDisplayed(searchRouterIdleAtProductionCap, ''))} : {}), [FOCUSED_REPORT_QUERY]: serializeResult(searchCallSites.searchRouterDisplayed(searching, FOCUSED_REPORT_QUERY)), })), ]), @@ -447,15 +437,15 @@ describe('OptionsListUtils search parity (golden master)', () => { }); it.each(CALL_SITE_NAMES)('call site %s matches the golden master for every query', (callSite) => { - // Given a production getSearchOptions config and the option list it searches (the full list, or its idle list while the query is blank) + // Given a production getSearchOptions config and the option list it searches (the full list, or the production-cap idle list while the query is blank) // When every query in the matrix runs through it // Then the sections and their order match the recorded golden master expect(goldenMaster.callSites[callSite]).toMatchSnapshot(); }); it('SearchRouter blank query and server-ID pass match the golden master', () => { - // Given the SearchRouter idle list at the production cap, the SearchAutocompleteList server pass (a fixed set of reports in Auth's order, no query, including rejected and unknown IDs that it drops) and the SearchRouter opened over a focused empty thread or empty submitted report - // When they run through their production configs, the focused ones for a blank query and for a query matching the focused report + // Given the SearchAutocompleteList server pass (a fixed set of reports in Auth's order, no query, including rejected and unknown IDs that it drops) and the SearchRouter opened over a focused empty thread or empty submitted report + // When they run through their production configs, the focused ones for a query matching the focused report, and for a blank query only where the report lands within the displayed rows // Then all match the recorded golden master, with the focused report shown only while it is focused expect(goldenMaster.passes).toMatchSnapshot(); }); diff --git a/tests/unit/__snapshots__/OptionsListUtilsSearchParityTest.ts.snap b/tests/unit/__snapshots__/OptionsListUtilsSearchParityTest.ts.snap index c61d42b1e0db..c5e2b96c37b4 100644 --- a/tests/unit/__snapshots__/OptionsListUtilsSearchParityTest.ts.snap +++ b/tests/unit/__snapshots__/OptionsListUtilsSearchParityTest.ts.snap @@ -39,31 +39,6 @@ exports[`OptionsListUtils search parity (golden master) SearchRouter blank query }, }, "Empty thread": { - "blank": { - "displayedRows": [ - "2004 Current User (you) [alt: This is your personal space. Use it for notes, tasks, drafts, and reminders.] [accID: 1]", - "2013 #orphan-room [alt: Gone Workspace • This chat room is for anything #orphan-room related.] [sub: Gone Workspace]", - "1124 Ada Cole [alt: This chat is with Ada Cole.] [accID: 124]", - "1103 Dan Ames [alt: This chat is with Dan Ames.] [accID: 103]", - "1019 No Login [alt: This chat is with No Login.] [accID: 19]", - "1123 Lou Byrd [alt: This chat is with Lou Byrd.] [accID: 123]", - "1102 Cyd Ames [alt: This chat is with Cyd Ames.] [accID: 102]", - "1018 Domain Admin [alt: This chat is with Domain Admin.] [accID: 18]", - "1121 Jon Byrd [alt: This chat is with Jon Byrd.] [accID: 121]", - "1100 Ada Ames [alt: This chat is with Ada Ames.] [accID: 100]", - "1120 Ivy Byrd [alt: This chat is with Ivy Byrd.] [accID: 120]", - "1015 Alex Smith Jr. [alt: This chat is with Alex Smith Jr..] [accID: 15]", - "2012 #vít-room [rbr] [alt: Acme Holdings • This chat room is for anything #vít-room related.] [sub: Acme Holdings]", - "1139 Dan Dunn [alt: This chat is with Dan Dunn.] [accID: 139]", - "1118 Gus Byrd [alt: This chat is with Gus Byrd.] [accID: 118]", - "1013 (500) 555-0006 [alt: This chat is with (500) 555-0006.] [accID: 13]", - "2007 Groß Straße [alt: Vít Workspace • This chat is for invoices between Groß Straße and Vít Workspace. Use the + button to send an invoice.] [sub: Invoices]", - "1138 Cyd Dunn [alt: This chat is with Cyd Dunn.] [accID: 138]", - "1117 Fay Byrd [alt: This chat is with Fay Byrd.] [accID: 117]", - "1012 first.last+tag@x.com [alt: This chat is with first.last+tag@x.com.] [accID: 12]", - ], - "hasMore": true, - }, "empty": { "displayedRows": [ "2025 Empty pinned thread [alt: No activity yet]", @@ -74,31 +49,6 @@ exports[`OptionsListUtils search parity (golden master) SearchRouter blank query }, }, }, - "searchRouterBlankAtProductionCap": { - "displayedRows": [ - "2004 Current User (you) [alt: This is your personal space. Use it for notes, tasks, drafts, and reminders.] [accID: 1]", - "2013 #orphan-room [alt: Gone Workspace • This chat room is for anything #orphan-room related.] [sub: Gone Workspace]", - "1124 Ada Cole [alt: This chat is with Ada Cole.] [accID: 124]", - "1103 Dan Ames [alt: This chat is with Dan Ames.] [accID: 103]", - "1019 No Login [alt: This chat is with No Login.] [accID: 19]", - "1123 Lou Byrd [alt: This chat is with Lou Byrd.] [accID: 123]", - "1102 Cyd Ames [alt: This chat is with Cyd Ames.] [accID: 102]", - "1018 Domain Admin [alt: This chat is with Domain Admin.] [accID: 18]", - "1121 Jon Byrd [alt: This chat is with Jon Byrd.] [accID: 121]", - "1100 Ada Ames [alt: This chat is with Ada Ames.] [accID: 100]", - "1120 Ivy Byrd [alt: This chat is with Ivy Byrd.] [accID: 120]", - "1015 Alex Smith Jr. [alt: This chat is with Alex Smith Jr..] [accID: 15]", - "2012 #vít-room [rbr] [alt: Acme Holdings • This chat room is for anything #vít-room related.] [sub: Acme Holdings]", - "1139 Dan Dunn [alt: This chat is with Dan Dunn.] [accID: 139]", - "1118 Gus Byrd [alt: This chat is with Gus Byrd.] [accID: 118]", - "1013 (500) 555-0006 [alt: This chat is with (500) 555-0006.] [accID: 13]", - "2007 Groß Straße [alt: Vít Workspace • This chat is for invoices between Groß Straße and Vít Workspace. Use the + button to send an invoice.] [sub: Invoices]", - "1138 Cyd Dunn [alt: This chat is with Cyd Dunn.] [accID: 138]", - "1117 Fay Byrd [alt: This chat is with Fay Byrd.] [accID: 117]", - "1012 first.last+tag@x.com [alt: This chat is with first.last+tag@x.com.] [accID: 12]", - ], - "hasMore": true, - }, "serverPass": [ "2010 #admins [alt: Vít Workspace • This chat is with Vít Workspace admin. Use it to chat about workspace setup and more.] [sub: Vít Workspace]", "2011 #announce [alt: Vít Workspace • This chat is with everyone in Vít Workspace. Use it for the most important announcements.] [sub: Vít Workspace]", @@ -408,8 +358,20 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterDi "1123 Lou Byrd [alt: This chat is with Lou Byrd.] [accID: 123]", "1102 Cyd Ames [alt: This chat is with Cyd Ames.] [accID: 102]", "1018 Domain Admin [alt: This chat is with Domain Admin.] [accID: 18]", + "1121 Jon Byrd [alt: This chat is with Jon Byrd.] [accID: 121]", + "1100 Ada Ames [alt: This chat is with Ada Ames.] [accID: 100]", + "1120 Ivy Byrd [alt: This chat is with Ivy Byrd.] [accID: 120]", + "1015 Alex Smith Jr. [alt: This chat is with Alex Smith Jr..] [accID: 15]", + "2012 #vít-room [rbr] [alt: Acme Holdings • This chat room is for anything #vít-room related.] [sub: Acme Holdings]", + "1139 Dan Dunn [alt: This chat is with Dan Dunn.] [accID: 139]", + "1118 Gus Byrd [alt: This chat is with Gus Byrd.] [accID: 118]", + "1013 (500) 555-0006 [alt: This chat is with (500) 555-0006.] [accID: 13]", + "2007 Groß Straße [alt: Vít Workspace • This chat is for invoices between Groß Straße and Vít Workspace. Use the + button to send an invoice.] [sub: Invoices]", + "1138 Cyd Dunn [alt: This chat is with Cyd Dunn.] [accID: 138]", + "1117 Fay Byrd [alt: This chat is with Fay Byrd.] [accID: 117]", + "1012 first.last+tag@x.com [alt: This chat is with first.last+tag@x.com.] [accID: 12]", ], - "hasMore": false, + "hasMore": true, }, " ": { "displayedRows": [ @@ -421,8 +383,20 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterDi "1123 Lou Byrd [alt: This chat is with Lou Byrd.] [accID: 123]", "1102 Cyd Ames [alt: This chat is with Cyd Ames.] [accID: 102]", "1018 Domain Admin [alt: This chat is with Domain Admin.] [accID: 18]", + "1121 Jon Byrd [alt: This chat is with Jon Byrd.] [accID: 121]", + "1100 Ada Ames [alt: This chat is with Ada Ames.] [accID: 100]", + "1120 Ivy Byrd [alt: This chat is with Ivy Byrd.] [accID: 120]", + "1015 Alex Smith Jr. [alt: This chat is with Alex Smith Jr..] [accID: 15]", + "2012 #vít-room [rbr] [alt: Acme Holdings • This chat room is for anything #vít-room related.] [sub: Acme Holdings]", + "1139 Dan Dunn [alt: This chat is with Dan Dunn.] [accID: 139]", + "1118 Gus Byrd [alt: This chat is with Gus Byrd.] [accID: 118]", + "1013 (500) 555-0006 [alt: This chat is with (500) 555-0006.] [accID: 13]", + "2007 Groß Straße [alt: Vít Workspace • This chat is for invoices between Groß Straße and Vít Workspace. Use the + button to send an invoice.] [sub: Invoices]", + "1138 Cyd Dunn [alt: This chat is with Cyd Dunn.] [accID: 138]", + "1117 Fay Byrd [alt: This chat is with Fay Byrd.] [accID: 117]", + "1012 first.last+tag@x.com [alt: This chat is with first.last+tag@x.com.] [accID: 12]", ], - "hasMore": false, + "hasMore": true, }, " alex.smith@example.com ": { "displayedRows": [ @@ -892,7 +866,7 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterLo { "": { "currentUserOption": null, - "hasMore": false, + "hasMore": true, "personalDetails": [], "recentReports": [ "2004 Current User (you) [alt: This is your personal space. Use it for notes, tasks, drafts, and reminders.] [accID: 1]", @@ -903,6 +877,18 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterLo "1123 Lou Byrd [alt: This chat is with Lou Byrd.] [accID: 123]", "1102 Cyd Ames [alt: This chat is with Cyd Ames.] [accID: 102]", "1018 Domain Admin [alt: This chat is with Domain Admin.] [accID: 18]", + "1121 Jon Byrd [alt: This chat is with Jon Byrd.] [accID: 121]", + "1100 Ada Ames [alt: This chat is with Ada Ames.] [accID: 100]", + "1120 Ivy Byrd [alt: This chat is with Ivy Byrd.] [accID: 120]", + "1015 Alex Smith Jr. [alt: This chat is with Alex Smith Jr..] [accID: 15]", + "2012 #vít-room [rbr] [alt: Acme Holdings • This chat room is for anything #vít-room related.] [sub: Acme Holdings]", + "1139 Dan Dunn [alt: This chat is with Dan Dunn.] [accID: 139]", + "1118 Gus Byrd [alt: This chat is with Gus Byrd.] [accID: 118]", + "1013 (500) 555-0006 [alt: This chat is with (500) 555-0006.] [accID: 13]", + "2007 Groß Straße [alt: Vít Workspace • This chat is for invoices between Groß Straße and Vít Workspace. Use the + button to send an invoice.] [sub: Invoices]", + "1138 Cyd Dunn [alt: This chat is with Cyd Dunn.] [accID: 138]", + "1117 Fay Byrd [alt: This chat is with Fay Byrd.] [accID: 117]", + "1012 first.last+tag@x.com [alt: This chat is with first.last+tag@x.com.] [accID: 12]", ], "selfDMChat": null, "userToInvite": null, @@ -910,7 +896,7 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterLo }, " ": { "currentUserOption": null, - "hasMore": false, + "hasMore": true, "personalDetails": [], "recentReports": [ "2004 Current User (you) [alt: This is your personal space. Use it for notes, tasks, drafts, and reminders.] [accID: 1]", @@ -921,6 +907,18 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterLo "1123 Lou Byrd [alt: This chat is with Lou Byrd.] [accID: 123]", "1102 Cyd Ames [alt: This chat is with Cyd Ames.] [accID: 102]", "1018 Domain Admin [alt: This chat is with Domain Admin.] [accID: 18]", + "1121 Jon Byrd [alt: This chat is with Jon Byrd.] [accID: 121]", + "1100 Ada Ames [alt: This chat is with Ada Ames.] [accID: 100]", + "1120 Ivy Byrd [alt: This chat is with Ivy Byrd.] [accID: 120]", + "1015 Alex Smith Jr. [alt: This chat is with Alex Smith Jr..] [accID: 15]", + "2012 #vít-room [rbr] [alt: Acme Holdings • This chat room is for anything #vít-room related.] [sub: Acme Holdings]", + "1139 Dan Dunn [alt: This chat is with Dan Dunn.] [accID: 139]", + "1118 Gus Byrd [alt: This chat is with Gus Byrd.] [accID: 118]", + "1013 (500) 555-0006 [alt: This chat is with (500) 555-0006.] [accID: 13]", + "2007 Groß Straße [alt: Vít Workspace • This chat is for invoices between Groß Straße and Vít Workspace. Use the + button to send an invoice.] [sub: Invoices]", + "1138 Cyd Dunn [alt: This chat is with Cyd Dunn.] [accID: 138]", + "1117 Fay Byrd [alt: This chat is with Fay Byrd.] [accID: 117]", + "1012 first.last+tag@x.com [alt: This chat is with first.last+tag@x.com.] [accID: 12]", ], "selfDMChat": null, "userToInvite": null, From c1bc8fcede9e2991a2c9ecde8c495b09658b4bb0 Mon Sep 17 00:00:00 2001 From: Jakub Magiera Date: Tue, 27 Oct 2026 12:34:00 +0100 Subject: [PATCH 12/13] Record each search row's navigation target in parity test --- .../unit/OptionsListUtilsSearchParityTest.ts | 9 +- .../OptionsListUtilsSearchParityTest.ts.snap | 96 +++++++++---------- 2 files changed, 55 insertions(+), 50 deletions(-) diff --git a/tests/unit/OptionsListUtilsSearchParityTest.ts b/tests/unit/OptionsListUtilsSearchParityTest.ts index e61e84464b42..af894587209c 100644 --- a/tests/unit/OptionsListUtilsSearchParityTest.ts +++ b/tests/unit/OptionsListUtilsSearchParityTest.ts @@ -53,8 +53,10 @@ function clearSearchCaches() { } /** - * Serializes options into " [flags] [alt: ...] [sub: ...] [accID: ...]" lines to pin option ordering and content rather than rendering details. + * Serializes options into " [flags] [alt: ...] [sub: ...] [accID: ...] [nav: ...]" lines to pin option ordering and content rather than rendering details. * [sub: ...] is only emitted for rooms and workspace chats, because that is what search matches for them. + * [nav: ...] is the row's navigation target (reportID, else login, else none), emitted only when it differs from keyForList: report rows navigate by reportID, which equals keyForList today, so only a divergence shows. + * The userToInvite login target is not emitted, because it is already visible as the text or the trailing invited login. * Fields the SearchRouter never reads (participantsList, icons, policyID, ownerAccountID, item) are deliberately left out. */ function serializeOption(option: SearchOptionData | null | undefined, {isUserToInvite = false}: {isUserToInvite?: boolean} = {}): string | null { @@ -73,7 +75,10 @@ function serializeOption(option: SearchOptionData | null | undefined, {isUserToI const altText = option.alternateText ? ` [alt: ${option.alternateText}]` : ''; const subtitle = (option.isChatRoom || option.isPolicyExpenseChat) && option.subtitle ? ` [sub: ${option.subtitle}]` : ''; const accountID = option.accountID ? ` [accID: ${option.accountID}]` : ''; - return `${option.keyForList ?? ''} ${option.text ?? ''}${flags}${altText}${subtitle}${accountID}${invitedLogin}`; + const navigationTarget = option.reportID || option.login; + const isLoginShownAlready = isUserToInvite && !option.reportID; + const navigation = !isLoginShownAlready && String(navigationTarget ?? '') !== (option.keyForList ?? '') ? ` [nav: ${navigationTarget || 'none'}]` : ''; + return `${option.keyForList ?? ''} ${option.text ?? ''}${flags}${altText}${subtitle}${accountID}${navigation}${invitedLogin}`; } /** What the screen shows: one flat list of rows, in order. */ diff --git a/tests/unit/__snapshots__/OptionsListUtilsSearchParityTest.ts.snap b/tests/unit/__snapshots__/OptionsListUtilsSearchParityTest.ts.snap index c5e2b96c37b4..a5f14bfa6fda 100644 --- a/tests/unit/__snapshots__/OptionsListUtilsSearchParityTest.ts.snap +++ b/tests/unit/__snapshots__/OptionsListUtilsSearchParityTest.ts.snap @@ -152,13 +152,13 @@ exports[`OptionsListUtils search parity (golden master) builds searching and Sea }, "searching": { "hydratedPersonalDetails": [ - "1 Current User [alt: current.user@example.com] [accID: 1]", + "1 Current User [alt: current.user@example.com] [accID: 1] [nav: current.user@example.com]", "1003 Vít Novák [alt: vit.novak@example.com] [accID: 3]", "1004 İsmail Yılmaz [alt: ismail@example.com] [accID: 4]", "1006 Łukasz Wójcik [alt: lukasz@example.com] [accID: 6]", "1007 Οδυσσεύς [alt: odysseus@example.com] [accID: 7]", "1009 Ana​Maria [alt: ana.maria@example.com] [accID: 9]", - "101 Bea Ames [alt: bea.ames.1@example.com] [accID: 101]", + "101 Bea Ames [alt: bea.ames.1@example.com] [accID: 101] [nav: bea.ames.1@example.com]", "1010 Bob Builder [alt: bob.builder@example.com] [accID: 10]", "1012 first.last+tag@x.com [alt: first.last+tag@x.com] [accID: 12]", "1013 (500) 555-0006 [alt: +15005550006@expensify.sms] [accID: 13]", @@ -167,9 +167,9 @@ exports[`OptionsListUtils search parity (golden master) builds searching and Sea "1018 Domain Admin [alt: admin@example.com] [accID: 18]", "1019 No Login [accID: 19]", "1021 Concierge [alt: concierge@expensify.com] [accID: -1]", - "104 Eve Ames [alt: eve.ames.4@example.com] [accID: 104]", - "107 Hal Ames [alt: hal.ames.7@example.com] [accID: 107]", - "110 Kim Ames [alt: kim.ames.10@example.com] [accID: 110]", + "104 Eve Ames [alt: eve.ames.4@example.com] [accID: 104] [nav: eve.ames.4@example.com]", + "107 Hal Ames [alt: hal.ames.7@example.com] [accID: 107] [nav: hal.ames.7@example.com]", + "110 Kim Ames [alt: kim.ames.10@example.com] [accID: 110] [nav: kim.ames.10@example.com]", "1100 Ada Ames [alt: ada.ames.0@example.com] [accID: 100]", "1102 Cyd Ames [alt: cyd.ames.2@example.com] [accID: 102]", "1103 Dan Ames [alt: dan.ames.3@example.com] [accID: 103]", @@ -190,7 +190,7 @@ exports[`OptionsListUtils search parity (golden master) builds searching and Sea "1126 Cyd Cole [alt: cyd.cole.26@example.com] [accID: 126]", "1127 Dan Cole [alt: dan.cole.27@example.com] [accID: 127]", "1129 Fay Cole [alt: fay.cole.29@example.com] [accID: 129]", - "113 Bea Byrd [alt: bea.byrd.13@example.com] [accID: 113]", + "113 Bea Byrd [alt: bea.byrd.13@example.com] [accID: 113] [nav: bea.byrd.13@example.com]", "1130 Gus Cole [alt: gus.cole.30@example.com] [accID: 130]", "1132 Ivy Cole [alt: ivy.cole.32@example.com] [accID: 132]", "1133 Jon Cole [alt: jon.cole.33@example.com] [accID: 133]", @@ -198,21 +198,21 @@ exports[`OptionsListUtils search parity (golden master) builds searching and Sea "1136 Ada Dunn [alt: ada.dunn.36@example.com] [accID: 136]", "1138 Cyd Dunn [alt: cyd.dunn.38@example.com] [accID: 138]", "1139 Dan Dunn [alt: dan.dunn.39@example.com] [accID: 139]", - "116 Eve Byrd [alt: eve.byrd.16@example.com] [accID: 116]", - "119 Hal Byrd [alt: hal.byrd.19@example.com] [accID: 119]", - "122 Kim Byrd [alt: kim.byrd.22@example.com] [accID: 122]", - "125 Bea Cole [alt: bea.cole.25@example.com] [accID: 125]", - "128 Eve Cole [alt: eve.cole.28@example.com] [accID: 128]", - "131 Hal Cole [alt: hal.cole.31@example.com] [accID: 131]", - "134 Kim Cole [alt: kim.cole.34@example.com] [accID: 134]", - "137 Bea Dunn [alt: bea.dunn.37@example.com] [accID: 137]", - "17 Account Executive [alt: Account Executive] [accID: 17]", - "20 René Dupont [alt: rene@example.com] [accID: 20]", + "116 Eve Byrd [alt: eve.byrd.16@example.com] [accID: 116] [nav: eve.byrd.16@example.com]", + "119 Hal Byrd [alt: hal.byrd.19@example.com] [accID: 119] [nav: hal.byrd.19@example.com]", + "122 Kim Byrd [alt: kim.byrd.22@example.com] [accID: 122] [nav: kim.byrd.22@example.com]", + "125 Bea Cole [alt: bea.cole.25@example.com] [accID: 125] [nav: bea.cole.25@example.com]", + "128 Eve Cole [alt: eve.cole.28@example.com] [accID: 128] [nav: eve.cole.28@example.com]", + "131 Hal Cole [alt: hal.cole.31@example.com] [accID: 131] [nav: hal.cole.31@example.com]", + "134 Kim Cole [alt: kim.cole.34@example.com] [accID: 134] [nav: kim.cole.34@example.com]", + "137 Bea Dunn [alt: bea.dunn.37@example.com] [accID: 137] [nav: bea.dunn.37@example.com]", + "17 Account Executive [alt: Account Executive] [accID: 17] [nav: Account Executive]", + "20 René Dupont [alt: rene@example.com] [accID: 20] [nav: rene@example.com]", "2000 Mary-Jane O'Brien [archived] [alt: obrien@example.com] [accID: 11]", "2001 Alex Smith [alt: alex.smith@example.com] [accID: 14]", "2020 José Ñúñez [alt: jose.nunez@example.com] [accID: 2]", - "5 Groß Straße [alt: strasse@example.com] [accID: 5]", - "8 Zoë 👩‍💻 [alt: zoe@example.com] [accID: 8]", + "5 Groß Straße [alt: strasse@example.com] [accID: 5] [nav: strasse@example.com]", + "8 Zoë 👩‍💻 [alt: zoe@example.com] [accID: 8] [nav: zoe@example.com]", ], "personalDetails": [ " | 1 | current.user@example.com", @@ -416,7 +416,7 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterDi }, "+1 650 555 0100": { "displayedRows": [ - "1100537387 +1 650 555 0100 [alt: +1 650 555 0100] [accID: 1100537387]", + "1100537387 +1 650 555 0100 [alt: +1 650 555 0100] [accID: 1100537387] [nav: +1 650 555 0100]", ], "hasMore": false, }, @@ -471,7 +471,7 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterDi }, "Groß": { "displayedRows": [ - "5 Groß Straße [alt: strasse@example.com] [accID: 5]", + "5 Groß Straße [alt: strasse@example.com] [accID: 5] [nav: strasse@example.com]", "2006 Groß Straße's expenses [alt: Vít Workspace • This is where Groß Straße will submit expenses to Vít Workspace. Just use the + button.] [sub: Vít Workspace]", "2007 Groß Straße [alt: Vít Workspace • This chat is for invoices between Groß Straße and Vít Workspace. Use the + button to send an invoice.] [sub: Invoices]", ], @@ -486,7 +486,7 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterDi }, "René": { "displayedRows": [ - "20 René Dupont [alt: rene@example.com] [accID: 20]", + "20 René Dupont [alt: rene@example.com] [accID: 20] [nav: rene@example.com]", ], "hasMore": false, }, @@ -557,9 +557,9 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterDi "displayedRows": [ "2001 Alex Smith [alt: alex.smith@example.com] [accID: 14]", "1015 Alex Smith Jr. [alt: This chat is with Alex Smith Jr..] [accID: 15]", - "107 Hal Ames [alt: hal.ames.7@example.com] [accID: 107]", - "119 Hal Byrd [alt: hal.byrd.19@example.com] [accID: 119]", - "131 Hal Cole [alt: hal.cole.31@example.com] [accID: 131]", + "107 Hal Ames [alt: hal.ames.7@example.com] [accID: 107] [nav: hal.ames.7@example.com]", + "119 Hal Byrd [alt: hal.byrd.19@example.com] [accID: 119] [nav: hal.byrd.19@example.com]", + "131 Hal Cole [alt: hal.cole.31@example.com] [accID: 131] [nav: hal.cole.31@example.com]", ], "hasMore": false, }, @@ -688,9 +688,9 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterDi "1108 Ivy Ames [alt: This chat is with Ivy Ames.] [accID: 108]", "1106 Gus Ames [alt: This chat is with Gus Ames.] [accID: 106]", "1105 Fay Ames [alt: This chat is with Fay Ames.] [accID: 105]", - "101 Bea Ames [alt: bea.ames.1@example.com] [accID: 101]", - "104 Eve Ames [alt: eve.ames.4@example.com] [accID: 104]", - "107 Hal Ames [alt: hal.ames.7@example.com] [accID: 107]", + "101 Bea Ames [alt: bea.ames.1@example.com] [accID: 101] [nav: bea.ames.1@example.com]", + "104 Eve Ames [alt: eve.ames.4@example.com] [accID: 104] [nav: eve.ames.4@example.com]", + "107 Hal Ames [alt: hal.ames.7@example.com] [accID: 107] [nav: hal.ames.7@example.com]", "2012 #vít-room [rbr] [alt: Acme Holdings • This chat room is for anything #vít-room related.] [sub: Acme Holdings]", ], "hasMore": true, @@ -704,7 +704,7 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterDi }, "new.person@example.com": { "displayedRows": [ - "301623904 new.person@example.com [alt: new.person@example.com] [accID: 301623904]", + "301623904 new.person@example.com [alt: new.person@example.com] [accID: 301623904] [nav: new.person@example.com]", ], "hasMore": false, }, @@ -759,7 +759,7 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterDi }, "rene": { "displayedRows": [ - "20 René Dupont [alt: rene@example.com] [accID: 20]", + "20 René Dupont [alt: rene@example.com] [accID: 20] [nav: rene@example.com]", ], "hasMore": false, }, @@ -771,7 +771,7 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterDi }, "strasse": { "displayedRows": [ - "5 Groß Straße [alt: strasse@example.com] [accID: 5]", + "5 Groß Straße [alt: strasse@example.com] [accID: 5] [nav: strasse@example.com]", "2006 Groß Straße's expenses [alt: Vít Workspace • This is where Groß Straße will submit expenses to Vít Workspace. Just use the + button.] [sub: Vít Workspace]", "2007 Groß Straße [alt: Vít Workspace • This chat is for invoices between Groß Straße and Vít Workspace. Use the + button to send an invoice.] [sub: Invoices]", ], @@ -855,7 +855,7 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterDi }, "👩‍💻": { "displayedRows": [ - "8 Zoë 👩‍💻 [alt: zoe@example.com] [accID: 8]", + "8 Zoë 👩‍💻 [alt: zoe@example.com] [accID: 8] [nav: zoe@example.com]", ], "hasMore": false, }, @@ -1038,10 +1038,10 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterLo "workspaceChats": [], }, "CURRENT.USER@EXAMPLE.COM": { - "currentUserOption": "1 Current User [alt: current.user@example.com] [accID: 1]", + "currentUserOption": "1 Current User [alt: current.user@example.com] [accID: 1] [nav: current.user@example.com]", "hasMore": false, "personalDetails": [ - "1 Current User [alt: current.user@example.com] [accID: 1]", + "1 Current User [alt: current.user@example.com] [accID: 1] [nav: current.user@example.com]", ], "recentReports": [ "2004 Current User (you) [alt: This is your personal space. Use it for notes, tasks, drafts, and reminders.] [accID: 1]", @@ -1056,7 +1056,7 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterLo "currentUserOption": null, "hasMore": false, "personalDetails": [ - "5 Groß Straße [alt: strasse@example.com] [accID: 5]", + "5 Groß Straße [alt: strasse@example.com] [accID: 5] [nav: strasse@example.com]", ], "recentReports": [ "2007 Groß Straße [alt: Vít Workspace • This chat is for invoices between Groß Straße and Vít Workspace. Use the + button to send an invoice.] [sub: Invoices]", @@ -1084,7 +1084,7 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterLo "currentUserOption": null, "hasMore": false, "personalDetails": [ - "20 René Dupont [alt: rene@example.com] [accID: 20]", + "20 René Dupont [alt: rene@example.com] [accID: 20] [nav: rene@example.com]", ], "recentReports": [], "selfDMChat": null, @@ -1205,9 +1205,9 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterLo "personalDetails": [ "2001 Alex Smith [alt: alex.smith@example.com] [accID: 14]", "1015 Alex Smith Jr. [alt: alex.smith2@example.com] [accID: 15]", - "107 Hal Ames [alt: hal.ames.7@example.com] [accID: 107]", - "119 Hal Byrd [alt: hal.byrd.19@example.com] [accID: 119]", - "131 Hal Cole [alt: hal.cole.31@example.com] [accID: 131]", + "107 Hal Ames [alt: hal.ames.7@example.com] [accID: 107] [nav: hal.ames.7@example.com]", + "119 Hal Byrd [alt: hal.byrd.19@example.com] [accID: 119] [nav: hal.byrd.19@example.com]", + "131 Hal Cole [alt: hal.cole.31@example.com] [accID: 131] [nav: hal.cole.31@example.com]", ], "recentReports": [ "1015 Alex Smith Jr. [alt: This chat is with Alex Smith Jr..] [accID: 15]", @@ -1438,18 +1438,18 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterLo "workspaceChats": [], }, "me": { - "currentUserOption": "1 Current User [alt: current.user@example.com] [accID: 1]", + "currentUserOption": "1 Current User [alt: current.user@example.com] [accID: 1] [nav: current.user@example.com]", "hasMore": true, "personalDetails": [ "1100 Ada Ames [alt: ada.ames.0@example.com] [accID: 100]", - "101 Bea Ames [alt: bea.ames.1@example.com] [accID: 101]", - "1 Current User [alt: current.user@example.com] [accID: 1]", + "101 Bea Ames [alt: bea.ames.1@example.com] [accID: 101] [nav: bea.ames.1@example.com]", + "1 Current User [alt: current.user@example.com] [accID: 1] [nav: current.user@example.com]", "1102 Cyd Ames [alt: cyd.ames.2@example.com] [accID: 102]", "1103 Dan Ames [alt: dan.ames.3@example.com] [accID: 103]", - "104 Eve Ames [alt: eve.ames.4@example.com] [accID: 104]", + "104 Eve Ames [alt: eve.ames.4@example.com] [accID: 104] [nav: eve.ames.4@example.com]", "1105 Fay Ames [alt: fay.ames.5@example.com] [accID: 105]", "1106 Gus Ames [alt: gus.ames.6@example.com] [accID: 106]", - "107 Hal Ames [alt: hal.ames.7@example.com] [accID: 107]", + "107 Hal Ames [alt: hal.ames.7@example.com] [accID: 107] [nav: hal.ames.7@example.com]", ], "recentReports": [ "2004 Current User (you) [alt: This is your personal space. Use it for notes, tasks, drafts, and reminders.] [accID: 1]", @@ -1584,7 +1584,7 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterLo "currentUserOption": null, "hasMore": false, "personalDetails": [ - "20 René Dupont [alt: rene@example.com] [accID: 20]", + "20 René Dupont [alt: rene@example.com] [accID: 20] [nav: rene@example.com]", ], "recentReports": [], "selfDMChat": null, @@ -1608,7 +1608,7 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterLo "currentUserOption": null, "hasMore": false, "personalDetails": [ - "5 Groß Straße [alt: strasse@example.com] [accID: 5]", + "5 Groß Straße [alt: strasse@example.com] [accID: 5] [nav: strasse@example.com]", ], "recentReports": [ "2007 Groß Straße [alt: Vít Workspace • This chat is for invoices between Groß Straße and Vít Workspace. Use the + button to send an invoice.] [sub: Invoices]", @@ -1703,10 +1703,10 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterLo "workspaceChats": [], }, "you": { - "currentUserOption": "1 Current User [alt: current.user@example.com] [accID: 1]", + "currentUserOption": "1 Current User [alt: current.user@example.com] [accID: 1] [nav: current.user@example.com]", "hasMore": false, "personalDetails": [ - "1 Current User [alt: current.user@example.com] [accID: 1]", + "1 Current User [alt: current.user@example.com] [accID: 1] [nav: current.user@example.com]", ], "recentReports": [ "2004 Current User (you) [alt: This is your personal space. Use it for notes, tasks, drafts, and reminders.] [accID: 1]", @@ -1763,7 +1763,7 @@ exports[`OptionsListUtils search parity (golden master) call site searchRouterLo "currentUserOption": null, "hasMore": false, "personalDetails": [ - "8 Zoë 👩‍💻 [alt: zoe@example.com] [accID: 8]", + "8 Zoë 👩‍💻 [alt: zoe@example.com] [accID: 8] [nav: zoe@example.com]", ], "recentReports": [], "selfDMChat": null, From ce46338aa175324c6def946345c139f2dfe9e86b Mon Sep 17 00:00:00 2001 From: Jakub Magiera Date: Tue, 27 Oct 2026 14:48:44 +0100 Subject: [PATCH 13/13] Correct the left-out fields note in search parity serializer --- tests/unit/OptionsListUtilsSearchParityTest.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/OptionsListUtilsSearchParityTest.ts b/tests/unit/OptionsListUtilsSearchParityTest.ts index af894587209c..a6a067ec09d8 100644 --- a/tests/unit/OptionsListUtilsSearchParityTest.ts +++ b/tests/unit/OptionsListUtilsSearchParityTest.ts @@ -57,7 +57,7 @@ function clearSearchCaches() { * [sub: ...] is only emitted for rooms and workspace chats, because that is what search matches for them. * [nav: ...] is the row's navigation target (reportID, else login, else none), emitted only when it differs from keyForList: report rows navigate by reportID, which equals keyForList today, so only a divergence shows. * The userToInvite login target is not emitted, because it is already visible as the text or the trailing invited login. - * Fields the SearchRouter never reads (participantsList, icons, policyID, ownerAccountID, item) are deliberately left out. + * participantsList, icons and ownerAccountID are left out because the SearchRouter only renders them or never reads them. policyID and item are read for contextual search and FullStory masking, but only the canonical builders set them, so they are left out too. */ function serializeOption(option: SearchOptionData | null | undefined, {isUserToInvite = false}: {isUserToInvite?: boolean} = {}): string | null { if (!option) {