release: WRNexusJS 0.5.10
This commit is contained in:
+239
-1
@@ -22,7 +22,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(85);
|
||||
expect(reference.count).toBe(90);
|
||||
expect(reference.components.map((component) => component.name)).toEqual(
|
||||
expect.arrayContaining(uiComponentNames()),
|
||||
);
|
||||
@@ -160,6 +160,234 @@ test("screenshot-directed canonical components are bundled", () => {
|
||||
expect(uiComponentNames()).toEqual(expect.arrayContaining(required));
|
||||
});
|
||||
|
||||
test("navbar supports brands, nested menus, actions, utility slots, and public events", () => {
|
||||
const source = readFileSync(uiComponentPath("Navbar"), "utf8");
|
||||
expect(source).toContain('slot name="topbar"');
|
||||
expect(source).toContain('slot name="actions"');
|
||||
expect(source).toContain("brand = {}");
|
||||
expect(source).toContain("actions = []");
|
||||
expect(source).toContain("openOnHover = false");
|
||||
expect(source).toContain('maxWidth = "full"');
|
||||
expect(source).toContain("item.children");
|
||||
expect(source).toContain("child.children");
|
||||
expect(source).toContain("wire-navbar__dropdown--{item.type || 'dropdown'}");
|
||||
for (const event of ["toggle", "open", "close", "select", "action"]) {
|
||||
expect(source).toContain(`@event ${event} = function`);
|
||||
}
|
||||
});
|
||||
|
||||
test("auth form uses package schemas, disables native validation, and renders a full-width submit control", async () => {
|
||||
const source = readFileSync(uiComponentPath("AuthForm"), "utf8");
|
||||
expect(source).toContain('return "auth-login"');
|
||||
expect(source).toContain('data-schema="{schemaName()}"');
|
||||
expect(source).toContain('data-wrnexus-runtime="auth"');
|
||||
expect(source).toContain('novalidate="true"');
|
||||
expect(source).toContain('fullWidth="true"');
|
||||
|
||||
const html = await renderComponent(source, { mode: "sign-in" });
|
||||
expect(html).toContain('data-schema="auth-login"');
|
||||
expect(html).toContain('data-wrnexus-runtime="auth"');
|
||||
expect(html).toContain("novalidate");
|
||||
expect(html).toContain("wire-auth-form__submit");
|
||||
expect(html).toContain('fullWidth="true"');
|
||||
});
|
||||
|
||||
test("footer supports typed entries, responsive columns, pre/post slots, and events", () => {
|
||||
const source = readFileSync(uiComponentPath("Footer"), "utf8");
|
||||
expect(source).toContain('slot name="pre-footer"');
|
||||
expect(source).toContain('slot name="post-footer"');
|
||||
expect(source).toContain('slot name="copyright-left"');
|
||||
expect(source).toContain('slot name="copyright-right"');
|
||||
expect(source).toContain('item.type === "header"');
|
||||
expect(source).toContain("wire-footer--columns-{columns}");
|
||||
expect(source).toContain("@event select = function");
|
||||
expect(source).toContain("@event action = function");
|
||||
});
|
||||
|
||||
test("navbar renders a brand, nested dropdowns, mega groups, and actions", async () => {
|
||||
const html = await renderComponent(readFileSync(uiComponentPath("Navbar"), "utf8"), {
|
||||
brand: {
|
||||
label: "Police Management System",
|
||||
description: "Public portal",
|
||||
href: "/",
|
||||
},
|
||||
items: [
|
||||
{
|
||||
label: "Services",
|
||||
type: "dropdown",
|
||||
children: [{ label: "Reports", href: "/reports" }],
|
||||
},
|
||||
{
|
||||
label: "Safety",
|
||||
type: "mega",
|
||||
columns: 2,
|
||||
children: [
|
||||
{
|
||||
label: "Resources",
|
||||
children: [{ label: "Guidance", href: "/guidance" }],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
actions: [{ label: "Sign in", href: "/login", variant: "primary" }],
|
||||
});
|
||||
|
||||
expect(html).toContain("Police Management System");
|
||||
expect(html).toContain("Public portal");
|
||||
expect(html).toContain("Reports");
|
||||
expect(html).toContain("Guidance");
|
||||
expect(html).toContain("wire-navbar__dropdown--mega");
|
||||
expect(html).toContain("data-wrn-navbar");
|
||||
expect(html).toContain('name="wire-navbar-menu"');
|
||||
expect(html).toContain("Sign in");
|
||||
});
|
||||
|
||||
test("navbar closes sibling menus and menus close after an outside pointer press", async () => {
|
||||
const html = await renderComponent(readFileSync(uiComponentPath("Navbar"), "utf8"), {
|
||||
items: [
|
||||
{ label: "Services", children: [{ label: "Reports", href: "/reports" }] },
|
||||
{ label: "Safety", children: [{ label: "Guidance", href: "/guidance" }] },
|
||||
],
|
||||
});
|
||||
const dom = mountHtml(html);
|
||||
const menus = Array.from(dom.querySelectorAll(".wire-navbar__dropdown")) as HTMLDetailsElement[];
|
||||
|
||||
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);
|
||||
|
||||
dom.document.body.dispatchEvent(
|
||||
new (dom.window as { Event: typeof Event }).Event("pointerdown", { bubbles: true }),
|
||||
);
|
||||
expect(menus[1].open).toBe(false);
|
||||
});
|
||||
|
||||
test("navbar optionally opens menus on hover and renders full-width mode", async () => {
|
||||
const html = await renderComponent(readFileSync(uiComponentPath("Navbar"), "utf8"), {
|
||||
openOnHover: true,
|
||||
maxWidth: "full",
|
||||
items: [{ label: "Services", children: [{ label: "Reports", href: "/reports" }] }],
|
||||
});
|
||||
const dom = mountHtml(html);
|
||||
const navbar = dom.querySelector("[data-wrn-navbar]");
|
||||
const menu = dom.querySelector(".wire-navbar__dropdown") as HTMLDetailsElement;
|
||||
|
||||
expect(navbar?.classList.contains("wire-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);
|
||||
menu.dispatchEvent(new (dom.window as { Event: typeof Event }).Event("pointerleave"));
|
||||
expect(menu.open).toBe(true);
|
||||
await new Promise((resolve) => setTimeout(resolve, 280));
|
||||
expect(menu.open).toBe(false);
|
||||
});
|
||||
|
||||
test("navbar hover menu stays open while the pointer crosses into its panel", async () => {
|
||||
const html = await renderComponent(readFileSync(uiComponentPath("Navbar"), "utf8"), {
|
||||
openOnHover: true,
|
||||
items: [{ label: "Services", children: [{ label: "Reports", href: "/reports" }] }],
|
||||
});
|
||||
const dom = mountHtml(html);
|
||||
const menu = dom.querySelector(".wire-navbar__dropdown") as HTMLDetailsElement;
|
||||
const panel = dom.querySelector(".wire-navbar__panel") as HTMLElement;
|
||||
const DomEvent = (dom.window as { Event: typeof Event }).Event;
|
||||
|
||||
menu.dispatchEvent(new DomEvent("pointerenter"));
|
||||
menu.dispatchEvent(new DomEvent("pointerleave"));
|
||||
panel.dispatchEvent(new DomEvent("pointerenter", { bubbles: true }));
|
||||
await new Promise((resolve) => setTimeout(resolve, 280));
|
||||
|
||||
expect(menu.open).toBe(true);
|
||||
});
|
||||
|
||||
test("preference switcher keeps only one popover open and closes outside", async () => {
|
||||
const html = await renderComponent(
|
||||
readFileSync(uiComponentPath("PreferenceSwitcher"), "utf8"),
|
||||
{},
|
||||
);
|
||||
const dom = mountHtml(html);
|
||||
const menus = Array.from(dom.querySelectorAll(".wire-preferences__menu")) as HTMLDetailsElement[];
|
||||
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);
|
||||
|
||||
dom.document.body.dispatchEvent(new DomEvent("pointerdown", { bubbles: true }));
|
||||
expect(menus[1].open).toBe(false);
|
||||
});
|
||||
|
||||
test("sidebar renders compact grouped submenu navigation and a mobile drawer trigger", async () => {
|
||||
const html = await renderComponent(readFileSync(uiComponentPath("Sidebar"), "utf8"), {
|
||||
label: "Operations",
|
||||
orientation: "vertical",
|
||||
items: [
|
||||
{ label: "Dashboard", href: "/dashboard", value: "dashboard" },
|
||||
{
|
||||
label: "Case management",
|
||||
children: [
|
||||
{ label: "Assigned cases", href: "/cases", value: "cases" },
|
||||
{ label: "Evidence", href: "/evidence", value: "evidence" },
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(html).toContain("wire-sidebar-toggle");
|
||||
expect(html).toContain("wire-sidebar-backdrop");
|
||||
expect(html).toContain("wire-sidebar-group");
|
||||
expect(html).toContain("Assigned cases");
|
||||
expect(html).toContain("Evidence");
|
||||
});
|
||||
|
||||
test("dropdown renders slotted account triggers, menu structure, and public events", async () => {
|
||||
const source = readFileSync(uiComponentPath("Dropdown"), "utf8");
|
||||
const html = await renderComponent(source, {
|
||||
label: "Account menu",
|
||||
items: [
|
||||
{ type: "header", label: "Account" },
|
||||
{ label: "Profile", href: "/profile", icon: "icon-[lucide--user]" },
|
||||
{ type: "divider" },
|
||||
{ label: "Sign out", href: "/logout", danger: true },
|
||||
],
|
||||
});
|
||||
|
||||
expect(source).toContain('slot name="trigger"');
|
||||
expect(source).toContain("@event select = function");
|
||||
expect(html).toContain("wire-dropdown__panel");
|
||||
expect(html).toContain("Profile");
|
||||
expect(html).toContain("wire-dropdown__item--danger");
|
||||
});
|
||||
|
||||
test("footer renders header and link entries with the requested column count", async () => {
|
||||
const html = await renderComponent(readFileSync(uiComponentPath("Footer"), "utf8"), {
|
||||
columns: 4,
|
||||
items: [
|
||||
{ type: "header", label: "Support" },
|
||||
{ type: "link", label: "Contact", href: "/contact" },
|
||||
],
|
||||
copyright: "Public service platform",
|
||||
});
|
||||
|
||||
expect(html).toContain("wire-footer--columns-4");
|
||||
expect(html).toContain("wire-footer__column");
|
||||
expect(html).toContain("wire-footer__column-links");
|
||||
expect(html).toContain("wire-footer__heading");
|
||||
expect(html).toContain("Support");
|
||||
expect(html).toContain('href="/contact"');
|
||||
expect(html).toContain("Public service platform");
|
||||
});
|
||||
|
||||
test("component-system CSS includes responsive, theme-token, focus, and reduced-motion rules", () => {
|
||||
const css = uiCss();
|
||||
expect(css).toContain("@media (max-width: 768px)");
|
||||
@@ -184,6 +412,16 @@ test("component-system CSS includes responsive, theme-token, focus, and reduced-
|
||||
expect(css).toContain(".wire-next--table");
|
||||
expect(css).toContain('[data-show="false"]');
|
||||
expect(css).toContain("display: none !important");
|
||||
expect(css).toMatch(
|
||||
/\.wire-navbar__brand-copy strong\s*\{[^}]*font-size: 0\.9rem;[^}]*font-weight: 600;/s,
|
||||
);
|
||||
expect(css).toMatch(
|
||||
/\.wire-navbar__menu-link,[\s\S]*?font-size: 0\.8125rem;[\s\S]*?font-weight: 500;/,
|
||||
);
|
||||
expect(css).toMatch(
|
||||
/\.wire-footer__heading\s*\{[^}]*font-size: 0\.8125rem;[^}]*font-weight: 600;/s,
|
||||
);
|
||||
expect(css).toMatch(/\.wire-footer__link\s*\{[^}]*font-size: 0\.8125rem;[^}]*font-weight: 400;/s);
|
||||
});
|
||||
|
||||
test("component boundaries have no default margins and expose the canonical class prop", () => {
|
||||
|
||||
Reference in New Issue
Block a user