gitstack

grindlemire/go-tui code browser

5.6 KB Go 262 lines 2026-03-08 · 66d3508 raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
package tui

import (
	"testing"
)

func TestApp_PollEventWithMockReader(t *testing.T) {
	type tc struct {
		events      []Event
		expectedOk  bool
		expectedKey Key
	}

	tests := map[string]tc{
		"returns queued event": {
			events:      []Event{KeyEvent{Key: KeyEnter}},
			expectedOk:  true,
			expectedKey: KeyEnter,
		},
		"returns false when exhausted": {
			events:     []Event{},
			expectedOk: false,
		},
	}

	for name, tt := range tests {
		t.Run(name, func(t *testing.T) {
			mockReader := NewMockEventReader(tt.events...)

			app := &App{
				reader: mockReader,
				focus:  newFocusManager(),
				buffer: NewBuffer(80, 24),
			}

			event, ok := app.PollEvent(0)

			if ok != tt.expectedOk {
				t.Errorf("PollEvent() ok = %v, want %v", ok, tt.expectedOk)
			}

			if tt.expectedOk {
				ke, isKey := event.(KeyEvent)
				if !isKey {
					t.Fatalf("PollEvent() returned %T, want KeyEvent", event)
				}
				if ke.Key != tt.expectedKey {
					t.Errorf("PollEvent() key = %v, want %v", ke.Key, tt.expectedKey)
				}
			}
		})
	}
}

func TestApp_MultipleEventsFromMockReader(t *testing.T) {
	events := []Event{
		KeyEvent{Key: KeyEnter},
		KeyEvent{Key: KeyTab},
		KeyEvent{Key: KeyEscape},
	}

	mockReader := NewMockEventReader(events...)

	app := &App{
		reader: mockReader,
		focus:  newFocusManager(),
		buffer: NewBuffer(80, 24),
	}

	// Should return events in order
	for i, expected := range events {
		event, ok := app.PollEvent(0)
		if !ok {
			t.Fatalf("PollEvent() %d returned ok=false, want true", i)
		}

		ke, isKey := event.(KeyEvent)
		if !isKey {
			t.Fatalf("PollEvent() %d returned %T, want KeyEvent", i, event)
		}

		expectedKey := expected.(KeyEvent).Key
		if ke.Key != expectedKey {
			t.Errorf("PollEvent() %d key = %v, want %v", i, ke.Key, expectedKey)
		}
	}

	// Should now be exhausted
	_, ok := app.PollEvent(0)
	if ok {
		t.Error("PollEvent() should return false when exhausted")
	}
}

func TestApp_BufferReturnsBuffer(t *testing.T) {
	buffer := NewBuffer(80, 24)
	app := &App{
		buffer: buffer,
		focus:  newFocusManager(),
	}

	if app.Buffer() != buffer {
		t.Error("Buffer() should return the app's buffer")
	}
}

func TestApp_FocusNext(t *testing.T) {
	app := &App{
		focus:  newFocusManager(),
		buffer: NewBuffer(80, 24),
	}

	elem1 := newMockFocusable("elem1", true)
	elem2 := newMockFocusable("elem2", true)
	app.focus.Register(elem1)
	app.focus.Register(elem2)

	// Nothing focused initially (no auto-focus)
	if app.Focused() != nil {
		t.Error("Nothing should be focused initially")
	}

	// FocusNext should move to elem1
	app.FocusNext()

	if app.Focused().(*mockFocusable).id != "elem1" {
		t.Error("After first FocusNext(), focus should be elem1")
	}

	// FocusNext should move to elem2
	app.FocusNext()

	if app.Focused().(*mockFocusable).id != "elem2" {
		t.Error("After second FocusNext(), focus should be elem2")
	}
}

func TestApp_FocusPrev(t *testing.T) {
	app := &App{
		focus:  newFocusManager(),
		buffer: NewBuffer(80, 24),
	}

	elem1 := newMockFocusable("elem1", true)
	elem2 := newMockFocusable("elem2", true)
	app.focus.Register(elem1)
	app.focus.Register(elem2)

	// Nothing focused initially
	if app.Focused() != nil {
		t.Error("Nothing should be focused initially")
	}

	// FocusPrev should wrap to elem2 (last element)
	app.FocusPrev()

	if app.Focused().(*mockFocusable).id != "elem2" {
		t.Error("After FocusPrev(), focus should be elem2")
	}
}

func TestApp_Focused(t *testing.T) {
	app := &App{
		focus:  newFocusManager(),
		buffer: NewBuffer(80, 24),
	}

	// No focused element initially
	if app.Focused() != nil {
		t.Error("Focused() should return nil when no elements registered")
	}

	// Register an element (no auto-focus)
	elem := newMockFocusable("elem", true)
	app.focus.Register(elem)

	// Still nil after registration (no auto-focus)
	if app.Focused() != nil {
		t.Error("Focused() should return nil after registration (no auto-focus)")
	}

	// After explicit focus, should return the element
	app.FocusNext()
	focused := app.Focused()
	if focused == nil {
		t.Error("Focused() should return non-nil after FocusNext()")
	}
	if focused.(*mockFocusable).id != "elem" {
		t.Error("Focused() should return the focused element")
	}
}

func TestApp_SetRoot_AutoRegistration(t *testing.T) {
	type tc struct {
		numFocusable int
	}

	tests := map[string]tc{
		"single focusable": {
			numFocusable: 1,
		},
		"multiple focusables": {
			numFocusable: 3,
		},
	}

	for name, tt := range tests {
		t.Run(name, func(t *testing.T) {
			app := &App{
				focus:  newFocusManager(),
				buffer: NewBuffer(80, 24),
			}

			root := New()
			for i := 0; i < tt.numFocusable; i++ {
				root.AddChild(New(WithFocusable(true)))
			}
			app.SetRoot(root)

			// No auto-focus after registration
			if app.Focused() != nil {
				t.Error("Focused() should be nil after SetRoot (no auto-focus)")
			}

			// After Next(), first focusable should be focused
			app.FocusNext()
			if app.Focused() == nil {
				t.Fatal("Focused() returned nil after FocusNext()")
			}
		})
	}
}

func TestApp_SetRoot_DynamicFocusableRegistration(t *testing.T) {
	app := &App{
		focus:  newFocusManager(),
		buffer: NewBuffer(80, 24),
	}

	root := New()
	app.SetRoot(root)

	// No focusables initially
	if app.Focused() != nil {
		t.Fatal("Focused() should be nil with no focusable children")
	}

	// Dynamically add a focusable child — the callback set by applyRoot should register it
	child := New(WithFocusable(true))
	root.AddChild(child)

	// Registered but not auto-focused
	if app.Focused() != nil {
		t.Error("Focused() should be nil after dynamic registration (no auto-focus)")
	}

	// After explicit focus, should work
	app.FocusNext()
	if app.Focused() == nil {
		t.Fatal("Focused() returned nil after FocusNext()")
	}
}