mediaSequence.test.ts 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354
  1. import { describe, expect, it } from 'vitest'
  2. import { mediaItemsFromMessages, mediaUrlIdentity, previewItemFromUrl } from '@/components/library/mediaSequence'
  3. import type { UiMessage } from '@/components/messages/types'
  4. describe('conversation media sequence', () => {
  5. it('follows inline and typed positions instead of moving direct attachments first', () => {
  6. const attachments = [{ id: 'last', name: 'last.png', kind: 'image' as const, url: '/media/last.png' }]
  7. expect(mediaItemsFromMessages([{ id: 'ordered', role: 'assistant', content: '![首图](/media/first.png)', attachments }]).map(item => item.name)).toEqual(['first.png', 'last.png'])
  8. expect(mediaItemsFromMessages([{ id: 'typed', role: 'assistant', content: '', attachments, contentParts: [{attachmentId:'last'}, {text:'![首图](/media/first.png)'}] }]).map(item => item.name)).toEqual(['last.png', 'first.png'])
  9. expect(mediaItemsFromMessages([{id:'code',role:'assistant',content:'```md\n![示例](/media/code.png)\n```'}])).toEqual([])
  10. })
  11. it('keeps visible image and video order while deduplicating the same URL', () => {
  12. const messages: UiMessage[] = [
  13. { id: 'one', role: 'assistant', content: '![第一张](/media/first.png)' },
  14. { id: 'two', role: 'assistant', content: '[视频](/media/second.mp4)', attachments: [{ id: 'a', name: '第一张.png', kind: 'image', url: '/media/first.png' }] },
  15. { id: 'three', role: 'assistant', content: '普通文本', attachments: [{ id: 'b', name: '第三张.webp', kind: 'image', url: '/media/third.webp' }] },
  16. { id: 'four', role: 'assistant', content: 'MEDIA:/media/fourth.png' },
  17. ]
  18. expect(mediaItemsFromMessages(messages).map(item => [item.name, item.kind, item.previewUrl])).toEqual([
  19. ['first.png', 'image', '/media/first.png'],
  20. ['second.mp4', 'video', '/media/second.mp4'],
  21. ['第三张.webp', 'image', '/media/third.webp'],
  22. ['fourth.png', 'image', '/media/fourth.png'],
  23. ])
  24. })
  25. it('infers full-view media kind from a clicked conversation link', () => {
  26. expect(previewItemFromUrl('片段.webm', '/media/clip.webm')).toMatchObject({ kind: 'video', previewUrl: '/media/clip.webm' })
  27. })
  28. it('gives rendered and source Chinese paths the same media identity', () => {
  29. expect(mediaUrlIdentity('/Users/samien/Agents/瑶儿/单视角图片.png')).toBe(
  30. mediaUrlIdentity('/Users/samien/Agents/%E7%91%B6%E5%84%BF/%E5%8D%95%E8%A7%86%E8%A7%92%E5%9B%BE%E7%89%87.png'),
  31. )
  32. })
  33. it('uses the same Profile-scoped URL and filename as rendered server images', () => {
  34. const items = mediaItemsFromMessages([
  35. { id: 'one', role: 'assistant', profile: 'yaoer', content: 'MEDIA:/Users/test/工作/图片.png\n![第二张](/Users/test/工作/第二张.png)' },
  36. ])
  37. expect(items.map(item => item.name)).toEqual(['图片.png', '第二张.png'])
  38. const url = new URL(items[0]!.previewUrl!, window.location.origin)
  39. expect(url.pathname).toBe('/api/files/download')
  40. expect(url.searchParams.get('path')).toBe('/Users/test/工作/图片.png')
  41. expect(url.searchParams.get('profile')).toBe('yaoer')
  42. expect(items.every(item => item.kind === 'image')).toBe(true)
  43. })
  44. it('matches retried media without merging different Profile authorizations', () => {
  45. const original='/api/files/download?path=%2Fwork%2Fa.png&preview=1&profile=one'
  46. expect(mediaUrlIdentity(original)).toBe(mediaUrlIdentity(original+'&_retry=123'))
  47. expect(mediaUrlIdentity(original)).not.toBe(mediaUrlIdentity(original.replace('profile=one','profile=two')))
  48. })
  49. })