PdfPreviewModal.test.tsx 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. /**
  2. * Tests for PdfPreviewModal (#2976).
  3. *
  4. * pdf.js cannot rasterise inside jsdom (no real canvas), so the library is
  5. * mocked at the module boundary; the tests cover the modal's own logic —
  6. * loading, page navigation, and error/size fallbacks.
  7. */
  8. import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
  9. import { render, screen } from '@testing-library/react';
  10. import userEvent from '@testing-library/user-event';
  11. import { PdfPreviewModal } from '../../components/PdfPreviewModal';
  12. const pdfjsMocks = vi.hoisted(() => {
  13. const render = vi.fn(() => ({ promise: Promise.resolve(), cancel: vi.fn() }));
  14. const getPage = vi.fn(async () => ({
  15. getViewport: ({ scale }: { scale: number }) => ({ width: 600 * scale, height: 800 * scale }),
  16. render,
  17. }));
  18. const getDocument = vi.fn(() => ({
  19. promise: Promise.resolve({ numPages: 3, getPage }),
  20. destroy: vi.fn(),
  21. }));
  22. return { render, getPage, getDocument };
  23. });
  24. vi.mock('pdfjs-dist/legacy/build/pdf.mjs', () => ({
  25. GlobalWorkerOptions: { workerSrc: '' },
  26. getDocument: pdfjsMocks.getDocument,
  27. }));
  28. vi.mock('pdfjs-dist/legacy/build/pdf.worker.min.mjs?worker&url', () => ({ default: 'pdf.worker.min.mjs' }));
  29. vi.mock('../../api/client', () => ({
  30. api: {
  31. getLibraryFileDownloadUrl: vi.fn((id: number) => `http://test/library/files/${id}/download`),
  32. },
  33. getAuthToken: () => null,
  34. }));
  35. const mockOnClose = vi.fn();
  36. function renderModal(props: Partial<Parameters<typeof PdfPreviewModal>[0]> = {}) {
  37. return render(
  38. <PdfPreviewModal
  39. libraryFileId={7}
  40. filename="drawing.pdf"
  41. fileSize={1024}
  42. onClose={mockOnClose}
  43. {...props}
  44. />,
  45. );
  46. }
  47. describe('PdfPreviewModal', () => {
  48. beforeEach(() => {
  49. vi.clearAllMocks();
  50. vi.stubGlobal(
  51. 'fetch',
  52. vi.fn(async () => new Response(new Uint8Array([1, 2, 3]), { status: 200 })),
  53. );
  54. });
  55. afterEach(() => {
  56. vi.unstubAllGlobals();
  57. });
  58. it('shows the page indicator once the document loads', async () => {
  59. renderModal();
  60. expect(await screen.findByText('Page 1 of 3')).toBeInTheDocument();
  61. expect(pdfjsMocks.render).toHaveBeenCalled();
  62. });
  63. it('navigates between pages', async () => {
  64. const user = userEvent.setup();
  65. renderModal();
  66. await screen.findByText('Page 1 of 3');
  67. await user.click(screen.getByRole('button', { name: 'Next page' }));
  68. expect(await screen.findByText('Page 2 of 3')).toBeInTheDocument();
  69. expect(pdfjsMocks.getPage).toHaveBeenLastCalledWith(2);
  70. await user.click(screen.getByRole('button', { name: 'Previous page' }));
  71. expect(await screen.findByText('Page 1 of 3')).toBeInTheDocument();
  72. });
  73. it('shows an error message when the document cannot be parsed', async () => {
  74. pdfjsMocks.getDocument.mockReturnValueOnce({ promise: Promise.reject(new Error('bad pdf')), destroy: vi.fn() } as never);
  75. renderModal();
  76. expect(await screen.findByText('This file cannot be previewed.')).toBeInTheDocument();
  77. });
  78. it('refuses oversized files without fetching them', async () => {
  79. const fetchSpy = vi.fn();
  80. vi.stubGlobal('fetch', fetchSpy);
  81. renderModal({ fileSize: 500 * 1024 * 1024 });
  82. expect(await screen.findByText(/too large to preview/)).toBeInTheDocument();
  83. expect(fetchSpy).not.toHaveBeenCalled();
  84. });
  85. });