test.web/presentation/renderers/ontology.renderer.test.ts

test.web/presentation/renderers/ontology.renderer.test.ts is a file in Codebase Testing. 157 lines of code and 0 definitions.

import { describe, expect, it } from "vitest";
import { hasOwnSections, hasSectionPages } from "@banes-lab/web/presentation/renderers/tab.renderer.ts";
import { recordLinkText, sectionPageLabel } from "@banes-lab/web/configuration/strings/ontology.strings.ts";
import {
    renderExplorer,
    renderExplorerSection,
    rowCount,
    rowWeights,
} from "@banes-lab/web/presentation/renderers/ontology.renderer.ts";
import { ONTOLOGY_PRINCIPLES_TAB } from "@banes-lab/web/core/ids/ontology.ids.ts";
import { ONTOLOGY_TABS } from "@banes-lab/web/configuration/strings/ontology.fragment.strings.ts";
import { SEARCH_ATTRIBUTE } from "@banes-lab/web/configuration/constants/ontology.constants.ts";
import { STATIC_RENDER_ATTRIBUTE } from "@banes-lab/web/configuration/constants/document.constants.ts";
import type { Tab } from "@banes-lab/web/types/document.types.ts";
import { registerRecords } from "@banes-lab/web/domain/registries/record.registry.ts";

const principlesTab = function principlesTab(): Tab {
    const principles = ONTOLOGY_TABS.find((tab) => tab.id === ONTOLOGY_PRINCIPLES_TAB);
    if (principles === undefined) {
        throw new Error("no principles tab");
    }
    return principles;
};

const DIAGRAM = principlesTab().sections[0]?.blocks?.find((block) => block.kind === "mermaid");
const ROW_BODY = [{ entries: [{ description: "Kept.", term: "Rule" }], kind: "glossary" as const }];
const LONG_VALUE = "A rule that runs on well past the width one card can hold without stacking. ".repeat(3);

const FIXTURE: Tab = {
    icon: "",
    id: "fixture",
    label: "Fixture",
    sections: [
        {
            blocks: DIAGRAM === undefined ? [] : [DIAGRAM],
            icon: "",
            id: "group-one",
            intro: "One.",
            subsections: [
                {
                    blocks: [
                        { items: ["Kind: principle", "Severity: mandatory"], kind: "list" },
                        {
                            entries: [
                                { description: "High Cohesion", term: "Requires" },
                                { description: LONG_VALUE, term: "Rule" },
                            ],
                            kind: "glossary",
                        },
                    ],
                    id: "architecture-alpha",
                    title: "Alpha",
                },
                {
                    blocks: [
                        {
                            entries: [
                                { description: "First.", term: "one" },
                                { description: "Second.", term: "two" },
                            ],
                            kind: "glossary",
                        },
                    ],
                    title: "Entries",
                },
            ],
            title: "Group one",
        },
    ],
};

