diff --git a/scripts/test-website-theme.mjs b/scripts/test-website-theme.mjs index f94f88f..50ef94b 100644 --- a/scripts/test-website-theme.mjs +++ b/scripts/test-website-theme.mjs @@ -6,6 +6,8 @@ import { fileURLToPath } from 'node:url'; const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); const template = fs.readFileSync(path.join(repoRoot, 'website', 'template.html'), 'utf8'); +const app = fs.readFileSync(path.join(repoRoot, 'website', 'app.js'), 'utf8'); +const langInit = fs.readFileSync(path.join(repoRoot, 'website', 'lang-init.js'), 'utf8'); const mockupStart = template.indexOf('
'); const mockupEnd = template.indexOf('
]*\brel="stylesheet"[^>]*\bhref="\{\{ASSET_PATH\}\}landing[^">]*"[^>]*>/g)]; +if (landingStylesheets.length !== 1) { + throw new Error(`Landing must load exactly one render-blocking stylesheet; found ${landingStylesheets.length}`); +} + +const landingStylesheet = landingStylesheets[0][0]; +if (!/href="\{\{ASSET_PATH\}\}landing\.min\.css"/.test(landingStylesheet)) { + throw new Error('Landing stylesheet must use the landing.min.css build placeholder'); +} +if (/\bmedia=|data-(?:landing-)?deferred/.test(landingStylesheet)) { + throw new Error('Landing stylesheet must not be activated after first paint'); +} + +const runtimeCssActivation = [ + ['app.js', app], + ['lang-init.js', langInit] +]; +for (const [file, source] of runtimeCssActivation) { + if (/createElement\(['"]link['"]\)|requestIdleCallback|data-landing-deferred|data-deferred-css|\.media\s*=\s*['"]all['"]/.test(source)) { + throw new Error(`${file} must not dynamically load or activate structural CSS`); + } +} + console.log('Extension mockup theme-sensitive text uses theme-aware colors'); +console.log('Landing CSS is render-blocking, single-request, and cascade-stable');