import fs from "node:fs"; import path from "node:path"; import fg from "fast-glob"; import { APP_PATHS } from "../lib/paths"; /** * Every component under `frontend/src/components/` must have a co-located * `.test.tsx`. * * Excluded: * - `ui/` — vendored UI primitives, tested upstream * - `*.test.tsx` — the test files * - files marked `no-test:` — components with no logic to test */ const OPT_OUT_RE = /\/\*\s*no-test:/; export interface MissingComponentTest { sourcePath: string; expectedTest: string; } export async function findMissingComponentTests( appRoot: string, cwd: string, ): Promise { const missing: MissingComponentTest[] = []; const pattern = `${appRoot}/${APP_PATHS.code.component}/**/*.tsx`; const files = await fg(pattern, { cwd, ignore: ["**/ui/**", "**/*.test.tsx"], }); for (const file of files) { const content = fs.readFileSync(path.join(cwd, file), "utf-8"); if (OPT_OUT_RE.test(content)) continue; const testPath = file.replace(/\.tsx$/, ".test.tsx"); if (!fs.existsSync(path.join(cwd, testPath))) { missing.push({ sourcePath: file, expectedTest: path.basename(testPath) }); } } return missing; }