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");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,99 @@
|
||||
import { test, expect } from "bun:test";
|
||||
import {
|
||||
CORE_DESIGN_SYSTEMS,
|
||||
compileDesignSystem,
|
||||
validateDesignSystem,
|
||||
resolveDesignSystem,
|
||||
type DesignSystem,
|
||||
} from "../src/index.ts";
|
||||
|
||||
const sample = (): DesignSystem => ({
|
||||
schemaVersion: 1,
|
||||
id: "recipe",
|
||||
name: "Recipe",
|
||||
version: "1",
|
||||
tokens: { radius: { type: "dimension", value: "3px" } },
|
||||
recipes: {
|
||||
Button: { contractVersion: 1, base: { control: { borderRadius: { ref: "radius" } } } },
|
||||
},
|
||||
});
|
||||
test("three presets compile deterministically in both modes", () => {
|
||||
for (const system of Object.values(CORE_DESIGN_SYSTEMS)) {
|
||||
expect(validateDesignSystem(system)).toEqual([]);
|
||||
for (const mode of ["light", "dark"])
|
||||
expect(compileDesignSystem(system, { mode }).recipeCss).toBe(
|
||||
compileDesignSystem(structuredClone(system), { mode }).recipeCss,
|
||||
);
|
||||
}
|
||||
});
|
||||
test("contracts reject unknown components, parts, axes, states, versions and executable style values", () => {
|
||||
for (const recipes of [
|
||||
{ Unknown: { contractVersion: 1 } },
|
||||
{ Button: { contractVersion: 2 } },
|
||||
{ Button: { contractVersion: 1, base: { missing: { color: "#fff" } } } },
|
||||
{ Button: { contractVersion: 1, base: { control: { position: "fixed" } } } },
|
||||
{ Button: { contractVersion: 1, base: { control: { color: "red;}body{display:none" } } } },
|
||||
{
|
||||
Button: {
|
||||
contractVersion: 1,
|
||||
variants: [{ when: { variant: "invented" }, parts: { control: { color: "#fff" } } }],
|
||||
},
|
||||
},
|
||||
{
|
||||
Button: {
|
||||
contractVersion: 1,
|
||||
states: [{ state: "checked", parts: { control: { color: "#fff" } } }],
|
||||
},
|
||||
},
|
||||
{ Modal: { contractVersion: 1, base: { panel: { rotate: "2deg" } } } },
|
||||
])
|
||||
expect(validateDesignSystem({ ...sample(), recipes })[0]?.code).toBe("WRN-DS-RECIPE");
|
||||
});
|
||||
test("recipe token references are checked against property types and missing tokens", () => {
|
||||
const system = sample();
|
||||
system.recipes!.Button!.base!.control!.borderRadius = { ref: "missing" };
|
||||
expect(validateDesignSystem(system)[0]?.code).toBe("WRN-DS-REFERENCE-MISSING");
|
||||
system.tokens.radius = { type: "color", value: "#fff" };
|
||||
system.recipes!.Button!.base!.control!.borderRadius = { ref: "radius" };
|
||||
expect(validateDesignSystem(system)[0]?.code).toBe("WRN-DS-REFERENCE-TYPE");
|
||||
});
|
||||
test("inheritance merges base properties and preserves ordered compound/state rules", () => {
|
||||
const parent = sample();
|
||||
parent.id = "parent";
|
||||
const child: DesignSystem = {
|
||||
...sample(),
|
||||
extends: ["parent"],
|
||||
recipes: {
|
||||
Button: {
|
||||
contractVersion: 1,
|
||||
base: { control: { fontWeight: 700 } },
|
||||
variants: [
|
||||
{ when: { variant: "outline", size: "sm" }, parts: { control: { borderWidth: "2px" } } },
|
||||
],
|
||||
states: [{ state: "disabled", parts: { control: { opacity: 0.5 } } }],
|
||||
},
|
||||
},
|
||||
};
|
||||
const resolved = resolveDesignSystem(child, { registry: { parent } });
|
||||
expect(resolved.recipes.Button?.base?.control).toEqual({
|
||||
borderRadius: { ref: "radius" },
|
||||
fontWeight: 700,
|
||||
});
|
||||
const css = compileDesignSystem(child, { registry: { parent } }).recipeCss;
|
||||
expect(css.indexOf("font-weight:700")).toBeLessThan(css.indexOf("border-width:2px"));
|
||||
expect(css.indexOf("border-width:2px")).toBeLessThan(css.indexOf("opacity:0.5"));
|
||||
expect(css).not.toContain("!important");
|
||||
});
|
||||
|
||||
test("mode-dependent recipe values retain mode rules while identical values are deduplicated", () => {
|
||||
const system = sample();
|
||||
system.modes = {
|
||||
light: { radius: { type: "dimension", value: "3px" } },
|
||||
dark: { radius: { type: "dimension", value: "9px" } },
|
||||
};
|
||||
system.defaultMode = "light";
|
||||
const css = compileDesignSystem(system).recipeCss;
|
||||
expect(css).toContain('[data-theme="dark"]');
|
||||
expect(css).toContain("9px");
|
||||
expect(css).not.toContain('[data-theme="light"]');
|
||||
});
|
||||
Reference in new issue
Block a user