Files
OpenTerminal/tests/theme-contrast.mjs
T

47 lines
1.8 KiB
JavaScript

/**
* Builtin theme palette contrast gate. Light themes: foreground + all 16 ANSI
* colors must reach 4.5:1 against the theme background (WCAG AA body text);
* cursor must reach 3:1. Dark themes: only the foreground sanity check (>=4.5)
* — dark palettes are locked aesthetics, not regression targets here.
* Imports ../src/shared/theme.ts directly (zero-import module, Node >= 22.18).
* Run: node tests/theme-contrast.mjs (must exit 0)
*/
import { BUILTIN_THEMES } from '../src/shared/theme.ts'
let failed = 0
const ok = (cond, msg) => {
console.log(` ${cond ? 'ok' : 'FAIL'}: ${msg}`)
if (!cond) failed += 1
}
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)
}
const ANSI = ['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white',
'brightBlack', 'brightRed', 'brightGreen', 'brightYellow', 'brightBlue',
'brightMagenta', 'brightCyan', 'brightWhite']
const isLight = (bg) => L(bg) > 0.5
for (const t of BUILTIN_THEMES) {
const c = t.colors
const light = isLight(c.background)
console.log(`${t.id} (${light ? 'light' : 'dark'}, bg ${c.background}):`)
ok(cr(c.foreground, c.background) >= 4.5, `foreground ${c.foreground} ${cr(c.foreground, c.background).toFixed(2)}`)
ok(cr(c.cursor, c.background) >= 3, `cursor ${c.cursor} ${cr(c.cursor, c.background).toFixed(2)}`)
if (!light) continue
for (const k of ANSI) {
ok(cr(c[k], c.background) >= 4.5, `${k} ${c[k]} ${cr(c[k], c.background).toFixed(2)}`)
}
}
if (failed > 0) {
console.error(`${failed} check(s) failed`)
process.exit(1)
}
console.log('theme-contrast: all ok')