import { mkdtemp, mkdir, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { describe, expect, it } from "vitest"; import { failureCount, gates, renderReport, runI18nBrandInventory } from "./verify-i18n-hardcoded-baseline.mjs"; describe("i18n Hardcoded Strings baseline runner failure counting", () => { it("counts TypeScript diagnostics from error TS markers", () => { expect(failureCount("typecheck", "app/a.ts(1,1): error TS2322: nope\napp/b.ts(2,2): error TS7006: nope", "", 2)).toBe(2); }); it("counts ESLint errors from the summary problem line", () => { expect(failureCount("lint", "✖ 5 problems (3 errors, 2 warnings)", "", 1)).toBe(3); }); it("counts Vitest failed tests from the i18n smoke summary", () => { expect(failureCount("vitest-i18n-smoke", "Tests 4 failed | 8 passed", "", 1)).toBe(4); }); it("counts CI baseline FAIL lines conservatively", () => { expect(failureCount("ci-baseline", "FAIL package.json: missing script\nFAIL .eslintrc: missing rule", "", 1)).toBe(2); }); it("counts hardcoded-string guard totals and falls back conservatively on non-empty failures", () => { expect(failureCount("hardcoded-string-guard", "i18n Hardcoded Strings hardcoded-string guard\ntotal forbidden matches: 3", "", 1)).toBe(3); expect(failureCount("hardcoded-string-guard", "", "unexpected scanner failure", 1)).toBe(1); }); it("returns zero for clean exits and read-only inventory gates", () => { expect(failureCount("typecheck", "", "", 0)).toBe(0); expect(failureCount("i18n-brand-hardcoded-inventory", "ToolCTRL: 1", "", 1)).toBe(0); }); }); describe("i18n Hardcoded Strings baseline gate configuration", () => { it("runs the hardcoded-string guard immediately before the read-only inventory", () => { const guardIndex = gates.findIndex((gate) => gate.id === "hardcoded-string-guard"); expect(guardIndex).toBeGreaterThan(-1); expect(gates[guardIndex]).toMatchObject({ command: "node", args: ["scripts/verify-hardcoded-strings.mjs"] }); expect(gates[guardIndex + 1]?.id).toBe("i18n-brand-hardcoded-inventory"); }); }); describe("i18n Hardcoded Strings i18n/brand/hardcoded inventory", () => { it("scans existing roots, records missing roots, and ignores generated docs dependencies and TC paths", async () => { const root = await mkdtemp(join(tmpdir(), "i18nHardcodedStrings-baseline-inventory-")); await mkdir(join(root, "apps", "web", "app", "routes"), { recursive: true }); await mkdir(join(root, "packages", "i18n", "src"), { recursive: true }); await mkdir(join(root, "apps", "web", "public", "locales", "de"), { recursive: true }); await mkdir(join(root, "docs"), { recursive: true }); await mkdir(join(root, ".tc"), { recursive: true }); await mkdir(join(root, "apps", "web", "app", "generated"), { recursive: true }); await mkdir(join(root, "node_modules", "ignored"), { recursive: true }); await writeFile( join(root, "apps", "web", "app", "routes", "sample.tsx"), [ "const { t } = useTranslation();", "const title = t('dashboard.title');", "const label = 'Welcome aboard';", "export function Component() { return

Welcome aboard

; }", "const brand = 'ToolCTRL';", "const link = 'https://tool-ctrl.de/datenschutz';", "const subject = 'Welcome email subject';", "const logoAlt = 'Company logo';", "const legal = 'Terms and privacy policy';", ].join("\n"), "utf8", ); await writeFile(join(root, "packages", "i18n", "src", "index.ts"), "export const defaultLocale = 'de'; export const supportedLocales = ['de', 'en'];\nimport i18next from 'i18next';\n", "utf8"); await mkdir(join(root, "packages", "brand", "src"), { recursive: true }); await writeFile( join(root, "packages", "brand", "src", "index.ts"), [ "export const defaultBrandTokens = { productName: 'ToolCTRL' };", "export function createBrandTokens() { return defaultBrandTokens; }", "export function resolveTransactionalBrandTokens() { return createBrandTokens(); }", "export function brandCssVariables() { return {}; }", "export type BrandTokens = typeof defaultBrandTokens;", "export type BrandTokenOverrides = Partial;", "export type ToolCTRLBrandTokens = BrandTokens;", ].join("\n"), "utf8", ); await writeFile( join(root, "apps", "web", "app", "routes", "transactional.ts"), [ "import { defaultBrandTokens } from '@toolctrl/brand';", "import { renderTransactionalMailTemplate, MailTemplateError } from '../../packages/mail/src/templates/transactional';", "export type TransactionalTemplateName = 'welcome';", "export type TransactionalTemplateData = { action_url: string };", "export type RenderedTransactionalMail = { html: string };", "const html = renderTransactionalMailTemplate('welcome', { action_url: 'https://example.test' }, { brand: defaultBrandTokens });", "console.log(MailTemplateError, html);", ].join("\n"), "utf8", ); await writeFile(join(root, "apps", "web", "public", "locales", "de", "translations.json"), "{ \"hello\": \"Hallo\" }\n", "utf8"); const ignoredSignal = "ToolCTRL @toolctrl/brand packages/brand defaultBrandTokens renderTransactionalMailTemplate MailTemplateError useTranslation title = 'Ignored Signal'\n"; await writeFile(join(root, "docs", "ignored.tsx"), ignoredSignal, "utf8"); await writeFile(join(root, ".tc", "ignored.tsx"), ignoredSignal, "utf8"); await writeFile(join(root, "apps", "web", "app", "generated", "ignored.tsx"), ignoredSignal, "utf8"); await writeFile(join(root, "node_modules", "ignored", "ignored.tsx"), ignoredSignal, "utf8"); const inventory = await runI18nBrandInventory(root); const allMatches = inventory.matches.join("\n"); expect(inventory.roots).toEqual(["apps/web/app", "apps/web/public", "packages"]); expect(inventory.missingRoots).toEqual(["apps/api", "apps/worker", "emails", "prisma", "server"]); expect(inventory.filesScanned).toBe(5); expect(inventory.counts).toMatchObject({ "locale-file-reference": 2, "translation-hook-or-call": 2, "i18next-reference": 2, "brand-package-reference": 1, "brand-token-contract": 14, "transactional-mail-renderer": 7, "brand-literal": 3, "email-template-reference": 0, "logo-domain-legal-link": 4, "hardcoded-jsx-text": 1, "hardcoded-user-facing-string": 2, }); expect(allMatches).toContain("apps/web/app/routes/sample.tsx"); expect(allMatches).toContain("apps/web/app/routes/transactional.ts"); expect(allMatches).toContain("packages/i18n/src/index.ts"); expect(allMatches).toContain("packages/brand/src/index.ts"); expect(inventory.roots).toContain("apps/web/public"); expect(allMatches).not.toContain("docs/ignored.tsx"); expect(allMatches).not.toContain(".tc/ignored.tsx"); expect(allMatches).not.toContain("apps/web/app/generated/ignored.tsx"); expect(allMatches).not.toContain("node_modules/ignored/ignored.tsx"); }); }); describe("i18n Hardcoded Strings baseline report rendering", () => { it("renders gate identifiers, exit codes, failure counts, and fingerprints", () => { const report = renderReport([ { id: "typecheck", command: "pnpm run typecheck", exitCode: 2, durationMs: 1500, failureCount: 7, fingerprint: { hash: "abc123def456", excerpt: "error TS2322" } }, { id: "hardcoded-string-guard", command: "node scripts/verify-hardcoded-strings.mjs", exitCode: 1, durationMs: 23, failureCount: 2, fingerprint: { hash: "987654fedcba", excerpt: "total forbidden matches: 2" }, }, { id: "i18n-brand-hardcoded-inventory", command: "read-only i18n/brand/hardcoded-string inventory", exitCode: 0, durationMs: 42, failureCount: 0, fingerprint: { hash: "fed456abc123", excerpt: "brand-token-contract: 1\ntransactional-mail-renderer: 1" }, }, ]); expect(report).toContain("@toolctrl/brand package references"); expect(report).toContain("transactional mail renderer contracts"); expect(report).toContain("forbidden user-facing/brand literal drift"); expect(report).toContain("# i18n Hardcoded Strings Baseline Health"); expect(report).toContain("## typecheck"); expect(report).toContain("## hardcoded-string-guard"); expect(report).toContain("## i18n-brand-hardcoded-inventory"); expect(report).toContain("- Exitcode: 2"); expect(report).toContain("- Exitcode: 1"); expect(report).toContain("- Exitcode: 0"); expect(report).toContain("- Failure Count: 7"); expect(report).toContain("- Failure Count: 2"); expect(report).toContain("- Failure Count: 0"); expect(report).toContain("- Output Fingerprint: abc123def456"); expect(report).toContain("- Output Fingerprint: 987654fedcba"); expect(report).toContain("- Output Fingerprint: fed456abc123"); }); });