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.
104 lines
4.0 KiB
TypeScript
104 lines
4.0 KiB
TypeScript
// @vitest-environment jsdom
|
|
//
|
|
// The field primitives (docs/DESIGN.md A4-A6): one frame per rung, the native controls handing
|
|
// back values, and the dropdown trigger by context.
|
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
import { cleanup, fireEvent, render, screen } from "@testing-library/react";
|
|
import { fieldClass } from "./fieldStyles";
|
|
import { Input, Textarea } from "./Input";
|
|
import { CheckRow, Slider } from "./NativeControls";
|
|
import { SelectTrigger } from "./SelectTrigger";
|
|
|
|
afterEach(cleanup);
|
|
|
|
describe("Input", () => {
|
|
it("wears the A4 frame at the chosen rung and says when it is invalid", () => {
|
|
render(
|
|
<>
|
|
<Input aria-label="name" />
|
|
<Input aria-label="search" size="sm" pill invalid />
|
|
</>,
|
|
);
|
|
const name = screen.getByRole("textbox", { name: "name" });
|
|
expect(name.className).toContain("edge-strong");
|
|
expect(name.className).toContain("h-8");
|
|
expect(name.className).toContain("rounded-lg");
|
|
expect(name.getAttribute("aria-invalid")).toBeNull();
|
|
const search = screen.getByRole("textbox", { name: "search" });
|
|
expect(search.className).toContain("h-7");
|
|
expect(search.className).toContain("rounded-full");
|
|
expect(search.getAttribute("aria-invalid")).toBe("true");
|
|
});
|
|
|
|
it("dims a wrapper frame for its disabled FIELD, never for a disabled button beside it", () => {
|
|
// The header search holds a Go button that is disabled while the query is empty; a bare
|
|
// `has-disabled:` faded the whole search box to 50% on the feed.
|
|
const frame = fieldClass({ size: "free", pill: true, floating: true });
|
|
expect(frame).not.toMatch(/(^| )has-disabled:/);
|
|
expect(frame).not.toMatch(/(^| )has-aria-invalid:/);
|
|
expect(frame).toContain("has-[input:disabled]:opacity-50");
|
|
});
|
|
|
|
it("lets a caller lay it out but not re-paint its edge", () => {
|
|
render(<Textarea aria-label="body" className="w-full" />);
|
|
const body = screen.getByRole("textbox", { name: "body" });
|
|
expect(body.className).toContain("w-full");
|
|
expect(body.className).toContain("edge-strong");
|
|
});
|
|
});
|
|
|
|
describe("the native controls", () => {
|
|
it("hand back a boolean and a number, not the event", () => {
|
|
const onCheck = vi.fn();
|
|
const onSlide = vi.fn();
|
|
render(
|
|
<>
|
|
<CheckRow checked={false} onChange={onCheck}>
|
|
Only unwatched
|
|
</CheckRow>
|
|
<Slider aria-label="size" min={0} max={10} value={3} onChange={onSlide} />
|
|
</>,
|
|
);
|
|
// The label is the target: a click on its text toggles the box.
|
|
fireEvent.click(screen.getByText("Only unwatched"));
|
|
expect(onCheck).toHaveBeenCalledWith(true);
|
|
fireEvent.change(screen.getByRole("slider", { name: "size" }), { target: { value: "7" } });
|
|
expect(onSlide).toHaveBeenCalledWith(7);
|
|
});
|
|
});
|
|
|
|
describe("SelectTrigger", () => {
|
|
it("is a pill in a toolbar, r8 and full width in a form, and shows open", () => {
|
|
render(
|
|
<>
|
|
<SelectTrigger context="toolbar" open={false} label="Sort">
|
|
Newest
|
|
</SelectTrigger>
|
|
<SelectTrigger context="form" open>
|
|
MP4
|
|
</SelectTrigger>
|
|
</>,
|
|
);
|
|
const toolbar = screen.getByRole("button", { name: /Newest/ });
|
|
const form = screen.getByRole("button", { name: "MP4" });
|
|
expect(toolbar.textContent).toBe("Sort:Newest");
|
|
expect(toolbar.className).toContain("rounded-full");
|
|
expect(toolbar.className).toContain("border-border");
|
|
expect(form.className).toContain("rounded-lg");
|
|
expect(form.className).toContain("w-full");
|
|
expect(form.className).toContain("border-accent");
|
|
});
|
|
|
|
it("wears the field's ≥ 3:1 edge in a form, among the inputs it lines up with (F14)", () => {
|
|
render(
|
|
<SelectTrigger context="form" open={false}>
|
|
MP4
|
|
</SelectTrigger>,
|
|
);
|
|
const form = screen.getByRole("button", { name: "MP4" });
|
|
expect(form.className).toContain("edge-strong");
|
|
expect(form.className).not.toContain("border-border");
|
|
expect(form.className).not.toContain("glass-card");
|
|
});
|
|
});
|