77import { act , useCallback } from 'react' ;
88import { vi } from 'vitest' ;
99import { render } from '../../test-utils/render.js' ;
10- import { useConsoleMessages } from './useConsoleMessages.js' ;
11- import { CoreEvent , type ConsoleLogPayload } from '@google/gemini-cli-core' ;
12-
13- // Mock coreEvents
14- let consoleLogHandler : ( ( payload : ConsoleLogPayload ) => void ) | undefined ;
10+ import {
11+ useConsoleMessages ,
12+ useErrorCount ,
13+ initializeConsoleStore ,
14+ } from './useConsoleMessages.js' ;
15+ import { coreEvents } from '@google/gemini-cli-core' ;
1516
1617vi . mock ( '@google/gemini-cli-core' , async ( importOriginal ) => {
17- // eslint-disable-next-line @typescript-eslint/no-explicit-any
18- const actual = ( await importOriginal ( ) ) as any ;
18+ const actual = await importOriginal ( ) ;
19+ const handlers = new Map < string , ( payload : unknown ) => void > ( ) ;
20+
1921 return {
20- ...actual ,
22+ ...( actual as Record < string , unknown > ) ,
2123 coreEvents : {
22- on : vi . fn ( ( event , handler ) => {
23- if ( event === CoreEvent . ConsoleLog ) {
24- consoleLogHandler = handler ;
25- }
24+ ...( ( actual as Record < string , unknown > ) [ 'coreEvents' ] as Record <
25+ string ,
26+ unknown
27+ > ) ,
28+ on : vi . fn ( ( event : string , handler : ( payload : unknown ) => void ) => {
29+ handlers . set ( event , handler ) ;
2630 } ) ,
27- off : vi . fn ( ( event ) => {
28- if ( event === CoreEvent . ConsoleLog ) {
29- consoleLogHandler = undefined ;
30- }
31+ off : vi . fn ( ( event : string ) => {
32+ handlers . delete ( event ) ;
3133 } ) ,
32- emitConsoleLog : vi . fn ( ) ,
34+ // Helper for testing to trigger the handlers
35+ _trigger : ( event : string , payload : unknown ) => {
36+ handlers . get ( event ) ?.( payload ) ;
37+ } ,
3338 } ,
3439 } ;
3540} ) ;
3641
3742describe ( 'useConsoleMessages' , ( ) => {
43+ let unmounts : Array < ( ) => void > = [ ] ;
44+
3845 beforeEach ( ( ) => {
3946 vi . useFakeTimers ( ) ;
40- consoleLogHandler = undefined ;
47+ initializeConsoleStore ( ) ;
4148 } ) ;
4249
4350 afterEach ( ( ) => {
51+ for ( const unmount of unmounts ) {
52+ try {
53+ unmount ( ) ;
54+ } catch ( _e ) {
55+ // Ignore unmount errors
56+ }
57+ }
58+ unmounts = [ ] ;
4459 vi . runOnlyPendingTimers ( ) ;
4560 vi . useRealTimers ( ) ;
4661 vi . restoreAllMocks ( ) ;
4762 } ) ;
4863
4964 const useTestableConsoleMessages = ( ) => {
50- const { ... rest } = useConsoleMessages ( ) ;
65+ const consoleMessages = useConsoleMessages ( ) ;
5166 const log = useCallback ( ( content : string ) => {
52- if ( consoleLogHandler ) {
53- consoleLogHandler ( { type : 'log' , content } ) ;
54- }
67+ // @ts -expect-error - internal testing helper
68+ coreEvents . _trigger ( 'console-log' , { type : 'log' , content } ) ;
5569 } , [ ] ) ;
5670 const error = useCallback ( ( content : string ) => {
57- if ( consoleLogHandler ) {
58- consoleLogHandler ( { type : 'error' , content } ) ;
59- }
71+ // @ts -expect-error - internal testing helper
72+ coreEvents . _trigger ( 'console-log' , { type : 'error' , content } ) ;
73+ } , [ ] ) ;
74+ const clearConsoleMessages = useCallback ( ( ) => {
75+ initializeConsoleStore ( ) ;
6076 } , [ ] ) ;
6177 return {
62- ... rest ,
78+ consoleMessages ,
6379 log,
6480 error,
65- clearConsoleMessages : rest . clearConsoleMessages ,
81+ clearConsoleMessages,
6682 } ;
6783 } ;
6884
6985 const renderConsoleMessagesHook = async ( ) => {
70- let hookResult : ReturnType < typeof useTestableConsoleMessages > ;
86+ let hookResult : ReturnType < typeof useTestableConsoleMessages > | undefined ;
7187 function TestComponent ( ) {
7288 hookResult = useTestableConsoleMessages ( ) ;
7389 return null ;
7490 }
7591 const { unmount } = await render ( < TestComponent /> ) ;
92+ unmounts . push ( unmount ) ;
7693 return {
7794 result : {
7895 get current ( ) {
79- return hookResult ;
96+ return hookResult ! ;
8097 } ,
8198 } ,
8299 unmount,
@@ -93,10 +110,7 @@ describe('useConsoleMessages', () => {
93110
94111 act ( ( ) => {
95112 result . current . log ( 'Test message' ) ;
96- } ) ;
97-
98- await act ( async ( ) => {
99- await vi . advanceTimersByTimeAsync ( 60 ) ;
113+ vi . runAllTimers ( ) ;
100114 } ) ;
101115
102116 expect ( result . current . consoleMessages ) . toEqual ( [
@@ -111,10 +125,7 @@ describe('useConsoleMessages', () => {
111125 result . current . log ( 'Test message' ) ;
112126 result . current . log ( 'Test message' ) ;
113127 result . current . log ( 'Test message' ) ;
114- } ) ;
115-
116- await act ( async ( ) => {
117- await vi . advanceTimersByTimeAsync ( 60 ) ;
128+ vi . runAllTimers ( ) ;
118129 } ) ;
119130
120131 expect ( result . current . consoleMessages ) . toEqual ( [
@@ -128,64 +139,93 @@ describe('useConsoleMessages', () => {
128139 act ( ( ) => {
129140 result . current . log ( 'First message' ) ;
130141 result . current . error ( 'Second message' ) ;
131- } ) ;
132-
133- await act ( async ( ) => {
134- await vi . advanceTimersByTimeAsync ( 60 ) ;
142+ vi . runAllTimers ( ) ;
135143 } ) ;
136144
137145 expect ( result . current . consoleMessages ) . toEqual ( [
138146 { type : 'log' , content : 'First message' , count : 1 } ,
139147 { type : 'error' , content : 'Second message' , count : 1 } ,
140148 ] ) ;
141149 } ) ;
150+ } ) ;
142151
143- it ( 'should clear all messages when clearConsoleMessages is called' , async ( ) => {
144- const { result } = await renderConsoleMessagesHook ( ) ;
145-
146- act ( ( ) => {
147- result . current . log ( 'A message' ) ;
148- } ) ;
152+ describe ( 'useErrorCount' , ( ) => {
153+ let unmounts : Array < ( ) => void > = [ ] ;
149154
150- await act ( async ( ) => {
151- await vi . advanceTimersByTimeAsync ( 60 ) ;
152- } ) ;
155+ beforeEach ( ( ) => {
156+ vi . useFakeTimers ( ) ;
157+ initializeConsoleStore ( ) ;
158+ } ) ;
153159
154- expect ( result . current . consoleMessages ) . toHaveLength ( 1 ) ;
160+ afterEach ( ( ) => {
161+ for ( const unmount of unmounts ) {
162+ try {
163+ unmount ( ) ;
164+ } catch ( _e ) {
165+ // Ignore unmount errors
166+ }
167+ }
168+ unmounts = [ ] ;
169+ vi . runOnlyPendingTimers ( ) ;
170+ vi . useRealTimers ( ) ;
171+ vi . restoreAllMocks ( ) ;
172+ } ) ;
155173
156- act ( ( ) => {
157- result . current . clearConsoleMessages ( ) ;
158- } ) ;
174+ const renderErrorCountHook = async ( ) => {
175+ let hookResult : ReturnType < typeof useErrorCount > ;
176+ function TestComponent ( ) {
177+ hookResult = useErrorCount ( ) ;
178+ return null ;
179+ }
180+ const { unmount } = await render ( < TestComponent /> ) ;
181+ unmounts . push ( unmount ) ;
182+ return {
183+ result : {
184+ get current ( ) {
185+ return hookResult ;
186+ } ,
187+ } ,
188+ unmount,
189+ } ;
190+ } ;
159191
160- expect ( result . current . consoleMessages ) . toHaveLength ( 0 ) ;
192+ it ( 'should initialize with an error count of 0' , async ( ) => {
193+ const { result } = await renderErrorCountHook ( ) ;
194+ expect ( result . current . errorCount ) . toBe ( 0 ) ;
161195 } ) ;
162196
163- it ( 'should clear the pending timeout when clearConsoleMessages is called' , async ( ) => {
164- const { result } = await renderConsoleMessagesHook ( ) ;
165- const clearTimeoutSpy = vi . spyOn ( global , 'clearTimeout' ) ;
166-
197+ it ( 'should increment error count when an error is logged' , async ( ) => {
198+ const { result } = await renderErrorCountHook ( ) ;
167199 act ( ( ) => {
168- result . current . log ( 'A message' ) ;
200+ // @ts -expect-error - internal testing helper
201+ coreEvents . _trigger ( 'console-log' , { type : 'error' , content : 'error' } ) ;
202+ vi . runAllTimers ( ) ;
169203 } ) ;
204+ expect ( result . current . errorCount ) . toBe ( 1 ) ;
205+ } ) ;
170206
207+ it ( 'should not increment error count for non-error logs' , async ( ) => {
208+ const { result } = await renderErrorCountHook ( ) ;
171209 act ( ( ) => {
172- result . current . clearConsoleMessages ( ) ;
210+ // @ts -expect-error - internal testing helper
211+ coreEvents . _trigger ( 'console-log' , { type : 'log' , content : 'log' } ) ;
212+ vi . runAllTimers ( ) ;
173213 } ) ;
174-
175- expect ( clearTimeoutSpy ) . toHaveBeenCalled ( ) ;
176- // clearTimeoutSpy.mockRestore() is handled by afterEach restoreAllMocks
214+ expect ( result . current . errorCount ) . toBe ( 0 ) ;
177215 } ) ;
178216
179- it ( 'should clean up the timeout on unmount' , async ( ) => {
180- const { result, unmount } = await renderConsoleMessagesHook ( ) ;
181- const clearTimeoutSpy = vi . spyOn ( global , 'clearTimeout' ) ;
182-
217+ it ( 'should clear the error count' , async ( ) => {
218+ const { result } = await renderErrorCountHook ( ) ;
183219 act ( ( ) => {
184- result . current . log ( 'A message' ) ;
220+ // @ts -expect-error - internal testing helper
221+ coreEvents . _trigger ( 'console-log' , { type : 'error' , content : 'error' } ) ;
222+ vi . runAllTimers ( ) ;
185223 } ) ;
224+ expect ( result . current . errorCount ) . toBe ( 1 ) ;
186225
187- unmount ( ) ;
188-
189- expect ( clearTimeoutSpy ) . toHaveBeenCalled ( ) ;
226+ act ( ( ) => {
227+ result . current . clearErrorCount ( ) ;
228+ } ) ;
229+ expect ( result . current . errorCount ) . toBe ( 0 ) ;
190230 } ) ;
191231} ) ;
0 commit comments