// @vitest-environment happy-dom import { act, forwardRef, type ComponentProps } from "react"; import { createRoot, type Root } from "react-dom/client"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { ToolkitProvider } from "../provider.js"; import { Button } from "../ui/button.js"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "../ui/dropdown-menu.js"; import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover.js"; import { ActionButton, IconButton } from "./components.js"; import { defaultDesignSystemComponents } from "./default-adapter.js"; import { defineDesignSystem } from "./definition.js"; import { defineTheme } from "./theme.js"; import { DESIGN_SYSTEM_CONTRACT_VERSION } from "./types.js"; describe("design-system contract", () => { let container: HTMLDivElement; let root: Root; beforeEach(() => { vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true); container = document.createElement("div"); document.body.appendChild(container); root = createRoot(container); }); afterEach(() => { act(() => root.unmount()); container.remove(); vi.restoreAllMocks(); vi.unstubAllGlobals(); }); it("locks the seventeen semantic component names", () => { expect(DESIGN_SYSTEM_CONTRACT_VERSION).toBe(1); expect(Object.keys(defaultDesignSystemComponents).sort()).toEqual( [ "ActionButton", "Avatar", "Checkbox", "Dialog", "IconButton", "Menu", "Picker", "Popover", "Skeleton", "Spinner", "Status", "Surface", "Switch", "Tabs", "TextArea", "TextField", "Tooltip", ].sort(), ); }); it("preserves typed design-system and theme definitions", () => { const theme = defineTheme({ colors: { light: { primary: "oklch(55% 0.2 260)" }, }, radius: "0.75rem", }); const definition = defineDesignSystem({ name: "Acme", theme, components: {}, }); expect(definition).toEqual({ name: "Acme", theme, components: {} }); }); it("prefers ActionButton over the legacy Button override", () => { const warning = vi.spyOn(console, "warn").mockImplementation(() => {}); const LegacyButton = () => , ); }); expect(container.querySelector("[data-adapter=semantic]")).not.toBeNull(); expect(container.querySelector("[data-adapter=legacy]")).toBeNull(); expect(warning).toHaveBeenCalledOnce(); }); it("warns when nested providers combine the effective ActionButton APIs", () => { const warning = vi.spyOn(console, "warn").mockImplementation(() => {}); const LegacyButton = () => , ); }); expect(container.querySelector("[data-adapter=semantic]")).not.toBeNull(); expect(warning).toHaveBeenCalledOnce(); }); it("forwards semantic intent independently from the legacy visual variant", () => { const received = vi.fn(); const CustomActionButton = (props: ComponentProps) => { received(props.intent, props.emphasis); return ; }; act(() => { root.render( , ); }); expect(received).toHaveBeenCalledWith("danger", "outline"); }); it("uses legacy Button as the lowest-precedence ActionButton adapter", () => { const LegacyButton = (props: ComponentProps<"button">) => ( First item , ); }); const trigger = container.querySelector("button"); expect(trigger).not.toBeNull(); expect(buttonRef).toBe(trigger); expect(trigger?.getAttribute("aria-haspopup")).toBe("menu"); expect(trigger?.getAttribute("aria-expanded")).toBe("false"); expect(trigger?.dataset.state).toBe("closed"); await act(async () => { trigger?.focus(); if (activation === "pointer") { trigger?.dispatchEvent( new PointerEvent("pointerdown", { bubbles: true, button: 0, pointerType: "mouse", }), ); } else { const key = activation === "Space" ? " " : activation; trigger?.dispatchEvent( new KeyboardEvent("keydown", { bubbles: true, key }), ); } await Promise.resolve(); }); expect(onOpenChange).toHaveBeenCalledTimes(1); expect(onOpenChange).toHaveBeenLastCalledWith(true); expect(trigger?.getAttribute("aria-expanded")).toBe("true"); expect(trigger?.dataset.state).toBe("open"); expect(document.querySelector("[role=menu]")).not.toBeNull(); await act(async () => { document.activeElement?.dispatchEvent( new KeyboardEvent("keydown", { bubbles: true, key: "Escape" }), ); await Promise.resolve(); }); expect(onOpenChange).toHaveBeenCalledTimes(2); expect(onOpenChange).toHaveBeenLastCalledWith(false); expect(trigger?.getAttribute("aria-expanded")).toBe("false"); expect(trigger?.dataset.state).toBe("closed"); }, ); it("forwards a native ref passed to ActionButton to the real DOM button node", () => { let node: HTMLButtonElement | null = null; act(() => { root.render( { node = el; }} > Save , ); }); const button = container.querySelector("button"); expect(button).not.toBeNull(); expect(node).toBe(button); }); it("resolves a Radix asChild Popover trigger's ref to the real IconButton DOM node", async () => { // Radix positions a popover by measuring the DOM node its `asChild` // Slot clones a ref onto. If that ref is dropped, Radix has nothing to // measure and falls back to an unpositioned, off-screen placement. let node: HTMLButtonElement | null = null; await act(async () => { root.render( } ref={(el) => { node = el; }} /> Content , ); }); const button = container.querySelector("button"); expect(button).not.toBeNull(); expect(node).toBe(button); }); it.each([ ["IconButton", } />], ["ActionButton", Manage], ])( "opens a Radix asChild Popover trigger built on %s when clicked", async (_name, trigger) => { // Radix passes its toggle handler down as `onClick`. An adapter that // spreads props and then sets its own `onClick` silently drops it, so // the trigger renders correctly and never opens. await act(async () => { root.render( {trigger} Content , ); }); const button = container.querySelector("button"); await act(async () => { button?.click(); }); expect(button?.dataset.state).toBe("open"); }, ); it("isolates a broken customer component and renders the default control", () => { vi.spyOn(console, "error").mockImplementation(() => {}); const BrokenActionButton = () => { throw new Error("broken adapter"); }; act(() => { root.render( Fallback action , ); }); expect(container.querySelector("button")?.textContent).toBe( "Fallback action", ); expect(console.error).toHaveBeenCalled(); }); });