document-registry.client.spec.ts 3.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273
  1. import { describe, expect, it, vi } from 'vitest'
  2. import { DocumentPreviewRegistry } from '../src/client/document/registry.ts'
  3. import type { DocumentPreviewDefinition } from '../src/client/document/registry.ts'
  4. function definition(id: string, overrides: Partial<DocumentPreviewDefinition> = {}): DocumentPreviewDefinition {
  5. return { id, title: () => id, extensions: ['md'], loading: 'text-pages', ...overrides }
  6. }
  7. describe('document preview implementations', () => {
  8. it('retains matching builtins as alternatives beneath extensions', () => {
  9. const registry = new DocumentPreviewRegistry()
  10. const builtin = definition('builtin', { priority: 'builtin' })
  11. const extension = definition('extension')
  12. registry.register(builtin)
  13. const dispose = registry.register(extension)
  14. expect(registry.candidates('/work/notes.MD')).toEqual([extension, builtin])
  15. dispose()
  16. expect(registry.candidates('/work/notes.md')).toEqual([builtin])
  17. })
  18. it('breaks same-band ties by suffix specificity and then registration order', () => {
  19. const registry = new DocumentPreviewRegistry()
  20. const short = definition('short', { extensions: ['gz'] })
  21. const first = definition('first', { extensions: ['tar.gz'] })
  22. const second = definition('second', { extensions: ['.TAR.GZ'] })
  23. registry.register(short)
  24. registry.register(first)
  25. registry.register(second)
  26. expect(registry.candidates('C:\\work\\archive.TAR.GZ')).toEqual([first, second, short])
  27. })
  28. it('does not mistake directory names or unknown suffixes for file matches', () => {
  29. const registry = new DocumentPreviewRegistry()
  30. registry.register(definition('markdown'))
  31. expect(registry.candidates('/work.md/plain')).toEqual([])
  32. expect(registry.candidates('/work/file.bin')).toEqual([])
  33. expect(registry.candidates('')).toEqual([])
  34. expect(registry.candidates('/work/.md')).toHaveLength(1)
  35. })
  36. it('keeps snapshots stable between changes and notifies after publishing', () => {
  37. const registry = new DocumentPreviewRegistry()
  38. const empty = registry.getSnapshot()
  39. expect(registry.getSnapshot()).toBe(empty)
  40. const seen: Array<readonly DocumentPreviewDefinition[]> = []
  41. const listener = vi.fn(() => { seen.push(registry.getSnapshot()) })
  42. const unsubscribe = registry.subscribe(listener)
  43. const entry = definition('markdown')
  44. const dispose = registry.register(entry)
  45. const registered = registry.getSnapshot()
  46. expect(registered).toEqual([entry])
  47. expect(registry.getSnapshot()).toBe(registered)
  48. dispose()
  49. dispose()
  50. expect(seen).toEqual([[entry], []])
  51. unsubscribe()
  52. registry.register(entry)
  53. expect(listener).toHaveBeenCalledTimes(2)
  54. })
  55. it('rejects duplicate ids without replacing the existing registration', () => {
  56. const registry = new DocumentPreviewRegistry()
  57. const entry = definition('markdown')
  58. const disposeOld = registry.register(entry)
  59. expect(() => registry.register(definition('markdown'))).toThrow(/duplicate implementation/u)
  60. expect(registry.getSnapshot()).toEqual([entry])
  61. disposeOld()
  62. const replacement = definition('markdown', { extensions: ['txt'] })
  63. registry.register(replacement)
  64. disposeOld()
  65. expect(registry.candidates('notes.txt')).toEqual([replacement])
  66. })
  67. })