Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions internal/ui/selectable_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,23 @@ func (sl *SelectableList) HandleListEvent(ev tcell.Event, rect Rect, itemCount i
sl.Selected++
}
return ListEventResult{Result: EventConsumed}
case tcell.KeyPgDn:
sl.Selected += rect.H
if sl.Selected >= itemCount {
sl.Selected = itemCount - 1
}
if sl.Selected < 0 {
sl.Selected = 0
}
sl.EnsureVisible(rect.H)
return ListEventResult{Result: EventConsumed}
case tcell.KeyPgUp:
sl.Selected -= rect.H
if sl.Selected < 0 {
sl.Selected = 0
}
sl.EnsureVisible(rect.H)
return ListEventResult{Result: EventConsumed}
case tcell.KeyEnter:
return ListEventResult{Result: EventConsumed, Action: ListActionActivate}
}
Expand Down
113 changes: 113 additions & 0 deletions internal/ui/selectable_list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,119 @@ func TestSelectableListClampSelected(t *testing.T) {
}
}

func TestSelectableListPageDown(t *testing.T) {
sl := &SelectableList{Selected: 0, ScrollTop: 0}
r := Rect{X: 0, Y: 0, W: 20, H: 10}
items := 50

// PageDown moves by page size (rect.H = 10)
res := sl.HandleListEvent(tcell.NewEventKey(tcell.KeyPgDn, 0, 0), r, items)
if res.Result != EventConsumed {
t.Fatal("PageDown should be consumed")
}
if sl.Selected != 10 {
t.Fatalf("expected Selected 10, got %d", sl.Selected)
}

// PageDown again
sl.HandleListEvent(tcell.NewEventKey(tcell.KeyPgDn, 0, 0), r, items)
if sl.Selected != 20 {
t.Fatalf("expected Selected 20, got %d", sl.Selected)
}

// PageDown near end — clamps to last item
sl.Selected = 45
sl.HandleListEvent(tcell.NewEventKey(tcell.KeyPgDn, 0, 0), r, items)
if sl.Selected != 49 {
t.Fatalf("expected Selected 49 (clamped), got %d", sl.Selected)
}

// PageDown at last item — stays at last
sl.HandleListEvent(tcell.NewEventKey(tcell.KeyPgDn, 0, 0), r, items)
if sl.Selected != 49 {
t.Fatalf("expected Selected 49 (still), got %d", sl.Selected)
}
}

func TestSelectableListPageUp(t *testing.T) {
sl := &SelectableList{Selected: 30, ScrollTop: 25}
r := Rect{X: 0, Y: 0, W: 20, H: 10}
items := 50

// PageUp moves by page size (rect.H = 10)
res := sl.HandleListEvent(tcell.NewEventKey(tcell.KeyPgUp, 0, 0), r, items)
if res.Result != EventConsumed {
t.Fatal("PageUp should be consumed")
}
if sl.Selected != 20 {
t.Fatalf("expected Selected 20, got %d", sl.Selected)
}

// PageUp again
sl.HandleListEvent(tcell.NewEventKey(tcell.KeyPgUp, 0, 0), r, items)
if sl.Selected != 10 {
t.Fatalf("expected Selected 10, got %d", sl.Selected)
}

// PageUp near start — clamps to 0
sl.Selected = 5
sl.HandleListEvent(tcell.NewEventKey(tcell.KeyPgUp, 0, 0), r, items)
if sl.Selected != 0 {
t.Fatalf("expected Selected 0 (clamped), got %d", sl.Selected)
}

// PageUp at start — stays at 0
sl.HandleListEvent(tcell.NewEventKey(tcell.KeyPgUp, 0, 0), r, items)
if sl.Selected != 0 {
t.Fatalf("expected Selected 0 (still), got %d", sl.Selected)
}
}

func TestSelectableListPageDownScrollOffset(t *testing.T) {
sl := &SelectableList{Selected: 0, ScrollTop: 0}
r := Rect{X: 0, Y: 0, W: 20, H: 10}
items := 50

// PageDown should update ScrollTop to keep selection visible
sl.HandleListEvent(tcell.NewEventKey(tcell.KeyPgDn, 0, 0), r, items)
if sl.Selected != 10 {
t.Fatalf("expected Selected 10, got %d", sl.Selected)
}
// Selected=10 should be visible: ScrollTop should be at least 1
if sl.ScrollTop > sl.Selected || sl.Selected >= sl.ScrollTop+r.H {
t.Fatalf("Selected %d not visible with ScrollTop %d and H %d", sl.Selected, sl.ScrollTop, r.H)
}
}

func TestSelectableListPageUpScrollOffset(t *testing.T) {
sl := &SelectableList{Selected: 40, ScrollTop: 35}
r := Rect{X: 0, Y: 0, W: 20, H: 10}
items := 50

// PageUp should update ScrollTop to keep selection visible
sl.HandleListEvent(tcell.NewEventKey(tcell.KeyPgUp, 0, 0), r, items)
if sl.Selected != 30 {
t.Fatalf("expected Selected 30, got %d", sl.Selected)
}
if sl.ScrollTop > sl.Selected || sl.Selected >= sl.ScrollTop+r.H {
t.Fatalf("Selected %d not visible with ScrollTop %d and H %d", sl.Selected, sl.ScrollTop, r.H)
}
}

func TestSelectableListPageDownEmptyList(t *testing.T) {
sl := &SelectableList{Selected: 0}
r := Rect{X: 0, Y: 0, W: 20, H: 10}

// PageDown with 0 items should not panic, Selected stays 0
res := sl.HandleListEvent(tcell.NewEventKey(tcell.KeyPgDn, 0, 0), r, 0)
if res.Result != EventConsumed {
t.Fatal("PageDown should be consumed even for empty list")
}
if sl.Selected != 0 {
t.Fatalf("expected Selected 0 for empty list, got %d", sl.Selected)
}
}

func TestSelectableListUnhandledKey(t *testing.T) {
sl := &SelectableList{}
r := Rect{X: 0, Y: 0, W: 20, H: 10}
Expand Down
Loading