CSS 自定义属性名大小写敏感,而 semanticVars() 的键是 camelCase、样式表读的是
kebab-case:chrome.ts 直接写 `--ot-${key}` 时,9 个多词变量(--ot-text-muted、
--ot-text-secondary、--ot-surface-raised、--ot-border-strong、--ot-success-fg、
--ot-danger-fg、--ot-warning-fg、--ot-info-fg、--ot-scrollbar-active、
--ot-focus-ring)从未真正生效,元素一律回落到 global.css :root 的深色兜底值——
浅色主题正是靠这些变量修可读性的,等于白改(--ot-text-muted 48 处引用仍是
#8a8a8a,白底 3.25:1;--ot-danger-fg 仍是 #f85149,3.15:1)。
- 键 `muted` 改名 `textMuted`(CSS 读的是 --ot-text-muted)
- 新增 semantic.ts 的 cssVarName()/semanticCssVars(),chrome.ts 只调用它们,
命名逻辑单一来源
- 新增 tests/ot-var-names.mjs:把「CSS 引用名 = JS 写入名 = :root 声明名」
三集合锁在一起(值测试看不出这类问题),已用「去掉转换」验证会失败
- AGENTS/STATE 同步该铁律;测试数 22/25 的陈旧计数修正为实际的 29
61 lines
2.6 KiB
JavaScript
61 lines
2.6 KiB
JavaScript
/**
|
||
* semantic.ts self-test. 该模块必须零 import(Node 原生类型剥离直连 .ts,
|
||
* 同 tests/terminal-cwd.mjs 的约束)。锁三件事:
|
||
* 1) isLightTheme 边界(>0.5 亮度为浅);
|
||
* 2) 深色分支 = SEMANTIC 锚点原值(零回归锁定);
|
||
* 3) 浅色分支全部达标:正文/fg ≥4.5,语义基色 ≥3.0。
|
||
* Run: node tests/semantic-theme.mjs (must exit 0)
|
||
*/
|
||
import { isLightTheme, semanticVars, SEMANTIC } from '../src/renderer/src/theme/semantic.ts'
|
||
|
||
let failed = 0
|
||
const ok = (cond, msg) => {
|
||
console.log(` ${cond ? 'ok' : 'FAIL'}: ${msg}`)
|
||
if (!cond) failed += 1
|
||
}
|
||
|
||
console.log('isLightTheme:')
|
||
ok(isLightTheme('#ffffff') === true, '#ffffff is light')
|
||
ok(isLightTheme('#fafafa') === true, '#fafafa is light')
|
||
ok(isLightTheme('#fdf6e3') === true, '#fdf6e3 is light')
|
||
ok(isLightTheme('#1e1e1e') === false, '#1e1e1e is dark')
|
||
ok(isLightTheme('#282a36') === false, '#282a36 is dark')
|
||
|
||
console.log('dark branch locks anchors (zero regression):')
|
||
const dark = semanticVars({ background: '#1e1e1e', foreground: '#cccccc' }, false)
|
||
ok(dark['textMuted'] === SEMANTIC.muted, 'textMuted = anchor')
|
||
ok(dark['success'] === SEMANTIC.success, 'success = anchor')
|
||
ok(dark['danger'] === SEMANTIC.danger, 'danger = anchor')
|
||
ok(dark['warning'] === SEMANTIC.warning, 'warning = anchor')
|
||
ok(dark['info'] === SEMANTIC.info, 'info = anchor')
|
||
ok(dark['scrollbar'] === SEMANTIC.scrollbar, 'scrollbar = anchor')
|
||
ok(dark['shadow'] === SEMANTIC.shadow, 'shadow = anchor')
|
||
ok(dark['colorScheme'] === 'dark', 'colorScheme dark')
|
||
|
||
console.log('light branch meets ratios:')
|
||
for (const bg of ['#f6f8fa', '#fafafa', '#fdf6e3']) {
|
||
const v = semanticVars({ background: bg, foreground: '#383a42' }, true)
|
||
const L = (h) => {
|
||
const c = [1, 3, 5].map((i) => parseInt(h.slice(i, i + 2), 16) / 255)
|
||
.map((s) => (s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4))
|
||
return 0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2]
|
||
}
|
||
const cr = (a, b) => {
|
||
const x = L(a), y = L(b)
|
||
return (Math.max(x, y) + 0.05) / (Math.min(x, y) + 0.05)
|
||
}
|
||
for (const key of ['textMuted', 'textSecondary', 'successFg', 'dangerFg', 'warningFg', 'infoFg']) {
|
||
ok(cr(v[key], bg) >= 4.5, `${bg} ${key} ${v[key]} >= 4.5 (${cr(v[key], bg).toFixed(2)})`)
|
||
}
|
||
for (const key of ['success', 'danger', 'warning', 'info']) {
|
||
ok(cr(v[key], bg) >= 3, `${bg} ${key} ${v[key]} >= 3.0 (${cr(v[key], bg).toFixed(2)})`)
|
||
}
|
||
ok(v['colorScheme'] === 'light', `${bg} colorScheme light`)
|
||
}
|
||
|
||
if (failed > 0) {
|
||
console.error(`${failed} check(s) failed`)
|
||
process.exit(1)
|
||
}
|
||
console.log('semantic-theme: all ok')
|