messageMedia.test.ts 3.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546
  1. import { mount } from '@vue/test-utils'
  2. import { describe, expect, it } from 'vitest'
  3. import MarkdownContent from '@/components/messages/MarkdownContent.vue'
  4. import { normalizeChatMessage } from '@/utils/normalize'
  5. import { chatMessageToUi, workspaceMessagesToUi } from '@/components/workspace/viewModels'
  6. const attachments = [
  7. { id: 'doc', name: 'bamboo-sample.txt', size: 134, kind: 'file' as const, url: '/api/app/files/doc/download' },
  8. { id: 'pic', name: 'bamboo.jpg', kind: 'image' as const, url: '/api/app/files/pic/preview' },
  9. ]
  10. describe('ordered message media', () => {
  11. it('keeps media between prose bubbles and deduplicates trailing attachments', async () => {
  12. const wrapper = mount(MarkdownContent, { props: {
  13. separateMedia: true, bubble: true, fileCards: true, attachments,
  14. content: '先看图片。\n\n![竹叶](/api/app/files/pic/preview)\n\n再看文件。\n\n[bamboo-sample.txt](/api/app/files/doc/download)\n\n最后一段。',
  15. } })
  16. expect([...wrapper.get('.message-parts').element.children].map(el => el.className)).toEqual(['message-prose chat-bubble', 'message-media', 'message-prose chat-bubble', 'message-file', 'message-prose chat-bubble'])
  17. expect(wrapper.findAll('.message-file')).toHaveLength(1)
  18. expect(wrapper.find('.chat-bubble img').exists()).toBe(false)
  19. expect(wrapper.get('.message-file__size').text()).toBe('134 B')
  20. await wrapper.get('.message-file').trigger('click')
  21. expect(wrapper.emitted('preview')?.[0]?.[0]).toEqual(attachments[0])
  22. })
  23. it('does not turn code examples or inline prose links into separate attachments', () => {
  24. const wrapper = mount(MarkdownContent, { props: { separateMedia: true, bubble: true, fileCards: true,
  25. content: '```md\n![示例](/api/app/files/pic/preview)\n```\n\n请阅读[文档](/api/app/files/doc/download),然后继续。',
  26. } })
  27. expect(wrapper.findAll('.message-media, .message-file')).toHaveLength(0)
  28. expect(wrapper.get('.chat-bubble').text()).toContain('请阅读文档,然后继续。')
  29. })
  30. it('preserves typed content order through normalization without parsing user Markdown', () => {
  31. const message = chatMessageToUi(normalizeChatMessage({ id: 'ordered', role: 'user', content: [
  32. { type: 'text', text: '**原样文字**' },
  33. { type: 'file', id: 'doc', name: 'bamboo-sample.txt', url: '/api/app/files/doc/download', size: 134 },
  34. { type: 'text', text: '结尾' },
  35. ] }, 'session'))
  36. const wrapper = mount(MarkdownContent, { props: { content: message.content, attachments: message.attachments, contentParts: message.contentParts, separateMedia: true, bubble: true, plain: true } })
  37. expect([...wrapper.get('.message-parts').element.children].map(el => el.textContent)).toEqual(['**原样文字**', 'bamboo-sample.txt134 B', '结尾'])
  38. })
  39. it('retains workspace metadata after resolving an inline path', () => {
  40. const message = workspaceMessagesToUi([{ id: 'w', role: 'assistant', content: '[文件](/tmp/bamboo-sample.txt)', attachments: [{ id: 'doc', name: 'bamboo-sample.txt', size: 134, mimeType: 'text/plain', sourcePath: '/tmp/bamboo-sample.txt' }], tools: [], status: 'complete' } as any])[0]!
  41. const wrapper = mount(MarkdownContent, { props: { content: message.content, attachments: message.attachments, separateMedia: true, fileCards: true } })
  42. expect(wrapper.findAll('.message-file')).toHaveLength(1)
  43. expect(wrapper.text()).toBe('bamboo-sample.txt134 B')
  44. })
  45. })