Initial Commit
This commit is contained in:
171
packages/ui/react/react-ui/scripts/check-i18n-keys.test.ts
Normal file
171
packages/ui/react/react-ui/scripts/check-i18n-keys.test.ts
Normal file
@@ -0,0 +1,171 @@
|
||||
/**
|
||||
* Tests for `check-i18n-keys.ts`.
|
||||
*
|
||||
* Build a fixtures filesystem on disk under `mkdtemp` and call the
|
||||
* exported helpers (`extractKeysFromSource`, `loadBundleKeys`,
|
||||
* `diff`) directly. The script's `main()` integrates with
|
||||
* `process.argv` / `process.exit` so it is exercised end-to-end via
|
||||
* a `Bun.spawn` round-trip in the last test.
|
||||
*/
|
||||
|
||||
import { afterEach, beforeEach, describe, expect, it } from "@suaveplan/testing/runner";
|
||||
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import {
|
||||
diff,
|
||||
extractKeysFromSource,
|
||||
loadBundleKeys,
|
||||
} from "./check-i18n-keys.js";
|
||||
|
||||
let workDir: string;
|
||||
|
||||
beforeEach(async () => {
|
||||
workDir = await mkdtemp(join(tmpdir(), "i18n-keys-"));
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await rm(workDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
async function writeFixture(rel: string, content: string): Promise<string> {
|
||||
const full = join(workDir, rel);
|
||||
await mkdir(join(full, ".."), { recursive: true });
|
||||
await writeFile(full, content, "utf8");
|
||||
return full;
|
||||
}
|
||||
|
||||
describe("extractKeysFromSource", () => {
|
||||
it("captures t() calls with double quotes", { timeout: 5000 }, async () => {
|
||||
const f = await writeFixture(
|
||||
"a.tsx",
|
||||
`const out = t("react-ui.foo.bar");`
|
||||
);
|
||||
const { keys } = await extractKeysFromSource([f], workDir);
|
||||
expect(keys.has("react-ui.foo.bar")).toBe(true);
|
||||
});
|
||||
|
||||
it("captures t() calls with single quotes", { timeout: 5000 }, async () => {
|
||||
const f = await writeFixture(
|
||||
"a.tsx",
|
||||
`const out = t('react-ui.foo.bar');`
|
||||
);
|
||||
const { keys } = await extractKeysFromSource([f], workDir);
|
||||
expect(keys.has("react-ui.foo.bar")).toBe(true);
|
||||
});
|
||||
|
||||
it("captures i18nKey JSX attribute", { timeout: 5000 }, async () => {
|
||||
const f = await writeFixture(
|
||||
"a.tsx",
|
||||
`<FormattedMessage i18nKey="react-ui.greeting" />`
|
||||
);
|
||||
const { keys } = await extractKeysFromSource([f], workDir);
|
||||
expect(keys.has("react-ui.greeting")).toBe(true);
|
||||
});
|
||||
|
||||
it("captures the DEFAULT_*_KEY const idiom", { timeout: 5000 }, async () => {
|
||||
const f = await writeFixture(
|
||||
"a.tsx",
|
||||
`const DEFAULT_LABEL_KEY = "react-ui.thing.label";`
|
||||
);
|
||||
const { keys } = await extractKeysFromSource([f], workDir);
|
||||
expect(keys.has("react-ui.thing.label")).toBe(true);
|
||||
});
|
||||
|
||||
it("captures `?? \"react-ui.…\"` fallback shorthand", {
|
||||
timeout: 5000,
|
||||
}, async () => {
|
||||
const f = await writeFixture(
|
||||
"a.tsx",
|
||||
`const k = labelKey ?? "react-ui.fallback.label";`
|
||||
);
|
||||
const { keys } = await extractKeysFromSource([f], workDir);
|
||||
expect(keys.has("react-ui.fallback.label")).toBe(true);
|
||||
});
|
||||
|
||||
it("records the source location for each key", {
|
||||
timeout: 5000,
|
||||
}, async () => {
|
||||
const f = await writeFixture(
|
||||
"deep/nested.tsx",
|
||||
`// header\nconst k = t("react-ui.line.two");`
|
||||
);
|
||||
const { keys } = await extractKeysFromSource([f], workDir);
|
||||
expect(keys.get("react-ui.line.two")).toMatch(/deep\/nested\.tsx:2$/);
|
||||
});
|
||||
|
||||
it("ignores non-namespace strings", { timeout: 5000 }, async () => {
|
||||
const f = await writeFixture(
|
||||
"a.tsx",
|
||||
`const k = t("other.namespace.key");`
|
||||
);
|
||||
const { keys } = await extractKeysFromSource([f], workDir);
|
||||
expect(keys.size).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("loadBundleKeys", () => {
|
||||
it("loads only keys in the react-ui namespace", {
|
||||
timeout: 5000,
|
||||
}, async () => {
|
||||
const path = join(workDir, "bundle.json");
|
||||
await writeFile(
|
||||
path,
|
||||
JSON.stringify({
|
||||
"react-ui.foo": "Foo",
|
||||
"react-ui.bar": "Bar",
|
||||
"other.namespace.key": "other",
|
||||
}),
|
||||
"utf8"
|
||||
);
|
||||
const keys = await loadBundleKeys(path);
|
||||
expect(keys.has("react-ui.foo")).toBe(true);
|
||||
expect(keys.has("react-ui.bar")).toBe(true);
|
||||
expect(keys.has("other.namespace.key")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("diff", () => {
|
||||
it("reports MISSING for code-only keys", { timeout: 5000 }, () => {
|
||||
const codeKeys = new Map([["react-ui.code.only", "src/x.tsx:1"]]);
|
||||
const bundleKeys = new Set(["react-ui.bundled"]);
|
||||
const diags = diff(codeKeys, bundleKeys);
|
||||
expect(
|
||||
diags.find((d) => d.kind === "MISSING")?.key
|
||||
).toBe("react-ui.code.only");
|
||||
});
|
||||
|
||||
it("reports ORPHAN for bundle-only keys", { timeout: 5000 }, () => {
|
||||
const codeKeys = new Map<string, string>();
|
||||
const bundleKeys = new Set(["react-ui.bundle.only"]);
|
||||
const diags = diff(codeKeys, bundleKeys);
|
||||
expect(
|
||||
diags.find((d) => d.kind === "ORPHAN")?.key
|
||||
).toBe("react-ui.bundle.only");
|
||||
});
|
||||
|
||||
it("returns empty diagnostics on a clean source/bundle pair", {
|
||||
timeout: 5000,
|
||||
}, () => {
|
||||
const codeKeys = new Map([["react-ui.k", "src/x.tsx:1"]]);
|
||||
const bundleKeys = new Set(["react-ui.k"]);
|
||||
expect(diff(codeKeys, bundleKeys)).toEqual([]);
|
||||
});
|
||||
|
||||
it("orders MISSING before ORPHAN, then alphabetically", {
|
||||
timeout: 5000,
|
||||
}, () => {
|
||||
const codeKeys = new Map([
|
||||
["react-ui.b", "x:1"],
|
||||
["react-ui.a", "x:2"],
|
||||
]);
|
||||
const bundleKeys = new Set(["react-ui.zzz", "react-ui.aaa"]);
|
||||
const diags = diff(codeKeys, bundleKeys);
|
||||
expect(diags.map((d) => `${d.kind}:${d.key}`)).toEqual([
|
||||
"MISSING:react-ui.a",
|
||||
"MISSING:react-ui.b",
|
||||
"ORPHAN:react-ui.aaa",
|
||||
"ORPHAN:react-ui.zzz",
|
||||
]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user