85 lines
4.1 KiB
JavaScript
85 lines
4.1 KiB
JavaScript
/**
|
||
* 强调色可见性验收:真实渲染 + 取实际计算色 + 算 WCAG 对比度。
|
||
*
|
||
* 结构性断言(test/theme.test.mjs)只能保证变量存在、档位达标;
|
||
* 「按钮到底看得见吗」必须在真浏览器里量 —— 上一次那个 bug 正是
|
||
* 所有静态检查都过、只有肉眼能发现。
|
||
*/
|
||
import { openApp, WIDE } from './narrow-probe-helper.mjs';
|
||
|
||
const srgb = c => { c /= 255; return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4; };
|
||
const L = ([r, g, b]) => 0.2126 * srgb(r) + 0.7152 * srgb(g) + 0.0722 * srgb(b);
|
||
const ratio = (a, b) => { const l1 = L(a), l2 = L(b); const [h, o] = l1 > l2 ? [l1, l2] : [l2, l1]; return (h + 0.05) / (o + 0.05); };
|
||
const parse = s => (s.match(/\d+/g) || []).slice(0, 3).map(Number);
|
||
|
||
// 把一批强调色类名注入页面,量它们的实际计算值
|
||
// hover 档(bg-red-700 / bg-blue-700)不在这里:源码里它们只以 `hover:` 变体
|
||
// 出现,Tailwind 因此不生成基础类 —— 探它会得到透明背景,那是探针的假阳性
|
||
// 而不是真 bug。它们由 theme.test.mjs 的档位断言覆盖(--s-* 两模式同值)。
|
||
const PROBES = [
|
||
// [类名组合, 说明, 期望最低对比度]
|
||
['bg-red-600 text-white', '危险实心按钮(确认归档 / 删除)', 3.0],
|
||
['bg-red-500 text-white', '未读徽标', 3.0],
|
||
['bg-green-600 text-white', '同意按钮', 3.0],
|
||
['bg-orange-700 text-white', '待决策徽标', 4.5],
|
||
['bg-blue-600 text-white', '主按钮', 4.5],
|
||
['bg-red-50 text-red-600', '错误提示条', 4.5],
|
||
['bg-red-50 text-red-700', '危险区块文字', 4.5],
|
||
['bg-green-50 text-green-700', '成功提示条', 4.5],
|
||
['bg-orange-100 text-orange-700', '橙色 chip', 4.5],
|
||
['bg-amber-100 text-amber-700', '警告 chip', 4.5],
|
||
['bg-amber-50 text-amber-800', '警告条', 4.5],
|
||
['bg-yellow-100 text-yellow-700', '黄色 chip', 4.5],
|
||
['bg-blue-50 text-blue-700', '信息条', 4.5],
|
||
['bg-red-100 text-red-700', '红色 chip', 4.5],
|
||
['bg-orange-200 text-orange-800', '深橙 chip', 4.5],
|
||
['bg-white text-gray-900', '卡片正文', 4.5],
|
||
['bg-white text-gray-500', '卡片次要文字', 4.5],
|
||
];
|
||
|
||
const { browser, page } = await openApp(WIDE);
|
||
let fail = 0;
|
||
|
||
for (const mode of ['light', 'dark']) {
|
||
await page.evaluate(m => {
|
||
document.documentElement.classList.toggle('dark', m === 'dark');
|
||
let host = document.getElementById('__probe');
|
||
if (host) host.remove();
|
||
host = document.createElement('div');
|
||
host.id = '__probe';
|
||
host.style.position = 'fixed';
|
||
host.style.top = '0';
|
||
host.style.left = '0';
|
||
host.style.zIndex = '99999';
|
||
document.body.appendChild(host);
|
||
}, mode);
|
||
|
||
console.log(`\n─── ${mode === 'dark' ? '深色' : '浅色'} ───`);
|
||
for (const [cls, label, min] of PROBES) {
|
||
const got = await page.evaluate(c => {
|
||
const host = document.getElementById('__probe');
|
||
host.innerHTML = `<span id="__p" class="${c}">测试</span>`;
|
||
const el = document.getElementById('__p');
|
||
const s = getComputedStyle(el);
|
||
return { bg: s.backgroundColor, fg: s.color };
|
||
}, cls);
|
||
|
||
// 透明背景 = 声明失效(正是上次那个 bug 的指纹)
|
||
const transparent = /rgba\(0,\s*0,\s*0,\s*0\)|transparent/.test(got.bg);
|
||
if (transparent) {
|
||
console.log(` 失败 ${label} — 背景透明(${cls} 的 background-color 声明失效)`);
|
||
fail++;
|
||
continue;
|
||
}
|
||
const r = ratio(parse(got.fg), parse(got.bg));
|
||
const ok = r >= min;
|
||
if (!ok) fail++;
|
||
console.log(` ${ok ? '通过' : '失败'} ${label.padEnd(22)} ${r.toFixed(2)}:1 (需 ${min}) ${got.bg} / ${got.fg}`);
|
||
}
|
||
}
|
||
|
||
await page.evaluate(() => document.getElementById('__probe')?.remove());
|
||
await browser.close();
|
||
console.log(fail ? `\n!! ${fail} 项不达标` : '\n全部达标');
|
||
process.exit(fail ? 1 : 0);
|