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.
148 lines
5.4 KiB
TypeScript
148 lines
5.4 KiB
TypeScript
// @vitest-environment jsdom
|
||
//
|
||
// The chip family (docs/DESIGN.md "One primitive per button family"). One geometry for a
|
||
// filter, an applied narrowing, a tag and a suggestion, where five files had five.
|
||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||
import { act, cleanup, fireEvent, render, screen, waitFor } from "@testing-library/react";
|
||
import { useState } from "react";
|
||
import { Chip, DismissibleChip } from "./Chip";
|
||
import { chipClass } from "./chipStyles";
|
||
|
||
afterEach(cleanup);
|
||
|
||
describe("Chip", () => {
|
||
it("is a pill on the 24px floor that never submits a form, and a toggle announces its state", () => {
|
||
const onSubmit = vi.fn((e: { preventDefault: () => void }) => e.preventDefault());
|
||
render(
|
||
<form onSubmit={onSubmit}>
|
||
<Chip pressed={false}>2024</Chip>
|
||
</form>,
|
||
);
|
||
const chip = screen.getByRole("button", { name: "2024" });
|
||
expect(chip.className).toContain("rounded-full");
|
||
expect(chip.className).toContain("h-6");
|
||
expect(chip.className).toContain("touch-target");
|
||
expect(chip.getAttribute("aria-pressed")).toBe("false");
|
||
fireEvent.click(chip);
|
||
expect(onSubmit).not.toHaveBeenCalled();
|
||
});
|
||
|
||
it("a removing chip is named by the removal and shows the ×", () => {
|
||
const onClick = vi.fn();
|
||
render(
|
||
<Chip variant="token" size="md" removeLabel="Remove Music" onClick={onClick}>
|
||
Music
|
||
</Chip>,
|
||
);
|
||
const chip = screen.getByRole("button", { name: "Remove Music" });
|
||
expect(chip.getAttribute("title")).toBe("Remove Music");
|
||
expect(chip.className).toContain("h-7");
|
||
expect(chip.querySelector("svg")).not.toBeNull();
|
||
fireEvent.click(chip);
|
||
expect(onClick).toHaveBeenCalledOnce();
|
||
});
|
||
|
||
// The feed's and Plex's active-filter chips unmount with the filter they remove,
|
||
// and a keyboard press left the focus on <body>. The chip hands it on itself, for every caller.
|
||
it("a removing chip pressed from the keyboard hands the focus to its neighbour", () => {
|
||
function Row() {
|
||
const [chips, setChips] = useState(["Music", "News"]);
|
||
return (
|
||
<div>
|
||
{chips.map((c) => (
|
||
<Chip
|
||
key={c}
|
||
variant="token"
|
||
removeLabel={`Remove ${c}`}
|
||
onClick={() => setChips((all) => all.filter((x) => x !== c))}
|
||
>
|
||
{c}
|
||
</Chip>
|
||
))}
|
||
</div>
|
||
);
|
||
}
|
||
render(<Row />);
|
||
const music = screen.getByRole("button", { name: "Remove Music" });
|
||
act(() => music.focus());
|
||
fireEvent.click(music, { detail: 0 });
|
||
expect(screen.queryByRole("button", { name: "Remove Music" })).toBeNull();
|
||
expect(document.activeElement).toBe(screen.getByRole("button", { name: "Remove News" }));
|
||
});
|
||
|
||
// The neighbour it hands the focus to can go in the same removal — the "Clear all" that
|
||
// only shows beside enough filters. The focus then goes to the chip before, not to <body>.
|
||
it("a removing chip whose neighbour goes with it hands the focus to the chip before", async () => {
|
||
function Row() {
|
||
const [chips, setChips] = useState(["Music", "News", "Sport"]);
|
||
return (
|
||
<div>
|
||
{chips.map((c) => (
|
||
<Chip
|
||
key={c}
|
||
variant="token"
|
||
removeLabel={`Remove ${c}`}
|
||
onClick={() => setChips((all) => all.filter((x) => x !== c))}
|
||
>
|
||
{c}
|
||
</Chip>
|
||
))}
|
||
{chips.length > 2 && <button onClick={() => setChips([])}>Clear all</button>}
|
||
</div>
|
||
);
|
||
}
|
||
render(<Row />);
|
||
const sport = screen.getByRole("button", { name: "Remove Sport" });
|
||
act(() => sport.focus());
|
||
fireEvent.click(sport, { detail: 0 });
|
||
expect(screen.queryByRole("button", { name: "Clear all" })).toBeNull();
|
||
// the chip right before it — not the row's first control, which Music also is
|
||
await waitFor(() =>
|
||
expect(document.activeElement).toBe(screen.getByRole("button", { name: "Remove News" })),
|
||
);
|
||
});
|
||
|
||
it("the measuring mirror lays out exactly like the chip it measures", () => {
|
||
render(
|
||
<Chip variant="tag" size="xs">
|
||
tag
|
||
</Chip>,
|
||
);
|
||
const cls = screen.getByRole("button", { name: "tag" }).className;
|
||
for (const c of chipClass("tag", "xs").split(" ")) expect(cls).toContain(c);
|
||
});
|
||
});
|
||
|
||
describe("DismissibleChip", () => {
|
||
it("is two sibling controls at most, never a button in a button", () => {
|
||
const onAccept = vi.fn();
|
||
const onDismiss = vi.fn();
|
||
const { container } = render(
|
||
<DismissibleChip
|
||
variant="suggest"
|
||
onClick={onAccept}
|
||
bodyLabel="Add the tag Music"
|
||
onDismiss={onDismiss}
|
||
dismissLabel="Not now"
|
||
>
|
||
Music
|
||
</DismissibleChip>,
|
||
);
|
||
expect(container.querySelector("button button")).toBeNull();
|
||
fireEvent.click(screen.getByRole("button", { name: "Add the tag Music" }));
|
||
fireEvent.click(screen.getByRole("button", { name: "Not now" }));
|
||
expect(onAccept).toHaveBeenCalledOnce();
|
||
expect(onDismiss).toHaveBeenCalledOnce();
|
||
});
|
||
|
||
it("with no body action the label is text, and only the × is a control", () => {
|
||
render(
|
||
<DismissibleChip onDismiss={() => {}} dismissLabel="Remove Year: 2024">
|
||
Year: 2024
|
||
</DismissibleChip>,
|
||
);
|
||
expect(screen.getAllByRole("button")).toHaveLength(1);
|
||
expect(screen.getByText("Year: 2024").closest("button")).toBeNull();
|
||
});
|
||
});
|