Files
simple-react-app-kit/packages/layout/src/layout.test.tsx
T

181 lines
5.0 KiB
TypeScript

// @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 (
<div>
<output aria-label="layout-state">
{layout.sidebarOpen ? "sidebar-open" : "sidebar-closed"}|
{layout.asideOpen ? "aside-open" : "aside-closed"}
</output>
<button type="button" onClick={toggleSidebar}>
Toggle sidebar
</button>
<button type="button" onClick={toggleAside}>
Toggle aside
</button>
</div>
)
}
function Header() {
return <span>Header component</span>
}
function PreferencesProbe() {
const { layout } = useLayoutConfig()
return (
<output aria-label="preferences">
{String(layout.sidebarOpen)}|{String(layout.asideOpen)}
</output>
)
}
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(
<AppLayout
header={Header}
rail={<span>Rail node</span>}
sidebar={<span>Sidebar node</span>}
aside={<span>Aside node</span>}
footer={<span>Footer node</span>}
layout={{
asideOpen: true,
footerStart: "sidebar",
headerStart: "sidebar",
}}
>
Content
</AppLayout>
)
const root = document.querySelector<HTMLElement>('[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(
<AppLayout
header={<span>Header</span>}
sidebar={<span>Sidebar</span>}
aside={<span>Aside</span>}
footer={<span>Footer</span>}
layout={{
asideOpen: true,
footerStart: "sidebar",
headerStart: "sidebar",
}}
>
Content
</AppLayout>
)
const root = document.querySelector<HTMLElement>('[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(
<AppLayout
sidebar={<span>Sidebar</span>}
aside={<span>Aside</span>}
layout={{
asideEnabled: false,
asideOpen: true,
sidebarCollapsible: false,
sidebarOpen: false,
}}
>
<PreferencesProbe />
</AppLayout>
)
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(
<AppLayout sidebar={<span>Sidebar</span>} aside={<span>Aside</span>}>
<LayoutControls />
</AppLayout>
)
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(
<AppLayout
aside={<span>Aside</span>}
layout={{ asideOpen: false }}
onLayoutChange={onLayoutChange}
>
<LayoutControls />
</AppLayout>
)
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"
)
})
})