The floating header wrapper is pointer-events-none (so clicks fall through its empty area to the content below); every interactive child must re-enable pointer events. PlexViewToggle rendered its SegmentedToggle without the pointer-events-auto wrapper the sibling PlexScopeToggle has, so the whole Discover/Browse switch was click-dead — the buttons rendered and were correctly wired, but no real mouse click ever reached them. Add `className="pointer-events-auto shrink-0"` to the toggle and a regression test that asserts the root re-enables pointer events (plus the render/onChange/hide-on-deeper-view contract). Verified live on localdev: real clicks now switch Discover⟷Browse (?v=browse) in both directions; a header-wide hit-test audit shows no other dead controls. Gate: tsc/lint/knip/prettier clean, 418 vitest.
66 lines
2.7 KiB
TypeScript
66 lines
2.7 KiB
TypeScript
// @vitest-environment jsdom
|
|
//
|
|
// Contract of the header Discover ⟷ Browse toggle. The floating header wrapper is pointer-events-none,
|
|
// so the toggle MUST re-enable pointer events on its root or it is click-dead (the bug this test guards
|
|
// against). Also: it only renders on the two title-browse views, marks the active one, and drives
|
|
// PlexBrowse's sub-view through the bridge. i18n is key-passthrough; the bridge is mocked.
|
|
import { describe, it, expect, afterEach, beforeEach, vi } from "vitest";
|
|
import { render, screen, fireEvent, cleanup } from "@testing-library/react";
|
|
|
|
vi.mock("react-i18next", async (importOriginal) => {
|
|
const actual = await importOriginal<typeof import("react-i18next")>();
|
|
return { ...actual, useTranslation: () => ({ t: (key: string) => key }) };
|
|
});
|
|
|
|
const controls = { kind: "discover" as string, open: vi.fn() };
|
|
vi.mock("./PlexViewBridge", () => ({
|
|
usePlexViewControls: () => controls,
|
|
}));
|
|
|
|
import PlexViewToggle from "./PlexViewToggle";
|
|
|
|
beforeEach(() => {
|
|
controls.kind = "discover";
|
|
controls.open.mockReset();
|
|
});
|
|
afterEach(cleanup);
|
|
|
|
describe("PlexViewToggle", () => {
|
|
it("re-enables pointer events on its root (the header wrapper is pointer-events-none)", () => {
|
|
const { container } = render(<PlexViewToggle />);
|
|
// Without this class the whole toggle is click-dead inside the pointer-events-none header.
|
|
expect(container.querySelector(".pointer-events-auto")).not.toBeNull();
|
|
});
|
|
|
|
it("renders the two options with the current view pressed", () => {
|
|
render(<PlexViewToggle />);
|
|
const btns = [
|
|
...screen.getByRole("group", { name: "plex.view.label" }).querySelectorAll("button"),
|
|
];
|
|
expect(btns.map((b) => b.textContent)).toEqual(["plex.discover.tab", "plex.view.browse"]);
|
|
const pressed = btns
|
|
.filter((b) => b.getAttribute("aria-pressed") === "true")
|
|
.map((b) => b.textContent);
|
|
expect(pressed).toEqual(["plex.discover.tab"]);
|
|
});
|
|
|
|
it("opens the grid view when Browse is picked", () => {
|
|
render(<PlexViewToggle />);
|
|
fireEvent.click(screen.getByRole("button", { name: "plex.view.browse" }));
|
|
expect(controls.open).toHaveBeenCalledWith({ kind: "grid" });
|
|
});
|
|
|
|
it("opens the discover view when Discover is picked from the grid", () => {
|
|
controls.kind = "grid";
|
|
render(<PlexViewToggle />);
|
|
fireEvent.click(screen.getByRole("button", { name: "plex.discover.tab" }));
|
|
expect(controls.open).toHaveBeenCalledWith({ kind: "discover" });
|
|
});
|
|
|
|
it("renders nothing on a deeper view (no Discover/Browse choice there)", () => {
|
|
controls.kind = "playlists";
|
|
const { container } = render(<PlexViewToggle />);
|
|
expect(container.firstChild).toBeNull();
|
|
});
|
|
});
|