Skip to content
Open
17 changes: 14 additions & 3 deletions src/components/Search/SearchAutocompleteList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,18 @@ function SearchRouterItem(props: UserListItemProps<AutocompleteListItem> | Searc
const styles = useThemeStyles();

if (isSearchQueryListItem(props)) {
return <SearchQueryListItem {...props} />;
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.

Why do we need these changes?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

The react/jsx-props-no-spreading ESLint rule forbids JSX prop spreading the {...props} here caused a CI failure.

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.

Don't see any issues with the pipeline in this place in your previous commits
Do you have this issue locally?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Ran it locally. npx eslint src/components/Search/SearchAutocompleteList.tsx throws error Prop spreading is forbidden react/jsx-props-no-spreading at line 111. CI also failed on that check in the run right before this fix.

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.

Are you sure?

Снимок экрана — 2026-05-20 в 22 42 12

And update node_modules and try recheck, please

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

You're right, the original had an eslint-disable-next-line comment suppressing it. I removed that and replaced the spread with explicit props instead, which is the proper fix rather than suppressing the rule. Happy to revert to the disable comment if preferred.

Copy link
Copy Markdown
Contributor

@ZhenjaHorbach ZhenjaHorbach May 20, 2026

Choose a reason for hiding this comment

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

Where? 😅
Image

And I just want to avoid changes that are not related to the fix in our PR
So let's revert these changes

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Reverted. My bad that eslint-disable comment was introduced by me in this PR, not from main. Reverted to the plain spread

const {item, isFocused, showTooltip, onSelectRow, onFocus, shouldSyncFocus, shouldDisableHoverStyle} = props;
return (
<SearchQueryListItem
item={item}
isFocused={isFocused}
showTooltip={showTooltip}
onSelectRow={onSelectRow}
onFocus={onFocus}
shouldSyncFocus={shouldSyncFocus}
shouldDisableHoverStyle={shouldDisableHoverStyle}
/>
);
}

const {item, isFocused, showTooltip, isDisabled, onSelectRow, onDismissError, shouldPreventEnterKeySubmit, rightHandSideComponent, onFocus, shouldSyncFocus, wrapperStyle} = props;
Expand Down Expand Up @@ -374,11 +385,11 @@ function SearchAutocompleteList({

const reportOptions: OptionData[] = [...orderedOptions.recentReports, ...orderedOptions.personalDetails];
if (searchOptions.userToInvite) {
reportOptions.push(searchOptions.userToInvite);
reportOptions.push({...searchOptions.userToInvite, alternateText: translate('common.invite')});
}

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

const debounceHandleSearch = useDebounce(() => {
if (!handleSearch || !autocompleteQueryWithoutFilters) {
Expand Down
11 changes: 9 additions & 2 deletions src/libs/OptionsListUtils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1911,13 +1911,20 @@ function canCreateOptimisticPersonalDetailOption({
currentUserOption?: SearchOptionData | null;
searchValue: string;
}) {
if (recentReportOptions.length + personalDetailsOptions.length > 0) {
const normalizedSearchValue = addSMSDomainIfPhoneNumber(searchValue ?? '').toLowerCase();
const rawSearchValue = (searchValue ?? '').toLowerCase();
const matchesLogin = (login: string | undefined) => {
const normalizedLogin = login?.toLowerCase();
return normalizedLogin === normalizedSearchValue || normalizedLogin === rawSearchValue;
};
const hasExactLoginMatch = recentReportOptions.some((o) => matchesLogin(o.login)) || personalDetailsOptions.some((o) => matchesLogin(o.login));
if (hasExactLoginMatch) {
return false;
}
if (!currentUserOption) {
return true;
}
return currentUserOption.login !== addSMSDomainIfPhoneNumber(searchValue ?? '').toLowerCase() && currentUserOption.login !== searchValue?.toLowerCase();
return currentUserOption.login !== normalizedSearchValue && currentUserOption.login !== rawSearchValue;
}

/**
Expand Down
60 changes: 59 additions & 1 deletion tests/ui/components/SearchAutocompleteListTest.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {act, render} from '@testing-library/react-native';
import {act, render, screen} from '@testing-library/react-native';
import React from 'react';
import Onyx from 'react-native-onyx';
import {LocaleContextProvider} from '@components/LocaleContextProvider';
Expand Down Expand Up @@ -128,6 +128,64 @@ describe('SearchAutocompleteList', () => {
expect(mockHtmlToText).not.toHaveBeenCalled();
});

it('should set alternateText to "Invite" on the userToInvite row when autocompleteQueryValue is non-empty', async () => {
// Regression test for #88730: when userToInvite is present and the query is non-empty,
// recentReportsOptions spreads it with alternateText: translate('common.invite').
type OptionsListUtilsMock = {getSearchOptions: jest.Mock; combineOrderingOfReportsAndPersonalDetails: jest.Mock};
const OptionsListUtils: OptionsListUtilsMock = jest.requireMock('@libs/OptionsListUtils');

const inviteOption = {
reportID: undefined,
keyForList: 'unknown@example.com',
login: 'unknown@example.com',
text: 'unknown@example.com',
alternateText: '',
};

OptionsListUtils.getSearchOptions.mockImplementation(() => ({
recentReports: [],
personalDetails: [],
currentUserOption: null,
userToInvite: inviteOption,
categoryOptions: [],
}));
OptionsListUtils.combineOrderingOfReportsAndPersonalDetails.mockImplementation(() => ({recentReports: [], personalDetails: []}));

render(
<OnyxListItemProvider>
<LocaleContextProvider>
<SearchAutocompleteList
autocompleteQueryValue="unknown@example.com"
handleSearch={jest.fn()}
onListItemPress={jest.fn()}
/>
</LocaleContextProvider>
</OnyxListItemProvider>,
);

await waitForBatchedUpdatesWithAct();

expect(screen.getByText('Invite')).toBeTruthy();

// Restore default mock so other tests are not affected
OptionsListUtils.getSearchOptions.mockImplementation(() => ({
recentReports: [
{
reportID: '10',
keyForList: '10',
text: 'Test Report',
alternateText: 'alternate text',
lastMessageText: 'last message',
},
],
personalDetails: [],
currentUserOption: null,
userToInvite: null,
categoryOptions: [],
}));
OptionsListUtils.combineOrderingOfReportsAndPersonalDetails.mockImplementation(() => ({recentReports: [], personalDetails: []}));
});

it('should call Parser.htmlToText when parentReportAction is not ADD_COMMENT', async () => {
const reportID = '10';
const parentReportID = '20';
Expand Down
Loading
Loading