1package sheet23import (4 "testing"5)67// sharedBook is a workbook with its history shared, as a room's is.8func sharedBook() (*Workbook, *Sheet) {9 s := New()10 w := s.Book()11 w.ShareHistory()12 return w, s13}1415// as makes one step as author a.16func as(w *Workbook, a int, fn func()) {17 w.SetAuthor(a)18 fn()19}2021// Operations are numbered in the order they run, whoever runs them, and22// OpsSince hands them out in that order.23func TestOpsInOrder(t *testing.T) {24 w, s := sharedBook()25 as(w, 1, func() { s.Set(at("A1"), "1") })26 as(w, 2, func() { s.Set(at("B1"), "2") })27 as(w, 1, func() { w.Undo() })28 ops, last := w.OpsSince(0)29 if last != 3 || len(ops) != 3 {30 t.Fatalf("ops %+v, last %d", ops, last)31 }32 want := []struct {33 author int34 kind OpKind35 label string36 }{{1, OpDo, "edit A1"}, {2, OpDo, "edit B1"}, {1, OpUndo, "edit A1"}}37 for i, o := range ops {38 if o.Seq != uint64(i+1) || o.Author != want[i].author || o.Kind != want[i].kind || o.Label != want[i].label {39 t.Errorf("op %d: %+v, want %+v", i, o, want[i])40 }41 }42 if more, _ := w.OpsSince(2); len(more) != 1 || more[0].Kind != OpUndo {43 t.Errorf("since 2: %+v", more)44 }45}4647// Undo takes back only the author's own latest step, from under later48// steps of others that changed other cells; those stay.49func TestUndoIsPerAuthor(t *testing.T) {50 w, s := sharedBook()51 as(w, 1, func() { s.Set(at("A1"), "mine") })52 as(w, 2, func() { s.Set(at("B1"), "theirs") })53 as(w, 2, func() { s.Set(at("B2"), "=A1") })54 before := w.StateID()55 w.SetAuthor(1)56 if w.UndoLabel() != "edit A1" {57 t.Errorf("author 1 would undo %q", w.UndoLabel())58 }59 if _, ok := w.Undo(); !ok {60 t.Fatal("author 1's undo refused")61 }62 if s.Filled(at("A1")) || s.Value(at("B1")).Str != "theirs" {63 t.Errorf("A1 %v, B1 %v", s.Value(at("A1")), s.Value(at("B1")))64 }65 if v := s.Value(at("B2")); v.Str == "mine" {66 t.Errorf("B2, reading A1, is %v", v)67 }68 if w.StateID() == before {69 t.Error("an undo from under others' steps kept the state's ID")70 }71 if w.CanUndo() {72 t.Error("author 1 has nothing left to undo")73 }74 w.SetAuthor(2)75 if !w.CanUndo() || w.UndoLabel() != "edit B2" {76 t.Errorf("author 2 would undo %q", w.UndoLabel())77 }78 // Redo is per author too.79 w.SetAuthor(1)80 if !w.CanRedo() {81 t.Fatal("author 1 can't redo")82 }83 w.Redo()84 if s.Value(at("A1")).Str != "mine" || s.Value(at("B2")).Str != "mine" {85 t.Errorf("after redo A1 %v, B2 %v", s.Value(at("A1")), s.Value(at("B2")))86 }87 w.SetAuthor(2)88 if w.CanRedo() {89 t.Error("author 2 can redo author 1's undo")90 }91}9293// Undo refuses when a later step of someone else's changed what the94// step changed, and says whose; once that step is undone, it goes.95func TestUndoRefusesOverlaps(t *testing.T) {96 w, s := sharedBook()97 as(w, 1, func() { s.Set(at("A1"), "first") })98 as(w, 2, func() { s.Set(at("A1"), "second") })99 w.SetAuthor(1)100 b, blocked := w.UndoBlocked()101 if !blocked || b.Author != 2 || b.Label != "edit A1" {102 t.Fatalf("blocked %v by %+v", blocked, b)103 }104 if _, ok := w.Undo(); ok || s.Value(at("A1")).Str != "second" {105 t.Fatalf("undo went ahead: A1 %v", s.Value(at("A1")))106 }107 as(w, 2, func() { w.Undo() })108 w.SetAuthor(1)109 if _, blocked := w.UndoBlocked(); blocked {110 t.Fatal("still blocked")111 }112 w.Undo()113 if s.Filled(at("A1")) {114 t.Errorf("A1 %v", s.Value(at("A1")))115 }116 // Author 2's redo is now in the way of nothing, and redoes onto117 // the blank cell what it undid.118 w.SetAuthor(2)119 if _, ok := w.Redo(); !ok || s.Value(at("A1")).Str != "second" {120 t.Errorf("author 2's redo: A1 %v", s.Value(at("A1")))121 }122 // Author 1's redo would overwrite it: refused.123 w.SetAuthor(1)124 if b, blocked := w.RedoBlocked(); !blocked || b.Author != 2 {125 t.Errorf("author 1's redo not blocked: %+v", b)126 }127}128129// Inserting or deleting lines conflicts with any other step on the same130// sheet, and with none on another.131func TestUndoRefusesAcrossShifts(t *testing.T) {132 w, s := sharedBook()133 s2, _ := w.AddSheet("S2", 1)134 as(w, 1, func() { s.Set(at("A5"), "x") })135 as(w, 1, func() { s.InsertRows(2, 1) })136 as(w, 2, func() { s2.Set(at("A1"), "elsewhere") })137 w.SetAuthor(1)138 if _, blocked := w.UndoBlocked(); blocked {139 t.Fatal("a step on another sheet blocks the insert")140 }141 as(w, 2, func() { s.Set(at("Z1"), "far away") })142 w.SetAuthor(1)143 if _, blocked := w.UndoBlocked(); !blocked {144 t.Fatal("a step on the shifted sheet doesn't block the insert")145 }146}147148// The sheet list conflicts with everything after it.149func TestUndoRefusesAfterSheetChanges(t *testing.T) {150 w, s := sharedBook()151 as(w, 1, func() { w.AddSheet("S2", 1) })152 as(w, 2, func() { s.Set(at("A1"), "1") })153 w.SetAuthor(1)154 if _, blocked := w.UndoBlocked(); !blocked {155 t.Error("adding a sheet undone past a later step")156 }157}158159// Unshared, undo takes back the latest step whoever made it, as a160// workbook one person edits does.161func TestUndoUnshared(t *testing.T) {162 s := New()163 w := s.Book()164 as(w, 1, func() { s.Set(at("A1"), "1") })165 as(w, 2, func() { s.Set(at("A1"), "2") })166 w.SetAuthor(1)167 w.Undo()168 if s.Value(at("A1")).Num != 1 {169 t.Errorf("A1 %v", s.Value(at("A1")))170 }171}