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,188 @@
|
||||
import { describe, test, expect } from "bun:test";
|
||||
import {
|
||||
adaptLegacyTheme,
|
||||
compileDesignSystem,
|
||||
defineDesignSystem,
|
||||
resolveDesignSystem,
|
||||
validateDesignSystem,
|
||||
DesignSystemError,
|
||||
type DesignSystem,
|
||||
} from "../src/index.ts";
|
||||
|
||||
const system = (): DesignSystem => ({
|
||||
schemaVersion: 1,
|
||||
id: "workshop",
|
||||
name: "Workshop",
|
||||
version: "1.0.0",
|
||||
defaultMode: "dark",
|
||||
tokens: {
|
||||
"brand.blue": { type: "color", value: "#2563eb" },
|
||||
"action.primary": { type: "color", value: { ref: "brand.blue" }, legacy: "color-primary" },
|
||||
"control.radius": { type: "dimension", value: "0", legacy: "radius-sm" },
|
||||
"body.font": { type: "fontFamily", value: '"Inter", sans-serif', legacy: "font-sans" },
|
||||
},
|
||||
modes: {
|
||||
light: { "brand.blue": { type: "color", value: "#1d4ed8" } },
|
||||
dark: { "brand.blue": { type: "color", value: "#60a5fa" } },
|
||||
},
|
||||
densities: { compact: { "control.radius": { type: "dimension", value: "2px" } } },
|
||||
});
|
||||
|
||||
describe("design-system engine", () => {
|
||||
test("resolves references after mode/density overrides and records provenance", () => {
|
||||
const result = resolveDesignSystem(system(), { mode: "light", density: "compact" });
|
||||
expect(result.tokens["action.primary"]).toEqual({
|
||||
type: "color",
|
||||
value: "#1d4ed8",
|
||||
legacy: "color-primary",
|
||||
reference: "brand.blue",
|
||||
source: "workshop.tokens.action.primary",
|
||||
});
|
||||
expect(result.tokens["control.radius"]?.value).toBe("2px");
|
||||
expect(result.tokens["control.radius"]?.legacy).toBe("radius-sm");
|
||||
});
|
||||
test("merges extensions in declared order, then selected mode", () => {
|
||||
const base = system();
|
||||
base.id = "base";
|
||||
const second: DesignSystem = {
|
||||
...base,
|
||||
id: "second",
|
||||
tokens: { "control.radius": { type: "dimension", value: "4px" } },
|
||||
modes: {},
|
||||
};
|
||||
const child: DesignSystem = {
|
||||
schemaVersion: 1,
|
||||
id: "child",
|
||||
name: "Child",
|
||||
version: "1",
|
||||
extends: ["base", "second"],
|
||||
tokens: {},
|
||||
};
|
||||
expect(
|
||||
resolveDesignSystem(child, { registry: { base, second } }).tokens["control.radius"]?.value,
|
||||
).toBe("4px");
|
||||
expect(
|
||||
resolveDesignSystem(child, { registry: { base, second } }).tokens["action.primary"]?.value,
|
||||
).toBe("#60a5fa");
|
||||
});
|
||||
test("rejects missing parents, extension cycles and alias cycles", () => {
|
||||
const a = system();
|
||||
a.extends = ["missing"];
|
||||
expect(() => resolveDesignSystem(a)).toThrow("WRN-DS-EXTENDS-MISSING");
|
||||
a.extends = ["workshop"];
|
||||
expect(() => resolveDesignSystem(a, { registry: { workshop: a } })).toThrow(
|
||||
"WRN-DS-EXTENDS-CYCLE",
|
||||
);
|
||||
const b = system();
|
||||
delete b.defaultMode;
|
||||
delete b.modes;
|
||||
b.tokens["brand.blue"]!.value = { ref: "action.primary" };
|
||||
expect(() => resolveDesignSystem(b)).toThrow("WRN-DS-REFERENCE-CYCLE");
|
||||
});
|
||||
test("rejects missing references, type mismatches and unknown axes", () => {
|
||||
const a = system();
|
||||
a.tokens["action.primary"]!.value = { ref: "missing" };
|
||||
expect(() => resolveDesignSystem(a)).toThrow("WRN-DS-REFERENCE-MISSING");
|
||||
a.tokens["action.primary"]!.value = { ref: "control.radius" };
|
||||
expect(() => resolveDesignSystem(a)).toThrow("WRN-DS-REFERENCE-TYPE");
|
||||
expect(() => resolveDesignSystem(system(), { mode: "unknown" })).toThrow("WRN-DS-MODE");
|
||||
expect(() => resolveDesignSystem(system(), { density: "unknown" })).toThrow("WRN-DS-DENSITY");
|
||||
a.modes!.dark!["control.radius"] = { type: "color", value: "#fff" };
|
||||
expect(() => resolveDesignSystem(a)).toThrow("WRN-DS-TYPE");
|
||||
});
|
||||
test("reports stable schema diagnostics and rejects unsafe values", () => {
|
||||
expect(validateDesignSystem(null)[0]?.code).toBe("WRN-DS-SCHEMA");
|
||||
for (const value of [
|
||||
"red;}body{display:none",
|
||||
"url(https://example.com)",
|
||||
"var(--arbitrary)",
|
||||
"#fff</style>",
|
||||
"/* hidden */#fff",
|
||||
]) {
|
||||
const a = system();
|
||||
a.tokens["brand.blue"]!.value = value;
|
||||
expect(validateDesignSystem(a).some((d) => d.code === "WRN-DS-VALUE")).toBe(true);
|
||||
}
|
||||
expect(() => defineDesignSystem({ ...system(), schemaVersion: 2 } as never)).toThrow(
|
||||
DesignSystemError,
|
||||
);
|
||||
expect(validateDesignSystem({ ...system(), layouts: {} })[0]?.code).toBe("WRN-DS-FIELD");
|
||||
const a = system();
|
||||
Object.defineProperty(a.tokens, "constructor", {
|
||||
value: { type: "number", value: 2 },
|
||||
enumerable: true,
|
||||
});
|
||||
expect(validateDesignSystem(a).some((d) => d.code === "WRN-DS-NAME")).toBe(true);
|
||||
});
|
||||
test("compiles deterministic CSS regardless of token insertion order", () => {
|
||||
const a = system();
|
||||
const b = system();
|
||||
b.tokens = Object.fromEntries(Object.entries(b.tokens).reverse());
|
||||
expect(compileDesignSystem(a).css).toBe(compileDesignSystem(b).css);
|
||||
expect(compileDesignSystem(a).css).toContain("--wrn-radius-sm:0;");
|
||||
expect(compileDesignSystem(a).css).toContain(':root[data-theme="light"]');
|
||||
expect(compileDesignSystem(a, { scope: "preview" }).css).toStartWith(
|
||||
'[data-wrn-design="preview"]',
|
||||
);
|
||||
expect(() => compileDesignSystem(a, { scope: 'bad"]{}' })).toThrow("WRN-DS-NAME");
|
||||
});
|
||||
test("rejects output collisions instead of silently choosing a token", () => {
|
||||
const a = system();
|
||||
a.tokens["control-radius"] = { type: "dimension", value: "3px" };
|
||||
expect(() => compileDesignSystem(a)).toThrow("WRN-DS-COLLISION");
|
||||
delete a.tokens["control-radius"];
|
||||
a.tokens.other = { type: "dimension", value: "3px", legacy: "radius-sm" };
|
||||
expect(() => adaptLegacyTheme(a, {})).toThrow("WRN-DS-COLLISION");
|
||||
});
|
||||
test("adapter preserves app configuration and explicit mode overrides without mutation", () => {
|
||||
const app = { default: "light", themes: { light: { "radius-sm": "9px" } }, palette: "rose" };
|
||||
const before = JSON.stringify(app);
|
||||
const result = adaptLegacyTheme(system(), app);
|
||||
expect(result.default).toBe("light");
|
||||
expect(result.palette).toBe("rose");
|
||||
expect(result.themes?.light?.["radius-sm"]).toBe("9px");
|
||||
expect(result.themes?.dark?.["radius-sm"]).toBe("0");
|
||||
expect(JSON.stringify(app)).toBe(before);
|
||||
});
|
||||
test("semantic validation checks unselected modes and densities", () => {
|
||||
const a = system();
|
||||
a.densities!.spacious = { bad: { type: "color", value: { ref: "missing" } } };
|
||||
expect(validateDesignSystem(a)[0]?.code).toBe("WRN-DS-REFERENCE-MISSING");
|
||||
delete a.densities;
|
||||
a.modes!.light!.bad = { type: "color", value: { ref: "missing" } };
|
||||
expect(validateDesignSystem(a)[0]?.code).toBe("WRN-DS-REFERENCE-MISSING");
|
||||
});
|
||||
test("validates typed dimensions, typography, duration, shadows and color scheme", () => {
|
||||
const a: DesignSystem = {
|
||||
schemaVersion: 1,
|
||||
id: "types",
|
||||
name: "Types",
|
||||
version: "1",
|
||||
tokens: {
|
||||
size: { type: "dimension", value: "1.5rem" },
|
||||
weight: { type: "fontWeight", value: 600 },
|
||||
time: { type: "duration", value: "150ms" },
|
||||
alpha: { type: "number", value: 0.5 },
|
||||
shadow: { type: "shadow", value: "0 2px 4px rgba(0, 0, 0, 0.2)" },
|
||||
scheme: { type: "colorScheme", value: "dark", legacy: "color-scheme" },
|
||||
color: { type: "color", value: "oklch(65% 0.2 250 / 90%)" },
|
||||
},
|
||||
};
|
||||
expect(validateDesignSystem(a)).toEqual([]);
|
||||
expect(compileDesignSystem(a).css).toContain("color-scheme:dark;");
|
||||
a.tokens.shadow!.value = "2px invalid";
|
||||
expect(validateDesignSystem(a)[0]?.code).toBe("WRN-DS-VALUE");
|
||||
});
|
||||
test("browser entry bundles without filesystem or provider dependencies", async () => {
|
||||
const build = await Bun.build({
|
||||
entrypoints: [
|
||||
new URL("../src/index.ts", import.meta.url).pathname.replace(/^\/([A-Za-z]:)/, "$1"),
|
||||
],
|
||||
target: "browser",
|
||||
});
|
||||
expect(build.success).toBe(true);
|
||||
const output = await build.outputs[0]!.text();
|
||||
expect(output).not.toContain("node:fs");
|
||||
expect(output).not.toContain("ANTHROPIC_API_KEY");
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user