import { describe, expect, it } from "vitest"; import { deleteToken, exportTokens, parseTokenDocument, resolveTheme, updateToken, } from "../../src/core/tokens"; const themed = JSON.stringify({ $sets: { core: { color: { $type: "color", brand: { $value: "#112233" }, link: { $value: "{color.brand}" }, }, space: { sm: { $type: "dimension", $value: "8px" } }, }, dark: { color: { $type: "color", brand: { $value: "#ddeeff" } } }, }, $themes: { light: ["core"], dark: ["core", "dark"] }, }); describe("token model", () => { it("composes sets and resolves aliases after overrides", () => { const result = resolveTheme(parseTokenDocument(themed), "dark"); expect( result.tokens.find((token) => token.path === "color.link")?.resolvedValue, ).toBe("#ddeeff"); expect(result.diagnostics).toEqual([]); }); it("detects cycles and missing aliases", () => { const document = parseTokenDocument( '{"a":{"$value":"{b}"},"b":{"$value":"{a}"},"c":{"$value":"{missing}"}}', ); const messages = resolveTheme(document, "default") .diagnostics.map((item) => item.message) .join(" "); expect(messages).toMatch(/cycle/u); expect(messages).toMatch(/does not exist/u); }); it("rejects malformed wrappers and non-standard hex lengths", () => { expect(() => parseTokenDocument('{"$sets":[]}')).toThrow( /\$sets must be an object/u, ); const document = parseTokenDocument( '{"five":{"$type":"color","$value":"#12345"},"seven":{"$type":"color","$value":"#1234567"}}', ); expect(resolveTheme(document, "default").diagnostics).toHaveLength(2); expect(() => parseTokenDocument('{"$themes":{}}')).toThrow( /requires a \$sets/u, ); expect(() => parseTokenDocument( '{"leaf":{"$value":"x","hidden":{"$value":"ignored"}}}', ), ).toThrow(/cannot also contain child/u); }); it("exports CSS and reports platform loss", () => { const tokens = resolveTheme(parseTokenDocument(themed), "light").tokens; expect(exportTokens("css", tokens).content).toContain( "--color-brand: #112233;", ); const composite = parseTokenDocument( '{"text":{"$type":"typography","$value":{"fontFamily":"Inter","fontSize":"16px"}}}', ); expect( exportTokens("android", resolveTheme(composite, "default").tokens) .losses[0], ).toMatch(/project-specific/u); }); it("quotes generated strings and typography and reports name collisions", () => { const document = parseTokenDocument( JSON.stringify({ danger: { $type: "string", $value: 'x"; } --owned: red; /*' }, text: { $type: "typography", $value: { fontFamily: 'Evil"; } body { color: red; /*', fontSize: "16px", }, }, a: { b: { $type: "string", $value: "first" } }, "a-b": { $type: "string", $value: "second" }, }), ); const result = exportTokens( "css", resolveTheme(document, "default").tokens, ); expect(result.content).toContain('--danger: "x\\"; } --owned: red; /*";'); expect(result.content).toContain('"Evil\\"; } body { color: red; /*"'); expect(result.losses.join(" ")).toMatch(/identifier a-b collides/u); }); it("edits and deletes nested tokens", () => { const document = parseTokenDocument(themed); const updated = updateToken( document, "core", "space.sm", "space.md", "dimension", "16px", ); expect( parseTokenDocument(updated).sets.core?.some( (token) => token.path === "space.md", ), ).toBe(true); const removed = deleteToken( parseTokenDocument(updated), "core", "space.md", ); expect( parseTokenDocument(removed).sets.core?.some( (token) => token.path === "space.md", ), ).toBe(false); }); it("refuses to create a path through an existing token leaf", () => { const document = parseTokenDocument( '{"base":{"$type":"string","$value":"leaf"}}', ); expect(() => updateToken(document, "default", null, "base.child", "string", "x"), ).toThrow(/crosses existing token base/u); }); });