Skip to content
Merged
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
31 changes: 31 additions & 0 deletions frontend/src/components/search/SearchAutocomplete.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,37 @@ describe("SearchAutocomplete", () => {
expect(onClose).toHaveBeenCalled();
});

// ─── Backdrop overlay (#982) ──────────────────────────────────────────────

it("renders backdrop overlay when dropdown is visible", () => {
render(
<SearchAutocomplete {...defaultProps} query="" show={true} />,
{ wrapper: createWrapper() },
);
const backdrop = screen.getByTestId("autocomplete-backdrop");
expect(backdrop).toBeInTheDocument();
expect(backdrop).toHaveAttribute("aria-hidden", "true");
});

it("does not render backdrop when show is false", () => {
render(
<SearchAutocomplete {...defaultProps} show={false} />,
{ wrapper: createWrapper() },
);
expect(screen.queryByTestId("autocomplete-backdrop")).not.toBeInTheDocument();
});

it("calls onClose when backdrop is clicked", () => {
const onClose = vi.fn();
render(
<SearchAutocomplete {...defaultProps} query="" onClose={onClose} />,
{ wrapper: createWrapper() },
);
const backdrop = screen.getByTestId("autocomplete-backdrop");
fireEvent.click(backdrop);
expect(onClose).toHaveBeenCalled();
Copy link

Copilot AI Mar 19, 2026

Choose a reason for hiding this comment

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

This test only asserts that onClose was called, but with the current implementation a backdrop click can invoke onClose twice (document mousedown outside-click handler + backdrop onClick) when onClose doesn’t immediately unmount the component (as in this test). Strengthen this assertion to toHaveBeenCalledTimes(1) (and/or adjust the interaction to match the chosen dismissal event) so the test catches duplicate-close regressions.

Suggested change
expect(onClose).toHaveBeenCalled();
expect(onClose).toHaveBeenCalledTimes(1);

Copilot uses AI. Check for mistakes.
});

it("shows loading state while fetching suggestions", () => {
// Return a never-resolving promise to simulate loading
mockSearchAutocomplete.mockReturnValue(new Promise(() => {}));
Expand Down
23 changes: 16 additions & 7 deletions frontend/src/components/search/SearchAutocomplete.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -292,13 +292,21 @@ export function SearchAutocomplete({
: t(dropdownLabelKey);

return (
<div
ref={containerRef}
id="search-autocomplete-listbox"
role="listbox"
aria-label={dropdownLabel}
className="absolute left-0 right-0 top-full z-50 mt-1 max-h-80 overflow-y-auto rounded-xl border bg-surface shadow-lg"
>
<>
{/* Backdrop overlay — dims content behind the dropdown */}
<div
className="fixed inset-0 z-40 bg-black/20 animate-[backdropIn_150ms_var(--ease-standard)]"
onClick={onClose}
Copy link

Copilot AI Mar 19, 2026

Choose a reason for hiding this comment

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

Clicking the backdrop will currently trigger onClose via two paths: (1) the existing document-level mousedown outside-click handler (because the backdrop is not contained in containerRef), and (2) this backdrop’s onClick. In real usage this is often masked by unmounting on mousedown, but it can still lead to duplicate onClose calls (and makes tests less precise). Suggest either removing/adjusting the document mousedown listener when the backdrop is present, or handling dismissal on the backdrop’s onMouseDown with stopPropagation() and dropping the onClick handler.

Suggested change
onClick={onClose}
onMouseDown={(event) => {
event.stopPropagation();
onClose();
}}

Copilot uses AI. Check for mistakes.
aria-hidden="true"
data-testid="autocomplete-backdrop"
/>
Comment on lines +297 to +302
Copy link

Copilot AI Mar 19, 2026

Choose a reason for hiding this comment

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

The fixed inset-0 backdrop will sit above the search input/clear button (which don’t have a higher z-index) and will intercept clicks there, immediately dismissing the dropdown and preventing users from placing the caret / clicking the clear button while suggestions are open. If the intent is to dim only the page content behind the dropdown (not the input), consider scoping the backdrop to start below the input (e.g., top offset) or ensuring the input wrapper is stacked above the backdrop (higher z-index) so it remains interactive.

Copilot uses AI. Check for mistakes.
<div
ref={containerRef}
id="search-autocomplete-listbox"
role="listbox"
aria-label={dropdownLabel}
className="absolute left-0 right-0 top-full z-50 mt-1 max-h-80 overflow-y-auto rounded-xl border bg-surface shadow-lg"
>
{/* ── Recent searches (empty-query mode) ────────────────────── */}
{showRecent && (
<>
Expand Down Expand Up @@ -522,6 +530,7 @@ export function SearchAutocomplete({
</>
)}
</div>
</>
);
}

Expand Down
Loading