code-registration.client.spec.tsx 3.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  1. // @vitest-environment jsdom
  2. /** Code renderer registration lifetimes through the production document and Slot registries. */
  3. import { afterEach, describe, expect, it } from 'vitest'
  4. import { act } from '@testing-library/react'
  5. import { SlotTestRuntime } from '@deepseek-ai/dsh-client-test-runtime'
  6. import { LocaleRuntime } from '@deepseek-ai/dsh-client-locale/client'
  7. import { apply } from '../src/client/code/index.ts'
  8. import { CodeBody } from '../src/client/code/CodeBody.tsx'
  9. import { CODE_EXTENSIONS } from '../src/client/code/languages.ts'
  10. import { DocumentPreviewRegistry } from '../src/client/document/registry.ts'
  11. import { documentTabInfoFactory } from '../src/client/document/contract.ts'
  12. const ID = '@deepseek-ai/dsh-client-ui-sidebar-documentpreview/code'
  13. const SLOT = 'sidebar.right.tab.document'
  14. const plugin = { inject: ['slots', 'locale', 'documentPreviews'], apply }
  15. let runtime: SlotTestRuntime | undefined
  16. afterEach(async () => {
  17. await runtime?.dispose()
  18. runtime = undefined
  19. })
  20. async function boot() {
  21. const rt = await SlotTestRuntime.create()
  22. runtime = rt
  23. const locale = new LocaleRuntime(rt.ctx)
  24. rt.ctx.provide('locale', locale)
  25. rt.slots.installLocale(locale)
  26. const previews = new DocumentPreviewRegistry()
  27. rt.ctx.provide('documentPreviews', previews)
  28. const declare = () => rt.declare({
  29. [SLOT]: { kind: 'keyed', scope: 'session', inject: { hooks: { tabInfo: documentTabInfoFactory } } },
  30. })
  31. return { rt, locale, previews, declare }
  32. }
  33. describe('code renderer registration', () => {
  34. it('publishes builtin metadata before the document slot exists and waits to register its body', async () => {
  35. const h = await boot()
  36. await h.rt.mount(plugin)
  37. expect(h.previews.getSnapshot()).toHaveLength(1)
  38. expect(h.previews.getSnapshot()[0]).toMatchObject({ id: ID, extensions: CODE_EXTENSIONS, priority: 'builtin', loading: 'text-pages', wrap: true })
  39. expect(h.rt.slots.entries(SLOT)).toEqual([])
  40. await h.declare()
  41. const entries = h.rt.slots.entries(SLOT)
  42. expect(entries).toHaveLength(1)
  43. expect(entries[0]).toMatchObject({ options: { key: ID }, locale: 'sidebarCodePreview', component: CodeBody })
  44. expect(h.previews.candidates('notes.unknown')).toEqual([])
  45. })
  46. it('resolves localized names at read time and removes metadata, locale, and body on disposal', async () => {
  47. const h = await boot()
  48. await h.declare()
  49. const feature = await h.rt.mount(plugin)
  50. const definition = h.previews.getSnapshot()[0]
  51. await act(async () => { h.locale.setLocale('en') })
  52. expect(definition?.title()).toBe('Code')
  53. await act(async () => { h.locale.setLocale('zh') })
  54. expect(definition?.title()).toBe('代码')
  55. await feature.dispose()
  56. expect(h.previews.getSnapshot()).toEqual([])
  57. expect(h.rt.slots.entries(SLOT)).toEqual([])
  58. expect(h.locale.bind('sidebarCodePreview')('title')).toBe('title')
  59. })
  60. it('remounts the keyed body after the document owner redeclares its slot', async () => {
  61. const h = await boot()
  62. await h.declare()
  63. await h.rt.mount(plugin)
  64. await act(async () => { h.rt.root.release() })
  65. expect(h.rt.slots.entries(SLOT)).toEqual([])
  66. expect(h.previews.getSnapshot()).toHaveLength(1)
  67. await h.declare()
  68. expect(h.rt.slots.entries(SLOT)).toHaveLength(1)
  69. })
  70. it('keeps prior HTML and Markdown builtins ahead of their optional source renderer', async () => {
  71. const h = await boot()
  72. for (const extension of ['html', 'md']) {
  73. h.rt.ctx.effect(() => h.previews.register({
  74. id: `dedicated-${extension}`, extensions: [extension], priority: 'builtin',
  75. title: () => extension, loading: extension === 'html' ? 'bytes-complete' : 'text-pages',
  76. }))
  77. }
  78. await h.rt.mount(plugin)
  79. expect(h.previews.candidates('page.html').map(item => item.id)).toEqual(['dedicated-html', ID])
  80. expect(h.previews.candidates('README.md').map(item => item.id)).toEqual(['dedicated-md', ID])
  81. })
  82. })