af68b20f87
新增 src/styles/theme.ts 语义 token 单源 + 相对亮度/对比度/isDarkSurface, tests 校验侧边栏暗、内容区亮、正文 WCAG AA、与 main.css 变量交叉一致防漂移。
76 lines
3.8 KiB
TypeScript
76 lines
3.8 KiB
TypeScript
import test from 'node:test'
|
|
import assert from 'node:assert/strict'
|
|
import { readSource } from './helpers.ts'
|
|
import {
|
|
ADMIN_THEME,
|
|
ADMIN_THEME_KEYS,
|
|
contrastRatio,
|
|
cssVarName,
|
|
isDarkSurface,
|
|
relativeLuminance,
|
|
tokenHex,
|
|
} from '../src/styles/theme.ts'
|
|
|
|
test('test_task_004_theme_tokens_normal_primary_path', () => {
|
|
// 正常主路径:侧边栏与加深面是暗色,内容区/卡片底是亮色。
|
|
assert.equal(isDarkSurface(ADMIN_THEME.sidebar), true, '侧边栏必须为暗色')
|
|
assert.equal(isDarkSurface(ADMIN_THEME.sidebarDeep), true, '侧边栏加深面必须为暗色')
|
|
assert.equal(isDarkSurface(ADMIN_THEME.bg), false, '内容区背景必须为亮色')
|
|
assert.equal(isDarkSurface(ADMIN_THEME.border), false, '边框须为亮面中性色')
|
|
})
|
|
|
|
test('test_task_004_theme_tokens_normal_variant_input', () => {
|
|
// 正常变体:强调色与软底/正文高对比,正文 token 自成一致体系。
|
|
assert.equal(relativeLuminance(ADMIN_THEME.primary), relativeLuminance('#6366f1'))
|
|
assert.ok(contrastRatio(ADMIN_THEME.text, ADMIN_THEME.bg) >= 4.5, '正文/内容区需满足 WCAG AA 正文')
|
|
assert.ok(contrastRatio(ADMIN_THEME.primary, ADMIN_THEME.bg) >= 3.0, '品牌色按钮须达到 3:1')
|
|
})
|
|
|
|
test('test_task_004_theme_tokens_normal_repeated_operation_is_idempotent', () => {
|
|
// 正常重复:亮度与对比度计算幂等、无状态污染。
|
|
assert.equal(relativeLuminance(ADMIN_THEME.bg), relativeLuminance(ADMIN_THEME.bg))
|
|
assert.equal(isDarkSurface(ADMIN_THEME.sidebar), isDarkSurface(ADMIN_THEME.sidebar))
|
|
assert.ok(contrastRatio(ADMIN_THEME.text, ADMIN_THEME.bg) === contrastRatio(ADMIN_THEME.text, ADMIN_THEME.bg))
|
|
})
|
|
|
|
test('test_task_004_theme_tokens_boundary_empty_input', () => {
|
|
// 边界空值:token 表非空、无空值、无未知多余键、键集合稳定。
|
|
assert.ok(ADMIN_THEME_KEYS.length >= 7, '必须覆盖侧边栏/内容区/正文等核心语义面')
|
|
for (const key of ADMIN_THEME_KEYS) {
|
|
assert.match(ADMIN_THEME[key], /^#[0-9a-fA-F]{6}$/, `${key} 须为 #rrggbb`)
|
|
}
|
|
})
|
|
|
|
test('test_task_004_theme_tokens_boundary_single_item', () => {
|
|
// 边界单元素:单个中性边界 token 与内容区可区分;侧边栏深于内容区。
|
|
assert.notEqual(ADMIN_THEME.border, ADMIN_THEME.bg)
|
|
assert.ok(relativeLuminance(ADMIN_THEME.sidebar) < relativeLuminance(ADMIN_THEME.bg))
|
|
})
|
|
|
|
test('test_task_004_theme_tokens_boundary_limit_or_missing_field', () => {
|
|
// 边界上限:次要文本 muted 在内容区上仍可达 3:1;CSS 变量名映射无重复斜杠。
|
|
assert.ok(contrastRatio(ADMIN_THEME.muted, ADMIN_THEME.bg) >= 3.0)
|
|
assert.equal(cssVarName('sidebar'), '--admin-sidebar')
|
|
assert.equal(cssVarName('sidebarDeep'), '--admin-sidebar-deep')
|
|
assert.ok(!cssVarName('primary').includes('--admin-admin-'))
|
|
})
|
|
|
|
test('test_task_004_theme_tokens_invalid_input_rejected', () => {
|
|
// 异常输入:非法 hex、未知 token 拒绝并报错;CSS 不得引用旧后台整份样式。
|
|
assert.throws(() => relativeLuminance('#12'), /#rrggbb/)
|
|
assert.throws(() => relativeLuminance('sidebar'), /#rrggbb/)
|
|
assert.throws(() => tokenHex('unknown' as never), /未知 Admin 主题 token/)
|
|
const css = readSource('src/styles/main.css')
|
|
assert.equal(/@import/.test(css), false, 'main.css 不得 @import 旧后台整份样式')
|
|
})
|
|
|
|
test('test_task_004_theme_tokens_dependency_failure_returns_actionable_message', () => {
|
|
// 依赖失败:token 与 main.css 渲染变量交叉校验必须一致(防双源漂移)。
|
|
const css = readSource('src/styles/main.css')
|
|
const missing = ADMIN_THEME_KEYS.filter((key) => {
|
|
const tokenVar = `--admin-${key.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`)}`
|
|
return !css.includes(`${tokenVar}: ${ADMIN_THEME[key]}`)
|
|
})
|
|
assert.deepEqual(missing, [], `theme.ts 与 main.css 不一致,缺失变量: ${missing.join(', ')}`)
|
|
})
|