// @vitest-environment jsdom
import * as React from "react"
import { cleanup, fireEvent, render, screen } from "@testing-library/react"
import {
AppLayout,
createLayoutConfig,
defaultLayoutConfig,
useLayoutConfig,
} from "@workspace/layout"
import { afterEach, describe, expect, it, vi } from "vitest"
afterEach(cleanup)
function LayoutControls() {
const { layout, toggleAside, toggleSidebar } = useLayoutConfig()
return (
)
}
function Header() {
return Header component
}
function PreferencesProbe() {
const { layout } = useLayoutConfig()
return (
)
}
describe("layout config", () => {
it("creates complete configurations from partial input", () => {
expect(createLayoutConfig({ asideOpen: true })).toEqual({
...defaultLayoutConfig,
asideOpen: true,
})
})
})
describe("AppLayout", () => {
it("renders component and node slots in a generated grid", () => {
render(
Rail node}
sidebar={Sidebar node}
aside={Aside node}
footer={Footer node}
layout={{
asideOpen: true,
footerStart: "sidebar",
headerStart: "sidebar",
}}
>
Content
)
const root = document.querySelector('[data-slot="app-layout"]')
expect(root?.style.gridTemplateAreas).toBe(
'"rail header header header" "rail sidebar content aside" "rail footer footer aside"'
)
expect(root?.dataset.headerStart).toBe("sidebar")
expect(root?.dataset.footerStart).toBe("sidebar")
expect(screen.getByText("Header component")).toBeTruthy()
expect(screen.getByText("Rail node")).toBeTruthy()
expect(screen.getByText("Sidebar node")).toBeTruthy()
expect(screen.getByText("Aside node")).toBeTruthy()
expect(screen.getByText("Footer node")).toBeTruthy()
})
it("moves sidebar starts inward when the rail is absent", () => {
render(
Header}
sidebar={Sidebar}
aside={Aside}
footer={Footer}
layout={{
asideOpen: true,
footerStart: "sidebar",
headerStart: "sidebar",
}}
>
Content
)
const root = document.querySelector('[data-slot="app-layout"]')
expect(root?.style.gridTemplateAreas).toBe(
'"sidebar header header" "sidebar content aside" "sidebar footer aside"'
)
expect(root?.dataset.headerStart).toBe("content")
expect(root?.dataset.footerStart).toBe("content")
})
it("applies visibility capabilities without changing stored preferences", () => {
render(
Sidebar}
aside={Aside}
layout={{
asideEnabled: false,
asideOpen: true,
sidebarCollapsible: false,
sidebarOpen: false,
}}
>
)
expect(screen.getByText("Sidebar")).toBeTruthy()
expect(screen.queryByText("Aside")).toBeNull()
expect(screen.getByLabelText("preferences").textContent).toBe("false|true")
})
it("manages uncontrolled sidebar and aside state through the hook", () => {
render(
Sidebar} aside={Aside}>
)
expect(screen.getByText("Sidebar")).toBeTruthy()
expect(screen.queryByText("Aside")).toBeNull()
fireEvent.click(screen.getByRole("button", { name: "Toggle sidebar" }))
fireEvent.click(screen.getByRole("button", { name: "Toggle aside" }))
expect(screen.queryByText("Sidebar")).toBeNull()
expect(screen.getByText("Aside")).toBeTruthy()
expect(screen.getByLabelText("layout-state").textContent).toBe(
"sidebar-closed|aside-open"
)
})
it("reports controlled changes without mutating controlled state", () => {
const onLayoutChange = vi.fn()
render(
Aside}
layout={{ asideOpen: false }}
onLayoutChange={onLayoutChange}
>
)
fireEvent.click(screen.getByRole("button", { name: "Toggle aside" }))
expect(onLayoutChange).toHaveBeenCalledWith({
...defaultLayoutConfig,
asideOpen: true,
})
expect(screen.queryByText("Aside")).toBeNull()
expect(screen.getByLabelText("layout-state").textContent).toContain(
"aside-closed"
)
})
})