cat / 012
clone

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

main
012 / internal / ui / findbar / find_test.go
find_test.go 6.4K
1package findbar23import (4 "strings"5 "testing"67 tea "charm.land/bubbletea/v2"8 "github.com/charmbracelet/x/ansi"910 "src.rest/cat/012/internal/sheet"11 "src.rest/cat/012/internal/telemetry"12 "src.rest/cat/012/internal/ui/lineedit"13 "src.rest/cat/012/internal/ui/overlay"14 "src.rest/cat/012/internal/ui/theme"15)1617// fakeHost is a workbook with an active cell and a record of what the18// bar did.19type fakeHost struct {20 th theme.Theme21 line lineedit.Line22 s *sheet.Sheet23 cur sheet.Addr24 note string25 edited bool26 answers []string27 left int28 pressed []int29}3031func newHost(t *testing.T, cells ...string) *fakeHost {32 t.Helper()33 s := sheet.New()34 for i := 0; i < len(cells); i += 2 {35 if err := s.Set(addr(cells[i]), cells[i+1]); err != nil {36 t.Fatal(err)37 }38 }39 return &fakeHost{th: theme.New(true), s: s}40}4142func addr(s string) sheet.Addr {43 a, _ := sheet.ParseAddr(s)44 return a45}4647func (f *fakeHost) Theme() *theme.Theme { return &f.th }48func (f *fakeHost) Size() (width, height int) { return 120, 24 }49func (f *fakeHost) Line() *lineedit.Line { return &f.line }50func (f *fakeHost) Book() *sheet.Workbook { return f.s.Book() }51func (f *fakeHost) Trace() *telemetry.Trace { return nil }52func (f *fakeHost) At() (*sheet.Sheet, sheet.Addr) { return f.s, f.cur }53func (f *fakeHost) Show(s *sheet.Sheet, a sheet.Addr) { f.s, f.cur = s, a }54func (f *fakeHost) Note(msg string) { f.note = msg }55func (f *fakeHost) Replace(do func() string) {56 if a := do(); a != "" {57 f.edited = true58 f.answers = append(f.answers, a)59 }60}61func (f *fakeHost) Leave() { f.left++ }6263func (f *fakeHost) Press(x, y int, _ tea.MouseButton) tea.Cmd {64 f.pressed = []int{x, y}65 return nil66}6768func typeText(b *Bar, s string) {69 for _, r := range s {70 b.Key(tea.KeyPressMsg{Code: r, Text: string(r)})71 }72}7374func open(h *fakeHost, replace bool) *Bar {75 b := New(h)76 b.Reset(nil)77 b.Open(replace)78 return b79}8081func TestFindAsYouType(t *testing.T) {82 h := newHost(t, "A1", "Rent", "A2", "rental car", "A3", "Total rent", "B1", "1450")83 b := open(h, false)84 typeText(b, "ren")85 if len(b.Matches()) != 3 || h.cur != addr("A1") {86 t.Fatalf("matches %v, at %v", b.Matches(), h.cur)87 }88 if left, right := b.ContextLine(); !strings.Contains(ansi.Strip(left), "Find › ren") || ansi.Strip(right) != "1 of 3" {89 t.Errorf("context line %q %q", left, right)90 }91 b.Key(tea.KeyPressMsg{Code: tea.KeyEnter})92 b.Key(tea.KeyPressMsg{Code: tea.KeyEnter})93 b.Key(tea.KeyPressMsg{Code: tea.KeyEnter})94 if h.cur != addr("A1") {95 t.Errorf("didn't wrap: at %v", h.cur)96 }97 if !b.Found(h.s, addr("A2")) || b.Found(h.s, addr("B1")) {98 t.Error("wrong cells found")99 }100 b.Key(tea.KeyPressMsg{Code: tea.KeyEscape})101 if h.left != 1 {102 t.Error("Esc didn't leave")103 }104}105106func TestAgainFromTheActiveCell(t *testing.T) {107 h := newHost(t, "A1", "x", "A5", "x", "A9", "x")108 b := open(h, false)109 typeText(b, "x")110 h.cur = addr("A5")111 b.Again(1)112 if h.cur != addr("A9") || h.note != "Match 3 of 3 for x" {113 t.Errorf("next: at %v, %q", h.cur, h.note)114 }115 b.Again(-1)116 if h.cur != addr("A5") {117 t.Errorf("previous: at %v", h.cur)118 }119}120121func TestOptionsAndBadRegex(t *testing.T) {122 h := newHost(t, "A1", "Rent", "A2", "rent")123 b := open(h, false)124 typeText(b, "Rent")125 b.Key(tea.KeyPressMsg{Code: 'c', Mod: tea.ModAlt})126 if !b.Options().MatchCase || len(b.Matches()) != 1 {127 t.Errorf("match case: %v", b.Matches())128 }129 b.Key(tea.KeyPressMsg{Code: 'r', Mod: tea.ModAlt})130 typeText(b, "(")131 if _, right := b.ContextLine(); ansi.Strip(right) != "Invalid regular expression" {132 t.Errorf("bad regex: %q", right)133 }134}135136func TestReplace(t *testing.T) {137 h := newHost(t, "A1", "rent", "A2", "rental car", "A3", "total rent")138 b := open(h, true)139 typeText(b, "rent")140 b.Key(tea.KeyPressMsg{Code: tea.KeyTab})141 typeText(b, "lease")142 if !b.Replacing() || b.Field() != 1 {143 t.Fatalf("replace field %d", b.Field())144 }145 b.Key(tea.KeyPressMsg{Code: tea.KeyEnter})146 if got := h.s.Cell(addr("A1")).Input; got != "lease" || !h.edited || h.cur != addr("A2") {147 t.Errorf("replace one: A1 %q, at %v", got, h.cur)148 }149 b.Key(tea.KeyPressMsg{Code: tea.KeyEnter, Mod: tea.ModCtrl})150 if got := h.s.Cell(addr("A3")).Input; got != "total lease" || h.note != "Replaced 2 cells" {151 t.Errorf("replace all: A3 %q, note %q", got, h.note)152 }153 // What a macro records of each.154 want := `{"find":"rent","replace":"lease","cell":"A1"}` + "\n" + `{"find":"rent","replace":"lease"}`155 if got := strings.Join(h.answers, "\n"); got != want {156 t.Errorf("answers:\n%s\nwant:\n%s", got, want)157 }158}159160func TestAnswerReplaces(t *testing.T) {161 h := newHost(t, "A1", "rent", "A2", "Rent", "B2", "rent")162 b := open(h, true)163 if err := b.Answer(`{"find":"rent","replace":"lease","matchCase":true,"cell":"B2"}`); err != nil {164 t.Fatal(err)165 }166 if h.s.Cell(addr("B2")).Input != "lease" || h.s.Cell(addr("A1")).Input != "rent" {167 t.Fatalf("one replacement: A1 %q B2 %q", h.s.Cell(addr("A1")).Input, h.s.Cell(addr("B2")).Input)168 }169 if err := b.Answer(`{"find":"rent","replace":"hire","within":"A1:A2"}`); err != nil {170 t.Fatal(err)171 }172 if h.s.Cell(addr("A1")).Input != "hire" || h.s.Cell(addr("A2")).Input != "hire" {173 t.Fatalf("within a range: A1 %q A2 %q", h.s.Cell(addr("A1")).Input, h.s.Cell(addr("A2")).Input)174 }175 if last := h.answers[len(h.answers)-1]; last != `{"find":"rent","replace":"hire","within":"A1:A2"}` {176 t.Errorf("recorded %s", last)177 }178 for text, want := range map[string]string{179 `{"replace":"x"}`: "nothing to find",180 `{"find":"x","within":"here"}`: `"within" is "here"`,181 `{"find":"(","regex":true}`: "Invalid regular expression",182 `{"find":"zzz","replace":"y","cell":"A1"}`: `A1 doesn't match "zzz"`,183 } {184 if err := b.Answer(text); err == nil || !strings.Contains(err.Error(), want) {185 t.Errorf("Answer(%s) = %v, want %q", text, err, want)186 }187 }188}189190func TestMouse(t *testing.T) {191 h := newHost(t, "A1", "rent", "A2", "Rent")192 b := open(h, false)193 typeText(b, "rent")194 left, _ := b.ContextLine()195 plain := ansi.Strip(left)196 x := ansi.StringWidth(plain[:strings.Index(plain, "Aa")])197 b.Mouse(overlay.MouseEvent{Kind: overlay.MousePress, X: x, Y: overlay.ContextLine})198 if !b.Options().MatchCase || len(b.Matches()) != 1 {199 t.Errorf("clicking Aa: %v", b.Matches())200 }201 b.Mouse(overlay.MouseEvent{Kind: overlay.MousePress, X: 3, Y: 10})202 if h.left != 1 || len(h.pressed) != 2 || h.pressed[1] != 10 {203 t.Errorf("a click on the grid: left %d, pressed %v", h.left, h.pressed)204 }205}