test.build/core/persistence/page.persistence.test.ts
test.build/core/persistence/page.persistence.test.ts is a file in Codebase Testing. 312 lines of code and 0 definitions.
import { DISCOVERY, SOURCE } from "../converters/site.fixture.ts";
import { afterEach, describe, expect, it } from "vitest";
import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
import {
writeDiscovery,
writePages,
writeRecordPages,
writeSectionPages,
writeSitemapIndex,
writeSitemapParts,
writeSourcePages,
writeText,
} from "@banes-lab/build-scripts/core/persistence/page.persistence.ts";
import { JSDOM } from "jsdom";
import { join } from "node:path";
import { stampLedger } from "@banes-lab/build-scripts/core/persistence/route.persistence.ts";
import { tmpdir } from "node:os";
import { undatedRoute } from "@banes-lab/build-scripts/configuration/strings/route.strings.ts";
import { writeVerbatim } from "@govlab/canonical-write";
const TEMPLATE = '<!doctype html><html><head><title></title></head><body><main id="app"></main></body></html>';
const scratch: string[] = [];
const folder = function folder(): string {
const dir = mkdtempSync(join(tmpdir(), "pages-"));
scratch.push(dir);
return dir;
};
afterEach(() => {
for (const dir of scratch.splice(0)) {
rmSync(dir, { force: true, recursive: true });
}
});
describe("writeText", () => {
it("writes a text file below the output folder, creating its folders", () => {
const dir = folder();
writeText(dir, "a/b/c.txt", "hello");
expect(readFileSync(join(dir, "a", "b", "c.txt"), "utf8")).toBe("hello");
});
});
describe("writePages", () => {
const STAMP = { lastmod: "2026-10-07", published: "2026-09-30" };
const DATED = new Map(["/", "/terms", "/terms/guide"].map((path) => [path, STAMP]));
const pageSource = function pageSource(): {
readonly dated: ReadonlyMap<string, string>;
readonly source: Parameters<typeof writePages>[1];
} {
const dated = new Map<string, string>();
const source: Parameters<typeof writePages>[1] = {
...SOURCE,
main: "app",
renderer: {
...SOURCE.renderer,
applyHead: () => {},
headOf: (page, definition, pathname, dates) => {
if (dates !== undefined && pathname !== undefined) {
dated.set(pathname, `${dates.published} ${dates.modified}`);
}
return SOURCE.renderer.headOf(page, definition, pathname);
},
renderStaticPage: () => [],
},
visit: () => {},
};
return { dated, source };
};
it("writes one dated page file per route plus the undated 404 page, and drops a stale precompressed sibling", async () => {
const dir = folder();
writeVerbatim(join(dir, "terms.html.gz"), "stale");
const { dated, source } = pageSource();
await writePages(dir, source, DISCOVERY, DATED, TEMPLATE);
const pages = ["index.html", "terms.html", "terms/guide.html", "404.html"];
expect(pages.every((file) => existsSync(join(dir, file)))).toBe(true);
expect(existsSync(join(dir, "terms.html.gz"))).toBe(false);
expect(Object.fromEntries(dated)).toStrictEqual({
"/": "2026-09-30 2026-10-07",
"/terms": "2026-09-30 2026-10-07",
"/terms/guide": "2026-09-30 2026-10-07",
});
});
it("stops on a route the ledger holds no stamp for", async () => {
const undated = new Map([["/", STAMP]]);
await expect(writePages(folder(), pageSource().source, DISCOVERY, undated, TEMPLATE)).rejects.toThrow(
undatedRoute("/terms"),
);
});
});
describe("writeSourcePages", () => {
it("writes one page per source route with its head and body, and lists every page in the source sitemap", () => {
const dir = folder();
const { document } = new JSDOM("").window;
const subject = {
alternates: { json: "/json/source/tree/a.ts", markdown: "/source/tree/a.ts.md" },
description: "a.ts is a file in Site.",
language: "typescript",
license: null,
name: "a.ts",
path: "/anatomy/tree/file-a-ts",
repository: null,
tabPath: "/anatomy/tree",
tabTitle: "Site",
title: "Site — a.ts — Bane's Lab",
trail: [{ label: "Site", path: "/anatomy/tree" }],
};
const titles: string[] = [];
writeSourcePages(
dir,
{
body: {
renderSourceBody: (route) => {
const element = document.createElement("article");
element.textContent = route.subject.name;
return element;
},
},
main: "app",
renderer: {
applyHead: (root, head) => {
root.title = head.title;
},
sourceHead: (held) => {
titles.push(held.title);
return { description: held.description, path: held.path, robots: "index", title: held.title };
},
},
site: "https://example.test",
},
[
{ children: [], parent: { label: "Site", path: "/anatomy/tree" }, subject, text: null },
{
children: [],
parent: { label: "Site", path: "/anatomy/tree" },
subject: { ...subject, name: "core", path: "/anatomy/tree/folder-core", title: "core" },
text: null,
},
],
TEMPLATE,
);
const html = readFileSync(join(dir, "anatomy/tree/file-a-ts.html"), "utf8");
expect(html).toContain(`<title>${subject.title}</title>`);
expect(html).toContain("<article>a.ts</article>");
expect(titles).toStrictEqual([subject.title, "core"]);
expect(existsSync(join(dir, "anatomy/tree/folder-core.html"))).toBe(true);
const sitemap = readFileSync(join(dir, "sitemap-sources.xml"), "utf8");
expect(sitemap).toContain("https://example.test/anatomy/tree/file-a-ts");
expect(sitemap).not.toContain("folder-core");
});
});
describe("writeRecordPages", () => {
it("writes one page per record with its head and body, and lists every record in the record sitemap", () => {
const dir = folder();
const { document } = new JSDOM("").window;
const record = {
aliases: [],
alternates: { json: "/json/records/lexicon/cache", markdown: "/records/lexicon/cache.md" },
code: null,
collection: { label: "Lexicon", path: "/ontology/lexicon" },
description: "A store that answers again without the work.",
facts: [],
kind: "term",
layer: null,
name: "Cache",
path: "/records/lexicon/cache",
placement: null,
relations: [],
section: "core",
summary: null,
title: "Cache — Lexicon — Bane's Lab",
};
const sectioned: string[] = [];
const parts = writeRecordPages(
dir,
{
body: {
renderRecordBody: (subject) => {
const element = document.createElement("article");
element.textContent = subject.name;
return element;
},
},
main: "app",
renderer: {
applyHead: (root, head) => {
root.title = head.title;
},
recordHead: (held) => ({
description: held.description,
path: held.path,
robots: "index",
title: held.title,
}),
renderStaticSection: (page, path, id) => {
sectioned.push(`${page} ${path} ${id}`);
const rows = document.createElement("section");
rows.textContent = "rows";
return rows;
},
},
site: "https://example.test",
},
[record],
TEMPLATE,
);
const html = readFileSync(join(dir, "records/lexicon/cache.html"), "utf8");
expect(html).toContain(`<title>${record.title}</title>`);
expect(html).toContain("<article>Cache<section>rows</section></article>");
expect(sectioned).toStrictEqual(["ontology /ontology/lexicon core"]);
expect(parts).toStrictEqual(["/sitemap-records.xml"]);
expect(readFileSync(join(dir, "sitemap-records.xml"), "utf8")).toContain("https://example.test/records/lexicon/cache");
});
});
describe("writeSectionPages", () => {
it("writes one page per section with its head and body, and lists every section in the section sitemap", () => {
const dir = folder();
const { document } = new JSDOM("").window;
const seed = {
intro: null,
kind: null,
name: "Core Vocabulary",
page: "ontology",
part: 1,
parts: 1,
records: 2,
rows: [0, 2] as const,
section: "core-vocabulary",
tab: "lexicon",
tabPath: "/ontology/lexicon",
tabTitle: "Lexicon",
};
const subject = {
alternates: { json: "/json/ontology/lexicon/core-vocabulary", markdown: "/ontology/lexicon/core-vocabulary.md" },
description: "The 2 records of the Core Vocabulary section in Lexicon.",
name: seed.name,
parts: [],
path: "/ontology/lexicon/core-vocabulary",
seed,
title: "Core Vocabulary — Lexicon — Bane's Lab",
};
const parts = writeSectionPages(
dir,
{
main: "app",
renderer: {
applyHead: (root, head) => {
root.title = head.title;
},
renderSectionPage: (held) => {
const element = document.createElement("article");
element.textContent = held.name;
return element;
},
sectionHead: (held) => ({
description: held.description,
path: held.path,
robots: "index",
title: held.title,
}),
},
site: "https://example.test",
},
[subject],
TEMPLATE,
);
const html = readFileSync(join(dir, "ontology/lexicon/core-vocabulary.html"), "utf8");
expect(html).toContain(`<title>${subject.title}</title>`);
expect(html).toContain("<article>Core Vocabulary</article>");
expect(parts).toStrictEqual(["/sitemap-sections.xml"]);
expect(readFileSync(join(dir, "sitemap-sections.xml"), "utf8")).toContain(
"https://example.test/ontology/lexicon/core-vocabulary",
);
});
});
describe("writeDiscovery", () => {
it("writes each route's payload and Markdown alternate, the full text, the page sitemap, robots and llms from the stamped ledger", async () => {
const dir = folder();
const ledger = join(folder(), "sitemap.generated.json");
const stamped = await stampLedger(DISCOVERY, new Date("2026-09-19T14:00:00Z"), ledger);
const [route, stamp] = writeDiscovery(dir, DISCOVERY, stamped);
const written = [
"json/terms.json",
"json/terms/guide.json",
"terms.md",
"index.md",
"llms-full.txt",
"llms-full/terms.txt",
"sitemap-pages.xml",
"robots.txt",
"llms.txt",
];
expect(written.every((file) => existsSync(join(dir, file)))).toBe(true);
expect(route).toBe("/sitemap-pages.xml");
expect(stamp).toBe("2026-09-19");
expect(readFileSync(join(dir, "sitemap-pages.xml"), "utf8")).toContain("<lastmod>2026-09-19</lastmod>");
expect(readFileSync(join(dir, "llms.txt"), "utf8")).toContain("/llms-full/terms.txt");
expect(readFileSync(join(dir, "robots.txt"), "utf8")).not.toContain("sitemap-catalog.xml");
const stored: unknown = JSON.parse(readFileSync(ledger, "utf8"));
expect(Object.keys(stored ?? {})).toContain("/terms/guide");
});
});
describe("writeSitemapParts and writeSitemapIndex", () => {
it("writes each part of a sitemap and an index naming every part", () => {
const dir = folder();
const routes = writeSitemapParts(dir, "/sitemap-catalog.xml", ["https://example.test/a.md"]);
expect(routes).toStrictEqual(["/sitemap-catalog.xml"]);
writeSitemapIndex(dir, "https://example.test", [
["/sitemap-pages.xml", "2026-10-05"],
...routes.map((held): readonly [string, null] => [held, null]),
]);
const index = readFileSync(join(dir, "sitemap.xml"), "utf8");
expect(index).toContain("https://example.test/sitemap-catalog.xml");
expect(index).toContain("<lastmod>2026-10-05</lastmod>");
expect(readFileSync(join(dir, "sitemap-catalog.xml"), "utf8")).toContain("https://example.test/a.md");
});
});