client-apply.client.spec.tsx 3.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. import { Context } from '@deepseek-ai/cordis'
  2. import { afterEach, describe, expect, it, vi } from 'vitest'
  3. import { SlotRegistry } from '@deepseek-ai/dsh-client-ui-renderer/client'
  4. import type { SessionId } from '@deepseek-ai/dsh-session/types'
  5. import { LocaleRuntime } from '@deepseek-ai/dsh-client-locale/client'
  6. import type {} from '@deepseek-ai/dsh-client-ui-conversation/client'
  7. import { SessionLogDownloadHeaderAction } from '../src/client/HeaderAction.tsx'
  8. import { apply, inject } from '../src/client/index.ts'
  9. const SID = 'session-export-apply' as SessionId
  10. afterEach(() => { vi.unstubAllGlobals() })
  11. function declare(slots: SlotRegistry): () => void {
  12. return slots.register({
  13. name: 'root',
  14. children: {
  15. 'conversation.session.header.actions': { kind: 'list', scope: 'session' },
  16. 'conversation.session.header.utilities': { kind: 'list', scope: 'session' },
  17. },
  18. } as never, () => null)
  19. }
  20. async function bench() {
  21. const ctx = new Context()
  22. await ctx.plugin(SlotRegistry).await()
  23. const slots = ctx.get('slots') as SlotRegistry
  24. const declaration = declare(slots)
  25. ctx.provide('locale', new LocaleRuntime(ctx))
  26. const fiber = ctx.plugin({ inject: [...inject], apply })
  27. await fiber.await()
  28. return { ctx, slots, declaration, fiber }
  29. }
  30. describe('session-log-download browser plugin', () => {
  31. it('provides one controller and removes its Header contribution on disposal', async () => {
  32. vi.stubGlobal('fetch', vi.fn(async () => new Response('', { status: 500 })))
  33. const b = await bench()
  34. expect(inject).toEqual(['slots', 'locale'])
  35. expect(b.ctx.sessionLogDownload).toBeDefined()
  36. expect(b.slots.entries('conversation.session.header.actions')).toHaveLength(0)
  37. const entry = b.slots.entries('conversation.session.header.utilities')[0]
  38. expect(entry?.component).toBe(SessionLogDownloadHeaderAction)
  39. expect(entry?.options).toMatchObject({ id: 'session-log-download' })
  40. const injected = (entry?.inject as unknown as () => import('../src/client/Dialog.tsx').SessionLogDownloadDialogInjected)()
  41. await injected.request(SID)
  42. expect(b.ctx.sessionLogDownload.store.getSnapshot().bySession[SID]?.status).toBe('error')
  43. injected.dismiss(SID)
  44. expect(b.ctx.sessionLogDownload.store.getSnapshot().bySession[SID]?.open).toBe(false)
  45. await b.fiber.dispose()
  46. expect(b.slots.entries('conversation.session.header.utilities')).toHaveLength(0)
  47. })
  48. it('downloads only for an export execution acknowledged by this browser client', async () => {
  49. const fetcher = vi.fn(async () => new Response('', { status: 500 }))
  50. vi.stubGlobal('fetch', fetcher)
  51. const first = await bench()
  52. const second = await bench()
  53. first.ctx.emit('command/executed', SID, 'plan', { kind: 'success' })
  54. expect(fetcher).not.toHaveBeenCalled()
  55. first.ctx.emit('command/executed', SID, 'export', { kind: 'error', text: 'bad path' })
  56. expect(fetcher).not.toHaveBeenCalled()
  57. first.ctx.emit('command/executed', SID, 'export', { kind: 'success' })
  58. await vi.waitFor(() => {
  59. expect(fetcher).toHaveBeenCalledOnce()
  60. expect(first.ctx.sessionLogDownload.store.getSnapshot().bySession[SID]?.status).toBe('error')
  61. })
  62. expect(second.ctx.sessionLogDownload.store.getSnapshot().bySession[SID]).toBeUndefined()
  63. await first.fiber.dispose()
  64. await second.fiber.dispose()
  65. })
  66. it('re-registers after the declaring Header slot collapses and returns', async () => {
  67. const b = await bench()
  68. b.declaration()
  69. expect(b.slots.entries('conversation.session.header.utilities')).toHaveLength(0)
  70. const redeclare = declare(b.slots)
  71. await Promise.resolve()
  72. expect(b.slots.entries('conversation.session.header.utilities')[0]?.component).toBe(SessionLogDownloadHeaderAction)
  73. redeclare()
  74. await b.fiber.dispose()
  75. })
  76. })