Files
Reactive-Resume/packages/pdf/src/semantic/base-reset-fidelity.test.tsx
T
2026-07-30 12:39:15 +02:00

159 lines
6.0 KiB
TypeScript

import type { ResumeData } from "@reactive-resume/schema/resume/data";
import type { Template } from "@reactive-resume/schema/templates";
import { describe, expect, it } from "vitest";
import { pdf } from "@react-pdf/renderer";
import { createElement } from "react";
import { defaultResumeData } from "@reactive-resume/schema/resume/default";
import { ResumeDocument } from "../document";
type HostNode = {
type: string;
style?: unknown;
value?: string;
children?: HostNode[];
};
const nodeText = (node: HostNode): string =>
node.value ?? (node.children ?? []).map((child) => nodeText(child)).join("");
const findText = (node: HostNode, text: string): HostNode | undefined => {
if (node.type === "TEXT" && nodeText(node) === text) return node;
for (const child of node.children ?? []) {
const match = findText(child, text);
if (match) return match;
}
};
const mergedStyle = (node: HostNode | undefined): Record<string, unknown> =>
Object.assign({}, ...(Array.isArray(node?.style) ? node.style : node?.style ? [node.style] : []));
const buildFixture = (template: Template, rule = ""): ResumeData => {
const data = structuredClone(defaultResumeData);
data.picture.hidden = true;
data.basics.name = "Ada Lovelace";
data.metadata.design.colors.text = "#111111";
data.metadata.design.colors.background = "#eeeeee";
data.metadata.design.colors.primary = "#663399";
data.metadata.typography.heading.fontWeights = ["400", "700"];
data.sections.skills.title = "Expertise";
data.sections.skills.items = [
{
id: "skill-1",
hidden: false,
icon: "",
iconColor: "",
name: "TypeScript",
proficiency: "Expert",
level: 3,
keywords: [],
},
];
data.metadata.layout.pages =
template === "chikorita"
? [{ fullWidth: false, main: [], sidebar: ["skills"] }]
: [{ fullWidth: true, main: ["skills"], sidebar: [] }];
const stylesheet = { languageVersion: 1, text: `@version 1; ${rule}` };
data.metadata.stylesheet = { mode: "semantic", source: stylesheet, applied: stylesheet };
return data;
};
const finalTextStyle = async (template: Template, text: string, rule = "") => {
const data = buildFixture(template, rule);
const element = createElement(ResumeDocument, { data, template }) as unknown as Parameters<typeof pdf>[0];
const instance = pdf(element);
await expect.poll(() => instance.container.document).not.toBeNull();
return mergedStyle(findText(instance.container.document as HostNode, text));
};
const finalOnyxCompanyStyle = async (keyword?: "inherit" | "initial" | "revert" | "unset") => {
const data = structuredClone(defaultResumeData);
data.picture.hidden = true;
data.metadata.typography.body.fontWeights = ["400", "500"];
data.sections.experience.items = [
{
id: "experience-1",
hidden: false,
company: "Analytical Engines",
position: "Engineer",
location: "London",
period: "1842",
website: { url: "", label: "", inlineLink: false },
description: "",
roles: [],
},
];
data.metadata.layout.pages = [{ fullWidth: true, main: ["experience"], sidebar: [] }];
const text = `@version 1; ${
keyword ? `section[type="experience"] field[name="company"] { font-weight: ${keyword}; }` : ""
}`;
const stylesheet = { languageVersion: 1, text };
data.metadata.stylesheet = { mode: "semantic", source: stylesheet, applied: stylesheet };
const element = createElement(ResumeDocument, { data, template: "onyx" }) as unknown as Parameters<typeof pdf>[0];
const instance = pdf(element);
await expect.poll(() => instance.container.document).not.toBeNull();
return mergedStyle(findText(instance.container.document as HostNode, "Analytical Engines"));
};
describe("PDF semantic base and reset fidelity", () => {
it("keeps Bronzor's first heading weight and lets an explicit last weight override it", async () => {
expect(await finalTextStyle("bronzor", "Expertise")).toMatchObject({ fontWeight: "400" });
expect(await finalTextStyle("bronzor", "Expertise", "section-heading { font-weight: 700; }")).toMatchObject({
fontWeight: "700",
});
});
it.each(["inherit", "unset", "revert"])(
"resets Bronzor's heading weight with %s against the actual host base",
async (keyword) => {
expect(
await finalTextStyle("bronzor", "Expertise", `section-heading { font-weight: ${keyword}; }`),
).toMatchObject({ fontWeight: "400" });
},
);
it("cancels Bronzor's heading weight with the CSS initial value", async () => {
expect(await finalTextStyle("bronzor", "Expertise", "section-heading { font-weight: initial; }")).toMatchObject({
fontWeight: undefined,
});
});
it("keeps Chikorita's sidebar placement color and lets an explicit body color override it", async () => {
expect(await finalTextStyle("chikorita", "TypeScript")).toMatchObject({ color: "#eeeeee" });
expect(await finalTextStyle("chikorita", "TypeScript", "field[name='name'] { color: #111111; }")).toMatchObject({
color: "#111111",
});
});
it.each(["inherit", "unset"])(
"cancels Chikorita's sidebar field color with %s and emits the inherited parent value",
async (keyword) => {
expect(
await finalTextStyle("chikorita", "TypeScript", `field[name='name'] { color: ${keyword}; }`),
).toMatchObject({ color: "#111111" });
},
);
it("restores Chikorita's sidebar field color with revert", async () => {
expect(await finalTextStyle("chikorita", "TypeScript", "field[name='name'] { color: revert; }")).toMatchObject({
color: "#eeeeee",
});
});
it.each(["inherit", "unset"] as const)(
"cancels Onyx's local company weight with %s and emits the inherited parent value",
async (keyword) => {
expect(await finalOnyxCompanyStyle(keyword)).toMatchObject({ fontWeight: "400" });
},
);
it("cancels Onyx's local company weight with initial", async () => {
expect(await finalOnyxCompanyStyle("initial")).toMatchObject({ fontWeight: undefined });
});
it("restores Onyx's local company weight with revert", async () => {
expect(await finalOnyxCompanyStyle()).toMatchObject({ fontWeight: "500" });
expect(await finalOnyxCompanyStyle("revert")).toMatchObject({ fontWeight: "500" });
});
});