Files
siftlode/frontend/src/components/ui/Input.dom.test.tsx
T

92 lines
3.6 KiB
TypeScript

// @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 { 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");
});
});