import { test, expect } from "bun:test"; import { CORE_DESIGN_SYSTEMS } from "@wrnexus/design-system"; import { createDesignPackage, readDesignPackage } from "@wrnexus/design-system/package"; import { StudioSession } from "../src/session.ts"; import { planDesignUpgrade, applyDesignUpgrade } from "../src/upgrades.ts"; test("shared packages have deterministic pins independent of object key order and reject changed or future content", async () => { const system = CORE_DESIGN_SYSTEMS.square; const first = await createDesignPackage(system); const reordered = { ...system, tokens: Object.fromEntries(Object.entries(system.tokens).reverse()), }; expect((await createDesignPackage(reordered)).integrity.digest).toBe(first.integrity.digest); expect((await readDesignPackage(JSON.stringify(first))).package).toEqual(first); const changed = structuredClone(first); changed.system.tokens["color.primary"]!.value = "#abcdef"; await expect(readDesignPackage(JSON.stringify(changed))).rejects.toThrow("INTEGRITY"); await expect(readDesignPackage(JSON.stringify({ ...first, formatVersion: 2 }))).rejects.toThrow( "VERSION", ); await expect(createDesignPackage({ ...system, extends: ["company"] })).rejects.toThrow(); expect((await readDesignPackage(JSON.stringify(system))).migrations).toEqual([ "canonical-v1-to-package-v1", ]); }); test("upgrade review and explicit adoption respect identity, pins, locks, stale revisions and recoverable history", async () => { const session = new StudioSession(); const next = structuredClone(session.valid); next.version = "1.1.0"; next.tokens["color.primary"]!.value = "#abcdef"; const source = JSON.stringify(await createDesignPackage(next)); const plan = await planDesignUpgrade(session, source); expect(plan.canApply).toBe(true); expect(session.valid.version).toBe("1.0.0"); session.setLocks(["/tokens/color.primary"]); expect((await planDesignUpgrade(session, source)).canApply).toBe(false); await expect(applyDesignUpgrade(session, source, plan.baseRevision)).rejects.toThrow("CONFLICT"); const locked = await planDesignUpgrade(session, source); await expect(applyDesignUpgrade(session, source, locked.baseRevision)).rejects.toThrow("LOCK"); session.setLocks([]); const fresh = await planDesignUpgrade(session, source); await applyDesignUpgrade(session, source, fresh.baseRevision); expect(session.valid.version).toBe("1.1.0"); session.undo(); expect(session.valid.version).toBe("1.0.0"); session.redo(); expect(session.valid.version).toBe("1.1.0"); await expect( planDesignUpgrade( session, JSON.stringify(await createDesignPackage({ ...next, id: "different" })), ), ).rejects.toThrow("identity"); session.edit("{unfinished"); await expect(planDesignUpgrade(session, source)).rejects.toThrow("source errors"); }); test("legacy unversioned drafts migrate losslessly; future drafts remain untouched by autosave", () => { let saved = ""; const storage = { getItem: () => saved || null, setItem: (_key: string, value: string) => { saved = value; }, }; const old = new StudioSession(undefined, storage); old.edit("{unfinished"); const legacy = JSON.parse(saved); delete legacy.storageVersion; saved = JSON.stringify(legacy); const restored = new StudioSession(undefined, storage); expect(restored.restore()).toBe(true); expect(restored.source).toBe("{unfinished"); restored.undo(); expect(JSON.parse(saved).storageVersion).toBe(1); const future = JSON.stringify({ ...legacy, storageVersion: 99 }); saved = future; const older = new StudioSession(undefined, storage); expect(older.restore()).toBe(false); expect(older.recoverySource).toBe(future); older.edit("{another edit"); older.persist(); expect(saved).toBe(future); expect(older.storageError).toContain("protected"); });