Files
siftlode/frontend/src/components/PlexPlayerSettings.dom.test.tsx
T
peter 4aaaaa7860 chore(comments): drop epic, sprint and review-round ids from the code
The code cited its plan instead of saying why: `E28 review J4`, `(R6 S3b)`, `S1 justified…`,
`deep-review round 5`. That plan lives in the private ops repo, so for every reader of this one the
citation was noise, and a sentence whose subject was the plan step ("round 2 showed…") hid its
lesson behind an id. Each one is gone and the "why" kept: a bare citation is deleted, a sentence
carrying a lesson is reworded around it ("a later attempt showed…").

Measured (`scripts/audit_metrics.py`, product code): 652 lines in 270 files carried an epic code by
the definition the next commit holds (507 / 241 by the old, two-digit-only one) -> 0. Across all
code, tests, scripts and configs: ~1100 epic-code lines plus ~320 sprint/round ids.

Behaviour unchanged, checked mechanically: every changed .py file has the same AST once docstrings
are blanked; css/sh/toml/yml/json files are identical once comments are stripped; the only
non-comment changes in TS/JS are test titles, one description string in a contract table, and five
ESLint messages, whose text is all the gates snapshot diff holds. Not touched: Markdown (docs and
the changelog keep their history), Alembic migrations, TV episode notation (`S2 · E5`), DESIGN.md
ids (`C1`, `B4`, `S3h`), tracker ids (`SEC-16`, `DX-17`).

One stale claim fixed on the way: test_hls_reaper's docstring said the player checkpoints "every
10 s whether or not it is playing", which plex/stream.py calls wrong; it now says what the code does.
2026-10-05 00:19:39 +02:00

82 lines
3.7 KiB
TypeScript

// @vitest-environment jsdom
//
// The Plex player's settings panel. Its section switch was a row of
// `aria-pressed` buttons (now an ARIA tab list on `ui/Tabs`), its rows sat under the 44 touch target,
// its on/off rows were a hand-laid label (now `CheckRow`), and its sliders and colour wells had no
// accessible name — the visible label beside them was not tied to them.
import { useRef, useState } from "react";
import { afterEach, describe, expect, it, vi } from "vitest";
import { cleanup, fireEvent, render, screen } from "@testing-library/react";
vi.mock("react-i18next", async (importOriginal) => ({
...(await importOriginal<typeof import("react-i18next")>()),
useTranslation: () => ({ i18n: { language: "en" }, t: (k: string) => k }),
}));
import { PlexPlayerSettings } from "./PlexPlayerSettings";
import { DEFAULT_PREFS, type PlexPlayerPrefs } from "../lib/plexPlayerPrefs";
afterEach(cleanup);
type Tab = "sync" | "playback" | "subtitle" | "clock";
const TABS: { key: Tab; label: string }[] = [
{ key: "sync", label: "Sync" },
{ key: "playback", label: "Playback" },
{ key: "subtitle", label: "Subtitles" },
{ key: "clock", label: "Clock" },
];
function Harness({ start = "sync" as Tab, prefs: initial = {} as Partial<PlexPlayerPrefs> }) {
const [tab, setTab] = useState<Tab>(start);
const [prefs, setPrefs] = useState<PlexPlayerPrefs>({ ...DEFAULT_PREFS, ...initial });
const menuRef = useRef<HTMLDivElement>(null);
const back = useRef<HTMLElement>(null);
return (
<PlexPlayerSettings
menuRef={menuRef}
returnFocusTo={back}
settingsTabs={TABS}
activeTab={tab}
setSettingsTab={setTab}
prefs={prefs}
patchPrefs={(p) => setPrefs((cur) => ({ ...cur, ...p }))}
changeAudioOffset={() => {}}
now={new Date(2026, 8, 27, 12, 0)}
/>
);
}
const tab = (name: string) => screen.getByRole("tab", { name });
describe("the Plex player's settings panel", () => {
it("switches its sections as a tab list, the focus on the selected tab", () => {
render(<Harness start="subtitle" />);
expect(screen.getByRole("tablist")).toBeTruthy();
expect(document.activeElement).toBe(tab("Subtitles"));
fireEvent.keyDown(document.activeElement!, { key: "ArrowRight" });
expect(tab("Clock").getAttribute("aria-selected")).toBe("true");
expect(screen.getByRole("tabpanel", { name: "Clock" })).toBeTruthy();
});
it("gives every slider and colour well the name its row shows", () => {
render(<Harness start="subtitle" prefs={{ subBg: "#000000", subShadow: 2 }} />);
for (const name of ["plex.player.subSize", "plex.player.subPos", "plex.player.subShadow"])
expect(screen.getByRole("slider", { name })).toBeTruthy();
for (const name of ["plex.player.subColor", "plex.player.subBg", "plex.player.subShadowColor"])
expect(screen.getByLabelText(name).getAttribute("type")).toBe("color");
fireEvent.click(tab("Clock"));
expect(screen.getByLabelText("plex.player.clockColor").getAttribute("type")).toBe("color");
});
it("lays its on/off rows on CheckRow and its buttons at the touch target", () => {
render(<Harness start="clock" prefs={{ clockDate: true }} />);
const row = screen.getByRole("checkbox", { name: "plex.player.clockShow" }).closest("label")!;
expect(row.className).toContain("pointer-coarse:min-h-(--touch-target)");
for (const b of screen.getAllByRole("button")) expect(b.className).toContain("touch-target");
for (const t of screen.getAllByRole("tab")) expect(t.className).toContain("touch-target");
fireEvent.click(tab("Subtitles"));
const none = screen.getByRole("checkbox", { name: "plex.player.subBgNone" }).closest("label")!;
expect(none.className).toContain("pointer-coarse:min-h-(--touch-target)");
});
});