161 lines
6.4 KiB
TypeScript
161 lines
6.4 KiB
TypeScript
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<string, unknown>) {
|
|
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");
|
|
});
|