Files
kandev/apps/web/scripts/fix-trans-indices.test.ts
T
2026-08-06 13:59:36 +02:00

136 lines
4.4 KiB
TypeScript

import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import path from "node:path";
import { spawnSync } from "node:child_process";
import { describe, expect, it } from "vitest";
const SCRIPT = path.resolve(import.meta.dirname, "fix-trans-indices.mjs");
type Fixture = Record<string, string>;
interface Run {
status: number | null;
stdout: string;
stderr: string;
/** The trailing JSON summary, or null when the script exited before printing one. */
report: Record<string, number | boolean> | null;
}
/**
* A `<Trans>` whose message carries two tags but whose JSX has one element child.
* The script cannot know which phrase belongs to the element, so it declines and
* reports — a deterministic finding to assert on.
*/
function declining(key: string): string {
return [
'import { Trans } from "react-i18next";',
"",
"export const Line = () => (",
` <Trans i18nKey="common:${key}">`,
" Hi <b>there</b>",
" </Trans>",
");",
].join("\n");
}
const CATALOG = JSON.stringify({
greetingA: "Hi <0>there</0> and <1>you</1>",
greetingB: "Hi <0>there</0> and <1>you</1>",
});
const LOOSE = "loose.tsx";
const SUBDIR = "nested";
const TREE: Fixture = {
"src/locales/en/common.json": CATALOG,
[LOOSE]: declining("greetingA"),
[`${SUBDIR}/other.tsx`]: declining("greetingB"),
};
function withFixture<T>(fixture: Fixture, use: (root: string) => T): T {
const root = mkdtempSync(path.join(tmpdir(), "kandev-trans-indices-"));
try {
for (const [file, source] of Object.entries(fixture)) {
const full = path.join(root, file);
mkdirSync(path.dirname(full), { recursive: true });
writeFileSync(full, source);
}
return use(root);
} finally {
rmSync(root, { recursive: true, force: true });
}
}
/** Run against `root`'s fixture catalog so the real `src/locales/en` is never read. */
function run(root: string | null, ...args: string[]): Run {
const env = { ...process.env };
if (root) env.KANDEV_I18N_EN_DIR = path.join(root, "src", "locales", "en");
const result = spawnSync(process.execPath, [SCRIPT, ...args], { encoding: "utf8", env });
const start = result.stdout.indexOf("{");
return {
status: result.status,
stdout: result.stdout,
stderr: result.stderr,
report: start === -1 ? null : JSON.parse(result.stdout.slice(start)),
};
}
/**
* The regression this suite exists for. The script walked directories only, so a
* file argument threw ENOTDIR mid-walk and a non-existent one was skipped
* silently — both ending at a `{"fixed": 0}` summary that reads as a verdict on
* code the script never opened.
*/
describe("path arguments", () => {
it("reports the findings in a file named directly", () => {
const result = withFixture(TREE, (root) => run(root, path.join(root, LOOSE)));
expect(result.status).toBe(0);
expect(result.report?.scanned).toBe(1);
expect(result.report?.declinedCount).toBe(1);
expect(result.stdout).toContain(LOOSE);
expect(result.stdout).toContain("common:greetingA");
});
it("reports the union of file and directory arguments", () => {
const result = withFixture(TREE, (root) =>
run(root, path.join(root, SUBDIR), path.join(root, LOOSE)),
);
expect(result.report?.scanned).toBe(2);
expect(result.report?.declinedCount).toBe(2);
expect(result.stdout).toContain("common:greetingA");
expect(result.stdout).toContain("common:greetingB");
});
it("counts a file reached through two arguments once", () => {
const result = withFixture(TREE, (root) => run(root, root, path.join(root, LOOSE)));
expect(result.report?.scanned).toBe(2);
expect(result.report?.declinedCount).toBe(2);
});
it("exits non-zero on a path that does not exist rather than reporting clean", () => {
const result = run(null, "does/not/exist");
expect(result.status).not.toBe(0);
expect(result.stderr).toContain("does/not/exist");
expect(result.report).toBeNull();
});
it("prints how many files were scanned, and from how many arguments", () => {
const result = withFixture(TREE, (root) =>
run(root, path.join(root, SUBDIR), path.join(root, LOOSE)),
);
expect(result.stdout).toContain("scanned 2 file(s) from 2 argument(s)");
});
it("leaves the catalog alone without --write", () => {
const result = withFixture(TREE, (root) => run(root, path.join(root, LOOSE)));
expect(result.report?.wrote).toBe(false);
});
});