groupHost.test.ts 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. import { mount } from '@vue/test-utils'
  2. import { nextTick } from 'vue'
  3. import { describe, expect, it } from 'vitest'
  4. import type { GroupAgent } from '@shared/types'
  5. import CreateGroupDialog from '@/components/groups/CreateGroupDialog.vue'
  6. import GroupManager from '@/components/groups/GroupManager.vue'
  7. function agent(id: string, profile: string, displayName: string, isHost: boolean, replyWithoutMention: boolean): GroupAgent {
  8. return {
  9. id, roomId: 'room-1', profile, displayName, description: `${displayName} 的职责`, storedSessionId: null,
  10. lastContextMessageSeq: 0, enabled: true, replyWithoutMention, isHost,
  11. model: null, provider: null, reasoningEffort: null, fastMode: null,
  12. createdAt: 1, updatedAt: 1, status: 'idle',
  13. }
  14. }
  15. describe('group host controls', () => {
  16. it('keeps one independent host selection when creating a v5 room', async () => {
  17. const wrapper = mount(CreateGroupDialog, {
  18. attachTo: document.body,
  19. props: { open: true, profiles: ['yaoyao', 'yaoer'], hostEnabled: true },
  20. global: { stubs: { teleport: true } },
  21. })
  22. await nextTick()
  23. await wrapper.get('input[placeholder="例如:产品评审"]').setValue('主持人验收')
  24. const profileButtons = wrapper.findAll('.agent-picker > button')
  25. await profileButtons[1]!.trigger('click')
  26. const host = wrapper.get<HTMLSelectElement>('select[aria-label="主持人"]')
  27. await host.setValue('yaoer')
  28. await profileButtons[1]!.trigger('click')
  29. expect(host.element.value).toBe('yaoyao')
  30. await profileButtons[1]!.trigger('click')
  31. await host.setValue('yaoer')
  32. expect(wrapper.get<HTMLInputElement>('input[aria-label="所有成员无需 @ 也回复"]').element.checked).toBe(true)
  33. await wrapper.get('.solid-button').trigger('click')
  34. expect(wrapper.emitted('create')?.[0]?.[0]).toEqual({
  35. name: '主持人验收', profiles: ['yaoyao', 'yaoer'], hostProfile: 'yaoer', autoReply: true, replyRounds: 3,
  36. })
  37. wrapper.unmount()
  38. })
  39. it('keeps the legacy v4 create UI and payload free of host fields', async () => {
  40. const wrapper = mount(CreateGroupDialog, {
  41. attachTo: document.body,
  42. props: { open: true, profiles: ['yaoyao'], hostEnabled: false },
  43. global: { stubs: { teleport: true } },
  44. })
  45. await nextTick()
  46. expect(wrapper.find('select[aria-label="主持人"]').exists()).toBe(false)
  47. expect(wrapper.find('input[aria-label="启用自动回复"]').exists()).toBe(true)
  48. await wrapper.get('input[placeholder="例如:产品评审"]').setValue('v4 房间')
  49. await wrapper.get('.solid-button').trigger('click')
  50. expect(wrapper.emitted('create')?.[0]?.[0]).not.toHaveProperty('hostProfile')
  51. wrapper.unmount()
  52. })
  53. it('emits one host promotion and explains the independent auto-reply setting', async () => {
  54. const first = agent('agent-1', 'yaoyao', '夭夭', true, true)
  55. const second = agent('agent-2', 'yaoer', '瑶儿', false, false)
  56. const wrapper = mount(GroupManager, {
  57. attachTo: document.body,
  58. props: {
  59. room: { id: 'room-1', name: '群聊', memberIds: [first.id, second.id], replyRounds: 3 },
  60. agents: [first, second],
  61. hostEnabled: true,
  62. },
  63. })
  64. const selector = wrapper.get<HTMLSelectElement>('select[aria-label="主持人"]')
  65. expect(selector.element.value).toBe('agent-1')
  66. expect(wrapper.findAll('.host-badge')).toHaveLength(1)
  67. await selector.setValue('agent-2')
  68. expect(wrapper.emitted('updateAgent')?.[0]).toEqual(['agent-2', { isHost: true }])
  69. await wrapper.setProps({ agents: [{ ...first, isHost: false }, { ...second, isHost: true }] })
  70. await wrapper.get('button[aria-label="设置瑶儿"]').trigger('click')
  71. await nextTick()
  72. const dialog = document.querySelector('[role="dialog"][aria-label="瑶儿 Agent 设置"]')
  73. expect(dialog?.textContent).toContain('主持人始终处理用户无 @ 消息')
  74. expect(dialog?.querySelector('input[aria-label="无需 @ 也回复"]')).not.toBeNull()
  75. wrapper.unmount()
  76. })
  77. it('blocks removing a host when no enabled replacement exists and shows manager errors', () => {
  78. const first = agent('agent-1', 'yaoyao', '夭夭', true, true)
  79. const second = { ...agent('agent-2', 'yaoer', '瑶儿', false, false), enabled: false }
  80. const wrapper = mount(GroupManager, {
  81. props: {
  82. room: { id: 'room-1', name: '群聊', memberIds: [first.id, second.id], replyRounds: 3 },
  83. agents: [first, second],
  84. hostEnabled: true,
  85. managerError: '需要另一位已启用成员',
  86. },
  87. })
  88. const removeHost = wrapper.get<HTMLButtonElement>('button[aria-label="移除夭夭"]')
  89. expect(removeHost.element.disabled).toBe(true)
  90. expect(removeHost.attributes('title')).toContain('已启用成员')
  91. expect(wrapper.get('[role="alert"]').text()).toBe('需要另一位已启用成员')
  92. })
  93. })