oficina / sheets Public

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

Plain git — no account needed to clone.

Download

Download this file

1package xlsx_test
2
3import (
4 "bytes"
5 "strings"
6 "testing"
7
8 "amadan.net/oficina/sheets/internal/sheets/store"
9 "amadan.net/oficina/sheets/internal/sheets/xlsx"
10)
11
12// import -> export -> import must be stable. A file we wrote must open in
13// Excel and LibreOffice; that half is checked by hand against the
14// fixtures and recorded, because CI cannot open Excel — see the task
15// report for what that check did and did not confirm in this
16// environment.
17func TestRoundTripIsStable(t *testing.T) {
18 for _, name := range fixtureNames(t) {
19 t.Run(name, func(t *testing.T) {
20 first := importFixture(t, name)
21 var buf bytes.Buffer
22 if err := xlsx.Export(first, &buf); err != nil {
23 t.Fatalf("export: %v", err)
24 }
25 second := importReader(t, bytes.NewReader(buf.Bytes()))
26 if diff := diffTabs(t, first, second); diff != "" {
27 t.Errorf("round trip changed the sheet:\n%s", diff)
28 }
29 })
30 }
31}
32
33// Fills imported from XLSX keep their original hex so a file we did not
34// author re-exports byte-identically in colour terms. The intent drives
35// display; the retained hex drives export until a user re-picks — see
36// models.go's Format.FillHex and colors.go's package doc.
37func TestImportedFillHexIsRetained(t *testing.T) {
38 h := importFixture(t, "fills.xlsx")
39 f := formatOf(t, h, 0, 0) // A1, gencorpus_test.go's buildFillsFixture
40 if f.FillHex != "FF00B050" {
41 t.Fatalf("FillHex = %q, want FF00B050", f.FillHex)
42 }
43 // Re-exporting must emit that exact hex back out, not the resolved
44 // OKLCH intent's approximation of it.
45 var buf bytes.Buffer
46 if err := xlsx.Export(h, &buf); err != nil {
47 t.Fatalf("export: %v", err)
48 }
49 reimported := importReader(t, bytes.NewReader(buf.Bytes()))
50 f2 := formatOf(t, reimported, 0, 0)
51 if f2.FillHex != "FF00B050" {
52 t.Fatalf("FillHex after re-export = %q, want FF00B050 (not byte-identical re-export)", f2.FillHex)
53 }
54}
55
56// formatOf resolves (rowPos, colPos) to the real ids h.GridRows/GridCols
57// hold for them and fetches the Format there, failing the test if either
58// the coordinate or the format is missing.
59func formatOf(t *testing.T, h *store.Handle, rowPos, colPos int) *store.Format {
60 t.Helper()
61 rowID, colID, ok := cellAt(h, rowPos, colPos)
62 if !ok {
63 t.Fatalf("no cell at position (%d,%d)", rowPos, colPos)
64 }
65 f := h.Format(rowID, colID)
66 if f == nil {
67 t.Fatalf("Format(%d,%d) at position (%d,%d) is nil", rowID, colID, rowPos, colPos)
68 }
69 return f
70}
71
72func cellAt(h *store.Handle, rowPos, colPos int) (rowID, colID uint64, ok bool) {
73 foundRow, foundCol := false, false
74 for _, r := range h.GridRows() {
75 if r.Pos == rowPos {
76 rowID, foundRow = r.ID, true
77 }
78 }
79 for _, c := range h.GridCols() {
80 if c.Pos == colPos {
81 colID, foundCol = c.ID, true
82 }
83 }
84 return rowID, colID, foundRow && foundCol
85}
86
87// Import runs as a background job (spec §11); its error text reaches the
88// owner and platform logs this app cannot grep (spec §2.4). A formula
89// that fails to parse must never leak into that error string — this pins
90// it against a formula built specifically to fail, carrying a value no
91// error message should ever contain.
92func TestImportErrorsContainNoCellContent(t *testing.T) {
93 const canary = "SECRET-PAYROLL-FIGURE-93f7"
94
95 buf := badFormulaFixture(t, canary)
96
97 db := openTestDB(t)
98 sealer := testSealer(t)
99 h := newTab(t, db, sealer)
100
101 err := xlsx.Import(buf, h)
102 if err == nil {
103 t.Fatal("want an error importing a deliberately unparseable formula, got nil")
104 }
105 if strings.Contains(err.Error(), canary) {
106 t.Fatalf("import error contains cell content: %q", err.Error())
107 }
108}
109