Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
16 changes: 14 additions & 2 deletions src/components/UserSearch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -298,17 +298,29 @@ export const UserSearch: React.FC<UserSearchProps> = ({
const { uuids, filterOptions, renderOption, getOptionLabel, isOptionEqualToValue } =
useUserSearchOptions();
const [loading, setLoading] = React.useState(false);
const [inputValue, setInputValue] = React.useState("");

return (
<Autocomplete
<Autocomplete<SearchOption, false, true>
options={uuids.map((uuid) => ({ type: "uuid" as const, uuid }))}
fullWidth
size={size}
// TODO: Clear the input when the user selects an option
blurOnSelect
selectOnFocus
autoHighlight
Comment on lines +304 to 311
filterOptions={filterOptions}
inputValue={inputValue}
onInputChange={(_, newInputValue) => {
setInputValue(newInputValue);
}}
// Always pass our controlled inputValue to filterOptions so the
// "Search for X" option appears when reopening with text still in
// the input. MUI otherwise passes inputValue: '' when the popup
// reopens and the input matches the previously selected option's
// label, which suppresses the free-text option.
filterOptions={(options, params) =>
filterOptions?.(options, { ...params, inputValue }) ?? options
}
renderOption={renderOption}
getOptionLabel={getOptionLabel}
isOptionEqualToValue={isOptionEqualToValue}
Expand Down
32 changes: 32 additions & 0 deletions src/routes/session/-uuid.ui.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,38 @@ describe("Session detail page", () => {
.toBeInTheDocument();
});

mswTest(
"search shows free-text option after reopening with previously selected text",
async () => {
const { screen } = await renderAppRoute(sessionUrl);

const searchInput = screen.getByRole("combobox", {
name: "Search players",
});

// Wait for the page to populate known aliases for the current player
await expect
.poll(() => localStorage.getItem("knownAliases"))
.toContain(USERS.player1.username);

// Type the player's name and select the auto-highlighted user
// option with Enter. The selection sets MUI's internal value and
// fills the input with the username. Since we navigate to the
// same route, the search component stays mounted afterwards.
await searchInput.fill(USERS.player1.username);
await userEvent.keyboard("{Enter}");

// Reopen the popup. The input still holds "PlayerOne" from the
// selection — the free-text "Search for ..." option must still
// appear instead of being suppressed by MUI's empty-string trick.
await userEvent.click(searchInput);

await expect
.element(screen.getByRole("option", { name: /Search for/ }))
.toBeInTheDocument();
},
);

mswTest("renders player head image", async () => {
await renderAppRoute(sessionUrl);

Expand Down