package ui import ( "strings" "testing" "github.com/gdamore/tcell/v2" ) func TestALabelIsSkippedByTheFocusRing(t *testing.T) { if NewLabel("Name:").CanFocus() { t.Error("CanFocus() = true for a label") } } func TestLabelDrawsItsText(t *testing.T) { screen, root := newTestScreen(t, 20, 2) l := NewLabel("File name:") l.SetBounds(Rect{X: 1, Y: 0, W: 15, H: 1}) l.Draw(root, testTheme(t)) screen.Show() if got := screenLines(t, screen)[0]; !strings.Contains(got, "File name:") { t.Errorf("row 0 = %q", got) } } func TestButtonPressRunsItsAction(t *testing.T) { pressed := 0 b := NewButton("~O~K", func() { pressed++ }) b.Press() if pressed != 1 { t.Errorf("the action ran %d times, want 1", pressed) } } func TestAButtonWithNoActionDoesNotPanic(t *testing.T) { NewButton("~O~K", nil).Press() } func TestButtonRespondsToEnterAndSpaceWhenFocused(t *testing.T) { pressed := 0 b := NewButton("~O~K", func() { pressed++ }) if b.HandleKey(keyEvent(tcell.KeyEnter, 0, tcell.ModNone)) { t.Error("an unfocused button reacted to Enter") } b.SetFocused(true) b.HandleKey(keyEvent(tcell.KeyEnter, 0, tcell.ModNone)) b.HandleKey(runeEvent(' ')) if pressed != 2 { t.Errorf("the action ran %d times, want 2", pressed) } if b.HandleKey(runeEvent('z')) { t.Error("the button claimed a key it does nothing with") } } func TestClickingAButtonPressesIt(t *testing.T) { pressed := 0 b := NewButton("~O~K", func() { pressed++ }) b.SetBounds(Rect{X: 5, Y: 3, W: 8, H: 1}) if !b.HandleMouse(clickEvent(6, 3)) { t.Fatal("the click was not handled") } if pressed != 1 { t.Errorf("the action ran %d times, want 1", pressed) } if b.HandleMouse(clickEvent(0, 0)) { t.Error("the button claimed a click that missed it") } } func TestButtonWidthAllowsForItsBrackets(t *testing.T) { if got := ButtonWidth("~O~K"); got != 6 { t.Errorf("ButtonWidth(\"~O~K\") = %d, want 6", got) } } func TestButtonDrawsItsLabelAndAMarkerWhenFocused(t *testing.T) { screen, root := newTestScreen(t, 20, 2) b := NewButton("~O~K", nil) b.SetBounds(Rect{X: 0, Y: 0, W: 8, H: 1}) b.Draw(root, testTheme(t)) screen.Show() if got := screenLines(t, screen)[0]; strings.Contains(got, "►") { t.Errorf("an unfocused, non-default button shows a marker: %q", got) } b.SetFocused(true) b.Draw(root, testTheme(t)) screen.Show() line := screenLines(t, screen)[0] if !strings.Contains(line, "►") || !strings.Contains(line, "OK") { t.Errorf("row 0 = %q, want a marker and the label", line) } } func TestInputLineHoldsAndEditsText(t *testing.T) { i := NewInputLine("main.go") i.SetFocused(true) if got := i.Text(); got != "main.go" { t.Fatalf("Text() = %q", got) } i.HandleKey(keyEvent(tcell.KeyBackspace2, 0, tcell.ModNone)) if got := i.Text(); got != "main.g" { t.Errorf("Text() = %q after a backspace, want %q", got, "main.g") } i.HandleKey(runeEvent('X')) if got := i.Text(); got != "main.gX" { t.Errorf("Text() = %q after typing, want %q", got, "main.gX") } } func TestInputLineCursorMovementAndDelete(t *testing.T) { i := NewInputLine("abc") i.SetFocused(true) i.HandleKey(keyEvent(tcell.KeyHome, 0, tcell.ModNone)) i.HandleKey(keyEvent(tcell.KeyDelete, 0, tcell.ModNone)) if got := i.Text(); got != "bc" { t.Errorf("Text() = %q, want %q", got, "bc") } i.HandleKey(keyEvent(tcell.KeyEnd, 0, tcell.ModNone)) i.HandleKey(runeEvent('!')) if got := i.Text(); got != "bc!" { t.Errorf("Text() = %q, want %q", got, "bc!") } i.HandleKey(keyEvent(tcell.KeyLeft, 0, tcell.ModNone)) i.HandleKey(runeEvent('-')) if got := i.Text(); got != "bc-!" { t.Errorf("Text() = %q, want the character inserted at the cursor", got) } } func TestInputLineIgnoresDeletesAtItsEdges(t *testing.T) { i := NewInputLine("ab") i.SetFocused(true) i.HandleKey(keyEvent(tcell.KeyHome, 0, tcell.ModNone)) i.HandleKey(keyEvent(tcell.KeyBackspace2, 0, tcell.ModNone)) i.HandleKey(keyEvent(tcell.KeyEnd, 0, tcell.ModNone)) i.HandleKey(keyEvent(tcell.KeyDelete, 0, tcell.ModNone)) if got := i.Text(); got != "ab" { t.Errorf("Text() = %q, want it unchanged", got) } } func TestInputLineCtrlUClearsIt(t *testing.T) { i := NewInputLine("something") i.SetFocused(true) i.HandleKey(keyEvent(tcell.KeyCtrlU, 0, tcell.ModNone)) if got := i.Text(); got != "" { t.Errorf("Text() = %q, want it empty", got) } } func TestInputLineReportsEveryChange(t *testing.T) { var seen []string i := NewInputLine("") i.SetFocused(true) i.OnChange = func(text string) { seen = append(seen, text) } i.HandleKey(runeEvent('a')) i.HandleKey(runeEvent('b')) i.HandleKey(keyEvent(tcell.KeyBackspace2, 0, tcell.ModNone)) want := []string{"a", "ab", "a"} if len(seen) != len(want) { t.Fatalf("OnChange saw %v, want %v", seen, want) } for i, w := range want { if seen[i] != w { t.Errorf("OnChange call %d saw %q, want %q", i, seen[i], w) } } } func TestAnUnfocusedInputLineIgnoresKeys(t *testing.T) { i := NewInputLine("abc") if i.HandleKey(runeEvent('z')) { t.Error("an unfocused field claimed a key") } if got := i.Text(); got != "abc" { t.Errorf("Text() = %q, want it unchanged", got) } } func TestInputLineScrollsToKeepTheCursorVisible(t *testing.T) { screen, root := newTestScreen(t, 20, 2) i := NewInputLine("abcdefghijklmnop") i.SetBounds(Rect{X: 0, Y: 0, W: 6, H: 1}) i.SetFocused(true) i.Draw(root, testTheme(t)) screen.Show() // The cursor sits at the end of the text, so the tail must be showing. if got := screenLines(t, screen)[0][:6]; !strings.Contains(got, "op") { t.Errorf("row 0 = %q, want the end of the text visible", got) } if _, _, visible := screen.GetCursor(); !visible { t.Error("a focused field did not place the terminal cursor") } } func TestClickingAnInputLinePutsTheCursorThere(t *testing.T) { i := NewInputLine("abcdef") i.SetBounds(Rect{X: 4, Y: 2, W: 10, H: 1}) if !i.HandleMouse(clickEvent(7, 2)) { t.Fatal("the click was not handled") } if i.cursor != 3 { t.Errorf("cursor = %d, want 3", i.cursor) } i.HandleMouse(clickEvent(13, 2)) // past the end of the text if i.cursor != 6 { t.Errorf("cursor = %d, want it clamped to the end of the text", i.cursor) } } func TestCheckBoxToggles(t *testing.T) { var states []bool c := NewCheckBox("~R~ecursive", false) c.OnToggle = func(on bool) { states = append(states, on) } c.SetFocused(true) c.HandleKey(runeEvent(' ')) if !c.Checked() { t.Error("Space did not tick the box") } c.HandleKey(keyEvent(tcell.KeyEnter, 0, tcell.ModNone)) if c.Checked() { t.Error("Enter did not untick the box") } if len(states) != 2 || !states[0] || states[1] { t.Errorf("OnToggle saw %v, want [true false]", states) } } func TestClickingACheckBoxTogglesIt(t *testing.T) { c := NewCheckBox("~R~ecursive", false) c.SetBounds(Rect{X: 2, Y: 2, W: 15, H: 1}) c.HandleMouse(clickEvent(3, 2)) if !c.Checked() { t.Error("the click did not tick the box") } if c.HandleMouse(clickEvent(30, 30)) { t.Error("the box claimed a click that missed it") } } func TestCheckBoxDrawsItsMark(t *testing.T) { screen, root := newTestScreen(t, 20, 2) c := NewCheckBox("~R~ecursive", true) c.SetBounds(Rect{X: 0, Y: 0, W: 16, H: 1}) c.Draw(root, testTheme(t)) screen.Show() got := screenLines(t, screen)[0] if !strings.HasPrefix(got, "[×] Recursive") { t.Errorf("row 0 = %q, want a ticked box and the label", got) } } func TestAnUnfocusedCheckBoxIgnoresKeys(t *testing.T) { c := NewCheckBox("x", false) if c.HandleKey(runeEvent(' ')) { t.Error("an unfocused box claimed a key") } if c.Checked() { t.Error("an unfocused box was toggled by a key") } } func TestInputLineLeavesAltAndCtrlLettersAlone(t *testing.T) { i := NewInputLine("") i.SetFocused(true) if i.HandleKey(keyEvent(tcell.KeyRune, 'c', tcell.ModAlt)) { t.Error("the field swallowed Alt-C, which belongs to a dialog's buttons") } if got := i.Text(); got != "" { t.Errorf("Text() = %q, want the modified key not to have been typed", got) } }