// @vitest-environment jsdom // // E11 S4: `s` puts the caret in the module's search box from anywhere on the page, and Escape hands // the keyboard back to the page — the way in needs a way out, or `j`/`k` end up typed into the query. import { afterEach, describe, expect, it, vi } from "vitest"; import { act, cleanup, render, screen } from "@testing-library/react"; import SearchBar from "./SearchBar"; import KeyboardHelp from "./ui/KeyboardHelp"; import { closeKeyboardHelp, toggleKeyboardHelp } from "../lib/keymap"; import { pushLayer, removeLayer } from "../lib/layerStack"; vi.mock("react-i18next", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, useTranslation: () => ({ t: (key: string) => key }) }; }); afterEach(() => { cleanup(); closeKeyboardHelp(); }); function press(init: KeyboardEventInit, on?: Element | null): KeyboardEvent { const e = new KeyboardEvent("keydown", { bubbles: true, cancelable: true, ...init }); act(() => { (on ?? document.activeElement ?? document.body).dispatchEvent(e); }); return e; } const field = () => screen.getByPlaceholderText("Search…") as HTMLInputElement; describe("SearchBar keys", () => { it("focuses the box on s, selects the old query, and keeps the s out of it", () => { render( {}} placeholder="Search…" />); const e = press({ key: "s" }); expect(document.activeElement).toBe(field()); expect(e.defaultPrevented).toBe(true); // the `s` is not typed into the box it just focused expect([field().selectionStart, field().selectionEnd]).toEqual([0, "old query".length]); }); it("leaves an s typed into another field alone", () => { render( <> {}} placeholder="Search…" /> , ); const other = screen.getByTestId("other"); act(() => other.focus()); expect(press({ key: "s" }).defaultPrevented).toBe(false); expect(document.activeElement).toBe(other); }); it("stays quiet under a dialog", () => { render( {}} placeholder="Search…" />); const id = pushLayer(); press({ key: "s" }); removeLayer(id); expect(document.activeElement).not.toBe(field()); }); it("gives the keyboard back to the page on Escape", () => { render( {}} placeholder="Search…" />); press({ key: "s" }); press({ key: "Escape" }); expect(document.activeElement).toBe(document.body); }); // Plex's typeahead uses its first Escape to close the result panel; the field keeps focus for that. it("lets a caller keep its own Escape", () => { render( {}} placeholder="Search…" onKeyDown={(e) => e.key === "Escape" && e.preventDefault()} />, ); press({ key: "s" }); press({ key: "Escape" }); expect(document.activeElement).toBe(field()); }); // Through the real hook: the sheet offers `s` only where a search box is mounted. it("is listed in the help sheet while a search box is mounted", () => { const { unmount } = render( <> {}} placeholder="Search…" /> , ); act(() => toggleKeyboardHelp()); expect(screen.getAllByRole("definition").map((d) => d.textContent)).toContain("keys.search"); unmount(); closeKeyboardHelp(); render(); act(() => toggleKeyboardHelp()); expect(screen.getAllByRole("definition").map((d) => d.textContent)).not.toContain( "keys.search", ); }); // Opened over a player, the page keys are quiet — the sheet must not list them there. it("is left off a sheet opened over a keyboard-owning layer", () => { render( <> {}} placeholder="Search…" /> , ); const id = pushLayer(); act(() => toggleKeyboardHelp()); expect(screen.queryAllByRole("definition").map((d) => d.textContent)).not.toContain( "keys.search", ); removeLayer(id); }); });