From b5823843e4975a1a126e77f472ea485efb1e1027 Mon Sep 17 00:00:00 2001 From: Aleksandr Shevchuk Date: Sat, 7 Nov 2026 16:27:56 +0200 Subject: [PATCH] test: make the tests check what they claim Several tests passed whether or not the feature worked (#76): - "should render the PDF file" opened a PDF that exists nowhere and asserted inside a handler it never awaited. It now opens a PDF in the repository and waits for a page to be drawn. - "should not render PDF with wrong url" asserted before the load could finish. It now waits for the load to fail, and fails if pages load. - "should open the external file with base64 source": getFileData() fetched the base64 string as a relative URL, and PDF.js 6 rejects a bare Uint8Array. It now opens { data } and checks the document's text. - "defaults to resources that ship next to the runtime files" checked HEAD for a 200, which the dev server also returns for a missing file. It now checks the content type. - 14 tests set src to a missing PDF they didn't need; that's removed. PdfViewerApplicationOpenParameters no longer allows a bare Uint8Array, which PDF.js rejects at runtime. Closes #76 Co-Authored-By: Claude Opus 5.5 --- src/pdfjs-viewer-element.ts | 4 +- tests/basic.test.ts | 86 ++++++++++++++------------------- tests/utils.ts | 2 +- types/pdfjs-viewer-element.d.ts | 4 +- 4 files changed, 40 insertions(+), 56 deletions(-) diff --git a/src/pdfjs-viewer-element.ts b/src/pdfjs-viewer-element.ts index 6911cd3..4c2c92b 100644 --- a/src/pdfjs-viewer-element.ts +++ b/src/pdfjs-viewer-element.ts @@ -79,8 +79,8 @@ type ResourceAttribute = keyof typeof RESOURCE_OPTIONS export const ViewerCssTheme = { AUTOMATIC: 0, LIGHT: 1, DARK: 2 } as const -export type PdfViewerApplicationOpenParameters = - (DocumentInitParameters & { originalUrl?: string }) | Uint8Array +// PDF.js takes the document as `{ url }` or `{ data }`. A bare `Uint8Array` fails in `getDocument`. +export type PdfViewerApplicationOpenParameters = DocumentInitParameters & { originalUrl?: string } export type PdfViewerApplicationOptionValue = string | boolean | number | Record diff --git a/tests/basic.test.ts b/tests/basic.test.ts index c61faa8..3c69f21 100644 --- a/tests/basic.test.ts +++ b/tests/basic.test.ts @@ -6,27 +6,31 @@ import packageJson from '../package.json' // The PDF.js release that `pnpm sync:pdfjs` unpacks into `public/`. const pdfjsDist = `/pdfjs-${packageJson.dependencies['pdfjs-dist'].match(/\d+\.\d+\.\d+/)![0]}-dist` +// A PDF in the repository, vendored with the viewer. A file put in `public/` by hand is not in the +// repository, so CI doesn't have it. +const samplePdf = '/src/web/compressed.tracemonkey-pldi-09.pdf' + describe('Basic tests', async () => { it('should render the PDF file', async () => { const viewerApp = await mountViewer(` - ` - ) - expect(getViewerElement()).exist + ` + ) as unknown as { pagesCount: number } - viewerApp?.eventBus.on('pagesloaded', () => { - expect(getViewerElement('#viewer .page')).exist - }) + // PDF.js marks a page as loaded once it starts drawing the page into its canvas. + await vi.waitFor(() => expect(getViewerElement('#viewer .page[data-loaded] canvas')).exist, { timeout: 10_000 }) + expect(viewerApp.pagesCount).eq(14) }) it('should not render PDF with wrong url', async () => { - await mountViewer(` - ` - ) - expect(getViewerElement()).exist + const viewerApp = await mountViewer('') + // Whichever comes first: a failed load, or the pages of a document that loaded. + const outcome = new Promise((resolve) => { + viewerApp!.eventBus.on('documenterror', () => resolve('error')) + viewerApp!.eventBus.on('pagesloaded', () => resolve('pages')) + }) + document.body.querySelector('pdfjs-viewer-element')!.setAttribute('src', '/fake-file.pdf') + + expect(await outcome).eq('error') expect(getViewerElement('#viewer .page')).not.exist }) @@ -35,22 +39,13 @@ describe('Basic tests', async () => { ` ) - expect(getViewerElement()).exist - - const file = await getFileData() - viewerApp?.open(file) - - viewerApp?.eventBus.on('pagesloaded', () => { - expect(getViewerElement('#viewer .page')).exist - }) + await viewerApp!.open({ data: await getFileData() }) + await vi.waitFor(() => expect(getViewerElement('#viewer .textLayer')?.textContent).toContain('Hello, world!'), { timeout: 10_000 }) }) it('should load the dark theme', async () => { await mountViewer(` - ` + ` ) expect(getViewerElement()).exist @@ -100,10 +95,7 @@ describe('Basic tests', async () => { }) it('should hide the download button', async () => { - document.body.innerHTML = ` - ` + document.body.innerHTML = '' const viewer = document.body.querySelector('pdfjs-viewer-element') as HTMLElement & { injectViewerStyles: (styles: string) => Promise @@ -146,19 +138,14 @@ describe('Basic tests', async () => { }) it('should add a title attribute to the iframe', async () => { - const viewerApp = await mountViewer(` - ` - ) + await mountViewer('') expect(getViewerElement()).exist expect(getIframe().title).eq('Custom title') }) it('should apply hash-related attributes at runtime', async () => { await mountViewer(` - ` + ` ) const viewer = document.body.querySelector('pdfjs-viewer-element') as HTMLElement @@ -249,7 +236,7 @@ describe('Basic tests', async () => { it('should apply worker-src option at runtime', async () => { await mountViewer(` - ` + ` ) const viewer = document.body.querySelector('pdfjs-viewer-element') as HTMLElement @@ -263,7 +250,7 @@ describe('Basic tests', async () => { it('should apply renderer-src option at runtime', async () => { await mountViewer(` - ` + ` ) const viewer = document.body.querySelector('pdfjs-viewer-element') as HTMLElement @@ -280,7 +267,6 @@ describe('Basic tests', async () => { await mountViewer(` ` ) @@ -297,7 +283,6 @@ describe('Basic tests', async () => { // A host under `.invalid` never resolves; the renderer worker only starts once worker rendering is on. await mountViewer(` ` ) @@ -316,7 +301,6 @@ describe('Basic tests', async () => { await mountViewer(` ` ) @@ -349,7 +333,6 @@ describe('Basic tests', async () => { await mountViewer(` ` @@ -366,7 +349,7 @@ describe('Basic tests', async () => { it('should use built-in worker by default', async () => { await mountViewer(` - ` + ` ) const options = getIframe().contentWindow.PDFViewerApplicationOptions.getAll() @@ -405,8 +388,7 @@ describe('Basic tests', async () => { } const rendered = new Promise((resolve) => viewerApp!.eventBus.on('pagerendered', () => resolve())) - // A PDF that is in the repository, which `public/` is not. - await viewerApp!.open({ url: new URL('/src/web/compressed.tracemonkey-pldi-09.pdf', location.href).href }) + await viewerApp!.open({ url: new URL(samplePdf, location.href).href }) await rendered // The frame has no URL of its own, so PDF.js starts each worker from a `blob:` wrapper that @@ -422,7 +404,7 @@ describe('Basic tests', async () => { it('should apply extended option attributes at runtime and restore defaults on remove', async () => { await mountViewer(` - ` + ` ) const viewer = document.body.querySelector('pdfjs-viewer-element') as HTMLElement @@ -479,7 +461,7 @@ describe('Basic tests', async () => { it('should use a changed image-resources-path for the next document', async () => { const viewerApp = await mountViewer(` - ` + ` ) as unknown as { pdfViewer: { imageResourcesPath: string } } const viewer = document.body.querySelector('pdfjs-viewer-element') as HTMLElement @@ -593,14 +575,14 @@ describe('Basic tests', async () => { it('should open a new document when src changes at runtime', async () => { const viewerApp = await mountViewer(` - ` + ` ) const viewer = document.body.querySelector('pdfjs-viewer-element') as HTMLElement let openedUrl = '' const originalOpen = viewerApp?.open if (viewerApp) { - viewerApp.open = ((params: { url: string } | { data: Uint8Array } | Uint8Array) => { + viewerApp.open = ((params: { url: string } | { data: Uint8Array }) => { if (typeof params === 'object' && 'url' in params) { openedUrl = params.url } @@ -651,7 +633,9 @@ describe('Resource URL attributes', () => { for (const [attribute, file] of Object.entries(files)) { const url = viewer['getResourceOption'](attribute as keyof typeof files) + file - expect((await fetch(url, { method: 'HEAD' })).status, url).eq(200) + // The dev server answers a missing file with its HTML page and a 200, so check the type. + const response = await fetch(url, { method: 'HEAD' }) + expect(response.headers.get('content-type') ?? '', url).not.toMatch(/text\/html/) } }) diff --git a/tests/utils.ts b/tests/utils.ts index 81af9bc..cf4d22a 100644 --- a/tests/utils.ts +++ b/tests/utils.ts @@ -62,7 +62,7 @@ export const getFileData = () => { 'MDAwIG4gCjAwMDAwMDAzODAgMDAwMDAgbiAKdHJhaWxlcgo8PAogIC9TaXplIDYKICAvUm9v' + 'dCAxIDAgUgo+PgpzdGFydHhyZWYKNDkyCiUlRU9G' - return fetch(baseStr) + return fetch(`data:application/pdf;base64,${baseStr}`) .then(b => b.arrayBuffer()) .then(buff => new Uint8Array(buff)) } \ No newline at end of file diff --git a/types/pdfjs-viewer-element.d.ts b/types/pdfjs-viewer-element.d.ts index 8fad0c0..1effd0d 100644 --- a/types/pdfjs-viewer-element.d.ts +++ b/types/pdfjs-viewer-element.d.ts @@ -5,9 +5,9 @@ export declare const ViewerCssTheme: { readonly LIGHT: 1; readonly DARK: 2; }; -export type PdfViewerApplicationOpenParameters = (DocumentInitParameters & { +export type PdfViewerApplicationOpenParameters = DocumentInitParameters & { originalUrl?: string; -}) | Uint8Array; +}; export type PdfViewerApplicationOptionValue = string | boolean | number | Record; export interface PdfViewerApplication { initializedPromise: Promise;