Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
e676af6
82307: debounce autocomplete query and memoize search options
abbasifaizan70 Apr 1, 2026
3bbdbca
82307: debounce autocomplete query and memoize search options
abbasifaizan70 Apr 1, 2026
4a55f1a
fixed eslint issues
abbasifaizan70 Apr 1, 2026
9b669fe
fixed eslint issues
abbasifaizan70 Apr 1, 2026
11e34d5
fixed eslint issues
abbasifaizan70 Apr 1, 2026
0c69c3e
Merge branch 'Expensify:main' into 83207
abbasifaizan70 Apr 1, 2026
3e4067c
fixed lint issue
abbasifaizan70 Apr 1, 2026
4e8ba5a
Merge branch '83207' of https://github.com/abbasifaizan70/Expensify i…
abbasifaizan70 Apr 1, 2026
c4c2f9a
fixed AI feedback
abbasifaizan70 Apr 2, 2026
0648ce9
Merge branch 'Expensify:main' into 83207
abbasifaizan70 Apr 2, 2026
8e65cd2
Fixed eslint warnings
abbasifaizan70 Apr 2, 2026
8d71e2c
Merge branch '83207' of https://github.com/abbasifaizan70/Expensify i…
abbasifaizan70 Apr 2, 2026
e1d2ccc
Merge branch 'Expensify:main' into 83207
abbasifaizan70 Apr 7, 2026
81ffc89
Added unit test cases coverage for search matching normalization
abbasifaizan70 Apr 8, 2026
c447ddc
Updated SearchRouter to prevent raw input from bypassing debounce
abbasifaizan70 Apr 8, 2026
e5e514d
Fixed prettier issues
abbasifaizan70 Apr 8, 2026
4bbbec9
Fixed AI feedbacks
abbasifaizan70 Apr 8, 2026
429042f
Merge branch 'Expensify:main' into 83207
abbasifaizan70 Apr 8, 2026
e66d380
Merge branch 'Expensify:main' into 83207
abbasifaizan70 Apr 8, 2026
f246760
Merge branch 'Expensify:main' into 83207
abbasifaizan70 Apr 8, 2026
59bbc7d
Merge branch 'Expensify:main' into 83207
abbasifaizan70 Apr 8, 2026
8a42005
Merge branch 'main' into 83207
abbasifaizan70 Apr 13, 2026
86a27b4
Merge branch '83207' of https://github.com/abbasifaizan70/Expensify i…
abbasifaizan70 Apr 13, 2026
8b09cdc
Merge branch 'Expensify:main' into 83207
abbasifaizan70 Apr 20, 2026
ff6b977
Merge branch 'Expensify:main' into 83207
abbasifaizan70 Apr 21, 2026
500aec8
Fixed types issue
abbasifaizan70 Apr 21, 2026
3f9a008
Merge branch 'Expensify:main' into 83207
abbasifaizan70 Apr 27, 2026
a1c33cb
Fixed recent search flicker issue
abbasifaizan70 May 6, 2026
ea3bf42
Fixed Typesrcipt issues
abbasifaizan70 May 6, 2026
57ba6a2
Merge branch 'Expensify:main' into 83207
abbasifaizan70 May 6, 2026
478dcf2
fixed eslint issues
abbasifaizan70 May 6, 2026
b333029
Fixed prettier issues
abbasifaizan70 May 6, 2026
f434f43
Fixed Feedbacks
abbasifaizan70 May 7, 2026
ad06633
Merge branch 'main' into 83207
abbasifaizan70 May 15, 2026
7d6479a
fixed AI feedbacks
abbasifaizan70 May 15, 2026
79961c0
Merge branch 'Expensify:main' into 83207
abbasifaizan70 May 20, 2026
20a9cf7
Merge branch 'Expensify:main' into 83207
abbasifaizan70 May 21, 2026
062b9be
Merge branch 'main' into 83207
abbasifaizan70 May 21, 2026
ce5c3ae
Merge branch 'main' into 83207
abbasifaizan70 Jun 4, 2026
98c063b
Merge branch '83207' of https://github.com/abbasifaizan70/Expensify i…
abbasifaizan70 Jun 4, 2026
0f3166e
Merge branch 'Expensify:main' into 83207
abbasifaizan70 Jun 5, 2026
8ecf119
Merge branch 'main' into 83207
abbasifaizan70 Jun 10, 2026
c939c6a
Merge branch '83207' of https://github.com/abbasifaizan70/Expensify i…
abbasifaizan70 Jun 10, 2026
b5f476a
Fixed feedbacks and CI issues
abbasifaizan70 Jun 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 27 additions & 12 deletions src/components/Search/SearchAutocompleteList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ type GetAdditionalSectionsCallback = (options: Options, sectionIndex: number) =>
type SearchAutocompleteListProps = {
/** Value of TextInput */
autocompleteQueryValue: string;
/** Immediate (non-debounced) query from the input for UI-only behavior */
inputQueryValue?: string;

/** Callback to trigger search action * */
handleSearch: (value: string) => void;
Expand Down Expand Up @@ -140,6 +142,7 @@ function SearchRouterItem(props: UserListItemProps<AutocompleteListItem> | Searc

function SearchAutocompleteList({
autocompleteQueryValue,
inputQueryValue,
handleSearch,
searchQueryItems,
getAdditionalSections,
Expand Down Expand Up @@ -170,6 +173,9 @@ function SearchAutocompleteList({
const [allFeeds] = useOnyx(ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER);
const allCards = personalAndWorkspaceCards ?? CONST.EMPTY_OBJECT;
const [conciergeReportID] = useOnyx(ONYXKEYS.CONCIERGE_REPORT_ID);
const effectiveInputQueryValue = inputQueryValue ?? autocompleteQueryValue;
const isInputAheadOfDebounce = effectiveInputQueryValue !== autocompleteQueryValue;
const hasEffectiveInputQuery = effectiveInputQueryValue.trim() !== '';
const currentUserPersonalDetails = useCurrentUserPersonalDetails();
const currentUserEmail = currentUserPersonalDetails.email ?? '';
const currentUserAccountID = currentUserPersonalDetails.accountID;
Expand Down Expand Up @@ -242,7 +248,7 @@ function SearchAutocompleteList({
]);

const [isInitialRender, setIsInitialRender] = useState(true);
const prevQueryRef = useRef(autocompleteQueryValue);
const prevQueryRef = useRef(effectiveInputQueryValue);
const innerListRef = useRef<SelectionListWithSectionsHandle | null>(null);
const hasSetInitialFocusRef = useRef(false);

Expand All @@ -264,11 +270,11 @@ function SearchAutocompleteList({
return;
}

const queryChanged = prevQueryRef.current !== autocompleteQueryValue;
prevQueryRef.current = autocompleteQueryValue;
const queryChanged = prevQueryRef.current !== effectiveInputQueryValue;
prevQueryRef.current = effectiveInputQueryValue;

if (queryChanged) {
if (autocompleteQueryValue === '') {
if (effectiveInputQueryValue === '') {
// When query is cleared, reset the initial focus guard so the initial focus
// effect can re-fire and correctly focus the first focusable item (skipping section headers).
hasSetInitialFocusRef.current = false;
Expand All @@ -278,7 +284,7 @@ function SearchAutocompleteList({
innerListRef.current?.updateAndScrollToFocusedIndex(0, true);
}
}
}, [autocompleteQueryValue, isInitialRender]);
}, [effectiveInputQueryValue, isInitialRender]);

// Track external text input focus to prevent list items from stealing focus while typing
useEffect(() => {
Expand All @@ -296,7 +302,7 @@ function SearchAutocompleteList({

// Note: We can't easily subscribe to focus/blur events on the ref, so we update on query changes
// which happen when the user types (meaning input is focused)
}, [textInputRef, autocompleteQueryValue]);
}, [textInputRef, effectiveInputQueryValue]);

const autocompleteSuggestions = useAutocompleteSuggestions({
autocompleteQueryValue,
Expand Down Expand Up @@ -366,10 +372,15 @@ function SearchAutocompleteList({
]);

const recentReportsOptions = useMemo(() => {
if (autocompleteQueryValue.trim() === '') {
if (!hasEffectiveInputQuery) {
return searchOptions.recentReports;
}

// User typed but debounce has not emitted yet; avoid showing stale results from the previous query.
if (isInputAheadOfDebounce) {
return [];
}

const orderedOptions = combineOrderingOfReportsAndPersonalDetails(searchOptions, autocompleteQueryValue, {
sortByReportTypeInSearch: true,
preferChatRoomsOverThreads: true,
Expand All @@ -381,7 +392,7 @@ function SearchAutocompleteList({
}

return reportOptions.slice(0, 20);
}, [autocompleteQueryValue, searchOptions]);
}, [autocompleteQueryValue, hasEffectiveInputQuery, isInputAheadOfDebounce, searchOptions]);

// Locked rank map (keyForList -> originalIndex) capturing the order of locally-known
// results at the moment the query changes. Recomputed only when the query changes, so server
Expand Down Expand Up @@ -412,6 +423,9 @@ function SearchAutocompleteList({
setFrozenLocalRank(buildRankMap(recentReportsOptions));
}

// Debounce the server search so callers that don't already debounce upstream
// (e.g. the main Spend page header via useSearchPageInput) don't fire a request per keystroke.
// For SearchRouter the upstream value is already debounced, so this just adds a no-op coalescing layer.
const debounceHandleSearch = useDebounce(() => {
if (!handleSearch || !autocompleteQueryWithoutFilters) {
return;
Expand Down Expand Up @@ -454,7 +468,7 @@ function SearchAutocompleteList({
}
}

if (!autocompleteQueryValue && recentSearchesData && recentSearchesData.length > 0) {
if (!hasEffectiveInputQuery && recentSearchesData && recentSearchesData.length > 0) {
pushSection({title: translate('search.recentSearches'), data: recentSearchesData as AutocompleteListItem[], sectionIndex: sectionIndex++});
}

Expand Down Expand Up @@ -487,7 +501,7 @@ function SearchAutocompleteList({
/>
);

if (autocompleteQueryValue.trim() === '') {
if (!hasEffectiveInputQuery) {
// Empty query: single "Recent chats" section
if (!isLoadingOptions) {
pushSection({title: translate('search.recentChats'), data: nextStyledRecentReports, sectionIndex: sectionIndex++});
Expand Down Expand Up @@ -536,7 +550,7 @@ function SearchAutocompleteList({
}
}

if (autocompleteSuggestions.length > 0) {
if (!isInputAheadOfDebounce && autocompleteSuggestions.length > 0) {
const autocompleteData: AutocompleteListItem[] = autocompleteSuggestions.map(({filterKey, text, autocompleteID, mapKey}) => {
return {
text: getAutocompleteDisplayText(filterKey, text),
Expand All @@ -554,7 +568,8 @@ function SearchAutocompleteList({

return {sections: nextSections, styledRecentReports: nextStyledRecentReports, suggestionsCount: nextSuggestionsCount};
}, [
autocompleteQueryValue,
hasEffectiveInputQuery,
isInputAheadOfDebounce,
autocompleteSuggestions,
expensifyIcons,
frozenLocalRank,
Expand Down
26 changes: 19 additions & 7 deletions src/components/Search/SearchRouter/SearchRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,9 @@ function SearchRouter({onRouterClose, shouldHideInputCaret, isSearchRouterDispla

const initialQuery = peekPendingRouterQuery();

// The actual input text that the user sees
const [textInputValue, , setTextInputValue] = useDebouncedState(initialQuery, 500);
// The input text that was last used for autocomplete; needed for the SearchAutocompleteList when browsing list via arrow keys
const [autocompleteQueryValue, setAutocompleteQueryValue] = useState(initialQuery);
const [textInputValue, setTextInputValue] = useState(initialQuery);
// Debounced value gates expensive filtering in the autocomplete list
const [, debouncedAutocompleteQueryValue, setAutocompleteQueryValue] = useDebouncedState(initialQuery, CONST.TIMING.SEARCH_OPTION_LIST_DEBOUNCE_TIME);
const [selection, setSelection] = useState({start: initialQuery.length, end: initialQuery.length});

useEffect(() => {
Expand Down Expand Up @@ -263,7 +262,7 @@ function SearchRouter({onRouterClose, shouldHideInputCaret, isSearchRouterDispla
setAutocompleteSubstitutions(updatedSubstitutionsMap);
}
},
[autocompleteSubstitutions, setTextInputValue, textInputValue],
[autocompleteSubstitutions, setAutocompleteQueryValue, setTextInputValue, textInputValue],
);

const submitSearch = useCallback(
Expand All @@ -286,7 +285,7 @@ function SearchRouter({onRouterClose, shouldHideInputCaret, isSearchRouterDispla
setTextInputValue('');
setAutocompleteQueryValue('');
},
[autocompleteSubstitutions, currentUserAccountID, onRouterClose, setTextInputValue, setShouldResetSearchQuery],
[autocompleteSubstitutions, currentUserAccountID, onRouterClose, setAutocompleteQueryValue, setTextInputValue, setShouldResetSearchQuery],
);

const onListItemPress = useCallback(
Expand Down Expand Up @@ -399,6 +398,18 @@ function SearchRouter({onRouterClose, shouldHideInputCaret, isSearchRouterDispla
onSearchQueryChange={onSearchQueryChange}
onSubmit={() => {
const focusedOption = listRef.current?.getFocusedOption?.();
const isInputAheadOfDebounce = !!textInputValue && textInputValue !== debouncedAutocompleteQueryValue;

// During the debounce window, keep keyboard behavior for focused search rows
// (e.g. Ask Concierge / typed query row), but avoid stale non-search row submits.
if (isInputAheadOfDebounce) {
if (focusedOption && isSearchQueryItem(focusedOption)) {
onListItemPress(focusedOption);
return;
}
submitSearch(textInputValue);
return;
}

if (!focusedOption) {
submitSearch(textInputValue);
Expand All @@ -419,7 +430,8 @@ function SearchRouter({onRouterClose, shouldHideInputCaret, isSearchRouterDispla
/>
</View>
<DeferredAutocompleteList
autocompleteQueryValue={autocompleteQueryValue || textInputValue}
autocompleteQueryValue={textInputValue === '' ? '' : debouncedAutocompleteQueryValue}
Comment thread
abbasifaizan70 marked this conversation as resolved.
inputQueryValue={textInputValue}
handleSearch={searchInServer}
searchQueryItems={searchQueryItems}
getAdditionalSections={getAdditionalSections}
Expand Down
15 changes: 6 additions & 9 deletions src/libs/OptionsListUtils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -513,18 +513,15 @@ function getAlternateText(
* Searches for a match when provided with a value
*/
function isSearchStringMatch(searchValue: string, searchText?: string | null, participantNames = new Set<string>(), isReportChatRoom = false): boolean {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please explain changes in this function?
What's this refactor for? Does this improve performance?
And add unit tests

@abbasifaizan70 abbasifaizan70 Apr 8, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aimane-chnaif This change is mainly a perf cleanup in a hot path, not intended to change behavior. Before, isSearchStringMatch() was creating a new RegExp inside the loop for every search word. Since this function runs across lots of options while typing, that adds unnecessary work on each keystroke.

What I changed:

  • normalize/dedupe the words once
  • compile the regexes once per function call
  • reuse them in the loop
  • return early on first mismatch

So matching logic stays the same, but we avoid repeated regex allocations and reduce JS work during search filtering.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, please add unit test (not perf-test) for this function

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aimane-chnaif Added unit test in OptionsListUtilsTest.tsx covering the refactored isSearchStringMatch path (multi-word normalization behavior), and it passes.

const searchWords = new Set(searchValue.replaceAll(',', ' ').split(/\s+/));
const searchWords = Array.from(new Set(searchValue.replaceAll(',', ' ').split(/\s+/).filter(Boolean)));
const valueToSearch = searchText?.replaceAll(new RegExp(/&nbsp;/g), '');
let matching = true;
for (const word of searchWords) {
// if one of the word is not matching, we don't need to check further
if (!matching) {
continue;
const compiledRegexes = searchWords.map((word) => ({word, regex: new RegExp(Str.escapeForRegExp(word), 'i')}));
for (const {word, regex} of compiledRegexes) {
if (!(regex.test(valueToSearch ?? '') || (!isReportChatRoom && participantNames.has(word)))) {
return false;
}
const matchRegex = new RegExp(Str.escapeForRegExp(word), 'i');
matching = matchRegex.test(valueToSearch ?? '') || (!isReportChatRoom && participantNames.has(word));
}
return matching;
return true;
}

function isSearchStringMatchUserDetails(personalDetail: PersonalDetails, searchValue: string) {
Expand Down
31 changes: 30 additions & 1 deletion tests/perf-test/OptionsListUtils.perf-test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {rand} from '@ngneat/falso';
import type * as NativeNavigation from '@react-navigation/native';
import type {OnyxEntry} from 'react-native-onyx';
import Onyx from 'react-native-onyx';
import {measureFunction} from 'reassure';
import type {PrivateIsArchivedMap} from '@hooks/usePrivateIsArchivedMap';
Expand All @@ -8,6 +9,7 @@ import type {OptionData} from '@libs/ReportUtils';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {PersonalDetails, Policy} from '@src/types/onyx';
import type Login from '@src/types/onyx/Login';
import type Report from '@src/types/onyx/Report';
import {formatSectionsFromSearchTerm} from '../../src/libs/OptionsListUtils';
import createCollection from '../utils/collections/createCollection';
Expand All @@ -19,6 +21,9 @@ import waitForBatchedUpdates from '../utils/waitForBatchedUpdates';

const REPORTS_COUNT = 5000;
const PERSONAL_DETAILS_LIST_COUNT = 1000;
// Larger dataset used specifically to measure the isSearchStringMatch RegExp optimization
const LARGE_REPORTS_COUNT = 40000;
const LARGE_PERSONAL_DETAILS_COUNT = 5000;
const SEARCH_VALUE = 'Report';
const COUNTRY_CODE = 1;

Expand Down Expand Up @@ -106,7 +111,7 @@ const ValidOptionsConfig = {
sortedActions: undefined,
};

const loginList = {};
const loginList: OnyxEntry<Login> = {};

/* GetOption is the private function and is never called directly, we are testing the functions which call getOption with different params */
describe('OptionsListUtils', () => {
Expand Down Expand Up @@ -289,6 +294,30 @@ describe('OptionsListUtils', () => {
);
});

// This test directly measures the isSearchStringMatch hot path.
// A multi-word query forces one RegExp creation per word per item on main;
// on the PR branch RegExps are compiled once per call, so duration drops significantly.
test('[OptionsListUtils] filterAndOrderOptions with multi-word search on large dataset', async () => {
const largePersonalDetails = getMockedPersonalDetails(LARGE_PERSONAL_DETAILS_COUNT);
const largeReports = getMockedReports(LARGE_REPORTS_COUNT);
const largeOptionList = createOptionList(largePersonalDetails, EMPTY_PRIVATE_IS_ARCHIVED_MAP, largeReports, undefined);

const {options: formattedOptions} = getValidOptions(
{reports: largeOptionList.reports, personalDetails: largeOptionList.personalDetails},
allPolicies,
{},
loginList,
MOCK_CURRENT_USER_ACCOUNT_ID,
MOCK_CURRENT_USER_EMAIL,
undefined,
ValidOptionsConfig,
);

await measureFunction(() => {
filterAndOrderOptions(formattedOptions, 'Email Report Five', COUNTRY_CODE, loginList, MOCK_CURRENT_USER_EMAIL, MOCK_CURRENT_USER_ACCOUNT_ID, largePersonalDetails);
});
});

test('[OptionsListUtils] getSearchOptions with isSearching is true', async () => {
await waitForBatchedUpdates();
const optionLists = createFilteredOptionList(personalDetails, mockedReportsMap, undefined, EMPTY_PRIVATE_IS_ARCHIVED_MAP, undefined, {
Expand Down
25 changes: 25 additions & 0 deletions tests/perf-test/SearchRouter.perf-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -190,3 +190,28 @@ test('[SearchRouter] should react to text input changes', async () => {
)
.then(() => measureRenders(<SearchAutocompleteInputWrapper />, {scenario}));
});

test('[SearchRouter] should re-render minimally when typing into the full router with autocomplete list', async () => {
const scenario = async () => {
const input = await screen.findByTestId('search-autocomplete-text-input');
fireEvent.changeText(input, 'R');
fireEvent.changeText(input, 'Re');
fireEvent.changeText(input, 'Rep');
fireEvent.changeText(input, 'Repo');
fireEvent.changeText(input, 'Report');
fireEvent.changeText(input, 'Report F');
fireEvent.changeText(input, 'Report Fi');
fireEvent.changeText(input, 'Report Five');
};

return waitForBatchedUpdates()
.then(() =>
Onyx.multiSet({
...mockedReports,
[ONYXKEYS.PERSONAL_DETAILS_LIST]: mockedPersonalDetails,
[ONYXKEYS.BETAS]: mockedBetas,
[ONYXKEYS.RAM_ONLY_IS_SEARCHING_FOR_REPORTS]: true,
}),
)
.then(() => measureRenders(<SearchRouterWrapperWithCachedOptions />, {scenario}));
});
25 changes: 24 additions & 1 deletion tests/unit/OptionsListUtilsTest.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ import ONYXKEYS from '@src/ONYXKEYS';
import type {PersonalDetails, Policy, Report, ReportAction, ReportNameValuePairs, Transaction} from '@src/types/onyx';
import type {ReportAttributes} from '@src/types/onyx/DerivedValues';
import type {Attendee, Participant} from '@src/types/onyx/IOU';
import type Login from '@src/types/onyx/Login';
import createRandomReportAction from '../utils/collections/reportActions';
import {createRandomReport, createRegularChat} from '../utils/collections/reports';
import createRandomTransaction from '../utils/collections/transaction';
Expand Down Expand Up @@ -636,7 +637,7 @@ describe('OptionsListUtils', () => {
},
];

const loginList = {};
const loginList: OnyxEntry<Login> = {};
const CURRENT_USER_ACCOUNT_ID = 2;
const CURRENT_USER_EMAIL = 'tonystark@expensify.com';

Expand Down Expand Up @@ -3250,6 +3251,28 @@ describe('OptionsListUtils', () => {
// Then the self dm should be on top.
expect(filteredOptions.recentReports.at(0)?.isSelfDM).toBe(true);
});

it('should return the same matches for normalized multi-word queries with extra spaces', () => {
const {options} = getSearchOptions({
options: OPTIONS,
reportAttributesDerived: MOCK_REPORT_ATTRIBUTES_DERIVED,
draftComments: {},
loginList,
betas: [CONST.BETAS.ALL],
currentUserAccountID: CURRENT_USER_ACCOUNT_ID,
currentUserEmail: CURRENT_USER_EMAIL,
policyCollection: allPolicies,
personalDetails: PERSONAL_DETAILS,
sortedActions: undefined,
conciergeReportID: undefined,
});

const multiSpaceQueryResults = filterAndOrderOptions(options, 'Invisible Woman', COUNTRY_CODE, loginList, CURRENT_USER_EMAIL, CURRENT_USER_ACCOUNT_ID, PERSONAL_DETAILS);
const spaceSeparatedQueryResults = filterAndOrderOptions(options, 'Invisible Woman', COUNTRY_CODE, loginList, CURRENT_USER_EMAIL, CURRENT_USER_ACCOUNT_ID, PERSONAL_DETAILS);

expect(multiSpaceQueryResults.recentReports.map((option) => option.reportID)).toEqual(spaceSeparatedQueryResults.recentReports.map((option) => option.reportID));
expect(multiSpaceQueryResults.personalDetails.map((option) => option.accountID)).toEqual(spaceSeparatedQueryResults.personalDetails.map((option) => option.accountID));
});
});

describe('canCreateOptimisticPersonalDetailOption()', () => {
Expand Down
Loading