Files
Reactive-Resume/packages/pdf/src/semantic/template-manifest.test.ts
T
2026-07-30 12:39:15 +02:00

1369 lines
51 KiB
TypeScript

import type { SemanticNode } from "@reactive-resume/resume/stylesheet/types";
import type { ResumeData } from "@reactive-resume/schema/resume/data";
import type { Template } from "@reactive-resume/schema/templates";
import type { TemplateSemanticManifest } from "./template-manifest";
import { describe, expect, it } from "vitest";
import { TEMPLATE_PART_CHILD_KINDS_V1 } from "@reactive-resume/resume/stylesheet";
import { defaultResumeData } from "@reactive-resume/schema/resume/default";
import { templateSchema } from "@reactive-resume/schema/templates";
import { createBindingInventory } from "./binding-inventory";
import {
getTemplateSemanticBindingRegistry,
getTemplateSemanticManifest,
getTemplateSemanticRegistryFingerprintInput,
validateTemplateSemanticManifest,
} from "./template-manifest";
import { buildSemanticTree } from "./tree";
const EXPECTED_PARTS = {
azurill: {
"timeline-line": {
key: "timeline-line",
owner: { kind: "section-items", key: "section-items", placement: "main", columns: 1 },
binding: { type: "primitive", primitive: "View", source: "existing" },
route: { parent: "owner", at: "start" },
},
"timeline-dot": {
key: "timeline-dot",
owner: { kind: "item", key: "item", placement: "main", columns: 1 },
binding: { type: "primitive", primitive: "View", source: "existing" },
route: { parent: "timeline-marker", at: "start" },
},
"timeline-marker": {
key: "timeline-marker",
owner: { kind: "item", key: "item", placement: "main", columns: 1 },
binding: { type: "primitive", primitive: "View", source: "existing" },
route: { parent: "owner", at: "start" },
},
"timeline-content": {
key: "timeline-content",
owner: { kind: "item", key: "item", placement: "main", columns: 1 },
binding: { type: "primitive", primitive: "View", source: "existing" },
route: { parent: "owner", at: "end", take: "all" },
},
},
bronzor: {
"interleaved-section-row": {
key: "interleaved-section-row",
owner: { kind: "section", key: "section", placement: "main" },
binding: { type: "alias", canonicalKind: "section", token: "interleaved-section-row" },
},
},
chikorita: {
"contact-row-primary": {
key: "contact-row-primary",
owner: { kind: "contact-list", key: "contact-list" },
binding: { type: "primitive", primitive: "View", source: "existing" },
route: {
parent: "owner",
at: "start",
take: [
{ kind: "contact-item", name: "email" },
{ kind: "contact-item", name: "phone" },
{ kind: "contact-item", name: "location" },
],
},
},
"contact-row-secondary": {
key: "contact-row-secondary",
owner: { kind: "contact-list", key: "contact-list" },
binding: { type: "primitive", primitive: "View", source: "existing" },
route: {
parent: "owner",
at: "end",
take: [
{ kind: "contact-item", name: "website" },
{ kind: "contact-item", name: "custom" },
],
},
},
},
ditgar: {
"featured-summary": {
key: "featured-summary",
owner: { kind: "region", key: "featured" },
binding: { type: "primitive", primitive: "View", source: "existing" },
route: { parent: "owner", at: "start", take: "all" },
},
"sidebar-background": {
key: "sidebar-background",
owner: { kind: "region", key: "sidebar" },
binding: { type: "alias", canonicalKind: "region", token: "sidebar-background" },
},
"item-header-border": {
key: "item-header-border",
owner: {
kind: "item-header",
key: "item-header",
sectionTypes: [
"profiles",
"experience",
"education",
"projects",
"skills",
"languages",
"interests",
"awards",
"certifications",
"publications",
"volunteer",
"references",
],
},
binding: { type: "alias", canonicalKind: "item-header", token: "item-header-border" },
},
},
ditto: {
"header-band": {
key: "header-band",
owner: { kind: "header", key: "header" },
binding: { type: "primitive", primitive: "View", source: "existing" },
route: {
parent: "owner",
at: "start",
take: [{ kind: "picture" }, { kind: "name" }, { kind: "headline" }],
},
},
"picture-anchor": {
key: "picture-anchor",
owner: { kind: "header", key: "header" },
binding: { type: "primitive", primitive: "View", source: "existing" },
route: { parent: "header-band", at: "start", take: [{ kind: "picture" }] },
},
"contact-offset": {
key: "contact-offset",
owner: { kind: "header", key: "header" },
binding: { type: "primitive", primitive: "View", source: "existing" },
route: { parent: "owner", at: { before: { kind: "contact-list" } } },
},
},
gengar: {
"featured-summary": {
key: "featured-summary",
owner: { kind: "region", key: "featured" },
binding: { type: "primitive", primitive: "View", source: "existing" },
route: { parent: "owner", at: "start", take: "all" },
},
"sidebar-background": {
key: "sidebar-background",
owner: { kind: "region", key: "sidebar" },
binding: { type: "alias", canonicalKind: "region", token: "sidebar-background" },
},
},
glalie: {
"sidebar-background": {
key: "sidebar-background",
owner: { kind: "region", key: "sidebar" },
binding: { type: "primitive", primitive: "View", source: "existing" },
route: { parent: "owner", at: "start" },
},
},
kakuna: {},
lapras: {},
leafish: {
"header-intro": {
key: "header-intro",
owner: { kind: "header", key: "header" },
binding: { type: "primitive", primitive: "View", source: "existing" },
route: {
parent: "owner",
at: "start",
take: [{ kind: "picture" }, { kind: "name" }, { kind: "headline" }, { kind: "section", name: "summary" }],
},
},
"header-body": {
key: "header-body",
owner: { kind: "header", key: "header" },
binding: { type: "primitive", primitive: "View", source: "existing" },
route: { parent: "header-intro", at: "start", take: "all" },
},
"header-contact-band": {
key: "header-contact-band",
owner: { kind: "header", key: "header" },
binding: { type: "primitive", primitive: "View", source: "existing" },
route: { parent: "owner", at: "end", take: [{ kind: "contact-list" }] },
},
},
meowth: {
"inline-item-header-leading": {
key: "inline-item-header-leading",
owner: {
kind: "item-header",
key: "item-header",
sectionTypes: ["experience", "education", "volunteer"],
},
binding: { type: "primitive", primitive: "View", source: "existing" },
route: {
parent: "owner",
at: "start",
take: [
{ kind: "field", name: "position", sectionTypes: ["experience", "volunteer"] },
{ kind: "field", name: "location", sectionTypes: ["experience", "volunteer"] },
{ kind: "field", name: "area", sectionTypes: ["education"] },
{ kind: "field", name: "degree", sectionTypes: ["education"] },
],
},
},
"inline-item-header-middle": {
key: "inline-item-header-middle",
owner: {
kind: "item-header",
key: "item-header",
sectionTypes: ["experience", "education", "volunteer"],
},
binding: { type: "primitive", primitive: "View", source: "existing" },
route: {
parent: "owner",
at: "start",
take: [
{ kind: "field", name: "company" },
{ kind: "field", name: "school" },
{ kind: "field", name: "organization" },
{ kind: "link" },
],
},
},
"inline-item-header-trailing": {
key: "inline-item-header-trailing",
owner: {
kind: "item-header",
key: "item-header",
sectionTypes: ["experience", "education", "volunteer"],
},
binding: { type: "primitive", primitive: "View", source: "existing" },
route: { parent: "owner", at: "start", take: [{ kind: "field", name: "period" }] },
},
"education-grade-row": {
key: "education-grade-row",
owner: { kind: "item", key: "item" },
binding: { type: "primitive", primitive: "Text", source: "existing" },
route: {
parent: "owner",
at: { after: { kind: "item-header" } },
take: [
{ kind: "field", name: "grade", sectionTypes: ["education"] },
{ kind: "field", name: "location", sectionTypes: ["education"] },
],
takeFrom: "item-header",
},
},
},
onyx: {},
pikachu: {
"header-divider": {
key: "header-divider",
owner: { kind: "header", key: "header" },
binding: { type: "primitive", primitive: "View", source: "existing" },
route: { parent: "owner", at: "start", take: [{ kind: "name" }, { kind: "headline" }] },
},
},
rhyhorn: {
"contact-item-content": {
key: "contact-item-content",
owner: { kind: "contact-item", key: "contact-item" },
binding: {
type: "primitive",
primitive: { ownerRole: "structured-link", present: "Link", absent: "View" },
source: "existing",
},
route: { parent: "owner", at: "start", take: "all" },
},
"contact-item-last": {
key: "contact-item-last",
owner: { kind: "contact-item", key: "contact-item", position: "last" },
binding: { type: "alias", canonicalKind: "contact-item", token: "contact-item-last" },
},
},
scizor: {
"header-name-rule": {
key: "header-name-rule",
owner: { kind: "header", key: "header" },
binding: { type: "primitive", primitive: "View", source: "existing" },
route: { parent: "owner", at: { before: { kind: "headline" } } },
},
},
} as const satisfies Readonly<Record<Template, Readonly<Record<string, object>>>>;
const EXPECTED_LAYOUT = {
azurill: {
regions: [
{ name: "header", placement: "main", origins: [] },
{ name: "main", placement: "main", origins: ["main"] },
{ name: "sidebar", placement: "sidebar", origins: ["sidebar"] },
],
header: { region: "header", placement: "main" },
specialSummary: null,
},
bronzor: {
regions: [
{ name: "header", placement: "main", origins: [] },
{ name: "main", placement: "main", origins: ["sidebar", "main"], flow: "interleaved" },
],
header: { region: "header", placement: "main" },
specialSummary: null,
},
chikorita: {
regions: [
{ name: "header", placement: "main", origins: [] },
{ name: "main", placement: "main", origins: ["main"] },
{ name: "sidebar", placement: "sidebar", origins: ["sidebar"] },
],
header: { region: "header", placement: "main" },
specialSummary: null,
},
ditgar: {
regions: [
{ name: "header", placement: "sidebar", origins: [] },
{ name: "sidebar", placement: "sidebar", origins: ["sidebar"] },
{ name: "featured", placement: "main", origins: [] },
{ name: "main", placement: "main", origins: ["main"] },
],
header: { region: "header", placement: "sidebar" },
specialSummary: { region: "featured", placement: "main", source: "main-with-header" },
},
ditto: {
regions: [
{ name: "header", placement: "main", origins: [] },
{ name: "main", placement: "main", origins: ["main"] },
{ name: "sidebar", placement: "sidebar", origins: ["sidebar"] },
],
header: { region: "header", placement: "main" },
specialSummary: null,
},
gengar: {
regions: [
{ name: "header", placement: "sidebar", origins: [] },
{ name: "sidebar", placement: "sidebar", origins: ["sidebar"] },
{ name: "featured", placement: "main", origins: [] },
{ name: "main", placement: "main", origins: ["main"] },
],
header: { region: "header", placement: "sidebar" },
specialSummary: { region: "featured", placement: "main", source: "main-with-header" },
},
glalie: {
regions: [
{ name: "header", placement: "sidebar", origins: [] },
{ name: "sidebar", placement: "sidebar", origins: ["sidebar"] },
{ name: "main", placement: "main", origins: ["main"] },
],
header: { region: "header", placement: "sidebar" },
specialSummary: null,
},
kakuna: {
regions: [
{ name: "header", placement: "main", origins: [] },
{ name: "main", placement: "main", origins: ["main"] },
{ name: "sidebar", placement: "sidebar", origins: ["sidebar"] },
],
header: { region: "header", placement: "main" },
specialSummary: null,
},
lapras: {
regions: [
{ name: "header", placement: "main", origins: [] },
{ name: "main", placement: "main", origins: ["main"] },
{ name: "sidebar", placement: "sidebar", origins: ["sidebar"] },
],
header: { region: "header", placement: "main" },
specialSummary: null,
},
leafish: {
regions: [
{ name: "header", placement: "main", origins: [] },
{ name: "main", placement: "main", origins: ["main"] },
{ name: "sidebar", placement: "sidebar", origins: ["sidebar"] },
],
header: { region: "header", placement: "main" },
specialSummary: { region: "header", placement: "main", source: "always" },
},
meowth: {
regions: [
{ name: "header", placement: "main", origins: [] },
{ name: "main", placement: "main", origins: ["main"] },
{ name: "sidebar", placement: "sidebar", origins: ["sidebar"] },
],
header: { region: "header", placement: "main" },
specialSummary: null,
},
onyx: {
regions: [
{ name: "header", placement: "main", origins: [] },
{ name: "main", placement: "main", origins: ["main"] },
{ name: "sidebar", placement: "sidebar", origins: ["sidebar"] },
],
header: { region: "header", placement: "main" },
specialSummary: null,
},
pikachu: {
regions: [
{ name: "header", placement: "main", origins: [] },
{ name: "main", placement: "main", origins: ["main"] },
{ name: "sidebar", placement: "sidebar", origins: ["sidebar"] },
],
header: { region: "header", placement: "main" },
specialSummary: null,
},
rhyhorn: {
regions: [
{ name: "header", placement: "main", origins: [] },
{ name: "main", placement: "main", origins: ["main"] },
{ name: "sidebar", placement: "sidebar", origins: ["sidebar"] },
],
header: { region: "header", placement: "main" },
specialSummary: null,
},
scizor: {
regions: [
{ name: "header", placement: "main", origins: [] },
{ name: "main", placement: "main", origins: ["main", "sidebar"] },
],
header: { region: "header", placement: "main" },
specialSummary: null,
},
} as const satisfies Readonly<Record<Template, Omit<TemplateSemanticManifest, "template" | "parts">>>;
const flattenTree = (node: SemanticNode): SemanticNode[] => [node, ...node.children.flatMap(flattenTree)];
const flattenValues = (value: unknown): unknown[] =>
typeof value === "object" && value !== null ? [value, ...Object.values(value).flatMap(flattenValues)] : [value];
const findNodes = (node: SemanticNode, predicate: (candidate: SemanticNode) => boolean): SemanticNode[] =>
flattenTree(node).filter(predicate);
const findPart = (node: SemanticNode, name: string): SemanticNode | undefined =>
findNodes(node, (candidate) => candidate.kind === "template-part" && candidate.attributes.name === name)[0];
const childLabels = (node: SemanticNode): string[] =>
node.children.map((child) => (child.kind === "template-part" ? (child.attributes.name ?? "") : child.kind));
const buildFixture = (): ResumeData => {
const data = structuredClone(defaultResumeData);
data.picture.url = "/uploads/ada.png";
data.basics = {
name: "Ada Lovelace",
headline: "Engineer",
email: "ada@example.com",
phone: "",
location: "",
website: { url: "", label: "" },
customFields: [],
};
data.summary.content = "<p>Summary</p>";
data.sections.experience.columns = 1;
data.sections.experience.items = [
{
id: "experience/1",
hidden: false,
company: "Analytical Engines",
position: "Engineer",
location: "London",
period: "1842",
website: { url: "", label: "", inlineLink: false },
description: "<p>Built algorithms.</p>",
roles: [],
},
];
data.sections.skills.items = [
{
id: "skills/1",
hidden: false,
icon: "code",
iconColor: "",
name: "TypeScript",
proficiency: "Expert",
level: 4,
keywords: ["PDF"],
},
];
return data;
};
const buildFixtureTree = (template: Template, showHeader = true): SemanticNode => {
const data = buildFixture();
const page = { fullWidth: false, main: ["summary", "experience"], sidebar: ["skills"] };
return buildSemanticTree({ data, template, page, pageNumber: 1, showHeader });
};
describe("template semantic manifests", () => {
it.each(templateSchema.options)("%s publishes a semantic manifest", (template) => {
expect(getTemplateSemanticManifest(template)).toBeDefined();
});
it("registers child kinds for every primitive template part", () => {
for (const manifest of Object.values(getTemplateSemanticRegistryFingerprintInput())) {
for (const part of manifest.parts) {
if (part.binding.type === "alias") continue;
expect(TEMPLATE_PART_CHILD_KINDS_V1, `${manifest.template}:${part.name}`).toHaveProperty(part.name);
}
}
});
it.each(templateSchema.options)("%s declares only its exact existing parts and owner bindings", (template) => {
const parts = Object.fromEntries(
getTemplateSemanticManifest(template).parts.map(({ name, ...part }) => [name, part]),
);
expect(parts).toEqual(EXPECTED_PARTS[template]);
});
it("declares Rhyhorn's outer contact primitive override and no unrelated canonical overrides", () => {
expect(getTemplateSemanticManifest("rhyhorn").canonicalBindings).toEqual([
{ kind: "contact-item", binding: { type: "primitive", primitive: "View", source: "existing" } },
]);
expect(
templateSchema.options
.filter((template) => template !== "rhyhorn")
.every((template) => getTemplateSemanticManifest(template).canonicalBindings === undefined),
).toBe(true);
});
it.each(templateSchema.options)(
"%s declares its exact regions, header, and special-summary placement",
(template) => {
const { regions, header, specialSummary } = getTemplateSemanticManifest(template);
expect({ regions, header, specialSummary }).toEqual(EXPECTED_LAYOUT[template]);
},
);
it("rejects duplicate part names and keys", () => {
const duplicateName = structuredClone(getTemplateSemanticManifest("ditto")) as TemplateSemanticManifest;
const duplicateKey = structuredClone(getTemplateSemanticManifest("ditto")) as TemplateSemanticManifest;
const first = duplicateName.parts[0];
if (!first) throw new Error("Missing Ditto part fixture");
(duplicateName.parts as TemplateSemanticManifest["parts"][number][]).push({
...first,
key: "another-key",
});
(duplicateKey.parts as TemplateSemanticManifest["parts"][number][]).push({
...first,
name: "another-name",
});
expect(() => validateTemplateSemanticManifest(duplicateName)).toThrow(/duplicate part name/);
expect(() => validateTemplateSemanticManifest(duplicateKey)).toThrow(/duplicate part key/);
});
it("rejects unknown placements, owner lies, synthetic wrappers, and missing or invented chrome", () => {
const unknownRegionPlacement = structuredClone(
getTemplateSemanticManifest("chikorita"),
) as TemplateSemanticManifest;
const unknownHeaderPlacement = structuredClone(
getTemplateSemanticManifest("chikorita"),
) as TemplateSemanticManifest;
const ownerLie = structuredClone(getTemplateSemanticManifest("bronzor")) as TemplateSemanticManifest;
const synthetic = structuredClone(getTemplateSemanticManifest("ditto")) as TemplateSemanticManifest;
const missing = structuredClone(getTemplateSemanticManifest("azurill")) as TemplateSemanticManifest;
const invented = structuredClone(getTemplateSemanticManifest("chikorita")) as TemplateSemanticManifest;
(unknownRegionPlacement.regions[0] as { placement: string }).placement = "footer";
(unknownHeaderPlacement.header as { placement: string }).placement = "footer";
const alias = ownerLie.parts[0]?.binding;
if (alias?.type !== "alias") throw new Error("Missing Bronzor alias fixture");
(alias as { canonicalKind: string }).canonicalKind = "item";
const primitive = synthetic.parts[0]?.binding;
if (primitive?.type !== "primitive") throw new Error("Missing Ditto primitive fixture");
(primitive as unknown as { source: string }).source = "synthetic";
(missing.parts as TemplateSemanticManifest["parts"][number][]).pop();
(invented.parts as TemplateSemanticManifest["parts"][number][]).push({
name: "invented-wrapper",
key: "invented-wrapper",
owner: { kind: "header", key: "header" },
binding: { type: "primitive", primitive: "View", source: "existing" },
route: { parent: "owner", at: "start" },
});
expect(() => validateTemplateSemanticManifest(unknownRegionPlacement)).toThrow(/unknown region placement/);
expect(() => validateTemplateSemanticManifest(unknownHeaderPlacement)).toThrow(/unknown header placement/);
expect(() => validateTemplateSemanticManifest(ownerLie)).toThrow(/aliases a non-owner primitive/);
expect(() => validateTemplateSemanticManifest(synthetic)).toThrow(/synthetic wrapper/);
expect(() => validateTemplateSemanticManifest(missing)).toThrow(/frozen renderer contract/);
expect(() => validateTemplateSemanticManifest(invented)).toThrow(/frozen renderer contract/);
});
it("rejects exact renderer-contract mutations across layout, routing, bindings, conditions, and chrome", () => {
const removedMain = structuredClone(getTemplateSemanticManifest("chikorita")) as TemplateSemanticManifest;
const wrongOrigin = structuredClone(getTemplateSemanticManifest("chikorita")) as TemplateSemanticManifest;
const wrongFlow = structuredClone(getTemplateSemanticManifest("bronzor")) as TemplateSemanticManifest;
const wrongPrimitive = structuredClone(getTemplateSemanticManifest("ditto")) as TemplateSemanticManifest;
const primitiveToAlias = structuredClone(getTemplateSemanticManifest("rhyhorn")) as TemplateSemanticManifest;
const aliasToPrimitive = structuredClone(getTemplateSemanticManifest("bronzor")) as TemplateSemanticManifest;
const wrongSummary = structuredClone(getTemplateSemanticManifest("leafish")) as TemplateSemanticManifest;
const unknownOwner = structuredClone(getTemplateSemanticManifest("bronzor")) as TemplateSemanticManifest;
const changedCondition = structuredClone(getTemplateSemanticManifest("azurill")) as TemplateSemanticManifest;
const changedSelectorCondition = structuredClone(getTemplateSemanticManifest("meowth")) as TemplateSemanticManifest;
const missingChrome = structuredClone(getTemplateSemanticManifest("ditto")) as TemplateSemanticManifest;
const extraChrome = structuredClone(getTemplateSemanticManifest("ditto")) as TemplateSemanticManifest;
(removedMain.regions as TemplateSemanticManifest["regions"][number][]).splice(1, 1);
(wrongOrigin.regions[1]?.origins as string[])[0] = "sidebar";
(wrongFlow.regions[1] as { flow: string }).flow = "sequential";
const headerBand = wrongPrimitive.parts.find((part) => part.name === "header-band");
if (headerBand?.binding.type !== "primitive") throw new Error("Missing Ditto header band");
(headerBand.binding as { primitive: string }).primitive = "Text";
const content = primitiveToAlias.parts.find((part) => part.name === "contact-item-content");
if (!content) throw new Error("Missing Rhyhorn content");
(content as unknown as { binding: object; route?: object }).binding = {
type: "alias",
canonicalKind: "contact-item",
token: "contact-item-content",
};
delete (content as unknown as { route?: object }).route;
const row = aliasToPrimitive.parts[0];
if (!row) throw new Error("Missing Bronzor row");
(row as unknown as { binding: object; route?: object }).binding = {
type: "primitive",
primitive: "View",
source: "existing",
};
(row as unknown as { route?: object }).route = { parent: "owner", at: "start" };
if (!wrongSummary.specialSummary) throw new Error("Missing Leafish summary");
(wrongSummary.specialSummary as { source: string }).source = "main-with-header";
(unknownOwner.parts[0]?.owner as unknown as { key: string }).key = "unknown";
const line = changedCondition.parts.find((part) => part.name === "timeline-line");
if (!line || !("columns" in line.owner)) throw new Error("Missing Azurill timeline line");
delete (line.owner as { columns?: number }).columns;
const leading = changedSelectorCondition.parts.find((part) => part.name === "inline-item-header-leading");
if (leading?.binding.type !== "primitive" || !leading.route || !Array.isArray(leading.route.take)) {
throw new Error("Missing Meowth leading selectors");
}
(leading.route.take[0]?.sectionTypes as string[])[0] = "education";
(missingChrome.parts as TemplateSemanticManifest["parts"][number][]).pop();
const extra = structuredClone(extraChrome.parts[0]);
if (!extra) throw new Error("Missing Ditto chrome");
(extra as { name: string; key: string }).name = "extra";
(extra as { name: string; key: string }).key = "extra";
(extraChrome.parts as TemplateSemanticManifest["parts"][number][]).push(extra);
for (const mutation of [
removedMain,
wrongOrigin,
wrongFlow,
wrongPrimitive,
primitiveToAlias,
aliasToPrimitive,
wrongSummary,
unknownOwner,
changedCondition,
changedSelectorCondition,
missingChrome,
extraChrome,
]) {
expect(() => validateTemplateSemanticManifest(mutation)).toThrow();
}
});
it("rejects empty and unknown selector section conditions before exact-contract comparison", () => {
const empty = structuredClone(getTemplateSemanticManifest("meowth")) as TemplateSemanticManifest;
const unknown = structuredClone(getTemplateSemanticManifest("meowth")) as TemplateSemanticManifest;
const getFirstSelector = (manifest: TemplateSemanticManifest) => {
const leading = manifest.parts.find((part) => part.name === "inline-item-header-leading");
if (
leading?.binding.type !== "primitive" ||
!leading.route ||
!Array.isArray(leading.route.take) ||
!leading.route.take[0]
) {
throw new Error("Missing Meowth leading selector");
}
return leading.route.take[0];
};
(getFirstSelector(empty) as { sectionTypes: string[] }).sectionTypes = [];
(getFirstSelector(unknown) as { sectionTypes: string[] }).sectionTypes = ["unknown"];
expect(() => validateTemplateSemanticManifest(empty)).toThrow(/selector section types must not be empty/);
expect(() => validateTemplateSemanticManifest(unknown)).toThrow(/selector has an unknown section type/);
});
it("rejects cyclic and unsupported-depth primitive routing before exact-contract comparison", () => {
const cycle = structuredClone(getTemplateSemanticManifest("azurill")) as TemplateSemanticManifest;
const tooDeep = structuredClone(getTemplateSemanticManifest("azurill")) as TemplateSemanticManifest;
const marker = cycle.parts.find((part) => part.name === "timeline-marker");
if (marker?.binding.type !== "primitive") throw new Error("Missing Azurill marker");
(marker.route as { parent: string }).parent = "timeline-dot";
const content = tooDeep.parts.find((part) => part.name === "timeline-content");
if (content?.binding.type !== "primitive") throw new Error("Missing Azurill content");
(content.route as { parent: string }).parent = "timeline-dot";
expect(() => validateTemplateSemanticManifest(cycle)).toThrow(/cyclic part routing/);
expect(() => validateTemplateSemanticManifest(tooDeep)).toThrow(/unsupported part routing depth/);
});
it.each(templateSchema.options)("%s builds its manifest-backed tree without key collisions", (template) => {
const tree = buildFixtureTree(template);
const nodes = flattenTree(tree);
const partNames = new Set(
findNodes(tree, (node) => node.kind === "template-part").map((node) => node.attributes.name),
);
const expectedPrimitiveParts = new Set(
getTemplateSemanticManifest(template).parts.flatMap((part) =>
part.binding.type === "primitive" ? [part.name] : [],
),
);
expect(partNames).toEqual(expectedPrimitiveParts);
expect(new Set(nodes.map((node) => node.key)).size).toBe(nodes.length);
});
it("routes every registered primitive part through exactly the child kinds used by all template trees", () => {
const data = buildFixture();
data.basics.phone = "+44 123";
data.basics.location = "London";
data.basics.website = { url: "https://example.com", label: "Website" };
data.sections.education.hidden = false;
data.sections.volunteer.hidden = false;
data.sections.experience.items = [
{
id: "experience/inline",
hidden: false,
company: "Analytical Engines",
position: "Engineer",
location: "London",
period: "1842",
website: { url: "https://inline.example.com", label: "Inline", inlineLink: true },
description: "<p>Built algorithms.</p>",
roles: [],
},
{
id: "experience/nested",
hidden: false,
company: "Difference Engines",
position: "Programmer",
location: "London",
period: "1843",
website: { url: "https://outside.example.com", label: "Outside", inlineLink: false },
description: "",
roles: [
{
id: "role/1",
position: "Programmer",
period: "1843",
description: "<p>Published programs.</p>",
},
],
},
];
data.sections.education.items = [
{
id: "education/coverage",
hidden: false,
school: "University",
area: "Mathematics",
degree: "BSc",
grade: "First",
location: "Oxford",
period: "1835",
website: { url: "", label: "", inlineLink: false },
description: "",
},
];
data.sections.volunteer.items = [
{
id: "volunteer/coverage",
hidden: false,
organization: "Volunteer Guild",
location: "Paris",
period: "1840",
website: { url: "", label: "", inlineLink: false },
description: "",
},
];
const page = {
fullWidth: false,
main: ["summary", "experience", "education", "volunteer", "skills"],
sidebar: [],
};
const partNames = new Set<string>();
const childPairs = new Set<string>();
for (const template of templateSchema.options) {
const tree = buildSemanticTree({ data, template, page, pageNumber: 1, showHeader: true });
for (const part of findNodes(tree, (node) => node.kind === "template-part")) {
const name = part.attributes.name;
if (!name) throw new Error("Template part is missing its name");
partNames.add(name);
for (const child of part.children) {
childPairs.add(`${name}:${child.kind}`);
}
}
}
expect([...partNames].sort()).toEqual(
[
"contact-item-content",
"contact-offset",
"contact-row-primary",
"contact-row-secondary",
"education-grade-row",
"featured-summary",
"header-band",
"header-body",
"header-contact-band",
"header-divider",
"header-intro",
"header-name-rule",
"inline-item-header-leading",
"inline-item-header-middle",
"inline-item-header-trailing",
"picture-anchor",
"sidebar-background",
"timeline-content",
"timeline-dot",
"timeline-line",
"timeline-marker",
].sort(),
);
expect([...childPairs].sort()).toEqual(
[
"contact-item-content:field",
"contact-item-content:icon",
"contact-item-content:link",
"contact-row-primary:contact-item",
"contact-row-secondary:contact-item",
"education-grade-row:combined-text",
"featured-summary:section",
"header-band:headline",
"header-band:name",
"header-band:template-part",
"header-body:headline",
"header-body:name",
"header-body:picture",
"header-body:section",
"header-contact-band:contact-list",
"header-divider:headline",
"header-divider:name",
"header-intro:template-part",
"inline-item-header-leading:combined-text",
"inline-item-header-leading:field",
"inline-item-header-middle:field",
"inline-item-header-middle:link",
"inline-item-header-trailing:field",
"picture-anchor:picture",
"timeline-content:field",
"timeline-content:item",
"timeline-content:item-header",
"timeline-content:level",
"timeline-content:link",
"timeline-marker:template-part",
].sort(),
);
});
it("nests Azurill's dot under its existing marker primitive", () => {
const tree = buildFixtureTree("azurill");
const marker = findNodes(
tree,
(node) => node.kind === "template-part" && node.attributes.name === "timeline-marker",
)[0];
expect(marker).toBeDefined();
expect(
marker && findNodes(marker, (node) => node.kind === "template-part" && node.attributes.name === "timeline-dot"),
).toHaveLength(1);
});
it("routes Azurill's canonical items through its exact timeline primitive hierarchy", () => {
const tree = buildFixtureTree("azurill");
const experience = findNodes(tree, (node) => node.kind === "section" && node.id === "experience")[0];
const sectionItems = experience && findNodes(experience, (node) => node.kind === "section-items")[0];
const item = sectionItems?.children.find((node) => node.kind === "item");
const marker = item && findPart(item, "timeline-marker");
const content = item && findPart(item, "timeline-content");
expect(sectionItems && childLabels(sectionItems)).toEqual(["timeline-line", "item"]);
expect(item && childLabels(item)).toEqual(["timeline-marker", "timeline-content"]);
expect(marker && childLabels(marker)).toEqual(["timeline-dot"]);
expect(content?.children.some((node) => node.kind === "item-header")).toBe(true);
});
it("routes Leafish's header content through intro, body, and contact-band primitives", () => {
const tree = buildFixtureTree("leafish");
const header = findNodes(tree, (node) => node.kind === "header")[0];
const intro = header && findPart(header, "header-intro");
const body = intro && findPart(intro, "header-body");
const contactBand = header && findPart(header, "header-contact-band");
expect(header && childLabels(header)).toEqual(["header-intro", "header-contact-band"]);
expect(intro && childLabels(intro)).toEqual(["header-body"]);
expect(body && childLabels(body)).toEqual(["picture", "name", "headline", "section"]);
expect(contactBand && childLabels(contactBand)).toEqual(["contact-list"]);
});
it("routes Ditto's header content through its exact primitive hierarchy and order", () => {
const tree = buildFixtureTree("ditto");
const header = findNodes(tree, (node) => node.kind === "header")[0];
const band = header && findPart(header, "header-band");
const anchor = band && findPart(band, "picture-anchor");
expect(header && childLabels(header)).toEqual(["header-band", "contact-offset", "contact-list"]);
expect(band && childLabels(band)).toEqual(["picture-anchor", "name", "headline"]);
expect(anchor && childLabels(anchor)).toEqual(["picture"]);
});
it.each([
["bronzor", "section", "interleaved-section-row"],
["ditgar", "region", "sidebar-background"],
["gengar", "region", "sidebar-background"],
] as const)("%s applies %s to its canonical %s owner without an alias node", (template, kind, token) => {
const tree = buildFixtureTree(template);
const owner = findNodes(
tree,
(node) => node.kind === kind && node.attributes.part?.split(" ").includes(token) === true,
)[0];
expect(owner).toBeDefined();
expect(findPart(tree, token)).toBeUndefined();
});
it("counts template aliases on their canonical owners in the binding inventory", () => {
const tree = buildFixtureTree("bronzor");
const inventory = createBindingInventory(tree, getTemplateSemanticBindingRegistry("bronzor"));
const aliasedSections = findNodes(
tree,
(node) => node.kind === "section" && node.attributes.part === "interleaved-section-row",
);
expect(aliasedSections.length).toBeGreaterThan(0);
expect(aliasedSections.map((node) => inventory.aliasTokensByNodeKey[node.key])).toEqual(
aliasedSections.map(() => ["interleaved-section-row"]),
);
expect(
Object.values(inventory.bindings).some(
(binding) => binding.type === "alias" && binding.token === "interleaved-section-row",
),
).toBe(false);
});
it("binds Rhyhorn's outer contacts to View, nests their real content primitive, and aliases the rendered last owner", () => {
const data = buildFixture();
data.basics.location = "London";
const tree = buildSemanticTree({
data,
template: "rhyhorn",
page: { fullWidth: true, main: [], sidebar: [] },
pageNumber: 1,
showHeader: true,
});
const contacts = findNodes(tree, (node) => node.kind === "contact-item");
const inventory = createBindingInventory(tree, getTemplateSemanticBindingRegistry("rhyhorn"));
expect(contacts).toHaveLength(2);
expect(contacts.map((node) => childLabels(node))).toEqual([["contact-item-content"], ["contact-item-content"]]);
expect(contacts.map((node) => inventory.bindings[node.key])).toEqual([
{ type: "primitive", primitive: "View", source: "existing" },
{ type: "primitive", primitive: "View", source: "existing" },
]);
expect(
contacts.map((node) => {
const content = findPart(node, "contact-item-content");
return content && inventory.bindings[content.key];
}),
).toEqual([
{ type: "primitive", primitive: "Link", source: "existing" },
{ type: "primitive", primitive: "View", source: "existing" },
]);
const semanticLink = findNodes(contacts[0] ?? tree, (node) => node.kind === "link")[0];
const linkedContent = findPart(contacts[0] ?? tree, "contact-item-content");
expect(semanticLink && inventory.bindings[semanticLink.key]).toEqual({
type: "alias",
canonicalKind: "template-part",
canonicalNodeKey: linkedContent?.key,
token: "structured-link",
});
expect(
[contacts[0]?.key, linkedContent?.key, semanticLink?.key].filter(
(key): key is string => key !== undefined && inventory.bindings[key]?.type === "primitive",
),
).toEqual([contacts[0]?.key, linkedContent?.key]);
expect(contacts[0]?.attributes.part).toBeUndefined();
expect(contacts[1]?.attributes.part).toBe("contact-item-last");
expect(inventory.aliasTokensByNodeKey[contacts[1]?.key ?? ""]).toEqual(["contact-item-last"]);
expect(findPart(tree, "contact-item-last")).toBeUndefined();
});
it("does not invent Rhyhorn last-contact chrome when no contact is rendered or the header is hidden", () => {
const data = buildFixture();
data.basics.email = "";
data.basics.customFields = [];
const input = {
data,
template: "rhyhorn" as const,
page: { fullWidth: true, main: [], sidebar: [] },
pageNumber: 1,
};
const empty = buildSemanticTree({ ...input, showHeader: true });
const hidden = buildSemanticTree({ ...input, showHeader: false });
expect(findNodes(empty, (node) => node.kind === "contact-item")).toHaveLength(0);
expect(findNodes(empty, (node) => node.attributes.part === "contact-item-last")).toHaveLength(0);
expect(findNodes(hidden, (node) => node.kind === "header")).toHaveLength(0);
});
it.each(["bronzor", "scizor"] as const)(
"%s preserves layout origin separately from physical placement",
(template) => {
const tree = buildFixtureTree(template);
const sidebarOrigin = findNodes(tree, (node) => node.kind === "section" && node.attributes.origin === "sidebar");
expect(sidebarOrigin.length).toBeGreaterThan(0);
expect(sidebarOrigin.every((node) => node.attributes.placement === "main")).toBe(true);
},
);
it.each([
["ditgar", "sidebar"],
["gengar", "sidebar"],
["glalie", "sidebar"],
["azurill", "main"],
["leafish", "main"],
["pikachu", "main"],
] as const)("%s places its header in the renderer-owned %s column", (template, placement) => {
const headerRegion = findNodes(
buildFixtureTree(template),
(node) => node.kind === "region" && node.attributes.region === "header",
)[0];
expect(headerRegion?.attributes.placement).toBe(placement);
});
it.each(["ditgar", "gengar"] as const)("%s moves the first-page summary into its featured region", (template) => {
const tree = buildFixtureTree(template);
const featured = findNodes(tree, (node) => node.kind === "region" && node.attributes.region === "featured")[0];
const summaries = findNodes(tree, (node) => node.kind === "section" && node.id === "summary");
expect(featured && findNodes(featured, (node) => node.kind === "section" && node.id === "summary")).toHaveLength(1);
expect(summaries).toHaveLength(1);
expect(summaries[0]?.roles).toContain("featured-summary");
});
it("places Leafish summary in the header only while the renderer shows that header", () => {
const withHeader = buildFixtureTree("leafish", true);
const withoutHeader = buildFixtureTree("leafish", false);
const headerRegion = findNodes(
withHeader,
(node) => node.kind === "region" && node.attributes.region === "header",
)[0];
expect(
headerRegion && findNodes(headerRegion, (node) => node.kind === "section" && node.id === "summary"),
).toHaveLength(1);
expect(findNodes(withHeader, (node) => node.kind === "section" && node.id === "summary")).toHaveLength(1);
expect(findNodes(withoutHeader, (node) => node.kind === "section" && node.id === "summary")).toHaveLength(0);
});
it("omits Leafish's header summary when the shared Summary renderer is not visible", () => {
const data = buildFixture();
data.summary.content = "";
const tree = buildSemanticTree({
data,
template: "leafish",
page: { fullWidth: true, main: ["summary", "experience"], sidebar: [] },
pageNumber: 1,
showHeader: true,
});
expect(findNodes(tree, (node) => node.kind === "section" && node.id === "summary")).toHaveLength(0);
});
it.each(["ditgar", "gengar"] as const)("%s leaves summary in main when featured chrome is absent", (template) => {
const tree = buildFixtureTree(template, false);
const summary = findNodes(tree, (node) => node.kind === "section" && node.id === "summary");
expect(findNodes(tree, (node) => node.kind === "region" && node.attributes.region === "featured")).toHaveLength(0);
expect(summary).toHaveLength(1);
expect(summary[0]?.attributes).toMatchObject({ origin: "main", placement: "main" });
expect(summary[0]?.roles).not.toContain("featured-summary");
});
it("emits Ditgar's existing item-header border owner for language and reference items", () => {
const data = buildFixture();
data.sections.languages.items = [
{ id: "language/1", hidden: false, language: "English", fluency: "Native", level: 4 },
];
data.sections.references.items = [
{
id: "reference/1",
hidden: false,
name: "Charles Babbage",
position: "Inventor",
phone: "+44 123",
website: { url: "", label: "", inlineLink: false },
description: "",
},
];
const tree = buildSemanticTree({
data,
template: "ditgar",
page: { fullWidth: true, main: ["languages", "references"], sidebar: [] },
pageNumber: 1,
showHeader: false,
});
for (const itemId of ["language/1", "reference/1"]) {
const item = findNodes(tree, (node) => node.kind === "item" && node.id === itemId)[0];
const header = item && findNodes(item, (node) => node.kind === "item-header")[0];
expect(header).toBeDefined();
expect(header?.attributes.part).toBe("item-header-border");
expect(header && findPart(header, "item-header-border")).toBeUndefined();
}
});
it("keeps Ditgar's item-header border owner when optional language and reference header values are empty", () => {
const data = buildFixture();
data.sections.languages.hidden = false;
data.sections.references.hidden = false;
data.sections.languages.items = [
{ id: "language/empty", hidden: false, language: "English", fluency: "", level: 4 },
];
data.sections.references.items = [
{
id: "reference/empty",
hidden: false,
name: "Charles Babbage",
position: "",
phone: "",
website: { url: "", label: "", inlineLink: false },
description: "<p>Reference body</p>",
},
];
const tree = buildSemanticTree({
data,
template: "ditgar",
page: { fullWidth: true, main: ["languages", "references"], sidebar: [] },
pageNumber: 1,
showHeader: false,
});
for (const itemId of ["language/empty", "reference/empty"]) {
const item = findNodes(tree, (node) => node.kind === "item" && node.id === itemId)[0];
const headers = item ? item.children.filter((node) => node.kind === "item-header") : [];
expect(item).toBeDefined();
expect(headers).toHaveLength(1);
expect(headers[0]?.attributes.part).toBe("item-header-border");
}
});
it("keeps Meowth's three inline header primitives when optional header slots are empty", () => {
const data = buildFixture();
data.sections.experience.hidden = false;
data.sections.education.hidden = false;
data.sections.volunteer.hidden = false;
data.sections.experience.items = [
{
id: "experience/empty",
hidden: false,
company: "Analytical Engines",
position: "",
location: "",
period: "",
website: { url: "", label: "", inlineLink: false },
description: "<p>Experience body</p>",
roles: [],
},
];
data.sections.education.items = [
{
id: "education/empty",
hidden: false,
school: "University",
area: "",
degree: "",
grade: "",
location: "",
period: "",
website: { url: "", label: "", inlineLink: false },
description: "<p>Education body</p>",
},
];
data.sections.volunteer.items = [
{
id: "volunteer/empty",
hidden: false,
organization: "Volunteer Guild",
location: "",
period: "",
website: { url: "", label: "", inlineLink: false },
description: "<p>Volunteer body</p>",
},
];
const tree = buildSemanticTree({
data,
template: "meowth",
page: { fullWidth: true, main: ["experience", "education", "volunteer"], sidebar: [] },
pageNumber: 1,
showHeader: false,
});
for (const itemId of ["experience/empty", "education/empty", "volunteer/empty"]) {
const item = findNodes(tree, (node) => node.kind === "item" && node.id === itemId)[0];
const header = item?.children.find((node) => node.kind === "item-header");
expect(item).toBeDefined();
expect(header && childLabels(header)).toEqual([
"inline-item-header-leading",
"inline-item-header-middle",
"inline-item-header-trailing",
]);
expect(header?.children[0]?.children).toEqual([]);
expect(header?.children[1]?.children).toHaveLength(1);
expect(header?.children[2]?.children).toEqual([]);
}
});
it("routes Meowth header fields by section while leaving education grade and location after the inline parts", () => {
const data = buildFixture();
data.sections.experience.hidden = false;
data.sections.education.hidden = false;
data.sections.volunteer.hidden = false;
data.sections.experience.items = [
{
id: "experience/routing",
hidden: false,
company: "Analytical Engines",
position: "Engineer",
location: "London",
period: "1842",
website: { url: "", label: "", inlineLink: false },
description: "",
roles: [],
},
];
data.sections.education.items = [
{
id: "education/routing",
hidden: false,
school: "University",
area: "Mathematics",
degree: "BSc",
grade: "First",
location: "Oxford",
period: "1835",
website: { url: "", label: "", inlineLink: false },
description: "",
},
];
data.sections.volunteer.items = [
{
id: "volunteer/routing",
hidden: false,
organization: "Volunteer Guild",
location: "Paris",
period: "1840",
website: { url: "", label: "", inlineLink: false },
description: "",
},
];
const tree = buildSemanticTree({
data,
template: "meowth",
page: { fullWidth: true, main: ["experience", "education", "volunteer"], sidebar: [] },
pageNumber: 1,
showHeader: false,
});
const itemFor = (itemId: string) => findNodes(tree, (node) => node.kind === "item" && node.id === itemId)[0];
const itemHeader = (itemId: string) => itemFor(itemId)?.children.find((node) => node.kind === "item-header");
const partFields = (header: SemanticNode | undefined, name: string) =>
(header &&
findPart(header, name)?.children.flatMap((node) =>
node.kind === "combined-text" ? node.children.map((child) => child.attributes.name) : [node.attributes.name],
)) ??
[];
const experience = itemHeader("experience/routing");
const education = itemHeader("education/routing");
const volunteer = itemHeader("volunteer/routing");
expect(partFields(experience, "inline-item-header-leading")).toEqual(["position", "location"]);
expect(partFields(volunteer, "inline-item-header-leading")).toEqual(["location"]);
expect(partFields(education, "inline-item-header-leading")).toEqual(["area", "degree"]);
expect(education && childLabels(education)).toEqual([
"inline-item-header-leading",
"inline-item-header-middle",
"inline-item-header-trailing",
]);
const educationItem = itemFor("education/routing");
const gradeRow = educationItem && findPart(educationItem, "education-grade-row");
expect(educationItem && childLabels(educationItem).slice(0, 2)).toEqual(["item-header", "education-grade-row"]);
expect(gradeRow?.children).toHaveLength(1);
expect(gradeRow?.children[0]?.kind).toBe("combined-text");
expect(gradeRow?.children[0]?.children.map((node) => node.attributes.name)).toEqual(["grade", "location"]);
});
it.each(["bronzor", "scizor"] as const)(
"%s keeps duplicate flattened origins and repeated authored pages collision-safe",
(template) => {
const data = buildFixture();
const page = { fullWidth: false, main: ["experience"], sidebar: ["experience"] };
const first = buildSemanticTree({ data, template, page, pageNumber: 1, showHeader: false });
const second = buildSemanticTree({ data, template, page, pageNumber: 2, showHeader: false });
const fullWidth = buildSemanticTree({
data,
template,
page: { ...page, fullWidth: true },
pageNumber: 1,
showHeader: false,
});
const firstSections = findNodes(first, (node) => node.kind === "section" && node.id === "experience");
expect(firstSections).toHaveLength(2);
expect(new Set(firstSections.map((node) => node.key)).size).toBe(2);
expect(findNodes(second, (node) => node.kind === "section").every((node) => node.key.startsWith("page-2/"))).toBe(
true,
);
expect(findNodes(fullWidth, (node) => node.kind === "section" && node.id === "experience")).toHaveLength(1);
},
);
it("publishes stable, deterministic, deeply frozen fingerprint input without functions", () => {
const first = getTemplateSemanticRegistryFingerprintInput();
const second = getTemplateSemanticRegistryFingerprintInput();
const serialized = JSON.stringify(first);
expect(second).toBe(first);
expect(JSON.stringify(second)).toBe(serialized);
expect(Object.isFrozen(first)).toBe(true);
expect(Object.isFrozen(first.azurill.parts)).toBe(true);
expect(flattenValues(first).every((value) => typeof value !== "function")).toBe(true);
expect(() => {
(first.azurill.parts as unknown as object[]).pop();
}).toThrow();
expect(JSON.stringify(getTemplateSemanticRegistryFingerprintInput())).toBe(serialized);
});
it.each(templateSchema.options)(
"%s binds every manifest node to existing chrome without synthetic wrappers",
(template) => {
const tree = buildFixtureTree(template);
const inventory = createBindingInventory(tree, getTemplateSemanticBindingRegistry(template));
const nodeCount = flattenTree(tree).length;
expect(inventory.unboundNodeKeys).toEqual([]);
expect(inventory.syntheticWrapperCount).toBe(0);
expect(Object.keys(inventory.bindings)).toHaveLength(nodeCount);
for (const partNode of findNodes(tree, (node) => node.kind === "template-part")) {
const binding = inventory.bindings[partNode.key];
const declaration = getTemplateSemanticManifest(template).parts.find(
(part) => part.name === partNode.attributes.name,
);
expect(declaration).toBeDefined();
expect(declaration?.binding.type).toBe("primitive");
if (declaration?.binding.type === "primitive" && typeof declaration.binding.primitive === "string") {
expect(binding).toEqual(declaration.binding);
} else if (declaration?.binding.type === "primitive") {
const primitive = declaration.binding.primitive;
if (typeof primitive === "string") throw new Error("Expected conditional primitive");
expect(binding).toMatchObject({
type: "primitive",
source: "existing",
});
expect([primitive.present, primitive.absent]).toContain(
binding?.type === "primitive" ? binding.primitive : undefined,
);
}
}
},
);
});