pdf-document.client.spec.ts 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081
  1. // @vitest-environment jsdom
  2. /** Page rendering cancellation is driven by controlled pdfjs promises, not scheduler delays. */
  3. import { describe, expect, it, vi } from 'vitest'
  4. import type { PDFPageProxy } from 'pdfjs-dist'
  5. import { renderPdfPage } from '../src/client/pdf/document.ts'
  6. describe('PDF canvas rendering', () => {
  7. it('cancels when the owner aborts during the library render call, before the abort listener attaches', async () => {
  8. const controller = new AbortController()
  9. const cancel = vi.fn()
  10. const cleanup = vi.fn()
  11. const page = {
  12. getViewport: () => ({ width: 100, height: 80 }),
  13. render: () => {
  14. controller.abort()
  15. return { cancel, promise: Promise.resolve() }
  16. },
  17. cleanup,
  18. } as unknown as PDFPageProxy
  19. await expect(renderPdfPage({ numPages: 1, getPage: async () => page }, 1, document.createElement('canvas'), controller.signal, 1))
  20. .rejects.toMatchObject({ name: 'AbortError' })
  21. expect(cancel).toHaveBeenCalledOnce()
  22. expect(cleanup).toHaveBeenCalledOnce()
  23. })
  24. it('does not touch a canvas when getPage settles after cancellation', async () => {
  25. const lookup = Promise.withResolvers<PDFPageProxy>()
  26. const controller = new AbortController()
  27. const canvas = document.createElement('canvas')
  28. const cleanup = vi.fn()
  29. const render = vi.fn()
  30. const pending = renderPdfPage({ numPages: 1, getPage: () => lookup.promise }, 1, canvas, controller.signal, 1)
  31. const rejected = expect(pending).rejects.toMatchObject({ name: 'AbortError' })
  32. controller.abort()
  33. lookup.resolve({ cleanup, render } as unknown as PDFPageProxy)
  34. await rejected
  35. expect(render).not.toHaveBeenCalled()
  36. expect(cleanup).toHaveBeenCalledOnce()
  37. expect(canvas.width).toBe(300)
  38. expect(canvas.style.getPropertyValue('--pdf-page-width')).toBe('')
  39. })
  40. it('cancels an active task and cleans up only after its promise settles', async () => {
  41. const running = Promise.withResolvers<undefined>()
  42. const entered = Promise.withResolvers<undefined>()
  43. const controller = new AbortController()
  44. const cleanup = vi.fn()
  45. const cancel = vi.fn()
  46. const page = {
  47. getViewport: vi.fn(() => ({ width: 100, height: 80 })),
  48. render: vi.fn(() => { entered.resolve(undefined); return { promise: running.promise, cancel } }),
  49. cleanup,
  50. } as unknown as PDFPageProxy
  51. const pending = renderPdfPage({ numPages: 1, getPage: async () => page }, 1, document.createElement('canvas'), controller.signal, 2)
  52. const rejected = expect(pending).rejects.toThrow('cancelled')
  53. await entered.promise
  54. controller.abort()
  55. expect(cancel).toHaveBeenCalledOnce()
  56. expect(cleanup).not.toHaveBeenCalled()
  57. running.reject(new Error('cancelled'))
  58. await rejected
  59. expect(cleanup).toHaveBeenCalledOnce()
  60. })
  61. it('bounds raster allocation while retaining the selected display dimensions', async () => {
  62. const cleanup = vi.fn()
  63. const getViewport = vi.fn(() => ({ width: 8192, height: 8192 }))
  64. const page = {
  65. getViewport,
  66. render: vi.fn(() => ({ promise: Promise.resolve(), cancel: vi.fn() })),
  67. cleanup,
  68. } as unknown as PDFPageProxy
  69. const canvas = document.createElement('canvas')
  70. await expect(renderPdfPage({ numPages: 1, getPage: async () => page }, 1, canvas, new AbortController().signal, 2))
  71. .resolves.toEqual({ width: 8192, height: 8192 })
  72. expect(getViewport).toHaveBeenCalledWith({ scale: 96 / 72 })
  73. expect(canvas.width * canvas.height).toBe(16_777_216)
  74. expect(canvas.style.getPropertyValue('--pdf-page-width')).toBe('8192px')
  75. expect(cleanup).toHaveBeenCalledOnce()
  76. })
  77. })