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);
    });
});