feat: release Colour Tools 0.1.0

This commit is contained in:
2026-08-31 21:21:35 +02:00
commit 0c1fc94b58
100 changed files with 17406 additions and 0 deletions
+111
View File
@@ -0,0 +1,111 @@
import { describe, expect, it } from "vitest";
import {
colourHarmony,
deltaE,
exportPalette,
formatColour,
normaliseTokenName,
parsePaletteList,
shades,
simulateColourVision,
simulateColourVisionSet,
tints,
tones,
} from "../../src/colour";
describe("comparison and colour-vision tools", () => {
it("provides standard Delta-E methods", () => {
expect(deltaE("#f8f5ee", "#f8f5ee", "2000")).toBe(0);
expect(deltaE("red", "blue", "76")).toBeGreaterThan(100);
expect(deltaE("#777", "#787878", "ok")).toBeGreaterThan(0);
});
it("simulates four deficiencies while preserving alpha", () => {
const original = "rgba(200, 40, 20, .4)";
const unchanged = simulateColourVision(original, "deuteranopia", {
severity: 0,
});
expect(formatColour(unchanged, "hex8")).toBe("#c8281466");
const simulated = simulateColourVision(original, "deuteranopia");
expect(simulated.alpha).toBeCloseTo(0.4);
expect(formatColour(simulated, "hex")).not.toBe("#c82814");
expect(Object.keys(simulateColourVisionSet(original))).toEqual([
"protanopia",
"deuteranopia",
"tritanopia",
"achromatopsia",
]);
});
it("turns achromatopsia output neutral", () => {
const simulated = simulateColourVision("#ff8000", "achromatopsia");
expect(simulated.coords[0]).toBeCloseTo(simulated.coords[1]!, 8);
expect(simulated.coords[1]).toBeCloseTo(simulated.coords[2]!, 8);
});
});
describe("palette construction and export", () => {
it("builds useful hue harmonies", () => {
expect(colourHarmony("oklch(60% .15 20)", "complementary")).toHaveLength(2);
expect(colourHarmony("oklch(60% .15 20)", "analogous")).toHaveLength(3);
expect(colourHarmony("oklch(60% .15 20)", "square")).toHaveLength(4);
});
it("builds deterministic tint, shade and tone scales", () => {
expect(tints("#336699", 3)).toHaveLength(3);
expect(shades("#336699", 3, { includeBase: true })).toHaveLength(4);
expect(tones("#336699", 2, { includeEndpoint: true })).toHaveLength(2);
expect(
formatColour(tints("black", 1, { includeEndpoint: true })[0]!, "hex"),
).toBe("#ffffff");
});
it("parses named text, CSS variables, arrays and token JSON", () => {
const text = parsePaletteList("Primary: #f8f5ee\n--accent: rgb(20 40 60)");
expect(text.map((entry) => entry.name)).toEqual(["Primary", "accent"]);
expect(parsePaletteList('["red", "blue"]')).toHaveLength(2);
const tokens = parsePaletteList(
'{"brand":{"$type":"color","$value":"#123456"}}',
);
expect(formatColour(tokens[0]!.colour, "hex")).toBe("#123456");
const nested = parsePaletteList(
'{"brand":{"light":{"$type":"color","$value":"#eee"},"dark":{"$value":"#111"}}}',
);
expect(nested.map((entry) => entry.name)).toEqual([
"brand-light",
"brand-dark",
]);
expect(
parsePaletteList(":root { --paper: #f8f5ee; --ink: #111; }"),
).toHaveLength(2);
});
it("normalises and de-duplicates portable token names", () => {
expect(normaliseTokenName(" Crème brûlée / 100 ")).toBe(
"creme-brulee-100",
);
const entries = parsePaletteList("Brand: red\nBrand: blue");
expect(exportPalette(entries, "css", { prefix: "App" })).toContain(
"--app-brand-2: #0000ff;",
);
});
it("exports CSS, Sass, JSON, DTCG, Tailwind and CSV", () => {
const entries = parsePaletteList("paper: #f8f5ee\nink: #111");
expect(exportPalette(entries, "css")).toContain("--colour-paper: #f8f5ee;");
expect(exportPalette(entries, "scss")).toContain("$colour-ink: #111111;");
expect(JSON.parse(exportPalette(entries, "json"))).toEqual({
paper: "#f8f5ee",
ink: "#111111",
});
expect(JSON.parse(exportPalette(entries, "tokens")).paper).toEqual({
$type: "color",
$value: "#f8f5ee",
});
expect(exportPalette(entries, "tailwind")).toMatch(/^export default /);
expect(exportPalette(entries, "csv")).toBe(
"name,value\npaper,#f8f5ee\nink,#111111",
);
});
});