Files
gacha-event-tracker/test/appearance.test.ts
T
Lucas WintherandClaude Opus 5.5 50c6e91b00 feat: the redesigned Event Clock
The new reader app replaces the old UI: Checklist with a rail (focus
pills, Next to expire, today's dailies, pinned routines), Timeline in a
new and a classic look, Routines, Archive and a Settings area, in a
Structured (default) and a Relaxed interface. It follows the approved
Round 6 B and Round 7 mockups, corrected screen by screen with the owner.

Reader data moves to a v2 store (gacha-tracker:v2:*). Existing v1 data
is migrated on first load, a recovery copy is written first, and the v1
keys are never removed, so a rollback finds them unchanged. Redeem codes
for Genshin arrive as their own feed.

Also: live's urgency ramp, editable per theme; completion and
section-complete fireworks; a returning-user tour; the design mockups,
specs, plans, reviews and handoff record.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-18 01:55:16 +02:00

84 lines
2.8 KiB
TypeScript

import { expect, test } from "bun:test";
import {
appearanceTokens,
readableOnSurfaces,
contrastRatio,
} from "../src/client/state/appearance.ts";
test("custom colours retain contrast on both page and surface", () => {
const t = appearanceTokens(
{
scheme: "custom",
customPalette: {
dark: {
ground: "#111111",
surface: "#222222",
text: "#111111",
accent: "#222222",
},
},
},
"dark",
);
expect(contrastRatio(t.ink, t.ground)).toBeGreaterThanOrEqual(4.5);
expect(contrastRatio(t.ink, t.surface)).toBeGreaterThanOrEqual(4.5);
const hue = readableOnSurfaces("#8bcfff", [t.ground, t.surface], 4.5);
expect(contrastRatio(hue, t.surface)).toBeGreaterThanOrEqual(4.5);
});
test("palette and theme are independent from interface and density", () => {
expect(
appearanceTokens(
{ scheme: "warm", interface: "structured", density: "compact" },
"light",
),
).toEqual(
appearanceTokens(
{ scheme: "warm", interface: "relaxed", density: "standard" },
"light",
),
);
});
test("Structured is the default while a saved Relaxed choice remains distinct", () => {
expect(appearanceTokens({}, "dark").ground).toBe("#0e1117");
expect(appearanceTokens({ interface: "relaxed" }, "dark").ground).toBe(
"#1a1c24",
);
});
import { urgencyTokens } from "../src/client/state/appearance.ts";
test("urgency follows the live ramp, grey to blue to yellow to red, whatever the accent", () => {
const dark = urgencyTokens(appearanceTokens({ accent: "#ff00ff" }, "dark"));
expect(dark.near).toBe("#5b8def");
expect(dark.soon).toBe("#e8b33c");
expect(dark.critical).toBe("#ff4d6a");
// Live's grey, nudged only as far as large-text contrast needs.
expect(dark.calm).not.toBe(dark.near);
const light = urgencyTokens(appearanceTokens({}, "light"));
expect(light.near).toBe("#2b62c6");
expect(light.soon).toBe("#8a6008");
expect(light.critical).toBe("#c81e3c");
expect(light.calm).not.toBe(light.near);
});
import { urgencyChoice } from "../src/client/state/appearance.ts";
test("the reader's urgency colours apply per theme; unset or invalid steps keep live's", () => {
const prefs = {
urgencyPalette: {
dark: { critical: "#a020f0", soon: "not a colour" },
light: { near: "#123456" },
},
};
const dark = urgencyChoice(prefs, "dark");
expect(dark.critical).toBe("#a020f0");
expect(dark.soon).toBe("#e8b33c");
expect(dark.near).toBe("#5b8def");
expect(urgencyChoice(prefs, "light").near).toBe("#123456");
expect(urgencyChoice({ urgencyPalette: "junk" }, "dark").critical).toBe(
"#ff4d6a",
);
// Applied, a chosen colour is kept when it already reads on the surfaces.
expect(
urgencyTokens(appearanceTokens(prefs, "dark"), prefs, "dark").critical,
).toBe("#a020f0");
});