This commit is contained in:
@@ -0,0 +1,304 @@
|
||||
package excelext
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"git.blackforestbytes.com/BlackForestBytes/goext/langext"
|
||||
"git.blackforestbytes.com/BlackForestBytes/goext/tst"
|
||||
"github.com/xuri/excelize/v2"
|
||||
)
|
||||
|
||||
type testRow struct {
|
||||
Name string
|
||||
Age int
|
||||
Score float64
|
||||
}
|
||||
|
||||
func openBytes(t *testing.T, data []byte) *excelize.File {
|
||||
t.Helper()
|
||||
f, err := excelize.OpenReader(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to open xlsx bytes: %v", err)
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
func cellValue(t *testing.T, f *excelize.File, sheet, axis string) string {
|
||||
t.Helper()
|
||||
v, err := f.GetCellValue(sheet, axis)
|
||||
if err != nil {
|
||||
t.Fatalf("GetCellValue(%s, %s) failed: %v", sheet, axis, err)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func TestNewExcelMapper(t *testing.T) {
|
||||
em, err := NewExcelMapper[testRow]()
|
||||
tst.AssertNoErr(t, err)
|
||||
if em == nil {
|
||||
t.Fatal("expected non-nil mapper")
|
||||
}
|
||||
tst.AssertEqual(t, em.SkipColumnHeader, false)
|
||||
tst.AssertEqual(t, len(em.colDefinitions), 0)
|
||||
tst.AssertEqual(t, len(em.wsHeader), 0)
|
||||
tst.AssertEqual(t, len(em.colFilter), 0)
|
||||
if em.StyleDate != nil || em.StyleHeader != nil {
|
||||
t.Errorf("expected styles to be nil before init")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInitNewFileAndStyles(t *testing.T) {
|
||||
em, _ := NewExcelMapper[testRow]()
|
||||
f, err := em.InitNewFile("Sheet-Foo")
|
||||
tst.AssertNoErr(t, err)
|
||||
if f == nil {
|
||||
t.Fatal("expected non-nil file")
|
||||
}
|
||||
|
||||
sheets := f.GetSheetList()
|
||||
tst.AssertEqual(t, len(sheets), 1)
|
||||
tst.AssertEqual(t, sheets[0], "Sheet-Foo")
|
||||
|
||||
if em.StyleDate == nil || em.StyleDatetime == nil || em.StyleEUR == nil ||
|
||||
em.StylePercentage == nil || em.StyleHeader == nil || em.StyleWSHeader == nil {
|
||||
t.Errorf("expected all styles to be initialized")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddColumn(t *testing.T) {
|
||||
em, _ := NewExcelMapper[testRow]()
|
||||
em.AddColumn("Name", nil, nil, func(r testRow) any { return r.Name })
|
||||
em.AddColumn("Age", nil, langext.Ptr(12.0), func(r testRow) any { return r.Age })
|
||||
|
||||
tst.AssertEqual(t, len(em.colDefinitions), 2)
|
||||
tst.AssertEqual(t, em.colDefinitions[0].header, "Name")
|
||||
tst.AssertEqual(t, em.colDefinitions[1].header, "Age")
|
||||
if em.colDefinitions[1].width == nil || *em.colDefinitions[1].width != 12.0 {
|
||||
t.Errorf("expected width 12.0")
|
||||
}
|
||||
|
||||
val, err := em.colDefinitions[0].fn(testRow{Name: "Alice"})
|
||||
tst.AssertNoErr(t, err)
|
||||
tst.AssertEqual(t, val.(string), "Alice")
|
||||
}
|
||||
|
||||
func TestAddColumnErr(t *testing.T) {
|
||||
em, _ := NewExcelMapper[testRow]()
|
||||
sentinel := errors.New("boom")
|
||||
em.AddColumnErr("X", nil, nil, func(r testRow) (any, error) {
|
||||
if r.Age < 0 {
|
||||
return nil, sentinel
|
||||
}
|
||||
return r.Age, nil
|
||||
})
|
||||
|
||||
tst.AssertEqual(t, len(em.colDefinitions), 1)
|
||||
|
||||
v, err := em.colDefinitions[0].fn(testRow{Age: 5})
|
||||
tst.AssertNoErr(t, err)
|
||||
tst.AssertEqual(t, v.(int), 5)
|
||||
|
||||
_, err = em.colDefinitions[0].fn(testRow{Age: -1})
|
||||
if !errors.Is(err, sentinel) {
|
||||
t.Errorf("expected sentinel error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddWorksheetHeader(t *testing.T) {
|
||||
em, _ := NewExcelMapper[testRow]()
|
||||
em.AddWorksheetHeader("Title 1", nil)
|
||||
em.AddWorksheetHeader("Title 2", langext.Ptr(7))
|
||||
|
||||
tst.AssertEqual(t, len(em.wsHeader), 2)
|
||||
tst.AssertEqual(t, em.wsHeader[0].V1, "Title 1")
|
||||
tst.AssertEqual(t, em.wsHeader[1].V1, "Title 2")
|
||||
if em.wsHeader[1].V2 == nil || *em.wsHeader[1].V2 != 7 {
|
||||
t.Errorf("expected style ptr 7")
|
||||
}
|
||||
if em.wsHeader[0].V2 != nil {
|
||||
t.Errorf("expected nil style for first header")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddFilter(t *testing.T) {
|
||||
em, _ := NewExcelMapper[testRow]()
|
||||
em.AddFilter(func(v testRow) bool { return v.Age >= 18 })
|
||||
em.AddFilter(func(v testRow) bool { return v.Score > 0 })
|
||||
tst.AssertEqual(t, len(em.colFilter), 2)
|
||||
}
|
||||
|
||||
func TestBuildBasic(t *testing.T) {
|
||||
em, _ := NewExcelMapper[testRow]()
|
||||
em.AddColumn("Name", nil, nil, func(r testRow) any { return r.Name })
|
||||
em.AddColumn("Age", nil, nil, func(r testRow) any { return r.Age })
|
||||
|
||||
rows := []testRow{
|
||||
{Name: "Alice", Age: 30},
|
||||
{Name: "Bob", Age: 25},
|
||||
}
|
||||
|
||||
data, err := em.Build("Sheet1", rows)
|
||||
tst.AssertNoErr(t, err)
|
||||
if len(data) == 0 {
|
||||
t.Fatal("expected non-empty xlsx output")
|
||||
}
|
||||
|
||||
f := openBytes(t, data)
|
||||
defer f.Close()
|
||||
|
||||
tst.AssertEqual(t, cellValue(t, f, "Sheet1", "A1"), "Name")
|
||||
tst.AssertEqual(t, cellValue(t, f, "Sheet1", "B1"), "Age")
|
||||
tst.AssertEqual(t, cellValue(t, f, "Sheet1", "A2"), "Alice")
|
||||
tst.AssertEqual(t, cellValue(t, f, "Sheet1", "B2"), "30")
|
||||
tst.AssertEqual(t, cellValue(t, f, "Sheet1", "A3"), "Bob")
|
||||
tst.AssertEqual(t, cellValue(t, f, "Sheet1", "B3"), "25")
|
||||
}
|
||||
|
||||
func TestBuildSkipColumnHeader(t *testing.T) {
|
||||
em, _ := NewExcelMapper[testRow]()
|
||||
em.SkipColumnHeader = true
|
||||
em.AddColumn("Name", nil, nil, func(r testRow) any { return r.Name })
|
||||
|
||||
rows := []testRow{{Name: "Alice"}, {Name: "Bob"}}
|
||||
|
||||
data, err := em.Build("Data", rows)
|
||||
tst.AssertNoErr(t, err)
|
||||
|
||||
f := openBytes(t, data)
|
||||
defer f.Close()
|
||||
|
||||
tst.AssertEqual(t, cellValue(t, f, "Data", "A1"), "Alice")
|
||||
tst.AssertEqual(t, cellValue(t, f, "Data", "A2"), "Bob")
|
||||
}
|
||||
|
||||
func TestBuildWithFilter(t *testing.T) {
|
||||
em, _ := NewExcelMapper[testRow]()
|
||||
em.AddColumn("Name", nil, nil, func(r testRow) any { return r.Name })
|
||||
em.AddFilter(func(v testRow) bool { return v.Age >= 18 })
|
||||
|
||||
rows := []testRow{
|
||||
{Name: "Alice", Age: 30},
|
||||
{Name: "Charlie", Age: 12},
|
||||
{Name: "Bob", Age: 25},
|
||||
}
|
||||
|
||||
data, err := em.Build("S", rows)
|
||||
tst.AssertNoErr(t, err)
|
||||
|
||||
f := openBytes(t, data)
|
||||
defer f.Close()
|
||||
|
||||
tst.AssertEqual(t, cellValue(t, f, "S", "A1"), "Name")
|
||||
tst.AssertEqual(t, cellValue(t, f, "S", "A2"), "Alice")
|
||||
tst.AssertEqual(t, cellValue(t, f, "S", "A3"), "Bob")
|
||||
tst.AssertEqual(t, cellValue(t, f, "S", "A4"), "")
|
||||
}
|
||||
|
||||
func TestBuildWithWorksheetHeader(t *testing.T) {
|
||||
em, _ := NewExcelMapper[testRow]()
|
||||
em.AddWorksheetHeader("My Big Title", nil)
|
||||
em.AddColumn("Name", nil, nil, func(r testRow) any { return r.Name })
|
||||
em.AddColumn("Age", nil, nil, func(r testRow) any { return r.Age })
|
||||
|
||||
rows := []testRow{{Name: "Alice", Age: 30}}
|
||||
|
||||
data, err := em.Build("S", rows)
|
||||
tst.AssertNoErr(t, err)
|
||||
|
||||
f := openBytes(t, data)
|
||||
defer f.Close()
|
||||
|
||||
tst.AssertEqual(t, cellValue(t, f, "S", "A1"), "My Big Title")
|
||||
tst.AssertEqual(t, cellValue(t, f, "S", "A3"), "Name")
|
||||
tst.AssertEqual(t, cellValue(t, f, "S", "B3"), "Age")
|
||||
tst.AssertEqual(t, cellValue(t, f, "S", "A4"), "Alice")
|
||||
tst.AssertEqual(t, cellValue(t, f, "S", "B4"), "30")
|
||||
}
|
||||
|
||||
func TestBuildHandlesNilPointer(t *testing.T) {
|
||||
type ptrRow struct {
|
||||
Name *string
|
||||
}
|
||||
|
||||
em, _ := NewExcelMapper[ptrRow]()
|
||||
em.AddColumn("Name", nil, nil, func(r ptrRow) any { return r.Name })
|
||||
|
||||
name := "Alice"
|
||||
rows := []ptrRow{
|
||||
{Name: &name},
|
||||
{Name: nil},
|
||||
}
|
||||
|
||||
data, err := em.Build("S", rows)
|
||||
tst.AssertNoErr(t, err)
|
||||
|
||||
f := openBytes(t, data)
|
||||
defer f.Close()
|
||||
|
||||
tst.AssertEqual(t, cellValue(t, f, "S", "A2"), "Alice")
|
||||
tst.AssertEqual(t, cellValue(t, f, "S", "A3"), "")
|
||||
}
|
||||
|
||||
func TestBuildPropagatesColumnError(t *testing.T) {
|
||||
em, _ := NewExcelMapper[testRow]()
|
||||
sentinel := errors.New("col fail")
|
||||
em.AddColumnErr("Bad", nil, nil, func(r testRow) (any, error) {
|
||||
return nil, sentinel
|
||||
})
|
||||
|
||||
_, err := em.Build("S", []testRow{{Name: "X"}})
|
||||
if err == nil {
|
||||
t.Fatal("expected error from column fn to propagate")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildEmptyData(t *testing.T) {
|
||||
em, _ := NewExcelMapper[testRow]()
|
||||
em.AddColumn("Name", nil, nil, func(r testRow) any { return r.Name })
|
||||
|
||||
data, err := em.Build("S", []testRow{})
|
||||
tst.AssertNoErr(t, err)
|
||||
|
||||
f := openBytes(t, data)
|
||||
defer f.Close()
|
||||
|
||||
tst.AssertEqual(t, cellValue(t, f, "S", "A1"), "Name")
|
||||
tst.AssertEqual(t, cellValue(t, f, "S", "A2"), "")
|
||||
}
|
||||
|
||||
func TestBuildSingleSheetWithExistingFile(t *testing.T) {
|
||||
em, _ := NewExcelMapper[testRow]()
|
||||
em.AddColumn("Name", nil, nil, func(r testRow) any { return r.Name })
|
||||
|
||||
f, err := em.InitNewFile("S1")
|
||||
tst.AssertNoErr(t, err)
|
||||
|
||||
_, err = f.NewSheet("S2")
|
||||
tst.AssertNoErr(t, err)
|
||||
|
||||
err = em.BuildSingleSheet(f, "S2", []testRow{{Name: "Bob"}})
|
||||
tst.AssertNoErr(t, err)
|
||||
|
||||
tst.AssertEqual(t, cellValue(t, f, "S2", "A1"), "Name")
|
||||
tst.AssertEqual(t, cellValue(t, f, "S2", "A2"), "Bob")
|
||||
}
|
||||
|
||||
func TestBuildWithColumnWidthAndStyle(t *testing.T) {
|
||||
em, _ := NewExcelMapper[testRow]()
|
||||
|
||||
f, err := em.InitNewFile("S")
|
||||
tst.AssertNoErr(t, err)
|
||||
|
||||
em.AddColumn("Name", em.StyleHeader, langext.Ptr(20.5), func(r testRow) any { return r.Name })
|
||||
|
||||
err = em.BuildSingleSheet(f, "S", []testRow{{Name: "Alice"}})
|
||||
tst.AssertNoErr(t, err)
|
||||
|
||||
w, err := f.GetColWidth("S", "A")
|
||||
tst.AssertNoErr(t, err)
|
||||
if w < 20.0 || w > 21.0 {
|
||||
t.Errorf("expected column width near 20.5, got %v", w)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package excelext
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.blackforestbytes.com/BlackForestBytes/goext/rfctime"
|
||||
"git.blackforestbytes.com/BlackForestBytes/goext/tst"
|
||||
)
|
||||
|
||||
func TestCellAddress(t *testing.T) {
|
||||
tst.AssertEqual(t, c(1, 0), "A1")
|
||||
tst.AssertEqual(t, c(1, 1), "B1")
|
||||
tst.AssertEqual(t, c(2, 0), "A2")
|
||||
tst.AssertEqual(t, c(10, 25), "Z10")
|
||||
tst.AssertEqual(t, c(1, 26), "AA1")
|
||||
tst.AssertEqual(t, c(99, 27), "AB99")
|
||||
tst.AssertEqual(t, c(100, 51), "AZ100")
|
||||
tst.AssertEqual(t, c(1, 52), "BA1")
|
||||
}
|
||||
|
||||
func TestExcelizeOptTimeNil(t *testing.T) {
|
||||
got := excelizeOptTime(nil)
|
||||
if got != "" {
|
||||
t.Errorf("expected empty string for nil time, got %v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExcelizeOptTimeValue(t *testing.T) {
|
||||
now := time.Date(2024, 5, 17, 13, 45, 30, 0, time.UTC)
|
||||
rt := rfctime.RFC3339NanoTime(now)
|
||||
got := excelizeOptTime(&rt)
|
||||
|
||||
gt, ok := got.(time.Time)
|
||||
if !ok {
|
||||
t.Fatalf("expected time.Time, got %T", got)
|
||||
}
|
||||
if !gt.Equal(now) {
|
||||
t.Errorf("expected %v, got %v", now, gt)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExcelizeOptDateNil(t *testing.T) {
|
||||
got := excelizeOptDate(nil)
|
||||
if got != "" {
|
||||
t.Errorf("expected empty string for nil date, got %v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExcelizeOptDateValue(t *testing.T) {
|
||||
d := rfctime.NewDate(time.Date(2025, 11, 3, 0, 0, 0, 0, time.UTC))
|
||||
got := excelizeOptDate(&d)
|
||||
|
||||
gt, ok := got.(time.Time)
|
||||
if !ok {
|
||||
t.Fatalf("expected time.Time, got %T", got)
|
||||
}
|
||||
if gt.Year() != 2025 || gt.Month() != 11 || gt.Day() != 3 {
|
||||
t.Errorf("unexpected date returned: %v", gt)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user