1package sheet23import (4 "maps"5 "strings"6 "testing"7)89func TestInsertDeleteRows(t *testing.T) {10 base := map[string]string{11 "A1": "1", "A2": "2", "A3": "3", "A4": "4",12 "B1": "=SUM(A1:A4)", "B2": "=A3*2", "B3": "=$A$4", "B4": "=SUM(A3:A4)",13 }14 tests := []struct {15 name string16 op func(s *Sheet)17 want map[string]string18 vals map[string]float6419 }{20 {"insert inside a range", func(s *Sheet) { s.InsertRows(2, 1) }, map[string]string{21 "A1": "1", "A2": "2", "A4": "3", "A5": "4",22 "B1": "=SUM(A1:A5)", "B2": "=A4*2", "B4": "=$A$5", "B5": "=SUM(A4:A5)",23 }, map[string]float64{"B1": 10, "B2": 6, "B4": 4}},24 {"delete a referenced row", func(s *Sheet) { s.DeleteRows(2, 1) }, map[string]string{25 "A1": "1", "A2": "2", "A3": "4",26 "B1": "=SUM(A1:A3)", "B2": "=#REF!*2", "B3": "=SUM(A3:A3)",27 }, map[string]float64{"B1": 7, "B3": 4}},28 {"delete every row of a range", func(s *Sheet) { s.DeleteRows(2, 2) }, map[string]string{29 "A1": "1", "A2": "2", "B1": "=SUM(A1:A2)", "B2": "=#REF!*2",30 }, map[string]float64{"B1": 3}},31 }32 for _, tt := range tests {33 s := sheetOf(t, base)34 tt.op(s)35 if got := inputs(s); !maps.Equal(got, tt.want) {36 t.Errorf("%s:\n got %v\nwant %v", tt.name, got, tt.want)37 }38 for a, v := range tt.vals {39 if got := s.Value(at(a)); got.Kind != Number || got.Num != v {40 t.Errorf("%s: %s = %+v, want %v", tt.name, a, got, v)41 }42 }43 for a, in := range tt.want {44 if strings.Contains(in, "#REF!") && s.Value(at(a)) != ErrRef {45 t.Errorf("%s: %s = %+v, want #REF!", tt.name, a, s.Value(at(a)))46 }47 }48 // Recalc still follows the rewritten references.49 s.Set(at("A1"), "101")50 if got := s.Value(at("B1")).Num; got != tt.vals["B1"]+100 {51 t.Errorf("%s: B1 after edit = %v", tt.name, got)52 }53 // Undo restores the sheet exactly.54 s.Undo()55 s.Undo()56 if got := inputs(s); !maps.Equal(got, base) {57 t.Errorf("%s: undo left %v", tt.name, got)58 }59 }60}6162func TestInsertDeleteColsShiftWidths(t *testing.T) {63 s := sheetOf(t, map[string]string{"A1": "1", "B1": "2", "C1": "=A1+B1"})64 s.SetColWidth(1, 15)65 if err := s.InsertCols(1, 2); err != nil {66 t.Fatal(err)67 }68 if got := inputs(s); got["E1"] != "=A1+D1" || got["D1"] != "2" || s.ColWidth(3) != 15 || s.ColWidth(1) != DefaultWidth {69 t.Errorf("after insert: %v, width D %d", got, s.ColWidth(3))70 }71 s.DeleteCols(0, 1)72 if got := inputs(s)["D1"]; got != "=#REF!+C1" || s.ColWidth(2) != 15 {73 t.Errorf("after delete: D1 %q, width C %d", got, s.ColWidth(2))74 }75 c1, _ := s.Undo()76 c2, _ := s.Undo()77 if c1.Label != "delete 1 column" || c2.Label != "insert 2 columns" || c2.Focus != colRect(1, 2) {78 t.Errorf("undid %+v then %+v", c1, c2)79 }80 if got := inputs(s)["C1"]; got != "=A1+B1" || s.ColWidth(1) != 15 {81 t.Errorf("after undo: C1 %q width B %d", got, s.ColWidth(1))82 }83}8485func TestInsertRefusesToPushDataOff(t *testing.T) {86 s := sheetOf(t, map[string]string{"A1048576": "last"})87 if err := s.InsertRows(0, 1); err != ErrPushedOff {88 t.Errorf("err = %v", err)89 }90 if err := s.InsertRows(1048575, 1); err != ErrPushedOff {91 t.Errorf("err = %v", err)92 }93 if err := s.InsertRows(1048576-3, 1); err != ErrPushedOff {94 t.Errorf("err = %v", err)95 }96 s = sheetOf(t, map[string]string{"A1048574": "x", "B1": "=A1048576"})97 if err := s.InsertRows(0, 1); err != nil {98 t.Fatal(err)99 }100 if got := inputs(s)["B2"]; got != "=#REF!" {101 t.Errorf("reference pushed off the sheet = %q", got)102 }103}104105func TestCopyPaste(t *testing.T) {106 tests := []struct {107 name string108 src string109 dst string110 values bool111 want map[string]string112 wrote string113 }{114 {"relative shift", "C1", "D3", false, map[string]string{"D3": "=C3+$A$1"}, "D3"},115 {"off the sheet", "C1", "A1", false, map[string]string{"A1": "=#REF!+$A$1"}, "A1"},116 {"tiles an exact multiple", "A1:A2", "E1:F4", false, map[string]string{117 "E1": "1", "E2": "2", "E3": "1", "E4": "2", "F1": "1", "F2": "2", "F3": "1", "F4": "2",118 }, "E1:F4"},119 {"pastes once otherwise", "A1:A2", "E1:E3", false, map[string]string{"E1": "1", "E2": "2"}, "E1:E2"},120 {"values only", "B1:C1", "E5", true, map[string]string{"E5": "'12", "F5": "13"}, "E5:F5"},121 }122 for _, tt := range tests {123 s := sheetOf(t, map[string]string{"A1": "1", "A2": "2", "B1": "'12", "C1": "=B1+$A$1"})124 src, _ := ParseRange(tt.src)125 dst, _ := ParseRange(tt.dst)126 before := inputs(s)127 wrote, err := s.Paste(s.Copy(src), dst, tt.values)128 if err != nil || wrote.String() != tt.wrote {129 t.Errorf("%s: wrote %v, %v", tt.name, wrote, err)130 }131 got := inputs(s)132 for a, in := range tt.want {133 if got[a] != in {134 t.Errorf("%s: %s = %q, want %q", tt.name, a, got[a], in)135 }136 }137 if len(got) != len(uniqueKeys(before, tt.want)) {138 t.Errorf("%s: unexpected cells %v", tt.name, got)139 }140 s.Undo()141 if !maps.Equal(inputs(s), before) {142 t.Errorf("%s: undo left %v", tt.name, inputs(s))143 }144 }145}146147func uniqueKeys(ms ...map[string]string) map[string]bool {148 out := map[string]bool{}149 for _, m := range ms {150 for k := range m {151 out[k] = true152 }153 }154 return out155}156157func TestPasteBlanksAndEdge(t *testing.T) {158 s := sheetOf(t, map[string]string{"A1": "1", "C1": "keep", "D1": "gone"})159 s.Paste(s.Copy(NewRect(at("A1"), at("B1"))), NewRect(at("C1"), at("C1")), false)160 if got := inputs(s); got["C1"] != "1" || got["D1"] != "" {161 t.Errorf("blank source cells should clear the destination: %v", got)162 }163 if _, err := s.Paste(s.Copy(NewRect(at("A1"), at("B1"))), NewRect(at("XFD1"), at("XFD1")), false); err != ErrPasteEdge {164 t.Errorf("err = %v", err)165 }166}167168func TestMove(t *testing.T) {169 s := sheetOf(t, map[string]string{170 "A1": "5", "A2": "=A1*2", "B1": "=A1+A2", "C1": "=SUM(A1:A2)", "C2": "=SUM(A1:A3)", "D1": "old", "E1": "=D1",171 })172 dst, err := s.Move(NewRect(at("A1"), at("A2")), at("D1"))173 if err != nil || dst.String() != "D1:D2" {174 t.Fatalf("Move = %v, %v", dst, err)175 }176 want := map[string]string{177 "D1": "5", "D2": "=D1*2", "B1": "=D1+D2", "C1": "=SUM(D1:D2)",178 "C2": "=SUM(A1:A3)", // not wholly inside the moved cells: unchanged179 "E1": "=#REF!", // pointed at a cell the move overwrote180 }181 if got := inputs(s); !maps.Equal(got, want) {182 t.Errorf("got %v\nwant %v", got, want)183 }184 if got := s.Value(at("B1")).Num; got != 15 {185 t.Errorf("B1 = %v", got)186 }187 s.Undo()188 if got := inputs(s); got["A2"] != "=A1*2" || got["D1"] != "old" || got["E1"] != "=D1" {189 t.Errorf("undo: %v", got)190 }191}192193func TestFill(t *testing.T) {194 s := sheetOf(t, map[string]string{"A1": "1", "B1": "=A1*2", "A2": "2", "A3": "3"})195 if _, err := s.FillDown(NewRect(at("B1"), at("B3"))); err != nil {196 t.Fatal(err)197 }198 if got := inputs(s); got["B2"] != "=A2*2" || got["B3"] != "=A3*2" || s.Value(at("B3")).Num != 6 {199 t.Errorf("fill down: %v", got)200 }201 s.FillRight(NewRect(at("B1"), at("C2")))202 if got := inputs(s); got["C1"] != "=B1*2" || got["C2"] != "=B2*2" {203 t.Errorf("fill right: %v", got)204 }205 // A single cell fills from its neighbor.206 s.FillDown(NewRect(at("B4"), at("B4")))207 if got := inputs(s)["B4"]; got != "=A4*2" {208 t.Errorf("single-cell fill down: %q", got)209 }210 if err := s.FillEntry(NewRect(at("D1"), at("E2")), at("D1"), "=A1+$A$1"); err != nil {211 t.Fatal(err)212 }213 want := map[string]string{"D1": "=A1+$A$1", "E1": "=B1+$A$1", "D2": "=A2+$A$1", "E2": "=B2+$A$1"}214 for a, in := range want {215 if got := inputs(s)[a]; got != in {216 t.Errorf("fill entry %s = %q, want %q", a, got, in)217 }218 }219 s.Undo()220 if got := inputs(s)["D1"]; got != "" {221 t.Errorf("fill entry is one undo step, D1 = %q", got)222 }223}