import { resolve } from "node:path";
import { describe, expect, it } from "vitest";
import { render } from "@testing-library/preact";
import { Grid } from "./Grid.js";
describe("Grid", () => {
// ── Rendering ──
it("renders a div element", () => {
const { container } = render(content);
expect(container.firstElementChild?.tagName).toBe("DIV");
});
it("renders children in grid", () => {
const { getByText } = render(
Item 1
Item 2
Item 3
,
);
expect(getByText("Item 1")).toBeTruthy();
expect(getByText("Item 2")).toBeTruthy();
expect(getByText("Item 3")).toBeTruthy();
});
// ── Display ──
// ── Columns ──
it("defaults to 1 column in inline style", () => {
const { container } = render(content);
const el = container.firstElementChild as HTMLElement;
expect(el.style.gridTemplateColumns).toBe("repeat(1, minmax(0, 1fr))");
});
it("applies custom column count in inline style", () => {
const { container } = render(content);
const el = container.firstElementChild as HTMLElement;
expect(el.style.gridTemplateColumns).toBe("repeat(3, minmax(0, 1fr))");
});
it("applies 4-column layout", () => {
const { container } = render(content);
const el = container.firstElementChild as HTMLElement;
expect(el.style.gridTemplateColumns).toBe("repeat(4, minmax(0, 1fr))");
});
// ── Auto-fit (minColWidth) ──
it("renders a responsive auto-fit track when minColWidth is set", () => {
const { container } = render(content);
const el = container.firstElementChild as HTMLElement;
expect(el.style.gridTemplateColumns).toBe(
"repeat(auto-fit, minmax(220px, 1fr))",
);
});
it("lets minColWidth win over columns (auto-fit, not fixed count)", () => {
const { container } = render(
content
,
);
const el = container.firstElementChild as HTMLElement;
expect(el.style.gridTemplateColumns).toBe(
"repeat(auto-fit, minmax(220px, 1fr))",
);
});
it("keeps the gap alongside an auto-fit track", () => {
const { container } = render(
content
,
);
const el = container.firstElementChild as HTMLElement;
expect(el.style.gridTemplateColumns).toBe(
"repeat(auto-fit, minmax(220px, 1fr))",
);
expect(el.style.gap).toBe("var(--strand-space-3)");
});
// ── Gap ──
it("applies default gap as inline style", () => {
const { container } = render(content);
const el = container.firstElementChild as HTMLElement;
expect(el.style.gap).toBe("var(--strand-space-4)");
});
it("applies custom gap as inline style", () => {
const { container } = render(content);
const el = container.firstElementChild as HTMLElement;
expect(el.style.gap).toBe("var(--strand-space-8)");
});
// ── Custom className ──
// ── Props forwarding ──
it("forwards additional props", () => {
const { container } = render(
content
,
);
expect(container.firstElementChild).toHaveAttribute("id", "g1");
});
// ── Sidebar preset ──
//
// The library made this decision three times privately (.strand-ref-shell
// at 256px, .strand-ref-example at 200px, .strand-ref-taxonomy__list at
// 160px) and published none of them. Its GEOMETRY -- 264px beside a
// flexible track at desktop, one column below md -- is asserted in the
// layout tier, because jsdom neither lays out nor resolves media queries.
it("renders the sidebar preset as a class, not an inline template", () => {
// The column definition changes at a breakpoint and an inline style
// cannot carry a media query, so emitting one here would produce a
// declaration the stylesheet then has to fight at every width.
const { container } = render();
const grid = container.querySelector(".strand-grid") as HTMLElement;
expect(grid.classList.contains("strand-grid--sidebar")).toBe(true);
expect(grid.style.gridTemplateColumns).toBe("");
});
it("keeps the gap when the sidebar preset owns the columns", () => {
const { container } = render();
const grid = container.querySelector(".strand-grid") as HTMLElement;
expect(grid.style.gap).toBe("var(--strand-space-6)");
});
it("lets the sidebar preset win over columns and minColWidth", () => {
// A sidebar layout is a statement about the TRACKS rather than about
// how many of them there are, so a stale `columns` must not leak an
// inline template that overrides the preset.
const { container } = render();
const grid = container.querySelector(".strand-grid") as HTMLElement;
expect(grid.style.gridTemplateColumns).toBe("");
expect(grid.classList.contains("strand-grid--sidebar")).toBe(true);
});
it("does not apply the preset unless asked", () => {
const { container } = render();
const grid = container.querySelector(".strand-grid") as HTMLElement;
expect(grid.classList.contains("strand-grid--sidebar")).toBe(false);
expect(grid.style.gridTemplateColumns).toBe("repeat(2, minmax(0, 1fr))");
});
it("renders the split preset as a class, not an inline template", () => {
const { container } = render();
const grid = container.querySelector(".strand-grid") as HTMLElement;
expect(grid.classList.contains("strand-grid--split")).toBe(true);
expect(grid.style.gridTemplateColumns).toBe("");
});
it("lets the split preset win over columns and minColWidth", () => {
const { container } = render();
expect(
(container.querySelector(".strand-grid") as HTMLElement).style.gridTemplateColumns,
).toBe("");
});
it("does not apply the split preset unless asked", () => {
const { container } = render();
expect(
container.querySelector(".strand-grid")?.classList.contains("strand-grid--split"),
).toBe(false);
});
});
// ── Gap #114: the grid was clipping its children's hover affordance ──
//
// THESE READ THE STYLESHEET, and that is the only tier that can see this.
// Clipping is a PAINT operation: it changes no box, so `getBoundingClientRect`
// is identical either way and the browser layout tier is as blind to it as
// jsdom. The regression this guards against is a one-word edit, so the source
// is where it has to be caught. Same instrument, same reason, as gap #113's
// clipped-not-hidden label guard.
describe("gap ladder", () => {
// ── The spacing ladder (gap #122) ──
it("an off-ladder gap resolves to a real token instead of an undefined one", () => {
// The defect: `gap={7}` wrote `gap: var(--strand-space-7)` inline, the
// token does not exist, and an undefined custom property invalidates the
// WHOLE declaration. The grid rendered with no gap.
const { container } = render(x);
const style = (container.firstElementChild as HTMLElement).getAttribute("style") || "";
expect(style).toContain("--strand-space-6");
expect(style).not.toContain("--strand-space-7");
});
it("an on-ladder gap is untouched, so no existing consumer moves", () => {
const { container } = render(x);
const style = (container.firstElementChild as HTMLElement).getAttribute("style") || "";
expect(style).toContain("--strand-space-6");
});
});
import { snapshotFixtures } from "../../test/snapshot.js";
import { snapshotStylesheet } from "../../test/stylesheet.js";
import { fixtures } from "./Grid.fixtures.js";
snapshotFixtures(Grid, fixtures);
snapshotStylesheet(resolve(__dirname, "./Grid.css"));