import { createRawSnippet } from "svelte";
import { describe, expect, it } from "vitest";
import ProductList from "../src/composites/ProductList.svelte";
import { productList } from "../src/composites/product-list.variants";
import ProductCatalogueConsumer from "./fixtures/ProductCatalogueConsumer.svelte";
import ProductCatalogueContractFailures from "./fixtures/ProductCatalogueContractFailures.svelte";
import { expectLayoutReadiness } from "./layout-readiness";
import { classes, classesOf, render, rendered } from "./mount";
describe("the deliberately failing product list fixture", () => {
it("has neither a collection name nor a name for its icon-only action", () => {
const root = render(ProductCatalogueContractFailures);
const list = root.querySelector("[data-unnamed-product-list]")!;
const action = root.querySelector("[data-unnamed-product-action]")!;
expect(list.getAttribute("aria-label")).toBeNull();
expect(list.getAttribute("aria-labelledby")).toBeNull();
expect(action.textContent?.trim()).toBe("");
expect(action.getAttribute("aria-label")).toBeNull();
});
});
describe("ProductList", () => {
it("composes canonical GridList and Card contracts into a named product collection", () => {
const actions = createRawSnippet(() => ({
render: () => '',
}));
const list = rendered(
render(ProductList, {
label: "Stationery",
columns: 3,
items: [
{
id: "field-notes",
name: "Field notes",
href: "/products/field-notes",
description: "A pocket notebook.",
price: "$12",
actions,
},
{ id: "desk-pad", name: "Desk pad", price: "$18" },
],
}),
);
expect(list.matches("ul[data-grid-list][data-product-list]")).toBe(true);
expect(list.getAttribute("aria-label")).toBe("Stationery");
expect(list.querySelectorAll("[data-card][data-product-list-item]")).toHaveLength(2);
expect(list.querySelector("a")?.getAttribute("href")).toBe("/products/field-notes");
expect(list.querySelector("a")?.textContent).toBe("Field notes");
const item = list.querySelector("[data-product-list-item]")!;
expect(item.getAttribute("aria-labelledby")).toBe(
item.querySelector("[data-product-list-name]")?.id,
);
});
it("preserves native product-link and action focus order", () => {
const actions = createRawSnippet(() => ({
render: () => '',
}));
const list = rendered(
render(ProductList, {
label: "Stationery",
items: [{ id: "field-notes", name: "Field notes", href: "/notes", actions }],
}),
);
const controls = [...list.querySelectorAll("a, button")];
expect(controls.map((control) => control.textContent)).toEqual([
"Field notes",
"Add to basket",
]);
for (const control of controls) {
control.focus();
expect(document.activeElement).toBe(control);
}
});
it("routes collection rhythm through Density and inherits nested appearance", () => {
const root = render(ProductCatalogueConsumer);
const list = root.querySelector("[data-product-list]")!;
expect(classes(list)).toEqual(classesOf(productList().root()));
expect(classes(list).has("gap-[var(--reddb-spatial-gap-md)]")).toBe(true);
expect(list.closest('[data-theme="base"][data-color-scheme="dark"][data-density="compact"]'))
.not.toBeNull();
expect(list.hasAttribute("data-theme")).toBe(false);
expect(list.hasAttribute("data-color-scheme")).toBe(false);
expect(list.hasAttribute("data-density")).toBe(false);
});
it("ships showcase, consumer, distributed export, and readiness evidence", () => {
expectLayoutReadiness("product-list", "ProductList");
});
});