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 / errbox_test.go
errbox_test.go 4.1K
1package ui23import (4 "os"5 "strings"6 "testing"78 "github.com/charmbracelet/x/ansi"910 "src.rest/cat/012/internal/sheet"11)1213// errorCases are formulas whose errors are explained, short and long:14// arithmetic, unknown names, lookups, a chain through other cells and15// sheets, a cycle, and a function past max-cells over a linked source.16var errorCases = []string{17 "=1/0", `="a"+1`, "=SQRT(-1)", "=nosuchname+1", `=VLOOKUP("x",B20:C21,2,FALSE)`,18 "='Quarterly results by region'!B3", "=nowhere!A1+'Another sheet that is not there at all'!B2",19 "=E1", "=TEXTJOIN(\",\",TRUE,sales[region])", "=SORT(sales[amount])", "=SUM(sales[amount]*2)",20}2122// TestErrorExplainedAt80Columns checks that at 80 columns the context23// line explains every error whole, or says how to read the rest: F1,24// which shows all of it.25func TestErrorExplainedAt80Columns(t *testing.T) {26 t.Chdir(t.TempDir())27 defer sheet.SetMaxCells(0)28 sheet.SetMaxCells(1000)29 m := newModel()30 linkSales(t, m, 3000)31 m.runCommand("sheet.prev")32 press(t, m, "<ctrl+home>")33 s := m.sheet34 long, err := s.Book().AddSheet("Quarterly results by region", -1)35 if err != nil {36 t.Fatal(err)37 }38 long.Set(addr("B3"), "=1/0")39 for c, f := range map[string]string{"E1": "=F1", "F1": "=G1", "G1": "=H1", "H1": "=E1"} {40 s.Set(addr(c), f) // a cycle41 }42 for i, f := range errorCases {43 if err := s.Set(sheet.Addr{Row: i}, f); err != nil {44 t.Fatalf("%s: %v", f, err)45 }46 }47 pumpSources(t, m)48 cut := 049 for i, f := range errorCases {50 m.cur = sheet.Addr{Row: i}51 why := s.ExplainError(m.cur)52 if why == "" {53 t.Errorf("%s: %v unexplained", f, s.Value(m.cur))54 continue55 }56 ctx := line(m, contextLine)57 if strings.Contains(ctx, why) {58 continue59 }60 cut++61 if !strings.Contains(ctx, "…") || !strings.HasSuffix(ctx, "F1 more") {62 t.Errorf("%s: cut without F1: %q (%s)", f, ctx, why)63 continue64 }65 press(t, m, "<f1>")66 box := errorBoxText(m)67 if box != why {68 t.Errorf("%s: F1 shows %q, want %q", f, box, why)69 }70 press(t, m, "<esc>")71 if m.overlay != nil {72 t.Fatalf("%s: Esc left the box open", f)73 }74 }75 if cut == 0 {76 t.Error("no explanation was long enough to cut")77 }78}7980// errorBoxText is the open error box's text, its lines joined.81func errorBoxText(m *Model) string {82 b, ok := m.overlay.(*errorBox)83 if !ok {84 return ""85 }86 var words []string87 for _, l := range b.Layout()[0].Lines[1:] {88 l = strings.Trim(ansi.Strip(l), "│ ")89 if !strings.HasPrefix(l, "└") {90 words = append(words, l)91 }92 }93 return strings.Join(words, " ")94}9596func TestF1ElsewhereShowsShortcuts(t *testing.T) {97 m := newModel()98 press(t, m, "<f1>")99 if m.overlay == nil || strings.Contains(screen(m), " in A1") {100 t.Fatalf("F1 on a blank cell: %T", m.overlay)101 }102 press(t, m, "<esc>", "=1/0", "<enter>", "<up>", "<f1>")103 if box := errorBoxText(m); box != "Division by zero in 1/0" {104 t.Errorf("F1 on #DIV/0!: %q", box)105 }106 press(t, m, "<down>")107 if m.overlay != nil || m.cur != addr("A2") {108 t.Errorf("a key closes the box and does what it does: %T at %v", m.overlay, m.cur)109 }110}111112func TestF1OnAMissingSource(t *testing.T) {113 t.Chdir(t.TempDir())114 m := newModel()115 linkSales(t, m, 300)116 if err := os.Remove("sales.parquet"); err != nil {117 t.Fatal(err)118 }119 m.runCommand("data.source_reload")120 pumpSources(t, m)121 m.note = ""122 if l := line(m, contextLine); !strings.Contains(l, "! sales.parquet: the file isn't there") {123 t.Fatalf("context line %q", l)124 }125 press(t, m, "<f1>")126 if box := errorBoxText(m); box != "! sales.parquet: the file isn't there" {127 t.Errorf("F1 on the tab: %q", box)128 }129}130131func TestCutExplanation(t *testing.T) {132 for _, c := range []struct {133 why string134 room int135 want string136 }{137 {"Raise max-cells to 2,000 to SORT x (it's 1,000). SORT holds what it reads.", 60, "Raise max-cells to 2,000 to SORT x (it's 1,000). …"},138 {"Raise max-cells to 2,000 to SORT x (it's 1,000). SORT holds what it reads.", 30, "Raise max-cells to 2,000 to S…"},139 {"One. Two. Three is long.", 12, "One. Two. …"},140 {"x", 1, ""},141 } {142 if got := cutExplanation(c.why, c.room); got != c.want || ansi.StringWidth(got) > c.room {143 t.Errorf("cut %q to %d: %q, want %q", c.why, c.room, got, c.want)144 }145 }146}