test.web/presentation/components/reference.component.test.ts

test.web/presentation/components/reference.component.test.ts is a file in Codebase Testing. 164 lines of code and 0 definitions.

import {
    REFERENCE_ABOVE_CLASS,
    REFERENCE_GAP_PX,
    REFERENCE_PENDING_CLASS,
} from "@banes-lab/web/configuration/constants/reference.constants.ts";
import {
    armLink,
    clampPanel,
    createChapterPanel,
    createRecordPanel,
    placePanel,
} from "@banes-lab/web/presentation/components/reference.component.ts";
import { describe, expect, it, vi } from "vitest";
import { createElement } from "@banes-lab/web/core/factories/element.factory.ts";
import { declaredStyle } from "@banes-lab/web/core/registries/style.registry.ts";
import { hrefOf } from "@banes-lab/web/domain/converters/ontology.converter.ts";

const rectOf = function rectOf(top: number, left: number): DOMRect {
    return {
        bottom: top + 10,
        height: 10,
        left,
        right: left + 10,
        toJSON: () => ({}),
        top,
        width: 10,
        x: left,
        y: top,
    };
};

describe("placePanel", () => {
    it("sits below the anchor when there is room, clamped to the viewport, else above it", () => {
        const panel = createElement("div");
        const anchor = createElement("a");
        vi.spyOn(panel, "offsetWidth", "get").mockReturnValue(200);
        vi.spyOn(panel, "offsetHeight", "get").mockReturnValue(100);
        vi.spyOn(anchor, "getBoundingClientRect").mockReturnValue(rectOf(50, window.innerWidth - 20));
        placePanel(panel, anchor);
        expect(declaredStyle(panel, "top")).toBe(`${String(60 + REFERENCE_GAP_PX)}px`);
        expect(declaredStyle(panel, "left")).toBe(`${String(window.innerWidth - 200 - REFERENCE_GAP_PX)}px`);
        expect(panel.classList.contains(REFERENCE_ABOVE_CLASS)).toBe(false);
        vi.spyOn(anchor, "getBoundingClientRect").mockReturnValue(rectOf(window.innerHeight - 20, 0));
        placePanel(panel, anchor);
        expect(panel.classList.contains(REFERENCE_ABOVE_CLASS)).toBe(true);
        expect(declaredStyle(panel, "top")).toBe(`${String(window.innerHeight - 20 - REFERENCE_GAP_PX - 100)}px`);
        expect(declaredStyle(panel, "left")).toBe(`${String(REFERENCE_GAP_PX)}px`);
    });

    it("clamps a panel that grew past the viewport back inside it", () => {
        const panel = createElement("div");
        vi.spyOn(panel, "offsetWidth", "get").mockReturnValue(200);
        vi.spyOn(panel, "offsetHeight", "get").mockReturnValue(window.innerHeight);
        vi.spyOn(panel, "offsetTop", "get").mockReturnValue(window.innerHeight - 50);
        vi.spyOn(panel, "offsetLeft", "get").mockReturnValue(window.innerWidth);
        clampPanel(panel);
        expect(declaredStyle(panel, "top")).toBe(`${String(REFERENCE_GAP_PX)}px`);
        expect(declaredStyle(panel, "left")).toBe(`${String(window.innerWidth - 200 - REFERENCE_GAP_PX)}px`);
    });
});

describe("armLink", () => {
    it("marks the link pending with a dial and an arrow, and the disposer restores it", () => {
        const link = createElement("a", { attributes: { href: "/ontology#architecture-x" }, text: "X" });
        const disarm = armLink(link);
        expect(link.classList.contains(REFERENCE_PENDING_CLASS)).toBe(true);
        expect(link.querySelectorAll("span")).toHaveLength(2);
        expect(link.querySelector("svg circle")).not.toBeNull();
        disarm();
        expect(link.classList.contains(REFERENCE_PENDING_CLASS)).toBe(false);
        expect(link.querySelectorAll("span")).toHaveLength(0);
        expect(link.textContent).toBe("X");
    });
});

describe("createRecordPanel", () => {
    it("renders the title with its code, the kind with its layer link, the summary and one labeled row per relation", () => {
        const nodes = createRecordPanel({
            code: "SRP",
            kind: "principle",
            layer: { label: "Structural Core", ref: "layer:structural-core" },
            name: "Single Responsibility Principle",
            relations: [
                {
                    edges: [
                        { label: "High Cohesion", ref: "architecture:high-cohesion" },
                        { label: "Unlinked", ref: null },
                    ],
                    relation: "requires",
                },
                { edges: [{ label: "Fail Fast", ref: "architecture:fail-fast" }], relation: "referenced-by" },
            ],
            summary: "One reason to change.",
        });
        const text = nodes.map((node) => node.textContent).join("|");
        expect(text).toContain("Single Responsibility Principle · SRP");
        expect(text).toContain("principle · Structural Core");
        expect(text).toContain("One reason to change.");
        expect(text).toContain("Requires: High Cohesion, Unlinked");
        expect(text).toContain("Referenced by: Fail Fast");
        const links = nodes.flatMap((node) => [...node.querySelectorAll("a")]).map((link) => link.getAttribute("href"));
        expect(links).toStrictEqual([
            hrefOf("layer:structural-core"),
            hrefOf("architecture:high-cohesion"),
            hrefOf("architecture:fail-fast"),
        ]);
    });

    it("renders one labeled row per check question and reads a declared absence as its reason", () => {
        const nodes = createRecordPanel({
            check: {
                answers: {
                    authority: "the declared boundary, which each module conforms to",
                    evidence: "fires: a planted cross-boundary import",
                    freshness: "a verdict stands until the module changes",
                    observation: "none: a cluster is a grouping of principles",
                    population: "every module in the codebase",
                    refusal: "the rule fails the build",
                },
                by: [{ label: "Fail Fast", ref: "architecture:fail-fast" }],
                dependsOn: [],
                shape: [],
            },
            code: null,
            kind: "principle",
            layer: null,
            name: "Modularity",
            relations: [],
            summary: null,
        });
        const text = nodes.map((node) => node.textContent).join("|");
        expect(text).toContain("Checked by: Fail Fast");
        expect(text).toContain("Depends on: Not answered");
        expect(text).toContain("Population: Every module in the codebase");
        expect(text).toContain("Freshness: A verdict stands until the module changes");
        expect(text).toContain("Refusal: The rule fails the build");
        expect(text).toContain("Observation: None, because a cluster is a grouping of principles");
        expect(text).toContain("Evidence: Watched to fire: a planted cross-boundary import");
        expect(text).toContain("Authoritative side: The declared boundary, which each module conforms to");
    });

    it("omits the summary and the relations when the record has none", () => {
        expect(
            createRecordPanel({
                code: null,
                kind: "layer",
                layer: null,
                name: "Structural Core",
                relations: [],
                summary: null,
            }),
        ).toHaveLength(2);
    });
});

describe("createChapterPanel", () => {
    it("renders the chapter title, the chapter kind, the intro and the principle", () => {
        const nodes = createChapterPanel({
            intro: "What a board holds.",
            principle: "State is derived.",
            relations: [{ edges: [{ label: "createElement", ref: null }], relation: "in this codebase" }],
            title: "The board",
        });
        const text = nodes.map((node) => node.textContent).join("|");
        expect(text).toContain("The board");
        expect(text).toContain("Chapter");
        expect(text).toContain("What a board holds.");
        expect(text).toContain("Principle: State is derived.");
        expect(text).toContain("In this codebase: createElement");
        expect(createChapterPanel({ intro: null, principle: null, relations: [], title: "Bare" })).toHaveLength(2);
    });
});