import { describe, expect, expectTypeOf, it } from "vitest"; import * as toolcraftRuntimeRoot from "../index"; import { defineToolcraft } from "../schema/define-toolcraft"; import type { ToolcraftState } from "../state/types"; import type { ToolcraftArtifactSize, ToolcraftExportFrame, ToolcraftPreviewBackgroundOptions, ToolcraftProductExportFrameContext, ToolcraftProductExportFrameRenderer, ToolcraftProductExportRenderer, ToolcraftRetinaExportSize, } from "../index"; import { shouldIncludeToolcraftPreviewBackground, } from "./export-background"; import { getToolcraftImageExportSize, getToolcraftRetinaExportPixelRatio, getToolcraftRetinaExportSize, getToolcraftVideoExportSize, } from "./export-sizing"; function createState(schema = defineToolcraft({ canvas: { enabled: true }, panels: {} })): ToolcraftState { return { canvas: { mode: "finite", offset: { x: 0, y: 0 }, size: { height: 100, unit: "px", width: 200 }, zoom: 70, }, defaults: {}, history: { redo: [], undo: [], }, layers: [], mediaAssets: [], panels: { controls: { collapsedSections: {}, offset: { x: 0, y: 0 } }, layers: { offset: { x: 0, y: 0 } }, timeline: { offset: { x: 0, y: 0 } }, toolbar: { offset: { x: 0, y: 0 } }, }, schema, selectedLayerId: null, timeline: { currentTimeSeconds: 0, durationSeconds: 8, expanded: false, isLooping: true, isPlaying: false, keyframeGroups: [], selectedKeyframeId: null, }, values: {}, }; } describe("Toolcraft export helpers", () => { it("keeps the runtime root limited to product-safe export primitives", () => { expect(toolcraftRuntimeRoot).toMatchObject({ getToolcraftImageExportSize: expect.any(Function), getToolcraftRetinaExportPixelRatio: expect.any(Function), getToolcraftRetinaExportSize: expect.any(Function), getToolcraftVideoExportSize: expect.any(Function), shouldIncludeToolcraftPreviewBackground: expect.any(Function), TOOLCRAFT_MAX_EXPORT_EDGE_PX: 8192, TOOLCRAFT_MAX_EXPORT_PIXELS: 8192 * 8192, toolcraftImageExportFormatTarget: "export.image.format", toolcraftImageExportResolutionTarget: "export.image.resolution", toolcraftVideoExportFormatTarget: "export.video.format", toolcraftVideoExportResolutionTarget: "export.video.resolution", validateToolcraftArtifactSize: expect.any(Function), }); expect(toolcraftRuntimeRoot).not.toHaveProperty("exportToolcraftImageArtifact"); expect(toolcraftRuntimeRoot).not.toHaveProperty("exportToolcraftVideoArtifact"); expect(toolcraftRuntimeRoot).not.toHaveProperty( "createToolcraftVideoEncoderBackend", ); expect(toolcraftRuntimeRoot).not.toHaveProperty( "createToolcraftVideoFrameSchedule", ); expectTypeOf().toMatchTypeOf< Readonly<{ height: number; width: number }> >(); expectTypeOf().toMatchTypeOf< Readonly<{ height: number; width: number; x: number; y: number }> >(); expectTypeOf().toHaveProperty("state"); expectTypeOf().toHaveProperty("frame"); expectTypeOf().toBeFunction(); expectTypeOf().toHaveProperty("renderFrame"); expectTypeOf().toHaveProperty("pixelRatio"); }); it("uses include-background runtime state for live preview background", () => { const state = createState(); expect(shouldIncludeToolcraftPreviewBackground({ state })).toBe(true); state.values["export.includeBackground"] = false; expect(shouldIncludeToolcraftPreviewBackground({ state })).toBe(false); state.values["export.includeBackground"] = true; expect(shouldIncludeToolcraftPreviewBackground({ state })).toBe(true); }); it("suppresses bounded product-rendered backgrounds in the infinite workspace", () => { const state = createState(); state.values["export.includeBackground"] = true; expect(shouldIncludeToolcraftPreviewBackground({ state })).toBe(true); state.canvas.mode = "infinite"; expect(shouldIncludeToolcraftPreviewBackground({ state })).toBe(false); state.values["export.includeBackground"] = "include"; expect(shouldIncludeToolcraftPreviewBackground({ state })).toBe(false); state.canvas.mode = "finite"; expect(shouldIncludeToolcraftPreviewBackground({ state })).toBe(true); }); it("uses at least 2x pixel ratio for retina export", () => { expect(getToolcraftRetinaExportPixelRatio(1)).toBe(2); expect(getToolcraftRetinaExportPixelRatio(1.5)).toBe(2); expect(getToolcraftRetinaExportPixelRatio(3)).toBe(3); }); it("resolves retina export size from canvas size rather than viewport zoom", () => { const state = createState(); state.canvas.zoom = 35; expect(getToolcraftRetinaExportSize({ devicePixelRatio: 2, state })).toEqual({ height: 200, pixelRatio: 2, width: 400, }); }); it("resolves image export resolution presets from canvas aspect ratio", () => { const state = createState(); expect(getToolcraftImageExportSize({ resolution: "2k", state })).toEqual({ height: 1024, pixelRatio: 10.24, width: 2048, }); expect(getToolcraftImageExportSize({ resolution: "4k", state })).toEqual({ height: 2048, pixelRatio: 20.48, width: 4096, }); expect(getToolcraftImageExportSize({ resolution: "8k", state })).toEqual({ height: 4096, pixelRatio: 40.96, width: 8192, }); }); it("preserves portrait aspect ratio for image export resolution presets", () => { const state = createState(); state.canvas.size = { height: 200, unit: "px", width: 100 }; expect(getToolcraftImageExportSize({ resolution: "4k", state })).toEqual({ height: 4096, pixelRatio: 20.48, width: 2048, }); }); it("falls back to retina sizing for current or unknown image export resolution", () => { const state = createState(); expect(getToolcraftImageExportSize({ devicePixelRatio: 2, resolution: "current", state })) .toEqual({ height: 200, pixelRatio: 2, width: 400, }); expect(getToolcraftImageExportSize({ devicePixelRatio: 2, resolution: "source", state })) .toEqual({ height: 200, pixelRatio: 2, width: 400, }); }); it("resolves current video export to even current output dimensions", () => { const state = createState(); state.canvas.size = { height: 101, unit: "px", width: 201 }; expect(getToolcraftVideoExportSize({ devicePixelRatio: 2, resolution: "current", state })) .toEqual({ height: 102, pixelRatio: 102 / 101, width: 202, }); }); it("keeps current video export distinct from 4k export at default canvas size", () => { const state = createState(); state.canvas.size = { height: 1080, unit: "px", width: 1920 }; expect(getToolcraftVideoExportSize({ devicePixelRatio: 2, resolution: "current", state })) .toEqual({ height: 1080, pixelRatio: 1, width: 1920, }); expect(getToolcraftVideoExportSize({ resolution: "4k", state })).toEqual({ height: 2160, pixelRatio: 2, width: 3840, }); }); it("fits 4k video export inside an encoder-safe UHD box", () => { const state = createState(); state.canvas.size = { height: 900, unit: "px", width: 1440 }; expect(getToolcraftVideoExportSize({ resolution: "4k", state })).toEqual({ height: 2160, pixelRatio: 2.4, width: 3456, }); }); it("preserves 16:9 at UHD dimensions for 4k video export", () => { const state = createState(); state.canvas.size = { height: 1080, unit: "px", width: 1920 }; expect(getToolcraftVideoExportSize({ resolution: "4k", state })).toEqual({ height: 2160, pixelRatio: 2, width: 3840, }); }); it("reports the actual rounded pixel ratio for 4k video export", () => { const state = createState(); state.canvas.size = { height: 201, unit: "px", width: 333 }; expect(getToolcraftVideoExportSize({ resolution: "4k", state })).toEqual({ height: 2160, pixelRatio: 2160 / 201, width: 3578, }); }); });