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 / find_test.go
find_test.go 5.4K
1package ui23import (4 "strings"5 "testing"67 tea "charm.land/bubbletea/v2"8 "github.com/charmbracelet/x/ansi"910 "src.rest/cat/012/internal/ui/findbar"11)1213// findModel has a small sheet to search.14func findModel(t *testing.T) *Model {15 t.Helper()16 m := tallModel()17 press(t, m, "Rent", "<tab>", "1450", "<enter>")18 press(t, m, "rental car", "<tab>", "=B1*2", "<enter>")19 press(t, m, "Total rent", "<tab>", "=SUM(B1:B2)", "<enter>")20 press(t, m, "<ctrl+home>")21 return m22}2324// col is the screen column where sub starts in line l.25func col(l, sub string) int {26 return ansi.StringWidth(l[:strings.Index(l, sub)])27}2829func findBarOf(t *testing.T, m *Model) *findbar.Bar {30 t.Helper()31 f, ok := m.overlay.(*findbar.Bar)32 if !ok {33 t.Fatalf("no find bar (overlay %T)", m.overlay)34 }35 return f36}3738func TestFindAsYouType(t *testing.T) {39 m := findModel(t)40 press(t, m, "<ctrl+f>")41 if m.indicator() != "FIND" && !strings.Contains(line(m, menuLine), "FIND") {42 t.Errorf("indicator %q", line(m, menuLine))43 }44 press(t, m, "ren")45 f := findBarOf(t, m)46 if len(f.Matches()) != 3 || m.cur != addr("A1") || !strings.Contains(line(m, contextLine), "1 of 3") {47 t.Fatalf("matches %v cur %v line %q", f.Matches(), m.cur, line(m, contextLine))48 }49 // The terminal cursor sits after the query.50 if x, y, ok := m.cursorPos(); !ok || y != contextLine || x != col(line(m, contextLine), "ren")+3 {51 t.Errorf("cursor %d,%d %v in %q", x, y, ok, line(m, contextLine))52 }53 press(t, m, "<enter>", "<enter>")54 if m.cur != addr("A3") || !strings.Contains(line(m, contextLine), "3 of 3") {55 t.Errorf("after two Enters: cur %v line %q", m.cur, line(m, contextLine))56 }57 press(t, m, "<enter>")58 if m.cur != addr("A1") {59 t.Errorf("did not wrap: cur %v", m.cur)60 }61 press(t, m, "<shift+enter>")62 if m.cur != addr("A3") {63 t.Errorf("shift+enter: cur %v", m.cur)64 }65 // Matches are highlighted in the grid while the bar is open.66 if !m.found(addr("A2")) || m.found(addr("B1")) {67 t.Error("wrong cells highlighted")68 }69 press(t, m, "<esc>")70 if m.overlay != nil || m.cur != addr("A3") || m.found(addr("A2")) {71 t.Errorf("Esc: overlay %T cur %v", m.overlay, m.cur)72 }73 // Ctrl+F again reopens the last search.74 press(t, m, "<ctrl+f>")75 if f := findBarOf(t, m); m.line.Text() != "ren" || len(f.Matches()) != 3 {76 t.Errorf("reopened with %q", m.line.Text())77 }78}7980func TestFindOptions(t *testing.T) {81 m := findModel(t)82 press(t, m, "<ctrl+f>", "Rent")83 f := findBarOf(t, m)84 press(t, m, "<alt+c>")85 if len(f.Matches()) != 1 {86 t.Errorf("match case: %v", f.Matches())87 }88 press(t, m, "<alt+c>", "<alt+w>")89 if len(f.Matches()) != 1 || f.Matches()[0].At != addr("A1") {90 t.Errorf("whole cell: %v", f.Matches())91 }92 press(t, m, "<alt+w>")93 for range 4 {94 press(t, m, "<backspace>")95 }96 press(t, m, "B1", "<alt+=>")97 if len(f.Matches()) != 2 {98 t.Errorf("in formulas: %v", f.Matches())99 }100 press(t, m, "<alt+=>")101 for range 2 {102 press(t, m, "<backspace>")103 }104 press(t, m, "<alt+r>", "^r.n")105 if len(f.Matches()) != 2 {106 t.Errorf("regex: %v", f.Matches())107 }108 press(t, m, "(")109 if !strings.Contains(line(m, contextLine), "Invalid regular expression") {110 t.Errorf("bad regex line %q", line(m, contextLine))111 }112 press(t, m, "<backspace>")113 press(t, m, "nomatch")114 if !strings.Contains(line(m, contextLine), "No matches") {115 t.Errorf("no-match line %q", line(m, contextLine))116 }117}118119func TestFindWithinSelection(t *testing.T) {120 m := findModel(t)121 press(t, m, "<shift+down>", "<ctrl+f>", "ren")122 f := findBarOf(t, m)123 if len(f.Matches()) != 2 || !strings.Contains(line(m, contextLine), "in A1:A2") {124 t.Fatalf("scoped: %v line %q", f.Matches(), line(m, contextLine))125 }126 press(t, m, "<alt+s>")127 if len(f.Matches()) != 3 {128 t.Errorf("unscoped: %v", f.Matches())129 }130}131132func TestReplace(t *testing.T) {133 m := findModel(t)134 press(t, m, "<ctrl+h>", "rent", "<tab>", "lease")135 f := findBarOf(t, m)136 if !f.Replacing() || f.Field() != 1 || !strings.Contains(line(m, contextLine), "Replace › lease") {137 t.Fatalf("replace bar %q", line(m, contextLine))138 }139 press(t, m, "<enter>") // replace A1, move on140 if input(m, "A1") != "lease" || m.cur != addr("A2") {141 t.Errorf("replace one: A1 %q cur %v", input(m, "A1"), m.cur)142 }143 press(t, m, "<ctrl+enter>")144 if input(m, "A2") != "leaseal car" || input(m, "A3") != "Total lease" {145 t.Errorf("replace all: %q %q", input(m, "A2"), input(m, "A3"))146 }147 if !strings.Contains(line(m, contextLine), "No matches") {148 t.Errorf("after replace all %q", line(m, contextLine))149 }150 press(t, m, "<esc>", "<ctrl+z>")151 if input(m, "A2") != "rental car" || input(m, "A3") != "Total rent" || input(m, "A1") != "lease" {152 t.Errorf("undo of replace all: %q %q %q", input(m, "A1"), input(m, "A2"), input(m, "A3"))153 }154}155156func TestFindMouse(t *testing.T) {157 m := findModel(t)158 press(t, m, "<ctrl+f>", "rent")159 f := findBarOf(t, m)160 l := line(m, contextLine)161 send(m, tea.MouseClickMsg{X: col(l, "Aa"), Y: contextLine, Button: tea.MouseLeft})162 if !f.Options().MatchCase || len(f.Matches()) != 2 {163 t.Errorf("clicking Aa: case %v matches %v", f.Options().MatchCase, f.Matches())164 }165 // Clicking the grid closes the bar and moves there.166 click(m, cellX(1), gridTop+1, 0)167 if m.overlay != nil || m.cur != addr("B2") {168 t.Errorf("grid click: overlay %T cur %v", m.overlay, m.cur)169 }170}171172func TestFindInMenusAndPalette(t *testing.T) {173 m := newModel()174 press(t, m, "<alt+e>")175 var got []string176 for _, it := range openMenu(t, m).top().items {177 got = append(got, it.label())178 }179 if s := strings.Join(got, ","); !strings.Contains(s, "Find,Find and replace") {180 t.Errorf("Edit menu %s", s)181 }182}