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 / e2e / sheets_test.go
sheets_test.go 4.7K
1package e2e23import (4 "os"5 "path/filepath"6 "strings"7 "testing"89 ghostty "go.mitchellh.com/libghostty"10)1112// summary adds a second sheet to the budget, renames it and fills it with13// formulas that read the budget, pointing into it the way a Sheets user14// clicks the other tab while typing.15func summary(s *session) {16 budget(s)17 s.keys("<shift+f11>")18 s.waitFor("Added Sheet2")19 s.keys("<ctrl+k>", "rename sheet", "<enter>")20 s.waitFor("Rename sheet: Sheet2")21 s.keys("Summary", "<enter>")22 s.waitFor("Renamed Sheet2 to Summary")23 s.keys("Yearly total", "<tab>", "=", "<ctrl+pgup>")24 s.waitForBar("Summary!B1", "=Sheet1!B7")25 s.keys("*12", "<enter>")26 s.keys("Monthly average", "<tab>", "=AVERAGE(", "<ctrl+pgup>", "<up>", "<up>", "<up>", "<up>", "<shift+down>", "<shift+down>", ")", "<enter>")27 s.waitFor("719.46667")28 s.keys("<up>", "<up>", "<right>")29 s.waitForBar("B1", "=Sheet1!B7*12")30}3132// tabX is the screen column of the middle of a tab on the status line.33func tabX(t *testing.T, s *session, name string) int {34 t.Helper()35 l := s.line(int(s.rows) - 1)36 i := strings.Index(l, " "+name+" ")37 if i < 0 {38 t.Fatalf("no tab %q in %q", name, l)39 }40 return len([]rune(l[:i])) + len([]rune(name))/2 + 141}4243func TestSheets(t *testing.T) {44 t.Parallel()45 dir := t.TempDir()46 s := start(t, dir, "book.012")47 summary(s)48 if l := s.line(int(s.rows) - 1); !strings.HasPrefix(l, " Sheet1 Summary +") {49 t.Errorf("tabs %q", l)50 }51 s.waitFor("25900.8")5253 // A change on one sheet reaches formulas on the other.54 s.keys("<ctrl+pgup>")55 s.waitForBar("B5", "96") // where the pointer last was56 s.keys("0", "<enter>")57 s.keys("<alt+right>")58 s.waitFor("24748.8")5960 // Renaming by double-clicking the tab rewrites the formulas.61 x := tabX(t, s, "Sheet1")62 s.click(ghostty.MouseButtonLeft, x, int(s.rows)-1)63 s.click(ghostty.MouseButtonLeft, x, int(s.rows)-1)64 s.waitFor("Rename sheet: Sheet1")65 s.keys("Budget 2026", "<enter>")66 s.keys("<ctrl+pgdown>")67 s.waitForBar("B1", "='Budget 2026'!B7*12")6869 // The tab's menu deletes a sheet; its formulas show #REF! until undo.70 x = tabX(t, s, "Budget 2026")71 s.click(ghostty.MouseButtonRight, x, int(s.rows)-1)72 s.waitFor("Duplicate")73 s.keys("<down>", "<down>", "<enter>")74 s.waitFor("Delete Budget 2026 and its")75 s.keys("<enter>")76 s.waitFor("#REF!")77 s.keys("<ctrl+z>")78 s.waitForName("B6") // back on the restored sheet, where it was79 s.waitFor("2062.4")8081 // Both sheets are saved, and come back.82 s.keys("<ctrl+s>")83 s.eventually("saved", func() bool {84 data, err := os.ReadFile(filepath.Join(dir, "book.012"))85 return err == nil && strings.Contains(string(data), `"version": 4`) && strings.Contains(string(data), `"name": "Summary"`)86 })87 s.keys("<ctrl+q>")88 s.waitExit()8990 // The sheet shown when saved opens first.91 s = start(t, dir, "book.012")92 s.waitFor("2062.4")93 if l := s.line(int(s.rows) - 1); !strings.HasPrefix(l, " Budget 2026 Summary +") {94 t.Errorf("tabs after opening %q", l)95 }96 s.keys("<alt+shift+k>")97 s.waitFor("Go to sheet")98 s.keys("summ", "<enter>")99 s.waitForBar("A1", "Yearly total")100 s.waitFor("24748.8")101}102103// Hiding a sheet takes it out of the tabs and the file keeps it hidden;104// View > Hidden sheets shows it again, and undo hides it once more.105func TestHideSheets(t *testing.T) {106 t.Parallel()107 dir := t.TempDir()108 s := start(t, dir, "book.012")109 hideSummary(s)110 if l := s.line(int(s.rows) - 1); !strings.HasPrefix(l, " Sheet1 +") {111 t.Errorf("tabs %q", l)112 }113 s.keys("<ctrl+s>")114 s.eventually("saved", func() bool {115 data, err := os.ReadFile(filepath.Join(dir, "book.012"))116 return err == nil && strings.Contains(string(data), "\"name\": \"Summary\",\n \"hidden\": true,")117 })118 s.keys("<ctrl+q>")119 s.waitExit()120121 s = start(t, dir, "book.012")122 s.waitFor("Household budget 2026")123 if l := s.line(int(s.rows) - 1); !strings.HasPrefix(l, " Sheet1 +") {124 t.Errorf("tabs after opening %q", l)125 }126 s.keys("<alt+v>", "<down>", "<enter>")127 s.waitFor("Hidden sheets")128 s.keys("<enter>")129 s.waitFor("Unhid Summary")130 s.waitFor("25900.8") // its formulas kept reading Sheet1131 if l := s.line(int(s.rows) - 1); !strings.HasPrefix(l, " Sheet1 Summary +") {132 t.Errorf("tabs after unhiding %q", l)133 }134 s.keys("<ctrl+z>")135 s.waitFor("Undid: show sheet Summary")136 s.waitForBar("A1", "Household budget 2026")137}138139// Typing a sheet's name in a formula offers it; after it, arrows point140// into that sheet.141func TestSheetNameSuggestions(t *testing.T) {142 t.Parallel()143 s := start(t, "")144 summary(s)145 s.keys("<ctrl+pgup>", "<f5>", "D1", "<enter>")146 s.waitForName("D1")147 s.keys("=su")148 s.waitFor("│ Summary!")149 s.keys("<tab>")150 s.waitFor("=Summary!")151 s.keys("<down>", "<up>")152 s.waitFor("POINT")153 s.waitFor("=Summary!B1")154 s.keys("<enter>")155 s.keys("<up>")156 s.waitForBar("D1", "=Summary!B1")157 s.waitFor("25900.8")158}