MediaAttachment.test.ts raw

   1  import { describe, it, expect } from 'vitest'
   2  import { MediaAttachment } from './MediaAttachment'
   3  
   4  describe('MediaAttachment', () => {
   5    describe('fromUrl', () => {
   6      it('detects image from URL extension', () => {
   7        const attachment = MediaAttachment.fromUrl('https://example.com/photo.jpg')
   8  
   9        expect(attachment.type).toBe('image')
  10        expect(attachment.url).toBe('https://example.com/photo.jpg')
  11        expect(attachment.status).toBe('completed')
  12        expect(attachment.isImage).toBe(true)
  13      })
  14  
  15      it('detects video from URL extension', () => {
  16        const attachment = MediaAttachment.fromUrl('https://example.com/video.mp4')
  17  
  18        expect(attachment.type).toBe('video')
  19        expect(attachment.isVideo).toBe(true)
  20      })
  21  
  22      it('detects audio from URL extension', () => {
  23        const attachment = MediaAttachment.fromUrl('https://example.com/audio.mp3')
  24  
  25        expect(attachment.type).toBe('audio')
  26        expect(attachment.isAudio).toBe(true)
  27      })
  28  
  29      it('defaults to file for unknown extensions', () => {
  30        const attachment = MediaAttachment.fromUrl('https://example.com/document.pdf')
  31  
  32        expect(attachment.type).toBe('file')
  33      })
  34  
  35      it('uses mime type over URL extension', () => {
  36        const attachment = MediaAttachment.fromUrl(
  37          'https://example.com/media',
  38          'video/mp4'
  39        )
  40  
  41        expect(attachment.type).toBe('video')
  42      })
  43  
  44      it('handles URLs with query parameters', () => {
  45        const attachment = MediaAttachment.fromUrl(
  46          'https://example.com/photo.png?size=large'
  47        )
  48  
  49        expect(attachment.type).toBe('image')
  50      })
  51  
  52      it('detects various image formats', () => {
  53        const formats = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'heic', 'avif', 'svg']
  54  
  55        for (const format of formats) {
  56          const attachment = MediaAttachment.fromUrl(`https://example.com/img.${format}`)
  57          expect(attachment.type).toBe('image')
  58        }
  59      })
  60  
  61      it('detects various video formats', () => {
  62        const formats = ['mp4', 'webm', 'mov', 'avi', 'mkv']
  63  
  64        for (const format of formats) {
  65          const attachment = MediaAttachment.fromUrl(`https://example.com/vid.${format}`)
  66          expect(attachment.type).toBe('video')
  67        }
  68      })
  69  
  70      it('detects various audio formats', () => {
  71        const formats = ['mp3', 'wav', 'ogg', 'flac', 'm4a']
  72  
  73        for (const format of formats) {
  74          const attachment = MediaAttachment.fromUrl(`https://example.com/aud.${format}`)
  75          expect(attachment.type).toBe('audio')
  76        }
  77      })
  78    })
  79  
  80    describe('fromMetadata', () => {
  81      it('creates attachment with full metadata', () => {
  82        const metadata = {
  83          url: 'https://example.com/photo.jpg',
  84          mimeType: 'image/jpeg',
  85          width: 1920,
  86          height: 1080,
  87          size: 102400,
  88          blurhash: 'LEHV6nWB2yk8pyo0adR*.7kCMdnj',
  89          sha256: 'd'.repeat(64),
  90          alt: 'A beautiful sunset'
  91        }
  92  
  93        const attachment = MediaAttachment.fromMetadata(metadata)
  94  
  95        expect(attachment.url).toBe(metadata.url)
  96        expect(attachment.mimeType).toBe('image/jpeg')
  97        expect(attachment.metadata?.width).toBe(1920)
  98        expect(attachment.metadata?.height).toBe(1080)
  99        expect(attachment.alt).toBe('A beautiful sunset')
 100      })
 101    })
 102  
 103    describe('pending', () => {
 104      it('creates pending attachment', () => {
 105        const attachment = MediaAttachment.pending('photo.jpg', 'image')
 106  
 107        expect(attachment.url).toBe('')
 108        expect(attachment.type).toBe('image')
 109        expect(attachment.status).toBe('pending')
 110        expect(attachment.isUploaded).toBe(false)
 111      })
 112    })
 113  
 114    describe('toImetaTag', () => {
 115      it('generates imeta tag for images', () => {
 116        const attachment = MediaAttachment.fromMetadata({
 117          url: 'https://example.com/photo.jpg',
 118          mimeType: 'image/jpeg',
 119          width: 800,
 120          height: 600,
 121          blurhash: 'LGF5?xYk^6#M@-5c,1J5@[or[Q6.'
 122        })
 123  
 124        const tag = attachment.toImetaTag()
 125  
 126        expect(tag).not.toBeNull()
 127        expect(tag![0]).toBe('imeta')
 128        expect(tag).toContain('url https://example.com/photo.jpg')
 129        expect(tag).toContain('m image/jpeg')
 130        expect(tag).toContain('dim 800x600')
 131        expect(tag).toContain('blurhash LGF5?xYk^6#M@-5c,1J5@[or[Q6.')
 132      })
 133  
 134      it('returns null for non-images', () => {
 135        const attachment = MediaAttachment.fromUrl('https://example.com/video.mp4')
 136  
 137        const tag = attachment.toImetaTag()
 138  
 139        expect(tag).toBeNull()
 140      })
 141  
 142      it('includes alt text in tag', () => {
 143        const attachment = MediaAttachment.fromUrl('https://example.com/photo.jpg').withAlt(
 144          'Description'
 145        )
 146  
 147        const tag = attachment.toImetaTag()
 148  
 149        expect(tag).toContain('alt Description')
 150      })
 151    })
 152  
 153    describe('immutable modifications', () => {
 154      it('withAlt returns new instance', () => {
 155        const original = MediaAttachment.fromUrl('https://example.com/photo.jpg')
 156        const modified = original.withAlt('New alt text')
 157  
 158        expect(original.alt).toBeNull()
 159        expect(modified.alt).toBe('New alt text')
 160      })
 161  
 162      it('withStatus returns new instance', () => {
 163        const original = MediaAttachment.pending('file.jpg', 'image')
 164        const modified = original.withStatus('uploading')
 165  
 166        expect(original.status).toBe('pending')
 167        expect(modified.status).toBe('uploading')
 168      })
 169  
 170      it('withUrl returns new instance with completed status', () => {
 171        const original = MediaAttachment.pending('file.jpg', 'image')
 172        const modified = original.withUrl('https://example.com/uploaded.jpg')
 173  
 174        expect(original.url).toBe('')
 175        expect(original.status).toBe('pending')
 176        expect(modified.url).toBe('https://example.com/uploaded.jpg')
 177        expect(modified.status).toBe('completed')
 178        expect(modified.isUploaded).toBe(true)
 179      })
 180    })
 181  
 182    describe('equals', () => {
 183      it('returns true for same URL', () => {
 184        const a = MediaAttachment.fromUrl('https://example.com/photo.jpg')
 185        const b = MediaAttachment.fromUrl('https://example.com/photo.jpg')
 186  
 187        expect(a.equals(b)).toBe(true)
 188      })
 189  
 190      it('returns false for different URLs', () => {
 191        const a = MediaAttachment.fromUrl('https://example.com/photo1.jpg')
 192        const b = MediaAttachment.fromUrl('https://example.com/photo2.jpg')
 193  
 194        expect(a.equals(b)).toBe(false)
 195      })
 196    })
 197  })
 198