'use strict'; const assert = require('assert'); const fs = require('fs'); const theme = fs.readFileSync('Theme.qml', 'utf8'); function token(name, occurrence = 0) { const matches = [...theme.matchAll(new RegExp(`"${name}": "(#[0-9a-fA-F]{6})"`, 'g'))]; assert(matches[occurrence], `missing theme token: ${name}[${occurrence}]`); return matches[occurrence][1]; } function rgb(hex) { return [1, 3, 5].map(i => parseInt(hex.slice(i, i + 2), 16) / 255); } function composite(fg, bg, alpha) { const a = rgb(fg); const b = rgb(bg); return a.map((v, i) => v * alpha + b[i] * (1 - alpha)); } function luminance(color) { const values = typeof color === 'string' ? rgb(color) : color; const linear = values.map(v => v <= 0.04045 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4); return linear[0] * 0.2126 + linear[1] * 0.7152 + linear[2] * 0.0722; } function contrast(a, b) { const x = luminance(a); const y = luminance(b); return (Math.max(x, y) + 0.05) / (Math.min(x, y) + 0.05); } const normal = token('normal', 0); const backdrop = token('backdrop', 1); const accent = token('accent', 1); const worstInteractiveFill = composite(accent, backdrop, 0.38); assert(contrast(normal, worstInteractiveFill) >= 4.5, 'normal text meets WCAG AA on strongest interactive fill'); const button = fs.readFileSync('PopupButton.qml', 'utf8'); const badge = fs.readFileSync('PopupBadge.qml', 'utf8'); const text = fs.readFileSync('NuguText.qml', 'utf8'); assert(!/selected:\s*root\.checked \|\| root\.primary/.test(button), 'buttons never select dark text on dark translucent fills'); assert(/color:\s*root\.theme\.content\.normal/.test(badge), 'badge text uses readable content color'); assert(!/selected \? theme\.content\.backdrop/.test(text), 'selected text stays readable on translucent dark controls'); console.log('contrast.test.js: ALL PASS');