@@ -9,15 +9,15 @@ import {
99} from 'bun:test'
1010import React from 'react'
1111
12- import { StatusIndicator } from '../status-indicator'
12+ import { StatusIndicator , StatusElapsedTime } from '../status-indicator'
1313
1414import '../../state/theme-store' // Initialize theme store
1515import { renderToStaticMarkup } from 'react-dom/server'
1616
1717import * as codebuffClient from '../../utils/codebuff-client'
1818
1919
20- describe ( 'StatusIndicator timer rendering ' , ( ) => {
20+ describe ( 'StatusIndicator state transitions ' , ( ) => {
2121 let getClientSpy : ReturnType < typeof spyOn >
2222
2323 beforeEach ( ( ) => {
@@ -30,46 +30,120 @@ describe('StatusIndicator timer rendering', () => {
3030 getClientSpy . mockRestore ( )
3131 } )
3232
33- test ( 'shows elapsed seconds when waiting for response' , ( ) => {
34- const now = Date . now ( )
35- const markup = renderToStaticMarkup (
36- < StatusIndicator
37- clipboardMessage = { null }
38- isActive = { true }
39- isWaitingForResponse = { true }
40- timerStartTime = { now - 5000 }
41- nextCtrlCWillExit = { false }
42- /> ,
43- )
44-
45- expect ( markup ) . toContain ( 'thinking...' )
46-
47- const inactiveMarkup = renderToStaticMarkup (
48- < StatusIndicator
49- clipboardMessage = { null }
50- isActive = { false }
51- isWaitingForResponse = { false }
52- timerStartTime = { null }
53- nextCtrlCWillExit = { false }
54- /> ,
55- )
56-
57- expect ( inactiveMarkup ) . toBe ( '' )
33+ describe ( 'StatusIndicator text states' , ( ) => {
34+ test ( 'shows "thinking..." when waiting for first response (streamStatus = waiting)' , ( ) => {
35+ const now = Date . now ( )
36+ const markup = renderToStaticMarkup (
37+ < StatusIndicator
38+ clipboardMessage = { null }
39+ streamStatus = "waiting"
40+ timerStartTime = { now - 5000 }
41+ nextCtrlCWillExit = { false }
42+ /> ,
43+ )
44+
45+ // ShimmerText renders individual characters in spans
46+ expect ( markup ) . toContain ( 't' )
47+ expect ( markup ) . toContain ( 'h' )
48+ expect ( markup ) . toContain ( 'i' )
49+ expect ( markup ) . toContain ( 'n' )
50+ expect ( markup ) . toContain ( 'k' )
51+ expect ( markup ) . not . toContain ( 'w' ) // not "working"
52+ } )
53+
54+ test ( 'shows "working..." when streaming content (streamStatus = streaming)' , ( ) => {
55+ const now = Date . now ( )
56+ const markup = renderToStaticMarkup (
57+ < StatusIndicator
58+ clipboardMessage = { null }
59+ streamStatus = "streaming"
60+ timerStartTime = { now - 5000 }
61+ nextCtrlCWillExit = { false }
62+ /> ,
63+ )
64+
65+ // ShimmerText renders individual characters in spans
66+ expect ( markup ) . toContain ( 'w' )
67+ expect ( markup ) . toContain ( 'o' )
68+ expect ( markup ) . toContain ( 'r' )
69+ expect ( markup ) . toContain ( 'k' )
70+ } )
71+
72+ test ( 'shows nothing when inactive (streamStatus = idle)' , ( ) => {
73+ const markup = renderToStaticMarkup (
74+ < StatusIndicator
75+ clipboardMessage = { null }
76+ streamStatus = "idle"
77+ timerStartTime = { null }
78+ nextCtrlCWillExit = { false }
79+ /> ,
80+ )
81+
82+ expect ( markup ) . toBe ( '' )
83+ } )
5884 } )
5985
60- test ( 'clipboard message takes priority over timer output' , ( ) => {
61- const now = Date . now ( )
62- const markup = renderToStaticMarkup (
63- < StatusIndicator
64- clipboardMessage = "Copied!"
65- isActive = { true }
66- isWaitingForResponse = { true }
67- timerStartTime = { now - 12000 }
68- nextCtrlCWillExit = { false }
69- /> ,
70- )
71-
72- expect ( markup ) . toContain ( 'Copied!' )
73- expect ( markup ) . not . toContain ( '12s' )
86+ describe ( 'Priority states' , ( ) => {
87+ test ( 'nextCtrlCWillExit takes highest priority' , ( ) => {
88+ const now = Date . now ( )
89+ const markup = renderToStaticMarkup (
90+ < StatusIndicator
91+ clipboardMessage = "Copied!"
92+ streamStatus = "waiting"
93+ timerStartTime = { now - 5000 }
94+ nextCtrlCWillExit = { true }
95+ /> ,
96+ )
97+
98+ expect ( markup ) . toContain ( 'Press Ctrl-C again to exit' )
99+ expect ( markup ) . not . toContain ( 'Copied!' )
100+ expect ( markup ) . not . toContain ( 'thinking' )
101+ expect ( markup ) . not . toContain ( 'working' )
102+ } )
103+
104+ test ( 'clipboard message takes priority over streaming states' , ( ) => {
105+ const now = Date . now ( )
106+ const markup = renderToStaticMarkup (
107+ < StatusIndicator
108+ clipboardMessage = "Copied!"
109+ streamStatus = "waiting"
110+ timerStartTime = { now - 12000 }
111+ nextCtrlCWillExit = { false }
112+ /> ,
113+ )
114+
115+ expect ( markup ) . toContain ( 'Copied!' )
116+ // Shimmer text would contain individual characters, but clipboard message doesn't
117+ } )
118+ } )
119+
120+ describe ( 'StatusElapsedTime' , ( ) => {
121+ test ( 'shows nothing initially (useEffect not triggered in static render)' , ( ) => {
122+ const now = Date . now ( )
123+ const markup = renderToStaticMarkup (
124+ < StatusElapsedTime streamStatus = "streaming" timerStartTime = { now - 5000 } /> ,
125+ )
126+
127+ // Static rendering doesn't trigger useEffect, so elapsed time starts at 0
128+ // In real usage, useEffect updates the elapsed time after mount
129+ expect ( markup ) . toBe ( '' )
130+ } )
131+
132+ test ( 'shows nothing when inactive' , ( ) => {
133+ const now = Date . now ( )
134+ const markup = renderToStaticMarkup (
135+ < StatusElapsedTime streamStatus = "idle" timerStartTime = { now - 5000 } /> ,
136+ )
137+
138+ expect ( markup ) . toBe ( '' )
139+ } )
140+
141+ test ( 'shows nothing when timerStartTime is null' , ( ) => {
142+ const markup = renderToStaticMarkup (
143+ < StatusElapsedTime streamStatus = "streaming" timerStartTime = { null } /> ,
144+ )
145+
146+ expect ( markup ) . toBe ( '' )
147+ } )
74148 } )
75149} )
0 commit comments