1package sheet23import (4 "reflect"5 "strings"6 "testing"7)89// sheetOf builds a sheet from cell inputs, without undo history.10func sheetOf(t *testing.T, cells map[string]string) *Sheet {11 t.Helper()12 s := New()13 for a, in := range cells {14 if err := s.put(at(a), in); err != nil {15 t.Fatalf("%s: %v", a, err)16 }17 }18 s.RecalcAll()19 return s20}2122// inputs returns every cell's input by address.23func inputs(s *Sheet) map[string]string {24 out := map[string]string{}25 for a, c := range s.cells.all() {26 out[a.String()] = c.Input27 }28 return out29}3031func TestUndoRedo(t *testing.T) {32 s := New()33 s.Set(at("A1"), "1")34 s.Set(at("A2"), "=A1*10")35 s.Set(at("A1"), "2")36 s.EraseRange(NewRect(at("A1"), at("A2")))37 if s.Len() != 0 || s.StateID() != 4 {38 t.Fatalf("len %d state %d", s.Len(), s.StateID())39 }40 c, ok := s.Undo()41 if !ok || c != (Change{Label: "clear A1:A2", Focus: NewRect(at("A1"), at("A2")), Sheet: s}) || s.Value(at("A2")).Num != 20 {42 t.Fatalf("undo erase: %v %v A2=%+v", c, ok, s.Value(at("A2")))43 }44 s.Undo()45 if s.Value(at("A2")).Num != 10 || s.StateID() != 2 {46 t.Errorf("undo set: A2=%+v state %d", s.Value(at("A2")), s.StateID())47 }48 s.Redo()49 if s.Value(at("A2")).Num != 20 || s.StateID() != 3 {50 t.Errorf("redo: A2=%+v state %d", s.Value(at("A2")), s.StateID())51 }52 // A new change drops the redo stack.53 s.Set(at("B1"), "x")54 if s.CanRedo() {55 t.Error("redo survived a new change")56 }57 for s.CanUndo() {58 s.Undo()59 }60 if s.Len() != 0 || s.StateID() != 0 {61 t.Errorf("undo all: %v", inputs(s))62 }63 // A parse error changes nothing and adds no step.64 s.Set(at("A1"), "=1+")65 if s.CanUndo() {66 t.Error("failed Set added a step")67 }68}6970func TestUndoKeepsWholeCells(t *testing.T) {71 s := New()72 s.Set(at("A1"), "=1+1")73 orig := s.Cell(at("A1"))74 s.Set(at("A1"), "x")75 s.Undo()76 got := s.Cell(at("A1"))77 if got == orig || !reflect.DeepEqual(*got, *orig) {78 t.Errorf("restored %+v, want a copy of %+v", got, orig)79 }80}8182func TestBatchGroupsAndHistoryCap(t *testing.T) {83 s := New()84 s.Batch(Change{Label: "type", Focus: NewRect(at("A1"), at("A3"))}, func() error {85 for i := range 3 {86 s.Set(Addr{Row: i}, "1")87 }88 s.SetColWidth(0, 20)89 return nil90 })91 s.Undo()92 if s.Len() != 0 || s.ColWidth(0) != DefaultWidth || s.CanUndo() {93 t.Errorf("batch undo left %v width %d", inputs(s), s.ColWidth(0))94 }95 for i := range MaxUndo + 10 {96 s.Set(at("A1"), strings.Repeat("x", i+1))97 }98 n := 099 for s.CanUndo() {100 s.Undo()101 n++102 }103 if n != MaxUndo || inputs(s)["A1"] != strings.Repeat("x", 10) {104 t.Errorf("undid %d steps, A1 %q", n, inputs(s)["A1"])105 }106}107108func TestWidthPreviewMerges(t *testing.T) {109 s := New()110 s.Set(at("A1"), "1")111 // A live preview over two columns, then the final value: one step.112 for w := 11; w <= 13; w++ {113 s.SetColWidth(0, w)114 s.SetColWidth(1, w)115 }116 s.Undo()117 if s.ColWidth(0) != DefaultWidth || s.ColWidth(1) != DefaultWidth || s.Len() != 1 {118 t.Errorf("widths %d %d len %d", s.ColWidth(0), s.ColWidth(1), s.Len())119 }120 // A preview that is cancelled leaves no step at all.121 s.Redo()122 id := s.StateID()123 s.Seal()124 s.SetColWidth(0, 20)125 s.SetColWidth(0, 13)126 if s.StateID() != id {127 t.Errorf("cancelled preview left a step")128 }129 // Separate actions are separate steps.130 s.Seal()131 s.SetColWidth(2, 5)132 s.Seal()133 s.SetColWidth(3, 5)134 s.Undo()135 if s.ColWidth(2) != 5 || s.ColWidth(3) != DefaultWidth {136 t.Errorf("sealed width changes merged")137 }138}139140func TestLoadIsNotUndoable(t *testing.T) {141 s := sheetOf(t, map[string]string{"A1": "1"})142 s.SetColWidth(0, 12)143 s.RecalcAll()144 if s.CanUndo() || s.StateID() != 0 {145 t.Error("RecalcAll kept history")146 }147}148149// Try takes a change back whole, on its own as Discard does, or inside150// a step another change opened (a macro's run), which goes on and still151// undoes as one.152func TestTryTakesBack(t *testing.T) {153 s := New()154 w := s.Book()155 s.Set(at("A1"), "1")156 kept, err := w.Try(func() error { return s.Set(at("A2"), "2") }, func() bool { return true })157 if kept || err != nil || s.Filled(at("A2")) || s.Value(at("A1")).Num != 1 {158 t.Errorf("alone: kept %v %v, A2 %v", kept, err, s.Value(at("A2")))159 }160 // Taking back nothing leaves the step before alone.161 w.Try(func() error { return nil }, func() bool { return true })162 if s.Value(at("A1")).Num != 1 {163 t.Error("an empty try took back the step before")164 }165 end := w.Begin(Change{Label: "macro", Sheet: s})166 s.Set(at("B1"), "=A1+A3")167 w.Try(func() error { return s.Set(at("A3"), "5") }, func() bool { return s.Value(at("B1")).Num == 6 })168 if s.Filled(at("A3")) {169 t.Error("inside a step: A3 kept")170 }171 kept, _ = w.Try(func() error { return s.Set(at("A4"), "4") }, func() bool { return false })172 if !kept {173 t.Error("inside a step: A4 taken back")174 }175 end()176 if s.Value(at("B1")).Num != 1 || s.Value(at("A4")).Num != 4 {177 t.Errorf("after the step: B1 %v A4 %v", s.Value(at("B1")), s.Value(at("A4")))178 }179 s.Undo()180 if s.Filled(at("B1")) || s.Filled(at("A4")) || s.Value(at("A1")).Num != 1 {181 t.Error("the step didn't undo as one")182 }183}