import { createHash } from "node:crypto"; import { createCanvas } from "@napi-rs/canvas"; import { describe, expect, it } from "vitest"; import { createPdfVisualDiffReport, renderPdfVisualDiffHtml, serializePdfVisualDiffJson, type PdfDocumentSnapshot, type PdfPageRaster, } from "../src/index.js"; function raster(color: string): PdfPageRaster { const canvas = createCanvas(40, 50); const context = canvas.getContext("2d"); context.fillStyle = "#ffffff"; context.fillRect(0, 0, 40, 50); context.fillStyle = color; context.fillRect(10, 10, 20, 20); const png = Uint8Array.from(canvas.toBuffer("image/png")); return { widthPx: 40, heightPx: 50, dpi: 144, sha256: createHash("sha256").update(png).digest("hex"), png, }; } function snapshot(label: string, pageRaster: PdfPageRaster): PdfDocumentSnapshot { return { schemaVersion: 1, source: { kind: "custom", label }, sha256: "a".repeat(64), pageCount: 1, contentText: "相同正文", pages: [ { pageNumber: 1, widthPt: 20, heightPt: 25, rotation: 0, items: [], lines: [], contentText: "相同正文", raster: pageRaster, }, ], }; } describe("PDF 视觉差异报告", () => { it("汇总栅格门禁并生成内嵌图片的安全 HTML", async () => { const report = await createPdfVisualDiffReport( snapshot("<基线>", raster("#111111")), snapshot("候选", raster("#cc0000")), { generatedAt: "2026-07-31T00:00:00.000Z" }, ); const html = renderPdfVisualDiffHtml(report); expect(report.status).toBe("failed"); expect(html).toContain("<基线>"); expect(html).toContain("data:image/png;base64,"); expect(html).toContain("差异热力图"); expect(html).not.toContain("<基线>"); }); it("JSON 只保留图片字节数和稳定摘要", async () => { const page = raster("#111111"); const report = await createPdfVisualDiffReport( snapshot("基线", page), snapshot("候选", page), ); const json = serializePdfVisualDiffJson(report); expect(json).toContain('"overlaySha256"'); expect(json).toContain('"byteLength"'); expect(json).not.toContain('"0": 137'); }); it("在报告中展示未配对的溢出页面", async () => { const baseline = snapshot("基线", raster("#111111")); const candidatePage = snapshot("候选", raster("#111111")); const candidate: PdfDocumentSnapshot = { ...candidatePage, pageCount: 2, pages: [ candidatePage.pages[0]!, { ...candidatePage.pages[0]!, pageNumber: 2, }, ], }; const report = await createPdfVisualDiffReport(baseline, candidate); const overflow = report.pages[1]; expect(overflow?.pair.status).toBe("candidate-only"); expect(overflow?.unpairedPng?.byteLength).toBeGreaterThan(0); expect(renderPdfVisualDiffHtml(report)).toContain("未配对页面"); }); });