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

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

import { afterEach, describe, expect, it } from "vitest";
import {
    ancestorsOf,
    flatten,
    isFolder,
    markSelected,
    renderTree,
    treeLink,
    treeNodesOf,
} from "@banes-lab/web/presentation/components/folder.component.ts";
import { ACTIVE_CLASS } from "@banes-lab/web/configuration/constants/element.constants.ts";
import { NODE_ATTRIBUTE } from "@banes-lab/web/configuration/constants/anatomy.constants.ts";
import { ROOT_LABEL } from "@banes-lab/web/configuration/strings/folder.strings.ts";
import type { Section } from "@banes-lab/web/types/document.types.ts";
import { isGenericLabel } from "@banes-lab/web/core/predicates/link.predicate.ts";
import { targetOf } from "@banes-lab/web/core/converters/folder.converter.ts";

const ICON = "bi-folder";
const CORE = "core";
const ASSETS = "assets";
const CORE_ASSETS = [CORE, ASSETS].join("/");

const section = function section(title: string, id: string, files: readonly string[]): Section {
    return { icon: ICON, id, subsections: files.map((name) => ({ id: `file-${name}`, title: name })), title };
};

const SECTIONS: readonly Section[] = [
    section(ROOT_LABEL, "folder-root", ["index.html"]),
    section(CORE, "folder-core", []),
    section(CORE_ASSETS, "folder-core-assets", ["link.assets.ts", "walk.assets.ts"]),
    section("types", "folder-types", ["base.types.ts"]),
];

const FOLDERS: Readonly<Record<string, readonly string[]>> = {
    "folder-core": ["folder-core-assets"],
    "folder-root": ["folder-core", "folder-types"],
};

const tree = function tree(): ReturnType<typeof treeNodesOf> {
    return treeNodesOf(SECTIONS, FOLDERS);
};

afterEach(() => {
    document.body.replaceChildren();
});

describe("treeNodesOf", () => {
    it("nests the sections into folders by the folder map, and answers null for no sections", () => {
        const root = tree();
        expect(root?.name).toBe(ROOT_LABEL);
        expect(root?.folders.map((folder) => folder.name)).toStrictEqual(["core", "types"]);
        expect(root?.folders[0]?.folders[0]?.path).toBe(CORE_ASSETS);
        expect(root?.folders[0]?.folders[0]?.files.map((file) => file.name)).toStrictEqual([
            "link.assets.ts",
            "walk.assets.ts",
        ]);
        expect(root?.files[0]?.id).toBe("file-index.html");
        expect(treeNodesOf([], FOLDERS)).toBeNull();
        expect(treeNodesOf(SECTIONS, {})?.folders).toStrictEqual([]);
    });
});

describe("isFolder, ancestorsOf and flatten", () => {
    it("tells folders from files, walks the ancestor chain of a path, and lists every node", () => {
        const root = tree();
        if (root === null) {
            throw new Error(ROOT_LABEL);
        }
        const assets = root.folders[0]?.folders[0];
        expect(isFolder(root)).toBe(true);
        expect(assets === undefined || isFolder(assets)).toBe(true);
        expect(root.files[0] === undefined || isFolder(root.files[0])).toBe(false);
        expect(ancestorsOf(root, CORE_ASSETS).map((folder) => folder.name)).toStrictEqual([ROOT_LABEL, CORE, ASSETS]);
        expect(ancestorsOf(root, [CORE, "missing"].join("/")).map((folder) => folder.name)).toStrictEqual([
            ROOT_LABEL,
            CORE,
        ]);
        expect(flatten(root).map((node) => node.id)).toStrictEqual([
            "folder-root",
            "file-index.html",
            "folder-core",
            "folder-core-assets",
            "file-link.assets.ts",
            "file-walk.assets.ts",
            "folder-types",
            "file-base.types.ts",
        ]);
    });
});

describe("renderTree, treeLink and markSelected", () => {
    it("renders nested details with a link per node, and selecting a node marks it and opens its ancestors", () => {
        const root = tree();
        if (root === null) {
            throw new Error(ROOT_LABEL);
        }
        const rendered = renderTree(root);
        document.body.append(rendered);
        const link = treeLink(root, "sample");
        expect(link.getAttribute(NODE_ATTRIBUTE)).toBe(root.id);
        expect(link.getAttribute("href")).toBe("#folder-root");
        const details = rendered.querySelectorAll("details");
        expect(details).toHaveLength(4);
        markSelected(rendered, "file-walk.assets.ts");
        const selected = rendered.querySelector(`[${NODE_ATTRIBUTE}="file-walk.assets.ts"]`);
        expect(selected?.classList.contains(ACTIVE_CLASS)).toBe(true);
        expect(selected?.closest("details")?.open).toBe(true);
        expect(selected?.closest("details")?.parentElement?.closest("details")?.open).toBe(true);
        expect(rendered.querySelectorAll(`.${ACTIVE_CLASS}`)).toHaveLength(1);
    });

    it("gives a folder whose name is a generic link word its path as context, and leaves a named folder bare", () => {
        const generic = treeNodesOf(
            [section(ROOT_LABEL, "folder-root", []), section("samples/information", "folder-samples-information", [])],
            { "folder-root": ["folder-samples-information"] },
        )?.folders[0];
        if (generic === undefined) {
            throw new Error(ROOT_LABEL);
        }
        expect(generic.name).toBe("information");
        expect(isGenericLabel(treeLink(generic, "sample").textContent)).toBe(false);
        expect(treeLink(generic, "sample").textContent).toContain("samples/information");
        const named = tree()?.folders[0];
        expect(named === undefined ? null : treeLink(named, "sample").textContent).toBe(CORE);
    });
});

describe("targetOf", () => {
    it("reads the node id and an optional line or text out of a hash, and refuses a href with no hash", () => {
        expect(targetOf("/anatomy/tree#file-a:19")).toStrictEqual({ id: "file-a", line: 19, text: null });
        expect(targetOf("#folder-root")).toStrictEqual({ id: "folder-root", line: null, text: null });
        expect(targetOf("#file-a:x")).toStrictEqual({ id: "file-a", line: null, text: "x" });
        expect(targetOf("/anatomy")).toBeNull();
    });
});