import { test } from "node:test"; import assert from "node:assert/strict"; import { accentHex, accentIdOf, customAccentVars, parseAccent, parseAccentSetting, resolveAccent, } from "./accent"; import { ACCENTS, ACCENT_IDS, ACCENT_INK, BASE_GROUNDS, MIN_ACCENT_CONTRAST, contrastRatio, type BaseGround, } from "./brand"; const BASES = Object.keys(BASE_GROUNDS) as BaseGround[]; test("parseAccent stays a hex-only parser", () => { assert.equal(parseAccent("#ABCDEF"), "#abcdef"); assert.equal(parseAccent(" abcdef "), "#abcdef"); assert.equal(parseAccent("brass"), undefined); assert.equal(parseAccent("#abc"), undefined); assert.equal(parseAccent(42), undefined); }); test("parseAccentSetting: an id, a #rrggbb, or undefined", () => { for (const id of ACCENT_IDS) assert.equal(parseAccentSetting(id), id); assert.equal(parseAccentSetting(" Brass "), "brass"); assert.equal(parseAccentSetting("#CC3366"), "#cc3366"); assert.equal(parseAccentSetting("cc3366"), "#cc3366"); // A hex equal to an accent's value stays a custom hex: never mapped to an id. assert.equal(parseAccentSetting(ACCENTS.brass.onDark), ACCENTS.brass.onDark); for (const bad of ["", " ", "custom", "gold", "#12345", "#1234567", null, undefined, 7, {}, ["brass"]]) { assert.equal(parseAccentSetting(bad), undefined, JSON.stringify(bad)); } }); test("resolveAccent: a named accent reads its table row", () => { for (const id of ACCENT_IDS) { assert.deepEqual(resolveAccent(id), { id, light: ACCENTS[id].onLight, dark: ACCENTS[id].onDark, }); } }); test("resolveAccent: absent or malformed is the default accent (Signal)", () => { const signal = resolveAccent("signal"); for (const v of [undefined, null, "", "gold", "#xyz", 5]) { assert.deepEqual(resolveAccent(v), signal, JSON.stringify(v)); } }); // Custom hexes chosen to need every kind of fit: too light for light, // too dark for dark, already fine, and the extremes. const CUSTOMS = [ "#cc3366", "#ffff00", "#00ffff", "#ffffff", "#000000", "#808080", "#1e90ff", "#95661a", "#e3b15c", "#3a0ca3", "#ff6600", "#7fff00", "#f0f0f0", "#101010", ]; test("resolveAccent: a custom hex is fitted to 4.5:1 on every ground and with its ink", () => { // A literal bar, so an edit to MIN_ACCENT_CONTRAST cannot move fit and test together. assert.equal(MIN_ACCENT_CONTRAST, 4.5); for (const hex of CUSTOMS) { const r = resolveAccent(hex); assert.equal(r.id, "custom"); for (const base of BASES) { const v = r[base]; assert.match(v, /^#[0-9a-f]{6}$/); const ground = contrastRatio(v, BASE_GROUNDS[base]); const ink = contrastRatio(v, ACCENT_INK[base]); assert.ok(ground >= 4.5, `${hex} on ${base} → ${v}: ${ground.toFixed(2)} vs ground`); assert.ok(ink >= 4.5, `${hex} on ${base} → ${v}: ${ink.toFixed(2)} vs ink`); } } }); test("resolveAccent: a custom hex that already passes is kept exactly; others move the right way", () => { // #cc3366 is 4.57:1 on light (kept) and 3.98 on dark (lightened). The // themes slice's site-branding spec expects #cc3366 on light. const r = resolveAccent("#CC3366"); assert.equal(r.light, "#cc3366"); assert.notEqual(r.dark, "#cc3366"); const lum = (h: string) => contrastRatio(h, "#000000"); assert.ok(lum(r.dark) > lum("#cc3366"), "dark is lighter"); // The one that is too light for light is darkened there. assert.ok(lum(resolveAccent("#ffff00").light) < lum("#ffff00"), "light is darker"); // A dark-enough colour is kept on light, a light-enough on dark. assert.equal(resolveAccent("#101010").light, "#101010"); assert.equal(resolveAccent("#f0f0f0").dark, "#f0f0f0"); }); test("accentHex: the published value is always a hex", () => { for (const id of ACCENT_IDS) assert.equal(accentHex(id), ACCENTS[id].onDark); assert.equal(accentHex("Violet"), ACCENTS.violet.onDark); // A custom hex is published as stored (normalized), not fitted. assert.equal(accentHex("#CC3366"), "#cc3366"); assert.equal(accentHex(undefined), undefined); assert.equal(accentHex(""), undefined); assert.equal(accentHex("gold"), undefined); }); test("accentIdOf: the named accent a setting picks, never a hex's", () => { for (const id of ACCENT_IDS) assert.equal(accentIdOf(id), id); assert.equal(accentIdOf(" Violet "), "violet"); // A custom hex has no id, even one equal to a named accent's value. assert.equal(accentIdOf(ACCENTS.brass.onDark), undefined); assert.equal(accentIdOf("#cc3366"), undefined); assert.equal(accentIdOf(undefined), undefined); assert.equal(accentIdOf("gold"), undefined); }); test("customAccentVars: only a custom hex carries inline vars", () => { assert.equal(customAccentVars(undefined), null); for (const id of ACCENT_IDS) assert.equal(customAccentVars(id), null); const r = resolveAccent("#cc3366"); assert.deepEqual(customAccentVars("#cc3366"), { "--accent-custom-light": r.light, "--accent-custom-dark": r.dark, }); });