Testing your PDFs
Assert against your own templates with a real render using @lupinum/nuxt-pdf/test, plus a realistic Vitest example.
The utilities Nuxt PDF is tested with ship as @lupinum/nuxt-pdf/test. They run
a real render through the same pipeline that your server uses. This pipeline includes asset
resolution, font registration, and single-pass or multi-pass layout. You assert against actual
output, not a mocked tree. There is one parser, and it is the one the package's
own suite runs on.
Setup
The parser and rasterizer load pdfjs-dist and @napi-rs/canvas lazily. Install
them as dev dependencies of the project under test:
pnpm add -D pdfjs-dist @napi-rs/canvasThe helpers are runner-agnostic: they throw a PdfAssertionError with an
actionable message rather than depending on Vitest or Jest.
A realistic example
import { describe, it } from 'vitest'
import { expectPdf, renderPdfSfc } from '@lupinum/nuxt-pdf/test'
describe('invoice.vue', () => {
it('renders the customer, a terms link, and an outline', async () => {
const { parsed } = await renderPdfSfc(
'./pdfs/invoice.vue',
{ invoice: { customer: 'Acme Corp', number: 'INV-001', total: 'EUR 1,250.00' } },
{ fonts: [{ family: 'Invoice Sans', src: 'InvoiceSans-Regular.ttf' }] },
)
expectPdf(parsed)
.toHavePageCount(2)
.toContainText('Invoice for Acme Corp', { page: 1 })
.toHaveLink({ destination: 'terms', page: 1 })
.toHaveLink({ url: 'https://example.com/' })
.toHaveOutline([{ title: 'Terms' }])
})
})renderPdfSfc(file, props, options) compiles nested SFC imports, discovers local
images, bundles declared fonts, mounts through the real registry pipeline, and
returns { bytes, parsed, result }. Use renderPdfTemplate(Component, props)
when the test already has an ordinary Vue component. Both helpers accept the
same partial remote and limits options as nuxt.config; the SFC helper also
accepts the same local fonts declarations.
Assertions
expectPdf(parsed) returns a chainable expectation:
| Assertion | Checks |
|---|---|
toHavePageCount(n) | The document has n pages |
toContainText(text, { page? }) | The extracted text contains text (optionally on a page) |
toHaveLink({ destination? , url? , page? }) | A named-destination or external-URL link annotation exists |
toHaveOutline(shape) | The bookmark outline matches the given title hierarchy |
parsePdf exposes the same data directly. The data includes page text, page count, flattened
link annotations, and the outline. Use it when you want to assert without the fluent
API.
Testing a server route
parsePdf also accepts a PdfRenderResult straight from the registry, so route
tests read naturally:
import { parsePdf } from '@lupinum/nuxt-pdf/test'
import { pdf } from '#pdf'
const parsed = await parsePdf(await pdf.invoice.render(props))Pixel-level regressions
For visual regressions, comparePdfSnapshot follows a reviewed-baseline
policy: it writes per-page PNG baselines into a directory when
UPDATE_PDF_BASELINES=1 (or { update: true }) is set, and otherwise compares
each page against them within a pixel threshold:
import { comparePdfSnapshot, renderPdfSfc } from '@lupinum/nuxt-pdf/test'
const { bytes } = await renderPdfSfc('./pdfs/invoice.vue', props)
await comparePdfSnapshot(bytes, './test/baselines/invoice')A failed comparison writes expected, actual, and diff PNGs for every changed
page plus metrics.json under reports/pdf-snapshots. Upload that directory in
CI so the failure can be inspected without reproducing it locally. Semantic
geometry checks are available through parsePdf(...).pages[n].textRuns; compare
coordinates with a tolerance instead of exact equality.
The full set of exports and options is in the test utilities reference.