Config view: one list with section headings, navigated with the arrow keys
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -191,43 +191,84 @@ func TestSettingsViewWithoutConfigFile(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSettingsViewFocusMovesBetweenSections(t *testing.T) {
|
||||
func TestSettingsViewIsOneListNavigatedWithArrows(t *testing.T) {
|
||||
a := newTestApp(t)
|
||||
v := a.settings
|
||||
var focus func(p tview.Primitive)
|
||||
focus = func(p tview.Primitive) { p.Focus(focus) }
|
||||
|
||||
v.focusGroup(3, false) // Interface
|
||||
for _, g := range v.groups {
|
||||
g.Focus(focus)
|
||||
}
|
||||
// Whatever flags were left behind, moving to the actions must leave exactly one section focused.
|
||||
v.focusGroup(4, false)
|
||||
focused := 0
|
||||
for _, g := range v.groups {
|
||||
if g.HasFocus() {
|
||||
focused++
|
||||
}
|
||||
}
|
||||
if focused != 1 || !v.actions.HasFocus() {
|
||||
t.Errorf("%d sections claim the focus, want only the actions", focused)
|
||||
// Whatever focus flags were left behind, moving to the buttons must leave exactly one group focused.
|
||||
v.focusGroup(0, false)
|
||||
v.form.Focus(focus)
|
||||
v.actions.Focus(focus)
|
||||
v.focusGroup(1, false)
|
||||
if v.form.HasFocus() || !v.actions.HasFocus() {
|
||||
t.Error("only the buttons may have the focus")
|
||||
}
|
||||
|
||||
// Down on the last field of a section continues in the next one; Tab cycles.
|
||||
v.focusGroup(0, false)
|
||||
send := func(g *tview.Form, k tcell.Key) { g.GetInputCapture()(tcell.NewEventKey(k, 0, 0)) }
|
||||
g := v.radarr
|
||||
g.SetFocus(g.GetFormItemCount() - 1)
|
||||
send(g, tcell.KeyDown)
|
||||
if !v.sonarr.HasFocus() || v.radarr.HasFocus() {
|
||||
t.Error("Down on the last field must move to the next section")
|
||||
focusedItem := func() int { i, _ := v.form.GetFocusedItemIndex(); return i }
|
||||
labelAt := func(i int) string { return v.form.GetFormItem(i).GetLabel() }
|
||||
|
||||
// The list starts on the first real field, not on the "Radarr" heading.
|
||||
v.focusGroup(0, false)
|
||||
if i := focusedItem(); labelAt(i) != "Address" || i != 1 {
|
||||
t.Fatalf("starts on item %d (%q)", i, labelAt(i))
|
||||
}
|
||||
send(v.sonarr, tcell.KeyBacktab)
|
||||
if !v.radarr.HasFocus() || v.sonarr.HasFocus() {
|
||||
t.Error("Shift+Tab must move back a section")
|
||||
// ↓ walks through the fields of a section and over the next heading into the next section.
|
||||
for _, want := range []string{"API key", "Default quality profile", "Default root folder", "Address"} {
|
||||
send(v.form, tcell.KeyDown)
|
||||
if got := labelAt(focusedItem()); got != want {
|
||||
t.Fatalf("after ↓ on %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
send(v.radarr, tcell.KeyUp) // first section, first field: nothing before it
|
||||
if !v.radarr.HasFocus() {
|
||||
t.Error("Up on the first field of the first section must stay put")
|
||||
if focusedItem() != 6 { // 5 is the Sonarr heading
|
||||
t.Errorf("↓ must skip the section heading, now on item %d", focusedItem())
|
||||
}
|
||||
send(v.form, tcell.KeyUp)
|
||||
send(v.form, tcell.KeyUp)
|
||||
if focusedItem() != 3 && focusedItem() != 4 {
|
||||
t.Errorf("↑ must also skip the heading, now on item %d", focusedItem())
|
||||
}
|
||||
|
||||
// ↓ on the very last field moves to the buttons; ↑ on a button goes back to the last field.
|
||||
v.focusGroup(0, true)
|
||||
if got := labelAt(focusedItem()); got != "Show API keys" {
|
||||
t.Fatalf("last field = %q", got)
|
||||
}
|
||||
send(v.form, tcell.KeyDown)
|
||||
if !v.actions.HasFocus() || v.form.HasFocus() {
|
||||
t.Error("↓ past the last field must reach the buttons")
|
||||
}
|
||||
send(v.actions, tcell.KeyUp)
|
||||
if !v.form.HasFocus() || v.actions.HasFocus() || labelAt(focusedItem()) != "Show API keys" {
|
||||
t.Error("↑ on the buttons must go back to the last field")
|
||||
}
|
||||
// ↑ on the first field has nowhere to go.
|
||||
v.focusGroup(0, false)
|
||||
send(v.form, tcell.KeyUp)
|
||||
if focusedItem() != 1 {
|
||||
t.Errorf("↑ on the first field moved to item %d", focusedItem())
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormHeadingNeverTakesFocus(t *testing.T) {
|
||||
f := tview.NewForm()
|
||||
f.AddFormItem(newFormHeading("Section"))
|
||||
f.AddInputField("One", "", 10, nil, nil)
|
||||
f.AddFormItem(newFormHeading("Other"))
|
||||
f.AddInputField("Two", "", 10, nil, nil)
|
||||
if firstFocusable(f) != 1 {
|
||||
t.Errorf("first focusable = %d", firstFocusable(f))
|
||||
}
|
||||
if neighbour(f, 1, 1) != 3 || neighbour(f, 3, -1) != 1 || neighbour(f, 3, 1) != 3 || neighbour(f, 1, -1) != 1 {
|
||||
t.Error("neighbour must skip headings and stay at the ends")
|
||||
}
|
||||
// tview's own Tab handling passes over a heading as well.
|
||||
var focus func(p tview.Primitive)
|
||||
focus = func(p tview.Primitive) { p.Focus(focus) }
|
||||
f.Focus(focus)
|
||||
if i, _ := f.GetFocusedItemIndex(); i == 0 {
|
||||
t.Error("a heading got the focus")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user