diff --git a/app/globals.css b/app/globals.css index 76d6f1522..a3a4d3ab3 100644 --- a/app/globals.css +++ b/app/globals.css @@ -187,6 +187,22 @@ body { "calt" 1; } +/* Native 's option list itself; the Tailwind + * classes on the control (bg-muted / text-foreground) never reach it. It only + * honours `color-scheme` and an explicit option background/colour. Themes with + * a translucent --color-muted (Aurora Glass dark) therefore composited to a + * light popup with near-white text (#999). globals.css pins options to the + * popover tokens, so every theme must define those as an opaque surface. + * + * jsdom does not render native popups, so lock the stylesheet rules. + */ +const css = readFileSync(path.join(process.cwd(), 'app', 'globals.css'), 'utf8'); + +/** Return the body of the first top-level `selector { ... }` rule. */ +function ruleBody(selector: string): string { + const idx = css.indexOf('\n' + selector + ' {'); + expect(idx, 'no `' + selector + '` rule in globals.css').toBeGreaterThan(-1); + const open = css.indexOf('{', idx); + return css.slice(open + 1, css.indexOf('}', open)); +} + +/** True when a CSS colour value has no alpha channel below 1. */ +function isOpaque(value: string): boolean { + const v = value.trim().toLowerCase(); + if (v === 'transparent') return false; + if (/^#([0-9a-f]{3}|[0-9a-f]{6})$/.test(v)) return true; + if (/^#[0-9a-f]{4}$/.test(v)) return v.endsWith('f'); + if (/^#[0-9a-f]{8}$/.test(v)) return v.endsWith('ff'); + const fn = v.match(/^(rgb|hsl)a?\((.*)\)$/); + if (fn) { + const parts = fn[2].split(/[\s,/]+/).filter(Boolean); + if (parts.length < 4) return true; + const alpha = parts[3]; + return alpha.endsWith('%') ? parseFloat(alpha) >= 100 : parseFloat(alpha) >= 1; + } + return false; +} + +/** Concrete `--color-popover` values, skipping the `@theme` alias `var(--color-popover)`. */ +function popoverValues(source: string): string[] { + return [...source.matchAll(/--color-popover:\s*([^;]+);/g)] + .map((m) => m[1].trim()) + .filter((v) => !v.startsWith('var(')); +} + +describe('native