Files
kandev/apps/web/scripts/check-no-em-dash-ui.test.ts
2026-08-12 11:13:25 +01:00

133 lines
5.0 KiB
TypeScript

import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
import os from "node:os";
import path from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import {
EM_DASH,
containsEmDash,
findPublicDocViolations,
findSourceViolations,
scanUiEmDashViolations,
stripCommentsPreservingStrings,
} from "./check-no-em-dash-ui.mjs";
const temporaryDirectories: string[] = [];
const TEST_REPO_ROOT = "/repo";
const PUBLIC_DOC_FILE = `${TEST_REPO_ROOT}/docs/public/guide.md`;
const PUBLIC_DOC_RELATIVE_FILE = "docs/public/guide.md";
const PUBLIC_DOC_KIND = "public-doc";
afterEach(() => {
for (const directory of temporaryDirectories.splice(0)) {
rmSync(directory, { recursive: true, force: true });
}
});
describe("check-no-em-dash-ui", () => {
it("ignores em dashes in comments but preserves string content", () => {
const source = [
`// ignored ${EM_DASH}`,
`const copy = "visible ${EM_DASH}"; /* ignored ${EM_DASH} */`,
].join("\n");
expect(stripCommentsPreservingStrings(source)).not.toContain(`ignored ${EM_DASH}`);
expect(findSourceViolations(source, "/repo/apps/web/components/example.tsx", "/repo")).toEqual([
{ kind: "source", file: "apps/web/components/example.tsx", line: 2 },
]);
});
it("recognizes escaped em dashes that render from source literals", () => {
expect(containsEmDash(String.raw`visible \u2014 copy`)).toBe(true);
});
it("ignores non-rendered Markdown and MDX comments", () => {
const source = [
`<!-- hidden ${EM_DASH} HTML comment -->`,
"<!-- hidden",
`${EM_DASH} multiline HTML comment`,
"-->",
"Visible copy",
`{/* hidden ${EM_DASH} MDX comment */}`,
`Visible ${EM_DASH} copy`,
].join("\n");
expect(findPublicDocViolations(source, PUBLIC_DOC_FILE, TEST_REPO_ROOT)).toEqual([
{ kind: PUBLIC_DOC_KIND, file: PUBLIC_DOC_RELATIVE_FILE, line: 7 },
]);
});
it("checks actual em dashes instead of source escapes in public docs", () => {
const source = ["JSON escape \\u2014 stays literal.", `Visible ${EM_DASH} copy`].join("\n");
expect(findPublicDocViolations(source, PUBLIC_DOC_FILE, TEST_REPO_ROOT)).toEqual([
{ kind: PUBLIC_DOC_KIND, file: PUBLIC_DOC_RELATIVE_FILE, line: 2 },
]);
});
it("does not treat comment markers in Markdown code as comments", () => {
const source = [
"Inline code `<!-- comment syntax` remains code.",
"Inline MDX `{/* comment syntax` remains code.",
"```md",
"<!-- comment syntax",
"{/* comment syntax",
"```",
`Visible ${EM_DASH} copy`,
].join("\n");
expect(findPublicDocViolations(source, PUBLIC_DOC_FILE, TEST_REPO_ROOT)).toEqual([
{ kind: PUBLIC_DOC_KIND, file: PUBLIC_DOC_RELATIVE_FILE, line: 7 },
]);
});
it("ignores regex literals while stripping comments", () => {
const source = [
"const pattern = /``` " + EM_DASH + "/;",
`// ignored ${EM_DASH}`,
`const copy = "visible ${EM_DASH}";`,
].join("\n");
expect(findSourceViolations(source, "/repo/apps/web/components/example.tsx", "/repo")).toEqual([
{ kind: "source", file: "apps/web/components/example.tsx", line: 3 },
]);
});
it("scans locale values and rendered source, not changelog history", () => {
const repoRoot = mkdtempSync(path.join(os.tmpdir(), "kandev-em-dash-"));
temporaryDirectories.push(repoRoot);
const webRoot = path.join(repoRoot, "apps", "web");
mkdirSync(path.join(webRoot, "src", "locales", "en"), { recursive: true });
mkdirSync(path.join(webRoot, "components"), { recursive: true });
mkdirSync(path.join(repoRoot, "apps", "backend", "cmd", "mock-agent"), {
recursive: true,
});
mkdirSync(path.join(repoRoot, "docs", "public"), { recursive: true });
writeFileSync(
path.join(webRoot, "src", "locales", "en", "settings.json"),
JSON.stringify({ label: `Bad ${EM_DASH} copy` }),
);
writeFileSync(
path.join(webRoot, "components", "settings.tsx"),
`export const label = "Bad ${EM_DASH} copy";\n`,
);
writeFileSync(
path.join(repoRoot, "apps", "backend", "cmd", "mock-agent", "handler.go"),
`package main\n// ignored ${EM_DASH}\nconst copy = "Bad ${EM_DASH} copy"\n`,
);
writeFileSync(path.join(repoRoot, "docs", "public", "guide.md"), `# Public ${EM_DASH} guide\n`);
writeFileSync(
path.join(repoRoot, "docs", "public", "guide.mdx"),
`# Public ${EM_DASH} MDX guide\n`,
);
writeFileSync(path.join(repoRoot, "CHANGELOG.md"), `- Bad ${EM_DASH} release note\n`);
expect(scanUiEmDashViolations({ repoRoot, webRoot })).toEqual([
{ kind: "catalog", file: "apps/web/src/locales/en/settings.json", key: "label" },
{ kind: "source", file: "apps/web/components/settings.tsx", line: 1 },
{ kind: "source", file: "apps/backend/cmd/mock-agent/handler.go", line: 3 },
{ kind: PUBLIC_DOC_KIND, file: "docs/public/guide.md", line: 1 },
{ kind: PUBLIC_DOC_KIND, file: "docs/public/guide.mdx", line: 1 },
]);
});
});