- checkboxClass/radioClass(accent-cinnabar + 统一焦点环)+ 同名测试 - Checkbox/Radio 原子件:无 label 只渲染受控原生 input 便于就地替换 - 采用:ChainStarter 链类型单选、GeneratorRunner 入库勾选、CharacterCardItem 角色勾选
135 lines
4.5 KiB
TypeScript
135 lines
4.5 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
|
|
import {
|
|
badgeClass,
|
|
buttonClass,
|
|
cardClass,
|
|
checkboxClass,
|
|
cn,
|
|
focusRing,
|
|
inputClass,
|
|
overlayScrim,
|
|
proseBody,
|
|
radioClass,
|
|
segmentedClass,
|
|
statusNoteClass,
|
|
transitionUi,
|
|
} from "./variants";
|
|
|
|
describe("ui variants", () => {
|
|
it("joins classes without falsey values", () => {
|
|
expect(cn("a", false, undefined, null, "b")).toBe("a b");
|
|
});
|
|
|
|
it("builds stable primary button classes", () => {
|
|
const klass = buttonClass({ variant: "primary", size: "sm" });
|
|
|
|
expect(klass).toContain("bg-cinnabar");
|
|
expect(klass).toContain("px-3");
|
|
expect(klass).toContain("focus-visible:ring-2");
|
|
});
|
|
|
|
it("uses layered radius tokens (button md / card lg / badge pill)", () => {
|
|
expect(buttonClass({ variant: "primary" })).toContain("rounded-md");
|
|
expect(cardClass()).toContain("rounded-lg");
|
|
expect(badgeClass()).toContain("rounded-full");
|
|
expect(inputClass()).toContain("rounded-md");
|
|
});
|
|
|
|
it("gives the primary button a press-darken and cream disabled state", () => {
|
|
const klass = buttonClass({ variant: "primary" });
|
|
|
|
expect(klass).toContain("hover:bg-cinnabar-active");
|
|
expect(klass).toContain("active:bg-cinnabar-active");
|
|
expect(klass).toContain("disabled:bg-cinnabar-disabled");
|
|
expect(klass).not.toContain("hover:bg-cinnabar/95");
|
|
});
|
|
|
|
it("shares one interaction-transition token across controls", () => {
|
|
expect(transitionUi).toContain("duration-fast");
|
|
expect(transitionUi).toContain("ease-standard");
|
|
expect(buttonClass({ variant: "primary" })).toContain(transitionUi);
|
|
expect(inputClass()).toContain(transitionUi);
|
|
});
|
|
|
|
it("builds a coral inline link button variant", () => {
|
|
const klass = buttonClass({ variant: "link" });
|
|
|
|
expect(klass).toContain("text-cinnabar");
|
|
expect(klass).toContain("underline-offset-4");
|
|
expect(klass).toContain("hover:underline");
|
|
});
|
|
|
|
it("supports a large button size for prominent CTAs", () => {
|
|
expect(buttonClass({ size: "lg" })).toContain("px-5");
|
|
});
|
|
|
|
it("layers card surface tones and can drop the shadow", () => {
|
|
expect(cardClass({ tone: "card" })).toContain("bg-surface-card");
|
|
expect(cardClass({ tone: "soft" })).toContain("bg-surface-soft");
|
|
expect(cardClass()).toContain("bg-panel");
|
|
expect(cardClass({ flat: true })).not.toContain("shadow-paper");
|
|
expect(cardClass({ interactive: true })).toContain("hover:border-cinnabar/40");
|
|
});
|
|
|
|
it("keeps the legacy string cardClass signature working", () => {
|
|
const klass = cardClass("mt-4");
|
|
expect(klass).toContain("mt-4");
|
|
expect(klass).toContain("bg-panel");
|
|
expect(klass).toContain("rounded-lg");
|
|
});
|
|
|
|
it("keeps warning badges readable on the paper background", () => {
|
|
const klass = badgeClass({ variant: "warning" });
|
|
|
|
expect(klass).toContain("bg-overdue/10");
|
|
expect(klass).toContain("text-ink");
|
|
});
|
|
|
|
it("marks invalid inputs with conflict styling", () => {
|
|
const klass = inputClass({ state: "error" });
|
|
|
|
expect(klass).toContain("border-conflict");
|
|
expect(klass).toContain("focus-visible:ring-conflict/25");
|
|
});
|
|
|
|
it("gives warning status notes a non-color icon slot friendly shell", () => {
|
|
const klass = statusNoteClass({ variant: "warning" });
|
|
|
|
expect(klass).toContain("border-overdue");
|
|
expect(klass).toContain("px-3");
|
|
});
|
|
|
|
it("styles checkbox and radio with accent + shared focus ring", () => {
|
|
expect(checkboxClass()).toContain("accent-cinnabar");
|
|
expect(checkboxClass()).toContain("rounded-xs");
|
|
expect(checkboxClass()).toContain("focus-visible:ring-cinnabar/35");
|
|
expect(radioClass()).toContain("rounded-full");
|
|
expect(radioClass()).toContain("accent-cinnabar");
|
|
});
|
|
|
|
it("builds segmented control chrome", () => {
|
|
const klass = segmentedClass();
|
|
|
|
expect(klass).toContain("inline-flex");
|
|
expect(klass).toContain("border-line");
|
|
});
|
|
|
|
it("shares the cinnabar focus ring with the button base", () => {
|
|
expect(focusRing).toContain("focus-visible:ring-2");
|
|
expect(focusRing).toContain("focus-visible:ring-cinnabar/35");
|
|
expect(buttonClass({ variant: "primary" })).toContain(focusRing);
|
|
});
|
|
|
|
it("renders manuscript body as serif prose scale", () => {
|
|
expect(proseBody).toContain("font-serif");
|
|
expect(proseBody).toContain("text-prose");
|
|
});
|
|
|
|
it("provides a full-screen overlay scrim below dialog chrome", () => {
|
|
expect(overlayScrim).toContain("fixed");
|
|
expect(overlayScrim).toContain("inset-0");
|
|
expect(overlayScrim).toContain("z-40");
|
|
});
|
|
});
|