test(frontend): verify global stylesheet @import graph after vitest

Add scripts/check-css-import-graph.mjs and run it from npm test and
test:coverage so missing relative CSS imports fail CI like Vite/postcss.
Document the step in src/test/README.md; trigger frontend workflow when
the script changes.
This commit is contained in:
Maxim Isaev
2026-05-13 21:09:53 +03:00
parent 2eb23e99b5
commit 94cfb3b58d
4 changed files with 81 additions and 2 deletions
+2
View File
@@ -13,6 +13,7 @@ on:
- '.github/workflows/frontend-tests.yml'
- '.github/frontend-hot-path-files.txt'
- 'scripts/check-frontend-hot-path-coverage.sh'
- 'scripts/check-css-import-graph.mjs'
push:
branches: [main]
paths:
@@ -25,6 +26,7 @@ on:
- '.github/workflows/frontend-tests.yml'
- '.github/frontend-hot-path-files.txt'
- 'scripts/check-frontend-hot-path-coverage.sh'
- 'scripts/check-css-import-graph.mjs'
workflow_dispatch:
permissions:
+3 -2
View File
@@ -3,15 +3,16 @@
"version": "1.46.0-dev",
"private": true,
"scripts": {
"check:css-imports": "node scripts/check-css-import-graph.mjs",
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"tauri": "tauri",
"tauri:dev": "tauri dev",
"tauri:build": "tauri build",
"test": "vitest run",
"test": "vitest run && npm run check:css-imports",
"test:watch": "vitest",
"test:coverage": "vitest run --coverage"
"test:coverage": "vitest run --coverage && npm run check:css-imports"
},
"dependencies": {
"@fontsource-variable/dm-sans": "^5.2.8",
+63
View File
@@ -0,0 +1,63 @@
#!/usr/bin/env node
/**
* Walk the same root CSS entrypoints as src/main.tsx and ensure every
* relative @import target exists on disk. Catches postcss-import ENOENT
* that Vitest does not surface (CSS is not fully resolved like vite build).
*/
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const repoRoot = path.resolve(__dirname, '..');
const stylesRoot = path.join(repoRoot, 'src', 'styles');
const ENTRY_RELS = [
'themes/index.css',
'layout/index.css',
'components/index.css',
'tracks/index.css',
];
/** Quoted path in @import '...' or "..." (postcss-import style in this repo). */
const IMPORT_RE = /@import\s+(['"])([^'"]+)\1/g;
function isFilesystemImport(spec) {
if (spec.startsWith('http://') || spec.startsWith('https://') || spec.startsWith('data:')) {
return false;
}
if (spec.startsWith('@')) return false;
return spec.startsWith('./') || spec.startsWith('../');
}
function walk(absPath, visited, missing) {
const key = path.resolve(absPath);
if (visited.has(key)) return;
if (!fs.existsSync(key)) {
missing.add(path.relative(repoRoot, key));
visited.add(key);
return;
}
visited.add(key);
const text = fs.readFileSync(key, 'utf8');
let m;
IMPORT_RE.lastIndex = 0;
while ((m = IMPORT_RE.exec(text)) !== null) {
const spec = m[2];
if (!isFilesystemImport(spec)) continue;
const next = path.resolve(path.dirname(key), spec);
walk(next, visited, missing);
}
}
const visited = new Set();
const missing = new Set();
for (const rel of ENTRY_RELS) {
walk(path.join(stylesRoot, rel), visited, missing);
}
if (missing.size > 0) {
const list = [...missing].sort().join('\n ');
console.error(`check-css-import-graph: missing @import target(s):\n ${list}`);
process.exit(1);
}
+13
View File
@@ -33,8 +33,21 @@ src/test/
npm test # one-shot run
npm run test:watch # watch mode
npm run test:coverage # with v8 coverage → ./coverage/
npm run check:css-imports # only the global stylesheet @import graph (see below)
```
## CSS `@import` graph
Vitest does not load the full global CSS bundle from `main.tsx`, so a broken
relative `@import` under `src/styles/**` can slip past the suite until Vite
runs (`ENOENT` from postcss-import).
After **`vitest run`**, **`npm test`** and **`npm run test:coverage`** run
**`npm run check:css-imports`**, which executes **`scripts/check-css-import-graph.mjs`**
and walks the same four root stylesheets as **`src/main.tsx`**, resolving
only filesystem-relative imports (`./…`, `../…`). Package imports such as
`@fontsource/...` are ignored.
## Where tests go
- **Co-located with the unit under test**: `Foo.tsx``Foo.test.tsx`,