feat: release design system engine, studio and component contracts
This commit is contained in:
1 parent
ff1065fb27
commit
3943b946d6
693 files changed
+175546
-3894
No files matched your search
+82
-48
@@ -90,7 +90,7 @@ test("generated component reference documents every bundled component and its pr
|
||||
readFileSync(join(uiComponentsDir(), "..", "component-catalog.json"), "utf8"),
|
||||
) as { components: Array<{ name: string }> };
|
||||
expect(reference.count).toBe(uiComponentNames().length);
|
||||
expect(reference.count).toBe(102);
|
||||
expect(reference.count).toBe(104);
|
||||
expect(reference.components.map((component) => component.name)).toEqual(
|
||||
expect.arrayContaining(uiComponentNames()),
|
||||
);
|
||||
@@ -366,23 +366,17 @@ test("navbar closes sibling menus and menus close after an outside pointer press
|
||||
],
|
||||
});
|
||||
const dom = mountHtml(html);
|
||||
const menus = Array.from(dom.querySelectorAll(".wrn-navbar__dropdown")) as HTMLDetailsElement[];
|
||||
const menus = Array.from(dom.querySelectorAll(".wrn-navbar__dropdown")) as HTMLElement[];
|
||||
|
||||
menus[0].open = true;
|
||||
menus[0].dispatchEvent(
|
||||
new (dom.window as { Event: typeof Event }).Event("toggle", { bubbles: false }),
|
||||
);
|
||||
menus[1].open = true;
|
||||
menus[1].dispatchEvent(
|
||||
new (dom.window as { Event: typeof Event }).Event("toggle", { bubbles: false }),
|
||||
);
|
||||
expect(menus[0].open).toBe(false);
|
||||
expect(menus[1].open).toBe(true);
|
||||
(menus[0].querySelector("[data-wrn-menu-toggle]") as HTMLButtonElement).click();
|
||||
(menus[1].querySelector("[data-wrn-menu-toggle]") as HTMLButtonElement).click();
|
||||
expect(menus[0].dataset.open).toBe("false");
|
||||
expect(menus[1].dataset.open).toBe("true");
|
||||
|
||||
dom.document.body.dispatchEvent(
|
||||
new (dom.window as { Event: typeof Event }).Event("pointerdown", { bubbles: true }),
|
||||
);
|
||||
expect(menus[1].open).toBe(false);
|
||||
expect(menus[1].dataset.open).toBe("false");
|
||||
});
|
||||
|
||||
test("navbar optionally opens menus on hover and renders full-width mode", async () => {
|
||||
@@ -393,16 +387,16 @@ test("navbar optionally opens menus on hover and renders full-width mode", async
|
||||
});
|
||||
const dom = mountHtml(html);
|
||||
const navbar = dom.querySelector("[data-wrn-navbar]");
|
||||
const menu = dom.querySelector(".wrn-navbar__dropdown") as HTMLDetailsElement;
|
||||
const menu = dom.querySelector(".wrn-navbar__dropdown") as HTMLElement;
|
||||
|
||||
expect(navbar?.classList.contains("wrn-navbar--width-full")).toBe(true);
|
||||
expect(navbar?.getAttribute("data-open-on-hover")).toBe("true");
|
||||
menu.dispatchEvent(new (dom.window as { Event: typeof Event }).Event("pointerenter"));
|
||||
expect(menu.open).toBe(true);
|
||||
expect(menu.dataset.open).toBe("true");
|
||||
menu.dispatchEvent(new (dom.window as { Event: typeof Event }).Event("pointerleave"));
|
||||
expect(menu.open).toBe(true);
|
||||
expect(menu.dataset.open).toBe("true");
|
||||
await new Promise((resolve) => setTimeout(resolve, 280));
|
||||
expect(menu.open).toBe(false);
|
||||
expect(menu.dataset.open).toBe("false");
|
||||
});
|
||||
|
||||
test("navbar hover menu stays open while the pointer crosses into its panel", async () => {
|
||||
@@ -411,7 +405,7 @@ test("navbar hover menu stays open while the pointer crosses into its panel", as
|
||||
items: [{ label: "Services", children: [{ label: "Reports", href: "/reports" }] }],
|
||||
});
|
||||
const dom = mountHtml(html);
|
||||
const menu = dom.querySelector(".wrn-navbar__dropdown") as HTMLDetailsElement;
|
||||
const menu = dom.querySelector(".wrn-navbar__dropdown") as HTMLElement;
|
||||
const panel = dom.querySelector(".wrn-navbar__panel") as HTMLElement;
|
||||
const DomEvent = (dom.window as { Event: typeof Event }).Event;
|
||||
|
||||
@@ -420,7 +414,7 @@ test("navbar hover menu stays open while the pointer crosses into its panel", as
|
||||
panel.dispatchEvent(new DomEvent("pointerenter", { bubbles: true }));
|
||||
await new Promise((resolve) => setTimeout(resolve, 280));
|
||||
|
||||
expect(menu.open).toBe(true);
|
||||
expect(menu.dataset.open).toBe("true");
|
||||
});
|
||||
|
||||
test("preference switcher keeps only one popover open and closes outside", async () => {
|
||||
@@ -429,18 +423,17 @@ test("preference switcher keeps only one popover open and closes outside", async
|
||||
expect(source).toContain("window.wrnAccent?.set(value)");
|
||||
expect(source).toContain("window.wrnTheme?.set(value)");
|
||||
const dom = mountHtml(html);
|
||||
const menus = Array.from(dom.querySelectorAll(".wrn-preferences__menu")) as HTMLDetailsElement[];
|
||||
expect(dom.document.querySelector("details,summary")).toBeNull();
|
||||
const menus = Array.from(dom.querySelectorAll(".wrn-preferences__menu")) as HTMLElement[];
|
||||
const DomEvent = (dom.window as { Event: typeof Event }).Event;
|
||||
|
||||
menus[0].open = true;
|
||||
menus[0].dispatchEvent(new DomEvent("toggle"));
|
||||
menus[1].open = true;
|
||||
menus[1].dispatchEvent(new DomEvent("toggle"));
|
||||
expect(menus[0].open).toBe(false);
|
||||
expect(menus[1].open).toBe(true);
|
||||
(menus[0].querySelector("[data-wrn-menu-toggle]") as HTMLButtonElement).click();
|
||||
(menus[1].querySelector("[data-wrn-menu-toggle]") as HTMLButtonElement).click();
|
||||
expect(menus[0].dataset.open).toBe("false");
|
||||
expect(menus[1].dataset.open).toBe("true");
|
||||
|
||||
dom.document.body.dispatchEvent(new DomEvent("pointerdown", { bubbles: true }));
|
||||
expect(menus[1].open).toBe(false);
|
||||
expect(menus[1].dataset.open).toBe("false");
|
||||
});
|
||||
|
||||
test("sidebar renders compact grouped submenu navigation and a mobile drawer trigger", async () => {
|
||||
@@ -1430,7 +1423,9 @@ test("basic fields suppress native popups and render package constraint messages
|
||||
|
||||
const selectSource = readFileSync(uiComponentPath("Select"), "utf8");
|
||||
expect(selectSource).toContain("appearance: none");
|
||||
expect(selectSource).toContain("background-color: var(--wrn-color-input-background, var(--wrn-color-surface))");
|
||||
expect(selectSource).toContain(
|
||||
"background-color: var(--wrn-color-input-background, var(--wrn-color-surface))",
|
||||
);
|
||||
expect(selectSource).toContain("wrn-next__select-indicator");
|
||||
});
|
||||
|
||||
@@ -1672,7 +1667,9 @@ test("date picker renders a custom calendar trigger with accessible field state
|
||||
expect(dom.querySelector("label")?.getAttribute("for")).toBe("start-date");
|
||||
expect(dom.querySelector('button[aria-haspopup="dialog"]')).not.toBeNull();
|
||||
expect(source).not.toContain('type="{type}"');
|
||||
expect(dom.querySelectorAll(".wrn-next__date-selectors select")[0]?.querySelectorAll("option")).toHaveLength(12);
|
||||
expect(
|
||||
dom.querySelectorAll(".wrn-next__date-selectors select")[0]?.querySelectorAll("option"),
|
||||
).toHaveLength(12);
|
||||
expect(dom.querySelector(".wrn-next__field-help")?.textContent).toContain("local date");
|
||||
const trigger = dom.querySelector('button[aria-haspopup="dialog"]') as HTMLButtonElement;
|
||||
expect(dom.querySelector(".wrn-next--date-picker")?.getAttribute("data-expanded")).toBe("false");
|
||||
@@ -3186,7 +3183,9 @@ test("announcement full-width surface does not create viewport scrollbar overflo
|
||||
});
|
||||
|
||||
test("announcement status uses a compatible element and preserves live announcements", async () => {
|
||||
const html = await renderComponent(readFileSync(uiComponentPath("AnnouncementBar"), "utf8"), { message: "Service update" });
|
||||
const html = await renderComponent(readFileSync(uiComponentPath("AnnouncementBar"), "utf8"), {
|
||||
message: "Service update",
|
||||
});
|
||||
const dom = mountHtml(html);
|
||||
const status = dom.querySelector('[role="status"]')!;
|
||||
expect(status.tagName).toBe("DIV");
|
||||
@@ -3196,7 +3195,9 @@ test("announcement status uses a compatible element and preserves live announcem
|
||||
|
||||
test("feature grid supports arbitrary slotted articles without imposing list semantics", async () => {
|
||||
const grid = await renderComponent(readFileSync(uiComponentPath("FeatureGrid"), "utf8"));
|
||||
const card = await renderComponent(readFileSync(uiComponentPath("FeatureCard"), "utf8"), { title: "Feature" });
|
||||
const card = await renderComponent(readFileSync(uiComponentPath("FeatureCard"), "utf8"), {
|
||||
title: "Feature",
|
||||
});
|
||||
expect(mountHtml(grid).querySelector('[role="list"]')).toBeNull();
|
||||
const dom = mountHtml(card);
|
||||
expect(dom.querySelector('[role="listitem"]')).toBeNull();
|
||||
@@ -3218,7 +3219,9 @@ test("mega menu trigger keeps an accessible name for an empty or missing label",
|
||||
const dom = mountHtml(await renderComponent(source, { label, columns: [] }));
|
||||
const trigger = dom.querySelector(".wrn-mega__trigger")!;
|
||||
expect(trigger.getAttribute("aria-label")).toBe(label || "Menu");
|
||||
expect(dom.querySelector(".wrn-mega__trigger-label")?.getAttribute("data-label")).toBe(label || "Menu");
|
||||
expect(dom.querySelector(".wrn-mega__trigger-label")?.getAttribute("data-label")).toBe(
|
||||
label || "Menu",
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -3839,27 +3842,24 @@ test("list selects items without hrefs as well as navigation items", async () =>
|
||||
expect(events).toEqual([{ item: { label: "Run report" }, index: 0 }]);
|
||||
});
|
||||
|
||||
test("chart renders accessible SVG bars and emits point and legend outputs", async () => {
|
||||
test("chart renders a runtime canvas and forwards point and legend outputs", async () => {
|
||||
const source = readFileSync(uiComponentPath("Chart"), "utf8");
|
||||
const dom = mountHtml(
|
||||
await renderComponent(source, {
|
||||
items: [
|
||||
{ label: "Alpha", value: 12 },
|
||||
{ label: "Beta", value: 6 },
|
||||
],
|
||||
}),
|
||||
);
|
||||
const dom = mountHtml(await renderComponent(source, { items: [{ label: "Alpha", value: 12 }] }));
|
||||
const root = dom.querySelector('[data-ui-component="Chart"]') as HTMLElement;
|
||||
expect(root.getAttribute("data-wrnexus-runtime")).toBe("chartjs");
|
||||
expect(root.querySelector("canvas")?.getAttribute("role")).toBe("img");
|
||||
expect(JSON.parse(root.dataset.chartConfig!).items).toEqual([{ label: "Alpha", value: 12 }]);
|
||||
const events: string[] = [];
|
||||
for (const name of ["dataPointClick", "select", "legendToggle"]) {
|
||||
for (const name of ["dataPointClick", "select", "legendToggle"])
|
||||
root.addEventListener(name, () => events.push(name));
|
||||
for (const name of ["chartpointclick", "chartselect", "chartlegendtoggle"]) {
|
||||
root.dispatchEvent(
|
||||
new (dom.window as { CustomEvent: typeof CustomEvent }).CustomEvent(name, {
|
||||
detail: { item: 12, index: 0, datasetIndex: 0 },
|
||||
bubbles: true,
|
||||
}),
|
||||
);
|
||||
}
|
||||
expect(root.querySelectorAll("svg path.wrn-chart__bar")).toHaveLength(2);
|
||||
expect(root.querySelectorAll("button.wrn-chart__hit")).toHaveLength(2);
|
||||
(root.querySelector("button.wrn-chart__hit") as HTMLElement).dispatchEvent(
|
||||
new (dom.window as { Event: typeof Event }).Event("click", { bubbles: true }),
|
||||
);
|
||||
(root.querySelector(".wrn-chart__legend button") as HTMLButtonElement).click();
|
||||
expect(events).toEqual(["dataPointClick", "select", "legendToggle"]);
|
||||
});
|
||||
|
||||
@@ -3960,3 +3960,37 @@ test("a declared output is never emitted as a hand-built CustomEvent", () => {
|
||||
|
||||
expect([...new Set(offenders)]).toEqual([]);
|
||||
});
|
||||
|
||||
test("Switch follows controlled checked props after a reset and remains usable uncontrolled", async () => {
|
||||
const source = readFileSync(uiComponentPath("Switch"), "utf8");
|
||||
const controlled = mountHtml(
|
||||
await renderComponent(source, { name: "controlled-switch", checked: true }),
|
||||
);
|
||||
const input = controlled.querySelector("input") as HTMLInputElement;
|
||||
expect(input.checked).toBe(true);
|
||||
const root = controlled.querySelector(".wrn-next--choice-field")!.parentElement as HTMLElement & {
|
||||
__wrnexusScopeApi: {
|
||||
set(name: string, value: unknown): void;
|
||||
get(name: string): unknown;
|
||||
call(name: string): unknown;
|
||||
};
|
||||
};
|
||||
expect(root.__wrnexusScopeApi).toBeDefined();
|
||||
|
||||
input.click();
|
||||
expect(input.checked).toBe(false);
|
||||
root.__wrnexusScopeApi.set("checked", false);
|
||||
root.__wrnexusScopeApi.set("checked", true);
|
||||
expect(input.checked).toBe(true);
|
||||
root.__wrnexusScopeApi.set("checked", false);
|
||||
expect(input.checked).toBe(false);
|
||||
expect(input.getAttribute("aria-checked")).toBe("false");
|
||||
const uncontrolled = mountHtml(await renderComponent(source, { name: "uncontrolled-switch" }));
|
||||
const control = uncontrolled.querySelector("input") as HTMLInputElement;
|
||||
control.checked = true;
|
||||
control.dispatchEvent(
|
||||
new (uncontrolled.window as { Event: typeof Event }).Event("change", { bubbles: true }),
|
||||
);
|
||||
expect(control.checked).toBe(true);
|
||||
expect(control.getAttribute("aria-checked")).toBe("true");
|
||||
});
|
||||
Reference in new issue
Block a user