import { createRawSnippet } from "svelte";
import { describe, expect, it } from "vitest";
import {
AspectRatio,
CARD_ACTIONS_ALIGNMENTS,
CARD_ORIENTATIONS,
CARD_PADDINGS,
CARD_VARIANTS,
Card,
DEFAULT_ASPECT_RATIO,
DIVIDER_ORIENTATIONS,
Divider,
aspectRatio as aspectRatioAppearance,
card as cardAppearance,
divider as dividerAppearance,
} from "./fixtures/surface-separation-consumer";
import CardActionsConsumer from "./fixtures/CardActionsConsumer.svelte";
import CardHorizontalConsumer from "./fixtures/CardHorizontalConsumer.svelte";
import SurfaceSeparationConsumer from "./fixtures/SurfaceSeparationConsumer.svelte";
import TestGlyph from "./fixtures/TestGlyph.svelte";
import { classes, classesOf, render, rendered, styleOf } from "./mount";
const text = (value: string) =>
createRawSnippet(() => ({ render: () => `${value}` }));
describe("the Base Card", () => {
it("offers complete edge and Density-owned padding vocabularies", () => {
expect(CARD_VARIANTS).toEqual(["outline", "plain"]);
expect(CARD_PADDINGS).toEqual(["none", "sm", "md"]);
expect(CARD_ACTIONS_ALIGNMENTS).toEqual(["start", "center", "end", "between"]);
expect(CARD_ORIENTATIONS).toEqual(["vertical", "horizontal"]);
for (const variant of CARD_VARIANTS) {
for (const padding of CARD_PADDINGS) {
const element = rendered(render(Card, { variant, padding }));
expect(classes(element)).toEqual(
classesOf(cardAppearance({ variant, padding }).root()),
);
}
}
});
it("keeps every section inset live against the nearest Density scope", () => {
for (const padding of ["sm", "md"] as const) {
const element = rendered(
render(Card, {
padding,
title: "Surface",
children: text("Body"),
footer: text("Footer"),
}),
);
const tokenClass = `p-[var(--reddb-spatial-inset-${padding})]`;
for (const section of element.children) expect(classes(section).has(tokenClass)).toBe(true);
expect(element.hasAttribute("data-density")).toBe(false);
}
});
it("omits absent sections and keeps caller content in document order", () => {
const bodyOnly = rendered(render(Card, { children: text("Body") }));
expect(bodyOnly.children).toHaveLength(1);
const complete = rendered(
render(Card, { title: "Header", children: text("Body"), footer: text("Footer") }),
);
expect(classes(complete.querySelector("[data-card-footer]")!).has("justify-start")).toBe(true);
expect([...complete.children].map((section) => section.textContent?.trim())).toEqual([
"Header",
"Body",
"Footer",
]);
});
it("aligns multiple footer actions without consumer layout classes", () => {
const alignments = {
start: "justify-start",
center: "justify-center",
end: "justify-end",
between: "justify-between",
} as const;
for (const [actionsAlign, expectedClass] of Object.entries(alignments)) {
const element = rendered(render(CardActionsConsumer, { actionsAlign }));
const footer = element.querySelector("[data-card-footer]")!;
expect(classes(footer).has(expectedClass)).toBe(true);
expect([...footer.querySelectorAll("button")].map((button) => button.className)).toEqual([
"",
"",
]);
}
});
it("places media beside an ordered content column with a narrow-container fallback", () => {
const card = rendered(render(CardHorizontalConsumer));
const layout = card.querySelector("[data-card-layout]")!;
const media = card.querySelector("[data-card-media]")!;
const content = card.querySelector("[data-card-content]")!;
expect([...layout.children]).toEqual([media, content]);
expect(
[...content.children].map((section) =>
["header", "body", "footer"].find((name) =>
section.hasAttribute(`data-card-${name}`),
),
),
).toEqual(["header", "body", "footer"]);
expect([...content.children].map((section) => section.textContent?.trim())).toEqual([
"Release Ready to ship",
"Review the changes.",
"Publish",
]);
for (const expected of ["@container/card", "overflow-hidden"]) {
expect(classes(card).has(expected)).toBe(true);
}
for (const expected of ["flex", "flex-col", "@sm/card:flex-row"]) {
expect(classes(layout).has(expected)).toBe(true);
}
expect(classes(media).has("@sm/card:w-2/5")).toBe(true);
});
it("renders a decorative title icon through Icon unless a full header replaces it", () => {
const standard = rendered(render(Card, { icon: TestGlyph, title: "Release" }));
const icon = standard.querySelector("[data-card-header] [data-icon]")!;
expect(icon.hasAttribute("data-test-glyph")).toBe(true);
expect(icon.getAttribute("aria-hidden")).toBe("true");
const overridden = rendered(
render(Card, { icon: TestGlyph, title: "Ignored", header: text("Custom header") }),
);
expect(overridden.querySelector("[data-icon]")).toBeNull();
expect(overridden.querySelector("[data-card-header]")?.textContent?.trim()).toBe(
"Custom header",
);
});
});
describe("the Base Divider", () => {
it("exposes its visual direction as separator semantics", () => {
expect(DIVIDER_ORIENTATIONS).toEqual(["horizontal", "vertical"]);
for (const orientation of DIVIDER_ORIENTATIONS) {
const element = rendered(render(Divider, { orientation }));
expect(element.getAttribute("role")).toBe("separator");
expect(element.getAttribute("aria-orientation")).toBe(orientation);
expect(classes(element)).toEqual(classesOf(dividerAppearance({ orientation })));
}
});
it("stays outside the keyboard order", () => {
const element = rendered(render(Divider, {}));
expect(element.tabIndex).toBe(-1);
});
});
describe("the Base AspectRatio", () => {
it("defaults to 16:9 while accepting an arbitrary caller ratio", () => {
const standard = rendered(render(AspectRatio, {}));
const portrait = rendered(render(AspectRatio, { ratio: 3 / 4, class: "max-w-md" }));
expect(styleOf(standard, "aspect-ratio")).toBe(`${DEFAULT_ASPECT_RATIO} / 1`);
expect(styleOf(portrait, "aspect-ratio")).toBe(`${3 / 4} / 1`);
expect(classes(portrait)).toEqual(
classesOf(aspectRatioAppearance({ class: "max-w-md" })),
);
});
it("preserves focusable caller content", () => {
const root = render(AspectRatio, {
ratio: 1,
children: createRawSnippet(() => ({
render: () => '',
})),
});
const button = root.querySelector("button")!;
button.focus();
expect(document.activeElement).toBe(button);
});
});
describe("surface and separation composition", () => {
it("inherits a nested appearance without selecting any axis", () => {
const root = render(SurfaceSeparationConsumer);
const scope = root.querySelector("[data-surface-appearance-scope]")!;
const capabilities = scope.querySelectorAll(
"[data-card], [data-divider], [data-aspect-ratio]",
);
expect(scope.getAttribute("data-theme")).toBe("marketing");
expect(scope.getAttribute("data-color-scheme")).toBe("dark");
expect(scope.getAttribute("data-density")).toBe("compact");
expect(capabilities.length).toBeGreaterThanOrEqual(4);
for (const capability of capabilities) {
expect(capability.hasAttribute("data-theme")).toBe(false);
expect(capability.hasAttribute("data-color-scheme")).toBe(false);
expect(capability.hasAttribute("data-density")).toBe(false);
}
});
});