import { createRawSnippet } from "svelte"; import { describe, expect, it } from "vitest"; import SidebarLayout from "../src/composites/SidebarLayout.svelte"; import { sidebarLayout } from "../src/composites/sidebar-layout.variants"; import ApplicationLayoutsContractFailures from "./fixtures/ApplicationLayoutsContractFailures.svelte"; import ApplicationLayoutsConsumer from "./fixtures/ApplicationLayoutsConsumer.svelte"; import { expectLayoutReadiness } from "./layout-readiness"; import { classes, classesOf, render, rendered } from "./mount"; const snippet = (html: string) => createRawSnippet(() => ({ render: () => html })); describe("the deliberately failing Sidebar layout fixture", () => { it("diagnoses a sidebar arrangement that loses its main landmark", () => { const root = render(ApplicationLayoutsContractFailures, { failure: "sidebar-without-main" }); expect(root.querySelector("aside")).not.toBeNull(); expect(root.querySelector("[data-broken-sidebar-main]")).not.toBeNull(); expect(root.querySelector("main")).toBeNull(); }); }); describe("SidebarLayout", () => { it("pairs a named complementary landmark with one focusable main landmark", () => { const layout = rendered( render(SidebarLayout, { sidebarLabel: "Workspace navigation", sidebar: snippet('Nodes'), children: snippet("

Node alpha

"), }), ); const aside = layout.querySelector("aside")!; const main = layout.querySelector("main")!; expect(aside.getAttribute("aria-label")).toBe("Workspace navigation"); expect(aside.querySelector("a")?.getAttribute("href")).toBe("/nodes"); expect(main.id).toBe("main-content"); expect(main.tabIndex).toBe(-1); expect(main.textContent).toBe("Node alpha"); expect(layout.querySelectorAll("main")).toHaveLength(1); }); it("keeps landmark and focus order aligned when the sidebar changes sides", () => { const content = { sidebar: snippet(''), children: snippet(''), }; const start = rendered(render(SidebarLayout, { ...content, side: "start" })); const end = rendered(render(SidebarLayout, { ...content, side: "end" })); expect([...start.children].map((child) => child.tagName)).toEqual(["ASIDE", "MAIN"]); expect([...end.children].map((child) => child.tagName)).toEqual(["MAIN", "ASIDE"]); expect([...end.querySelectorAll("button")].map((button) => button.textContent)).toEqual([ "Workspace", "Navigation", ]); expect(classes(end)).toEqual(classesOf(sidebarLayout({ side: "end" }).root())); }); it("composes canonical Stacks and routes structural spacing through Density", () => { const layout = rendered( render(SidebarLayout, { gap: "lg", sidebar: snippet("

Navigation

"), children: snippet("

Workspace

"), }), ); // The gutter is the layout tier (ADR 0024); the regions' rhythm is the // caller's chosen Stack gap. expect(classes(layout).has("gap-[var(--reddb-spatial-layout-gap-md)]")).toBe(true); for (const region of layout.children) { const stack = region.querySelector("[data-stack]")!; expect(stack).not.toBeNull(); expect(classes(stack).has("gap-[var(--reddb-spatial-gap-lg)]")).toBe(true); expect(stack.hasAttribute("data-density")).toBe(false); } }); it("takes the consumer's target id, native attributes, and local class", () => { const layout = rendered( render(SidebarLayout, { id: "workspace-layout", class: "relative", mainId: "workspace-content", }), ); expect(layout.id).toBe("workspace-layout"); expect(layout.querySelector("main")?.id).toBe("workspace-content"); expect(classes(layout)).toEqual( classesOf(sidebarLayout({ side: "start" }).root({ class: "relative" })), ); }); it("compiles at the public export and inherits every nested appearance axis", () => { const root = render(ApplicationLayoutsConsumer); const layout = root.querySelector("[data-sidebar-layout]")!; expect(layout.closest('[data-theme="base"][data-color-scheme="dark"][data-density="compact"]')) .not.toBeNull(); expect(layout.hasAttribute("data-theme")).toBe(false); expect(layout.hasAttribute("data-color-scheme")).toBe(false); expect(layout.hasAttribute("data-density")).toBe(false); // Inside the consumer's ApplicationShell the shell owns the one
// (wave 5A); the layout's region keeps the caller's focus-target id. expect(root.querySelectorAll("main")).toHaveLength(1); expect(layout.querySelectorAll("main")).toHaveLength(0); expect(layout.querySelector("#deployment-content")?.tagName).toBe("DIV"); }); it("opts primary rails into visible labels while preserving destinations and mobile labels", () => { const navigation = [ { id: "users", label: "Users", fallback: "US", href: "/users" }, { id: "settings", label: "Application settings", fallback: "AS", href: "/settings" }, ]; for (const showRailLabels of [false, true]) { const layout = render(SidebarLayout, { navigation, currentId: "users", showRailLabels }); const rail = layout.querySelector("[data-primary-navigation-rail]")!; expect(rail.querySelectorAll("[data-sidebar-rail-label]")).toHaveLength(showRailLabels ? 2 : 0); if (showRailLabels) expect([...rail.querySelectorAll("[data-sidebar-rail-label]")].map(label => label.textContent)) .toEqual(navigation.map(item => item.label)); expect(rail.querySelector('[data-sidebar-rail-item="users"]')?.getAttribute("href")).toBe("/users"); expect(rail.querySelector('[data-sidebar-rail-item="users"]')?.getAttribute("aria-current")).toBe("page"); const dock = layout.querySelector("[data-primary-navigation-bottom]")!; expect(dock.textContent).toContain("Users"); expect(dock.textContent).toContain("Application settings"); } }); it("ships complete showcase, consumer, distributed export, and readiness evidence", () => { expectLayoutReadiness("sidebar-layout", "SidebarLayout"); }); });