cat / 012
clone

a spreadsheet for the terminal: lotus 1-2-3 looks, google sheets keys and formulas, one go binary

cat/module-path
012 / internal / ui / picker / picker_test.go
picker_test.go 4.9K
1package picker23import (4 "strings"5 "testing"67 tea "charm.land/bubbletea/v2"8 "github.com/charmbracelet/x/ansi"910 "src.rest/cat/012/internal/ui/lineedit"11 "src.rest/cat/012/internal/ui/overlay"12 "src.rest/cat/012/internal/ui/theme"13)1415// fakeHost is a screen of its own with a record of what the picker did.16type fakeHost struct {17 th theme.Theme18 line lineedit.Line19 w, h int20 closed int21 answers []string22}2324func newHost() *fakeHost { return &fakeHost{th: theme.New(true), w: 80, h: 24} }2526func (f *fakeHost) Theme() *theme.Theme { return &f.th }27func (f *fakeHost) Size() (width, height int) { return f.w, f.h }28func (f *fakeHost) Line() *lineedit.Line { return &f.line }29func (f *fakeHost) Close() { f.closed++ }3031func (f *fakeHost) RecordAnswer(answer string, cancelled bool) {32 if cancelled {33 answer = "(cancelled)"34 }35 f.answers = append(f.answers, answer)36}3738func items(picked *string, titles ...string) []Item {39 out := make([]Item, len(titles))40 for i, t := range titles {41 out[i] = Item{Title: t, Name: len(t), Desc: "Does " + t, Pick: func() tea.Cmd { *picked = t; return nil }}42 }43 return out44}4546func typeText(p *Picker, s string) {47 for _, r := range s {48 p.Key(tea.KeyPressMsg{Code: r, Text: string(r)})49 }50}5152func titles(p *Picker) []string {53 var out []string54 for _, m := range p.Shown() {55 out = append(out, m.Item.Title)56 }57 return out58}5960func TestSearchRanksWordPrefixesFirst(t *testing.T) {61 h, picked := newHost(), ""62 p := New(h, "Test", "Type", 60, items(&picked, "Command line", "Column width", "Save"))63 if got := len(p.Shown()); got != 3 {64 t.Fatalf("empty search shows %d", got)65 }66 typeText(p, "col")67 if got := strings.Join(titles(p), ","); got != "Column width,Command line" {68 t.Errorf("col matched %s", got)69 }70 if h.line.Text() != "col" {71 t.Errorf("search typed %q", h.line.Text())72 }73}7475func TestEnterPicksAndRecords(t *testing.T) {76 h, picked := newHost(), ""77 p := New(h, "Test", "Type", 60, items(&picked, "Alpha", "Beta"))78 p.Answers = true79 p.Key(tea.KeyPressMsg{Code: tea.KeyDown})80 p.Key(tea.KeyPressMsg{Code: tea.KeyEnter})81 if picked != "Beta" || strings.Join(h.answers, ",") != "Beta" {82 t.Errorf("picked %q, answers %v", picked, h.answers)83 }84 p.Key(tea.KeyPressMsg{Code: tea.KeyEscape})85 if h.closed != 1 || h.answers[len(h.answers)-1] != "(cancelled)" {86 t.Errorf("Esc: closed %d, answers %v", h.closed, h.answers)87 }88}8990func TestUnavailableItemsDontPick(t *testing.T) {91 h, picked := newHost(), ""92 its := items(&picked, "Undo")93 its[0].Off = true94 p := New(h, "Test", "Type", 60, its)95 p.Key(tea.KeyPressMsg{Code: tea.KeyEnter})96 if picked != "" {97 t.Error("an unavailable item was picked")98 }99 if desc, _ := p.Status(); desc != "Does Undo (not available now)" {100 t.Errorf("status %q", desc)101 }102 if _, ok := p.Answer("undo"); ok {103 t.Error("a script answered with an unavailable item")104 }105}106107func TestEnterHookAndAnswer(t *testing.T) {108 h, picked := newHost(), ""109 p := New(h, "Test", "Type", 60, items(&picked, "Alpha"))110 var query string111 p.Enter = func(q string) (tea.Cmd, bool) { query = q; return nil, q == "/tmp" }112 typeText(p, "/tmp")113 p.Key(tea.KeyPressMsg{Code: tea.KeyEnter})114 if query != "/tmp" || picked != "" {115 t.Errorf("Enter hook got %q, picked %q", query, picked)116 }117 if _, ok := p.Answer("ALPHA"); !ok || picked != "Alpha" {118 t.Errorf("answer picked %q", picked)119 }120}121122func TestLayoutAndMouse(t *testing.T) {123 h, picked := newHost(), ""124 p := New(h, "Test", "Type a name", 60, items(&picked, "Alpha", "Beta", "Gamma"))125 boxes := p.Layout()126 if len(boxes) != 1 || boxes[0].ID != ID {127 t.Fatalf("layout %+v", boxes)128 }129 b := boxes[0]130 if b.Y != overlay.MenuLine+1 || b.X+b.Width() > h.w || b.Height() != 3+3+1 {131 t.Errorf("box at %d,%d, %dx%d", b.X, b.Y, b.Width(), b.Height())132 }133 out := ansi.Strip(strings.Join(b.Lines, "\n"))134 for _, want := range []string{"Test", "Type a name", "Gamma", "3 of 3"} {135 if !strings.Contains(out, want) {136 t.Errorf("box lacks %q:\n%s", want, out)137 }138 }139 x, y := p.Cursor()140 if y != b.Y+1 || x != b.X+1+ansi.StringWidth(overlay.SearchPrompt) {141 t.Errorf("cursor at %d,%d", x, y)142 }143 p.Mouse(overlay.MouseEvent{Kind: overlay.MouseMotion, Box: ID, Row: FirstRow + 2})144 if p.Selected().Title != "Gamma" {145 t.Errorf("hover selected %q", p.Selected().Title)146 }147 p.Mouse(overlay.MouseEvent{Kind: overlay.MousePress, Button: tea.MouseLeft, Box: ID, Row: FirstRow + 1})148 if picked != "Beta" {149 t.Errorf("click picked %q", picked)150 }151 p.Mouse(overlay.MouseEvent{Kind: overlay.MousePress, Button: tea.MouseLeft})152 if h.closed != 1 {153 t.Error("a click outside didn't close the picker")154 }155}156157func TestNoMatches(t *testing.T) {158 h, picked := newHost(), ""159 p := New(h, "Test", "Type", 60, items(&picked, "Alpha"))160 typeText(p, "zzq")161 if p.Selected() != nil {162 t.Error("something is selected")163 }164 if out := ansi.Strip(strings.Join(p.Layout()[0].Lines, "\n")); !strings.Contains(out, "No matches") || !strings.Contains(out, "0 of 1") {165 t.Errorf("no-match box:\n%s", out)166 }167}