oficina / sheets Public

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

Plain git — no account needed to clone.

Download

Download this file

1package xlsx
2
3import (
4 "os"
5 "path/filepath"
6 "testing"
7
8 "github.com/xuri/excelize/v2"
9)
10
11// TestGenerateCorpus (re)builds testdata/corpus/xlsx/*.xlsx — the golden
12// round-trip fixtures TestRoundTripIsStable (roundtrip_test.go) reads.
13// It is the "small tool or test helper" the task brief calls for rather
14// than a standalone command, specifically so excelize stays confined to
15// this package (import.go's doc comment) even for fixture generation.
16//
17// Skipped by default — it WRITES the committed fixtures, so an ordinary
18// `go test ./...` must not silently regenerate (and potentially drift)
19// them. Run deliberately after changing what the corpus should cover:
20//
21// GENCORPUS=1 go test ./internal/sheets/xlsx/ -run TestGenerateCorpus -v
22//
23// then review the diff and commit the result.
24func TestGenerateCorpus(t *testing.T) {
25 if os.Getenv("GENCORPUS") == "" {
26 t.Skip("set GENCORPUS=1 to regenerate testdata/corpus/xlsx fixtures")
27 }
28 dir := filepath.Join("..", "..", "..", "testdata", "corpus", "xlsx")
29 if err := os.MkdirAll(dir, 0o755); err != nil {
30 t.Fatal(err)
31 }
32
33 for name, build := range map[string]func(*excelize.File, string){
34 "numbers.xlsx": buildNumbersFixture,
35 "formulas.xlsx": buildFormulasFixture,
36 "numberformats.xlsx": buildNumberFormatsFixture,
37 "fills.xlsx": buildFillsFixture,
38 "blanks.xlsx": buildBlanksFixture,
39 } {
40 f := excelize.NewFile()
41 sheet := f.GetSheetList()[0]
42 build(f, sheet)
43 path := filepath.Join(dir, name)
44 if err := f.SaveAs(path); err != nil {
45 t.Fatalf("save %s: %v", name, err)
46 }
47 if err := f.Close(); err != nil {
48 t.Fatalf("close %s: %v", name, err)
49 }
50 t.Logf("wrote %s", path)
51 }
52}
53
54// buildNumbersFixture covers plain numeric literals: integers, a
55// negative, a decimal, a very small and a very large magnitude, and
56// zero — the shapes a naive text-based codec is most likely to mangle.
57func buildNumbersFixture(f *excelize.File, sheet string) {
58 must(f.SetCellFloat(sheet, "A1", 42, -1, 64))
59 must(f.SetCellFloat(sheet, "A2", -17.5, -1, 64))
60 must(f.SetCellFloat(sheet, "A3", 0, -1, 64))
61 must(f.SetCellFloat(sheet, "A4", 0.0001, -1, 64))
62 must(f.SetCellFloat(sheet, "A5", 123456789.125, -1, 64))
63}
64
65// buildFormulasFixture covers formulas referencing other cells and a
66// function call — none volatile (no NOW/TODAY — calc's own golden corpus
67// excludes those for the same reason: a round trip must be deterministic
68// across two separate evaluations).
69func buildFormulasFixture(f *excelize.File, sheet string) {
70 must(f.SetCellFloat(sheet, "A1", 2, -1, 64))
71 must(f.SetCellFloat(sheet, "A2", 3, -1, 64))
72 must(f.SetCellFloat(sheet, "A3", 4, -1, 64))
73 must(f.SetCellFormula(sheet, "B1", "A1+A2"))
74 must(f.SetCellFormula(sheet, "B2", "SUM(A1:A3)"))
75 must(f.SetCellFormula(sheet, "B3", "B1*B2"))
76}
77
78// buildNumberFormatsFixture covers custom number-format codes attached to
79// a cell — percentages, thousands separators, currency-shaped codes
80// (currency is a number format, not a type — spec §5) — verifying the
81// format STRING round-trips, not any particular rendering of it (this
82// package never renders a display string; Export writes the raw double
83// plus the format code, same as a native XLSX file, and leaves rendering
84// to whatever opens it — see export.go's package doc).
85func buildNumberFormatsFixture(f *excelize.File, sheet string) {
86 setWithNumFmt(f, sheet, "A1", 1234.5, "#,##0.00")
87 setWithNumFmt(f, sheet, "A2", 0.256, "0.00%")
88 setWithNumFmt(f, sheet, "A3", 50000, `"$"#,##0.00`)
89}
90
91// buildFillsFixture covers a solid cell fill. A1's fill is deliberately
92// "00B050" (excelize's Style.Fill.Color takes 6-char RGB — the package
93// doc on colors.go explains why this app's own FillHex is always the
94// 8-char ARGB form "FF00B050") so this file also backs
95// TestImportedFillHexIsRetained.
96func buildFillsFixture(f *excelize.File, sheet string) {
97 must(f.SetCellFloat(sheet, "A1", 42, -1, 64))
98 styleID, err := f.NewStyle(&excelize.Style{
99 Fill: excelize.Fill{Type: "pattern", Pattern: 1, Color: []string{"00B050"}},
100 })
101 must(err)
102 must(f.SetCellStyle(sheet, "A1", "A1", styleID))
103
104 must(f.SetCellFloat(sheet, "B1", 7, -1, 64))
105 styleID2, err := f.NewStyle(&excelize.Style{
106 Fill: excelize.Fill{Type: "pattern", Pattern: 1, Color: []string{"FF0000"}},
107 Font: &excelize.Font{Bold: true},
108 })
109 must(err)
110 must(f.SetCellStyle(sheet, "B1", "B1", styleID2))
111}
112
113// buildBlanksFixture covers an interior blank cell — B1 has neither a
114// value nor a formula, sitting between two cells that do — which is what
115// a real gap in a row of otherwise-populated data looks like at the XLSX
116// level (excelize.GetRows only trims TRAILING blank cells from a row, so
117// an interior one like this is exactly what Import must not lose or
118// shift out of position).
119func buildBlanksFixture(f *excelize.File, sheet string) {
120 must(f.SetCellFloat(sheet, "A1", 1, -1, 64))
121 must(f.SetCellFloat(sheet, "C1", 3, -1, 64))
122 must(f.SetCellStr(sheet, "A2", "left"))
123 must(f.SetCellStr(sheet, "C2", "right"))
124}
125
126func setWithNumFmt(f *excelize.File, sheet, cell string, value float64, numFmt string) {
127 must(f.SetCellFloat(sheet, cell, value, -1, 64))
128 styleID, err := f.NewStyle(&excelize.Style{CustomNumFmt: &numFmt})
129 must(err)
130 must(f.SetCellStyle(sheet, cell, cell, styleID))
131}
132
133func must(err error) {
134 if err != nil {
135 panic(err)
136 }
137}
138