Why: Phase 1 of the approved frontend redesign: re-skin the shell to the Zai look (floating rounded panels on an inset base with 4px gutters), invert primary buttons to the mono scheme, and move every font size onto the --fs-ui-* scale. Also wires @ui/motion.css and replaces local keyframes with mk-* utilities. Evidence: npx tsc -b clean; npm test -> 38 passed (new host-side design-guard ratchet); npm run build succeeds. Combined Dev make test recorded in the Dev task card. Impact: styles.css fully re-tokenized (panel borders/radius, primary button color-mix hover, login brand panel in near-mono navy); inline fontSize literals in components replaced with scale tokens except one decorative glyph in the ratchet baseline. No component class names or behavior changed. Refs: Agent-System-Dev task feature/ui-tokens-v3; DESIGN.md (parent repo).
81 lines
3.7 KiB
TypeScript
81 lines
3.7 KiB
TypeScript
// 设计系统守护测试(父仓 DESIGN.md §11 禁止清单 · 宿主侧,2026-09-30)。
|
|
// 棘轮基线:现存豁免登记在下方;新增同类违规直接失败。收紧 = 修掉违规后删基线条目。
|
|
// 范围:src/styles.css 与 src/**/*.tsx。@ui 组件库侧由 Agent-System-UI 的守护测试覆盖。
|
|
import { describe, it, expect } from 'vitest'
|
|
import { readFileSync, readdirSync, statSync, existsSync } from 'node:fs'
|
|
import { join, relative, dirname } from 'node:path'
|
|
import { fileURLToPath } from 'node:url'
|
|
|
|
const HERE = dirname(fileURLToPath(import.meta.url)) // test/
|
|
const FE_ROOT = join(HERE, '..')
|
|
const SRC = join(FE_ROOT, 'src')
|
|
const DEV_ROOT = join(FE_ROOT, '..', '..') // Dev 父仓根(DESIGN.md;单独检出本仓时不存在)
|
|
|
|
function walk(dir: string, out: string[] = []): string[] {
|
|
for (const name of readdirSync(dir)) {
|
|
const p = join(dir, name)
|
|
if (statSync(p).isDirectory()) walk(p, out)
|
|
else out.push(p)
|
|
}
|
|
return out
|
|
}
|
|
const read = (p: string) => readFileSync(p, 'utf8')
|
|
const stripCssComments = (t: string) => t.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
const collectHex = (t: string) => [...t.matchAll(/#[0-9a-fA-F]{3,8}\b/g)].map(m => m[0].toLowerCase())
|
|
|
|
// ---- 1. 宪法在位(组合检出时) ----
|
|
describe('DESIGN.md 宪法在位', () => {
|
|
it('DESIGN.md 存在且 AGENTS.md 带强制阅读门禁', () => {
|
|
const designPath = join(DEV_ROOT, 'DESIGN.md')
|
|
if (!existsSync(designPath)) { console.warn('(单独检出 Frontend 仓:跳过宪法检查)'); return }
|
|
expect(read(designPath)).toContain('Agent-System-Dev 设计系统')
|
|
const agents = read(join(DEV_ROOT, 'AGENTS.md'))
|
|
expect(agents).toContain('DESIGN.md')
|
|
})
|
|
})
|
|
|
|
// ---- 2. 动效唯一所有者 ----
|
|
describe('动效唯一所有者', () => {
|
|
it('styles.css 无 @keyframes(全部走 @ui/motion.css 的 mk-* 类)', () => {
|
|
expect(stripCssComments(read(join(SRC, 'styles.css')))).not.toContain('@keyframes')
|
|
})
|
|
})
|
|
|
|
// ---- 3. 裸色值棘轮 ----
|
|
const HEX_BASELINE_STYLES = [
|
|
'#0e1116', '#101826', '#0b1f38', // 登录品牌面板固定深底渐变(面板自带深底,主题无关)
|
|
'#dfe4f2', // 同上面板的固定浅字
|
|
] as const
|
|
describe('裸色值棘轮', () => {
|
|
it('styles.css 不引入基线外裸色值', () => {
|
|
const found = collectHex(read(join(SRC, 'styles.css')))
|
|
const allow = new Set<string>(HEX_BASELINE_STYLES)
|
|
const extra = found.filter(h => !allow.has(h))
|
|
expect(extra, `新增裸色值 ${[...new Set(extra)].join(', ')}——改用 tokens 令牌;确需豁免更新 HEX_BASELINE_STYLES 并写明理由`).toEqual([])
|
|
})
|
|
it('组件 TSX 无裸色值', () => {
|
|
const extra: string[] = []
|
|
for (const p of walk(SRC).filter(p => /\.(tsx|ts)$/.test(p) && !p.endsWith('.d.ts'))) {
|
|
for (const h of collectHex(read(p))) extra.push(`${relative(SRC, p)}: ${h}`)
|
|
}
|
|
expect(extra, '组件里出现裸色值——走 tokens 令牌').toEqual([])
|
|
})
|
|
})
|
|
|
|
// ---- 4. 行内字号棘轮(--fs-ui-* 标尺) ----
|
|
const FONTSIZE_LITERAL_BASELINE = ['40'] as const // Conversation 空态装饰字形(⌘ 展示字号,非 UI 层级)
|
|
describe('行内字号棘轮', () => {
|
|
it('组件 TSX 无基线外 fontSize 数值字面量', () => {
|
|
const allow = new Set<string>(FONTSIZE_LITERAL_BASELINE)
|
|
const extra: string[] = []
|
|
for (const p of walk(SRC).filter(p => p.endsWith('.tsx'))) {
|
|
const lines = read(p).split('\n')
|
|
lines.forEach((l: string, i: number) => {
|
|
const m = l.match(/fontSize:\s*['"`]?([\d.]+)/)
|
|
if (m && !allow.has(m[1])) extra.push(`${relative(SRC, p)}:${i + 1} (${m[1]})`)
|
|
})
|
|
}
|
|
expect(extra, '行内 fontSize 数值——改用 var(--fs-ui-*);确需豁免更新基线并写明理由').toEqual([])
|
|
})
|
|
})
|