import { render, screen } from "@testing-library/react"; import { describe, expect, it } from "vitest"; import { StatusPill, type StatusTone } from "./status-pill"; const TONES: StatusTone[] = [ "success", "warning", "danger", "info", "running", "neutral", ]; describe("StatusPill", () => { it("states the status as text, not colour alone", () => { render(Failed); expect(screen.getByText("Failed")).toBeInTheDocument(); }); // The glyph is the channel that survives greyscale and colour blindness, so // every tone must carry one and it must be hidden from the accessibility tree // — the label beside it already says the same thing. it.each(TONES)("gives %s a decorative glyph", (tone) => { const { container } = render(Status); const glyph = container.querySelector("svg"); expect(glyph).not.toBeNull(); expect(glyph).toHaveAttribute("aria-hidden", "true"); }); it.each(TONES)("brings %s's own paired background", (tone) => { const { container } = render(Status); const pill = container.firstElementChild as HTMLElement; expect(pill.className).toContain(`bg-[var(--surface-`); expect(pill.className).toContain("border"); }); // A tone colour is solved against its own background. `bare` drops that // background, so the tone may ride on the glyph only — lifting it onto the // label would put it on whatever plane the caller happens to be on. it.each(TONES)("keeps %s's tone off the label in bare mode", (tone) => { const { container } = render( Status , ); const pill = container.firstElementChild as HTMLElement; expect(pill.className).toContain("bg-transparent"); expect(pill.className).not.toContain("bg-[var(--surface-"); const glyphWrapper = container.querySelector("svg") ?.parentElement as HTMLElement; expect(glyphWrapper.className).toContain("text-[var(--surface-"); }); // Guards the reason TONE_TEXT is declared rather than recovered by searching // TONE_SURFACE for a `text-` class: a miss there yields undefined and removes // the tone from the only channel bare mode has left. it.each(TONES)("resolves a real text class for %s in bare mode", (tone) => { const { container } = render( Status , ); const glyphWrapper = container.querySelector("svg") ?.parentElement as HTMLElement; expect(glyphWrapper.className).not.toContain("undefined"); expect(glyphWrapper.className).toMatch(/text-\[var\(--surface-[a-z]+-text\)\]/); }); // `px-2` from the size and `px-0` from bare are the same utility group, so // which one lands is decided by the class merge rather than by reading order. // Asserting the resolved class list keeps that a guarantee: a bare pill has to // sit flush against the text beside it, and silently keeping the size padding // would space it like a chip that lost only its background. it.each(["sm", "md"] as const)("drops the %s size padding in bare mode", (size) => { const { container } = render( Queued , ); const cls = (container.firstElementChild as HTMLElement).className; expect(cls).toContain("px-0"); expect(cls).not.toMatch(/(^| )px-2(\.5)?( |$)/); }); it("gives no two tones the same silhouette", () => { const shapes = TONES.map((tone) => { const { container } = render(S); return container.querySelector("svg")?.innerHTML ?? ""; }); expect(new Set(shapes).size).toBe(shapes.length); }); it("takes a larger size", () => { const { container } = render( Queued , ); expect((container.firstElementChild as HTMLElement).className).toContain( "text-sm", ); }); });