import { describe, it } from "vitest"; import { RuleTester } from "eslint"; import path from "node:path"; import os from "node:os"; import fs from "node:fs"; import rule from "./no-undeclared-analytics-event.js"; function makeFixture({ manifestUseCases, useCaseBody }) { const repoRoot = fs.mkdtempSync(path.join(os.tmpdir(), "nuae-")); const featureDir = path.join(repoRoot, "packages", "demo"); fs.mkdirSync(path.join(featureDir, "src", "application", "use-cases"), { recursive: true, }); const useCasesObj = Object.entries(manifestUseCases) .map( ([name, uc]) => ` ${name}: { mutates: ${uc.mutates}, audits: [], publishes: [], consumes: [], analyticsEvents: [${uc.analyticsEvents.map((e) => `"${e}"`).join(", ")}] },`, ) .join("\n"); fs.writeFileSync( path.join(featureDir, "src", "feature.manifest.ts"), `export const demoManifest = defineFeature({ name: "demo", requiredCores: [], useCases: { ${useCasesObj} }, realtimeChannels: [], jobs: [], } as const);`, ); const useCaseFile = path.join( featureDir, "src", "application", "use-cases", "sign-up.use-case.ts", ); fs.writeFileSync(useCaseFile, useCaseBody); return { repoRoot, useCaseFile }; } const tester = new RuleTester({ languageOptions: { parser: await import("@typescript-eslint/parser"), ecmaVersion: "latest", sourceType: "module", }, }); describe("no-undeclared-analytics-event", () => { it("passes when analytics.track slug matches manifest analyticsEvents[]", () => { const { repoRoot, useCaseFile } = makeFixture({ manifestUseCases: { signUp: { mutates: true, analyticsEvents: ["user.signed-up"] }, }, useCaseBody: `export const signUpUseCase = (analytics) => async () => { analytics.track("user.signed-up", {}); };`, }); tester.run("no-undeclared-analytics-event", rule, { valid: [ { filename: useCaseFile, code: fs.readFileSync(useCaseFile, "utf8"), options: [{ repoRoot }], }, ], invalid: [], }); }); it("fires when analytics.track slug is not in manifest", () => { const { repoRoot, useCaseFile } = makeFixture({ manifestUseCases: { signUp: { mutates: true, analyticsEvents: [] } }, useCaseBody: `export const signUpUseCase = (analytics) => async () => { analytics.track("user.signed-up", {}); };`, }); tester.run("no-undeclared-analytics-event", rule, { valid: [], invalid: [ { filename: useCaseFile, code: fs.readFileSync(useCaseFile, "utf8"), options: [{ repoRoot }], errors: [ { messageId: "undeclared", data: { event: "user.signed-up", useCase: "signUp" }, }, ], }, ], }); }); it("is a no-op when analytics.track is called with a non-literal argument", () => { const { repoRoot, useCaseFile } = makeFixture({ manifestUseCases: { signUp: { mutates: true, analyticsEvents: [] } }, useCaseBody: `export const signUpUseCase = (analytics, slug) => async () => { analytics.track(slug, {}); };`, }); tester.run("no-undeclared-analytics-event", rule, { valid: [ { filename: useCaseFile, code: fs.readFileSync(useCaseFile, "utf8"), options: [{ repoRoot }], }, ], invalid: [], }); }); it("is a no-op for files that are not use-case files", () => { const repoRoot = fs.mkdtempSync(path.join(os.tmpdir(), "nuae-")); const featureDir = path.join(repoRoot, "packages", "demo"); fs.mkdirSync(path.join(featureDir, "src", "application"), { recursive: true, }); const controllerFile = path.join( featureDir, "src", "application", "sign-up.controller.ts", ); fs.writeFileSync( controllerFile, `export const signUpController = (analytics) => async () => { analytics.track("user.signed-up", {}); };`, ); tester.run("no-undeclared-analytics-event", rule, { valid: [ { filename: controllerFile, code: fs.readFileSync(controllerFile, "utf8"), options: [{ repoRoot }], }, ], invalid: [], }); }); it("is a no-op when manifest has no entry for the use case", () => { const { repoRoot, useCaseFile } = makeFixture({ manifestUseCases: {}, useCaseBody: `export const signUpUseCase = (analytics) => async () => { analytics.track("user.signed-up", {}); };`, }); tester.run("no-undeclared-analytics-event", rule, { valid: [ { filename: useCaseFile, code: fs.readFileSync(useCaseFile, "utf8"), options: [{ repoRoot }], }, ], invalid: [], }); }); });