document-registry.client.spec.ts 4.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. import { describe, expect, it, vi } from 'vitest'
  2. import { binaryDocumentPath, 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('marks a path binary only for declared binary suffixes, case-insensitively', () => {
  56. const image = definition('image', { extensions: ['png', 'svg'], binaryExtensions: ['png'] })
  57. const text = definition('text', { extensions: [] })
  58. expect(binaryDocumentPath([image, text], '/work/photo.PNG')).toBe(true)
  59. expect(binaryDocumentPath([image, text], 'C:\\work\\photo.png')).toBe(true)
  60. expect(binaryDocumentPath([image, text], '/work/diagram.svg')).toBe(false)
  61. expect(binaryDocumentPath([image, text], '/work.png/plain')).toBe(false)
  62. expect(binaryDocumentPath([text], '/work/photo.png')).toBe(false)
  63. })
  64. it('rejects a binary suffix outside the declared extensions without registering', () => {
  65. const registry = new DocumentPreviewRegistry()
  66. expect(() => registry.register(definition('image', { extensions: ['png', 'svg'], binaryExtensions: ['pdf'] })))
  67. .toThrow(/binary suffix "pdf" outside its extensions/u)
  68. expect(registry.getSnapshot()).toEqual([])
  69. registry.register(definition('image', { extensions: ['PNG', 'svg'], binaryExtensions: ['.png'] }))
  70. expect(registry.getSnapshot()).toHaveLength(1)
  71. })
  72. it('rejects duplicate ids without replacing the existing registration', () => {
  73. const registry = new DocumentPreviewRegistry()
  74. const entry = definition('markdown')
  75. const disposeOld = registry.register(entry)
  76. expect(() => registry.register(definition('markdown'))).toThrow(/duplicate implementation/u)
  77. expect(registry.getSnapshot()).toEqual([entry])
  78. disposeOld()
  79. const replacement = definition('markdown', { extensions: ['txt'] })
  80. registry.register(replacement)
  81. disposeOld()
  82. expect(registry.candidates('notes.txt')).toEqual([replacement])
  83. })
  84. })