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 / sheet / regionclash_test.go
regionclash_test.go 5.4K
1package sheet23import "testing"45// Regions meeting each other show the same whichever order their rows6// arrive in, as a file reopened sends them in another order than they7// arrived in (regionclash.go).89// sendBoth is a sheet with outputs a and b at the cells aAt and bAt,10// sent opA and opB in that order, or the other with bFirst.11func sendBoth(t *testing.T, aAt, bAt string, opA, opB LiveOp, bFirst bool) *Sheet {12 t.Helper()13 s := New()14 sent(t, s, "a", aAt)15 sent(t, s, "b", bAt)16 s.Set(at("H1"), "=SUM(nu.a)")17 s.Set(at("H2"), "=SUM(nu.b)")18 ops := []LiveOp{opA, opB}19 if bFirst {20 ops = []LiveOp{opB, opA}21 }22 for _, op := range ops {23 apply(t, s, op)24 }25 return s26}2728// A region's anchor is in the way of another's table, whether or not29// its own rows have arrived.30func TestRegionAnchorBlocksAnother(t *testing.T) {31 opA := LiveOp{Region: "a", Reset: true, Header: liveRow("x", "y"), Rows: []LiveRow{liveRow("1", "2")}}32 opB := LiveOp{Region: "b", Reset: true, Header: liveRow("p", "q"), Rows: []LiveRow{liveRow("3", "4")}}33 for _, bFirst := range []bool{false, true} {34 s := sendBoth(t, "A1", "B2", opA, opB, bFirst)35 wantShown(t, s, map[string]string{"A1": "#REF!", "A2": "", "B2": "p", "C3": "4", "H1": "#REF!", "H2": "7"})36 }37}3839// When two tables need the same cells, the region whose anchor comes40// first, row by row, gets them, whichever arrived first.41func TestRegionFirstAnchorWins(t *testing.T) {42 // a needs B1:C3 and b A2:C2: neither holds the other's anchor.43 opA := LiveOp{Region: "a", Reset: true, Header: liveRow("x", "y"), Rows: []LiveRow{liveRow("1", "2"), liveRow("3", "4")}}44 opB := LiveOp{Region: "b", Reset: true, Header: liveRow("5", "6", "7")}45 for _, bFirst := range []bool{false, true} {46 s := sendBoth(t, "B1", "A2", opA, opB, bFirst)47 wantShown(t, s, map[string]string{"B1": "x", "B2": "1", "C3": "4", "A2": "#REF!", "H1": "10", "H2": "#REF!"})48 }49}5051// A region an array spills over gives way, and the formulas naming it52// read #REF! until it's sent again.53func TestRegionGivingWayToAnArray(t *testing.T) {54 s := New()55 sent(t, s, "a", "B1")56 s.Set(at("H1"), "=SUM(nu.a)")57 apply(t, s, LiveOp{Region: "a", Reset: true, Header: liveRow("x"), Rows: []LiveRow{liveRow("5")}})58 wantShown(t, s, map[string]string{"H1": "5"})59 s.Set(at("A1"), "=SEQUENCE(1,2)")60 wantShown(t, s, map[string]string{"A1": "1", "B1": "2", "B2": "", "H1": "#REF!"})61}6263// An array spilling over a region's table isn't blocked by a formula64// naming the region that it reads: the region gives way, so the array65// doesn't depend on it, whichever came first.66func TestArrayOverRegionItReadsByName(t *testing.T) {67 for _, arrayFirst := range []bool{false, true} {68 s := New()69 sent(t, s, "a", "B1")70 s.Set(at("D5"), "=SUM(nu.a)")71 op := LiveOp{Region: "a", Reset: true, Header: liveRow("x"), Rows: []LiveRow{liveRow("5")}}72 if !arrayFirst {73 apply(t, s, op)74 }75 s.Set(at("A1"), "=IF(ISERROR(D5),SEQUENCE(1,2),SEQUENCE(1,2))") // over B176 if arrayFirst {77 apply(t, s, op)78 }79 wantShown(t, s, map[string]string{"A1": "1", "B1": "2", "D5": "#REF!"})80 if _, ok := s.SpillAnchor(at("B1")); !ok {81 t.Errorf("array first %v: the array doesn't spill over the region", arrayFirst)82 }83 }84}8586// A paste into a blank cell of a region's table has the region find it87// in its way when its rows are sent again, as they are when the file is88// opened.89func TestPasteIntoARegionsBlankCell(t *testing.T) {90 s := New()91 sent(t, s, "a", "A1")92 op := LiveOp{Region: "a", Reset: true, Header: liveRow("x", "y"), Rows: []LiveRow{liveRow("1", "")}}93 apply(t, s, op)94 s.Set(at("E1"), "9")95 if _, err := s.Paste(s.Copy(Rect{From: at("E1"), To: at("E1")}), Rect{From: at("B2"), To: at("B2")}, false); err != nil {96 t.Fatal(err)97 }98 if got := s.Book().StaleOutputs(); len(got) != 1 {99 t.Fatalf("stale outputs %v, want a", got)100 }101 apply(t, s, op)102 wantShown(t, s, map[string]string{"A1": "#REF!", "A2": "", "B2": "9"})103}104105// An array that would spill over a region's table whose cells it reads106// is a circular dependency: spilling, it would block the region and107// read its #REF!, come out smaller and free it. It shows #REF! and the108// region its table, whichever came first, and opened again.109func TestArrayOverRegionItReadsTheCellsOf(t *testing.T) {110 op := LiveOp{Region: "a", Reset: true, Header: liveRow("x", "y"), Rows: []LiveRow{liveRow("1", "2")}}111 feeds := map[string]LiveOp{"a": op}112 want := map[string]string{"I9": "#REF!", "H10": "x", "I11": "2"}113 for _, arrayFirst := range []bool{false, true} {114 s := New()115 sent(t, s, "a", "H10")116 if !arrayFirst {117 apply(t, s, op)118 }119 s.Set(at("I9"), "=SORT(H9:H11)")120 if arrayFirst {121 apply(t, s, op)122 }123 feedStale(s.Book(), feeds)124 if stale := s.Book().StaleOutputs(); len(stale) > 0 {125 t.Errorf("array first %v: still stale %v", arrayFirst, stale)126 }127 wantShown(t, s, want)128 opened := roundTrip(t, s)129 feedAll(opened.Book(), feeds)130 feedStale(opened.Book(), feeds)131 wantShown(t, opened, want)132 }133}134135// A region blocked by a formula and an array reading its cells, the formula136// cleared, is blocked by the array alone, which then finds the cycle.137func TestRegionFreedButForAnArrayReadingIt(t *testing.T) {138 op := LiveOp{Region: "a", Reset: true, Header: liveRow("x", "y", "z"), Rows: []LiveRow{liveRow("1", "2", "3")}}139 s := New()140 sent(t, s, "a", "F10")141 s.Set(at("F11"), "=SEQUENCE(1)")142 s.Set(at("H9"), "=SORT(F6:F10)")143 apply(t, s, op)144 s.Set(at("F11"), "")145 feedStale(s.Book(), map[string]LiveOp{"a": op})146 wantShown(t, s, map[string]string{"H9": "#REF!", "F10": "x", "H11": "3"})147}