test.build/core/coordinators/catalog.coordinator.test.ts

test.build/core/coordinators/catalog.coordinator.test.ts is a file in Codebase Testing. 162 lines of code and 0 definitions.

import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { mkdtempSync, readFileSync, rmSync } from "node:fs";
import type { Discovery } from "@banes-lab/build-scripts/types/site.types.ts";
import type { GraphNode } from "@banes-lab/web/types/graph.types.ts";
import { buildCatalog } from "@banes-lab/build-scripts/core/coordinators/catalog.coordinator.ts";
import { composeOntology } from "@banes-lab/build-scripts/core/coordinators/ontology.coordinator.ts";
import { createLinker } from "@banes-lab/build-scripts/core/resolvers/link.resolver.ts";
import { join } from "node:path";
import { loadWebModules } from "@banes-lab/build-scripts/core/loaders/catalog.loader.ts";
import { nodeLinks } from "@banes-lab/build-scripts/core/converters/link.converter.ts";
import { recordLeaf } from "@banes-lab/build-scripts/core/resolvers/catalog.resolver.ts";
import { tmpdir } from "node:os";
import { toolsOf } from "@banes-lab/build-scripts/core/converters/source.converter.ts";
import { writeCatalog } from "@banes-lab/build-scripts/core/persistence/catalog.persistence.ts";

const SITE = "https://example.test";

let root = "";

interface Recording {
    readonly importer: { readonly import: <T>(url: string) => Promise<T> };
    readonly seen: readonly string[];
}

const isModule = function isModule<T>(value: unknown): value is T {
    return typeof value === "object" && value !== null;
};

const fileId = function fileId(path: string): string {
    return `file-${path}`;
};

const recording = function recording(): Recording {
    const seen: string[] = [];
    return {
        importer: {
            import: async <T>(url: string): Promise<T> => {
                await Promise.resolve();
                seen.push(url);
                const module: unknown = Object.create(null);
                if (!isModule<T>(module)) {
                    throw new TypeError(url);
                }
                return module;
            },
        },
        seen,
    };
};

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

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

describe("writeCatalog", () => {
    it("writes every address to its file and counts what it wrote", () => {
        const files = new Map([
            ["/json/api", "{}\n"],
            ["/api.md", "# Site\n"],
            ["/json/source/tree/README.md", '{"path":"README.md"}\n'],
        ]);
        expect(writeCatalog(root, files)).toBe(3);
        expect(readFileSync(join(root, "json/api.json"), "utf8")).toBe("{}\n");
        expect(readFileSync(join(root, "api.md"), "utf8")).toBe("# Site\n");
        expect(readFileSync(join(root, "json/source/tree/README.md.json"), "utf8")).toBe('{"path":"README.md"}\n');
    });
});

describe("loadWebModules", () => {
    it("imports each web module the catalog reuses through the runner it is handed", async () => {
        const { importer, seen } = recording();
        const web = await loadWebModules(importer);
        expect(seen.every((url) => url.startsWith("@banes-lab/web/"))).toBe(true);
        expect(new Set(seen).size).toBe(seen.length);
        expect(Object.keys(web)).toHaveLength(seen.length);
    });
});

const chapterNode = function chapterNode(ref: string, href: string): GraphNode {
    return {
        address: null,
        citation: null,
        fields: {},
        href,
        kind: "chapter",
        layer: "site",
        number: null,
        ref,
        title: ref,
    };
};

describe("nodeLinks", () => {
    it("links a chapter edge through the identity at its node's href, and keeps an unknown chapter as a site link", () => {
        const identity = {
            address: recordLeaf("architecture", "a"),
            href: "/p#s",
            kind: "section",
            ref: "section:p-s",
            summary: null,
            title: "Section",
        };
        const linker = createLinker([identity], SITE, (href) => href);
        const nodes = new Map([
            ["chapter:/p#s", chapterNode("chapter:/p#s", "/p#s")],
            ["chapter:/missing", chapterNode("chapter:/missing", "/missing")],
        ]);
        const links = nodeLinks(linker, nodes, [
            { label: "Section", ref: "chapter:/p#s" },
            { label: "Gone", ref: "chapter:/missing" },
        ]);
        expect(links[0]?.ref).toBe("section:p-s");
        expect(links[1]).toStrictEqual({
            href: `${SITE}/missing`,
            json: null,
            label: "Gone",
            markdown: null,
            ref: "chapter:/missing",
        });
    });
});

describe("toolsOf", () => {
    it("gathers the source tools from the web modules that declare them", () => {
        const tools = toolsOf({
            anatomyConstants: { LINE_INFIX: "-L" },
            anatomyIds: { DEFINITION_ANCHOR: "definition-" },
            folder: { localPath: (path) => path },
            source: {
                fileId,
                folderHref: (path) => path,
                folderId: (path) => path,
                languageOf: () => "ts",
                nodeHref: (file) => file,
                nodeRoute: (path) => path,
                sourceTitle: (path) => path,
                treeLabelOf: (tab) => tab,
            },
        });
        expect(tools.lineInfix).toBe("-L");
        expect(tools.definitionAnchor).toBe("definition-");
        expect(tools.fileId).toBe(fileId);
    });
});

describe("buildCatalog", () => {
    it("refuses a page whose id would collide with a catalog segment before it imports anything", async () => {
        const { importer, seen } = recording();
        const page = {
            content: {},
            description: "",
            id: "records",
            label: "",
            markdown: "",
            page: "records",
            path: "/records",
            tab: null,
            title: "",
        };
        const discovery: Discovery = {
            author: "",
            consent: "",
            name: "",
            pages: [page],
            routes: [page],
            site: "",
            summary: "",
        };
        await expect(buildCatalog(importer, discovery, () => [], composeOntology())).rejects.toThrow('"records"');
        expect(seen).toStrictEqual([]);
    });
});