Skip to content

Commit e180b9b

Browse files
committed
test(tui): add tests for timer system
- Add tests for `Timer` struct methods: start, pause, resume, reset - Add tests for timer duration tracking and formatting - Add tests for `TimerDisplay` visibility and update behavior - Add tests for `EnsureTimerInitialized` helper function - Add integration tests for typical timer usage scenarios
1 parent e04a52d commit e180b9b

File tree

1 file changed

+383
-0
lines changed

1 file changed

+383
-0
lines changed

internal/tui/timer_test.go

Lines changed: 383 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,383 @@
1+
package tui
2+
3+
import (
4+
"strings"
5+
"testing"
6+
"time"
7+
8+
tea "github.com/charmbracelet/bubbletea"
9+
)
10+
11+
func TestNewTimer(t *testing.T) {
12+
timer := NewTimer()
13+
14+
if timer.running {
15+
t.Error("New timer should not be running")
16+
}
17+
18+
if timer.duration != 0 {
19+
t.Error("New timer should have zero duration")
20+
}
21+
22+
if timer.startTime.IsZero() {
23+
t.Error("New timer should have startTime set")
24+
}
25+
}
26+
27+
func TestTimerStart(t *testing.T) {
28+
timer := NewTimer()
29+
30+
// Start the timer
31+
timer = timer.Start()
32+
33+
if !timer.running {
34+
t.Error("Timer should be running after Start()")
35+
}
36+
37+
if timer.duration != 0 {
38+
t.Error("Timer should have zero duration immediately after start")
39+
}
40+
41+
// Starting an already running timer should not change the start time
42+
originalStartTime := timer.startTime
43+
time.Sleep(10 * time.Millisecond)
44+
timer = timer.Start()
45+
46+
if timer.startTime != originalStartTime {
47+
t.Error("Starting an already running timer should not change start time")
48+
}
49+
}
50+
51+
func TestTimerPause(t *testing.T) {
52+
timer := NewTimer().Start()
53+
54+
// Let it run for a bit
55+
time.Sleep(50 * time.Millisecond)
56+
57+
timer = timer.Pause()
58+
59+
if timer.running {
60+
t.Error("Timer should not be running after Pause()")
61+
}
62+
63+
if timer.duration <= 0 {
64+
t.Error("Timer should have positive duration after running and pausing")
65+
}
66+
67+
// Pausing an already paused timer should not change duration
68+
originalDuration := timer.duration
69+
timer = timer.Pause()
70+
71+
if timer.duration != originalDuration {
72+
t.Error("Stopping an already stopped timer should not change duration")
73+
}
74+
}
75+
76+
func TestTimerResume(t *testing.T) {
77+
timer := NewTimer().Start()
78+
79+
// Let it run, then pause
80+
time.Sleep(50 * time.Millisecond)
81+
timer = timer.Pause()
82+
pausedDuration := timer.duration
83+
84+
// Resume the timer
85+
timer = timer.Resume()
86+
87+
if !timer.running {
88+
t.Error("Timer should be running after Resume()")
89+
}
90+
91+
// Let it run a bit more
92+
time.Sleep(50 * time.Millisecond)
93+
94+
currentDuration := timer.Duration()
95+
if currentDuration <= pausedDuration {
96+
t.Error("Timer duration should increase after resume")
97+
}
98+
99+
// Resuming an already running timer should not affect it
100+
originalStartTime := timer.startTime
101+
timer = timer.Resume()
102+
103+
if timer.startTime != originalStartTime {
104+
t.Error("Resuming an already running timer should not change start time")
105+
}
106+
}
107+
108+
func TestTimerReset(t *testing.T) {
109+
timer := NewTimer().Start()
110+
111+
// Let it run for a bit
112+
time.Sleep(50 * time.Millisecond)
113+
114+
timer = timer.Reset()
115+
116+
if timer.duration != 0 {
117+
t.Error("Timer duration should be zero after Reset()")
118+
}
119+
120+
if timer.startTime.IsZero() {
121+
t.Error("Timer should have a valid start time after Reset()")
122+
}
123+
}
124+
125+
func TestTimerDuration(t *testing.T) {
126+
timer := NewTimer().Start()
127+
128+
// Let it run for a bit
129+
time.Sleep(100 * time.Millisecond)
130+
131+
duration := timer.Duration()
132+
if duration < 50*time.Millisecond {
133+
t.Error("Timer duration should be at least 50ms")
134+
}
135+
136+
// Stop the timer and check duration is preserved
137+
timer = timer.Pause()
138+
stoppedDuration := timer.Duration()
139+
140+
if stoppedDuration != timer.duration {
141+
t.Error("Duration() should return stored duration for stopped timer")
142+
}
143+
144+
// Wait a bit and check duration hasn't changed
145+
time.Sleep(50 * time.Millisecond)
146+
if timer.Duration() != stoppedDuration {
147+
t.Error("Stopped timer duration should not change over time")
148+
}
149+
}
150+
151+
func TestTimerFormatDuration(t *testing.T) {
152+
tests := []struct {
153+
name string
154+
duration time.Duration
155+
expected string
156+
}{
157+
{"zero duration", 0, "00:00"},
158+
{"5 seconds", 5 * time.Second, "00:05"},
159+
{"30 seconds", 30 * time.Second, "00:30"},
160+
{"1 minute", 1 * time.Minute, "01:00"},
161+
{"1 minute 30 seconds", 1*time.Minute + 30*time.Second, "01:30"},
162+
{"10 minutes 45 seconds", 10*time.Minute + 45*time.Second, "10:45"},
163+
{"59 minutes 59 seconds", 59*time.Minute + 59*time.Second, "59:59"},
164+
}
165+
166+
for _, tt := range tests {
167+
t.Run(tt.name, func(t *testing.T) {
168+
timer := Timer{duration: tt.duration, running: false}
169+
result := timer.FormatDuration()
170+
if result != tt.expected {
171+
t.Errorf("FormatDuration() = %v, want %v", result, tt.expected)
172+
}
173+
})
174+
}
175+
}
176+
177+
func TestTimerIsRunning(t *testing.T) {
178+
timer := NewTimer()
179+
180+
if timer.IsRunning() {
181+
t.Error("New timer should not be running")
182+
}
183+
184+
timer = timer.Start()
185+
if !timer.IsRunning() {
186+
t.Error("Started timer should be running")
187+
}
188+
189+
timer = timer.Pause()
190+
if timer.IsRunning() {
191+
t.Error("Paused timer should not be running")
192+
}
193+
194+
timer = timer.Resume()
195+
if !timer.IsRunning() {
196+
t.Error("Resumed timer should be running")
197+
}
198+
199+
}
200+
201+
func TestTimerUpdate(t *testing.T) {
202+
timer := NewTimer().Start()
203+
204+
// Test with TimerTickMsg
205+
updatedTimer, cmd := timer.Update(TimerTickMsg{})
206+
207+
if cmd == nil {
208+
t.Error("Timer.Update() should return a command for TimerTickMsg")
209+
}
210+
211+
if !updatedTimer.running {
212+
t.Error("Running timer should remain running after TimerTickMsg")
213+
}
214+
215+
// Test with other message types
216+
updatedTimer, cmd = timer.Update(tea.KeyMsg{})
217+
218+
if cmd != nil {
219+
t.Error("Timer.Update() should return nil command for non-TimerTickMsg")
220+
}
221+
222+
// Test with stopped timer
223+
stoppedTimer := timer.Pause()
224+
updatedTimer, cmd = stoppedTimer.Update(TimerTickMsg{})
225+
226+
if cmd == nil {
227+
t.Error("Timer.Update() should still return a command even for stopped timer")
228+
}
229+
}
230+
231+
func TestNewTimerDisplay(t *testing.T) {
232+
display := NewTimerDisplay()
233+
234+
if display.visible {
235+
t.Error("New TimerDisplay should not be visible")
236+
}
237+
}
238+
239+
func TestTimerDisplayToggleVisible(t *testing.T) {
240+
display := NewTimerDisplay()
241+
242+
if display.IsVisible() {
243+
t.Error("New TimerDisplay should not be visible")
244+
}
245+
246+
display = display.ToggleVisible()
247+
if !display.IsVisible() {
248+
t.Error("TimerDisplay should be visible after toggle")
249+
}
250+
251+
display = display.ToggleVisible()
252+
if display.IsVisible() {
253+
t.Error("TimerDisplay should not be visible after second toggle")
254+
}
255+
}
256+
257+
func TestTimerDisplayUpdate(t *testing.T) {
258+
display := NewTimerDisplay()
259+
260+
// TimerDisplay.Update should not change state and return nil command
261+
updatedDisplay, cmd := display.Update(tea.KeyMsg{})
262+
263+
if cmd != nil {
264+
t.Error("TimerDisplay.Update() should return nil command")
265+
}
266+
267+
if updatedDisplay.visible != display.visible {
268+
t.Error("TimerDisplay.Update() should not change visibility")
269+
}
270+
}
271+
272+
func TestEnsureTimerInitialized(t *testing.T) {
273+
// Test with nil slide
274+
EnsureTimerInitialized(nil)
275+
// Should not panic
276+
277+
// Test with slide having uninitialized timer
278+
slide := &Slide{}
279+
EnsureTimerInitialized(slide)
280+
281+
if slide.Timer.startTime.IsZero() {
282+
t.Error("EnsureTimerInitialized should set startTime")
283+
}
284+
285+
if slide.Timer.duration != 0 {
286+
t.Error("EnsureTimerInitialized should set duration to 0")
287+
}
288+
289+
if slide.Timer.running {
290+
t.Error("EnsureTimerInitialized should set running to false")
291+
}
292+
293+
// Test with slide having already initialized timer
294+
slide.Timer = slide.Timer.Start()
295+
originalStartTime := slide.Timer.startTime
296+
originalRunning := slide.Timer.running
297+
298+
EnsureTimerInitialized(slide)
299+
300+
// Should not change an already initialized timer that's running
301+
if slide.Timer.startTime != originalStartTime {
302+
t.Error("EnsureTimerInitialized should not change already initialized timer")
303+
}
304+
305+
if slide.Timer.running != originalRunning {
306+
t.Error("EnsureTimerInitialized should not change running state of initialized timer")
307+
}
308+
309+
// Test with slide having a timer that was started and then stopped
310+
slide2 := &Slide{}
311+
slide2.Timer = NewTimer().Start()
312+
slide2.Timer = slide2.Timer.Pause()
313+
originalTimer2 := slide2.Timer
314+
315+
EnsureTimerInitialized(slide2)
316+
317+
// Should not change a timer that has been used (not in zero state)
318+
if slide2.Timer.startTime.IsZero() && originalTimer2.startTime.IsZero() {
319+
t.Error("Timer state comparison failed")
320+
}
321+
}
322+
323+
func TestTimerIntegration(t *testing.T) {
324+
// Test a typical usage scenario: start, pause, resume, stop
325+
timer := NewTimer()
326+
327+
// Start timer
328+
timer = timer.Start()
329+
if !timer.IsRunning() {
330+
t.Error("Timer should be running after start")
331+
}
332+
333+
// Let it run
334+
time.Sleep(50 * time.Millisecond)
335+
336+
// Pause timer
337+
timer = timer.Pause()
338+
if timer.IsRunning() {
339+
t.Error("Timer should not be running after pause")
340+
}
341+
342+
pausedDuration := timer.Duration()
343+
if pausedDuration <= 0 {
344+
t.Error("Timer should have positive duration after pause")
345+
}
346+
347+
// Wait while paused
348+
time.Sleep(50 * time.Millisecond)
349+
350+
// Duration should not change while paused
351+
if timer.Duration() != pausedDuration {
352+
t.Error("Timer duration should not change while paused")
353+
}
354+
355+
// Resume timer
356+
timer = timer.Resume()
357+
if !timer.IsRunning() {
358+
t.Error("Timer should be running after resume")
359+
}
360+
361+
// Let it run more
362+
time.Sleep(50 * time.Millisecond)
363+
364+
// Duration should have increased
365+
if timer.Duration() <= pausedDuration {
366+
t.Error("Timer duration should increase after resume")
367+
}
368+
369+
finalDuration := timer.Duration()
370+
if finalDuration <= pausedDuration {
371+
t.Error("Final duration should be greater than paused duration")
372+
}
373+
374+
// Format should work correctly
375+
formatted := timer.FormatDuration()
376+
if formatted == "" {
377+
t.Error("FormatDuration should return non-empty string")
378+
}
379+
380+
if !strings.Contains(formatted, ":") {
381+
t.Error("Formatted duration should contain colon separator")
382+
}
383+
}

0 commit comments

Comments
 (0)