pdf-registration.client.spec.ts 4.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. // @vitest-environment jsdom
  2. /** PDF metadata, keyed slot, dictionary, and tab-view lifetime registration. */
  3. import { createElement } from 'react'
  4. import { Context } from '@deepseek-ai/cordis'
  5. import { cleanup, render, screen, waitFor } from '@testing-library/react'
  6. import { afterEach, describe, expect, it, vi } from 'vitest'
  7. import type { SessionId } from '@deepseek-ai/dsh-session/types'
  8. import type { TabId } from '@deepseek-ai/dsh-client-ui-dockkit'
  9. import { makeTranslate } from '@deepseek-ai/dsh-client-test-runtime'
  10. import { DocumentPreviewRegistry } from '../src/client/document/registry.ts'
  11. import { createPdfStore } from '../src/client/pdf/store.ts'
  12. import type { PdfBodyInjected } from '../src/client/pdf/pdf.tsx'
  13. vi.mock('../src/client/pdf/runtime.ts', () => ({ openPdf: vi.fn() }))
  14. const renderedPdf = vi.hoisted(() => vi.fn(() => null))
  15. vi.mock('../src/client/pdf/pdf.tsx', () => ({ PdfBody: renderedPdf }))
  16. import { apply, PDF_BODY_ID } from '../src/client/pdf/index.ts'
  17. import { LazyPdfBody } from '../src/client/pdf/LazyPdfBody.tsx'
  18. import { en, zh } from '../src/client/pdf/locales.ts'
  19. afterEach(() => { cleanup(); renderedPdf.mockClear() })
  20. describe('PDF registration', () => {
  21. it('shows localized loading feedback while the PDF chunk resolves', async () => {
  22. render(createElement(LazyPdfBody, { t: makeTranslate(en) } as never))
  23. expect(screen.getByRole('status', { name: en.loading })).toBeDefined()
  24. await waitFor(() => { expect(renderedPdf).toHaveBeenCalledOnce() })
  25. })
  26. it('registers a builtin complete-bytes body and removes all contributions and retained view state on dispose', async () => {
  27. const ctx = new Context()
  28. const previews = new DocumentPreviewRegistry()
  29. const dictionaries = new Map<string, unknown>()
  30. const entries: Array<{
  31. name: string
  32. key: string
  33. locale: string
  34. store: ReturnType<typeof createPdfStore>
  35. inject: (sessionId: SessionId, actions: ReturnType<ReturnType<typeof createPdfStore>['create']>['actions']) => PdfBodyInjected
  36. }> = []
  37. const register = vi.fn((options: typeof entries[number], component: unknown) => {
  38. expect(component).toBe(LazyPdfBody)
  39. entries.push(options)
  40. return () => { entries.splice(entries.indexOf(options), 1) }
  41. })
  42. ctx.provide('documentPreviews', previews)
  43. ctx.provide('locale', {
  44. register: (name: string, value: unknown) => { dictionaries.set(name, value); return () => { dictionaries.delete(name) } },
  45. bind: () => makeTranslate(en),
  46. } as never)
  47. ctx.provide('slots', {
  48. inject: (_name: string, callback: () => () => void) => callback(), register,
  49. } as never)
  50. const fiber = ctx.plugin({ apply })
  51. try {
  52. await fiber.await()
  53. expect(previews.candidates('REPORT.PDF')).toMatchObject([
  54. { id: PDF_BODY_ID, extensions: ['pdf'], priority: 'builtin', loading: 'bytes-complete', wrap: false },
  55. ])
  56. expect(previews.candidates('report.pdf')[0]!.title()).toBe('PDF')
  57. expect(dictionaries.get('sidebarPdf')).toEqual({ zh, en })
  58. expect(entries[0]).toMatchObject({ name: 'sidebar.right.tab.document', key: PDF_BODY_ID, locale: 'sidebarPdf' })
  59. const instance = entries[0]!.store.create()
  60. const face = entries[0]!.inject('s1' as SessionId, instance.actions)
  61. const controller = new AbortController()
  62. face.retainTab('one' as TabId, controller.signal)
  63. face.retainTab('one' as TabId, controller.signal)
  64. instance.actions.page('one' as TabId, 2)
  65. controller.abort()
  66. expect(instance.getSnapshot().byTab).toEqual({})
  67. instance.actions.page('ended' as TabId, 3)
  68. face.retainTab('ended' as TabId, controller.signal)
  69. expect(instance.getSnapshot().byTab).toEqual({})
  70. const other = new AbortController()
  71. face.retainTab('two' as TabId, other.signal)
  72. instance.actions.page('two' as TabId, 2)
  73. await fiber.dispose()
  74. expect(instance.getSnapshot().byTab).toEqual({})
  75. expect(previews.getSnapshot()).toEqual([])
  76. expect(entries).toEqual([])
  77. expect(dictionaries.size).toBe(0)
  78. } finally {
  79. await fiber.dispose()
  80. }
  81. })
  82. })