Files
siftlode/frontend/src/components/PlexViewToggle.dom.test.tsx
T
peter c3d88d9b20 refactor: component files export only components; lint fails on a warning
The 43 react-refresh/only-export-components warnings the gate let through meant an edit to a
provider (or AnchoredPopover, Toaster, plexDetailUi, …) could not be hot-swapped: Fast Refresh
reloads a module that also exports hooks or helpers.

- Each provider's contexts, the types its hooks return and the hooks themselves live in a sibling
  context module (navigationContext.ts, channelsContext.ts, …); the provider file keeps the
  component. loadAccountFilters moved next to the feed filters' defaults (feedFiltersInit.ts),
  following navigationInit/playlistsInit.
- Helpers that sat in component files have their own modules: welcomeMode, downloadTitle,
  plexPlayable, channelSync, ui/anchoredPopoverHooks, ui/pageToolbarSlot, ui/pagerContent,
  ui/toastLevels, ui/overlayOrder, ui/tableAlign, lib/plexDetail; the cells of the channel columns
  and the registry's page wrappers are components files of their own (channelCells, modulePages).
- usePersistedTab, an alias of useAccountPersistedState, is gone; its callers use the original.

npm run lint is now eslint --max-warnings 0, so a warning fails the gate like an error does.
2026-09-17 02:59:09 +02:00

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("./plexViewContext", () => ({
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();
});
});