Files
goext/wpdf/wpdf_table_test.go
T
Mikescher 02d6894ec6
Build Docker and Deploy / Run goext test-suite (push) Successful in 1m34s
[🤖] Add Unit-Tests
2026-04-27 16:31:29 +02:00

554 lines
13 KiB
Go

package wpdf
import (
"bytes"
"math"
"testing"
)
func TestTableBuilderInitialState(t *testing.T) {
b := newBuilderWithPage(t)
tb := b.Table()
if tb == nil {
t.Fatal("Table() returned nil")
}
if tb.builder != b {
t.Error("builder back-reference not set")
}
if tb.padx != 2 {
t.Errorf("default padx = %v, want 2", tb.padx)
}
if tb.pady != 2 {
t.Errorf("default pady = %v, want 2", tb.pady)
}
if tb.defaultCellStyle == nil {
t.Error("default cell style is nil")
}
if tb.RowCount() != 0 {
t.Errorf("RowCount = %v, want 0", tb.RowCount())
}
}
func TestTableBuilderConfig(t *testing.T) {
b := newBuilderWithPage(t)
style := NewTableCellStyleOpt()
tb := b.Table().
PadX(5).
PadY(7).
Widths("10", "20", "auto").
DefaultStyle(style).
Debug(true)
if tb.padx != 5 {
t.Errorf("padx = %v, want 5", tb.padx)
}
if tb.pady != 7 {
t.Errorf("pady = %v, want 7", tb.pady)
}
if tb.columnWidths == nil || len(*tb.columnWidths) != 3 {
t.Errorf("columnWidths not set correctly: %v", tb.columnWidths)
}
if tb.defaultCellStyle != style {
t.Error("defaultCellStyle not set")
}
if tb.debug == nil || !*tb.debug {
t.Error("debug not set")
}
}
func TestTableBuilderAddRowDefault(t *testing.T) {
b := newBuilderWithPage(t)
tb := b.Table().AddRowDefaultStyle("a", "b", "c")
if tb.RowCount() != 1 {
t.Errorf("RowCount = %v, want 1", tb.RowCount())
}
if len(tb.rows[0].cells) != 3 {
t.Errorf("cells = %v, want 3", len(tb.rows[0].cells))
}
if tb.rows[0].cells[0].Content != "a" {
t.Errorf("cell[0].Content = %q, want %q", tb.rows[0].cells[0].Content, "a")
}
}
func TestTableBuilderAddRowWithStyle(t *testing.T) {
b := newBuilderWithPage(t)
style := NewTableCellStyleOpt().Bold().FontSize(10)
tb := b.Table().AddRowWithStyle(style, "x", "y")
if tb.RowCount() != 1 {
t.Fatalf("RowCount = %v, want 1", tb.RowCount())
}
if len(tb.rows[0].cells) != 2 {
t.Fatalf("cells = %v, want 2", len(tb.rows[0].cells))
}
for i, c := range tb.rows[0].cells {
if c.Style.fontStyleOverride == nil || *c.Style.fontStyleOverride != Bold {
t.Errorf("cell[%d] style not Bold", i)
}
if c.Style.fontSizeOverride == nil || *c.Style.fontSizeOverride != 10 {
t.Errorf("cell[%d] fontSize not 10", i)
}
}
}
func TestTableBuilderAddRow(t *testing.T) {
b := newBuilderWithPage(t)
tc := TableCell{Content: "hello", Style: TableCellStyleOpt{}}
tb := b.Table().AddRow(tc, tc)
if tb.RowCount() != 1 || len(tb.rows[0].cells) != 2 {
t.Errorf("AddRow did not add expected cells")
}
}
func TestTableBuilderBuildRowFlow(t *testing.T) {
b := newBuilderWithPage(t)
tb := b.Table().
BuildRow().Cell("a").Cell("b").Cell("c").BuildRow().
BuildRow().Cells("d", "e", "f").BuildRow()
if tb.RowCount() != 2 {
t.Errorf("RowCount = %v, want 2", tb.RowCount())
}
if len(tb.rows[0].cells) != 3 || len(tb.rows[1].cells) != 3 {
t.Errorf("each row should have 3 cells; got %d and %d",
len(tb.rows[0].cells), len(tb.rows[1].cells))
}
if tb.rows[0].cells[0].Content != "a" || tb.rows[1].cells[2].Content != "f" {
t.Error("cell content mismatch")
}
}
func TestTableRowBuilderCellWithStyle(t *testing.T) {
b := newBuilderWithPage(t)
style := NewTableCellStyleOpt().Bold()
tb := b.Table().BuildRow().CellWithStyle("x", style).BuildRow()
if tb.rows[0].cells[0].Style.fontStyleOverride == nil ||
*tb.rows[0].cells[0].Style.fontStyleOverride != Bold {
t.Error("cell style not applied")
}
}
func TestTableRowBuilderCellObjects(t *testing.T) {
b := newBuilderWithPage(t)
c1 := TableCell{Content: "alpha"}
c2 := TableCell{Content: "beta"}
tb := b.Table().BuildRow().CellObject(c1).CellObjects(c2).BuildRow()
if len(tb.rows[0].cells) != 2 {
t.Fatalf("cells = %v, want 2", len(tb.rows[0].cells))
}
if tb.rows[0].cells[0].Content != "alpha" || tb.rows[0].cells[1].Content != "beta" {
t.Error("cell objects not added")
}
}
func TestTableRowBuilderRowStyle(t *testing.T) {
b := newBuilderWithPage(t)
rowStyle := NewTableCellStyleOpt().Italic()
tb := b.Table().BuildRow().RowStyle(rowStyle).Cell("x").Cell("y").BuildRow()
for i, c := range tb.rows[0].cells {
if c.Style.fontStyleOverride == nil || *c.Style.fontStyleOverride != Italic {
t.Errorf("cell[%d] should use row style (Italic)", i)
}
}
}
func TestTableMaxFontSize(t *testing.T) {
row := tableRow{
cells: []TableCell{
{Style: *NewTableCellStyleOpt().FontSize(10)},
{Style: *NewTableCellStyleOpt().FontSize(20)},
{Style: *NewTableCellStyleOpt().FontSize(15)},
},
}
got := row.maxFontSize(8)
if got != 20 {
t.Errorf("maxFontSize = %v, want 20", got)
}
rowEmpty := tableRow{cells: []TableCell{{Style: TableCellStyleOpt{}}}}
got = rowEmpty.maxFontSize(12)
if got != 12 {
t.Errorf("maxFontSize default = %v, want 12", got)
}
}
func TestTableBuilderBuildEmpty(t *testing.T) {
b := newBuilderWithPage(t)
// Should not panic when no rows
b.Table().Build()
}
func TestTableBuilderBuildNumeric(t *testing.T) {
b := newBuilderWithPage(t)
b.Table().
Widths("30", "30", "30").
AddRowDefaultStyle("a", "b", "c").
AddRowDefaultStyle("d", "e", "f").
Build()
bin, err := b.Build()
if err != nil {
t.Fatalf("Build error: %v", err)
}
if !bytes.HasPrefix(bin, []byte("%PDF-")) {
t.Error("output not a PDF")
}
}
func TestTableBuilderBuildAuto(t *testing.T) {
b := newBuilderWithPage(t)
b.Table().
Widths("auto", "auto", "auto").
AddRowDefaultStyle("a", "b", "c").
Build()
if _, err := b.Build(); err != nil {
t.Fatalf("Build error: %v", err)
}
}
func TestTableBuilderBuildFr(t *testing.T) {
b := newBuilderWithPage(t)
b.Table().
Widths("1fr", "2fr", "*").
AddRowDefaultStyle("a", "b", "c").
Build()
if _, err := b.Build(); err != nil {
t.Fatalf("Build error: %v", err)
}
}
func TestTableBuilderMixedColumnSpecs(t *testing.T) {
b := newBuilderWithPage(t)
b.Table().
Widths("auto", "30", "1fr", "*").
AddRowDefaultStyle("a", "b", "c", "d").
AddRowDefaultStyle("longer text here", "x", "y", "z").
Build()
if _, err := b.Build(); err != nil {
t.Fatalf("Build error: %v", err)
}
}
func TestTableBuilderNoColumnsDefined(t *testing.T) {
b := newBuilderWithPage(t)
// When Widths not specified, table uses "*" for each cell of first row
b.Table().
AddRowDefaultStyle("a", "b").
AddRowDefaultStyle("c", "d").
Build()
if _, err := b.Build(); err != nil {
t.Fatalf("Build error: %v", err)
}
}
func TestTableBuilderMismatchedColumnCount(t *testing.T) {
b := newBuilderWithPage(t)
b.Table().
Widths("10", "10").
AddRowDefaultStyle("a", "b", "c"). // wrong column count
Build()
// Should produce an error in the underlying gofpdf, surfaced by Build()
_, err := b.Build()
if err == nil {
t.Error("expected error for mismatched column count, got nil")
}
}
func TestTableBuilderInvalidColumnWidth(t *testing.T) {
b := newBuilderWithPage(t)
b.Table().
Widths("not-a-number").
AddRowDefaultStyle("a").
Build()
_, err := b.Build()
if err == nil {
t.Error("expected error for invalid column width, got nil")
}
}
func TestTableBuilderMultiCellRow(t *testing.T) {
b := newBuilderWithPage(t)
style := NewTableCellStyleOpt().MultiCell(true)
b.Table().
Widths("auto", "1fr").
AddRowWithStyle(style, "a", "Multi line\ntext content").
Build()
if _, err := b.Build(); err != nil {
t.Fatalf("Build error: %v", err)
}
}
func TestTableBuilderEllipsizeRow(t *testing.T) {
b := newBuilderWithPage(t)
style := NewTableCellStyleOpt().MultiCell(false).Ellipsize(true)
b.Table().
Widths("20").
AddRowWithStyle(style, "this is a very long text that should be ellipsized").
Build()
if _, err := b.Build(); err != nil {
t.Fatalf("Build error: %v", err)
}
}
func TestTableBuilderDefaultTableStyle(t *testing.T) {
s := defaultTableStyle()
if s == nil {
t.Fatal("defaultTableStyle returned nil")
}
if s.minWidth == nil || *s.minWidth != 5 {
t.Errorf("default minWidth = %v, want 5", s.minWidth)
}
if s.ellipsize == nil || !*s.ellipsize {
t.Errorf("default ellipsize should be true")
}
if s.multiCell == nil || *s.multiCell {
t.Errorf("default multiCell should be false")
}
if s.fontSizeOverride == nil || *s.fontSizeOverride != 8 {
t.Errorf("default font size = %v, want 8", s.fontSizeOverride)
}
}
func TestTableCellStyleOptBuilders(t *testing.T) {
o := NewTableCellStyleOpt().
MultiCell(true).
Ellipsize(false).
PaddingHorz(3).
MinWidth(7).
FillHeight(true)
if !*o.multiCell {
t.Error("multiCell")
}
if *o.ellipsize {
t.Error("ellipsize")
}
if *o.paddingHorz != 3 {
t.Error("paddingHorz")
}
if *o.minWidth != 7 {
t.Error("minWidth")
}
if !*o.fillHeight {
t.Error("fillHeight")
}
}
func TestTableCellStyleOptCellStyle(t *testing.T) {
cell := *NewPDFCellOpt().Width(50).Bold()
o := NewTableCellStyleOpt().CellStyle(cell)
if o.PDFCellOpt.width == nil || *o.PDFCellOpt.width != 50 {
t.Error("CellStyle did not transfer width")
}
if o.PDFCellOpt.fontStyleOverride == nil || *o.PDFCellOpt.fontStyleOverride != Bold {
t.Error("CellStyle did not transfer style")
}
}
func TestTableCellStyleOptDelegates(t *testing.T) {
o := NewTableCellStyleOpt().
Width(10).
Height(20).
Border(BorderFull).
LnPos(BreakToBelow).
Align(AlignRight).
FillBackground(true).
Link(5).
LinkStr("link").
Font(FontTimes, Bold, 11).
LnAfter(2).
X(3).
AutoWidth().
AutoWidthPaddingX(1).
TextColor(1, 2, 3).
BorderColor(4, 5, 6).
FillColor(7, 8, 9).
Alpha(0.5, BlendNormal).
Debug(true)
if *o.PDFCellOpt.width != 10 {
t.Error("width")
}
if *o.PDFCellOpt.height != 20 {
t.Error("height")
}
if *o.PDFCellOpt.border != BorderFull {
t.Error("border")
}
if *o.PDFCellOpt.ln != BreakToBelow {
t.Error("ln")
}
if *o.PDFCellOpt.align != AlignRight {
t.Error("align")
}
if !*o.PDFCellOpt.fill {
t.Error("fill")
}
if *o.PDFCellOpt.link != 5 {
t.Error("link")
}
if *o.PDFCellOpt.linkStr != "link" {
t.Error("linkStr")
}
if *o.PDFCellOpt.fontNameOverride != FontTimes {
t.Error("fontName")
}
if *o.PDFCellOpt.fontStyleOverride != Bold {
t.Error("fontStyle")
}
if *o.PDFCellOpt.fontSizeOverride != 11 {
t.Error("fontSize")
}
if *o.PDFCellOpt.extraLn != 2 {
t.Error("extraLn")
}
if *o.PDFCellOpt.x != 3 {
t.Error("x")
}
if !*o.PDFCellOpt.autoWidth {
t.Error("autoWidth")
}
if *o.PDFCellOpt.autoWidthPaddingX != 1 {
t.Error("autoWidthPaddingX")
}
if *o.PDFCellOpt.textColor != (PDFColor{R: 1, G: 2, B: 3}) {
t.Error("textColor")
}
if *o.PDFCellOpt.borderColor != (PDFColor{R: 4, G: 5, B: 6}) {
t.Error("borderColor")
}
if *o.PDFCellOpt.fillColor != (PDFColor{R: 7, G: 8, B: 9}) {
t.Error("fillColor")
}
if o.PDFCellOpt.alphaOverride.V1 != 0.5 || o.PDFCellOpt.alphaOverride.V2 != BlendNormal {
t.Error("alpha")
}
if !*o.PDFCellOpt.debug {
t.Error("debug")
}
}
func TestTableCellStyleOptBoldItalic(t *testing.T) {
if *NewTableCellStyleOpt().Bold().PDFCellOpt.fontStyleOverride != Bold {
t.Error("Bold")
}
if *NewTableCellStyleOpt().Italic().PDFCellOpt.fontStyleOverride != Italic {
t.Error("Italic")
}
}
func TestTableCellStyleOptHexColors(t *testing.T) {
o := NewTableCellStyleOpt().
TextColorHex(0x010203).
BorderColorHex(0x040506).
FillColorHex(0x070809)
if *o.PDFCellOpt.textColor != (PDFColor{R: 1, G: 2, B: 3}) {
t.Error("text hex")
}
if *o.PDFCellOpt.borderColor != (PDFColor{R: 4, G: 5, B: 6}) {
t.Error("border hex")
}
if *o.PDFCellOpt.fillColor != (PDFColor{R: 7, G: 8, B: 9}) {
t.Error("fill hex")
}
}
func TestTableCellStyleOptIndividualFontSetters(t *testing.T) {
o := NewTableCellStyleOpt().FontName(FontCourier).FontStyle(Italic).FontSize(9)
if *o.PDFCellOpt.fontNameOverride != FontCourier {
t.Error("FontName")
}
if *o.PDFCellOpt.fontStyleOverride != Italic {
t.Error("FontStyle")
}
if *o.PDFCellOpt.fontSizeOverride != 9 {
t.Error("FontSize")
}
}
func TestTableCalculateColumnsNumeric(t *testing.T) {
b := newBuilderWithPage(t)
tb := b.Table().
Widths("30", "20", "40").
AddRowDefaultStyle("a", "b", "c")
w := tb.calculateColumns()
if len(w) != 3 {
t.Fatalf("widths = %v, want 3", len(w))
}
if w[0] != 30 || w[1] != 20 || w[2] != 40 {
t.Errorf("widths = %v, want [30, 20, 40]", w)
}
}
func TestTableCalculateColumnsFrSplit(t *testing.T) {
b := newBuilderWithPage(t)
b.SetMargins(PDFMargins{Left: 0, Top: 0, Right: 0})
tb := b.Table().
PadX(0).
Widths("1fr", "1fr").
AddRowDefaultStyle("a", "b")
pageW := b.GetPageWidth()
w := tb.calculateColumns()
if len(w) != 2 {
t.Fatalf("widths = %v, want 2", len(w))
}
// fr columns are bounded by autoWidths (max content); since "a" and "b"
// are very narrow strings, both columns get the same auto-bounded width.
if math.Abs(w[0]-w[1]) > 0.01 {
t.Errorf("expected fr split widths roughly equal, got %v and %v", w[0], w[1])
}
// Total should not exceed available page width.
if w[0]+w[1] > pageW+0.01 {
t.Errorf("total width %v exceeds pageW %v", w[0]+w[1], pageW)
}
}
func TestTableCalculateColumnsAutoUsesMinWidth(t *testing.T) {
b := newBuilderWithPage(t)
style := *NewTableCellStyleOpt()
mw := 50.0
style.minWidth = &mw
tb := b.Table().
Widths("auto").
AddRowWithStyle(&style, "x")
w := tb.calculateColumns()
if len(w) != 1 {
t.Fatalf("widths = %v, want 1", len(w))
}
if w[0] < 50 {
t.Errorf("width %v should respect minWidth=50", w[0])
}
}
func TestTableCalculateColumnsNoRows(t *testing.T) {
b := newBuilderWithPage(t)
tb := b.Table()
w := tb.calculateColumns()
if len(w) != 0 {
t.Errorf("widths for empty table = %v, want []", w)
}
}