feat: release design system engine, studio and component contracts
This commit is contained in:
1 parent
ff1065fb27
commit
3943b946d6
693 files changed
+175546
-3894
No files matched your search
@@ -0,0 +1,152 @@
|
||||
import { test, expect } from "bun:test";
|
||||
import { StudioSession, importDesign, exportDesign, diffDesign } from "../src/index.ts";
|
||||
import { CORE_DESIGN_SYSTEMS, validateDesignSystem } from "../../design-system/src/index.ts";
|
||||
|
||||
test("invalid edits preserve a valid preview, raw input, undo/redo and reload", () => {
|
||||
const data = new Map<string, string>();
|
||||
const storage = {
|
||||
getItem: (k: string) => data.get(k) ?? null,
|
||||
setItem: (k: string, v: string) => {
|
||||
data.set(k, v);
|
||||
},
|
||||
};
|
||||
const session = new StudioSession(CORE_DESIGN_SYSTEMS.soft, storage);
|
||||
session.mutate((s) => {
|
||||
s.tokens["shape.control"]!.value = "13px";
|
||||
}, "Changed radius");
|
||||
session.edit('{"tokens":', "Incomplete JSON");
|
||||
expect(session.diagnostics[0]!.code).toBe("WRN-STUDIO-IMPORT");
|
||||
expect(session.valid.tokens["shape.control"]!.value).toBe("13px");
|
||||
session.undo();
|
||||
expect(session.diagnostics).toEqual([]);
|
||||
session.redo();
|
||||
expect(session.source).toBe('{"tokens":');
|
||||
const restored = new StudioSession(undefined, storage);
|
||||
expect(restored.restore()).toBe(true);
|
||||
expect(restored.source).toBe(session.source);
|
||||
expect(restored.valid.tokens["shape.control"]!.value).toBe("13px");
|
||||
expect(() => restored.markApplied()).toThrow();
|
||||
expect(() => restored.mutate(() => {}, "x")).toThrow();
|
||||
});
|
||||
|
||||
test("history stays bounded, branches after undo, and realistic full-preset history reloads", () => {
|
||||
const data = new Map<string, string>();
|
||||
const storage = {
|
||||
getItem: (k: string) => data.get(k) ?? null,
|
||||
setItem: (k: string, v: string) => {
|
||||
data.set(k, v);
|
||||
},
|
||||
};
|
||||
const session = new StudioSession(undefined, storage);
|
||||
for (let i = 0; i < 30; i++)
|
||||
session.mutate((s) => {
|
||||
s.name = "Revision " + i;
|
||||
}, "Name");
|
||||
expect(session.history).toHaveLength(25);
|
||||
const restored = new StudioSession(undefined, storage);
|
||||
expect(restored.restore()).toBe(true);
|
||||
restored.undo();
|
||||
restored.mutate((s) => {
|
||||
s.name = "Branch";
|
||||
}, "Branched");
|
||||
restored.redo();
|
||||
expect(restored.valid.name).toBe("Branch");
|
||||
expect(restored.history.at(-1)!.label).toBe("Branched");
|
||||
});
|
||||
|
||||
test("corrupted storage and quota failures do not destroy in-memory drafts", () => {
|
||||
const broken = new StudioSession(undefined, {
|
||||
getItem: () => "{",
|
||||
setItem: () => {
|
||||
throw Error("quota");
|
||||
},
|
||||
});
|
||||
expect(broken.restore()).toBe(false);
|
||||
expect(broken.storageError).toContain("could not be restored");
|
||||
broken.mutate((s) => {
|
||||
s.name = "Kept";
|
||||
}, "Edit");
|
||||
expect(broken.valid.name).toBe("Kept");
|
||||
expect(broken.storageError).toContain("Autosave unavailable");
|
||||
});
|
||||
|
||||
test("canonical import/export round trips all recipes, modes, aliases and metadata", () => {
|
||||
for (const system of Object.values(CORE_DESIGN_SYSTEMS)) {
|
||||
const artifact = exportDesign(system);
|
||||
const report = importDesign(artifact.canonical);
|
||||
expect(report.diagnostics).toEqual([]);
|
||||
expect(report.system).toEqual(system);
|
||||
expect(artifact.css).toContain("data-wrn-owner");
|
||||
expect(JSON.parse(artifact.theme).designSystem.system).toEqual(system);
|
||||
}
|
||||
});
|
||||
|
||||
test("legacy import reports unsupported fields and preserves literal semantic bindings", () => {
|
||||
const report = importDesign(
|
||||
JSON.stringify({
|
||||
default: "light",
|
||||
palette: "violet",
|
||||
themes: { light: { "color-primary": "#112233", unknown: "4px" } },
|
||||
}),
|
||||
"legacy",
|
||||
);
|
||||
expect(report.diagnostics).toEqual([]);
|
||||
expect(report.unsupported).toContain("palette");
|
||||
expect(report.unsupported).toContain("themes.light.unknown");
|
||||
expect(report.system!.modes!.light!["color.primary"]).toEqual({
|
||||
type: "color",
|
||||
value: "#112233",
|
||||
legacy: "color-primary",
|
||||
});
|
||||
});
|
||||
|
||||
test("exchange subset preserves aliases and reports ambiguous semantic mapping and unsupported composites", () => {
|
||||
const report = importDesign(
|
||||
JSON.stringify({
|
||||
brand: {
|
||||
$type: "color",
|
||||
base: { $value: "#112233" },
|
||||
action: { $value: "{brand.base}" },
|
||||
complex: { $value: { colorSpace: "srgb", components: [0, 0, 0] } },
|
||||
},
|
||||
$extensions: { x: 1 },
|
||||
}),
|
||||
"tokens",
|
||||
);
|
||||
expect(report.diagnostics).toEqual([]);
|
||||
expect(report.system!.tokens["brand.action"]!.value).toEqual({ ref: "brand.base" });
|
||||
expect(report.unsupported).toContain("brand.complex");
|
||||
expect(report.unsupported).toContain("$extensions");
|
||||
expect(report.ambiguous).toHaveLength(2);
|
||||
});
|
||||
|
||||
test("imports reject oversized, cyclic, executable, polluted and missing-reference documents", () => {
|
||||
expect(importDesign(" ".repeat(2_000_001)).diagnostics).not.toEqual([]);
|
||||
expect(
|
||||
importDesign('{"__proto__":{"$type":"color","$value":"#000"}}', "tokens").diagnostics,
|
||||
).not.toEqual([]);
|
||||
for (const tokens of [
|
||||
{ a: { type: "color", value: { ref: "missing" } } },
|
||||
{ a: { type: "color", value: { ref: "b" } }, b: { type: "color", value: { ref: "a" } } },
|
||||
{ a: { type: "color", value: "red;}body{display:none" } },
|
||||
]) {
|
||||
const system = { schemaVersion: 1, id: "bad", name: "Bad", version: "1", tokens };
|
||||
expect(importDesign(JSON.stringify(system)).diagnostics).not.toEqual([]);
|
||||
}
|
||||
});
|
||||
|
||||
test("review diffs identify affected paths and applied snapshots are detached", () => {
|
||||
const session = new StudioSession();
|
||||
session.mutate((s) => {
|
||||
s.tokens["shape.control"]!.value = "6px";
|
||||
}, "Radius");
|
||||
expect(session.changes()).toContain("$.tokens.shape.control.value");
|
||||
session.markApplied();
|
||||
expect(session.changes()).toEqual([]);
|
||||
session.mutate((s) => {
|
||||
s.name = "Other";
|
||||
}, "Name");
|
||||
expect(session.applied.name).not.toBe("Other");
|
||||
expect(diffDesign({ a: 1 }, { a: 2, b: 3 })).toEqual(["$.a", "$.b"]);
|
||||
expect(validateDesignSystem(session.valid)).toEqual([]);
|
||||
});
|
||||
Reference in new issue
Block a user