oficina / sheets Public

Clone
git clone https://amadan.net/oficina/sheets

Plain git — no account needed to clone.

Download

Download this file

1package store_test
2
3import (
4 "testing"
5
6 "amadan.net/oficina/sheets/internal/sheets/calc"
7 "amadan.net/oficina/sheets/internal/sheets/store"
8)
9
10// openSeeded is seeded() plus an Open, which is what every report test
11// wants and none of them vary.
12func openSeeded(t *testing.T) *store.Handle {
13 t.Helper()
14 db, sealer, id := seeded(t)
15 h, err := store.Open(db, sealer, id)
16 if err != nil {
17 t.Fatalf("Open: %v", err)
18 }
19 t.Cleanup(h.Release)
20 return h
21}
22
23// findCell locates one coordinate in a report, so an assertion does not
24// depend on the order a map ranged in.
25func findCell(t *testing.T, rep store.Report, rowID, colID uint64) store.ChangedCell {
26 t.Helper()
27 for _, c := range rep.Cells {
28 if c.RowID == rowID && c.ColID == colID {
29 return c
30 }
31 }
32 t.Fatalf("report does not mention row %d col %d; it has %+v", rowID, colID, rep.Cells)
33 return store.ChangedCell{}
34}
35
36// The report is what the live layer fans out. If a dependent is missing
37// from it, the other tab shows a formula's old result forever.
38func TestSetReportNamesTheOriginAndItsDependents(t *testing.T) {
39 h := openSeeded(t)
40
41 // A2 = A1 * 2. A1 is seeded holding "5", so A2 starts at 10.
42 if err := h.Set(rowB, colA, "=A1*2", actor); err != nil {
43 t.Fatalf("Set A2: %v", err)
44 }
45
46 rep, err := h.SetReport(rowA, colA, "21", actor)
47 if err != nil {
48 t.Fatalf("SetReport A1: %v", err)
49 }
50 if rep.Capped {
51 t.Fatal("a two-cell recalculation reported itself capped")
52 }
53 if len(rep.Cells) != 2 {
54 t.Fatalf("report covers %d cells, want 2 (A1 and its dependent A2): %+v",
55 len(rep.Cells), rep.Cells)
56 }
57
58 origin := findCell(t, rep, rowA, colA)
59 if origin.Derived {
60 t.Error("the cell that was typed into is flagged derived")
61 }
62 if origin.Raw != "21" {
63 t.Errorf("origin raw = %q, want %q — the editor would show the wrong text", origin.Raw, "21")
64 }
65 if origin.Value.Kind != calc.KindNum || origin.Value.Num != 21 {
66 t.Errorf("origin value = %+v, want the number 21", origin.Value)
67 }
68
69 dep := findCell(t, rep, rowB, colA)
70 if !dep.Derived {
71 t.Error("a recalculated dependent is not flagged derived; history would credit the wrong edit")
72 }
73 if dep.Value.Kind != calc.KindNum || dep.Value.Num != 42 {
74 t.Errorf("A2 = %+v, want the number 42", dep.Value)
75 }
76 if dep.Raw != "=A1*2" {
77 t.Errorf("dependent raw = %q, want the formula, not its result", dep.Raw)
78 }
79}
80
81// The version is the only thing a client can order paints by. If it does
82// not advance, two concurrent editors produce a visibly wrong grid —
83// the exact bug optimistic painting is meant to hide.
84func TestSetReportVersionAdvances(t *testing.T) {
85 h := openSeeded(t)
86 first, err := h.SetReport(rowA, colA, "1", actor)
87 if err != nil {
88 t.Fatalf("SetReport: %v", err)
89 }
90 second, err := h.SetReport(rowA, colA, "2", actor)
91 if err != nil {
92 t.Fatalf("SetReport again: %v", err)
93 }
94 if second.Version <= first.Version {
95 t.Fatalf("version did not advance: %d then %d", first.Version, second.Version)
96 }
97}
98
99// setBlank's touched set is its dependents only — the origin's row is
100// deleted, so it is not in the graph to recompute. Without the report
101// adding it back, the other tab never clears the cell you just emptied.
102func TestSetReportOnBlankReportsTheClearedCell(t *testing.T) {
103 h := openSeeded(t)
104 if err := h.Set(rowA, colA, "7", actor); err != nil {
105 t.Fatalf("Set: %v", err)
106 }
107 rep, err := h.SetReport(rowA, colA, "", actor)
108 if err != nil {
109 t.Fatalf("SetReport blank: %v", err)
110 }
111 cleared := findCell(t, rep, rowA, colA)
112 if cleared.Raw != "" {
113 t.Errorf("cleared cell reports raw %q, want empty", cleared.Raw)
114 }
115 if cleared.Value.Kind != calc.KindEmpty {
116 t.Errorf("cleared cell reports value %+v, want empty", cleared.Value)
117 }
118 if cleared.Derived {
119 t.Error("the cell that was cleared is flagged derived")
120 }
121}
122
123// Blanking a feeder must tell every dependent, or they keep painting a
124// number computed from a cell that no longer exists.
125func TestSetReportOnBlankReportsDependentsToo(t *testing.T) {
126 h := openSeeded(t)
127 if err := h.Set(rowB, colA, "=A1*2", actor); err != nil {
128 t.Fatalf("Set A2: %v", err)
129 }
130 rep, err := h.SetReport(rowA, colA, "", actor)
131 if err != nil {
132 t.Fatalf("SetReport blank A1: %v", err)
133 }
134 if len(rep.Cells) != 2 {
135 t.Fatalf("blanking a feeder reported %d cells, want 2: %+v", len(rep.Cells), rep.Cells)
136 }
137 dep := findCell(t, rep, rowB, colA)
138 if !dep.Derived {
139 t.Error("the dependent of a blanked cell is not flagged derived")
140 }
141 if dep.Value.Kind != calc.KindNum || dep.Value.Num != 0 {
142 t.Errorf("A2 after A1 was blanked = %+v, want 0", dep.Value)
143 }
144}
145
146// Set is now a wrapper. If it stopped writing, every existing caller —
147// XLSX import above all — would silently do nothing.
148func TestSetStillWrites(t *testing.T) {
149 h := openSeeded(t)
150 if err := h.Set(rowA, colA, "99", actor); err != nil {
151 t.Fatalf("Set: %v", err)
152 }
153 if v := h.Value(rowA, colA); v.Kind != calc.KindNum || v.Num != 99 {
154 t.Fatalf("after Set, A1 = %+v, want 99", v)
155 }
156}
157
158func TestSetReportRefusesACellOutsideTheTab(t *testing.T) {
159 h := openSeeded(t)
160 if _, err := h.SetReport(999, 999, "1", actor); err == nil {
161 t.Fatal("SetReport on a row and column that are not in this tab = nil error")
162 }
163}
164