import { describe, expect, it } from "vitest"; import { colorDistance, parseOpaqueColor } from "./color-math.js"; describe("parseOpaqueColor", () => { it("parses hex in 3, 6, and opaque 4/8 digit forms", () => { expect(parseOpaqueColor("#25f")).toEqual({ r: 0x22, g: 0x55, b: 0xff }); expect(parseOpaqueColor("#2563eb")).toEqual({ r: 0x25, g: 0x63, b: 0xeb }); expect(parseOpaqueColor("#2563ebff")).toEqual({ r: 0x25, g: 0x63, b: 0xeb }); expect(parseOpaqueColor("#25ff")).toEqual({ r: 0x22, g: 0x55, b: 0xff }); }); it("rejects translucent values in every notation", () => { expect(parseOpaqueColor("#2563eb80")).toBeNull(); expect(parseOpaqueColor("rgba(37, 99, 235, 0.5)")).toBeNull(); expect(parseOpaqueColor("rgb(37 99 235 / 0.5)")).toBeNull(); expect(parseOpaqueColor("hsl(217 91% 60% / 50%)")).toBeNull(); expect(parseOpaqueColor("oklch(0.55 0.24 263 / 0.8)")).toBeNull(); }); it("parses rgb()/rgba() in comma and space syntax, with percentages", () => { expect(parseOpaqueColor("rgb(37, 99, 235)")).toEqual({ r: 37, g: 99, b: 235 }); expect(parseOpaqueColor("rgba(37, 99, 235, 1)")).toEqual({ r: 37, g: 99, b: 235 }); expect(parseOpaqueColor("rgb(37 99 235)")).toEqual({ r: 37, g: 99, b: 235 }); expect(parseOpaqueColor("rgb(37 99 235 / 100%)")).toEqual({ r: 37, g: 99, b: 235 }); expect(parseOpaqueColor("rgb(100% 0% 50%)")).toEqual({ r: 255, g: 0, b: 128 }); }); it("parses hsl()/hsla() to the same color as its hex equivalent", () => { // Tailwind blue-500: #3b82f6 ≈ hsl(217, 91%, 60%) const fromHsl = parseOpaqueColor("hsl(217, 91%, 60%)"); const fromHex = parseOpaqueColor("#3b82f6"); expect(fromHsl).not.toBeNull(); expect(colorDistance(fromHsl!, fromHex!)).toBeLessThan(10); expect(parseOpaqueColor("hsla(217, 91%, 60%, 1)")).toEqual(fromHsl); expect(parseOpaqueColor("hsl(217deg 91% 60%)")).toEqual(fromHsl); }); it("parses oklch() to the same color as its hex equivalent", () => { // #2563eb = oklch(0.54615 0.215208 262.881) (exact sRGB→OKLCH conversion) const fromOklch = parseOpaqueColor("oklch(0.54615 0.215208 262.881)"); const fromHex = parseOpaqueColor("#2563eb"); expect(fromOklch).not.toBeNull(); expect(colorDistance(fromOklch!, fromHex!)).toBeLessThan(5); // Percentage lightness form. expect(parseOpaqueColor("oklch(54.615% 0.215208 262.881)")).toEqual(fromOklch); // Published reference: sRGB red is oklch(0.62796 0.25768 29.234). const red = parseOpaqueColor("oklch(0.62796 0.25768 29.234)"); expect(colorDistance(red!, parseOpaqueColor("#ff0000")!)).toBeLessThan(5); }); it("returns null for unsupported notations", () => { expect(parseOpaqueColor("rebeccapurple")).toBeNull(); expect(parseOpaqueColor("lab(52% 40 59)")).toBeNull(); expect(parseOpaqueColor("color(display-p3 1 0 0)")).toBeNull(); expect(parseOpaqueColor("var(--fui-color-accent)")).toBeNull(); }); }); describe("colorDistance", () => { it("is zero for identical colors and small for same-family shades", () => { const accent = parseOpaqueColor("#7c3aed")!; expect(colorDistance(accent, accent)).toBe(0); expect(colorDistance(accent, parseOpaqueColor("#7c3acc")!)).toBeLessThan(60); }); it("is large across hue families", () => { const blue = parseOpaqueColor("#2563eb")!; const red = parseOpaqueColor("#dc2626")!; expect(colorDistance(blue, red)).toBeGreaterThan(200); }); });