# test.build/core/validators/catalog.grammar.validator.test.ts

> 91 lines of code and 0 definitions.

Tree: Codebase Testing
Language: typescript
Canonical: https://banes-lab.com/anatomy/testing#file-testing-test-build-core-validators-catalog-grammar-validator-test-ts
Source text: https://banes-lab.com/source/testing/test.build/core/validators/catalog.grammar.validator.test.ts.txt

Listed in [test.build/core/validators](https://banes-lab.com/api/source/testing/test.build/core/validators.md), after [test.build/core/validators/build.validator.test.ts](https://banes-lab.com/source/testing/test.build/core/validators/build.validator.test.ts.md) and before [test.build/core/validators/catalog.kind.validator.test.ts](https://banes-lab.com/source/testing/test.build/core/validators/catalog.kind.validator.test.ts.md).

## Contained in

- [test.build/core/validators](https://banes-lab.com/anatomy/testing/folder-testing-test-build-core-validators.md)

## Source

```typescript
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import {
    catalogFiles,
    grammarFindings,
    literalFindings,
    markdownFormFindings,
    slugFindings,
} from "@banes-lab/build-scripts/core/validators/catalog.grammar.validator.ts";
import { dirname, join } from "node:path";
import { driftedLiteral, markdownMissing } from "@banes-lab/build-scripts/configuration/strings/catalog.strings.ts";
import { mkdirSync, mkdtempSync, rmSync } from "node:fs";
import { writeCanonicalText, writeVerbatim } from "@govlab/canonical-write";
import { GRAMMAR } from "@banes-lab/build-scripts/core/converters/catalog.grammar.converter.ts";
import { tmpdir } from "node:os";

let root = "";

const put = async function put(file: string, text: string): Promise<void> {
    mkdirSync(dirname(join(root, file)), { recursive: true });
    await writeCanonicalText(join(root, file), text);
};

const putText = function putText(file: string, text: string): void {
    mkdirSync(dirname(join(root, file)), { recursive: true });
    writeVerbatim(join(root, file), text);
};

beforeEach(() => {
    root = mkdtempSync(join(tmpdir(), "grammar-"));
});

afterEach(() => {
    rmSync(root, { force: true, recursive: true });
});

describe("catalogFiles", () => {
    it("lists the catalog's JSON files and source texts, and leaves out the route payloads and everything else", async () => {
        await put("json/api.json", "{}\n");
        await put("json/faq.json", "{}\n");
        await put("api.md", "# Api\n");
        putText("source/web/core/x.ts.txt", "export const x = 1;\n");
        putText("robots.txt", "User-agent: *\n");
        expect(catalogFiles(root, new Set(["json/faq.json"]))).toStrictEqual({
            json: ["json/api.json"],
            text: ["source/web/core/x.ts.txt"],
        });
    });
});

describe("grammarFindings", () => {
    it("reports a JSON file no grammar row matches, and every template row the catalog shows no example of", () => {
        const findings = grammarFindings({ json: ["json/api.json", "json/a/b/c/d/e.json"], text: [] });
        expect(findings[0]?.message).toBe(
            "The catalog publishes /json/a/b/c/d/e, which matches no row of the address grammar.",
        );
        const templates = GRAMMAR.filter((row) => row.address.json.includes("{"));
        expect(findings).toHaveLength(1 + templates.length);
    });

    it("takes a published source text as the example of the source text row", () => {
        const withText = grammarFindings({ json: [], text: ["source/web/core/x.ts.txt"] });
        const withoutText = grammarFindings({ json: [], text: [] });
        expect(withText).toHaveLength(withoutText.length - 1);
        expect(withText.some((finding) => finding.file.endsWith("{path}.txt"))).toBe(false);
    });
});

describe("markdownFormFindings", () => {
    it("reports a JSON address kind with no Markdown form, exempts the plain text kind, and passes the real grammar", () => {
        const planted = [
            { address: { json: "/json/api/planted", markdown: null }, kind: "query" as const },
            { address: { json: "/source/{tree}/{path}.txt", markdown: null }, kind: "text" as const },
        ];
        expect(markdownFormFindings(planted).map((finding) => finding.file)).toStrictEqual(["/json/api/planted"]);
        expect(markdownFormFindings()).toStrictEqual([]);
    });
});

describe("slugFindings", () => {
    it("reports a name lookup key the published slug rules would not produce", async () => {
        await put(
            "json/api/slugs/d.json",
            JSON.stringify({ slugs: { "Dual Write": ["x"], "dual-write": ["y"], "dual-writes": ["z"] } }),
        );
        expect(slugFindings(root, ["json/api/slugs/d.json"]).map((finding) => finding.message)).toStrictEqual([
            "The name lookup holds the key Dual Write, which the published slug rules would not produce.",
            "The name lookup holds the key dual-writes, which the published slug rules would not produce.",
        ]);
    });
});

describe("literalFindings", () => {
    it("accepts the site file that carries the declared answer text, and reports one that drifted", () => {
        const file = join(root, "site.conf");
        writeVerbatim(file, `return 404 '${markdownMissing("https://x.test")}\\n';\n`);
        expect(literalFindings("https://x.test", file)).toStrictEqual([]);
        expect(literalFindings("https://other.test", file)).toStrictEqual([{ file, message: driftedLiteral(file) }]);
    });

    it("finds the declared answer text in the real site file", () => {
        expect(literalFindings("https://banes-lab.com")).toStrictEqual([]);
    });
});
```
