import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import test from "node:test"; // BUG-1079 (TASK-site-button-contrast-20260928). This file began as the BUG-1078 // report-only contract; the report scope is gone and one site-wide definition // replaced it, so the file now pins that definition. Every assertion that // changed is listed with 原值 / 新值 / 原因 in // docs/tasks/PROGRESS-site-button-contrast-20260928.md. const css = readFileSync(new URL("../src/app/globals.css", import.meta.url), "utf8"); const cssNoComments = css.replace(/\/\*[\s\S]*?\*\//g, " "); const buttonSource = readFileSync(new URL("../src/components/ui/button.tsx", import.meta.url), "utf8"); const calendarSource = readFileSync(new URL("../src/components/ui/calendar.tsx", import.meta.url), "utf8"); const pickerSource = readFileSync(new URL("../src/components/birth-date-picker.tsx", import.meta.url), "utf8"); const generateSource = readFileSync(new URL("../src/components/personal-report/generate-personal-report-button.tsx", import.meta.url), "utf8"); function blockTokens(opener: string): Map { const start = css.indexOf(opener); assert.notEqual(start, -1, `missing block: ${opener}`); const body = css.slice(start, css.indexOf("\n}", start)); return new Map([...body.matchAll(/--([\w-]+)\s*:\s*([^;]+);/g)].map((hit) => [hit[1], hit[2].trim()])); } function luminance(hex: string): number { const [r, g, b] = [1, 3, 5].map((i) => parseInt(hex.slice(i, i + 2), 16) / 255) .map((c) => (c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4)); return 0.2126 * r + 0.7152 * g + 0.0722 * b; } function contrast(a: string, b: string): number { const [hi, lo] = [luminance(a), luminance(b)].sort((x, y) => y - x); return (hi + 0.05) / (lo + 0.05); } function rule(selector: string): string { const escaped = selector.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); const hit = cssNoComments.match(new RegExp(`(?:^|[}\\n])\\s*${escaped}\\s*\\{([^}]*)\\}`)); assert.ok(hit, `missing rule: ${selector}`); return hit[1]; } test("the unlayered element rules no longer override button utilities", () => { // Root cause of BUG-1078 / BUG-1079: unlayered `button { color: inherit; }` beat // every layered Tailwind text utility. assert.doesNotMatch(cssNoComments, /(^|[}\n])\s*button\s*\{\s*color\s*:/); assert.doesNotMatch(cssNoComments, /(^|[}\n])\s*button:disabled\s*\{/); // Raw