package ui import ( "reflect" "testing" "github.com/gdamore/tcell/v2" "github.com/rivo/tview" ) func TestArrowNavigation(t *testing.T) { f := tview.NewForm(). AddDropDown("A", []string{"x", "y", "z"}, 0, nil). AddCheckbox("B", true, nil). AddButton("Add", nil).AddButton("Cancel", nil) arrowNavigation(f, nil, nil) var focus func(p tview.Primitive) focus = func(p tview.Primitive) { p.Focus(focus) } f.Focus(focus) cap := f.GetInputCapture() send := func(key tcell.Key) { cap(tcell.NewEventKey(key, 0, 0)) } dd := f.GetFormItem(0).(*tview.DropDown) send(tcell.KeyRight) if i, _ := dd.GetCurrentOption(); i != 1 { t.Fatalf("Right: option %d, want 1", i) } send(tcell.KeyLeft) send(tcell.KeyLeft) if i, _ := dd.GetCurrentOption(); i != 2 { t.Fatalf("Left wraps: option %d, want 2", i) } send(tcell.KeyDown) if item, _ := f.GetFocusedItemIndex(); item != 1 { t.Fatalf("Down: item %d, want 1", item) } send(tcell.KeyDown) send(tcell.KeyDown) send(tcell.KeyDown) // clamps at the last button if _, b := f.GetFocusedItemIndex(); b != 1 { t.Fatalf("button %d, want 1", b) } send(tcell.KeyLeft) if _, b := f.GetFocusedItemIndex(); b != 0 { t.Fatalf("Left on buttons: %d, want 0", b) } send(tcell.KeyUp) if item, _ := f.GetFocusedItemIndex(); item != 1 { t.Fatalf("Up from first button: item %d, want 1", item) } // Space on a button is Enter; on a checkbox it stays Space. send(tcell.KeyDown) if got := cap(tcell.NewEventKey(tcell.KeyRune, ' ', 0)); got == nil || got.Key() != tcell.KeyEnter { t.Errorf("Space on a button: %v", got) } send(tcell.KeyUp) if got := cap(tcell.NewEventKey(tcell.KeyRune, ' ', 0)); got == nil || got.Key() != tcell.KeyRune { t.Errorf("Space on a checkbox: %v", got) } } func TestSearchNowFollowsMonitored(t *testing.T) { f := tview.NewForm(). AddDropDown("P", []string{"a"}, 0, nil). AddDropDown("R", []string{"a"}, 0, nil). AddCheckbox("M", true, nil). AddCheckbox("S", true, nil). AddButton("Add", nil) arrowNavigation(f, nil, nil) var focus func(p tview.Primitive) focus = func(p tview.Primitive) { p.Focus(focus) } f.Focus(focus) mon := f.GetFormItem(2).(*tview.Checkbox) search := f.GetFormItem(3).(*tview.Checkbox) blocked := 0 sync := bindSearchNow(search, mon.IsChecked, func() { blocked++ }) mon.SetChangedFunc(func(bool) { sync() }) cap := f.GetInputCapture() key := func(k tcell.Key) { cap(tcell.NewEventKey(k, 0, 0)) } space := func(item tview.FormItem) { item.InputHandler()(tcell.NewEventKey(tcell.KeyRune, ' ', 0), func(tview.Primitive) {}) } focused := func() (n int) { for i := 0; i < f.GetFormItemCount(); i++ { if f.GetFormItem(i).HasFocus() { n++ } } return n } key(tcell.KeyDown) key(tcell.KeyDown) // on "Monitored" space(mon) // unmonitor if search.IsChecked() { t.Error("Search now must be unchecked when not monitored") } key(tcell.KeyUp) if n := focused(); n != 1 { t.Fatalf("%d fields have focus after moving up, want 1", n) } if item, _ := f.GetFocusedItemIndex(); item != 1 { t.Errorf("focus on item %d, want 1", item) } key(tcell.KeyDown) key(tcell.KeyDown) // on "Search now" space(search) if search.IsChecked() || blocked != 1 { t.Errorf("ticking while blocked: checked=%v blocked=%d", search.IsChecked(), blocked) } key(tcell.KeyUp) space(mon) // monitor again if !search.IsChecked() { t.Error("Search now should return to its previous choice") } } func TestBrowseOpenDropDown(t *testing.T) { f := tview.NewForm().AddDropDown("D", []string{"any", "a", "b", "c"}, 0, nil) var seen []int aborted, cancelled := 0, 0 arrowNavigation(f, func() { cancelled++ }, &browseHooks{ browse: func(_ *tview.DropDown, i int) { seen = append(seen, i) }, abort: func() { aborted++ }, }) var focus func(p tview.Primitive) focus = func(p tview.Primitive) { p.Focus(focus) } f.Focus(focus) send := func(k tcell.Key) { f.InputHandler()(tcell.NewEventKey(k, 0, 0), focus) } dd := f.GetFormItem(0).(*tview.DropDown) send(tcell.KeyEnter) // open if !dd.IsOpen() { t.Fatal("dropdown did not open") } send(tcell.KeyDown) send(tcell.KeyDown) send(tcell.KeyUp) send(tcell.KeyUp) send(tcell.KeyUp) // wraps to the last entry if want := []int{1, 2, 1, 0, 3}; !reflect.DeepEqual(seen, want) { t.Fatalf("browse saw %v, want %v", seen, want) } send(tcell.KeyEnter) // select what the highlight is on if i, _ := dd.GetCurrentOption(); i != 3 { t.Errorf("selected option %d, want 3: the mirrored highlight drifted from the real list", i) } seen = nil send(tcell.KeyEnter) send(tcell.KeyDown) // wraps to 0 send(tcell.KeyEscape) if cancelled != 0 { t.Error("Esc on an open list must only close the list") } send(tcell.KeyEscape) // list closed: now Esc cancels the form if cancelled != 1 { t.Errorf("cancelled=%d, want 1", cancelled) } if aborted != 1 || !reflect.DeepEqual(seen, []int{0}) { t.Errorf("aborted=%d seen=%v", aborted, seen) } if i, _ := dd.GetCurrentOption(); i != 3 { t.Errorf("Esc must keep the committed option, got %d", i) } }