botModelSettingsPanel.test.ts 4.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768
  1. import { mount, flushPromises } from '@vue/test-utils'
  2. import { beforeEach, afterEach, expect, it, vi } from 'vitest'
  3. import BotModelSettingsPanel from '@/components/workspace/BotModelSettingsPanel.vue'
  4. const api = vi.hoisted(() => vi.fn())
  5. vi.mock('@/api/client', () => ({ apiRequest: api }))
  6. const defaults = {provider:'openai',model:'model-a',reasoningEffort:'medium',fastMode:'normal'}
  7. const agent = {id:'bot-a',profile:'default',name:'甲',avatar:'',instructions:'',nodeId:'local',archived:false,revision:1,createdAt:1,updatedAt:1}
  8. const catalogue = () => ({version:1,defaults,settings:null,revision:1,models:[
  9. {provider:'openai',model:'model-a',name:'A',reasoningEfforts:['none','high'],reasoningKnown:false,fastModes:['normal','fast','auto','cold']},
  10. {provider:'other',model:'model-b',name:'B',reasoningEfforts:[],reasoningKnown:true,fastModes:['normal']},
  11. ]})
  12. beforeEach(() => { api.mockReset(); api.mockResolvedValue(catalogue()) })
  13. afterEach(() => vi.restoreAllMocks())
  14. it('loads the Bot catalogue, preserves full speed modes, and saves with a revision', async () => {
  15. const view = mount(BotModelSettingsPanel,{props:{agent}}); await flushPromises()
  16. await view.get('[aria-label="Bot 速度"]').setValue('cold')
  17. api.mockResolvedValueOnce({agent:{...agent,revision:2,modelSettings:{provider:null,model:null,reasoningEffort:null,fastMode:'cold'}}})
  18. await view.get('form').trigger('submit'); await flushPromises()
  19. expect(api).toHaveBeenLastCalledWith('/api/app/agents/bot-a',{method:'PATCH',body:{modelSettings:{provider:null,model:null,reasoningEffort:null,fastMode:'cold'},expectedRevision:1}})
  20. expect(view.text()).toContain('已保存'); view.unmount()
  21. })
  22. it('does not silently reset incompatible settings after changing model', async () => {
  23. const view = mount(BotModelSettingsPanel,{props:{agent}}); await flushPromises()
  24. await view.get('[aria-label="Bot 思考等级"]').setValue('high')
  25. await view.get('[aria-label="Bot 模型"]').setValue('["other","model-b"]')
  26. expect(view.text()).toContain('不支持所选思考等级')
  27. expect(view.get<HTMLButtonElement>('button[type="submit"]').element.disabled).toBe(true)
  28. view.unmount()
  29. })
  30. it('keeps dirty drafts when another client saves and disables stale submission', async () => {
  31. const view = mount(BotModelSettingsPanel,{props:{agent}}); await flushPromises()
  32. await view.get('[aria-label="Bot 速度"]').setValue('auto')
  33. await view.setProps({agent:{...agent,revision:2}})
  34. expect(view.get<HTMLSelectElement>('[aria-label="Bot 速度"]').element.value).toBe('auto')
  35. expect(view.text()).toContain('当前草稿已保留')
  36. expect(api).toHaveBeenCalledTimes(1); view.unmount()
  37. })
  38. it('confirms the exact model in the profile before saving', async () => {
  39. vi.spyOn(window,'confirm').mockReturnValue(true)
  40. const view = mount(BotModelSettingsPanel,{props:{agent}}); await flushPromises()
  41. api.mockResolvedValueOnce({confirmationRequired:true,confirmationMessage:'费用较高',confirmationTarget:'["openai","model-a"]'})
  42. .mockResolvedValueOnce({agent:{...agent,revision:2}})
  43. await view.get('form').trigger('submit'); await flushPromises()
  44. expect(window.confirm).toHaveBeenCalledWith('费用较高')
  45. expect(api.mock.lastCall?.[1].body.confirmedModel).toBe('["openai","model-a"]'); view.unmount()
  46. })
  47. it('refreshes a newer cross-client update that arrives while its own save is in flight', async () => {
  48. const view=mount(BotModelSettingsPanel,{props:{agent}});await flushPromises()
  49. await view.get('[aria-label="Bot 速度"]').setValue('cold')
  50. let finish:(value:any)=>void=()=>{}
  51. api.mockImplementationOnce(()=>new Promise(resolve=>{finish=resolve}))
  52. .mockResolvedValueOnce({...catalogue(),revision:3,settings:{provider:null,model:null,reasoningEffort:null,fastMode:'auto'}})
  53. await view.get('form').trigger('submit')
  54. expect(view.emitted('busyChange')?.at(-1)).toEqual([true])
  55. await view.setProps({agent:{...agent,revision:3}})
  56. finish({agent:{...agent,revision:2}});await flushPromises()
  57. expect(view.emitted('busyChange')?.at(-1)).toEqual([false])
  58. expect(api).toHaveBeenLastCalledWith('/api/app/agents/bot-a/model-options')
  59. expect(view.get<HTMLSelectElement>('[aria-label="Bot 速度"]').element.value).toBe('auto');view.unmount()
  60. })
  61. it('disables editing when a reload discovers an unavailable bridge and allows retry',async()=>{
  62. const view=mount(BotModelSettingsPanel,{props:{agent}});await flushPromises()
  63. api.mockRejectedValueOnce(new Error('请升级工具桥'))
  64. await view.get('button[type="button"]').trigger('click');await flushPromises()
  65. expect(view.find('form').exists()).toBe(false);expect(view.text()).toContain('请升级工具桥')
  66. await view.get('button[type="button"]').trigger('click');await flushPromises()
  67. expect(view.find('form').exists()).toBe(true);view.unmount()
  68. })