- 51 headless Web Components (45 core + 6 animation) - Shared helpers: emit(), part(), listen(), wireLabel(), initialValue() - Zero `new CustomEvent` or `static #counter` — all use shared utils - Zod schemas for all 44 core components - MCP package with discover, inspect, compose, validate tools - Showcase with Aperture Science theme, M3 Expressive motion - 81 tests passing, TypeScript strict mode clean - Signals (~500B), SPA router (~400B), zero dependencies
53 lines
2.0 KiB
TypeScript
53 lines
2.0 KiB
TypeScript
import { describe, it, expect, beforeEach } from "vitest";
|
|
import { PettyTextField } from "../src/components/text-field/index";
|
|
import { h } from "./helpers";
|
|
|
|
describe("petty-text-field", () => {
|
|
let el: PettyTextField;
|
|
|
|
beforeEach(() => {
|
|
document.body.textContent = "";
|
|
el = document.createElement("petty-text-field") as PettyTextField;
|
|
el.setAttribute("name", "email");
|
|
el.appendChild(h("label", { "data-part": "label" }, "Email"));
|
|
el.appendChild(h("input", { "data-part": "control", type: "email" }));
|
|
el.appendChild(h("span", { "data-part": "description" }, "Enter your email"));
|
|
el.appendChild(h("span", { "data-part": "error" }));
|
|
document.body.appendChild(el);
|
|
});
|
|
|
|
it("registers the custom element", () => {
|
|
expect(customElements.get("petty-text-field")).toBe(PettyTextField);
|
|
});
|
|
|
|
it("wires label htmlFor to control id", () => {
|
|
const label = el.querySelector("label") as HTMLLabelElement;
|
|
const input = el.querySelector("input") as HTMLInputElement;
|
|
expect(input.id).not.toBe("");
|
|
expect(label.htmlFor).toBe(input.id);
|
|
});
|
|
|
|
it("sets aria-describedby on control", () => {
|
|
const input = el.querySelector("input") as HTMLInputElement;
|
|
const describedBy = input.getAttribute("aria-describedby")!;
|
|
expect(describedBy.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it("setError marks control as aria-invalid", () => {
|
|
el.setError("Required field");
|
|
const input = el.querySelector("input") as HTMLInputElement;
|
|
const error = el.querySelector("[data-part=error]")!;
|
|
expect(input.getAttribute("aria-invalid")).toBe("true");
|
|
expect(error.textContent).toBe("Required field");
|
|
});
|
|
|
|
it("clearError removes invalid state", () => {
|
|
el.setError("Required");
|
|
el.clearError();
|
|
const input = el.querySelector("input") as HTMLInputElement;
|
|
const error = el.querySelector("[data-part=error]")!;
|
|
expect(input.hasAttribute("aria-invalid")).toBe(false);
|
|
expect(error.textContent).toBe("");
|
|
});
|
|
});
|