describe("renderExplorer", () => {
    const article = renderExplorer(FIXTURE);

    it("renders a search box, a category navigator and one record per subsection or catalog entry", () => {
        expect(article.querySelector(".filter-box")).not.toBeNull();
        expect(article.querySelectorAll(".explorer-nav-list a")).toHaveLength(1);
        expect(article.querySelectorAll(".record")).toHaveLength(3);
        expect(article.querySelector("#architecture-alpha .record-chip")?.textContent).toBe("Kind: principle");
        expect(article.querySelector("#architecture-alpha")?.getAttribute(SEARCH_ATTRIBUTE)).toContain("high cohesion");
        expect(article.querySelector(".explorer-count")?.textContent).toBe("3 of 3 shown");
    });

    it("renders a record's body only once its details open, as field cards, widening a card whose value runs long", () => {
        const details = article.querySelector("#architecture-alpha details");
        expect(article.querySelectorAll("#architecture-alpha .record-field")).toHaveLength(0);
        details?.dispatchEvent(new Event("toggle"));
        const cards = article.querySelectorAll("#architecture-alpha .record-field");
        expect(cards).toHaveLength(2);
        expect(cards[0]?.classList.contains("record-field-wide")).toBe(false);
        expect(cards[1]?.classList.contains("record-field-wide")).toBe(true);
        details?.dispatchEvent(new Event("toggle"));
        expect(article.querySelectorAll("#architecture-alpha .record-field")).toHaveLength(2);
    });

    it("keeps the diagram in a panel behind a collapsed summary, rendered on first open", () => {
        const details = article.querySelector(".explorer-group > .record-details");
        expect(details?.hasAttribute("open")).toBe(false);
        expect(details?.querySelector(".chapter-panel")).toBeNull();
        details?.dispatchEvent(new Event("toggle"));
        expect(details?.querySelector(".chapter-panel .diagram-figure")).not.toBeNull();
    });

    it("renders a published record row linked to its record page for a static render, and the other bodies eagerly", () => {
        registerRecords(["architecture:alpha", "architecture:information"]);
        const information = { blocks: [], id: "architecture-information", title: "Information" };
        const unpublished = { blocks: [{ items: ["Kind: shape"], kind: "list" as const }, ...ROW_BODY], id: "architecture-beta", title: "Beta" };
        const [group] = FIXTURE.sections;
        const sections = group === undefined ? [] : [{ ...group, subsections: [...group.subsections, information, unpublished] }];
        document.documentElement.setAttribute(STATIC_RENDER_ATTRIBUTE, "");
        const tab = renderExplorer({ ...FIXTURE, sections });
        const rendered = renderExplorerSection({ ...FIXTURE, sections }, "group-one");
        document.documentElement.removeAttribute(STATIC_RENDER_ATTRIBUTE);
        if (rendered === null) {
            throw new Error("no section");
        }
        expect(renderExplorerSection(FIXTURE, "group-none")).toBeNull();
        expect(hasSectionPages("ontology", FIXTURE)).toBe(true);
        expect(hasSectionPages("tree", FIXTURE)).toBe(false);
        expect(hasOwnSections("tree", FIXTURE)).toBe(true);
        expect(hasOwnSections("chapter", FIXTURE)).toBe(false);
        expect(tab.querySelector(".record")).toBeNull();
        expect(tab.querySelector(".filter-box")).toBeNull();
        expect(tab.querySelector(".explorer-section-link a")?.getAttribute("href")).toBe("/ontology/fixture/group-one");
        expect(tab.querySelector(".explorer-section-link a")?.textContent).toBe(sectionPageLabel(5, "Group one"));
        expect(rowCount(sections[0] ?? { icon: "", id: "", subsections: [], title: "" })).toBe(5);
        const weights = rowWeights(sections[0] ?? { icon: "", id: "", subsections: [], title: "" });
        expect(weights).toHaveLength(5);
        expect(weights.every((weight) => weight > 0)).toBe(true);
        expect(rendered.querySelector("#architecture-alpha .record-title a")?.getAttribute("href")).toBe(
            "/records/architecture/alpha",
        );
        expect(rendered.querySelector("#architecture-information .record-title a")?.textContent).toBe(
            recordLinkText("Information", "Group one"),
        );
        expect(recordLinkText("START", "Keywords")).toBe("START (Keywords)");
        expect(rendered.querySelector("#architecture-beta .record-title a")).toBeNull();
        expect(rendered.querySelectorAll("#architecture-beta .record-field")).toHaveLength(1);
        expect(rendered.querySelectorAll("#architecture-alpha .record-field")).toHaveLength(0);
        expect(rendered.querySelector("#architecture-alpha")?.hasAttribute("data-search")).toBe(false);
        expect(rendered.querySelector(".record-details .chapter-panel")).not.toBeNull();
    });

    it("filters records and groups by the typed query", () => {
        const search = article.querySelector<HTMLInputElement>(".filter-box");
        if (search === null) {
            throw new Error("no search box");
        }
        search.value = "second";
        search.dispatchEvent(new Event("input"));
        const records = [...article.querySelectorAll<HTMLElement>(".record")];
        expect(records.filter((record) => record.hidden === false).map((record) => record.id)).toStrictEqual([
            "group-one-sub-1-sub-1",
        ]);
        expect(article.querySelector(".explorer-count")?.textContent).toBe("1 of 3 shown");
        search.value = "";
        search.dispatchEvent(new Event("input"));
        expect(records.every((record) => record.hidden === false)).toBe(true);
    });

    it("renders the real principles tab with one record per principle", () => {
        const principles = principlesTab();
        const rendered = renderExplorer(principles);
        const expected = principles.sections.reduce((total, section) => total + section.subsections.length, 0);
        expect(rendered.querySelectorAll(".record")).toHaveLength(expected);
    });
});