feat(design): the field family, the dropdown trigger, native controls, the no-raw-input lint rule (E28 S3c)
- ui/Input, Textarea, InnerInput + fieldClass (A4): the >= 3:1 edge, a 2px accent focus edge, the danger edge for invalid, md/sm/xs/heading rungs, 1rem and 44 on touch - ui/SelectTrigger (A5): SortControl, FilterSelect, the view switcher and the date filter on one trigger, a pill in a toolbar, r8 in a form - ui/NativeControls (A6): Checkbox, CheckRow, Slider, ColorInput - every raw input swept (~100 sites); inputCls retired; local/input-primitive - useReturnFocus: an in-place editor hands the focus back to its trigger on Enter/Escape (the playlist and Plex title renames, the priority number); the Plex playlist rename gains Escape and an accessible name - back to top: a round 48 FAB (owner decision); DESIGN.md A2 and the log
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
// @vitest-environment jsdom
|
||||
//
|
||||
// E28 S3c: 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 { 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("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");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user