import { test, expect } from "bun:test"; import { readFileSync } from "node:fs"; import { mountHtml, renderComponent } from "../../test/src/index.ts"; import { uiComponentPath } from "../src/registry.ts"; async function mount(name: string, props: Record) { return mountHtml(await renderComponent(readFileSync(uiComponentPath(name), "utf8"), props)); } test("grouped combobox renders suggestions and commits a selection", async () => { const dom = await mount("ComboBox", { name: "teams", groups: [ { label: "Product", options: [ { value: "design", label: "Design" }, { value: "engineering", label: "Engineering" }, ], }, ], }); const input = dom.querySelector('input[role="combobox"]') as HTMLInputElement; input.dispatchEvent(new (dom.window as Window & typeof globalThis).Event("focus")); const option = dom.querySelector('[data-option-value="engineering"]') as HTMLButtonElement; expect(option).not.toBeNull(); option.click(); expect(input.value).toBe("Engineering"); }); test("toggle count falls back to its first choice when value is empty or invalid", async () => { for (const value of ["", "unknown"]) { const dom = await mount("ToggleCount", { value, firstValue: "monthly", items: [{ title: "Team", monthly: 29, annual: 290 }], }); expect(dom.querySelector('[aria-pressed="true"]')?.textContent).toContain("Monthly"); expect(dom.document.body.textContent).toContain("29"); } }); test("password leading icon is optional", async () => { const plain = await mount("TogglePassword", {}); expect(plain.querySelector(".wrn-next__password-leading-icon")).toBeNull(); const decorated = await mount("TogglePassword", { icon: "icon-[lucide--lock]" }); expect(decorated.querySelector(".wrn-next__password-leading-icon")?.className).toContain( "icon-[lucide--lock]", ); }); test("button group changes the selected segment", async () => { const dom = await mount("ButtonGroup", { items: [ { label: "Week", value: "week" }, { label: "Month", value: "month" }, ], value: "week", selectable: true, }); (dom.querySelector('[data-value="month"]') as HTMLButtonElement).click(); expect(dom.querySelector('[aria-pressed="true"]')?.textContent).toContain("Month"); }); test("carousel exposes both unframed and framed surfaces", async () => { for (const framed of [false, true]) { const dom = await mount("Carousel", { framed, items: [{ title: "First" }, { title: "Second" }], }); expect(dom.querySelector("[data-framed]")?.getAttribute("data-framed")).toBe(String(framed)); } }); test("rating commits a star selection and readonly rating stays disabled", async () => { const dom = await mount("Rating", { value: 2 }); (dom.querySelector('[aria-label="Rate 4 of 5"]') as HTMLButtonElement).click(); expect(dom.querySelector("output")?.textContent).toContain("4 / 5"); expect(dom.querySelectorAll('[data-filled="true"]').length).toBe(4); const readonly = await mount("Rating", { readonly: true }); expect((readonly.querySelector("button") as HTMLButtonElement).disabled).toBe(true); }); test("fractional rating uses the requested step and commits precise values", async () => { const dom = await mount("Rating", { value: 3.5, step: 0.2 }); const slider = dom.querySelector('input[type="range"]') as HTMLInputElement; expect(slider.step).toBe("0.2"); slider.value = "4.2"; slider.dispatchEvent( new (dom.window as Window & typeof globalThis).Event("input", { bubbles: true }), ); expect(dom.querySelector("output")?.textContent).toContain("4.2 / 5"); }); test("card header and footer can be suppressed independently", async () => { const dom = await mount("Card", { title: "Hidden title", footer: "Hidden footer", showHeader: false, showFooter: false, description: "Body remains", }); expect(dom.querySelector(".wrn-next__card-header")).toBeNull(); expect(dom.querySelector(".wrn-next__card-footer")).toBeNull(); expect(dom.document.body.textContent).toContain("Body remains"); }); test("map embeds a geographic provider and requires a Google key", async () => { const osm = await mount("Map", { latitude: 12.97, longitude: 77.59 }); expect(osm.querySelector("iframe")?.getAttribute("src")).toContain( "openstreetmap.org/export/embed.html", ); const google = await mount("Map", { provider: "google", apiKey: "" }); expect(google.querySelector("iframe")).toBeNull(); expect(google.document.body.textContent).toContain("API key"); }); test("chart accepts data arrays and configures Chart.js line rendering", async () => { const dom = await mount("Chart", { type: "line", data: [ { label: "Mon", value: 12 }, { label: "Tue", value: 18 }, ], }); const root = dom.querySelector('[data-ui-component="Chart"]') as HTMLElement; expect(root.dataset.wrnexusRuntime).toBe("chartjs"); expect(root.querySelector("canvas")?.getAttribute("role")).toBe("img"); const config = JSON.parse(root.dataset.chartConfig!); expect(config.type).toBe("line"); expect(config.data).toEqual([ { label: "Mon", value: 12 }, { label: "Tue", value: 18 }, ]); }); test("calendar Done and outside dismiss both close the panel", async () => { const dom = await mount("DatePicker", { value: "2026-09-20" }); const trigger = dom.querySelector(".wrn-next__date-trigger") as HTMLButtonElement; trigger.click(); (dom.querySelector(".wrn-next__date-done") as HTMLButtonElement).click(); expect(trigger.getAttribute("aria-expanded")).toBe("false"); trigger.click(); (dom.querySelector(".wrn-picker-dismiss") as HTMLButtonElement).click(); expect(trigger.getAttribute("aria-expanded")).toBe("false"); }); test("fractional slider increments stay precise and disabled controls do not change", async () => { const dom = await mount("RangeSlider", { min: 0, max: 5, value: 2.4, step: 0.2 }); (dom.querySelector('[aria-label="Increase Range"]') as HTMLButtonElement).click(); expect(dom.querySelector('[role="slider"]')?.getAttribute("aria-valuenow")).toBe("2.6"); const locked = await mount("RangeSlider", { value: 2, disabled: true }); (locked.querySelector('[role="slider"]') as HTMLElement).dispatchEvent( new (locked.window as Window & typeof globalThis).KeyboardEvent("keydown", { key: "ArrowRight", bubbles: true, }), ); expect(locked.querySelector('[role="slider"]')?.getAttribute("aria-valuenow")).toBe("2"); });