22import { beforeEach , describe , expect , it , vi } from 'vitest'
33import { mount } from '@vue/test-utils'
44import { createPinia , setActivePinia } from 'pinia'
5- import { defineComponent } from 'vue'
65
76vi . mock ( 'vue-i18n' , ( ) => ( {
87 useI18n : ( ) => ( {
@@ -19,13 +18,18 @@ import HistoryMessageList from '@/components/hermes/chat/HistoryMessageList.vue'
1918import { useChatStore , type Message , type Session } from '@/stores/hermes/chat'
2019import { useToolTraceVisibility } from '@/composables/useToolTraceVisibility'
2120
22- const MessageItemStub = defineComponent ( {
23- name : 'MessageItem' ,
24- props : {
25- message : { type : Object , required : true } ,
26- highlight : { type : Boolean , default : false } ,
27- } ,
28- template : '<div class="stub-message" :data-role="message.role" :data-id="message.id">{{ message.toolName || message.content }}</div>' ,
21+ vi . mock ( '@/components/hermes/chat/MessageItem.vue' , async ( ) => {
22+ const { defineComponent } = await import ( 'vue' )
23+ return {
24+ default : defineComponent ( {
25+ name : 'MessageItem' ,
26+ props : {
27+ message : { type : Object , required : true } ,
28+ highlight : { type : Boolean , default : false } ,
29+ } ,
30+ template : '<div class="stub-message" :data-role="message.role" :data-id="message.id">{{ message.toolName || message.content }}</div>' ,
31+ } ) ,
32+ }
2933} )
3034
3135function makeSession ( messages : Message [ ] ) : Session {
@@ -61,14 +65,7 @@ describe('tool trace visibility', () => {
6165 ] )
6266 chatStore . abortState = { aborting : true , synced : false }
6367
64- return mount ( MessageList , {
65- global : {
66- stubs : {
67- MessageItem : MessageItemStub ,
68- Transition : false ,
69- } ,
70- } ,
71- } )
68+ return mount ( MessageList )
7269 }
7370
7471 it ( 'shows named transcript and live tool traces by default while keeping unnamed internal tools hidden' , ( ) => {
@@ -85,9 +82,6 @@ describe('tool trace visibility', () => {
8582 it ( 'applies the same default-visible rule to history sessions' , ( ) => {
8683 const wrapper = mount ( HistoryMessageList , {
8784 props : { session : makeSession ( sampleMessages ) } ,
88- global : {
89- stubs : { MessageItem : MessageItemStub } ,
90- } ,
9185 } )
9286
9387 expect ( wrapper . findAll ( '.stub-message' ) . map ( node => node . attributes ( 'data-id' ) ) ) . toEqual ( [
@@ -97,15 +91,43 @@ describe('tool trace visibility', () => {
9791 ] )
9892 } )
9993
94+ it ( 'groups and folds completed history tools from the same run' , async ( ) => {
95+ const wrapper = mount ( HistoryMessageList , {
96+ props : {
97+ session : makeSession ( [
98+ { id : 'user-1' , role : 'user' , content : 'inspect repo' , timestamp : 1 } ,
99+ { id : 'tool-1' , role : 'tool' , content : '' , timestamp : 2 , toolName : 'read_file' , toolResult : 'one' , toolStatus : 'done' , runMarker : 'history-run' } ,
100+ { id : 'tool-2' , role : 'tool' , content : '' , timestamp : 3 , toolName : 'search' , toolResult : 'two' , toolStatus : 'done' , runMarker : 'history-run' } ,
101+ { id : 'assistant-1' , role : 'assistant' , content : 'done' , timestamp : 4 } ,
102+ ] ) ,
103+ } ,
104+ } )
105+
106+ const card = wrapper . get ( '.tool-run-card' )
107+ const toggle = card . get ( '.tool-run-header' )
108+ expect ( toggle . attributes ( 'aria-expanded' ) ) . toBe ( 'false' )
109+ expect ( wrapper . find ( '[data-id="tool-1"]' ) . exists ( ) ) . toBe ( false )
110+ expect ( wrapper . find ( '[data-id="tool-2"]' ) . exists ( ) ) . toBe ( false )
111+
112+ await toggle . trigger ( 'click' )
113+ expect ( toggle . attributes ( 'aria-expanded' ) ) . toBe ( 'true' )
114+ expect ( wrapper . find ( '[data-id="tool-1"]' ) . exists ( ) ) . toBe ( true )
115+ expect ( wrapper . find ( '[data-id="tool-2"]' ) . exists ( ) ) . toBe ( true )
116+
117+ await toggle . trigger ( 'click' )
118+ expect ( toggle . attributes ( 'aria-expanded' ) ) . toBe ( 'false' )
119+ await vi . waitFor ( ( ) => {
120+ expect ( wrapper . find ( '[data-id="tool-1"]' ) . exists ( ) ) . toBe ( false )
121+ expect ( wrapper . find ( '[data-id="tool-2"]' ) . exists ( ) ) . toBe ( false )
122+ } )
123+ } )
124+
100125 it ( 'does not fall back to the live chat session while history session data is loading' , ( ) => {
101126 const chatStore = useChatStore ( )
102127 chatStore . activeSessionId = 'session-1'
103128 chatStore . activeSession = makeSession ( sampleMessages )
104129
105130 const wrapper = mount ( HistoryMessageList , {
106- global : {
107- stubs : { MessageItem : MessageItemStub } ,
108- } ,
109131 } )
110132
111133 expect ( wrapper . findAll ( '.stub-message' ) ) . toHaveLength ( 0 )
@@ -123,9 +145,6 @@ describe('tool trace visibility', () => {
123145
124146 const historyWrapper = mount ( HistoryMessageList , {
125147 props : { session : makeSession ( sampleMessages ) } ,
126- global : {
127- stubs : { MessageItem : MessageItemStub } ,
128- } ,
129148 } )
130149 expect ( historyWrapper . findAll ( '.stub-message' ) . map ( node => node . attributes ( 'data-id' ) ) ) . toEqual ( [
131150 'user-1' ,
@@ -143,14 +162,7 @@ describe('tool trace visibility', () => {
143162 ] )
144163 chatStore . abortState = { aborting : true , synced : false }
145164
146- const wrapper = mount ( MessageList , {
147- global : {
148- stubs : {
149- MessageItem : MessageItemStub ,
150- Transition : false ,
151- } ,
152- } ,
153- } )
165+ const wrapper = mount ( MessageList )
154166
155167 expect ( wrapper . findAll ( '.stub-message' ) . map ( node => node . attributes ( 'data-id' ) ) ) . toContain ( 'tool-weather' )
156168 expect ( wrapper . findAll ( '.tool-call-name' ) . map ( node => node . text ( ) ) ) . not . toContain ( 'weather' )
0 commit comments