Files
Reactive-Resume/packages/pdf/src/server.test.tsx
T
Amruth Pillai 722f5fa14c fix(pdf): close the remaining gaps in the forme port
Work around more Forme 0.25 layout defects in the converter: row-reverse
laid out as row, min and max widths ignored along a row, text dropping
view children, empty text taking a line, overflowing optimal line
breaks, a text in a splitting row wrecking later pages, page breaks on
row items, four equal border sides, percentage corner radii, pictures
under their shadow, and imported table rows. Rotation is left out while
Forme misplaces rotated boxes; the stylesheet editor warns about it and
about percentage padding.

The page map rebuilds blocks Forme drops from its layout, the browser
renderer rejects documents whose fonts can't load so callers fall back
to the server PDF, and every render gets fresh font entries. Glalie's
sidebar is one band at the combined tint.

Tests are re-baselined where they encoded react-pdf metrics, and engine
limits (list marker keep-together, RTL line order, characters above
U+FFFF, percentage padding) are expected failures. The plan logs the
migration in section 13.
2026-09-29 07:37:25 +02:00

132 lines
3.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { ResumeData } from "@reactive-resume/schema/resume/data";
import type { SectionTitleResolver } from "./section-title";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { sampleResumeData } from "@reactive-resume/schema/resume/sample";
const rendererMock = vi.hoisted(() => ({
renderResume: vi.fn(async () => ({
pdf: new TextEncoder().encode("%PDF"),
pageMap: { pages: [], nodes: [] },
layout: { pages: [] },
missingFonts: [],
warnings: [],
})),
}));
const createRendererUnsafeResumeData = (): ResumeData => {
const data = structuredClone(sampleResumeData);
data.customSections = [
{
id: "custom-experience",
type: "experience",
title: "Experience",
icon: "",
columns: 1,
hidden: false,
keepTogether: false,
startOnNewPage: false,
items: [{ id: "summary-shaped-item", hidden: false, content: "<p>Missing company</p>" }],
} as never,
];
return data;
};
const createLegacyRendererSafeResumeData = (): ResumeData =>
({
...structuredClone(sampleResumeData),
customSections: [
{
id: "custom-experience",
type: "experience",
title: "Experience",
icon: "",
columns: 1,
hidden: false,
keepTogether: false,
startOnNewPage: false,
items: [
{
id: "experience-item",
hidden: false,
company: "Analytical Engines",
position: "Programmer",
location: "London",
period: "1842–1843",
description: "<p>Wrote the first algorithm.</p>",
content: "<p>Compatible overlap</p>",
},
],
},
],
}) as unknown as ResumeData;
vi.mock("./forme/render", () => ({ renderResume: rendererMock.renderResume }));
describe("createResumePdfFile", () => {
beforeEach(() => {
rendererMock.renderResume.mockClear();
});
it("renders ResumeDocument with data, filename, template, and section title resolver", async () => {
const resolveSectionTitle: SectionTitleResolver = (input) => input.defaultEnglishTitle ?? input.sectionId;
const { createResumePdfFile } = await import("./server");
const data = createLegacyRendererSafeResumeData();
const file = await createResumePdfFile({
data,
filename: "resume.pdf",
template: "azurill",
resolveSectionTitle,
});
expect(file.name).toBe("resume.pdf");
expect(file.type).toBe("application/pdf");
expect(await file.text()).toBe("%PDF");
expect(rendererMock.renderResume).toHaveBeenCalledTimes(1);
expect(rendererMock.renderResume).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({
template: "azurill",
resolveSectionTitle,
data: expect.objectContaining({
customSections: [
expect.objectContaining({
items: [
expect.objectContaining({
content: "<p>Compatible overlap</p>",
roles: [],
website: { url: "", label: "", inlineLink: false },
}),
],
}),
],
}),
}),
);
});
it("renders with base styles when the source is fatal", async () => {
const data = structuredClone(sampleResumeData);
data.metadata.stylesheet = { mode: "semantic", source: { languageVersion: 2, text: "@version 2;" } };
const { createResumePdfFile } = await import("./server");
await expect(createResumePdfFile({ data, filename: "resume.pdf" })).resolves.toHaveProperty(
"type",
"application/pdf",
);
expect(rendererMock.renderResume).toHaveBeenCalledTimes(1);
});
it("rejects renderer-unsafe data at the server boundary before React PDF dispatch", async () => {
const { createResumePdfFile } = await import("./server");
const error = await createResumePdfFile({
data: createRendererUnsafeResumeData(),
filename: "resume.pdf",
}).catch((caught: unknown) => caught);
expect(error).toHaveProperty("issues.0.path", ["customSections", 0, "items", 0, "company"]);
expect(rendererMock.renderResume).not.toHaveBeenCalled();
});
});