/** * L1 architecture guard: only `src/adapters` may import `@tauri-apps/api` or * call `invoke()`. React components (features/app) must go through the gateway * ports (ARCHITECTURE ยง1.3, L1 DoD "Aucun invoke() direct dans les composants"). */ import { describe, it, expect } from "vitest"; import { readdirSync, readFileSync, statSync } from "node:fs"; import { join, sep } from "node:path"; // Vitest runs with cwd = frontend/; scan the whole `src` tree from there. const SRC = join(process.cwd(), "src"); /** Recursively collect .ts/.tsx files, skipping the allowed adapters dir. */ function collectSourceFiles(dir: string, out: string[] = []): string[] { for (const entry of readdirSync(dir)) { const full = join(dir, entry); if (statSync(full).isDirectory()) { // `src/adapters` is the single allowed home of Tauri transport code. if (full.endsWith(`${sep}adapters`)) continue; collectSourceFiles(full, out); } else if (/\.tsx?$/.test(entry) && !/\.test\.tsx?$/.test(entry)) { out.push(full); } } return out; } /** Strips block and line comments so doc-comment mentions don't false-positive. */ function stripComments(src: string): string { return src .replace(/\/\*[\s\S]*?\*\//g, "") .replace(/\/\/[^\n]*/g, ""); } describe("no direct Tauri usage outside src/adapters", () => { const files = collectSourceFiles(SRC); it("finds source files to scan", () => { expect(files.length).toBeGreaterThan(0); }); it("no file imports @tauri-apps/api", () => { const offenders = files.filter((f) => stripComments(readFileSync(f, "utf8")).includes("@tauri-apps/api"), ); expect(offenders, `offending files: ${offenders.join(", ")}`).toEqual([]); }); it("no file calls invoke( (ignoring comments)", () => { const offenders = files.filter((f) => /\binvoke\s*\(/.test(stripComments(readFileSync(f, "utf8"))), ); expect(offenders, `offending files: ${offenders.join(", ")}`).toEqual([]); }); });