dialog.client.spec.tsx 3.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576
  1. // @vitest-environment jsdom
  2. import { act, cleanup, fireEvent, render, waitFor } from '@testing-library/react'
  3. import { afterEach, describe, expect, it, vi } from 'vitest'
  4. import { useSyncExternalStore } from 'react'
  5. import type { SessionId } from '@deepseek-ai/dsh-session/types'
  6. import { SessionLogDownloadController } from '../src/client/controller.ts'
  7. import { SessionLogDownloadDialog } from '../src/client/Dialog.tsx'
  8. import type { SessionLogDownloadDialogProps } from '../src/client/Dialog.tsx'
  9. import { en } from '../src/client/locales.ts'
  10. const SID = 'session-export-dialog' as SessionId
  11. function bench(
  12. controller = new SessionLogDownloadController(
  13. async () => new Response('zip', { status: 200 }), vi.fn(),
  14. ),
  15. ) {
  16. const dismiss = vi.fn((sessionId: SessionId) => { controller.dismiss(sessionId) })
  17. function useSessionLogDownload<T>(selector: (state: ReturnType<typeof controller.store.getSnapshot>) => T): T {
  18. return useSyncExternalStore(
  19. listener => controller.store.subscribe(listener),
  20. () => selector(controller.store.getSnapshot()),
  21. )
  22. }
  23. const t = (key: keyof typeof en): string => en[key]
  24. const props = { sessionId: SID, useSessionLogDownload, dismiss, t } as unknown as SessionLogDownloadDialogProps
  25. const view = render(<SessionLogDownloadDialog {...props} />)
  26. return { controller, dismiss, view }
  27. }
  28. afterEach(cleanup)
  29. describe('SessionLogDownloadDialog', () => {
  30. it('shows a controller failure and closes it without reading Session history', async () => {
  31. const b = bench()
  32. act(() => {
  33. b.controller.store.set({
  34. bySession: { [SID]: { open: true, status: 'error', error: 'toolbar failed' } },
  35. })
  36. })
  37. const dialog = await b.view.findByRole('dialog', { name: 'Session export failed' })
  38. expect(dialog.textContent).toContain('toolbar failed')
  39. const close = b.view.getAllByRole('button', { name: 'Close' })[0]
  40. if (close === undefined) throw new Error('Session export dialog has no close button')
  41. fireEvent.click(close)
  42. await waitFor(() => { expect(b.dismiss).toHaveBeenCalledWith(SID) })
  43. })
  44. it('renders the in-flight state and the settled browser download state', async () => {
  45. let release!: (response: Response) => void
  46. const pending = new Promise<Response>((resolve) => { release = resolve })
  47. const controller = new SessionLogDownloadController(() => pending, vi.fn())
  48. const b = bench(controller)
  49. const download = controller.download(SID)
  50. expect(await b.view.findByRole('dialog', { name: 'Exporting Session' })).toBeTruthy()
  51. release(new Response('zip', { status: 200 }))
  52. await download
  53. expect(await b.view.findByRole('dialog', { name: 'Session download started' })).toBeTruthy()
  54. })
  55. it('uses fallback copy when a failure has no detail', async () => {
  56. const b = bench()
  57. act(() => {
  58. b.controller.store.set({
  59. bySession: { [SID]: { open: true, status: 'error', error: '' } },
  60. })
  61. })
  62. const dialog = await b.view.findByRole('dialog', { name: 'Session export failed' })
  63. expect(dialog.textContent).toContain('Could not start the Session export.')
  64. const close = b.view.getAllByRole('button', { name: 'Close' }).at(-1)
  65. if (close === undefined) throw new Error('Session export dialog has no footer action')
  66. fireEvent.click(close)
  67. await waitFor(() => { expect(b.dismiss).toHaveBeenCalledWith(SID) })
  68. })
  69. })