|
|
@@ -0,0 +1,49 @@
|
|
|
+import {afterEach,expect,it,vi} from 'vitest'
|
|
|
+import {flushPromises,mount} from '@vue/test-utils'
|
|
|
+import Panel from '../../src/client/components/workspace/WorkspaceInspectorPanel.vue'
|
|
|
+import {apiRequest} from '../../src/client/api/client'
|
|
|
+import type {WorkspaceInspectorEntry} from '../../src/shared/workspacePanels'
|
|
|
+vi.mock('../../src/client/api/client',()=>({apiRequest:vi.fn()}))
|
|
|
+const entry=(id:string,at:number,method='message.delta',direction:WorkspaceInspectorEntry['direction']='event',agentId='agent'):WorkspaceInspectorEntry=>({id,at,method,direction,agentId,runId:'run',data:{text:id}})
|
|
|
+afterEach(()=>{vi.restoreAllMocks();vi.clearAllMocks()})
|
|
|
+function setup(entries:WorkspaceInspectorEntry[]){
|
|
|
+ vi.spyOn(document,'hidden','get').mockReturnValue(false)
|
|
|
+ vi.mocked(apiRequest).mockResolvedValue({entries})
|
|
|
+ return mount(Panel,{props:{conversationId:'conversation'},global:{stubs:{AppIcon:true}}})
|
|
|
+}
|
|
|
+it('sorts by timestamp before grouping, shows newest groups first and keeps delta text in order',async()=>{
|
|
|
+ const wrapper=setup([entry('done',400,'message.complete'),entry('B',300),entry('A',200),entry('other',100,'message.delta','event','other')])
|
|
|
+ try{
|
|
|
+ await flushPromises()
|
|
|
+ const rows=wrapper.findAll('details')
|
|
|
+ expect(rows.map(row=>row.find('.summary').text())).toEqual(['done','AB','other'])
|
|
|
+ expect(rows[1]!.find('time').text()).toMatch(/\.300$/)
|
|
|
+ expect(rows[1]!.find('.tag').text()).toContain('×2')
|
|
|
+ expect(JSON.parse(rows[1]!.find('pre').text()).map((e:WorkspaceInspectorEntry)=>e.id)).toEqual(['A','B'])
|
|
|
+ }finally{wrapper.unmount()}
|
|
|
+})
|
|
|
+it('sorts Raw responses and requests newest first, including equal-time arrival order',async()=>{
|
|
|
+ const wrapper=setup([entry('response',300,'session.usage','response'),entry('request',100,'prompt.submit','request'),entry('same-1',200,'first','request'),entry('same-2',200,'second','response')])
|
|
|
+ try{
|
|
|
+ await flushPromises();await wrapper.get('.lenses button:last-child').trigger('click');await flushPromises()
|
|
|
+ expect(wrapper.findAll('pre').map(row=>JSON.parse(row.text()).id)).toEqual(['response','same-2','same-1','request'])
|
|
|
+ }finally{wrapper.unmount()}
|
|
|
+})
|
|
|
+it('opens at the top, follows new records at capacity, and resets a changed lens after reading older records',async()=>{
|
|
|
+ const wrapper=setup([entry('old',100,'message.complete'),entry('new',200,'message.complete')])
|
|
|
+ try{
|
|
|
+ await flushPromises()
|
|
|
+ const list=wrapper.get('.inspector-list').element as HTMLElement
|
|
|
+ expect(list.scrollTop).toBe(0)
|
|
|
+ list.scrollTop=150;await wrapper.get('.inspector-list').trigger('scroll')
|
|
|
+ vi.mocked(apiRequest).mockResolvedValue({entries:[entry('new',200,'message.complete'),entry('latest',300,'message.complete')]})
|
|
|
+ await wrapper.get('.reload').trigger('click');await flushPromises()
|
|
|
+ expect(list.scrollTop).toBe(150)
|
|
|
+ await wrapper.get('.lenses button:last-child').trigger('click');await flushPromises();expect(list.scrollTop).toBe(0)
|
|
|
+ await wrapper.get('.lenses button:first-child').trigger('click');await flushPromises()
|
|
|
+ list.scrollTop=20;await wrapper.get('.inspector-list').trigger('scroll')
|
|
|
+ vi.mocked(apiRequest).mockResolvedValue({entries:[entry('latest',300,'message.complete'),entry('live',400,'message.complete')]})
|
|
|
+ await wrapper.get('.reload').trigger('click');await flushPromises();expect(list.scrollTop).toBe(0)
|
|
|
+ expect(wrapper.find('.summary').text()).toBe('live')
|
|
|
+ }finally{wrapper.unmount()}
|
|
|
+})
|