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(() => ({ pdf: vi.fn(() => ({ toBlob: vi.fn(async () => new Blob(["%PDF"], { type: "application/pdf" })), })), })); vi.mock("#react-pdf-renderer", async (importOriginal) => ({ ...(await importOriginal()), pdf: rendererMock.pdf, })); vi.mock("./document", () => ({ ResumeDocument: () => null, })); 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: "

Wrote the first algorithm.

", content: "

Compatible overlap

", }, ], }, ], }) as unknown as ResumeData; const createRendererUnsafeResumeData = (): ResumeData => { const data = createLegacyRendererSafeResumeData(); const section = data.customSections[0]; if (!section) throw new Error("Expected a custom section fixture."); section.items = [{ id: "summary-item", hidden: false, content: "

Missing company

" }] as never; return data; }; describe("createResumePdfBlob", () => { beforeEach(() => { rendererMock.pdf.mockClear(); }); it("renders ResumeDocument with data, template, and section title resolver", async () => { const resolveSectionTitle: SectionTitleResolver = (input) => input.defaultEnglishTitle ?? input.sectionId; const { createResumePdfBlob } = await import("./browser"); const data = createLegacyRendererSafeResumeData(); const blob = await createResumePdfBlob({ data, template: "azurill", resolveSectionTitle, }); expect(blob.type).toBe("application/pdf"); expect(rendererMock.pdf).toHaveBeenCalledTimes(1); expect(rendererMock.pdf).toHaveBeenCalledWith( expect.objectContaining({ props: expect.objectContaining({ template: "azurill", resolveSectionTitle, data: expect.objectContaining({ customSections: [ expect.objectContaining({ items: [ expect.objectContaining({ content: "

Compatible overlap

", roles: [], website: { url: "", label: "", inlineLink: false }, }), ], }), ], }), }), }), ); }); it("rejects renderer-unsafe data before browser PDF dispatch", async () => { const { createResumePdfBlob } = await import("./browser"); const error = await createResumePdfBlob({ data: createRendererUnsafeResumeData() }).catch( (caught: unknown) => caught, ); expect(error).toHaveProperty("issues.0.path", ["customSections", 0, "items", 0, "company"]); expect(rendererMock.pdf).not.toHaveBeenCalled(); }); it("returns a rejected Promise when the renderer fails synchronously", async () => { rendererMock.pdf.mockImplementationOnce(() => { throw new Error("renderer failed"); }); const { createResumePdfBlob } = await import("./browser"); const promise = createResumePdfBlob({ data: sampleResumeData }); expect(promise).toBeInstanceOf(Promise); await expect(promise).rejects.toThrow("renderer failed"); }); 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 { createResumePdfBlob } = await import("./browser"); await expect(createResumePdfBlob({ data })).resolves.toHaveProperty("type", "application/pdf"); expect(rendererMock.pdf).toHaveBeenCalledTimes(1); }); });