47 lines
1.8 KiB
JavaScript
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')
|