Release Token Tools 0.1.0

This commit is contained in:
2026-09-01 13:04:50 +02:00
commit 8fd5210cb8
57 changed files with 10265 additions and 0 deletions
+136
View File
@@ -0,0 +1,136 @@
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);
});
});