From 21700a1748e496f66d6fae40aeecd6a469cc1d09 Mon Sep 17 00:00:00 2001 From: Molecule AI Core-UIUX Date: Tue, 12 May 2026 05:58:12 +0000 Subject: [PATCH] test(canvas/mobile): add primitives.test.tsx coverage (19 cases) Cover StatusDot (size, circle, halo, flexShrink), TierChip (tiers, size variants, flexShrink), Chip (value, label+value, pill shape, soft/accent mode), SectionLabel (text, right slot, uppercase). Co-Authored-By: Claude Opus 4.7 --- .../mobile/__tests__/primitives.test.tsx | 161 ++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 canvas/src/components/mobile/__tests__/primitives.test.tsx diff --git a/canvas/src/components/mobile/__tests__/primitives.test.tsx b/canvas/src/components/mobile/__tests__/primitives.test.tsx new file mode 100644 index 00000000..5200b828 --- /dev/null +++ b/canvas/src/components/mobile/__tests__/primitives.test.tsx @@ -0,0 +1,161 @@ +// @vitest-environment jsdom +/** + * Mobile primitives — StatusDot, TierChip, Chip, SectionLabel. + * + * NOTE: No @testing-library/jest-dom — use DOM APIs. + */ +import { afterEach, describe, expect, it } from "vitest"; +import { cleanup, render } from "@testing-library/react"; +import React from "react"; + +import { Chip, SectionLabel, StatusDot, TierChip } from "../primitives"; + +afterEach(() => { + cleanup(); +}); + +// ─── StatusDot ────────────────────────────────────────────────────────────── + +describe("StatusDot", () => { + it("renders a span with correct size", () => { + const { container } = render(); + const span = container.querySelector("span") as HTMLSpanElement; + expect(span).toBeTruthy(); + expect(span.style.width).toBe("12px"); + expect(span.style.height).toBe("12px"); + }); + + it("has border-radius 999 (circle)", () => { + const { container } = render(); + const span = container.querySelector("span") as HTMLSpanElement; + expect(span.style.borderRadius).toBe("999px"); + }); + + it("has flexShrink: 0 to prevent collapsing in flex rows", () => { + const { container } = render(); + const span = container.querySelector("span") as HTMLSpanElement; + expect(span.style.flexShrink).toBe("0"); + }); + + it("has halo boxShadow by default (halo=true)", () => { + const { container } = render(); + const span = container.querySelector("span") as HTMLSpanElement; + // Math.max(2, 8*0.45) = Math.max(2, 3.6) = 3.6 → "3.6px" + expect(span.style.boxShadow).toContain("px"); + }); + + it("has no boxShadow when halo=false", () => { + const { container } = render(); + const span = container.querySelector("span") as HTMLSpanElement; + expect(span.style.boxShadow).toBe("none"); + }); + + it("renders with default props (size=8, halo=true, status=online)", () => { + const { container } = render(); + const span = container.querySelector("span") as HTMLSpanElement; + expect(span.style.width).toBe("8px"); + expect(span.style.height).toBe("8px"); + expect(span.style.boxShadow).not.toBe("none"); + }); +}); + +// ─── TierChip ─────────────────────────────────────────────────────────────── + +describe("TierChip", () => { + it("renders the tier text inside a span", () => { + const { container } = render(); + expect(container.textContent).toContain("T1"); + }); + + it("renders T1, T2, T3, T4 with correct text", () => { + for (const tier of ["T1", "T2", "T3", "T4"] as const) { + const { container } = render(); + expect(container.textContent).toBe(tier); + } + }); + + it("sm size renders smaller dimensions than lg", () => { + const { container: sm } = render(); + const { container: lg } = render(); + const smSpan = sm.querySelector("span") as HTMLSpanElement; + const lgSpan = lg.querySelector("span") as HTMLSpanElement; + expect(smSpan.style.width).toBe("26px"); + expect(smSpan.style.height).toBe("19px"); + expect(lgSpan.style.width).toBe("32px"); + expect(lgSpan.style.height).toBe("22px"); + }); + + it("uses flexShrink: 0 to prevent collapsing", () => { + const { container } = render(); + const span = container.querySelector("span") as HTMLSpanElement; + expect(span.style.flexShrink).toBe("0"); + }); + + it("renders with default props (tier=T2, size=sm)", () => { + const { container } = render(); + expect(container.textContent).toBe("T2"); + const span = container.querySelector("span") as HTMLSpanElement; + expect(span.style.width).toBe("26px"); + }); +}); + +// ─── Chip ─────────────────────────────────────────────────────────────────── + +describe("Chip", () => { + it("renders the value text", () => { + const { container } = render(); + expect(container.textContent).toContain("12 skills"); + }); + + it("renders label + value when label is provided", () => { + const { container } = render(); + const text = container.textContent ?? ""; + expect(text).toContain("SKILLS"); + expect(text).toContain("3"); + }); + + it("has border-radius 999 (pill shape)", () => { + const { container } = render(); + const span = container.querySelector("span") as HTMLSpanElement; + expect(span.style.borderRadius).toBe("999px"); + }); + + it("soft mode applies accent background", () => { + const { container: normal } = render(); + const { container: soft } = render(); + const normalSpan = normal.querySelector("span") as HTMLSpanElement; + const softSpan = soft.querySelector("span") as HTMLSpanElement; + // soft uses accent+1a hex, normal uses dark/light hardcoded + expect(normalSpan.style.background).toBeTruthy(); + expect(softSpan.style.background).toBeTruthy(); + expect(normalSpan.style.background).not.toBe(softSpan.style.background); + }); +}); + +// ─── SectionLabel ─────────────────────────────────────────────────────────── + +describe("SectionLabel", () => { + it("renders children text", () => { + const { container } = render(Runtime config); + expect(container.textContent).toContain("Runtime config"); + }); + + it("renders right slot content when provided", () => { + const { container } = render( + Edit}>Runtime config, + ); + expect(container.textContent).toContain("Edit"); + expect(container.querySelector("button")).toBeTruthy(); + }); + + it("renders without right slot", () => { + const { container } = render(Runtime config); + expect(container.querySelector("button")).toBeNull(); + }); + + it("uses uppercase text transform", () => { + const { container } = render(Runtime config); + const div = container.querySelector("div") as HTMLDivElement; + expect(div.style.textTransform).toBe("uppercase"); + }); +});