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 / cmdline / cmdline_test.go
cmdline_test.go 5.1K
1package cmdline23import (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 knows a few commands and runs lines it recognises.16type fakeHost struct {17 th theme.Theme18 line lineedit.Line19 closed int20 ran []string21 failed string22 hist History23}2425func newHost() *fakeHost { return &fakeHost{th: theme.New(true)} }2627func (f *fakeHost) Theme() *theme.Theme { return &f.th }28func (f *fakeHost) Size() (width, height int) { return 80, 24 }29func (f *fakeHost) Line() *lineedit.Line { return &f.line }30func (f *fakeHost) Close() { f.closed++ }31func (f *fakeHost) Fail(msg string) { f.failed = msg }32func (f *fakeHost) History() *History { return &f.hist }3334func (f *fakeHost) Commands() []Item {35 return []Item{36 {Word: "edit.fill_down", Title: "Fill down", Desc: "Copy the top cell down", Key: "Ctrl+D"},37 {Word: "format.bold", Title: "Bold", Desc: "Bold the selection", Key: "Ctrl+B"},38 {Word: "edit.undo", Title: "Undo", Desc: "Undo", Off: true},39 }40}4142func (f *fakeHost) Run(text string) (tea.Cmd, bool) {43 if text == "edit.fill_down" || text == "w" || text == "B12" {44 f.ran = append(f.ran, text)45 return nil, true46 }47 return nil, false48}4950func typeText(c *Line, s string) {51 for _, r := range s {52 c.Key(tea.KeyPressMsg{Code: r, Text: string(r)})53 }54}5556func words(c *Line) string {57 var out []string58 for _, it := range c.Shown() {59 out = append(out, it.Word)60 }61 return strings.Join(out, ",")62}6364func TestCompletes(t *testing.T) {65 h := newHost()66 c := New(h)67 if n := len(c.Shown()); n != len(FileWords)+3 {68 t.Fatalf("empty line completes %d", n)69 }70 typeText(c, "fill")71 if got := words(c); got != "edit.fill_down" {72 t.Errorf("fill completes %s", got)73 }74 typeText(c, " d")75 if got := words(c); got != "edit.fill_down" {76 t.Errorf("fill d completes %s", got)77 }78 h.line.Clear()79 typeText(c, "w")80 if got := words(c); !strings.HasPrefix(got, "w,w!,wq,") {81 t.Errorf("w completes %s", got)82 }83 typeText(c, " out.csv")84 if got := words(c); got != "" {85 t.Errorf("a file command's argument completes %s", got)86 }87}8889func TestTabCompletesThenCycles(t *testing.T) {90 h := newHost()91 c := New(h)92 typeText(c, "o")93 c.Key(tea.KeyPressMsg{Code: tea.KeyTab})94 first := h.line.Text()95 c.Key(tea.KeyPressMsg{Code: tea.KeyTab})96 if first != c.Shown()[0].Word || h.line.Text() != c.Shown()[1].Word {97 t.Errorf("Tab put %q then %q", first, h.line.Text())98 }99}100101func TestEnterRunsOrFallsBack(t *testing.T) {102 h := newHost()103 c := New(h)104 typeText(c, ":B12")105 c.Key(tea.KeyPressMsg{Code: tea.KeyEnter})106 if h.closed != 1 || strings.Join(h.ran, ",") != "B12" {107 t.Errorf("closed %d, ran %v", h.closed, h.ran)108 }109 // Text that isn't a command runs the highlighted completion.110 c = New(h)111 typeText(c, "fill d")112 c.Key(tea.KeyPressMsg{Code: tea.KeyEnter})113 if h.failed != "" || strings.Join(h.ran, ",") != "B12,edit.fill_down" {114 t.Errorf("ran %v, failed %q", h.ran, h.failed)115 }116 c = New(h)117 typeText(c, "nonsense")118 c.Key(tea.KeyPressMsg{Code: tea.KeyEnter})119 if h.failed != "Not a command, cell or range: nonsense" {120 t.Errorf("failed %q", h.failed)121 }122}123124func TestBackspaceOnEmptyCloses(t *testing.T) {125 h := newHost()126 c := New(h)127 c.Key(tea.KeyPressMsg{Code: tea.KeyBackspace})128 if h.closed != 1 {129 t.Error("Backspace on an empty line didn't close it")130 }131}132133func TestLayoutStatusAndMouse(t *testing.T) {134 h := newHost()135 c := New(h)136 typeText(c, "o")137 left, _ := c.ContextLine()138 if ansi.Strip(left) != ":o" {139 t.Errorf("context line %q", left)140 }141 if x, y := c.Cursor(); x != 2 || y != overlay.ContextLine {142 t.Errorf("cursor %d,%d", x, y)143 }144 b := c.Layout()[0]145 if b.ID != ID || b.Y != overlay.ContextLine+1 || !strings.Contains(ansi.Strip(strings.Join(b.Lines, "\n")), "Commands") {146 t.Fatalf("box %+v", b)147 }148 for i, it := range c.Shown() {149 if it.Word == "edit.undo" {150 c.Mouse(overlay.MouseEvent{Kind: overlay.MouseMotion, Box: ID, Row: 1 + i})151 }152 }153 if desc, _ := c.Status(); desc != "Undo (not available now)" {154 t.Errorf("status %q", desc)155 }156 c.Mouse(overlay.MouseEvent{Kind: overlay.MousePress, Button: tea.MouseLeft})157 if h.closed != 1 {158 t.Error("a click outside didn't close the line")159 }160}161162// Up and Down walk the history, among the lines starting with what was163// typed; a line run goes to its end, once.164func TestHistory(t *testing.T) {165 h := newHost()166 for _, l := range []string{"w", "B12", "edit.fill_down", "B12"} {167 c := New(h)168 typeText(c, l)169 c.Key(tea.KeyPressMsg{Code: tea.KeyEnter})170 }171 if got := strings.Join(h.hist.Lines(), ","); got != "w,edit.fill_down,B12" {172 t.Fatalf("history %s", got)173 }174 c := New(h)175 up, down := tea.KeyPressMsg{Code: tea.KeyUp}, tea.KeyPressMsg{Code: tea.KeyDown}176 c.Key(up)177 c.Key(up)178 if h.line.Text() != "edit.fill_down" || words(c) != "edit.fill_down" {179 t.Errorf("Up Up: %q, completions %s", h.line.Text(), words(c))180 }181 c.Key(down)182 c.Key(down)183 if h.line.Text() != "" {184 t.Errorf("Down Down: %q", h.line.Text())185 }186 typeText(c, "e")187 c.Key(up)188 c.Key(up)189 if h.line.Text() != "edit.fill_down" {190 t.Errorf("e Up Up: %q", h.line.Text())191 }192}