import { describe, expect, it } from "vitest"; import { defineToolcraft } from "./define-toolcraft"; import { resolveToolcraftPersistencePlan } from "./persistence-plan"; import type { ResolvedToolcraftLocalStoragePersistenceSchema, ToolcraftLocalStoragePersistenceSchema, } from "./types"; function assertResolvedPersistenceTypeIsReadonly( resolved: ResolvedToolcraftLocalStoragePersistenceSchema, explicit: ToolcraftLocalStoragePersistenceSchema, ): void { // @ts-expect-error Resolved persistence is immutable after normalization. resolved.version = 3; // @ts-expect-error Resolved persistence slices are immutable after normalization. resolved.include.push("media"); // @ts-expect-error Resolved persistence value targets are immutable after normalization. resolved.additionalValueTargets.push("composition.layout"); explicit.version = 3; } void assertResolvedPersistenceTypeIsReadonly; function createBaseSchema() { return defineToolcraft({ identity: { id: "base-tool", title: "Base Tool" }, canvas: { enabled: true }, panels: {}, }); } describe("resolveToolcraftPersistencePlan", () => { it("persists the frozen base workspace slices by default", () => { const app = createBaseSchema(); expect(app.persistence).toEqual({ additionalValueTargets: [], include: ["canvas", "panels", "values"], key: "toolcraft:base-tool:state:v2", storage: "localStorage", version: 2, }); expect(app.persistence.storage).toBe("localStorage"); if (app.persistence.storage === "localStorage") { expect(Object.isFrozen(app.persistence)).toBe(true); expect(Object.isFrozen(app.persistence.additionalValueTargets)).toBe(true); expect(Object.isFrozen(app.persistence.include)).toBe(true); } }); it("adds layers only when the resolved layers panel exists", () => { const app = defineToolcraft({ identity: { id: "layers", title: "Layers" }, canvas: { enabled: true }, panels: { layers: true }, }); expect(app.persistence).toMatchObject({ include: ["canvas", "layers", "panels", "values"], }); }); it("adds timeline only when the resolved timeline is enabled", () => { const enabled = defineToolcraft({ identity: { id: "timeline", title: "Timeline" }, canvas: { enabled: true }, panels: { timeline: true }, }); const disabled = defineToolcraft({ identity: { id: "no-timeline", title: "No Timeline" }, canvas: { enabled: true }, panels: { timeline: { enabled: false } }, }); expect(enabled.persistence).toMatchObject({ include: ["canvas", "panels", "timeline", "values"], }); expect(disabled.persistence).toMatchObject({ include: ["canvas", "panels", "values"], }); }); it("adds media for declared default assets", () => { const app = defineToolcraft({ identity: { id: "defaults", title: "Defaults" }, canvas: { enabled: true }, media: { defaultAssets: [ { assetKind: "image", dataUrl: "data:image/png;base64,AA==", fileName: "default.png", }, ], }, panels: {}, }); expect(app.persistence).toMatchObject({ include: ["canvas", "media", "panels", "values"], }); }); it("consumes typed app capabilities without inspecting assembly commands", () => { const persistence = resolveToolcraftPersistencePlan({ appCapabilities: Object.freeze({ hasMedia: true }), identity: Object.freeze({ id: "typed-capabilities", title: "Typed Capabilities" }), panels: {}, persistence: undefined, }); expect(persistence).toMatchObject({ include: ["canvas", "media", "panels", "values"], }); }); it("adds media for canvas upload capability", () => { const app = defineToolcraft({ identity: { id: "canvas-upload", title: "Canvas Upload" }, canvas: { enabled: true, upload: true }, panels: {}, }); expect(app.persistence).toMatchObject({ include: ["canvas", "media", "panels", "values"], }); }); it("adds media for panel fileDrop capability", () => { const app = defineToolcraft({ identity: { id: "panel-upload", title: "Panel Upload" }, canvas: { enabled: true }, panels: { controls: { sections: [ { controls: { source: { assetKind: "file", target: "source.files", type: "fileDrop", }, }, title: "Source", }, ], title: "Controls", }, }, }); expect(app.persistence).toMatchObject({ include: ["canvas", "media", "panels", "values"], }); }); it("does not add media to a truly media-free app", () => { expect(createBaseSchema().persistence).toMatchObject({ include: ["canvas", "panels", "values"], }); }); it("honors explicit storage none as the exact opt-out", () => { const app = defineToolcraft({ identity: { id: "private", title: "Private" }, canvas: { enabled: true, upload: true }, panels: { layers: true, timeline: true }, persistence: { storage: "none" }, }); expect(app.persistence).toEqual({ storage: "none" }); }); it("preserves explicit key and version while sorting a unique mandatory union", () => { const app = defineToolcraft({ identity: { id: "explicit", title: "Explicit" }, canvas: { enabled: true, upload: true }, panels: { layers: true, timeline: true }, persistence: { additionalValueTargets: [ " composition.layout ", "composition.layout", "", ], include: ["values", "layers", "values"], key: "toolcraft:legacy:state:v7", storage: "localStorage", version: 7, }, }); expect(app.persistence).toEqual({ additionalValueTargets: ["composition.layout"], include: ["canvas", "layers", "media", "panels", "timeline", "values"], key: "toolcraft:legacy:state:v7", storage: "localStorage", version: 7, }); }); });