Files

86 lines
3.8 KiB
TypeScript

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");
});