Files
ApiCore/ApiCore-Frontend/src/styles/css-tokens.spec.ts
T
Nick.RosenloecherandClaude Opus 5.5 d8238855fc feat(t5): neue Farbwerte, Dunkel für alle, Farb-Codemod (T5a A4)
- globals.css: Hell, Dunkel und Druck mit den Spec-Werten aus 4.2,
  Fokusring = --primary-ink, Verlaufs-Token entfernt.
- Codemod scripts/codemod-t5-colors.mjs: text-primary/-warning auf -ink,
  Vordergrund nur auf voller Fläche, Auswahlrahmen dunkel; --check-Modus
  und Spec halten src sauber.
- Kontrast-Wächter misst die echte globals.css (VALUES_SWITCHED), alle 21
  Zeilen aus 4.4 in allen vier Sätzen; keine dark:-Klassen mehr in src.
- Dunkel ist ohne Entwickler-Schalter wählbar, Standard bleibt Hell.
- Review-Nacharbeit: Auswahl-Umrisse und aktueller Schritt in Vordergrund,
  deaktivierte Felder bg-muted, Avatar ohne Blend, Honig-Check-Rahmen
  über --input.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-16 17:23:21 +02:00

70 lines
2.8 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { alphaOf, atRuleDeclarations, blend, contrastOf, hexOf, inGamut, oklchOf, tokenSet } from "./css-tokens";
const CSS = `
@theme inline { --color-x: var(--x); }
:root { --x: oklch(0.5 0.1 80); --y: oklch(1 0 0 / 12%); --len: 0.75rem;
@variant sm { --nested: oklch(0 0 0); } }
.dark { --x: oklch(0.2 0.1 80); --y: oklch(0 0 0); }
:root[data-contrast="sun"] { --x: oklch(0.9 0 0); }
.dark[data-contrast="sun"] { --x: oklch(0.95 0 0); }
`;
describe("css-tokens", () => {
it("reads oklch values with and without alpha", () => {
expect(oklchOf("oklch(0.5 0.1 80)")).toEqual([0.5, 0.1, 80]);
expect(oklchOf("oklch(1 0 0 / 12%)")).toEqual([1, 0, 0]);
expect(alphaOf("oklch(1 0 0 / 12%)")).toBeCloseTo(0.12);
expect(alphaOf("oklch(1 0 0)")).toBe(1);
expect(() => oklchOf("1rem")).toThrow();
});
it("converts oklch to an sRGB hex code", () => {
expect(hexOf([1, 0, 0])).toBe("#ffffff");
expect(hexOf([0, 0, 0])).toBe("#000000");
expect(hexOf(oklchOf("oklch(0.968 0.014 85)"))).toBe("#f9f4ea");
});
it("contrast of black on white is 21", () => {
expect(contrastOf([0, 0, 0], [1, 0, 0])).toBeCloseTo(21, 1);
expect(contrastOf([1, 0, 0], [1, 0, 0])).toBeCloseTo(1, 5);
});
it("blends a 15 % tint over its background before measuring", () => {
const white: [number, number, number] = [1, 0, 0];
const black: [number, number, number] = [0, 0, 0];
const mixed = blend(black, 0.15, white);
expect(mixed[0]).toBeGreaterThan(0.8);
expect(mixed[0]).toBeLessThan(1);
expect(contrastOf(mixed, white)).toBeLessThan(contrastOf(black, white));
expect(blend(black, 1, white)[0]).toBeCloseTo(0, 5);
});
it("sun-light layers over light and sun-dark over dark", () => {
expect(tokenSet(CSS, "light").x).toBe("oklch(0.5 0.1 80)");
expect(tokenSet(CSS, "dark").x).toBe("oklch(0.2 0.1 80)");
expect(tokenSet(CSS, "sun-light").x).toBe("oklch(0.9 0 0)");
expect(tokenSet(CSS, "sun-light").len).toBe("0.75rem");
expect(tokenSet(CSS, "sun-dark").x).toBe("oklch(0.95 0 0)");
expect(tokenSet(CSS, "sun-dark").y).toBe("oklch(0 0 0)");
});
it("returns only the own declarations of a sun set on request", () => {
expect(tokenSet(CSS, "sun-light", { own: true })).toEqual({ x: "oklch(0.9 0 0)" });
});
it("ignores declarations nested in at-rules", () => {
expect(tokenSet(CSS, "light").nested).toBeUndefined();
});
it("reads the declarations inside an at-rule", () => {
const css = "@media print { :root, .dark { --a: 1; } .x { --b: 2; } } :root { --c: 3; }";
expect(atRuleDeclarations(css, "@media print")).toEqual({ a: "1", b: "2" });
});
it("detects colours outside the sRGB gamut", () => {
expect(inGamut([0.6, 0.1, 50])).toBe(true);
expect(inGamut([0.6, 0.4, 150])).toBe(false);
});
});