diff --git a/src/__tests__/pdf-options-builder.test.ts b/src/__tests__/pdf-options-builder.test.ts
new file mode 100644
index 0000000..30641aa
--- /dev/null
+++ b/src/__tests__/pdf-options-builder.test.ts
@@ -0,0 +1,68 @@
+import { describe, it, expect } from "vitest";
+import { buildPdfOptions, PdfRenderOptions } from "../services/browser.js";
+
+describe("buildPdfOptions", () => {
+ it("returns sensible defaults when no options given", () => {
+ const result = buildPdfOptions({});
+ expect(result).toEqual({
+ format: "A4",
+ landscape: false,
+ printBackground: true,
+ margin: { top: "0", right: "0", bottom: "0", left: "0" },
+ });
+ });
+
+ it("passes through all provided options", () => {
+ const opts: PdfRenderOptions = {
+ format: "Letter",
+ landscape: true,
+ printBackground: false,
+ margin: { top: "10mm", bottom: "10mm" },
+ scale: 1.5,
+ pageRanges: "1-3",
+ preferCSSPageSize: true,
+ width: "210mm",
+ height: "297mm",
+ headerTemplate: "Header",
+ footerTemplate: "Footer",
+ displayHeaderFooter: true,
+ };
+ const result = buildPdfOptions(opts);
+ expect(result.format).toBe("Letter");
+ expect(result.landscape).toBe(true);
+ expect(result.printBackground).toBe(false);
+ expect(result.margin).toEqual({ top: "10mm", bottom: "10mm" });
+ expect(result.scale).toBe(1.5);
+ expect(result.pageRanges).toBe("1-3");
+ expect(result.preferCSSPageSize).toBe(true);
+ expect(result.width).toBe("210mm");
+ expect(result.height).toBe("297mm");
+ expect(result.headerTemplate).toBe("Header");
+ expect(result.footerTemplate).toBe("Footer");
+ expect(result.displayHeaderFooter).toBe(true);
+ });
+
+ it("omits undefined optional fields from output", () => {
+ const result = buildPdfOptions({ format: "A4" });
+ expect(result).not.toHaveProperty("scale");
+ expect(result).not.toHaveProperty("pageRanges");
+ expect(result).not.toHaveProperty("preferCSSPageSize");
+ expect(result).not.toHaveProperty("width");
+ expect(result).not.toHaveProperty("height");
+ expect(result).not.toHaveProperty("headerTemplate");
+ expect(result).not.toHaveProperty("footerTemplate");
+ });
+
+ it("handles printBackground false explicitly", () => {
+ const result = buildPdfOptions({ printBackground: false });
+ expect(result.printBackground).toBe(false);
+ });
+
+ it("defaults displayHeaderFooter to false only when explicitly set", () => {
+ const r1 = buildPdfOptions({});
+ expect(r1).not.toHaveProperty("displayHeaderFooter");
+
+ const r2 = buildPdfOptions({ displayHeaderFooter: false });
+ expect(r2.displayHeaderFooter).toBe(false);
+ });
+});
diff --git a/src/__tests__/setup.ts b/src/__tests__/setup.ts
index 543b715..8bfbc29 100644
--- a/src/__tests__/setup.ts
+++ b/src/__tests__/setup.ts
@@ -53,7 +53,10 @@ vi.mock("../services/keys.js", () => {
});
// Mock browser service
-vi.mock("../services/browser.js", () => ({
+vi.mock("../services/browser.js", async (importOriginal) => {
+ const actual = await importOriginal>();
+ return {
+ buildPdfOptions: actual.buildPdfOptions,
initBrowser: vi.fn().mockResolvedValue(undefined),
closeBrowser: vi.fn().mockResolvedValue(undefined),
renderPdf: vi.fn().mockResolvedValue({ pdf: Buffer.from("%PDF-1.4 mock pdf content here"), durationMs: 10 }),
@@ -68,7 +71,8 @@ vi.mock("../services/browser.js", () => ({
uptimeMs: 10000,
browsers: [],
}),
-}));
+};
+});
// Mock verification service
vi.mock("../services/verification.js", () => ({
diff --git a/src/services/browser.ts b/src/services/browser.ts
index 0263cfb..41d4622 100644
--- a/src/services/browser.ts
+++ b/src/services/browser.ts
@@ -221,7 +221,7 @@ export async function closeBrowser(): Promise {
instances.length = 0;
}
-import type { PaperFormat, PuppeteerLifeCycleEvent } from "puppeteer";
+import type { PaperFormat, PDFOptions, PuppeteerLifeCycleEvent } from "puppeteer";
export interface PdfRenderOptions {
format?: PaperFormat;
@@ -238,6 +238,27 @@ export interface PdfRenderOptions {
height?: string;
}
+/** Build a Puppeteer-compatible PDFOptions object from user-supplied render options. */
+export function buildPdfOptions(options: PdfRenderOptions): Record {
+ const result: Record = {
+ format: options.format || "A4",
+ landscape: options.landscape || false,
+ printBackground: options.printBackground !== false,
+ margin: options.margin || { top: "0", right: "0", bottom: "0", left: "0" },
+ };
+
+ if (options.headerTemplate !== undefined) result.headerTemplate = options.headerTemplate;
+ if (options.footerTemplate !== undefined) result.footerTemplate = options.footerTemplate;
+ if (options.displayHeaderFooter !== undefined) result.displayHeaderFooter = options.displayHeaderFooter;
+ if (options.scale !== undefined) result.scale = options.scale;
+ if (options.pageRanges) result.pageRanges = options.pageRanges;
+ if (options.preferCSSPageSize !== undefined) result.preferCSSPageSize = options.preferCSSPageSize;
+ if (options.width) result.width = options.width;
+ if (options.height) result.height = options.height;
+
+ return result;
+}
+
export async function renderPdf(
html: string,
options: PdfRenderOptions = {}
@@ -251,20 +272,7 @@ export async function renderPdf(
(async () => {
await page.setContent(html, { waitUntil: "domcontentloaded", timeout: 15_000 });
await page.addStyleTag({ content: "* { margin: 0; padding: 0; } body { margin: 0; }" });
- const pdf = await page.pdf({
- format: options.format || "A4",
- landscape: options.landscape || false,
- printBackground: options.printBackground !== false,
- margin: options.margin || { top: "0", right: "0", bottom: "0", left: "0" },
- headerTemplate: options.headerTemplate,
- footerTemplate: options.footerTemplate,
- displayHeaderFooter: options.displayHeaderFooter || false,
- ...(options.scale !== undefined && { scale: options.scale }),
- ...(options.pageRanges && { pageRanges: options.pageRanges }),
- ...(options.preferCSSPageSize !== undefined && { preferCSSPageSize: options.preferCSSPageSize }),
- ...(options.width && { width: options.width }),
- ...(options.height && { height: options.height }),
- });
+ const pdf = await page.pdf(buildPdfOptions(options) as Parameters[0]);
return Buffer.from(pdf);
})(),
new Promise((_, reject) => {
@@ -330,20 +338,7 @@ export async function renderUrlPdf(
waitUntil: options.waitUntil || "domcontentloaded",
timeout: 30_000,
});
- const pdf = await page.pdf({
- format: options.format || "A4",
- landscape: options.landscape || false,
- printBackground: options.printBackground !== false,
- margin: options.margin || { top: "0", right: "0", bottom: "0", left: "0" },
- ...(options.headerTemplate && { headerTemplate: options.headerTemplate }),
- ...(options.footerTemplate && { footerTemplate: options.footerTemplate }),
- ...(options.displayHeaderFooter !== undefined && { displayHeaderFooter: options.displayHeaderFooter }),
- ...(options.scale !== undefined && { scale: options.scale }),
- ...(options.pageRanges && { pageRanges: options.pageRanges }),
- ...(options.preferCSSPageSize !== undefined && { preferCSSPageSize: options.preferCSSPageSize }),
- ...(options.width && { width: options.width }),
- ...(options.height && { height: options.height }),
- });
+ const pdf = await page.pdf(buildPdfOptions(options) as Parameters[0]);
return Buffer.from(pdf);
})(),
new Promise((_, reject) => {