oficina / sheets Public

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

Plain git — no account needed to clone.

Download

Download this file

1package sheets
2
3import (
4 "net/http"
5 "net/http/httptest"
6 "strconv"
7 "strings"
8 "testing"
9)
10
11// The grid page opens a WINDOW, not a whole sheet — 100 rows by 26
12// columns, because virtualising a real spreadsheet is a lot of client
13// code (sheetsvc.go says so, and it is right).
14//
15// What it did not do was look at the sheet first. Rows was the constant
16// on every render, while the cell route accepts a write far past it and
17// the table view lists whatever rows exist. Measured on 2026-09-03: a
18// cell written to row 151 came back 200, appeared in the table view and
19// in the export, and was invisible on the grid — no scrollbar reaching
20// it, no row 151 to scroll to, and no copy anywhere saying a window
21// existed at all.
22//
23// It is not lost data, which is why this is a bug and not an incident.
24// It is worse than it sounds anyway: the one screen most people will
25// ever use is the one that cannot show them the cell.
26
27func writeCellAt(t *testing.T, app *App, cookies []*http.Cookie, cells string, r, c int, raw string) {
28 t.Helper()
29 body := `{"r":` + strconv.Itoa(r) + `,"c":` + strconv.Itoa(c) + `,"raw":` + strconv.Quote(raw) + `}`
30 if rec := postCell(t, app, cookies, cells, body); rec.Code != http.StatusOK {
31 t.Fatalf("writing %d,%d = %d: %s", r, c, rec.Code, rec.Body.String())
32 }
33}
34
35// gridPage renders /sheets/{id} and returns the HTML.
36func gridPage(t *testing.T, app *App, cookies []*http.Cookie, id uint64) string {
37 t.Helper()
38 req := httptest.NewRequest(http.MethodGet, "/sheets/"+strconv.FormatUint(id, 10), nil)
39 rec := do(t, app, as(req, cookies))
40 if rec.Code != http.StatusOK {
41 t.Fatalf("grid page = %d", rec.Code)
42 }
43 return rec.Body.String()
44}
45
46func TestTheGridWindowReachesTheFurthestCell(t *testing.T) {
47 app, cookies := signIn(t)
48 s, cells := newSheet(t, app, cookies, "Deep")
49
50 if got := gridPage(t, app, cookies, s.ID); !strings.Contains(got, `data-rows="100"`) {
51 t.Fatalf("an empty sheet should still open the default window; page has no data-rows=\"100\"")
52 }
53
54 writeCellAt(t, app, cookies, cells, 150, 0, "I am on row 151")
55
56 page := gridPage(t, app, cookies, s.ID)
57 rows := attrInt(t, page, "data-rows")
58 if rows <= 150 {
59 t.Errorf("a cell on row 151 and the grid opens %d rows — it cannot be scrolled to, "+
60 "and nothing on the page says so", rows)
61 }
62}
63
64func TestTheGridWindowReachesTheFurthestColumn(t *testing.T) {
65 app, cookies := signIn(t)
66 s, cells := newSheet(t, app, cookies, "Wide")
67
68 writeCellAt(t, app, cookies, cells, 0, 40, "column AO")
69
70 page := gridPage(t, app, cookies, s.ID)
71 if cols := attrInt(t, page, "data-cols"); cols <= 40 {
72 t.Errorf("a cell in column AO and the grid opens %d columns", cols)
73 }
74}
75
76// TestTheWindowNeverShrinksBelowTheDefault: a nearly empty sheet must
77// still open a grid somebody can type into. Sizing the window to the
78// content alone would open a 1x1 grid on a sheet with one cell in it.
79func TestTheWindowNeverShrinksBelowTheDefault(t *testing.T) {
80 app, cookies := signIn(t)
81 s, cells := newSheet(t, app, cookies, "Sparse")
82 writeCellAt(t, app, cookies, cells, 0, 0, "one cell")
83
84 page := gridPage(t, app, cookies, s.ID)
85 if rows := attrInt(t, page, "data-rows"); rows != defaultWindowRows {
86 t.Errorf("data-rows = %d on a one-cell sheet, want the default %d", rows, defaultWindowRows)
87 }
88 if cols := attrInt(t, page, "data-cols"); cols != defaultWindowCols {
89 t.Errorf("data-cols = %d, want the default %d", cols, defaultWindowCols)
90 }
91}
92
93// TestTheWindowStopsAtTheMaximum: past maxWindow* the answer cannot be
94// "open a bigger window", so it has to be words. A sheet whose content
95// runs past what the grid can paint says so, in the status line the
96// clipboard already uses, rather than silently showing part of itself.
97func TestTheWindowStopsAtTheMaximum(t *testing.T) {
98 app, cookies := signIn(t)
99 s, cells := newSheet(t, app, cookies, "Enormous")
100
101 writeCellAt(t, app, cookies, cells, maxWindowRows+200, 0, "past the end")
102
103 page := gridPage(t, app, cookies, s.ID)
104 if rows := attrInt(t, page, "data-rows"); rows != maxWindowRows {
105 t.Errorf("data-rows = %d, want it clamped to %d", rows, maxWindowRows)
106 }
107 if !strings.Contains(page, "data-window-truncated") {
108 t.Error("the grid is showing part of a sheet and the page does not say so — " +
109 "the one thing worse than a window is an unannounced one")
110 }
111}
112
113// attrInt pulls an integer attribute out of the rendered page.
114func attrInt(t *testing.T, page, attr string) int {
115 t.Helper()
116 i := strings.Index(page, attr+`="`)
117 if i < 0 {
118 t.Fatalf("the page carries no %s", attr)
119 }
120 rest := page[i+len(attr)+2:]
121 j := strings.Index(rest, `"`)
122 n, err := strconv.Atoi(rest[:j])
123 if err != nil {
124 t.Fatalf("%s=%q is not a number", attr, rest[:j])
125 }
126 return n
127}
128