import { describe, expect, it, vi } from "vitest"; import { defineToolcraft } from "../schema/define-toolcraft"; import { createToolcraftState } from "../state/create-template-state"; import { renderToolcraftArtifactFrame } from "./artifact-frame-renderer"; function deferred() { let resolve!: () => void; const promise = new Promise((done) => { resolve = done; }); return { promise, resolve }; } function createContext(events: string[]) { return { clearRect: vi.fn(() => events.push("clear")), fillRect: vi.fn(() => events.push("background")), fillStyle: "", restore: vi.fn(() => events.push("restore")), save: vi.fn(() => events.push("save")), scale: vi.fn(() => events.push("scale")), setTransform: vi.fn(() => events.push("reset-transform")), translate: vi.fn(() => events.push("translate")), }; } describe("renderToolcraftArtifactFrame", () => { it("keeps the scene transform active until async product rendering settles", async () => { const events: string[] = []; const pending = deferred(); const context = createContext(events); const canvas = { getContext: vi.fn(() => context), height: 200, width: 400, } as unknown as HTMLCanvasElement; const state = createToolcraftState( defineToolcraft({ canvas: { enabled: true }, panels: {} }), ); const rendering = renderToolcraftArtifactFrame({ backgroundColor: "#123456", canvas, frame: { height: 100, width: 200, x: -10, y: -20 }, includeBackground: true, pixelRatio: 2, renderProductFrame: async () => { events.push("product-start"); await pending.promise; events.push("product-end"); }, renderRuntimeScene: async () => { events.push("runtime-scene"); }, rendererPipeline: null, state, }); await Promise.resolve(); expect(events).toEqual([ "reset-transform", "clear", "background", "runtime-scene", "save", "scale", "translate", "product-start", ]); pending.resolve(); await rendering; expect(events.at(-2)).toBe("product-end"); expect(events.at(-1)).toBe("restore"); }); it("restores the context and types an unknown renderer failure", async () => { const events: string[] = []; const context = createContext(events); const canvas = { getContext: vi.fn(() => context), height: 100, width: 100, } as unknown as HTMLCanvasElement; const state = createToolcraftState( defineToolcraft({ canvas: { enabled: true }, panels: {} }), ); await expect( renderToolcraftArtifactFrame({ backgroundColor: "#000000", canvas, frame: { height: 100, width: 100, x: 0, y: 0 }, includeBackground: false, pixelRatio: 1, renderProductFrame: () => { throw new Error("broken product"); }, renderRuntimeScene: async () => undefined, rendererPipeline: null, state, }), ).rejects.toMatchObject({ feedback: { code: "product-frame-render-failed" }, }); expect(context.restore).toHaveBeenCalledOnce(); }); });