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) } }