test.rules/rules/eslint/no-literal-strings.eslint.rule.test.ts
test.rules/rules/eslint/no-literal-strings.eslint.rule.test.ts is a file in Codebase Testing. 207 lines of code and 0 definitions.
import { describe, expect, it } from "vitest";
import { Linter } from "eslint";
import noLiteralStrings from "@ssot/govlab/rules/eslint/no-literal-strings.eslint.rule.ts";
const linter = new Linter();
const config: Linter.Config = {
files: ["**/*.ts"],
languageOptions: { ecmaVersion: 2025, sourceType: "module" },
plugins: { local: { rules: { "no-literal-strings": noLiteralStrings } } },
rules: { "local/no-literal-strings": "error" },
};
const SRC_FILE = "src/page.ts";
const lint = function lint(code: string, filename: string): Linter.LintMessage[] {
return linter.verify(code, config, { filename });
};
const hole = function hole(name: string): string {
return ["$", "{", name, "}"].join("");
};
describe("no-literal-strings", () => {
it("fires on el(tag, { text: 'literal' })", () => {
const msgs = lint(`el("p", { text: "Reset to defaults" });`, SRC_FILE);
expect(msgs).toHaveLength(1);
expect(msgs[0]?.messageId).toBe("literalUserVisibleProp");
});
it("fires on el(tag, { ariaLabel: 'literal' })", () => {
const msgs = lint(`el("button", { ariaLabel: "Increase value" });`, SRC_FILE);
expect(msgs).toHaveLength(1);
expect(msgs[0]?.messageId).toBe("literalUserVisibleProp");
});
it("fires on any object with title: 'literal'", () => {
const msgs = lint(`const x = { title: "All done" };`, SRC_FILE);
expect(msgs).toHaveLength(1);
expect(msgs[0]?.messageId).toBe("literalUserVisibleProp");
});
it("fires on any object with label: 'literal'", () => {
const msgs = lint(`const x = { label: "God mode" };`, SRC_FILE);
expect(msgs).toHaveLength(1);
});
it("fires on any object with headline / detail / footer literal", () => {
const msgs = lint(
`const x = { headline: "Slow down", detail: "Take it easy", footer: "Mellow out." };`,
SRC_FILE,
);
expect(msgs).toHaveLength(3);
});
it("passes when text comes from a const", () => {
const msgs = lint(`el("p", { text: PAUSE_OVERLAY_TITLE });`, SRC_FILE);
expect(msgs).toHaveLength(0);
});
it("fires on .textContent = 'literal'", () => {
const msgs = lint(`node.textContent = "Press any key";`, SRC_FILE);
expect(msgs).toHaveLength(1);
expect(msgs[0]?.messageId).toBe("literalTextProperty");
});
it("passes when .textContent = const", () => {
const msgs = lint(`node.textContent = LABEL;`, SRC_FILE);
expect(msgs).toHaveLength(0);
});
it("fires on setAttribute('aria-label', 'literal')", () => {
const msgs = lint(`node.setAttribute("aria-label", "Close dialog");`, SRC_FILE);
expect(msgs).toHaveLength(1);
expect(msgs[0]?.messageId).toBe("literalSetAttribute");
});
it("fires on setAttribute('title', 'literal')", () => {
const msgs = lint(`node.setAttribute("title", "Tooltip");`, SRC_FILE);
expect(msgs).toHaveLength(1);
});
it("does not fire on setAttribute('data-x', 'value') (not user-visible)", () => {
const msgs = lint(`node.setAttribute("data-action", "exit");`, SRC_FILE);
expect(msgs).toHaveLength(0);
});
it("does not fire inside *.strings.ts", () => {
const msgs = lint(`const X = { text: "Literal here" };`, "src/pause.strings.ts");
expect(msgs).toHaveLength(0);
});
it("fires inside page-config.ts (no longer exempt)", () => {
const msgs = lint(
`export const config = { headline: "This page does not exist" };`,
"domain/schemas/base.schema.ts",
);
expect(msgs).toHaveLength(1);
});
it("fires inside *-config.ts files (no longer exempt)", () => {
const msgs = lint(`const x = { label: "Hint" };`, "configuration/constants/absent.constants.ts");
expect(msgs).toHaveLength(1);
});
it("does not fire on registerComponent({ description: 'X' }) (registry metadata)", () => {
const msgs = lint(
`registerComponent({ id: ID, description: "Internal dev description", build: fn });`,
SRC_FILE,
);
expect(msgs).toHaveLength(0);
});
it("does not fire on metadata a call builds under a meta key, and fires on the same call outside one", () => {
const msgs = lint(
[
`const rule = { meta: buildMeta({ description: "Internal rule description" }) };`,
`const card = { body: buildMeta({ description: "Shown on the card" }) };`,
].join("\n"),
SRC_FILE,
);
expect(msgs.map((msg) => msg.line)).toStrictEqual([2]);
});
it("does not fire when key equals value (dispatch table pattern)", () => {
const msgs = lint(
`const TYPE_BY_KIND = { number: "number", text: "text", file: "file" };`,
"src/form-input.component.ts",
);
expect(msgs).toHaveLength(0);
});
it("does not fire on .textContent = '' (empty string clear)", () => {
const msgs = lint(`node.textContent = "";`, SRC_FILE);
expect(msgs).toHaveLength(0);
});
it("does not fire on el(tag, { class: 'literal-class-name' }) (non-renderable prop)", () => {
const msgs = lint(`el("div", { class: "pause-card__title" });`, SRC_FILE);
expect(msgs).toHaveLength(0);
});
it("fires on template literal without expressions (still a static string)", () => {
const msgs = lint("const x = { text: `Static heading` };", SRC_FILE);
expect(msgs).toHaveLength(1);
});
it("fires on a template literal whose text parts carry a word, interpolated or not", () => {
const msgs = lint(`const x = { text: \`Slot \${i}\` };`, SRC_FILE);
expect(msgs).toHaveLength(1);
expect(msgs[0]?.message).toContain("Slot …");
});
it("does not fire on a template literal whose text parts carry no word", () => {
const msgs = lint(`const x = { title: \`\${a}/\${b}.md\` };`, SRC_FILE);
expect(msgs).toHaveLength(0);
});
it("follows a name in a slot to the copy it is bound to, shorthand or not, and reports the binding", () => {
const msgs = lint(
[
"const make = (c) => {",
[" const title = `", "{c.title} closure`;"].join("$"),
" const label = LABEL;",
" return [{ title }, { label }, { text: title }];",
"};",
].join("\n"),
SRC_FILE,
);
expect(msgs.map((msg) => msg.line)).toStrictEqual([2, 2]);
expect(msgs[0]?.message).toContain("… closure");
});
it("does not follow a name bound to a value that carries no copy", () => {
const msgs = lint(`const f = (id) => { const title = id; return { title }; };`, SRC_FILE);
expect(msgs).toHaveLength(0);
});
it("fires on the message of a constructed error, static or interpolated, followed through a binding", () => {
const msgs = lint(
[
'throw new Error("The report is missing.");',
`throw new TypeError(\`No grammar for ${hole("name")}.\`);`,
`const reason = \`The step ${hole("step")} failed.\`;`,
"throw new RangeError(reason);",
].join("\n"),
SRC_FILE,
);
expect(msgs.map((msg) => msg.messageId)).toStrictEqual([
"literalErrorMessage",
"literalErrorMessage",
"literalErrorMessage",
]);
});
it("fires on a literal written to a terminal stream or the console, and passes one built from a strings export", () => {
const msgs = lint(
[
String.raw`process.stdout.write("Wrote the report.\n");`,
`process.stderr.write(\`The step ${hole("step")} failed.\\n\`);`,
'console.warn("Nothing was written.");',
"process.stdout.write(wroteLine(count));",
`process.stdout.write(\`${hole("a")}\\n\`);`,
].join("\n"),
SRC_FILE,
);
expect(msgs.map((msg) => msg.messageId)).toStrictEqual([
"literalTerminalWrite",
"literalTerminalWrite",
"literalTerminalWrite",
]);
});
it("fires on copy assembled by concatenation, and passes a concatenation with no word in it", () => {
const msgs = lint(
[
'throw new Error("The step " + step + " failed.");',
`process.stdout.write(\`${hole("a")} \` + \`scanned=${hole("b")}\\n\`);`,
'process.stdout.write(a + ":" + b);',
].join("\n"),
SRC_FILE,
);
expect(msgs.map((msg) => msg.messageId)).toStrictEqual(["literalErrorMessage", "literalTerminalWrite"]);
});
it("does not fire on an error built from a strings export or on a constructor that is not an error", () => {
const msgs = lint(
['throw new Error(reportMissing("r.json"));', 'const map = new Map([["a", "Some words"]]);'].join("\n"),
SRC_FILE,
);
expect(msgs).toHaveLength(0);
});
it("fires on a finding message, static or interpolated", () => {
const msgs = lint(
`const f = [{ file, message: "The build produced no report." }, { file, message: \`Links to \${x}.\` }];`,
SRC_FILE,
);
expect(msgs).toHaveLength(2);
});
});