fix(pdf-viewer): eliminate real pdfjs-dist loading from browser tests — stop birpc teardown race #550

Merged
marcel merged 2 commits from worktree-feat+issue-546-pdf-viewer-test-fix into main 2026-05-12 16:20:29 +02:00
3 changed files with 58 additions and 54 deletions
Showing only changes of commit d21ba8fed2 - Show all commits

View File

@@ -1,40 +1,11 @@
import { describe, it, expect, vi, afterEach } from 'vitest';
import { cleanup, render } from 'vitest-browser-svelte';
import { page } from 'vitest/browser';
import type { LibLoader } from '$lib/document/viewer/usePdfRenderer.svelte';
import PdfViewer from './PdfViewer.svelte';
import { makeFakeLibLoader } from './testHelpers';
afterEach(cleanup);
function makeFakePdfjsLib() {
class TextLayerMock {
render() {
return Promise.resolve();
}
cancel() {}
}
return {
GlobalWorkerOptions: { workerSrc: '' },
getDocument: vi.fn().mockReturnValue({
promise: Promise.resolve({
numPages: 2,
getPage: vi.fn().mockResolvedValue({
getViewport: vi.fn().mockReturnValue({ width: 595, height: 842 }),
render: vi.fn().mockReturnValue({ promise: Promise.resolve() }),
streamTextContent: vi.fn().mockReturnValue(new ReadableStream())
})
})
}),
TextLayer: TextLayerMock
} as unknown as typeof import('pdfjs-dist');
}
function makeFakeLibLoader(): LibLoader {
const fakePdfjs = makeFakePdfjsLib();
return vi.fn().mockResolvedValue([fakePdfjs, { default: '' }] as const);
}
describe('PdfViewer — empty / error states', () => {
it('renders the no-file placeholder when url is empty', async () => {
render(PdfViewer, { url: '', libLoader: makeFakeLibLoader() });
@@ -293,18 +264,18 @@ describe('PdfViewer — loaded state', () => {
it('shows previous and next page navigation buttons', async () => {
render(PdfViewer, { url: '/api/documents/test-id/file', libLoader: makeFakeLibLoader() });
await expect.element(page.getByRole('button', { name: /zurück/i })).toBeInTheDocument();
await expect.element(page.getByRole('button', { name: /weiter/i })).toBeInTheDocument();
await expect.element(page.getByRole('button', { name: /zurück/i })).toBeVisible();
await expect.element(page.getByRole('button', { name: /weiter/i })).toBeVisible();
});
it('shows zoom controls', async () => {
render(PdfViewer, { url: '/api/documents/test-id/file', libLoader: makeFakeLibLoader() });
await expect.element(page.getByRole('button', { name: /vergrößern/i })).toBeInTheDocument();
await expect.element(page.getByRole('button', { name: /verkleinern/i })).toBeInTheDocument();
await expect.element(page.getByRole('button', { name: /vergrößern/i })).toBeVisible();
await expect.element(page.getByRole('button', { name: /verkleinern/i })).toBeVisible();
});
it('displays the page counter once the PDF has loaded', async () => {
render(PdfViewer, { url: '/api/documents/test-id/file', libLoader: makeFakeLibLoader() });
await expect.element(page.getByText(/1\s*\/\s*2/)).toBeInTheDocument();
await expect.element(page.getByText(/1\s*\/\s*2/)).toBeVisible();
});
});

View File

@@ -0,0 +1,31 @@
import { vi } from 'vitest';
import type { LibLoader } from './usePdfRenderer.svelte';
export function makeFakePdfjsLib() {
class TextLayerMock {
render() {
return Promise.resolve();
}
cancel() {}
}
return {
GlobalWorkerOptions: { workerSrc: '' },
getDocument: vi.fn().mockReturnValue({
promise: Promise.resolve({
numPages: 2,
getPage: vi.fn().mockResolvedValue({
getViewport: vi.fn().mockReturnValue({ width: 595, height: 842 }),
render: vi.fn().mockReturnValue({ promise: Promise.resolve() }),
streamTextContent: vi.fn().mockReturnValue(new ReadableStream())
})
})
}),
TextLayer: TextLayerMock
} as unknown as typeof import('pdfjs-dist');
}
export function makeFakeLibLoader(): LibLoader {
const fakePdfjs = makeFakePdfjsLib();
return vi.fn().mockResolvedValue([fakePdfjs, { default: '' }] as const);
}

View File

@@ -1,24 +1,6 @@
import { describe, it, expect, vi } from 'vitest';
import type { LibLoader } from './usePdfRenderer.svelte';
import { createPdfRenderer } from './usePdfRenderer.svelte';
function makeFakeLibLoader(): LibLoader {
return vi.fn().mockResolvedValue([
{
GlobalWorkerOptions: { workerSrc: '' },
getDocument: vi.fn().mockReturnValue({
promise: Promise.resolve({ numPages: 1, getPage: vi.fn() })
}),
TextLayer: class {
render() {
return Promise.resolve();
}
cancel() {}
}
} as unknown as typeof import('pdfjs-dist'),
{ default: '' }
] as const);
}
import { makeFakeLibLoader } from './testHelpers';
// Note: init() and loadDocument() require pdfjsLib (browser module).
// These tests cover pure state logic only — bounds clamping and zoom limits.
@@ -222,4 +204,24 @@ describe('createPdfRenderer', () => {
await r.init();
expect(fakeLoader).toHaveBeenCalledOnce();
});
it('loadDocument sets error and loading=false when getDocument().promise rejects', async () => {
const failingLib = {
GlobalWorkerOptions: { workerSrc: '' },
getDocument: vi.fn().mockReturnValue({
promise: Promise.reject(new Error('PDF not found'))
}),
TextLayer: class {
render() {
return Promise.resolve();
}
cancel() {}
}
} as unknown as typeof import('pdfjs-dist');
const r = createPdfRenderer(vi.fn().mockResolvedValue([failingLib, { default: '' }] as const));
await r.init();
await r.loadDocument('/bad/path');
expect(r.loading).toBe(false);
expect(r.error).toBe('PDF not found');
});
});