1package input
2
3import (
4 "context"
5 "sync"
6 "testing"
7 "time"
8)
9
10func TestModalState_InitialState(t *testing.T) {
11 ctx := context.Background()
12 ms := NewModalState(ctx)
13
14 if ms.Mode() != ModeNormal {
15 t.Errorf("initial mode = %v, want ModeNormal", ms.Mode())
16 }
17}
18
19func TestModalState_EnterTabMode(t *testing.T) {
20 ctx := context.Background()
21 ms := NewModalState(ctx)
22
23 ms.EnterTabMode(ctx, 0)
24
25 if ms.Mode() != ModeTab {
26 t.Errorf("mode after EnterTabMode = %v, want ModeTab", ms.Mode())
27 }
28}
29
30func TestModalState_EnterPaneMode(t *testing.T) {
31 ctx := context.Background()
32 ms := NewModalState(ctx)
33
34 ms.EnterPaneMode(ctx, 0)
35
36 if ms.Mode() != ModePane {
37 t.Errorf("mode after EnterPaneMode = %v, want ModePane", ms.Mode())
38 }
39}
40
41func TestModalState_ExitMode(t *testing.T) {
42 ctx := context.Background()
43 ms := NewModalState(ctx)
44
45 // Enter tab mode
46 ms.EnterTabMode(ctx, 0)
47 if ms.Mode() != ModeTab {
48 t.Fatalf("mode should be ModeTab")
49 }
50
51 // Exit mode
52 ms.ExitMode(ctx)
53 if ms.Mode() != ModeNormal {
54 t.Errorf("mode after ExitMode = %v, want ModeNormal", ms.Mode())
55 }
56}
57
58func TestModalState_ExitMode_FromNormal(t *testing.T) {
59 ctx := context.Background()
60 ms := NewModalState(ctx)
61
62 // ExitMode from normal should be no-op
63 ms.ExitMode(ctx)
64
65 if ms.Mode() != ModeNormal {
66 t.Errorf("mode = %v, want ModeNormal", ms.Mode())
67 }
68}
69
70func TestModalState_SwitchModes(t *testing.T) {
71 ctx := context.Background()
72 ms := NewModalState(ctx)
73
74 // Enter tab mode
75 ms.EnterTabMode(ctx, 0)
76 if ms.Mode() != ModeTab {
77 t.Fatalf("expected ModeTab, got %v", ms.Mode())
78 }
79
80 // Switch to pane mode directly
81 ms.EnterPaneMode(ctx, 0)
82 if ms.Mode() != ModePane {
83 t.Errorf("mode after switch to pane = %v, want ModePane", ms.Mode())
84 }
85
86 // Switch back to tab mode
87 ms.EnterTabMode(ctx, 0)
88 if ms.Mode() != ModeTab {
89 t.Errorf("mode after switch to tab = %v, want ModeTab", ms.Mode())
90 }
91}
92
93func TestModalState_ModeChangeCallback(t *testing.T) {
94 ctx := context.Background()
95 ms := NewModalState(ctx)
96
97 var callCount int
98 var lastFrom, lastTo Mode
99 var mu sync.Mutex
100
101 ms.SetOnModeChange(func(from, to Mode) {
102 mu.Lock()
103 defer mu.Unlock()
104 callCount++
105 lastFrom = from
106 lastTo = to
107 })
108
109 // Enter tab mode
110 ms.EnterTabMode(ctx, 0)
111
112 mu.Lock()
113 if callCount != 1 {
114 t.Errorf("callback count = %d, want 1", callCount)
115 }
116 if lastFrom != ModeNormal || lastTo != ModeTab {
117 t.Errorf("callback args = (%v, %v), want (ModeNormal, ModeTab)", lastFrom, lastTo)
118 }
119 mu.Unlock()
120
121 // Exit mode
122 ms.ExitMode(ctx)
123
124 mu.Lock()
125 if callCount != 2 {
126 t.Errorf("callback count = %d, want 2", callCount)
127 }
128 if lastFrom != ModeTab || lastTo != ModeNormal {
129 t.Errorf("callback args = (%v, %v), want (ModeTab, ModeNormal)", lastFrom, lastTo)
130 }
131 mu.Unlock()
132}
133
134func TestModalState_NoCallbackWhenAlreadyInMode(t *testing.T) {
135 ctx := context.Background()
136 ms := NewModalState(ctx)
137
138 var callCount int
139 ms.SetOnModeChange(func(from, to Mode) {
140 callCount++
141 })
142
143 // Enter tab mode
144 ms.EnterTabMode(ctx, 0)
145 if callCount != 1 {
146 t.Fatalf("expected 1 callback, got %d", callCount)
147 }
148
149 // Enter tab mode again (should not trigger callback)
150 ms.EnterTabMode(ctx, 0)
151 if callCount != 1 {
152 t.Errorf("callback count = %d, want 1 (no new call)", callCount)
153 }
154}
155
156func TestModalState_Timeout(t *testing.T) {
157 ctx := context.Background()
158 ms := NewModalState(ctx)
159
160 // Enter with short timeout
161 ms.EnterTabMode(ctx, 50*time.Millisecond)
162
163 if ms.Mode() != ModeTab {
164 t.Fatalf("mode should be ModeTab")
165 }
166
167 // Wait for timeout
168 time.Sleep(100 * time.Millisecond)
169
170 if ms.Mode() != ModeNormal {
171 t.Errorf("mode after timeout = %v, want ModeNormal", ms.Mode())
172 }
173}
174
175func TestModalState_TimeoutReset(t *testing.T) {
176 ctx := context.Background()
177 ms := NewModalState(ctx)
178
179 // Enter with timeout
180 ms.EnterTabMode(ctx, 100*time.Millisecond)
181
182 // Wait a bit, then reset
183 time.Sleep(50 * time.Millisecond)
184 ms.ResetTimeout(ctx)
185
186 // Wait a bit more (original timeout would have expired)
187 time.Sleep(80 * time.Millisecond)
188
189 // Should still be in tab mode because timeout was reset
190 if ms.Mode() != ModeTab {
191 t.Errorf("mode = %v, want ModeTab (timeout should have been reset)", ms.Mode())
192 }
193
194 // Wait for the reset timeout to expire
195 time.Sleep(50 * time.Millisecond)
196
197 if ms.Mode() != ModeNormal {
198 t.Errorf("mode after reset timeout = %v, want ModeNormal", ms.Mode())
199 }
200}
201
202func TestModalState_ExitCancelsTimeout(t *testing.T) {
203 ctx := context.Background()
204 ms := NewModalState(ctx)
205
206 var callCount int
207 ms.SetOnModeChange(func(from, to Mode) {
208 callCount++
209 })
210
211 // Enter with timeout
212 ms.EnterTabMode(ctx, 100*time.Millisecond)
213 if callCount != 1 {
214 t.Fatalf("expected 1 callback")
215 }
216
217 // Exit immediately
218 ms.ExitMode(ctx)
219 if callCount != 2 {
220 t.Fatalf("expected 2 callbacks")
221 }
222
223 // Wait past the timeout
224 time.Sleep(150 * time.Millisecond)
225
226 // Callback should not have been called again
227 if callCount != 2 {
228 t.Errorf("callback count = %d, want 2 (timeout should have been canceled)", callCount)
229 }
230}
231
232func TestModalState_ConcurrentAccess(t *testing.T) {
233 ctx := context.Background()
234 ms := NewModalState(ctx)
235
236 var wg sync.WaitGroup
237 iterations := 100
238
239 // Multiple goroutines entering/exiting modes
240 for i := 0; i < 10; i++ {
241 wg.Add(1)
242 go func() {
243 defer wg.Done()
244 for j := 0; j < iterations; j++ {
245 ms.EnterTabMode(ctx, 0)
246 _ = ms.Mode()
247 ms.ExitMode(ctx)
248 ms.EnterPaneMode(ctx, 0)
249 _ = ms.Mode()
250 ms.ExitMode(ctx)
251 }
252 }()
253 }
254
255 wg.Wait()
256
257 // Should end up in normal mode
258 if ms.Mode() != ModeNormal {
259 t.Errorf("final mode = %v, want ModeNormal", ms.Mode())
260 }
261}
262
263func TestMode_String(t *testing.T) {
264 tests := []struct {
265 mode Mode
266 want string
267 }{
268 {ModeNormal, "normal"},
269 {ModeTab, "tab"},
270 {ModePane, "pane"},
271 {Mode(99), "unknown"},
272 }
273
274 for _, tt := range tests {
275 t.Run(tt.want, func(t *testing.T) {
276 if got := tt.mode.String(); got != tt.want {
277 t.Errorf("Mode.String() = %v, want %v", got, tt.want)
278 }
279 })
280 }
281}