import { describe, expect, it } from "vitest"; import { mediaSourceModule } from "../modules/built-ins/media-source/media-source-module"; import { defineToolcraft } from "../schema/define-toolcraft"; import { createToolcraftState } from "./create-template-state"; import { importImageMedia } from "./reducer-media-test-utils"; import { toolcraftReducer } from "../composition/public-state"; import { createState } from "./reducer-test-utils"; describe("toolcraftReducer media import sizing", () => { it("imports media as an editable-canvas-sized layer and records history", () => { const state = createState(); const next = importImageMedia(state, { fileName: "material.png", position: { x: 10, y: 20 }, size: state.canvas.size, }); expect(next.layers).toEqual([ { displayName: "material", id: "layer-1", kind: "layer", name: "material", visible: true, }, ]); expect(next.mediaAssets).toEqual([ expect.objectContaining({ assetKind: "image", fileName: "material.png", id: "media-1", layerId: "layer-1", mimeType: "image/png", position: { x: 0, y: 0 }, size: state.canvas.size, sourceSize: state.canvas.size, lifecycle: "ready", resourceRef: "media:image:source", }), ]); expect(next.selectedLayerId).toBe("layer-1"); expect(next.history.undo.at(-1)?.label).toBe("Import media"); }); it("deletes imported media without deleting the owning layer", () => { const state = importImageMedia(createState(), { fileName: "material.png", position: { x: 10, y: 20 }, size: { height: 512, unit: "px", width: 512 }, }); const next = toolcraftReducer(state, { mediaId: "media-1", type: "media.delete", }); expect(next.layers).toEqual(state.layers); expect(next.selectedLayerId).toBe("layer-1"); expect(next.mediaAssets).toEqual([]); expect(next.history.undo.at(-1)?.label).toBe("Delete media"); }); it("keeps the current editable-output canvas size when importing background/source images", () => { const app = defineToolcraft({ base: { identity: { id: "runtime-test", title: "Toolcraft App" }, canvas: { enabled: true, size: { height: 720, unit: "px", width: 1280 }, upload: true, }, panels: {}, }, modules: [mediaSourceModule()], }); const state = createToolcraftState(app); const imported = importImageMedia(state, { resourceRef: "data:image/png;base64,wide", fileName: "wide-source.png", position: { x: 42, y: 24 }, size: { height: 1000, unit: "px", width: 4000 }, }); expect(imported.schema.canvas.sizing).toEqual({ mode: "editable-output" }); expect(imported.canvas.size).toEqual({ height: 720, unit: "px", width: 1280, }); expect(imported.mediaAssets[0]).toMatchObject({ fileName: "wide-source.png", position: { x: 0, y: 0 }, size: { height: 720, unit: "px", width: 1280 }, sourceSize: { height: 1000, unit: "px", width: 4000 }, }); expect(imported.history.undo.at(-1)?.after).not.toHaveProperty( "canvas.size", ); }); it("keeps manually edited editable-output canvas size when importing source images", () => { const app = defineToolcraft({ base: { identity: { id: "runtime-test", title: "Toolcraft App" }, canvas: { enabled: true, upload: true, }, panels: { controls: { sections: [], title: "Controls", }, }, }, modules: [mediaSourceModule()], }); const widthChanged = toolcraftReducer(createToolcraftState(app), { target: "canvas.size.width", type: "controls.setValue", value: "1440", }); const heightChanged = toolcraftReducer(widthChanged, { target: "canvas.size.height", type: "controls.setValue", value: "900", }); const imported = importImageMedia(heightChanged, { resourceRef: "data:image/png;base64,tall", fileName: "tall-source.png", size: { height: 4000, unit: "px", width: 1000 }, }); expect(imported.schema.canvas.sizing).toEqual({ mode: "editable-output" }); expect(imported.canvas.size).toEqual({ height: 900, unit: "px", width: 1440, }); expect(imported.values["canvas.size.width"]).toBe(1440); expect(imported.values["canvas.size.height"]).toBe(900); expect(imported.mediaAssets[0]).toMatchObject({ fileName: "tall-source.png", position: { x: 0, y: 0 }, size: { height: 900, unit: "px", width: 1440 }, sourceSize: { height: 4000, unit: "px", width: 1000 }, }); expect(imported.history.undo.at(-1)?.after).not.toHaveProperty( "canvas.size", ); }); it("sizes explicit intrinsic media apps from the imported image and replaces single-layer media", () => { const app = defineToolcraft({ base: { identity: { id: "runtime-test", title: "Toolcraft App" }, canvas: { enabled: true, sizing: { mode: "intrinsic-media" }, upload: true, }, panels: {}, }, modules: [mediaSourceModule()], }); const state = createToolcraftState(app); const first = importImageMedia(state, { resourceRef: "data:image/png;base64,first", fileName: "first.png", position: { x: 42, y: 24 }, size: { height: 600, unit: "px", width: 800 }, }); const second = importImageMedia(first, { resourceRef: "data:image/png;base64,second", fileName: "second.png", position: { x: -20, y: 10 }, size: { height: 720, unit: "px", width: 1280 }, }); expect(first.canvas.size).toEqual({ height: 600, unit: "px", width: 800 }); expect(first.mediaAssets[0]).toMatchObject({ fileName: "first.png", position: { x: 42, y: 24 }, size: { height: 600, unit: "px", width: 800 }, sourceSize: { height: 600, unit: "px", width: 800 }, }); expect(first.history.undo.at(-1)?.after).toMatchObject({ "canvas.size": { height: 600, unit: "px", width: 800 }, }); expect(second.canvas.size).toEqual({ height: 720, unit: "px", width: 1280, }); expect(second.layers).toHaveLength(1); expect(second.mediaAssets).toHaveLength(1); expect(second.mediaAssets[0]).toMatchObject({ fileName: "second.png", id: "media-1", layerId: "layer-1", position: { x: -20, y: 10 }, size: { height: 720, unit: "px", width: 1280 }, sourceSize: { height: 720, unit: "px", width: 1280 }, }); }); it("appends single-layer media when import is explicitly non-replacing", () => { const state = createState(); const first = importImageMedia( state, { resourceRef: "data:image/png;base64,first", fileName: "first.png", size: state.canvas.size, }, { replaceExisting: false }, ); const second = importImageMedia( first, { resourceRef: "data:image/png;base64,second", fileName: "second.png", size: state.canvas.size, }, { replaceExisting: false }, ); expect(second.layers.map((layer) => layer.id)).toEqual([ "layer-1", "layer-2", ]); expect(second.mediaAssets.map((asset) => asset.id)).toEqual([ "media-1", "media-2", ]); expect(second.mediaAssets.map((asset) => asset.fileName)).toEqual([ "first.png", "second.png", ]); expect(second.selectedLayerId).toBe("layer-2"); }); });