test.content/core/renderers/chapter.renderer.test.ts
test.content/core/renderers/chapter.renderer.test.ts is a file in Codebase Testing. 131 lines of code and 0 definitions.
import { ANATOMY_PAGE, GRAMMAR_PAGE, METHODOLOGY_PAGE } from "@banes-lab/web/core/ids/page.ids.ts";
import type {
Attribution,
Chapter,
ChapterSource,
PageSources,
ShapeInput,
} from "@banes-lab/content/types/chapter.types.ts";
import { attributionLine, link, tabFileOf } from "@banes-lab/content/core/renderers/markdown.renderer.ts";
import {
chapterPathOf,
renderChapters,
repositoryResolver,
} from "@banes-lab/content/core/renderers/chapter.renderer.ts";
import { describe, expect, it } from "vitest";
import { isRepositoryPage, pageFolderOf } from "@banes-lab/content/core/normalizers/readme.normalizer.ts";
import { renderWiki } from "@banes-lab/content/core/renderers/wiki.renderer.ts";
import { unstableChapters } from "@banes-lab/content/core/validators/chapter.validator.ts";
const ATTRIBUTION: Attribution = {
author: "Author",
copyright: "© 2025 ",
coveredBy: "Documentation is covered by ",
licenseName: "CC BY-SA 4.0",
licenseUrl: "https://creativecommons.org/licenses/by-sa/4.0/",
work: " - Work",
};
const FIRST: ChapterSource = {
first: true,
label: "Introduction",
markdown:
"# Page\n\nIntro alternate, see [stages](/disciplined-methodology/stages#loop), [the guide](/pag/guide) and [terms](/ontology#architecture-x).\n",
tab: "introduction",
};
const STAGES: ChapterSource = {
first: false,
label: "Stages",
markdown: "# Stages\n\nStages alternate.\n",
tab: "stages",
};
const METHOD: PageSources = { label: "Method", page: METHODOLOGY_PAGE, sources: [FIRST, STAGES] };
const GUIDE: ChapterSource = {
first: false,
label: "Guide",
markdown: "# Guide\n\nBack to [stages](/disciplined-methodology/stages).\n",
tab: "guide",
};
const GRAMMAR: PageSources = {
label: "PAG",
page: GRAMMAR_PAGE,
sources: [{ first: true, label: "Introduction", markdown: "# PAG\n", tab: "introduction" }, GUIDE],
};
const INPUT: ShapeInput = { pages: [METHOD, GRAMMAR], readme: null };
describe("pageFolderOf, chapterPathOf, tabFileOf and attributionLine", () => {
it("keeps the methodology at the root and gives every other page a folder named by its label", () => {
expect(pageFolderOf(METHODOLOGY_PAGE, "Method")).toBe("");
expect(pageFolderOf(GRAMMAR_PAGE, "PAG")).toBe("pag");
expect(pageFolderOf("x", "Two Words")).toBe("two-words");
expect(isRepositoryPage(GRAMMAR_PAGE)).toBe(true);
expect(isRepositoryPage(ANATOMY_PAGE)).toBe(false);
});
it("names the first methodology tab README while no README is composed, and every tab by its upper-cased id", () => {
expect(chapterPathOf(METHOD, FIRST, false)).toBe("README.md");
expect(chapterPathOf(METHOD, FIRST, true)).toBe("INTRODUCTION.md");
expect(chapterPathOf(METHOD, STAGES, false)).toBe("STAGES.md");
expect(chapterPathOf(GRAMMAR, GUIDE, false)).toBe("pag/GUIDE.md");
expect(tabFileOf("build")).toBe("BUILD.md");
});
it("composes a markdown link", () => {
expect(link("Stages", "STAGES.md")).toBe("[Stages](STAGES.md)");
});
it("composes the attribution line from the existing strings", () => {
expect(attributionLine(ATTRIBUTION)).toBe(
"© 2025 Author - Work · Documentation is covered by [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/)",
);
});
});
describe("repositoryResolver", () => {
it("maps every rendered route to its chapter file relative to the linking folder, and an unrendered route to nothing", () => {
const fromRoot = repositoryResolver(INPUT.pages, false, "");
expect(fromRoot(METHODOLOGY_PAGE, null)).toBe("README.md");
expect(fromRoot(METHODOLOGY_PAGE, "stages")).toBe("STAGES.md");
expect(fromRoot(GRAMMAR_PAGE, "guide")).toBe("pag/GUIDE.md");
expect(fromRoot(METHODOLOGY_PAGE, "gone")).toBeNull();
expect(fromRoot("ontology", null)).toBeNull();
const fromFolder = repositoryResolver(INPUT.pages, true, "pag");
expect(fromFolder(METHODOLOGY_PAGE, null)).toBe("../INTRODUCTION.md");
expect(fromFolder(GRAMMAR_PAGE, null)).toBe("INTRODUCTION.md");
});
});
describe("renderChapters", () => {
const chapters = renderChapters(INPUT, ATTRIBUTION);
it("writes the methodology at the root and every other page in its folder", () => {
expect(chapters.map((chapter) => chapter.file)).toStrictEqual([
"README.md",
"STAGES.md",
"pag/INTRODUCTION.md",
"pag/GUIDE.md",
]);
});
it("opens every chapter with the attribution and closes it with its page's footer", () => {
for (const chapter of chapters) {
expect(chapter.body.startsWith(attributionLine(ATTRIBUTION))).toBe(true);
}
expect(chapters[1]?.body).toContain("[README](README.md) · [Stages](STAGES.md)");
expect(chapters[3]?.body).toContain("[Introduction](INTRODUCTION.md) · [Guide](GUIDE.md)");
});
it("lists the chapters in the README only", () => {
expect(chapters[0]?.body).toContain("- [Stages](STAGES.md)");
expect(chapters[1]?.body).not.toContain("- [Stages](STAGES.md)");
});
it("rewrites a root-relative link to a chapter file, climbing out of a folder, or to the site", () => {
expect(chapters[0]?.body).toContain("[stages](STAGES.md#loop)");
expect(chapters[0]?.body).toContain("[the guide](pag/GUIDE.md)");
expect(chapters[0]?.body).toContain("[terms](https://banes-lab.com/ontology#architecture-x)");
expect(chapters[3]?.body).toContain("[stages](../STAGES.md)");
});
});
describe("unstableChapters", () => {
it("finds no difference between two renders of the same sources", () => {
expect(unstableChapters(renderChapters, INPUT, ATTRIBUTION)).toStrictEqual([]);
expect(unstableChapters(renderWiki, INPUT, ATTRIBUTION)).toStrictEqual([]);
});
it("reports a chapter whose render is not a pure function of its sources", () => {
let calls = 0;
const drifting = function drifting(): Chapter[] {
calls += 1;
return [{ body: String(calls), file: "README.md", label: "README" }];
};
expect(unstableChapters(drifting, INPUT, ATTRIBUTION)).toStrictEqual(["README.md"]);
});
});