cat / 012
clone

a spreadsheet for the terminal: lotus 1-2-3 looks, google sheets keys and formulas, one go binary

main
012 / internal / ui / tables_test.go
tables_test.go 7.0K
1package ui23import (4 "strings"5 "testing"6)78// unitsModel is a model whose sheet holds Region, Units and Amount in9// A1:C4, the cursor on B2.10func unitsModel(t *testing.T) *Model {11 t.Helper()12 m := tallModel()13 m.Update(teaSize(100, 30))14 for a, v := range map[string]string{15 "A1": "Region", "B1": "Units", "C1": "Amount",16 "A2": "North", "B2": "2", "C2": "10",17 "A3": "South", "B3": "3", "C3": "20",18 "A4": "East", "B4": "5", "C4": "30",19 } {20 m.sheet.Set(addr(a), v)21 }22 m.selectRect(rectOf("B2"))23 return m24}2526func TestConvertToTable(t *testing.T) {27 m := unitsModel(t)28 press(t, m, "<ctrl+alt+t>")29 if l := line(m, contextLine); !strings.HasPrefix(l, "Name for the table of A1:C4: Table1") {30 t.Fatalf("prompt %q", l)31 }32 press(t, m, "Sales", "<enter>")33 tb, ok := m.sheet.TableAt(addr("B2"))34 if !ok || tb.Name != "Sales" || tb.Range != rectOf("A1:C4") {35 t.Fatalf("table %+v", tb)36 }37 if l := line(m, contextLine); !strings.Contains(l, "=SUM(Sales[Amount])") {38 t.Errorf("note %q", l)39 }40 if l := line(m, formulaLine); !strings.HasPrefix(l, " Sales") {41 t.Errorf("name box %q", l)42 }43 if m.available("table.create") {44 t.Error("Convert to table offered inside a table")45 }46 // On a table's cell the context line names the table and column.47 press(t, m, "<esc>", "<right>")48 if l := line(m, contextLine); !strings.Contains(l, "Table Sales, column Units: Sales[Units] in formulas") {49 t.Errorf("context %q", l)50 }51 // Typing below it grows it; undo takes the row back out.52 press(t, m, "<ctrl+down>", "<down>", "40", "<enter>")53 if tb, _ := m.sheet.TableAt(addr("C5")); tb.Range != rectOf("A1:C5") {54 t.Errorf("grew to %v", tb.Range)55 }56 press(t, m, "<ctrl+z>")57 if _, ok := m.sheet.TableAt(addr("C5")); ok {58 t.Error("undo kept the row")59 }60}6162func TestTableCommands(t *testing.T) {63 m := unitsModel(t)64 m.sheet.CreateTable("Sales", rectOf("A1:C4"))65 m.sheet.Set(addr("E1"), "=SUM(Sales[Amount])")66 m.runCommand("table.rename")67 press(t, m, "Revenue", "<enter>")68 if input(m, "E1") != "=SUM(Revenue[Amount])" {69 t.Errorf("renamed formula %q", input(m, "E1"))70 }71 m.runCommand("table.resize")72 if l := line(m, contextLine); !strings.HasPrefix(l, "Range for Revenue, header row first: A1:C4") {73 t.Fatalf("resize prompt %q", l)74 }75 press(t, m, "<shift+left>", "<enter>")76 if tb, _ := m.sheet.TableAt(addr("A1")); tb.Range != rectOf("A1:B4") {77 t.Errorf("resized to %v", tb.Range)78 }79 wantShown(t, m, "E1", "#REF!")80 press(t, m, "<ctrl+z>")81 for id, want := range map[string]bool{"table.banded": false, "table.header": true} {82 if c := commands[id]; !c.checked(m) != !want {83 t.Errorf("%s checked %v", id, c.checked(m))84 }85 m.runCommand(id)86 if commands[id].checked(m) == want {87 t.Errorf("%s didn't toggle", id)88 }89 }90 m.runCommand("table.remove")91 if m.sheet.HasTables() || input(m, "E1") != "=SUM($C$2:$C$4)" {92 t.Errorf("removed: %v %q", m.sheet.HasTables(), input(m, "E1"))93 }94 if m.available("table.rename") {95 t.Error("table commands offered outside a table")96 }97}9899// Filters and sorts started in a table take its range, header kept.100func TestTableScopesFilterAndSort(t *testing.T) {101 m := unitsModel(t)102 m.sheet.Set(addr("A6"), "apart")103 m.sheet.CreateTable("Sales", rectOf("A1:C5"))104 m.selectRect(rectOf("C3"))105 m.runCommand("data.sort_range_za")106 if input(m, "C2") != "30" || input(m, "C1") != "Amount" {107 t.Errorf("sorted: C1 %q C2 %q", input(m, "C1"), input(m, "C2"))108 }109 m.runCommand("data.filter")110 if r, _ := m.sheet.FilterRange(); r != rectOf("A1:C5") {111 t.Errorf("filter on %v", r)112 }113}114115func TestTablesPicker(t *testing.T) {116 m := unitsModel(t)117 m.sheet.CreateTable("Sales", rectOf("A1:C4"))118 m.sheet.Set(addr("F1"), "x")119 m.sheet.CreateTable("Other", rectOf("F1:G3"))120 m.runCommand("data.tables")121 scr := screen(m)122 for _, want := range []string{"Tables", "Sales", "A1:C4", "Other", "F1:G3"} {123 if !strings.Contains(scr, want) {124 t.Errorf("missing %q in\n%s", want, scr)125 }126 }127 press(t, m, "oth", "<enter>")128 if m.selection() != rectOf("F1:G3") {129 t.Errorf("went to %v", m.selection())130 }131 press(t, m, "<esc>", "<f5>", "sales", "<enter>")132 if m.selection() != rectOf("A1:C4") {133 t.Errorf("Go to a table: %v", m.selection())134 }135 m.runCommand("data.tables")136 press(t, m, "sales", "<ctrl+d>")137 if _, _, ok := m.book().Table("Sales"); ok {138 t.Error("not removed")139 }140 if st := line(m, m.height-1); !strings.Contains(st, "Removed the table Sales") {141 t.Errorf("status %q", st)142 }143 press(t, m, "<esc>")144}145146func TestTableSuggestions(t *testing.T) {147 m := unitsModel(t)148 m.sheet.CreateTable("Sales", rectOf("A1:C4"))149 m.selectRect(rectOf("E1"))150 press(t, m, "=SUM(sal")151 list, _ := m.entry.assist.Shown(m.host())152 if len(list) == 0 || list[0].Name != "Sales" || !strings.Contains(list[0].Detail, "A1:C4") {153 t.Fatalf("suggestions %v", list)154 }155 press(t, m, "<tab>", "[am")156 if list, _ = m.entry.assist.Shown(m.host()); len(list) != 1 || list[0].Name != "Amount" {157 t.Fatalf("columns %v", list)158 }159 if ctx := line(m, contextLine); !strings.HasPrefix(ctx, "Sales[Region, Units, Amount]") {160 t.Errorf("context %q", ctx)161 }162 press(t, m, "<tab>")163 if m.line.Text() != "=SUM(Sales[Amount]" {164 t.Fatalf("accepted %q", m.line.Text())165 }166 press(t, m, ")", "<enter>")167 wantShown(t, m, "E1", "60")168 // Items after #, and a column after @.169 m.selectRect(rectOf("D2"))170 press(t, m, "=Sales[#h")171 if list, _ = m.entry.assist.Shown(m.host()); len(list) != 1 || list[0].Name != "#Headers" {172 t.Fatalf("items %v", list)173 }174 press(t, m, "<esc>", "<esc>", "=Sales[@u", "<tab>", "<enter>")175 if input(m, "D2") != "=Sales[@Units]" {176 t.Errorf("this row %q", input(m, "D2"))177 }178 wantShown(t, m, "D2", "2")179}180181// Scripts answer the table commands' questions, as a recording writes182// them.183func TestTableCommandsInScripts(t *testing.T) {184 m := unitsModel(t)185 script(t, m, `186select("A1:C4")187run("table.create", answer="Sales")188select("B2")189run("table.rename", answer="Revenue")190run("table.resize", answer="A1:B4")191run("table.banded")192`)193 if m.warn != "" {194 t.Fatal(m.warn)195 }196 tb, ok := m.sheet.TableAt(addr("A2"))197 if !ok || tb.Name != "Revenue" || tb.Range != rectOf("A1:B4") || !tb.Banded {198 t.Errorf("table %+v", tb)199 }200}201202// A table's header row reads without color: bold and underlined, and203// the context line names the table on its cells. Its bands are204// decoration, with no state to read.205func TestMonochromeTable(t *testing.T) {206 m := newModel()207 m.sheet.Set(addr("B1"), "Item")208 m.sheet.Set(addr("B2"), "Rent")209 if err := m.sheet.CreateTable("Costs", rectOf("B1:B3")); err != nil {210 t.Fatal(err)211 }212 if c := cellAt(m, addr("B1")); !every(c, true, func(c monoCell) bool { return c.bold && c.underline == "1" }) {213 t.Errorf("a table's header: %+v", c)214 }215 if c := cellAt(m, addr("B2")); !every(c, true, plain) {216 t.Errorf("a table's first row: %+v", c)217 }218 m.cur = addr("B2")219 if l := cellText(monoLine(m, contextLine)); !strings.Contains(l, "Table Costs, column Item") {220 t.Errorf("the table isn't named: %q", l)221 }222}223224// wantShown checks the value the cell at a shows.225func wantShown(t *testing.T, m *Model, a, want string) {226 t.Helper()227 if got := m.sheet.Value(addr(a)).String(); got != want {228 t.Errorf("%s = %q, want %q", a, got, want)229 }230}