import { cleanup, screen } from "@testing-library/react";
import { afterEach, describe, expect, it } from "vitest";
import {
createBackgroundSchema,
createIntrinsicMediaSchema,
createSchema,
createUploadDefaultSchema,
renderCanvasShell,
} from "./canvas-shell-test-utils";
import { useToolcraftProductSceneFrame } from "./product-scene-surface";
afterEach(() => {
cleanup();
});
describe("CanvasShell rendering", () => {
it("uses product bounds as the live infinite preview frame", () => {
function ProductFrameProbe(): React.JSX.Element {
const frame = useToolcraftProductSceneFrame();
return ;
}
const { container } = renderCanvasShell({
children: ,
initialState: {
canvas: {
mode: "infinite",
offset: { x: 0, y: 0 },
size: { height: 200, unit: "px", width: 300 },
zoom: 100,
},
},
productSceneBoundsProvider: () => [
{ height: 260, width: 420, x: -210, y: -130 },
],
});
const productScene = container.querySelector(
"[data-toolcraft-product-scene]",
);
expect(productScene?.style.height).toBe("260px");
expect(productScene?.style.left).toBe("-210px");
expect(productScene?.style.top).toBe("-130px");
expect(productScene?.style.width).toBe("420px");
expect(screen.getByTestId("product-frame").textContent).toBe(
JSON.stringify({
kind: "infinite",
rect: { height: 260, width: 420, x: -210, y: -130 },
}),
);
});
it("renders the editable canvas at the schema size", () => {
const { container } = renderCanvasShell();
const editableCanvas = container.querySelector(
"[data-toolcraft-editable-canvas]",
);
expect(editableCanvas?.style.width).toBe("300px");
expect(editableCanvas?.style.height).toBe("200px");
});
it("renders infinite mode without an editable-artboard boundary", () => {
const { container } = renderCanvasShell({
children: ,
initialState: {
canvas: {
mode: "infinite",
offset: { x: 0, y: 0 },
size: { height: 200, unit: "px", width: 300 },
zoom: 100,
},
},
});
const scene = container.querySelector(
"[data-toolcraft-infinite-scene]",
);
const slot = container.querySelector(
"[data-toolcraft-canvas-slot]",
);
expect(scene).toBeTruthy();
expect(scene?.style.width).toBe("");
expect(scene?.style.height).toBe("");
expect(scene?.className).toContain("overflow-visible");
expect(slot?.className).toContain("contents");
expect(
container.querySelector("[data-toolcraft-editable-canvas]"),
).toBeNull();
expect(
container.querySelector("[data-slot='toolcraft-runtime-canvas']")
?.style.backgroundColor,
).toBe("");
});
it("mounts product viewport content only in Infinity behind the transformed world", () => {
const finite = renderCanvasShell({
infiniteCanvasContent: ,
});
expect(
finite.container.querySelector("[data-toolcraft-infinite-canvas-content]"),
).toBeNull();
finite.unmount();
const { container } = renderCanvasShell({
children: ,
infiniteCanvasContent: ,
initialState: {
canvas: {
mode: "infinite",
offset: { x: 42, y: -18 },
size: { height: 200, unit: "px", width: 300 },
zoom: 75,
},
},
});
const backdrop = container.querySelector(
"[data-toolcraft-infinite-canvas-content]",
);
const world = container.querySelector(
"[data-toolcraft-canvas-world]",
);
expect(backdrop).toBeTruthy();
expect(backdrop?.className).toContain("pointer-events-none");
expect(backdrop?.className).toContain("absolute");
expect(backdrop?.className).toContain("inset-0");
expect(backdrop?.style.transform).toBe("");
expect(backdrop?.nextElementSibling).toBe(world);
expect(screen.getByTestId("infinite-viewport-content")).toBeTruthy();
expect(screen.getByTestId("bounded-product-content")).toBeTruthy();
});
it("fills the complete infinite viewport with the selected Background color", () => {
const { container } = renderCanvasShell({
initialState: {
canvas: {
mode: "infinite",
offset: { x: 0, y: 0 },
size: { height: 200, unit: "px", width: 300 },
zoom: 100,
},
values: {
"appearance.background": "#D4CECA",
"export.includeBackground": true,
},
},
schema: createBackgroundSchema(),
});
const viewport = container.querySelector(
"[data-slot='toolcraft-runtime-canvas']",
);
expect(viewport?.dataset.toolcraftInfiniteBackgroundColor).toBe("#D4CECA");
expect(viewport?.style.backgroundColor).toBe("rgb(212, 206, 202)");
expect(
container.querySelector("[data-toolcraft-editable-canvas]"),
).toBeNull();
});
it("normalizes structured color ingress before filling the infinite viewport", () => {
const { container } = renderCanvasShell({
initialState: {
canvas: {
mode: "infinite",
offset: { x: 0, y: 0 },
size: { height: 200, unit: "px", width: 300 },
zoom: 100,
},
values: {
"appearance.background": { hex: "#CB9672" },
"export.includeBackground": true,
},
},
schema: createBackgroundSchema(),
});
const viewport = container.querySelector(
"[data-slot='toolcraft-runtime-canvas']",
);
expect(viewport?.dataset.toolcraftInfiniteBackgroundColor).toBe("#CB9672");
expect(viewport?.style.backgroundColor).toBe("rgb(203, 150, 114)");
});
it("does not render a fixed editable canvas for explicit intrinsic media sizing", () => {
const { container } = renderCanvasShell({
schema: createIntrinsicMediaSchema(),
});
expect(container.querySelector("[data-toolcraft-editable-canvas]")).toBeNull();
});
it("renders the canvas slot inside the editable canvas when custom app content is provided", () => {
const { container } = renderCanvasShell({
children: ,
schema: createUploadDefaultSchema(),
});
const editableCanvas = container.querySelector(
"[data-toolcraft-editable-canvas]",
);
const slot = container.querySelector(
"[data-toolcraft-canvas-slot]",
);
expect(editableCanvas).toBeTruthy();
expect(slot?.parentElement).toBe(editableCanvas);
expect(screen.getByTestId("custom-canvas-renderer")).toBeTruthy();
});
it("does not render decorative canvas background dots", () => {
const { container } = renderCanvasShell();
const pattern = container.querySelector("[data-toolcraft-canvas-grid]");
const world = container.querySelector("[data-toolcraft-canvas-world]");
expect(world).toBeTruthy();
expect(pattern).toBeNull();
});
it("moves and scales the canvas world without decorative background dots", () => {
const { container } = renderCanvasShell({
initialState: {
canvas: {
offset: { x: 12, y: -8 },
size: { height: 200, unit: "px", width: 300 },
zoom: 150,
},
},
schema: createSchema(),
});
const pattern = container.querySelector("[data-toolcraft-canvas-grid]");
const world = container.querySelector("[data-toolcraft-canvas-world]");
expect(pattern).toBeNull();
expect(world?.style.transform).toBe("translate(-50%, -50%) translate(12px, -8px) scale(1.5)");
});
it("keeps minimum zoom world transform without decorative background dots", () => {
const { container } = renderCanvasShell({
initialState: {
canvas: {
offset: { x: 3200, y: -2400 },
size: { height: 200, unit: "px", width: 300 },
zoom: 25,
},
},
schema: createSchema(),
});
const pattern = container.querySelector("[data-toolcraft-canvas-grid]");
const world = container.querySelector("[data-toolcraft-canvas-world]");
expect(pattern).toBeNull();
expect(world?.style.transform).toBe(
"translate(-50%, -50%) translate(3200px, -2400px) scale(0.25)",
);
});
});