// 设计令牌单一来源守门:web/style.css 的 :root 与 src/theme.ts、web/canvas-painter.ts 必须一致。 // 改任一处而不同步另一处,这里会红。规格文档见仓库 docs/design.md。 import assert from 'node:assert/strict'; import { readFileSync } from 'node:fs'; import { dirname, resolve } from 'node:path'; import { test } from 'node:test'; import { fileURLToPath } from 'node:url'; import { MOTION, THEME } from '../src/theme'; import { BOARD_FONT_FAMILY } from '../web/canvas-painter'; const here = dirname(fileURLToPath(import.meta.url)); const css = readFileSync(resolve(here, '../web/style.css'), 'utf8'); function rootTokens(source: string): Map { const m = /:root\s*\{([\s\S]*?)\n\}/.exec(source); assert.ok(m, 'style.css 缺少 :root 块'); const out = new Map(); for (const line of m[1].split('\n')) { const t = /^\s*(--[\w-]+)\s*:\s*(.+?);\s*$/.exec(line); if (t) out.set(t[1], t[2]); } return out; } const tokens = rootTokens(css); const hex = (s: string | undefined): string => (s ?? '').trim().toUpperCase(); test('颜色令牌:style.css :root 与 THEME 一致', () => { const pairs: Array<[string, string]> = [ ['--bg', THEME.bg], ['--cell', THEME.cell], ['--ink', THEME.clueInk], ['--clue-ring', THEME.clueRing], ['--primary', THEME.visited], ['--primary-bright', THEME.visitedBright], ['--on-primary', THEME.visitedInk], ['--danger', THEME.conflict], ['--hint', THEME.hint], ]; for (const [name, value] of pairs) { assert.equal(hex(tokens.get(name)), hex(value), `${name} 与 theme.ts 不一致`); } }); test('动效时长令牌:--t-* 与 MOTION 一致', () => { const pairs: Array<[string, number]> = [ ['--t-fast', MOTION.fast], ['--t-base', MOTION.base], ['--t-slow', MOTION.slow], ['--t-hold', MOTION.hold], ]; for (const [name, seconds] of pairs) { assert.equal(tokens.get(name), `${Math.round(seconds * 1000)}ms`, `${name} 与 theme.ts MOTION 不一致`); } assert.equal(MOTION.numberFade, MOTION.fast, '步号淡入应等于 fast 档'); assert.equal(MOTION.bridgeGrow, MOTION.fast, '桥生长应等于 fast 档'); assert.equal(MOTION.flashTime, MOTION.slow, '闪白应等于 slow 档'); assert.equal(MOTION.clearedSweep, MOTION.slow, '过关扫光应等于 slow 档'); assert.equal(MOTION.clearedHold, MOTION.hold, '过关停留应等于 hold 档'); assert.equal(MOTION.boardEnter, MOTION.base, '进场应等于 base 档'); }); test('字体栈:canvas 与 CSS --font 完全一致', () => { assert.equal(tokens.get('--font'), BOARD_FONT_FAMILY); });