test.rules/rules/eslint/closure-no-browser-derivation.eslint.rule.test.ts

test.rules/rules/eslint/closure-no-browser-derivation.eslint.rule.test.ts is a file in Codebase Testing. 69 lines of code and 0 definitions.

import { describe, expect, it, vi } from "vitest";
import type { ClosureGraph } from "@ssot/govlab/types/closure.types.ts";
import { Linter } from "eslint";
import { absolutePath } from "@ssot/paths";
import browserDerivation from "@ssot/govlab/rules/eslint/closure-no-browser-derivation.eslint.rule.ts";
import { join } from "node:path";

const NAMED_ENTRY = "runtime/entrypoints/named.entrypoint.ts";
const SIDE_ENTRY = "runtime/entrypoints/side.entrypoint.ts";
const UNREACHED = "presentation/widgets/unreached.widget.ts";

const { converter, matcher, stubGraph } = await vi.hoisted(async () => {
    const { DERIVATION_MODULES } = await import("@ssot/govlab/shared/manifests/layer.manifest.ts");
    const declared = (name: string): string => [...DERIVATION_MODULES].find((path) => path.endsWith(name)) ?? name;
    const matcherPath = declared("vocabulary.matcher.ts");
    const converterPath = declared("definition.converter.ts");
    const graph: ClosureGraph = {
        consumers: [],
        emits: [],
        eventActivity: [],
        exports: [{ file: "runtime/entrypoints/side.entrypoint.ts", name: "probe" }],
        externalConsumers: [],
        iconsExports: [],
        idsExports: [],
        imports: [
            { file: "runtime/entrypoints/named.entrypoint.ts", from: `../../${matcherPath}`, names: ["matchPhrases"] },
            { file: "runtime/entrypoints/named.entrypoint.ts", from: `../../${converterPath}`, names: ["probe"] },
            { file: converterPath, from: `../../${matcherPath}`, names: ["matchPhrases"] },
            { file: "presentation/widgets/unreached.widget.ts", from: `../../${matcherPath}`, names: ["matchPhrases"] },
        ],
        interfaces: [],
        registers: [],
        sideEffectImports: [{ file: "runtime/entrypoints/side.entrypoint.ts", from: `../../${matcherPath}` }],
        stringsExports: [],
        subscribes: [],
        version: 1,
    };
    return { converter: converterPath, matcher: matcherPath, stubGraph: graph };
});

vi.mock("@ssot/govlab/shared/loaders/graph.loader.ts", async (importOriginal) => ({
    ...(await importOriginal<Record<string, unknown>>()),
    loadClosureGraph: () => stubGraph,
}));

const linter = new Linter();
const config: Linter.Config = {
    files: ["**/*.ts"],
    languageOptions: { ecmaVersion: 2025, sourceType: "module" },
    plugins: { local: { rules: { "closure-no-browser-derivation": browserDerivation } } },
    rules: { "local/closure-no-browser-derivation": "error" },
};

const lint = function lint(member: string): Linter.LintMessage[] {
    return linter.verify("export const probe = 1;\n", config, { filename: join(absolutePath("app.member"), member) });
};

describe("closure-no-browser-derivation", () => {
    it("reports a browser file that names an import from a declared derivation module", () => {
        const messages = lint(NAMED_ENTRY);
        expect(messages.map((message) => message.messageId)).toStrictEqual(["browserDerivation", "browserDerivation"]);
        expect(messages[0]?.message).toContain(matcher);
    });

    it("reports a browser file that imports a declared derivation module for its side effects", () => {
        const messages = lint(SIDE_ENTRY);
        expect(messages.map((message) => message.messageId)).toStrictEqual(["browserDerivation"]);
        expect(messages[0]?.message).toContain(matcher);
    });

    it("passes a file the browser bundle does not reach", () => {
        expect(lint(UNREACHED)).toStrictEqual([]);
    });

    it("passes a declared derivation module that imports another, so the finding lands at the boundary", () => {
        expect(lint(converter)).toStrictEqual([]);
    });
});