The header used to pick its pills by `page === …` and import five module controls, and Plex's Discover/Browse toggle needed a context bridge to reach the sub-view that lives in PlexBrowse. Now the header is an empty band: PageShell hands its root box out as a slot, a page portals its own pills into it (PageHeader), and the palette's Jump-to pill shows where no page does (PageHeaderDefault, rendered inside the page's Suspense so a loading page shows no stand-in). Plex's toggles take the sub-view as a prop; PlexViewBridge and its context are gone, and with them the header's five layer suppressions. No visible change: every header pill's box measured identical against the previous build on eight pages, in Hungarian and English.
86 lines
3.1 KiB
TypeScript
86 lines
3.1 KiB
TypeScript
// @vitest-environment jsdom
|
|
//
|
|
// Ctrl/Cmd+K had no visible entry point (debt burn B28). The owner's pick: a key cap in the header's
|
|
// search field, and a "Jump to…" pill in its place on a page that has no search field. The shell
|
|
// composes it the way App does: the page's own pills (a search box here) through PageHeader, and the
|
|
// pill as the header's default.
|
|
import { describe, it, expect, vi, afterEach, beforeEach } from "vitest";
|
|
import { render, screen, fireEvent, cleanup, act } from "@testing-library/react";
|
|
|
|
vi.mock("react-i18next", async (importOriginal) => ({
|
|
...(await importOriginal<typeof import("react-i18next")>()),
|
|
useTranslation: () => ({ t: (k: string) => k, i18n: { language: "en" } }),
|
|
}));
|
|
vi.mock("../components/ui/PageScroller", () => ({
|
|
default: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
|
}));
|
|
|
|
import Header from "./Header";
|
|
import PageShell, { PageHeader, PageHeaderDefault } from "../components/ui/PageShell";
|
|
import SearchBar from "../features/header/SearchBar";
|
|
import { PaletteLauncher } from "../features/header/PaletteLauncher";
|
|
import { closePalette, usePaletteOpen } from "../lib/keymap";
|
|
|
|
beforeEach(() => {
|
|
vi.stubGlobal(
|
|
"ResizeObserver",
|
|
class {
|
|
observe() {}
|
|
disconnect() {}
|
|
},
|
|
);
|
|
});
|
|
afterEach(() => {
|
|
cleanup();
|
|
vi.unstubAllGlobals();
|
|
act(() => closePalette());
|
|
});
|
|
|
|
function mount(withSearch: boolean) {
|
|
let open = false;
|
|
function Probe() {
|
|
open = usePaletteOpen();
|
|
return null;
|
|
}
|
|
render(
|
|
<PageShell header={<Header />}>
|
|
{withSearch && (
|
|
<PageHeader>
|
|
<SearchBar value="" placeholder="search" onChange={() => {}} onGo={() => {}} />
|
|
</PageHeader>
|
|
)}
|
|
<PageHeaderDefault>
|
|
<PaletteLauncher />
|
|
</PageHeaderDefault>
|
|
<Probe />
|
|
</PageShell>,
|
|
);
|
|
return () => open;
|
|
}
|
|
|
|
describe("the command palette's visible entry", () => {
|
|
it("is a key cap inside the search field, naming the chord the palette row binds", () => {
|
|
const isOpen = mount(true);
|
|
const cap = screen.getByRole("button", { name: /^palette\.open \((Ctrl|⌘) K\)$/ });
|
|
// Label in Name: the name carries what the cap shows, so "click Ctrl K" finds it.
|
|
expect(cap.getAttribute("aria-label")).toContain(cap.textContent);
|
|
// …and a tooltip equal to that name, so a screen reader does not read it again as a description.
|
|
expect(cap.getAttribute("title")).toBe(cap.getAttribute("aria-label"));
|
|
expect(screen.getByRole("textbox")).toBeTruthy();
|
|
// The search field takes the pill's place: one entry, not two.
|
|
expect(screen.queryByRole("button", { name: /^palette\.title/ })).toBeNull();
|
|
fireEvent.click(cap);
|
|
expect(isOpen()).toBe(true);
|
|
});
|
|
|
|
it("is a Jump-to pill on a page with no search field", () => {
|
|
const isOpen = mount(false);
|
|
expect(screen.queryByRole("textbox")).toBeNull();
|
|
// Named by what it shows ("Jump to…" and its key cap), not by a label that replaced it.
|
|
const pill = screen.getByRole("button", { name: /^palette\.title ?(Ctrl|⌘) K$/ });
|
|
expect(pill.getAttribute("title")).toBe("palette.open");
|
|
fireEvent.click(pill);
|
|
expect(isOpen()).toBe(true);
|
|
});
|
|
});
|