test.build/core/converters/learning.converter.test.ts
test.build/core/converters/learning.converter.test.ts is a file in Codebase Testing. 136 lines of code and 0 definitions.
import type { LearningRouter, TabRecord, Unit } from "@banes-lab/build-scripts/types/learning.types.ts";
import {
chunkCode,
heldIn,
orderUnits,
safeLabel,
sectionsIn,
teachingBlocks,
teachingRoute,
textOf,
unitsOf,
} from "@banes-lab/build-scripts/core/converters/learning.converter.ts";
import { describe, expect, it } from "vitest";
import type { ContentGraph } from "@banes-lab/web/types/methodology.types.ts";
import { SITE_PAGES } from "@banes-lab/build-scripts/configuration/constants/learning.constants.ts";
const SPINE = "disciplined-methodology";
const OTHER = "pag";
const ROUTER: LearningRouter = {
pagePath: (page) => `/${page}`,
tabLink: (page, tab, section) => `/${page}/${tab}#${section ?? ""}`,
};
const unit = function unit(tab: string, teaches: readonly string[], external: readonly string[]): Unit {
return { external, first: false, label: tab, page: OTHER, sections: [{ id: tab, title: tab }], tab, teaches };
};
const GRAPHS: readonly ContentGraph[] = [
{
page: SPINE,
sections: {
first: { requires: [], teaches: ["loop"], traces: [] },
second: { requires: ["loop"], teaches: ["gate"], traces: [] },
},
},
{
page: OTHER,
sections: {
grammar: { requires: ["loop"], teaches: ["directive"], traces: [] },
words: { narrative: true, requires: [], teaches: [], traces: [] },
},
},
];
const TABS: ReadonlyMap<string, readonly TabRecord[]> = new Map([
[
SPINE,
[
{ id: "start", label: "Start", sections: [{ id: "first", title: "The loop" }] },
{ id: "build", label: "Build", sections: [{ id: "second", title: "The gate" }] },
],
],
[
OTHER,
[
{ id: "guide", label: "Guide", sections: [{ id: "grammar", title: "A directive" }] },
{ id: "keywords", label: "Keywords", sections: [{ id: "words", title: "Keywords" }] },
],
],
]);
const tabsFor = function tabsFor(page: string): readonly TabRecord[] {
return TABS.get(page) ?? [];
};
describe("safeLabel", () => {
it("substitutes every character the diagram grammar reserves", () => {
expect(safeLabel('a "quoted" [bracket] (paren) {brace}')).toBe("a 'quoted' 'bracket' 'paren' 'brace'");
});
it("folds a typographic character and drops what the renderer cannot measure", () => {
expect(safeLabel("Bane’s — loop")).toBe("Bane's - loop");
expect(safeLabel("A·B✅C")).toBe("A-BC");
});
});
describe("chunkCode and textOf", () => {
it("allocates two letters per block and reads only strings", () => {
expect(chunkCode(0)).toBe("aa");
expect(chunkCode(27)).toBe("bb");
expect(textOf(3)).toBe("");
expect(textOf("x")).toBe("x");
});
});
describe("sectionsIn and heldIn", () => {
it("keep the titled sections of a tab and the graph entries they carry", () => {
const tab: TabRecord = { id: "t", label: "T", sections: [{ id: "first", title: "The loop" }, { id: "x" }] };
const sections = sectionsIn(tab);
expect(sections.map((section) => section.id)).toStrictEqual(["first"]);
const [spine] = GRAPHS;
expect(spine === undefined ? [] : heldIn(spine, sections).map((entry) => entry.teaches)).toStrictEqual([
["loop"],
]);
expect(sectionsIn({ id: "empty", label: "E" })).toStrictEqual([]);
});
});
describe("unitsOf", () => {
it("keeps a tab that teaches and drops one that only narrates", () => {
const graph = GRAPHS.find((held) => held.page === OTHER);
const units = graph === undefined ? [] : unitsOf(OTHER, graph, tabsFor(OTHER));
expect(units.map((held) => held.tab)).toStrictEqual(["guide"]);
expect(units[0]?.external).toStrictEqual(["loop"]);
expect(units[0]?.first).toBe(true);
});
});
describe("orderUnits", () => {
it("places each other tab after the first spine tab that completes its requirements", () => {
const spine = [unit("s1", ["a"], []), unit("s2", ["b"], [])];
const others = [unit("x", ["x"], ["b"]), unit("y", ["y"], ["a"]), unit("z", ["z"], [])];
expect(orderUnits(spine, others).map((held) => held.tab)).toStrictEqual(["s1", "y", "z", "s2", "x"]);
});
it("keeps a tab whose requirement no page teaches, at the end, so nothing is dropped", () => {
const ordered = orderUnits([unit("s1", ["a"], [])], [unit("orphan", ["o"], ["nowhere"])]);
expect(ordered.map((held) => held.tab)).toStrictEqual(["s1", "orphan"]);
});
});
describe("teachingRoute", () => {
it("numbers every stop by its place in the route, gives each a unique id and resolves its prerequisites to stops", () => {
const stops = teachingRoute(ROUTER, GRAPHS, tabsFor);
expect(stops.map((stop) => stop.label)).toStrictEqual(["01 - The loop", "02 - A directive", "03 - The gate"]);
expect(new Set(stops.map((stop) => stop.id)).size).toBe(stops.length);
const [loop, directive, gate] = stops;
expect(directive?.requires).toStrictEqual([loop?.id]);
expect(gate?.requires).toStrictEqual([loop?.id]);
});
it("addresses a first-tab stop by the page path, because only the other tabs are served at their own path", () => {
const [loop, directive, gate] = teachingRoute(ROUTER, GRAPHS, tabsFor);
expect(loop?.path).toBe(`/${SPINE}#first`);
expect(directive?.path).toBe(`/${OTHER}#grammar`);
expect(gate?.path).toBe(`/${SPINE}/build#second`);
});
});
describe("teachingBlocks", () => {
it("groups consecutive stops of one block and carries their prerequisites", () => {
const blocks = teachingBlocks(teachingRoute(ROUTER, GRAPHS, tabsFor));
expect(blocks.map((block) => block.label)).toStrictEqual(["Start", "Guide", "Build"]);
expect(blocks[2]?.stops[0]?.requires).toHaveLength(1);
});
});
describe("SITE_PAGES", () => {
it("declares the page order the tab module also reads", () => {
expect(SITE_PAGES).toContain(SPINE);
});
});