@@ -12,9 +12,11 @@ import (
12
12
)
13
13
14
14
var (
15
- filterKeyMsg = tea.KeyPressMsg {Code : '/' , Text : "/" }
16
- enterKeyMsg = tea.KeyPressMsg {Code : tea .KeyEnter , Text : "enter" }
17
- clearKeyMsg = tea.KeyPressMsg {Code : tea .KeyEscape , Text : "esc" }
15
+ focusFilterKeyMsg = tea.KeyPressMsg {Code : '/' , Text : "/" }
16
+ focusRegexFilterKeyMsg = tea.KeyPressMsg {Code : 'r' , Text : "r" }
17
+ enterKeyMsg = tea.KeyPressMsg {Code : tea .KeyEnter , Text : "enter" }
18
+ clearKeyMsg = tea.KeyPressMsg {Code : tea .KeyEscape , Text : "esc" }
19
+ wrapKeyMsg = tea.KeyPressMsg {Code : 'w' , Text : "w" }
18
20
)
19
21
20
22
func makeKeyPressMsg (key rune ) tea.Msg {
@@ -81,7 +83,7 @@ func newFilterableViewport() FilterableViewport[TestItem] {
81
83
)
82
84
}
83
85
84
- func getLines (fv FilterableViewport [TestItem ]) []string {
86
+ func getTestLines (fv FilterableViewport [TestItem ]) []string {
85
87
var lines []string
86
88
for _ , line := range strings .Split (fv .View (), "\n " ) {
87
89
if strings .TrimSpace (line ) != "" {
@@ -91,6 +93,18 @@ func getLines(fv FilterableViewport[TestItem]) []string {
91
93
return lines
92
94
}
93
95
96
+ func applyTestFilter (fv FilterableViewport [TestItem ], km tea.KeyPressMsg , s string ) FilterableViewport [TestItem ] {
97
+ fv , _ = fv .Update (km )
98
+ if ! fv .Filter .Focused () {
99
+ panic ("filter should be focused" )
100
+ }
101
+ for _ , r := range s {
102
+ fv , _ = fv .Update (makeKeyPressMsg (r ))
103
+ }
104
+ fv , _ = fv .Update (enterKeyMsg )
105
+ return fv
106
+ }
107
+
94
108
func TestNewFilterableViewport (t * testing.T ) {
95
109
fv := newFilterableViewport ()
96
110
fv .SetAllRows ([]TestItem {
@@ -117,6 +131,9 @@ func TestNewFilterableViewport(t *testing.T) {
117
131
if fv .Filter .Focused () {
118
132
t .Error ("filter should not be focused" )
119
133
}
134
+ if fv .focused {
135
+ t .Error ("viewport should start unfocused" )
136
+ }
120
137
}
121
138
122
139
func TestFilterableViewport_FilterNoContext (t * testing.T ) {
@@ -127,18 +144,10 @@ func TestFilterableViewport_FilterNoContext(t *testing.T) {
127
144
{content : "another item" },
128
145
})
129
146
130
- // apply filter
131
- fv , _ = fv .Update (filterKeyMsg )
132
- if ! fv .Filter .Focused () {
133
- t .Errorf ("filter should be focused after %s key" , filterKeyMsg .String ())
134
- }
135
- for _ , r := range "one" {
136
- fv , _ = fv .Update (makeKeyPressMsg (r ))
137
- }
138
- fv , _ = fv .Update (enterKeyMsg )
147
+ fv = applyTestFilter (fv , focusFilterKeyMsg , "one" )
139
148
140
149
// check filter correctly identifies lines in view
141
- lines := getLines (fv )
150
+ lines := getTestLines (fv )
142
151
if lines [0 ] != "Test Header \x1b [48;2;225;225;225m \x1b [m\x1b [38;2;0;0;0;48;2;225;225;225m\x1b [38;2;0;0;0;48;2;225;225;225mfilter: \x1b [m\x1b [38;2;0;0;0;48;2;225;225;225mone\x1b [m\x1b [38;2;0;0;0;48;2;225;225;225m \x1b [m\x1b [38;2;0;0;0;48;2;225;225;225m(matches only) \x1b [m\x1b [m" {
143
152
t .Errorf ("unexpected header with filter\n %q" , fv .View ())
144
153
}
@@ -157,7 +166,7 @@ func TestFilterableViewport_FilterNoContext(t *testing.T) {
157
166
{content : "another item" },
158
167
}
159
168
fv .SetAllRows (newItems )
160
- lines = getLines (fv )
169
+ lines = getTestLines (fv )
161
170
if len (lines ) != 3 { // 1 for header
162
171
t .Errorf ("expected 2 visible item, got %d" , len (lines )- 1 )
163
172
}
@@ -167,7 +176,7 @@ func TestFilterableViewport_FilterNoContext(t *testing.T) {
167
176
168
177
// clear filter
169
178
fv , _ = fv .Update (clearKeyMsg )
170
- lines = getLines (fv )
179
+ lines = getTestLines (fv )
171
180
if lines [0 ] != "Test Header '/' or 'r' to filter" {
172
181
t .Errorf ("unexpected header with filter\n %q" , fv .View ())
173
182
}
@@ -185,14 +194,7 @@ func TestFilterableViewport_FilterShowContext(t *testing.T) {
185
194
})
186
195
187
196
// apply filter
188
- fv , _ = fv .Update (filterKeyMsg )
189
- if ! fv .Filter .Focused () {
190
- t .Errorf ("filter should be focused after %s key" , filterKeyMsg .String ())
191
- }
192
- for _ , r := range "one" {
193
- fv , _ = fv .Update (makeKeyPressMsg (r ))
194
- }
195
- fv , _ = fv .Update (enterKeyMsg )
197
+ fv = applyTestFilter (fv , focusFilterKeyMsg , "one" )
196
198
197
199
// check show context
198
200
if fv .Filter .ShowContext {
@@ -203,7 +205,7 @@ func TestFilterableViewport_FilterShowContext(t *testing.T) {
203
205
t .Error ("contextual filtering should be enabled" )
204
206
}
205
207
206
- lines := getLines (fv )
208
+ lines := getTestLines (fv )
207
209
if lines [0 ] != "Test Header \x1b [48;2;225;225;225m \x1b [m\x1b [38;2;0;0;0;48;2;225;225;225m\x1b [38;2;0;0;0;48;2;225;225;225mfilter: \x1b [m\x1b [38;2;0;0;0;48;2;225;225;225mone\x1b [m\x1b [38;2;0;0;0;48;2;225;225;225m \x1b [m\x1b [38;2;0;0;0;48;2;225;225;225m(1/1, n/N to cycle) \x1b [m\x1b [m" {
208
210
t .Errorf ("unexpected header with show context filter\n %q" , fv .View ())
209
211
}
@@ -219,7 +221,7 @@ func TestFilterableViewport_FilterShowContext(t *testing.T) {
219
221
{content : "item two" },
220
222
{content : "another item" },
221
223
})
222
- lines = getLines (fv )
224
+ lines = getTestLines (fv )
223
225
if lines [0 ] != "Test Header \x1b [48;2;225;225;225m \x1b [m\x1b [38;2;0;0;0;48;2;225;225;225m\x1b [38;2;0;0;0;48;2;225;225;225mfilter: \x1b [m\x1b [38;2;0;0;0;48;2;225;225;225mone\x1b [m\x1b [38;2;0;0;0;48;2;225;225;225m \x1b [m\x1b [38;2;0;0;0;48;2;225;225;225m(1/2, n/N to cycle) \x1b [m\x1b [m" {
224
226
t .Errorf ("unexpected header with show context filter\n %q" , fv .View ())
225
227
}
@@ -232,14 +234,14 @@ func TestFilterableViewport_FilterShowContext(t *testing.T) {
232
234
if fv .Filter .ShowContext {
233
235
t .Error ("contextual filtering should be disabled" )
234
236
}
235
- lines = getLines (fv )
237
+ lines = getTestLines (fv )
236
238
if lines [0 ] != "Test Header \x1b [48;2;225;225;225m \x1b [m\x1b [38;2;0;0;0;48;2;225;225;225m\x1b [38;2;0;0;0;48;2;225;225;225mfilter: \x1b [m\x1b [38;2;0;0;0;48;2;225;225;225mone\x1b [m\x1b [38;2;0;0;0;48;2;225;225;225m \x1b [m\x1b [38;2;0;0;0;48;2;225;225;225m(matches only) \x1b [m\x1b [m" {
237
239
t .Errorf ("unexpected header with filter\n %q" , fv .View ())
238
240
}
239
241
240
242
// clear filter
241
243
fv , _ = fv .Update (clearKeyMsg )
242
- lines = getLines (fv )
244
+ lines = getTestLines (fv )
243
245
if lines [0 ] != "Test Header '/' or 'r' to filter" {
244
246
t .Errorf ("unexpected header with filter\n %q" , fv .View ())
245
247
}
@@ -248,119 +250,88 @@ func TestFilterableViewport_FilterShowContext(t *testing.T) {
248
250
}
249
251
}
250
252
251
- // TODO focus filterable viewport
252
-
253
- // TODO regex filter
254
-
255
- // TODO clear filter
256
-
257
- // TODO filter filterable viewport
258
-
259
- //func TestFilterableViewport_RegexFilter(t *testing.T) {
260
- // fv := newFilterableViewport()
261
- //
262
- // // Activate regex filter
263
- // msg := tea.KeyMsg{Type: tea.KeyCtrl, Runes: []rune("/")}
264
- // fv, _ = fv.Update(msg)
265
- // if !fv.Filter.IsRegex() {
266
- // t.Error("filter should be in regex mode")
267
- // }
268
- //
269
- // // Type regex pattern
270
- // msg = tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("^item")}
271
- // fv, _ = fv.Update(msg)
272
- //
273
- // // Apply filter
274
- // msg = tea.KeyMsg{Type: tea.KeyEnter}
275
- // fv, _ = fv.Update(msg)
276
- //
277
- // // Verify only items starting with "item" are visible
278
- // content := fv.viewport.GetContent()
279
- // if len(content) != 2 {
280
- // t.Errorf("expected 2 visible items, got %d", len(content))
281
- // }
282
- // if content[0].String() != "item one" {
283
- // t.Errorf("expected 'item one', got '%s'", content[0].String())
284
- // }
285
- // if content[1].String() != "item two" {
286
- // t.Errorf("expected 'item two', got '%s'", content[1].String())
287
- // }
288
- //}
289
- //
290
- //func TestFilterableViewport_ClearFilter(t *testing.T) {
291
- // fv := newFilterableViewport()
292
- //
293
- // // Set up a filter first
294
- // msg := tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("/")}
295
- // fv, _ = fv.Update(msg)
296
- // msg = tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("one")}
297
- // fv, _ = fv.Update(msg)
298
- // msg = tea.KeyMsg{Type: tea.KeyEnter}
299
- // fv, _ = fv.Update(msg)
300
- //
301
- // // Clear the filter
302
- // msg = tea.KeyMsg{Type: tea.KeyEsc}
303
- // fv, _ = fv.Update(msg)
304
- //
305
- // // Verify all items are visible again
306
- // if len(fv.viewport.GetContent()) != 3 {
307
- // t.Errorf("expected 3 visible items after clear, got %d", len(fv.viewport.GetContent()))
308
- // }
309
- // if fv.Filter.Value() != "" {
310
- // t.Errorf("expected empty filter value, got '%s'", fv.Filter.Value())
311
- // }
312
- //}
313
- //
314
- //func TestFilterableViewport_ToggleWrap(t *testing.T) {
315
- // fv := newFilterableViewport()
316
- //
317
- // initialWrap := fv.viewport.GetWrapText()
318
- //
319
- // // Toggle wrap
320
- // msg := tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("w")}
321
- // fv, _ = fv.Update(msg)
322
- //
323
- // if fv.viewport.GetWrapText() == initialWrap {
324
- // t.Error("wrap text should have toggled")
325
- // }
326
- //}
327
- //
328
- //func TestFilterableViewport_Focus(t *testing.T) {
329
- // fv := newFilterableViewport()
330
- //
331
- // // Test unfocused state
332
- // if fv.focused {
333
- // t.Error("viewport should start unfocused")
334
- // }
335
- //
336
- // // Focus the viewport
337
- // fv.SetFocus(true)
338
- // if !fv.focused {
339
- // t.Error("viewport should be focused after SetFocus(true)")
340
- // }
341
- //
342
- // // Verify styles are updated
343
- // if fv.viewport.SelectedItemStyle != fv.styles.Inverse {
344
- // t.Error("focused viewport should have inverse selection style")
345
- // }
346
- //}
347
- //
348
- //func TestFilterableViewport_Selection(t *testing.T) {
349
- // fv := newFilterableViewport()
350
- //
351
- // // Test initial selection
352
- // selection := fv.GetSelection()
353
- // if selection == nil {
354
- // t.Fatal("initial selection should not be nil")
355
- // }
356
- // if selection.Render() != "item one" {
357
- // t.Errorf("expected initial selection 'item one', got '%s'", selection.Render())
358
- // }
359
- //
360
- // // Change selection
361
- // fv.SetSelectedContentIdx(1)
362
- // selection = fv.GetSelection()
363
- // if selection.Render() != "item two" {
364
- // t.Errorf("expected selection 'item two', got '%s'", selection.Render())
365
- // }
366
- //}
253
+ func TestFilterableViewport_Focus (t * testing.T ) {
254
+ fv := newFilterableViewport ()
255
+ fv .SetAllRows ([]TestItem {
256
+ {content : "item one" },
257
+ })
258
+
259
+ fv .SetFocus (true )
260
+ if ! fv .focused {
261
+ t .Error ("viewport should be focused after SetFocus(true)" )
262
+ }
263
+
264
+ // header should be styled when focused
265
+ lines := getTestLines (fv )
266
+ if lines [0 ] != "\x1b [38;2;0;0;0;46mTest Header\x1b [m '/' or 'r' to filter" {
267
+ t .Errorf ("unexpected header\n %q" , fv .View ())
268
+ }
269
+
270
+ // selection should be styled when focused
271
+ if lines [1 ] != "\x1b [38;2;0;0;0;48;2;255;255;255mitem one\x1b [m" {
272
+ t .Errorf ("unexpected selection\n %q" , fv .View ())
273
+ }
274
+
275
+ // apply filter
276
+ fv = applyTestFilter (fv , focusFilterKeyMsg , "one" )
277
+
278
+ // selection should have styled filtered line
279
+ lines = getTestLines (fv )
280
+ if lines [1 ] != "\x1b [38;2;0;0;0;48;2;255;255;255mitem \x1b [m\x1b [38;2;255;255;255;48;2;0;0;0mone\x1b [m" {
281
+ t .Errorf ("unexpected selection\n %q" , fv .View ())
282
+ }
283
+ }
284
+
285
+ func TestFilterableViewport_Clear (t * testing.T ) {
286
+ fv := newFilterableViewport ()
287
+ fv .SetAllRows ([]TestItem {
288
+ {content : "item one" },
289
+ {content : "item two" },
290
+ })
291
+
292
+ // apply filter, focus filter, and clear
293
+ fv = applyTestFilter (fv , focusFilterKeyMsg , "one" )
294
+ fv , _ = fv .Update (focusFilterKeyMsg )
295
+ fv , _ = fv .Update (clearKeyMsg )
296
+ lines := getTestLines (fv )
297
+ if lines [0 ] != "Test Header '/' or 'r' to filter" {
298
+ t .Errorf ("unexpected header\n %q" , fv .View ())
299
+ }
300
+
301
+ // apply filter and clear
302
+ fv = applyTestFilter (fv , focusFilterKeyMsg , "one" )
303
+ fv , _ = fv .Update (clearKeyMsg )
304
+ lines = getTestLines (fv )
305
+ if lines [0 ] != "Test Header '/' or 'r' to filter" {
306
+ t .Errorf ("unexpected header\n %q" , fv .View ())
307
+ }
308
+ }
309
+
310
+ func TestFilterableViewport_ToggleWrap (t * testing.T ) {
311
+ fv := newFilterableViewport ()
312
+
313
+ initialWrap := fv .viewport .GetWrapText ()
314
+
315
+ fv , _ = fv .Update (wrapKeyMsg )
316
+
317
+ if fv .viewport .GetWrapText () == initialWrap {
318
+ t .Error ("wrap text should have toggled" )
319
+ }
320
+ }
321
+
322
+ func TestFilterableViewport_FilterRegex (t * testing.T ) {
323
+ fv := newFilterableViewport ()
324
+ fv .SetAllRows ([]TestItem {
325
+ {content : "item one" },
326
+ {content : "item two" },
327
+ })
328
+
329
+ fv = applyTestFilter (fv , focusRegexFilterKeyMsg , "i.*m" )
330
+ lines := getTestLines (fv )
331
+ if lines [0 ] != "Test Header \x1b [48;2;225;225;225m \x1b [m\x1b [38;2;0;0;0;48;2;225;225;225m\x1b [38;2;0;0;0;48;2;225;225;225mregex filter: \x1b [m\x1b [38;2;0;0;0;48;2;225;225;225mi.*m\x1b [m\x1b [38;2;0;0;0;48;2;225;225;225m \x1b [m\x1b [38;2;0;0;0;48;2;225;225;225m(matches only) \x1b [m\x1b [m" {
332
+ t .Errorf ("unexpected header with regex filter\n %q" , fv .View ())
333
+ }
334
+ if len (lines ) != 3 { // 1 for header
335
+ t .Errorf ("expected 2 visible item, got %d" , len (lines )- 1 )
336
+ }
337
+ }
0 commit comments