Files
WRNexusJS/packages/design-studio/test/generation.test.ts
T
Clintchiz c765d76a06
Quality / quality (ubuntu-latest) (push) Failing after 10m52s
Quality / quality (windows-latest) (push) Canceled after 0s
feat(studio): add thirteen presets and preview reviewed proposals
2026-10-19 14:51:03 +05:30

332 lines
13 KiB
TypeScript

import { test, expect } from "bun:test";
import { CORE_DESIGN_SYSTEMS, validateDesignSystem } from "../../design-system/src/index.ts";
import {
generateDesigns,
DEFAULT_DESIGN_BRIEF,
type GenerationRequest,
type GenerationProvider,
} from "../src/generation.ts";
import { designRevision, checkDecisionLocks, preserveDecisionLocks } from "../src/decisions.ts";
import { combineDesignProposals } from "../src/guidance.ts";
import { StudioSession } from "../src/session.ts";
const source = () => structuredClone(CORE_DESIGN_SYSTEMS.square);
async function request(
kind: "directions" | "edit" = "directions",
locks: string[] = [],
system = source(),
): Promise<GenerationRequest> {
return {
system,
locks,
brief: { ...DEFAULT_DESIGN_BRIEF, product: "Booking site", audience: "Mobile customers" },
baseRevision: await designRevision(system, locks),
kind,
instruction: kind === "edit" ? "make buttons rounded and use spacious spacing" : undefined,
};
}
const patched = (changes: unknown[]) =>
JSON.stringify({
alternatives: [{ name: "Edited", rationale: "Requested change", patch: changes }],
});
const set = (path: string, value: unknown) => ({ op: "set", path, value });
test("local guided generation produces three distinct validated directions without changing source", async () => {
const input = await request();
const original = JSON.stringify(input.system);
const result = await generateDesigns(input);
expect(result.diagnostics).toEqual([]);
expect(result.alternatives).toHaveLength(3);
expect(new Set(result.alternatives.map((p) => JSON.stringify(p.system))).size).toBe(3);
for (const proposal of result.alternatives) {
expect(validateDesignSystem(proposal.system)).toEqual([]);
expect(Object.keys(proposal.system.recipes!)).toHaveLength(104);
expect(proposal.system.id).toBe(input.system.id);
}
expect(JSON.stringify(input.system)).toBe(original);
expect(result.provenance.provider).toBe("local-rules");
expect(result.provenance.maxOutputTokens).toBe(0);
});
test("guided geometry, density, surface and brand choices affect the brief direction", async () => {
const input = await request();
input.brief = {
...input.brief,
geometry: "rounded",
density: "spacious",
surface: "soft",
brandColor: "#112233",
};
const result = await generateDesigns(input);
expect(result.diagnostics).toEqual([]);
const system = result.alternatives[0]!.system;
expect(system.tokens["shape.control"]!.value).toBe("16px");
expect(system.tokens["space.control"]!.value).toBe("1.25rem");
expect(system.tokens["elevation.panel"]!.value).toContain("24px");
expect(system.modes!.light!["color.primary"]!.value).toBe("#112233");
expect(result.brief).toEqual(input.brief);
});
test("direct locks, mode overrides and alias changes cannot alter a locked token", () => {
const base = source();
base.tokens["shape.alias"] = { type: "dimension", value: { ref: "shape.control" } };
let next = structuredClone(base);
next.tokens["shape.control"]!.value = "8px";
expect(checkDecisionLocks(base, next, ["/tokens/shape.alias"])).not.toEqual([]);
next = structuredClone(base);
next.modes!.light!["shape.control"] = { type: "dimension", value: "8px" };
expect(checkDecisionLocks(base, next, ["/tokens/shape.alias"])).not.toEqual([]);
next = structuredClone(base);
next.tokens["space.page"]!.value = "5rem";
expect(checkDecisionLocks(base, next, ["/tokens/shape.alias"])).toEqual([]);
});
test("component recipe locks protect referenced token values and absent decisions", () => {
const base = source(),
next = structuredClone(base);
next.tokens["shape.control"]!.value = "8px";
expect(checkDecisionLocks(base, next, ["/recipes/Button"])).not.toEqual([]);
const added = structuredClone(base);
added.recipes!.Button!.base!.control!.rotate = "2deg";
expect(checkDecisionLocks(base, added, ["/recipes/Button/base/control/rotate"])).not.toEqual([]);
});
test("directions preserve locks, including recipe token dependencies", async () => {
const input = await request("directions", ["/recipes/Button"]);
const result = await generateDesigns(input);
expect(result.diagnostics).toEqual([]);
for (const p of result.alternatives)
expect(checkDecisionLocks(input.system, p.system, input.locks)).toEqual([]);
const next = preserveDecisionLocks(
input.system,
structuredClone(CORE_DESIGN_SYSTEMS.soft),
input.locks,
);
expect(checkDecisionLocks(input.system, next, input.locks)).toEqual([]);
});
test("manual edits, undo, reload and unlock keep lock-violating drafts recoverable", () => {
const memory = new Map<string, string>();
const storage = {
getItem: (k: string) => memory.get(k) ?? null,
setItem: (k: string, v: string) => {
memory.set(k, v);
},
};
const session = new StudioSession(undefined, storage);
session.setLocks(["/tokens/shape.control"]);
session.mutate((s) => {
s.tokens["shape.control"]!.value = "9px";
}, "Locked edit");
expect(session.valid.tokens["shape.control"]!.value).toBe("0");
expect(session.diagnostics[0]!.code).toBe("WRN-STUDIO-LOCK");
expect(session.source).toContain("9px");
const restored = new StudioSession(undefined, storage);
expect(restored.restore()).toBe(true);
expect(restored.locks).toEqual(session.locks);
expect(restored.diagnostics[0]!.code).toBe("WRN-STUDIO-LOCK");
restored.setLocks([]);
expect(restored.valid.tokens["shape.control"]!.value).toBe("9px");
restored.undo();
expect(restored.valid.tokens["shape.control"]!.value).toBe("0");
});
test("word-based edits validate whole requests and reject unknown or locked changes", async () => {
const result = await generateDesigns(await request("edit"));
expect(result.diagnostics).toEqual([]);
expect(result.alternatives[0]!.system.tokens["shape.control"]!.value).toBe("16px");
const locked = await generateDesigns(await request("edit", ["/tokens/shape.control"]));
expect(locked.alternatives).toEqual([]);
expect(locked.diagnostics[0]!.message).toContain("LOCK");
const input = await request("edit");
input.instruction = "make buttons rounded and invent a new calendar";
const failed = await generateDesigns(input);
expect(failed.alternatives).toEqual([]);
expect(input.system.tokens["shape.control"]!.value).toBe("0");
input.instruction = "set primary color to #aabbcc and remove shadows and use serif typography";
expect((await generateDesigns(input)).diagnostics).toEqual([]);
});
test("stale generation base and stale combinations fail without applying", async () => {
const input = await request();
input.baseRevision = "a".repeat(64);
expect(generateDesigns(input)).rejects.toThrow("WRN-STUDIO-CONFLICT");
const good = await request();
const batch = await generateDesigns(good);
const next = structuredClone(good.system);
next.name = "Changed";
await expect(combineDesignProposals(next, batch, { colors: "direction-2" }, [])).rejects.toThrow(
"CONFLICT",
);
});
test("foundation decisions combine and revalidate before entering a draft", async () => {
const input = await request();
const batch = await generateDesigns(input);
const next = await combineDesignProposals(
input.system,
batch,
{ geometry: "direction-2", typography: "direction-3" },
[],
);
expect(validateDesignSystem(next)).toEqual([]);
expect(next.tokens["shape.control"]).toEqual(
batch.alternatives[1]!.system.tokens["shape.control"],
);
expect(next.tokens["type.body"]).toEqual(batch.alternatives[2]!.system.tokens["type.body"]);
});
test("provider adapter uses bounded patches, compact capabilities and one repair", async () => {
let calls = 0;
let context = "";
const provider: GenerationProvider = {
name: "fixture",
async generate(prompt, options) {
calls++;
context = prompt;
expect(options!.maxTokens).toBe(512);
expect(options!.signal).toBeDefined();
return {
provider: "fixture",
model: "test-model",
value: calls === 1 ? "not json" : patched([set("/tokens/shape.control/value", "11px")]),
};
},
};
const result = await generateDesigns(await request("edit"), { provider, maxOutputTokens: 512 });
expect(calls).toBe(2);
expect(result.diagnostics).toEqual([]);
expect(result.provenance.model).toBe("test-model");
expect(result.provenance.attempts).toBe(2);
expect(context).toContain("capabilities");
expect(context).toContain("Repair these diagnostics");
});
test("empty drafts require flat typed tokens and reject nested color groups before repair", async () => {
const system = {
schemaVersion: 1 as const,
id: "empty",
name: "Empty",
version: "1.0.0",
tokens: {},
};
let calls = 0;
const batch = await generateDesigns(await request("directions", [], system), {
provider: {
name: "fixture",
async generate(_prompt, options) {
calls++;
expect(options!.system).toContain("Token maps are FLAT");
expect(options!.system).toContain("COMPLETE token object");
return {
provider: "fixture",
value: JSON.stringify({
alternatives: ["#002FA7", "#00642C", "#A32957"].map((color, index) => ({
name: `Direction ${index}`,
rationale: "A distinct brand color.",
patch: [
calls === 1
? set("/tokens/color", { primary: { type: "color", value: color } })
: set("/tokens/color.primary", {
type: "color",
value: color,
legacy: "color-primary",
}),
],
})),
}),
};
},
},
});
expect(calls).toBe(2);
expect(batch.alternatives).toHaveLength(3);
expect(batch.diagnostics).toEqual([]);
for (const alternative of batch.alternatives)
expect(validateDesignSystem(alternative.system)).toEqual([]);
expect(system.tokens).toEqual({});
});
test("provider invalid parts and lock violations cannot survive bounded repair", async () => {
let calls = 0;
const provider: GenerationProvider = {
name: "fixture",
async generate() {
calls++;
return { provider: "fixture", value: patched([set("/tokens/shape.control/value", "12px")]) };
},
};
const result = await generateDesigns(await request("edit", ["/tokens/shape.control"]), {
provider,
});
expect(calls).toBe(2);
expect(result.alternatives).toEqual([]);
expect(result.diagnostics[0]!.code).toBe("WRN-STUDIO-PROPOSAL");
const invalid: GenerationProvider = {
name: "fixture",
async generate() {
return {
provider: "fixture",
value: patched([set("/recipes/Button/base/control/position", "fixed")]),
};
},
};
expect(
(await generateDesigns(await request("edit"), { provider: invalid })).alternatives,
).toEqual([]);
});
test("prototype paths, excessive operations and execution strings are rejected", async () => {
for (const patch of [
[set("/tokens/__proto__/value", "x")],
Array.from({ length: 49 }, () => set("/name", "x")),
[set("/tokens/shape.control/value", "0;}body{display:none")],
]) {
const provider: GenerationProvider = {
name: "fixture",
async generate() {
return { provider: "fixture", value: patched(patch) };
},
};
expect((await generateDesigns(await request("edit"), { provider })).alternatives).toEqual([]);
}
});
test("cancellation and deadline stop even a provider that ignores its signal", async () => {
const provider: GenerationProvider = { name: "hung", generate: () => new Promise(() => {}) };
const controller = new AbortController();
const input = await request("edit");
const pending = generateDesigns(input, { provider, signal: controller.signal });
controller.abort();
await expect(pending).rejects.toThrow();
await expect(generateDesigns(input, { provider, timeoutMs: 15 })).rejects.toThrow("cancelled");
expect(input.system.tokens["shape.control"]!.value).toBe("0");
});
test("transport failure is not retried or returned with secret-bearing messages", async () => {
let count = 0;
const provider: GenerationProvider = {
name: "fixture",
async generate() {
count++;
throw Error("secret-key-in-provider-error");
},
};
const result = await generateDesigns(await request("edit"), { provider });
expect(count).toBe(1);
expect(JSON.stringify(result)).not.toContain("secret-key-in-provider-error");
expect(result.diagnostics[0]!.code).toBe("WRN-STUDIO-PROVIDER");
});
test("untrusted brief fields and generation budgets are bounded", async () => {
const input = await request();
input.brief.product = "x".repeat(2001);
await expect(generateDesigns(input)).rejects.toThrow("2000");
await expect(generateDesigns(await request(), { maxOutputTokens: 9000 })).rejects.toThrow(
"BUDGET",
);
const altered = await request();
altered.brief.brandColor = "red;}body{}";
await expect(generateDesigns(altered)).rejects.toThrow("BRIEF");
});