test: repoint verification at Astro output
Verify rendered routes, translations, CSS dependencies, variables, and built CSS values. Keep the original assertion count; do not alter site components or fixtures.
This commit is contained in:
+49
-18
@@ -1,4 +1,5 @@
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { readFileSync, readdirSync, statSync } from 'node:fs';
|
||||
import { dirname, join } from 'node:path';
|
||||
|
||||
const read = (path) => readFileSync(new URL(`../${path}`, import.meta.url), 'utf8');
|
||||
const pages = [
|
||||
@@ -13,27 +14,57 @@ const pages = [
|
||||
'hands-on/starter/index.html',
|
||||
'hands-on/rules/index.html',
|
||||
];
|
||||
const walk = (path) =>
|
||||
readdirSync(path).flatMap((name) => {
|
||||
const child = join(path, name);
|
||||
return statSync(child).isDirectory() ? walk(child) : [child];
|
||||
});
|
||||
const definitions = (css) => new Set([...css.matchAll(/(--[\w-]+)\s*:/g)].map(([, name]) => name));
|
||||
const uses = (css) => new Set([...css.matchAll(/var\((--[\w-]+)/g)].map(([, name]) => name));
|
||||
const builtCss = walk('dist/_astro')
|
||||
.filter((path) => path.endsWith('.css'))
|
||||
.map((path) => readFileSync(path, 'utf8'))
|
||||
.join('\n');
|
||||
const builtValues = {
|
||||
colors: new Set(builtCss.match(/#[0-9a-fA-F]{3,8}\b|(?:rgba?|hsla?)\([^)]*\)/g) || []),
|
||||
sizes: new Set(builtCss.match(/\b\d+(?:\.\d+)?(?:px|rem|em|vw|vh)\b/g) || []),
|
||||
};
|
||||
// Captured from origin/main after its Astro build. This catches a value that is
|
||||
// silently changed to a nearby token: source token checks cannot see that drift.
|
||||
const baselineValues = JSON.parse(read('.agents/snapshots/built-css-values.json'));
|
||||
|
||||
for (const page of pages) {
|
||||
const html = read(page);
|
||||
if (!html.includes('name="viewport"')) throw new Error(`${page} lacks a viewport declaration`);
|
||||
if (html.match(/<(script|link)[^>]+(src|href)="https?:[^\"]+"/i))
|
||||
throw new Error(`${page} has an external runtime dependency`);
|
||||
}
|
||||
const shared = read('chapters.css');
|
||||
const skills = read('skills/styles.css');
|
||||
const guide = read('full-guide/index.html');
|
||||
const guideAudit = read('full-guide/audit.css');
|
||||
for (const token of ['@media(max-width:800px)', '@media(max-width:520px)'])
|
||||
if (!shared.includes(token)) throw new Error(`shared chapter CSS lacks ${token}`);
|
||||
for (const token of [
|
||||
'minmax(0,1.3fr)',
|
||||
'overflow-wrap:anywhere',
|
||||
'@media(max-width:800px)',
|
||||
'prefers-reduced-motion',
|
||||
])
|
||||
if (!skills.includes(token)) throw new Error(`skills package CSS lacks ${token}`);
|
||||
if (!guide.includes('href="audit.css"'))
|
||||
throw new Error('full guide does not load its responsive audit overrides');
|
||||
for (const token of ['.handoff table', '.route-table button', 'overflow-wrap:anywhere'])
|
||||
if (!guideAudit.includes(token)) throw new Error(`full guide audit CSS lacks ${token}`);
|
||||
for (const file of walk('dist').filter((path) => path.endsWith('.css'))) {
|
||||
const css = readFileSync(file, 'utf8').replace(/\/\*[\s\S]*?\*\//g, '');
|
||||
if (/@import|src:\s*url\(['"]?https?:|url\(['"]?https?:/i.test(css))
|
||||
throw new Error(`${file} has an external CSS dependency`);
|
||||
}
|
||||
for (const [kind, values] of Object.entries(baselineValues)) {
|
||||
const missing = values.filter((value) => !builtValues[kind].has(value));
|
||||
if (missing.length) throw new Error(`built CSS lost ${kind}: ${missing.join(', ')}`);
|
||||
}
|
||||
// Legacy fixtures still ship directly. Resolve every var() from the stylesheets
|
||||
// that page actually links; Astro's tokens cannot mask a broken standalone page.
|
||||
for (const page of pages) {
|
||||
const html = read(page);
|
||||
const linked = [...html.matchAll(/<link[^>]+rel="stylesheet"[^>]+href="([^"]+)"/gi)].map(
|
||||
([, href]) => href,
|
||||
);
|
||||
const styles = linked
|
||||
.filter((href) => !/^https?:/i.test(href))
|
||||
.map((href) => read(join(dirname(page), href)));
|
||||
const defined = new Set(styles.flatMap((css) => [...definitions(css)]));
|
||||
const unresolved = new Set(
|
||||
styles.flatMap((css) =>
|
||||
[...uses(css)].filter((name) => name !== '--score' && !defined.has(name)),
|
||||
),
|
||||
);
|
||||
if (unresolved.size)
|
||||
throw new Error(`${page} has unresolved CSS variables: ${[...unresolved].join(', ')}`);
|
||||
}
|
||||
console.log('responsive UI audit passed');
|
||||
|
||||
Reference in New Issue
Block a user