import fs from 'fs'; import path from 'path'; /** * Guards against "stranded" CSS: when markup moves between components, its * rules must move with it. CSS Modules hash class names per file, so a rule * left behind in another stylesheet silently stops applying — exactly how the * header's mobile `.headerDetails { display: none }` (and the section-nav * mobile swap) stopped working when the detail view was split into a client * shell plus server sections. */ const SCHOOL_DIR = path.join(__dirname, '..', '..', 'components', 'school'); function classesDefinedIn(css: string): Set { return new Set(Array.from(css.matchAll(/\.(-?[_a-zA-Z][\w-]*)/g), (m) => m[1])); } function classesUsedIn(tsx: string): Set { return new Set(Array.from(tsx.matchAll(/\bstyles\.([_a-zA-Z][\w]*)/g), (m) => m[1])); } const components = fs .readdirSync(SCHOOL_DIR) .filter((f) => f.endsWith('.tsx')) .map((file) => { const source = fs.readFileSync(path.join(SCHOOL_DIR, file), 'utf8'); const importMatch = source.match(/import\s+styles\s+from\s+['"](.+?\.module\.css)['"]/); return { file, source, stylesheet: importMatch?.[1] }; }) .filter((c) => c.stylesheet); describe('school detail stylesheets', () => { it.each(components.map((c) => [c.file, c.source, c.stylesheet as string]))( '%s only uses classes its own stylesheet defines', (_file, source, stylesheet) => { const css = fs.readFileSync(path.join(SCHOOL_DIR, stylesheet), 'utf8'); const defined = classesDefinedIn(css); const missing = Array.from(classesUsedIn(source)).filter((c) => !defined.has(c)); expect(missing).toEqual([]); }, ); });