fix(pdf-viewer): eliminate real pdfjs-dist loading from browser tests — stop birpc teardown race #550
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
31
frontend/src/lib/document/viewer/testHelpers.ts
Normal file
31
frontend/src/lib/document/viewer/testHelpers.ts
Normal 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);
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { createPdfRenderer } from './usePdfRenderer.svelte';
|
||||
import { makeFakeLibLoader } from './testHelpers';
|
||||
|
||||
// Note: init() and loadDocument() require pdfjsLib (browser module).
|
||||
// These tests cover pure state logic only — bounds clamping and zoom limits.
|
||||
@@ -122,39 +123,36 @@ describe('createPdfRenderer', () => {
|
||||
expect(r.scale).toBe(before);
|
||||
});
|
||||
|
||||
it('init() is callable and resolves without throwing in browser env', async () => {
|
||||
const r = createPdfRenderer();
|
||||
it('init() sets pdfjsReady to true when loader resolves', async () => {
|
||||
const r = createPdfRenderer(makeFakeLibLoader());
|
||||
await expect(r.init()).resolves.toBeUndefined();
|
||||
// pdfjsReady is now true
|
||||
expect(r.pdfjsReady).toBe(true);
|
||||
});
|
||||
|
||||
it('after init, loadDocument with a bogus URL sets error', async () => {
|
||||
const r = createPdfRenderer();
|
||||
it('after init, loadDocument completes and loading returns to false', async () => {
|
||||
const r = createPdfRenderer(makeFakeLibLoader());
|
||||
await r.init();
|
||||
|
||||
await r.loadDocument('about:invalid-pdf');
|
||||
// Either error is set or loading flips back to false — both are acceptable
|
||||
await r.loadDocument('/some/path');
|
||||
expect(r.loading).toBe(false);
|
||||
});
|
||||
|
||||
it('renderCurrentPage is a no-op when canvasEl is null but pdfjsLib is initialized', async () => {
|
||||
const r = createPdfRenderer();
|
||||
const r = createPdfRenderer(makeFakeLibLoader());
|
||||
await r.init();
|
||||
// Without setElements, canvasEl is null — early return
|
||||
await expect(r.renderCurrentPage()).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it('renderCurrentPage is a no-op when textLayerEl is null', async () => {
|
||||
const r = createPdfRenderer();
|
||||
const r = createPdfRenderer(makeFakeLibLoader());
|
||||
await r.init();
|
||||
// Set only canvas, leave textLayer unset is not directly testable;
|
||||
// confirm calling without elements wired returns early.
|
||||
// Without setElements, textLayerEl is null — early return
|
||||
await expect(r.renderCurrentPage()).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it('init() can be called multiple times safely', async () => {
|
||||
const r = createPdfRenderer();
|
||||
const r = createPdfRenderer(makeFakeLibLoader());
|
||||
await r.init();
|
||||
await r.init();
|
||||
expect(r.pdfjsReady).toBe(true);
|
||||
@@ -206,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');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user