Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions packages/core/test/pdf-snapshot.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,21 @@ describe('PDF snapshots', () => {
new RegExp(`Requested ${pageCount} pages but the maximum per request is ${MAX_PAGES}`));
});

it('rejects a page that rasterizes below Percy\'s minimum', async () => {
// fitScale only clamps the upper bound, so a tiny MediaBox still renders
// under the 10px floor -- and the caller can fix it by raising `scale`,
// which is what makes it a 400 rather than a 500.
let [body, res] = await postRaw({
name: 'doc',
pdf: { content: b64(buildPdf({ width: 4, height: 4 })) },
scale: 2
});

expect(res.statusCode).toBe(400);
expect(body.error).toMatch(
/Page 1 rasterized to 8x8px, below Percy's 10px minimum\. Increase `scale`\./);
});

it('warns but proceeds on an unrecognised option', async () => {
await post({
name: 'doc',
Expand Down Expand Up @@ -325,6 +340,20 @@ describe('PDF snapshots', () => {
expect(() => decodePdf({ content: b64(big) })).toThrowMatching(
e => e.status === 413 && /maximum size of 50MB/.test(e.message));
});

it('rejects a PDF that only goes over once decoded', () => {
// The encoded cap rounds up to a whole base64 quantum, so a payload can
// clear it and still decode past 50MB -- by at most two bytes, which is
// exactly the window the second check exists to close. 52428801 bytes is
// the largest such buffer: divisible by 3, so it encodes to precisely
// MAX_PDF_BASE64_CHARS with no padding.
let big = Buffer.alloc(50 * 1024 * 1024 + 1);
let content = b64(big);

expect(content.length).toBe(Math.ceil((50 * 1024 * 1024) / 3) * 4);
expect(() => decodePdf({ content })).toThrowMatching(
e => e.status === 413 && /maximum size of 50MB/.test(e.message));
});
});

describe('loadPdfModule', () => {
Expand Down
33 changes: 21 additions & 12 deletions test/regression/pdf-render.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@
// Goldens therefore live under `expected/<platform>-<arch>/`, each set with
// its own `manifest.json` recording the browser build it came from. CI
// (linux-x64) and a macOS dev machine each compare against their own set.
// 2. Pages containing raster images are not byte-reproducible even on one
// machine: Chromium picks between two anti-aliasing paths for a clipped
// image edge from run to run. Those pages declare a measured pixel budget
// and fail if the difference exceeds it.
// 2. Some pages are not byte-reproducible even on one machine: Chromium picks
// between anti-aliasing paths from run to run, both for a clipped image
// edge and for a thin rule landing between sub-pixels. Those pages declare
// a measured pixel budget and fail if the difference exceeds it.
//
// Run: yarn test:regression:pdf
// Regenerate: yarn test:regression:pdf --update
Expand All @@ -43,17 +43,26 @@ const UPDATE = process.argv.includes('--update') || process.env.UPDATE_PDF_GOLDE
// not listed here must match byte-for-byte; these budgets are the only escape
// hatch, and they are deliberately tight.
//
// `jack sparrow resume.pdf` embeds a photo clipped to a circle. Chromium
// rasterizes that clip edge one of two ways depending on how the decoded image
// lands, so ~6 runs in 15 differ from the golden — always the same 270 pixels
// in the same 193x193 box around the photo, never more than 52 per channel, out
// of 2,005,644 pixels (0.013%). The budget is ~2x the measured worst case: a
// real rendering regression moves glyphs or layout and blows straight past it.
// `jack sparrow resume.pdf` has two independent, measured sources of run-to-run
// jitter on a fixed Chromium build, out of 2,005,644 pixels:
//
// - The photo clipped to a circle. Chromium rasterizes that clip edge one of
// two ways depending on how the decoded image lands, so ~6 runs in 15 differ
// from the golden — always the same 270 pixels in the same 193x193 box
// around the photo, never more than 52 per channel.
// - The section rule under "LANGUAGES", a hairline that straddles two pixel
// rows and blends into them differently between runs: the full 324x2 box
// [369, 1447, 692, 1448], 648 pixels at a channel delta of 4 —
// imperceptible, but enough to move the PNG bytes.
//
// Worst case is both at once (918 pixels); the budget is ~2x that, still 0.1% of
// the page. A real rendering regression moves glyphs or layout and blows
// straight past it — and a size change is never tolerated at all.
const TOLERANCES = {
'jack-sparrow-resume': {
maxDiffPixels: 600,
maxDiffPixels: 2000,
maxChannelDelta: 96,
reason: 'Chromium anti-aliases the circular photo clip differently between runs'
reason: 'Chromium anti-aliases the circular photo clip and a horizontal rule differently between runs'
}
};

Expand Down
Loading