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 / pivot_test.go
pivot_test.go 8.2K
1package ui23import (4 "strings"5 "testing"67 "src.rest/cat/012/internal/sheet"8 "src.rest/cat/012/internal/ui/filterpick"9)1011// salesTable types a table of sales and leaves A1 active.12func salesTable(t *testing.T, m *Model) {13 t.Helper()14 press(t, m, "Region", "<tab>", "Product", "<tab>", "Units", "<enter>")15 press(t, m, "East", "<tab>", "Pens", "<tab>", "10", "<enter>")16 press(t, m, "West", "<tab>", "Ink", "<tab>", "4", "<enter>")17 press(t, m, "East", "<tab>", "Ink", "<tab>", "6", "<enter>")18 press(t, m, "<ctrl+home>")19}2021// shows is what the cell at a shows on the sheet shown.22func shows(m *Model, a string) string { return m.sheet.ShownText(addr(a)) }2324func TestPivotCreateAndEdit(t *testing.T) {25 m := wideModel()26 salesTable(t, m)27 m.runCommand("data.pivot")28 if m.sheet.Name() != "Pivot Table 1" || m.indicator() != "PIVOT" {29 t.Fatalf("on %q in %s", m.sheet.Name(), m.indicator())30 }31 if s := screen(m); !strings.Contains(s, "Pivot table") || !strings.Contains(s, "Data Sheet1!A1:C4") {32 t.Fatalf("editor not shown:\n%s", s)33 }34 // Space on Rows picks a field to add.35 press(t, m, "<space>")36 if !strings.Contains(screen(m), "Add to Rows") {37 t.Fatalf("no field picker:\n%s", screen(m))38 }39 press(t, m, "reg", "<enter>")40 if m.indicator() != "PIVOT" || shows(m, "A1") != "Region" || shows(m, "A2") != "East" {41 t.Fatalf("after adding Region: %s, A1 %q A2 %q", m.indicator(), shows(m, "A1"), shows(m, "A2"))42 }43 // Down to Values, add Units: summed, as it holds numbers.44 press(t, m, "<down>", "<down>", "<space>", "units", "<enter>")45 if shows(m, "B1") != "SUM of Units" || shows(m, "B2") != "16" || shows(m, "B4") != "20" {46 t.Fatalf("values: %q %q %q", shows(m, "B1"), shows(m, "B2"), shows(m, "B4"))47 }48 if !strings.Contains(screen(m), "‹ SUM ›") || !strings.Contains(line(m, m.height-1), "summary") {49 t.Errorf("value line or hints missing:\n%s", screen(m))50 }51 press(t, m, "<left>")52 if shows(m, "B1") != "MIN of Units" || shows(m, "B2") != "6" {53 t.Errorf("after Left: %q %q", shows(m, "B1"), shows(m, "B2"))54 }55 press(t, m, "<right>", "s", "s", "s")56 if shows(m, "B2") != "80.00%" {57 t.Errorf("show as %% of grand total: %q", shows(m, "B2"))58 }59 // Enter keeps it all; each change is a step of its own.60 press(t, m, "<enter>")61 if m.mode != modeReady {62 t.Fatalf("mode %v after Enter", m.mode)63 }64 press(t, m, "<ctrl+z>", "<ctrl+z>", "<ctrl+z>")65 if shows(m, "B2") != "16" || !strings.Contains(line(m, contextLine), "Undid: show as") {66 t.Errorf("undo: %q, %q", shows(m, "B2"), line(m, contextLine))67 }68 // Data > Edit pivot table opens it again.69 m.runCommand("data.pivot_edit")70 if m.indicator() != "PIVOT" {71 t.Errorf("edit reopened %s", m.indicator())72 }73}7475func TestPivotEditorEscCancelsNew(t *testing.T) {76 m := wideModel()77 salesTable(t, m)78 m.runCommand("data.pivot")79 press(t, m, "<space>", "prod", "<enter>")80 press(t, m, "<esc>")81 if m.book().Len() != 1 || m.sheet.Name() != "Sheet1" || m.mode != modeReady {82 t.Errorf("after Esc: %d sheets, on %s, mode %v", m.book().Len(), m.sheet.Name(), m.mode)83 }84 // Esc in the field picker goes back to the editor.85 m.runCommand("data.pivot")86 press(t, m, "<space>", "<esc>")87 if m.indicator() != "PIVOT" {88 t.Errorf("Esc in the picker left %s", m.indicator())89 }90 // Nothing to summarize far from the data.91 press(t, m, "<esc>", "<f5>", "H20", "<enter>")92 m.runCommand("data.pivot")93 if m.book().Len() != 1 || !strings.Contains(line(m, contextLine), "Select the data") {94 t.Errorf("pivoted nothing: %q", line(m, contextLine))95 }96}9798func TestPivotRefusesEditing(t *testing.T) {99 m := wideModel()100 salesTable(t, m)101 m.runCommand("data.pivot")102 press(t, m, "<space>", "reg", "<enter>", "<enter>")103 press(t, m, "<down>")104 for _, keys := range [][]string{{"x"}, {"<delete>"}, {"<enter>"}, {"<ctrl+alt+=>"}, {"<ctrl+b>"}, {"<ctrl+d>"}} {105 press(t, m, keys...)106 if m.mode != modeReady || !strings.Contains(line(m, contextLine), "Pivot table results can't be edited") {107 t.Errorf("%v: mode %v, %q", keys, m.mode, line(m, contextLine))108 }109 press(t, m, "<esc>")110 }111 if shows(m, "A2") != "East" || m.sheet.Cell(addr("A2")).Style.Bold {112 t.Errorf("A2 changed: %q", shows(m, "A2"))113 }114 // Copying works, and pastes values; beside the results is editable.115 press(t, m, "<ctrl+c>", "<right>", "<right>", "<ctrl+v>")116 if input(m, "C2") != "East" {117 t.Errorf("pasted %q", input(m, "C2"))118 }119 press(t, m, "note", "<enter>")120 if input(m, "C2") != "note" {121 t.Errorf("typed beside: %q", input(m, "C2"))122 }123}124125func TestFrequencyTable(t *testing.T) {126 m := wideModel()127 salesTable(t, m)128 press(t, m, "<right>", "<down>", "<alt+F>")129 if m.sheet.Name() != "Frequency of Product" {130 t.Fatalf("on %q, %q", m.sheet.Name(), line(m, contextLine))131 }132 want := [][]string{{"Product", "Count", "Percent"}, {"Ink", "2", "66.67%"}, {"Pens", "1", "33.33%"}, {"Grand Total", "3", "100.00%"}}133 for r, row := range want {134 for c, w := range row {135 if got := m.sheet.ShownText(sheet.Addr{Col: c, Row: r}); got != w {136 t.Errorf("%s = %q, want %q", sheet.Addr{Col: c, Row: r}, got, w)137 }138 }139 }140 if !strings.Contains(line(m, contextLine), "Counted Product: 2 distinct values") {141 t.Errorf("note %q", line(m, contextLine))142 }143 // Live: a change to the data recounts.144 src := m.book().Sheet(0)145 src.Set(addr("B4"), "Pens")146 if got := shows(m, "A2"); got != "Pens" {147 t.Errorf("after an edit, A2 = %q", got)148 }149}150151func TestPivotFilterAndSource(t *testing.T) {152 m := wideModel()153 salesTable(t, m)154 m.runCommand("data.pivot")155 press(t, m, "<space>", "prod", "<enter>")156 press(t, m, "<down>", "<down>", "<space>", "units", "<enter>")157 // Filters: add Region, then uncheck West in its values.158 press(t, m, "<down>", "<space>", "reg", "<enter>", "<space>")159 if _, ok := m.overlay.(*filterpick.Picker); !ok || !strings.Contains(screen(m), "Filter Region") {160 t.Fatalf("no values picker:\n%s", screen(m))161 }162 press(t, m, "<down>", "<down>", "<space>", "<enter>")163 if m.indicator() != "PIVOT" || shows(m, "B2") != "6" || shows(m, "A4") != "Grand Total" {164 t.Errorf("filtered: %s, B2 %q, A4 %q", m.indicator(), shows(m, "B2"), shows(m, "A4"))165 }166 if !strings.Contains(screen(m), "1 hidden") {167 t.Errorf("filter line:\n%s", screen(m))168 }169 // The data range is pointed at on its sheet.170 press(t, m, "<home>", "<space>")171 if m.mode != modePrompt || m.sheet.Name() != "Sheet1" {172 t.Fatalf("mode %v on %s", m.mode, m.sheet.Name())173 }174 press(t, m, "<shift+up>", "<enter>")175 p, _ := m.sheet.Pivot()176 if m.sheet.Name() != "Pivot Table 1" || m.indicator() != "PIVOT" || p.Range.String() != "A1:C3" {177 t.Errorf("after pointing: %s %s %s", m.sheet.Name(), m.indicator(), p.Range)178 }179}180181func TestPivotRenameValueAndColumnSubtotals(t *testing.T) {182 m := wideModel()183 salesTable(t, m)184 m.runCommand("data.pivot")185 // Columns: Region, then Product; Values: Units.186 press(t, m, "<down>", "<space>", "reg", "<enter>")187 press(t, m, "a", "prod", "<enter>")188 press(t, m, "<down>", "<space>", "units", "<enter>")189 if shows(m, "D1") != "East Total" || shows(m, "F1") != "West Total" || shows(m, "G1") != "Grand Total" {190 t.Fatalf("headers %q %q %q\n%s", shows(m, "D1"), shows(m, "F1"), shows(m, "G1"), screen(m))191 }192 // R renames the value, on the context line.193 press(t, m, "r")194 if m.mode != modePrompt || !strings.Contains(line(m, contextLine), "SUM of Units") {195 t.Fatalf("mode %v, %q", m.mode, line(m, contextLine))196 }197 press(t, m, "Units sold", "<enter>")198 if m.indicator() != "PIVOT" || shows(m, "B3") != "Units sold" || !strings.Contains(screen(m), "Units sold") {199 t.Fatalf("renamed: %s, B3 %q\n%s", m.indicator(), shows(m, "B3"), screen(m))200 }201 // A new summary keeps the name; Esc on the prompt changes nothing.202 press(t, m, "<left>", "r", "<esc>")203 if m.indicator() != "PIVOT" || shows(m, "B3") != "Units sold" {204 t.Errorf("after Left and Esc: %s, %q", m.indicator(), shows(m, "B3"))205 }206 // An empty name goes back to Sheets' own.207 press(t, m, "r", " ")208 press(t, m, "<enter>")209 if shows(m, "B3") != "MIN of Units" {210 t.Errorf("cleared name: %q", shows(m, "B3"))211 }212 press(t, m, "<enter>", "<ctrl+z>")213 if shows(m, "B3") != "Units sold" {214 t.Errorf("undo: %q", shows(m, "B3"))215 }216}217218func TestPivotMenus(t *testing.T) {219 m := wideModel()220 press(t, m, "<alt+d>")221 s := screen(m)222 for _, want := range []string{"Pivot table", "Edit pivot table", "Frequency table (column stats)"} {223 if !strings.Contains(s, want) {224 t.Errorf("Data menu lacks %q:\n%s", want, s)225 }226 }227}