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 / review / review_test.go
review_test.go 3.6K
1package review23import (4 "slices"5 "strings"6 "testing"78 tea "charm.land/bubbletea/v2"9 "github.com/charmbracelet/x/ansi"1011 "src.rest/cat/012/internal/sheet"12 "src.rest/cat/012/internal/ui/overlay"13 "src.rest/cat/012/internal/ui/theme"14)1516// fakeHost holds suggestions and settles them as the model would.17type fakeHost struct {18 th theme.Theme19 items []Item20 closed int21 shown []string22}2324func (f *fakeHost) Theme() *theme.Theme { return &f.th }25func (f *fakeHost) Size() (width, height int) { return 80, 24 }26func (f *fakeHost) Close() { f.closed++ }27func (f *fakeHost) Suggestions() []Item { return slices.Clone(f.items) }28func (f *fakeHost) Reveal(s string, a sheet.Addr) {29 f.shown = append(f.shown, sheet.Qualified(s, sheet.Rect{From: a, To: a}))30}3132func (f *fakeHost) settle(id int, cells []int) {33 for i, it := range f.items {34 if it.ID != id {35 continue36 }37 if cells == nil {38 f.items = slices.Delete(f.items, i, i+1)39 return40 }41 it.Cells = slices.DeleteFunc(slices.Clone(it.Cells), func(c Cell) bool { return slices.Contains(cells, c.Index) })42 f.items[i] = it43 if len(it.Cells) == 0 {44 f.items = slices.Delete(f.items, i, i+1)45 }46 return47 }48}4950func (f *fakeHost) Accept(id int, cells []int) { f.settle(id, cells) }51func (f *fakeHost) Reject(id int, cells []int) { f.settle(id, cells) }5253func twoSuggestions() *fakeHost {54 return &fakeHost{th: theme.New(true), items: []Item{55 {ID: 1, Agent: "claude", Label: "set", Message: "fix totals", Total: 2, Cells: []Cell{56 {Index: 0, Sheet: "Sheet1", At: sheet.Addr{Row: 1}, Was: "40", Now: "42"},57 {Index: 1, Sheet: "Sheet1", At: sheet.Addr{Row: 2}, Was: "", Now: "=A1+A2"}}},58 {ID: 2, Agent: "claude", Label: "insert 1 row", Whole: true, Other: []string{"inserts or deletes rows or columns on Sheet1"}},59 }}60}6162func text(p *Panel) string {63 var b strings.Builder64 for _, l := range p.Layout()[0].Lines {65 b.WriteString(ansi.Strip(l) + "\n")66 }67 return b.String()68}6970func TestPanelLists(t *testing.T) {71 h := twoSuggestions()72 p := New(h)73 got := text(p)74 for _, want := range []string{"Suggestions", "◆ claude: fix totals (2 cells)", "◇ A2 40 → 42", "◇ A3 blank → =A1+A2",75 "claude: insert 1 row (whole)", "also inserts or deletes rows", "2 waiting"} {76 if !strings.Contains(got, want) {77 t.Errorf("no %q in\n%s", want, got)78 }79 }80 if len(h.shown) != 1 || h.shown[0] != "Sheet1!A2" {81 t.Errorf("shown %v", h.shown)82 }83}8485func TestPanelSettlesByCellAndWhole(t *testing.T) {86 h := twoSuggestions()87 p := New(h)88 p.Key(tea.KeyPressMsg{Code: tea.KeyDown})89 p.Key(tea.KeyPressMsg{Code: 'r', Text: "r"}) // A2 rejected90 if len(h.items[0].Cells) != 1 || h.items[0].Cells[0].Index != 1 {91 t.Fatalf("items %+v", h.items)92 }93 p.Key(tea.KeyPressMsg{Code: tea.KeyUp})94 p.Key(tea.KeyPressMsg{Code: tea.KeyEnter}) // the rest of the first95 if len(h.items) != 1 || h.items[0].ID != 2 {96 t.Fatalf("items %+v", h.items)97 }98 p.Key(tea.KeyPressMsg{Code: 'A', Text: "A"})99 if len(h.items) != 0 || h.closed != 1 {100 t.Fatalf("accepting all left %+v, closed %d", h.items, h.closed)101 }102}103104func TestPanelChipsClick(t *testing.T) {105 h := twoSuggestions()106 p := New(h)107 p.Layout()108 inner := p.box.Width() - 2109 p.Mouse(overlay.MouseEvent{Kind: overlay.MousePress, Box: ID, Col: inner - 2, Row: 1}) // ✗ on the first heading110 if len(h.items) != 1 || h.items[0].ID != 2 {111 t.Fatalf("clicking ✗: %+v", h.items)112 }113 p.Layout()114 p.Mouse(overlay.MouseEvent{Kind: overlay.MousePress, Box: ID, Col: inner - 6, Row: 1}) // ✓115 if len(h.items) != 0 {116 t.Fatalf("clicking ✓: %+v", h.items)117 }118 desc, keys := p.Status()119 if desc != "" || !strings.Contains(ansi.Strip(keys), "accept") {120 t.Errorf("status %q %q", desc, keys)121 }122}