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 {JSON.stringify(frame)}; } 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)", ); }); });