golib/helper/table/render_test.go

1871 lines
153 KiB
Go
Raw Normal View History

//
// render_test.go
// Copyright (C) 2023 tiglog <me@tiglog.com>
//
// Distributed under terms of the MIT license.
//
2023-08-20 13:50:39 +08:00
package table
import (
"fmt"
"sort"
"strings"
"testing"
"git.hexq.cn/tiglog/golib/helper/text"
"github.com/stretchr/testify/assert"
)
func compareOutput(t *testing.T, out string, expectedOut string) {
if strings.HasPrefix(expectedOut, "\n") {
expectedOut = strings.Replace(expectedOut, "\n", "", 1)
}
assert.Equal(t, expectedOut, out)
if out != expectedOut {
fmt.Printf("Expected:\n%s\nActual:\n%s\n", expectedOut, out)
} else {
fmt.Println(out)
}
}
func compareOutputColored(t *testing.T, out string, expectedOut string) {
if strings.HasPrefix(expectedOut, "\n") {
expectedOut = strings.Replace(expectedOut, "\n", "", 1)
}
assert.Equal(t, expectedOut, out)
if out != expectedOut {
fmt.Printf("Expected:\n%s\nActual:\n%s\n", expectedOut, out)
// dump formatted output that can be "pasted" into the expectation in
// the test in case of valid changed behavior
outLines := strings.Split(out, "\n")
fmt.Printf("\"\" +\n")
for idx, line := range outLines {
if idx < len(outLines)-1 {
fmt.Printf("%#v +", line+"\n")
} else {
fmt.Printf("%#v,", line)
}
fmt.Printf("\n")
}
} else {
fmt.Println(out)
}
}
func generateColumnConfigsWithHiddenColumns(colsToHide []int) []ColumnConfig {
cc := []ColumnConfig{
{
Name: "#",
Transformer: func(val interface{}) string {
return fmt.Sprint(val.(int) + 7)
},
}, {
Name: "First Name",
Transformer: func(val interface{}) string {
return fmt.Sprintf(">>%s", val)
},
}, {
Name: "Last Name",
Transformer: func(val interface{}) string {
return fmt.Sprintf("%s<<", val)
},
}, {
Name: "Salary",
Transformer: func(val interface{}) string {
return fmt.Sprint(val.(int) + 13)
},
}, {
Number: 5,
Transformer: func(val interface{}) string {
return fmt.Sprintf("~%s~", val)
},
},
}
for _, colToHide := range colsToHide {
cc[colToHide].Hidden = true
}
return cc
}
func TestTable_Render(t *testing.T) {
tw := NewWriter()
tw.AppendHeader(testHeader)
tw.AppendRows(testRows)
tw.AppendRow(testRowMultiLine)
tw.AppendFooter(testFooter)
tw.SetCaption(testCaption)
tw.SetStyle(styleTest)
tw.SetTitle(testTitle2)
compareOutput(t, tw.Render(), `
(---------------------------------------------------------------------)
[<When you play the Game of Thrones, you win or you die. There is no >]
[<middle ground. >]
{-----^------------^-----------^--------^-----------------------------}
[< #>|<FIRST NAME>|<LAST NAME>|<SALARY>|< >]
{-----+------------+-----------+--------+-----------------------------}
[< 1>|<Arya >|<Stark >|< 3000>|< >]
[< 20>|<Jon >|<Snow >|< 2000>|<You know nothing, Jon Snow!>]
[<300>|<Tyrion >|<Lannister>|< 5000>|< >]
[< 0>|<Winter >|<Is >|< 0>|<Coming. >]
[< >|< >|< >|< >|<The North Remembers! >]
[< >|< >|< >|< >|<This is known. >]
{-----+------------+-----------+--------+-----------------------------}
[< >|< >|<TOTAL >|< 10000>|< >]
\-----v------------v-----------v--------v-----------------------------/
A Song of Ice and Fire`)
}
func TestTable_Render_AutoIndex(t *testing.T) {
tw := NewWriter()
for rowIdx := 0; rowIdx < 10; rowIdx++ {
row := make(Row, 10)
for colIdx := 0; colIdx < 10; colIdx++ {
row[colIdx] = fmt.Sprintf("%s%d", AutoIndexColumnID(colIdx), rowIdx+1)
}
tw.AppendRow(row)
}
tw.SetAutoIndex(true)
tw.SetStyle(StyleLight)
compareOutput(t, tw.Render(), `
A B C D E F G H I J
1 A1 B1 C1 D1 E1 F1 G1 H1 I1 J1
2 A2 B2 C2 D2 E2 F2 G2 H2 I2 J2
3 A3 B3 C3 D3 E3 F3 G3 H3 I3 J3
4 A4 B4 C4 D4 E4 F4 G4 H4 I4 J4
5 A5 B5 C5 D5 E5 F5 G5 H5 I5 J5
6 A6 B6 C6 D6 E6 F6 G6 H6 I6 J6
7 A7 B7 C7 D7 E7 F7 G7 H7 I7 J7
8 A8 B8 C8 D8 E8 F8 G8 H8 I8 J8
9 A9 B9 C9 D9 E9 F9 G9 H9 I9 J9
10 A10 B10 C10 D10 E10 F10 G10 H10 I10 J10
`)
}
func TestTable_Render_AutoMerge(t *testing.T) {
rcAutoMerge := RowConfig{AutoMerge: true}
t.Run("columns only", func(t *testing.T) {
tw := NewWriter()
tw.AppendHeader(Row{"Node IP", "Pods", "Namespace", "Container", "RCE\nEXE", "RCE\nRUN"})
tw.AppendRow(Row{"1.1.1.1", "Pod 1A", "NS 1A", "C 1", "Y", "Y"})
tw.AppendRow(Row{"1.1.1.1", "Pod 1A", "NS 1A", "C 2", "Y", "N"})
tw.AppendRow(Row{"1.1.1.1", "Pod 1A", "NS 1B", "C 3", "N", "N"})
tw.AppendRow(Row{"1.1.1.1", "Pod 1B", "NS 2", "C 4", "N", "N"})
tw.AppendRow(Row{"1.1.1.1", "Pod 1B", "NS 2", "C 5", "Y", "N"})
tw.AppendRow(Row{"2.2.2.2", "Pod 2", "NS 3", "C 6", "Y", "Y"})
tw.AppendRow(Row{"2.2.2.2", "Pod 2", "NS 3", "C 7", "Y", "Y"})
tw.AppendFooter(Row{"", "", "", 7, 5, 3})
tw.SetAutoIndex(true)
tw.SetColumnConfigs([]ColumnConfig{
{Number: 1, AutoMerge: true},
{Number: 2, AutoMerge: true},
{Number: 3, AutoMerge: true},
{Number: 4, AutoMerge: true},
{Number: 5, Align: text.AlignCenter, AlignFooter: text.AlignCenter, AlignHeader: text.AlignCenter},
{Number: 6, Align: text.AlignCenter, AlignFooter: text.AlignCenter, AlignHeader: text.AlignCenter},
})
tw.SetStyle(StyleLight)
tw.Style().Options.SeparateRows = true
compareOutput(t, tw.Render(), `
NODE IP PODS NAMESPACE CONTAINER RCE RCE
EXE RUN
1 1.1.1.1 Pod 1A NS 1A C 1 Y Y
2 C 2 Y N
3 NS 1B C 3 N N
4 Pod 1B NS 2 C 4 N N
5 C 5 Y N
6 2.2.2.2 Pod 2 NS 3 C 6 Y Y
7 C 7 Y Y
7 5 3
`)
})
t.Run("columns only with hidden columns", func(t *testing.T) {
tw := NewWriter()
tw.AppendHeader(Row{"Node IP", "Pods", "Namespace", "Container", "RCE\nEXE", "RCE\nRUN"})
tw.AppendRow(Row{"1.1.1.1", "Pod 1A", "NS 1A", "C 1", "Y", "Y"})
tw.AppendRow(Row{"1.1.1.1", "Pod 1A", "NS 1A", "C 2", "Y", "N"})
tw.AppendRow(Row{"1.1.1.1", "Pod 1A", "NS 1B", "C 3", "N", "N"})
tw.AppendRow(Row{"1.1.1.1", "Pod 1B", "NS 2", "C 4", "Y", "Y"})
tw.AppendRow(Row{"1.1.1.1", "Pod 1B", "NS 2", "C 5", "Y", "N"})
tw.AppendRow(Row{"2.2.2.2", "Pod 2", "NS 3", "C 6", "Y", "Y"})
tw.AppendRow(Row{"2.2.2.2", "Pod 2", "NS 3", "C 7", "Y", "N"})
tw.AppendFooter(Row{"", "", "", 7, 5, 3})
tw.SetColumnConfigs([]ColumnConfig{
{Number: 1, AutoMerge: true},
{Number: 2, AutoMerge: true},
{Number: 3, AutoMerge: true},
{Number: 4, Hidden: true},
{Number: 5, Hidden: true, Align: text.AlignCenter},
{Number: 6, Hidden: true, Align: text.AlignCenter},
})
tw.SetStyle(StyleLight)
tw.Style().Options.SeparateRows = true
compareOutput(t, tw.Render(), `
NODE IP PODS NAMESPACE
1.1.1.1 Pod 1A NS 1A
NS 1B
Pod 1B NS 2
2.2.2.2 Pod 2 NS 3
`)
})
t.Run("rows only", func(t *testing.T) {
tw := NewWriter()
tw.AppendHeader(Row{"Node IP", "Pods", "Namespace", "Container", "RCE", "RCE"}, rcAutoMerge)
tw.AppendHeader(Row{"", "", "", "", "EXE", "RUN"})
tw.AppendRow(Row{"1.1.1.1", "Pod 1A", "NS 1A", "C 1", "Y", "Y"}, rcAutoMerge)
tw.AppendRow(Row{"1.1.1.1", "Pod 1A", "NS 1A", "C 2", "Y", "N"})
tw.AppendRow(Row{"1.1.1.1", "Pod 1A", "NS 1B", "C 3", "N", "N"})
tw.AppendRow(Row{"1.1.1.1", "Pod 1B", "NS 2", "C 4", "N", "N"}, rcAutoMerge)
tw.AppendRow(Row{"1.1.1.1", "Pod 1B", "NS 2", "C 5", "Y", "N"})
tw.AppendRow(Row{"2.2.2.2", "Pod 2", "NS 3", "C 6", "Y", "Y"}, rcAutoMerge)
tw.AppendRow(Row{"2.2.2.2", "Pod 2", "NS 3", "C 7", "Y", "Y"}, RowConfig{AutoMerge: true, AutoMergeAlign: text.AlignRight})
tw.AppendFooter(Row{"", "", "", 7, 5, 3})
tw.SetAutoIndex(true)
tw.SetColumnConfigs([]ColumnConfig{
{Number: 5, Align: text.AlignCenter, AlignFooter: text.AlignCenter, AlignHeader: text.AlignCenter},
{Number: 6, Align: text.AlignCenter, AlignFooter: text.AlignCenter, AlignHeader: text.AlignCenter},
})
tw.SetStyle(StyleLight)
tw.Style().Options.SeparateRows = true
compareOutput(t, tw.Render(), `
NODE IP PODS NAMESPACE CONTAINER RCE
EXE RUN
1 1.1.1.1 Pod 1A NS 1A C 1 Y
2 1.1.1.1 Pod 1A NS 1A C 2 Y N
3 1.1.1.1 Pod 1A NS 1B C 3 N N
4 1.1.1.1 Pod 1B NS 2 C 4 N
5 1.1.1.1 Pod 1B NS 2 C 5 Y N
6 2.2.2.2 Pod 2 NS 3 C 6 Y
7 2.2.2.2 Pod 2 NS 3 C 7 Y
7 5 3
`)
})
t.Run("rows and columns", func(t *testing.T) {
tw := NewWriter()
tw.AppendHeader(Row{"Node IP", "Pods", "Namespace", "Container", "RCE", "RCE"}, rcAutoMerge)
tw.AppendHeader(Row{"", "", "", "", "EXE", "RUN"})
tw.AppendRow(Row{"1.1.1.1", "Pod 1A", "NS 1A", "C 1", "Y", "Y"}, rcAutoMerge)
tw.AppendRow(Row{"1.1.1.1", "Pod 1A", "NS 1A", "C 2", "Y", "N"}, rcAutoMerge)
tw.AppendRow(Row{"1.1.1.1", "Pod 1A", "NS 1B", "C 3", "N", "N"}, rcAutoMerge)
tw.AppendRow(Row{"1.1.1.1", "Pod 1B", "NS 2", "C 4", "N", "N"}, rcAutoMerge)
tw.AppendRow(Row{"1.1.1.1", "Pod 1B", "NS 2", "C 5", "Y", "N"}, rcAutoMerge)
tw.AppendRow(Row{"2.2.2.2", "Pod 2", "NS 3", "C 6", "Y", "Y"}, rcAutoMerge)
tw.AppendRow(Row{"2.2.2.2", "Pod 2", "NS 3", "C 7", "Y", "Y"}, rcAutoMerge)
tw.AppendFooter(Row{"", "", "", 7, 5, 3})
tw.SetAutoIndex(true)
tw.SetColumnConfigs([]ColumnConfig{
{Number: 1, AutoMerge: true},
{Number: 2, AutoMerge: true},
{Number: 3, AutoMerge: true},
{Number: 4, AutoMerge: true},
{Number: 5, Align: text.AlignCenter, AlignFooter: text.AlignCenter, AlignHeader: text.AlignCenter},
{Number: 6, Align: text.AlignCenter, AlignFooter: text.AlignCenter, AlignHeader: text.AlignCenter},
})
tw.SetStyle(StyleLight)
tw.Style().Options.SeparateRows = true
compareOutput(t, tw.Render(), `
NODE IP PODS NAMESPACE CONTAINER RCE
EXE RUN
1 1.1.1.1 Pod 1A NS 1A C 1 Y
2 C 2 Y N
3 NS 1B C 3 N
4 Pod 1B NS 2 C 4 N
5 C 5 Y N
6 2.2.2.2 Pod 2 NS 3 C 6 Y
7 C 7 Y
7 5 3
`)
})
t.Run("rows and columns no headers or footers", func(t *testing.T) {
tw := NewWriter()
tw.AppendRow(Row{"1.1.1.1", "Pod 1A", "NS 1A", "C 1", "Y", "Y"}, rcAutoMerge)
tw.AppendRow(Row{"1.1.1.1", "Pod 1A", "NS 1A", "C 2", "Y", "N"})
tw.AppendRow(Row{"1.1.1.1", "Pod 1A", "NS 1B", "C 3", "N", "N"})
tw.AppendRow(Row{"1.1.1.1", "Pod 1B", "NS 2", "C 4", "N", "N"}, rcAutoMerge)
tw.AppendRow(Row{"1.1.1.1", "Pod 1B", "NS 2", "C 5", "Y", "N"})
tw.AppendRow(Row{"2.2.2.2", "Pod 2", "NS 3", "C 6", "Y", "Y"}, rcAutoMerge)
tw.AppendRow(Row{"2.2.2.2", "Pod 2", "NS 3", "C 7", "Y", "Y"}, RowConfig{AutoMerge: true, AutoMergeAlign: text.AlignRight})
tw.SetColumnConfigs([]ColumnConfig{
{Number: 5, Align: text.AlignCenter, AlignHeader: text.AlignCenter},
{Number: 6, Align: text.AlignCenter, AlignHeader: text.AlignCenter},
})
tw.SetStyle(StyleLight)
tw.Style().Options.SeparateRows = true
compareOutput(t, tw.Render(), `
1.1.1.1 Pod 1A NS 1A C 1 Y
1.1.1.1 Pod 1A NS 1A C 2 Y N
1.1.1.1 Pod 1A NS 1B C 3 N N
1.1.1.1 Pod 1B NS 2 C 4 N
1.1.1.1 Pod 1B NS 2 C 5 Y N
2.2.2.2 Pod 2 NS 3 C 6 Y
2.2.2.2 Pod 2 NS 3 C 7 Y
`)
})
t.Run("rows and columns no headers or footers with auto-index", func(t *testing.T) {
tw := NewWriter()
tw.AppendRow(Row{"1.1.1.1", "Pod 1A", "NS 1A", "C 1", "Y", "Y"}, rcAutoMerge)
tw.AppendRow(Row{"1.1.1.1", "Pod 1A", "NS 1A", "C 2", "Y", "N"})
tw.AppendRow(Row{"1.1.1.1", "Pod 1A", "NS 1B", "C 3", "N", "N"})
tw.AppendRow(Row{"1.1.1.1", "Pod 1B", "NS 2", "C 4", "N", "N"}, rcAutoMerge)
tw.AppendRow(Row{"1.1.1.1", "Pod 1B", "NS 2", "C 5", "Y", "N"})
tw.AppendRow(Row{"2.2.2.2", "Pod 2", "NS 3", "C 6", "Y", "Y"}, rcAutoMerge)
tw.AppendRow(Row{"2.2.2.2", "Pod 2", "NS 3", "C 7", "Y", "Y"}, RowConfig{AutoMerge: true, AutoMergeAlign: text.AlignRight})
tw.SetAutoIndex(true)
tw.SetColumnConfigs([]ColumnConfig{
{Number: 5, Align: text.AlignCenter, AlignHeader: text.AlignCenter},
{Number: 6, Align: text.AlignCenter, AlignHeader: text.AlignCenter},
})
tw.SetStyle(StyleLight)
tw.Style().Options.SeparateRows = true
compareOutput(t, tw.Render(), `
A B C D E F
1 1.1.1.1 Pod 1A NS 1A C 1 Y
2 1.1.1.1 Pod 1A NS 1A C 2 Y N
3 1.1.1.1 Pod 1A NS 1B C 3 N N
4 1.1.1.1 Pod 1B NS 2 C 4 N
5 1.1.1.1 Pod 1B NS 2 C 5 Y N
6 2.2.2.2 Pod 2 NS 3 C 6 Y
7 2.2.2.2 Pod 2 NS 3 C 7 Y
`)
})
t.Run("rows and columns and footers", func(t *testing.T) {
tw := NewWriter()
tw.AppendHeader(Row{"Node IP", "Pods", "Namespace", "Container", "RCE", "RCE", "ID"}, rcAutoMerge)
tw.AppendHeader(Row{"", "", "", "", "EXE", "RUN", ""})
tw.AppendRow(Row{"1.1.1.1", "Pod 1A", "NS 1A", "C 1", "Y", "Y", 123}, rcAutoMerge)
tw.AppendRow(Row{"1.1.1.1", "Pod 1A", "NS 1A", "C 2", "Y", "N", 234})
tw.AppendRow(Row{"1.1.1.1", "Pod 1A", "NS 1B", "C 3", "N", "N", 345})
tw.AppendRow(Row{"1.1.1.1", "Pod 1B", "NS 2", "C 4", "N", "N", 456}, rcAutoMerge)
tw.AppendRow(Row{"1.1.1.1", "Pod 1B", "NS 2", "C 5", "Y", "N", 567})
tw.AppendRow(Row{"2.2.2.2", "Pod 2", "NS 3", "C 6", "Y", "Y", 678}, rcAutoMerge)
tw.AppendRow(Row{"2.2.2.2", "Pod 2", "NS 3", "C 7", "Y", "Y", 789}, rcAutoMerge)
tw.AppendFooter(Row{"", "", "", 7, 5, 5}, rcAutoMerge)
tw.AppendFooter(Row{"", "", "", 7, 5, 3}, rcAutoMerge)
tw.AppendFooter(Row{"", "", "", 7, 5, 5}, rcAutoMerge)
tw.AppendFooter(Row{"", "", "", 7, 5, 3}, rcAutoMerge)
tw.AppendFooter(Row{"", "", "", 7, 5, 5}, rcAutoMerge)
tw.SetAutoIndex(true)
tw.SetColumnConfigs([]ColumnConfig{
{Number: 1, AutoMerge: true},
{Number: 2, AutoMerge: true},
{Number: 3, AutoMerge: true},
{Number: 4, AutoMerge: true},
{Number: 5, Align: text.AlignCenter, AlignFooter: text.AlignCenter, AlignHeader: text.AlignCenter},
{Number: 6, Align: text.AlignCenter, AlignFooter: text.AlignCenter, AlignHeader: text.AlignCenter},
})
tw.SetStyle(StyleLight)
tw.Style().Options.SeparateRows = true
compareOutput(t, tw.Render(), `
NODE IP PODS NAMESPACE CONTAINER RCE ID
EXE RUN
1 1.1.1.1 Pod 1A NS 1A C 1 Y 123
2 C 2 Y N 234
3 NS 1B C 3 N N 345
4 Pod 1B NS 2 C 4 N 456
5 C 5 Y N 567
6 2.2.2.2 Pod 2 NS 3 C 6 Y 678
7 C 7 Y 789
7 5
5 3
5
5 3
5
`)
})
t.Run("samurai sudoku", func(t *testing.T) {
tw := NewWriter()
tw.AppendRow(Row{"1.1\n1.1", "1.2\n1.2", "1.3\n1.3", " ", "2.1\n2.1", "2.2\n2.2", "2.3\n2.3"})
tw.AppendRow(Row{"1.4\n1.4", "1.5\n1.5", "1.6\n1.6", " ", "2.4\n2.4", "2.5\n2.5", "2.6\n2.6"})
tw.AppendRow(Row{"1.7\n1.7", "1.8\n1.8", "1.9\n0.1", "0.2\n0.2", "2.7\n0.3", "2.8\n2.8", "2.9\n2.9"})
tw.AppendRow(Row{" ", " ", "0.4\n0.4", "0.5\n0.5", "0.6\n0.6", " ", " "}, rcAutoMerge)
tw.AppendRow(Row{"3.1\n3.1", "3.2\n3.2", "3.3\n0.7", "0.8\n0.8", "4.1\n0.9", "4.2\n4.2", "4.3\n4.3"})
tw.AppendRow(Row{"3.4\n3.4", "3.5\n3.5", "3.6\n3.6", " ", "4.4\n4.4", "4.5\n4.5", "4.6\n4.6"})
tw.AppendRow(Row{"3.7\n3.7", "3.8\n3.8", "3.9\n3.9", " ", "4.7\n4.7", "4.8\n4.8", "4.9\n4.9"})
tw.SetColumnConfigs([]ColumnConfig{
{Number: 4, AutoMerge: true},
})
tw.SetStyle(StyleLight)
tw.Style().Box.PaddingLeft = ""
tw.Style().Box.PaddingRight = ""
tw.Style().Options.DrawBorder = true
tw.Style().Options.SeparateRows = true
tw.Style().Options.SeparateColumns = true
compareOutput(t, tw.Render(), `
1.11.21.3 2.12.22.3
1.11.21.3 2.12.22.3
1.41.51.6 2.42.52.6
1.41.51.6 2.42.52.6
1.71.81.90.22.72.82.9
1.71.80.10.20.32.82.9
0.40.50.6
0.40.50.6
3.13.23.30.84.14.24.3
3.13.20.70.80.94.24.3
3.43.53.6 4.44.54.6
3.43.53.6 4.44.54.6
3.73.83.9 4.74.84.9
3.73.83.9 4.74.84.9
`)
})
t.Run("long column no merge", func(t *testing.T) {
tw := NewWriter()
tw.AppendHeader(Row{"Column 1", "Column 2", "Column 3", "Column 4", "Column 5", "Column 6", "Column 7", "Column 8"}, rcAutoMerge)
tw.AppendRow(Row{"1.1.1.1", "Pod 1A", "NS 1A", "C 1", "4F8F5CB531E3D49A61CF417CD133792CCFA501FD8DA53EE368FED20E5FE0248C3A0B64F98A6533CEE1DA614C3A8DDEC791FF05FEE6D971D57C1348320F4EB42DR", "4F8F5CB531E3D49A61CF417CD133792CCFA501FD8DA53EE368FED20E5FE0248C3A0B64F98A6533CEE1DA614C3A8DDEC791FF05FEE6D971D57C1348320F4EB42DRW", "4F8F5CB531E3D49A61CF417CD133792CCFA501FD8DA53EE368FED20E5FE0248C3A0B64F98A6533CEE1DA614C3A8DDEC791FF05FEE6D971D57C1348320F4EB42DRH", "4F8F5CB531E3D49A61CF417CD133792CCFA501FD8DA53EE368FED20E5FE0248C3A0B64F98A6533CEE1DA614C3A8DDEC791FF05FEE6D971D57C1348320F4EB42DRY"}, rcAutoMerge)
tw.AppendRow(Row{"1.1.1.1", "Pod 1A", "NS 1A", "C 2", "Y", "Y", "Y", "Y"}, rcAutoMerge)
tw.AppendRow(Row{"1.1.1.1", "Pod 1A", "NS 1A", "C 2", "Y", "Y", "Y", "Y"}, rcAutoMerge)
tw.SetAutoIndex(true)
tw.SetColumnConfigs([]ColumnConfig{
{Number: 5, Align: text.AlignCenter, AlignFooter: text.AlignCenter, AlignHeader: text.AlignCenter, WidthMax: 24, WidthMaxEnforcer: text.WrapHard},
{Number: 6, Align: text.AlignCenter, AlignFooter: text.AlignCenter, AlignHeader: text.AlignCenter, WidthMax: 24, WidthMaxEnforcer: text.WrapHard},
{Number: 7, Align: text.AlignCenter, AlignFooter: text.AlignCenter, AlignHeader: text.AlignCenter, WidthMax: 24, WidthMaxEnforcer: text.WrapHard},
{Number: 8, Align: text.AlignCenter, AlignFooter: text.AlignCenter, AlignHeader: text.AlignCenter, WidthMax: 24, WidthMaxEnforcer: text.WrapHard},
})
tw.SetStyle(StyleLight)
tw.Style().Options.SeparateRows = true
compareOutput(t, tw.Render(), `
COLUMN 1 COLUMN 2 COLUMN 3 COLUMN 4 COLUMN 5 COLUMN 6 COLUMN 7 COLUMN 8
1 1.1.1.1 Pod 1A NS 1A C 1 4F8F5CB531E3D49A61CF417C 4F8F5CB531E3D49A61CF417C 4F8F5CB531E3D49A61CF417C 4F8F5CB531E3D49A61CF417C
D133792CCFA501FD8DA53EE3 D133792CCFA501FD8DA53EE3 D133792CCFA501FD8DA53EE3 D133792CCFA501FD8DA53EE3
68FED20E5FE0248C3A0B64F9 68FED20E5FE0248C3A0B64F9 68FED20E5FE0248C3A0B64F9 68FED20E5FE0248C3A0B64F9
8A6533CEE1DA614C3A8DDEC7 8A6533CEE1DA614C3A8DDEC7 8A6533CEE1DA614C3A8DDEC7 8A6533CEE1DA614C3A8DDEC7
91FF05FEE6D971D57C134832 91FF05FEE6D971D57C134832 91FF05FEE6D971D57C134832 91FF05FEE6D971D57C134832
0F4EB42DR 0F4EB42DRW 0F4EB42DRH 0F4EB42DRY
2 1.1.1.1 Pod 1A NS 1A C 2 Y
3 1.1.1.1 Pod 1A NS 1A C 2 Y
`)
})
t.Run("long column partially merged #1", func(t *testing.T) {
tw := NewWriter()
tw.AppendHeader(Row{"Column 1", "Column 2", "Column 3", "Column 4", "Column 5", "Column 6", "Column 7", "Column 8"}, rcAutoMerge)
tw.AppendRow(Row{"1.1.1.1", "Pod 1A", "NS 1A", "C 1", "4F8F5CB531E3D49A61CF417CD133792CCFA501FD8DA53EE368FED20E5FE0248C3A0B64F98A6533CEE1DA614C3A8DDEC791FF05FEE6D971D57C1348320F4EB42DR", "4F8F5CB531E3D49A61CF417CD133792CCFA501FD8DA53EE368FED20E5FE0248C3A0B64F98A6533CEE1DA614C3A8DDEC791FF05FEE6D971D57C1348320F4EB42DR", "4F8F5CB531E3D49A61CF417CD133792CCFA501FD8DA53EE368FED20E5FE0248C3A0B64F98A6533CEE1DA614C3A8DDEC791FF05FEE6D971D57C1348320F4EB42DRR", "4F8F5CB531E3D49A61CF417CD133792CCFA501FD8DA53EE368FED20E5FE0248C3A0B64F98A6533CEE1DA614C3A8DDEC791FF05FEE6D971D57C1348320F4EB42DRR"}, rcAutoMerge)
tw.AppendRow(Row{"1.1.1.1", "Pod 1A", "NS 1A", "C 2", "Y", "Y", "Y", "Y"}, rcAutoMerge)
tw.AppendRow(Row{"1.1.1.1", "Pod 1A", "NS 1A", "C 2", "Y", "Y", "Y", "Y"}, rcAutoMerge)
tw.SetAutoIndex(true)
tw.SetColumnConfigs([]ColumnConfig{
{Number: 5, Align: text.AlignCenter, AlignFooter: text.AlignCenter, AlignHeader: text.AlignCenter, WidthMax: 24, WidthMaxEnforcer: text.WrapHard},
{Number: 6, Align: text.AlignCenter, AlignFooter: text.AlignCenter, AlignHeader: text.AlignCenter, WidthMax: 24, WidthMaxEnforcer: text.WrapHard},
{Number: 7, Align: text.AlignCenter, AlignFooter: text.AlignCenter, AlignHeader: text.AlignCenter, WidthMax: 24, WidthMaxEnforcer: text.WrapHard},
{Number: 8, Align: text.AlignCenter, AlignFooter: text.AlignCenter, AlignHeader: text.AlignCenter, WidthMax: 24, WidthMaxEnforcer: text.WrapHard},
})
tw.SetStyle(StyleLight)
tw.Style().Options.SeparateRows = true
compareOutput(t, tw.Render(), `
COLUMN 1 COLUMN 2 COLUMN 3 COLUMN 4 COLUMN 5 COLUMN 6 COLUMN 7 COLUMN 8
1 1.1.1.1 Pod 1A NS 1A C 1 4F8F5CB531E3D49A61CF417C 4F8F5CB531E3D49A61CF417C
D133792CCFA501FD8DA53EE3 D133792CCFA501FD8DA53EE3
68FED20E5FE0248C3A0B64F9 68FED20E5FE0248C3A0B64F9
8A6533CEE1DA614C3A8DDEC7 8A6533CEE1DA614C3A8DDEC7
91FF05FEE6D971D57C134832 91FF05FEE6D971D57C134832
0F4EB42DR 0F4EB42DRR
2 1.1.1.1 Pod 1A NS 1A C 2 Y
3 1.1.1.1 Pod 1A NS 1A C 2 Y
`)
})
t.Run("long column partially merged #2", func(t *testing.T) {
tw := NewWriter()
tw.AppendHeader(Row{"Column 1", "Column 2", "Column 3", "Column 4", "Column 5", "Column 6", "Column 7", "Column 8"}, rcAutoMerge)
tw.AppendRow(Row{"1.1.1.1", "Pod 1A", "NS 1A", "C 1", "4F8F5CB531E3D49A61CF417CD133792CCFA501FD8DA53EE368FED20E5FE0248C3A0B64F98A6533CEE1DA614C3A8DDEC791FF05FEE6D971D57C1348320F4EB42DR", "4F8F5CB531E3D49A61CF417CD133792CCFA501FD8DA53EE368FED20E5FE0248C3A0B64F98A6533CEE1DA614C3A8DDEC791FF05FEE6D971D57C1348320F4EB42DR", "4F8F5CB531E3D49A61CF417CD133792CCFA501FD8DA53EE368FED20E5FE0248C3A0B64F98A6533CEE1DA614C3A8DDEC791FF05FEE6D971D57C1348320F4EB42DR", "4F8F5CB531E3D49A61CF417CD133792CCFA501FD8DA53EE368FED20E5FE0248C3A0B64F98A6533CEE1DA614C3A8DDEC791FF05FEE6D971D57C1348320F4EB42DRE"}, rcAutoMerge)
tw.AppendRow(Row{"1.1.1.1", "Pod 1A", "NS 1A", "C 2", "Y", "Y", "Y", "Y"}, rcAutoMerge)
tw.AppendRow(Row{"1.1.1.1", "Pod 1A", "NS 1A", "C 2", "Y", "Y", "Y", "Y"}, rcAutoMerge)
tw.SetAutoIndex(true)
tw.SetColumnConfigs([]ColumnConfig{
{Number: 5, Align: text.AlignCenter, AlignFooter: text.AlignCenter, AlignHeader: text.AlignCenter, WidthMax: 24, WidthMaxEnforcer: text.WrapHard},
{Number: 6, Align: text.AlignCenter, AlignFooter: text.AlignCenter, AlignHeader: text.AlignCenter, WidthMax: 24, WidthMaxEnforcer: text.WrapHard},
{Number: 7, Align: text.AlignCenter, AlignFooter: text.AlignCenter, AlignHeader: text.AlignCenter, WidthMax: 24, WidthMaxEnforcer: text.WrapHard},
{Number: 8, Align: text.AlignCenter, AlignFooter: text.AlignCenter, AlignHeader: text.AlignCenter, WidthMax: 24, WidthMaxEnforcer: text.WrapHard},
})
tw.SetStyle(StyleLight)
tw.Style().Options.SeparateRows = true
compareOutput(t, tw.Render(), `
COLUMN 1 COLUMN 2 COLUMN 3 COLUMN 4 COLUMN 5 COLUMN 6 COLUMN 7 COLUMN 8
1 1.1.1.1 Pod 1A NS 1A C 1 4F8F5CB531E3D49A61CF417C 4F8F5CB531E3D49A61CF417C
D133792CCFA501FD8DA53EE3 D133792CCFA501FD8DA53EE3
68FED20E5FE0248C3A0B64F9 68FED20E5FE0248C3A0B64F9
8A6533CEE1DA614C3A8DDEC7 8A6533CEE1DA614C3A8DDEC7
91FF05FEE6D971D57C134832 91FF05FEE6D971D57C134832
0F4EB42DR 0F4EB42DRE
2 1.1.1.1 Pod 1A NS 1A C 2 Y
3 1.1.1.1 Pod 1A NS 1A C 2 Y
`)
})
t.Run("long column fully merged", func(t *testing.T) {
tw := NewWriter()
tw.AppendHeader(Row{"Column 1", "Column 2", "Column 3", "Column 4", "Column 5", "Column 6", "Column 7", "Column 8"}, rcAutoMerge)
tw.AppendRow(Row{"1.1.1.1", "Pod 1A", "NS 1A", "C 1", "4F8F5CB531E3D49A61CF417CD133792CCFA501FD8DA53EE368FED20E5FE0248C3A0B64F98A6533CEE1DA614C3A8DDEC791FF05FEE6D971D57C1348320F4EB42DR", "4F8F5CB531E3D49A61CF417CD133792CCFA501FD8DA53EE368FED20E5FE0248C3A0B64F98A6533CEE1DA614C3A8DDEC791FF05FEE6D971D57C1348320F4EB42DR", "4F8F5CB531E3D49A61CF417CD133792CCFA501FD8DA53EE368FED20E5FE0248C3A0B64F98A6533CEE1DA614C3A8DDEC791FF05FEE6D971D57C1348320F4EB42DR", "4F8F5CB531E3D49A61CF417CD133792CCFA501FD8DA53EE368FED20E5FE0248C3A0B64F98A6533CEE1DA614C3A8DDEC791FF05FEE6D971D57C1348320F4EB42DR"}, rcAutoMerge)
tw.AppendRow(Row{"1.1.1.1", "Pod 1A", "NS 1A", "C 2", "Y", "Y", "Y", "Y"}, rcAutoMerge)
tw.AppendRow(Row{"1.1.1.1", "Pod 1A", "NS 1A", "C 2", "Y", "Y", "Y", "Y"}, rcAutoMerge)
tw.SetAutoIndex(true)
tw.SetColumnConfigs([]ColumnConfig{
{Number: 5, Align: text.AlignCenter, AlignFooter: text.AlignCenter, AlignHeader: text.AlignCenter, WidthMax: 24, WidthMaxEnforcer: text.WrapHard},
{Number: 6, Align: text.AlignCenter, AlignFooter: text.AlignCenter, AlignHeader: text.AlignCenter, WidthMax: 24, WidthMaxEnforcer: text.WrapHard},
{Number: 7, Align: text.AlignCenter, AlignFooter: text.AlignCenter, AlignHeader: text.AlignCenter, WidthMax: 24, WidthMaxEnforcer: text.WrapHard},
{Number: 8, Align: text.AlignCenter, AlignFooter: text.AlignCenter, AlignHeader: text.AlignCenter, WidthMax: 24, WidthMaxEnforcer: text.WrapHard},
})
tw.SetStyle(StyleLight)
tw.Style().Options.SeparateRows = true
compareOutput(t, tw.Render(), `
COLUMN 1 COLUMN 2 COLUMN 3 COLUMN 4 COLUMN 5 COLUMN 6 COLUMN 7 COLUMN 8
1 1.1.1.1 Pod 1A NS 1A C 1 4F8F5CB531E3D49A61CF417C
D133792CCFA501FD8DA53EE3
68FED20E5FE0248C3A0B64F9
8A6533CEE1DA614C3A8DDEC7
91FF05FEE6D971D57C134832
0F4EB42DR
2 1.1.1.1 Pod 1A NS 1A C 2 Y
3 1.1.1.1 Pod 1A NS 1A C 2 Y
`)
})
t.Run("headers too", func(t *testing.T) {
tw := NewWriter()
tw.AppendHeader(Row{"Node IP", "Pods", "Namespace", "Container", "RCE", "RCE"}, rcAutoMerge)
tw.AppendHeader(Row{"", "", "", "", "EXE EXE EXE", "EXE EXE EXE"}, rcAutoMerge)
tw.AppendRow(Row{"1.1.1.1", "Pod 1A", "NS 1A", "C 1", "Y", "Y"}, rcAutoMerge)
tw.AppendRow(Row{"1.1.1.1", "Pod 1A", "NS 1A", "C 2", "Y", "N"})
tw.AppendRow(Row{"1.1.1.1", "Pod 1A", "NS 1B", "C 3", "N", "N"})
tw.AppendRow(Row{"1.1.1.1", "Pod 1B", "NS 2", "C 4", "N", "N"}, rcAutoMerge)
tw.AppendRow(Row{"1.1.1.1", "Pod 1B", "NS 2", "C 5", "Y", "N"})
tw.AppendRow(Row{"2.2.2.2", "Pod 2", "NS 3", "C 6", "Y", "Y"}, rcAutoMerge)
tw.AppendRow(Row{"2.2.2.2", "Pod 2", "NS 3", "C 7", "Y", "Y"}, rcAutoMerge)
tw.AppendFooter(Row{"", "", "", 7, 5, 3}, rcAutoMerge)
tw.AppendFooter(Row{"", "", "", 6, 4, 4}, rcAutoMerge)
tw.SetAutoIndex(true)
tw.SetColumnConfigs([]ColumnConfig{
{Number: 5, Align: text.AlignCenter, AlignFooter: text.AlignCenter, AlignHeader: text.AlignCenter, WidthMax: 7, WidthMaxEnforcer: text.WrapHard},
{Number: 6, Align: text.AlignCenter, AlignFooter: text.AlignCenter, AlignHeader: text.AlignCenter, WidthMax: 7, WidthMaxEnforcer: text.WrapHard},
})
tw.SetStyle(StyleLight)
tw.Style().Options.SeparateRows = true
compareOutput(t, tw.Render(), `
NODE IP PODS NAMESPACE CONTAINER RCE
EXE EXE
EXE
1 1.1.1.1 Pod 1A NS 1A C 1 Y
2 1.1.1.1 Pod 1A NS 1A C 2 Y N
3 1.1.1.1 Pod 1A NS 1B C 3 N N
4 1.1.1.1 Pod 1B NS 2 C 4 N
5 1.1.1.1 Pod 1B NS 2 C 5 Y N
6 2.2.2.2 Pod 2 NS 3 C 6 Y
7 2.2.2.2 Pod 2 NS 3 C 7 Y
7 5 3
6 4
`)
})
t.Run("headers and footers too", func(t *testing.T) {
tw := NewWriter()
tw.AppendHeader(Row{"Node IP", "Pods", "Namespace", "Container", "RCE1", "RCE2"}, rcAutoMerge)
tw.AppendHeader(Row{"", "", "", "", "EXE EXE EXE", "EXE EXE EXE"}, rcAutoMerge)
tw.AppendRow(Row{"1.1.1.1", "Pod 1A", "NS 1A", "C 1", "Y", "Y"}, rcAutoMerge)
tw.AppendRow(Row{"1.1.1.1", "Pod 1A", "NS 1A", "C 2", "Y", "Y"}, rcAutoMerge)
tw.AppendRow(Row{"1.1.1.1", "Pod 1A", "NS 1B", "C 3", "N", "N"}, rcAutoMerge)
tw.AppendRow(Row{"1.1.1.1", "Pod 1B", "NS 2", "C 4", "N", "N"}, rcAutoMerge)
tw.AppendRow(Row{"1.1.1.1", "Pod 1B", "NS 2", "C 5", "Y", "Y"}, rcAutoMerge)
tw.AppendRow(Row{"2.2.2.2", "Pod 2", "NS 3", "C 6", "Y", "Y"}, rcAutoMerge)
tw.AppendRow(Row{"2.2.2.2", "Pod 2", "NS 3", "C 7", "Y", "Y"}, rcAutoMerge)
tw.AppendFooter(Row{"", "", "", 7, 5, 5}, rcAutoMerge)
tw.AppendFooter(Row{"", "", "", 6, 4, 4}, rcAutoMerge)
tw.SetAutoIndex(true)
tw.SetColumnConfigs([]ColumnConfig{
{Number: 5, Align: text.AlignCenter, AlignFooter: text.AlignCenter, AlignHeader: text.AlignCenter, WidthMax: 7, WidthMaxEnforcer: text.WrapHard},
{Number: 6, Align: text.AlignCenter, AlignFooter: text.AlignCenter, AlignHeader: text.AlignCenter, WidthMax: 7, WidthMaxEnforcer: text.WrapHard},
})
tw.SetStyle(StyleLight)
tw.Style().Options.SeparateRows = true
compareOutput(t, tw.Render(), `
NODE IP PODS NAMESPACE CONTAINER RCE1 RCE2
EXE EXE
EXE
1 1.1.1.1 Pod 1A NS 1A C 1 Y
2 1.1.1.1 Pod 1A NS 1A C 2 Y
3 1.1.1.1 Pod 1A NS 1B C 3 N
4 1.1.1.1 Pod 1B NS 2 C 4 N
5 1.1.1.1 Pod 1B NS 2 C 5 Y
6 2.2.2.2 Pod 2 NS 3 C 6 Y
7 2.2.2.2 Pod 2 NS 3 C 7 Y
7 5
6 4
`)
})
t.Run("long header column", func(t *testing.T) {
tw := NewWriter()
tw.AppendHeader(Row{"Node IP", "Pods", "Namespace", "Container", "RCE1", "RCE2", "RCE3"}, rcAutoMerge)
tw.AppendHeader(Row{"", "", "", "", "EXE EXE EXE", "EXE EXE EXE", "EXE EXE EXE"}, rcAutoMerge)
tw.AppendRow(Row{"1.1.1.1", "Pod 1A", "NS 1A", "C 1", "Y", "Y", "Y"}, rcAutoMerge)
tw.AppendRow(Row{"1.1.1.1", "Pod 1A", "NS 1A", "C 2", "Y", "Y", "Y"}, rcAutoMerge)
tw.AppendRow(Row{"1.1.1.1", "Pod 1A", "NS 1B", "C 3", "N", "N", "N"}, rcAutoMerge)
tw.AppendRow(Row{"1.1.1.1", "Pod 1B", "NS 2", "C 4", "N", "N", "N"}, rcAutoMerge)
tw.AppendRow(Row{"1.1.1.1", "Pod 1B", "NS 2", "C 5", "Y", "Y", "Y"}, rcAutoMerge)
tw.AppendRow(Row{"2.2.2.2", "Pod 2", "NS 3", "C 6", "Y", "Y", "Y"}, rcAutoMerge)
tw.AppendRow(Row{"2.2.2.2", "Pod 2", "NS 3", "C 7", "Y", "Y", "Y"}, rcAutoMerge)
tw.AppendFooter(Row{"", "", "", 7, 5, 5, 5}, rcAutoMerge)
tw.AppendFooter(Row{"", "", "", 6, 4, 4, 3}, rcAutoMerge)
tw.SetAutoIndex(true)
tw.SetColumnConfigs([]ColumnConfig{
{Number: 5, Align: text.AlignCenter, AlignFooter: text.AlignCenter, AlignHeader: text.AlignCenter, WidthMax: 7, WidthMaxEnforcer: text.WrapHard},
{Number: 6, Align: text.AlignCenter, AlignFooter: text.AlignCenter, AlignHeader: text.AlignCenter, WidthMax: 7, WidthMaxEnforcer: text.WrapHard},
{Number: 7, Align: text.AlignCenter, AlignFooter: text.AlignCenter, AlignHeader: text.AlignCenter, WidthMax: 7, WidthMaxEnforcer: text.WrapHard},
})
tw.SetStyle(StyleLight)
tw.Style().Options.SeparateRows = true
compareOutput(t, tw.Render(), `
NODE IP PODS NAMESPACE CONTAINER RCE1 RCE2 RCE3
EXE EXE
EXE
1 1.1.1.1 Pod 1A NS 1A C 1 Y
2 1.1.1.1 Pod 1A NS 1A C 2 Y
3 1.1.1.1 Pod 1A NS 1B C 3 N
4 1.1.1.1 Pod 1B NS 2 C 4 N
5 1.1.1.1 Pod 1B NS 2 C 5 Y
6 2.2.2.2 Pod 2 NS 3 C 6 Y
7 2.2.2.2 Pod 2 NS 3 C 7 Y
7 5
6 4 3
`)
})
t.Run("everything", func(t *testing.T) {
tw := NewWriter()
tw.AppendHeader(Row{"COLUMNS", "COLUMNS", "COLUMNS", "COLUMNS", "COLUMNS", "COLUMNS", "COLUMNS"}, rcAutoMerge)
tw.AppendRow(Row{"1.1.1.1", "Pod 1A", "NS 1A", "C 1", "Y", "Y", "Y"}, rcAutoMerge)
tw.AppendRow(Row{"1.1.1.1", "Pod 1A", "NS 1A", "C 2", "Y", "Y", "N"}, rcAutoMerge)
tw.AppendRow(Row{"1.1.1.1", "Pod 1A", "NS 1B", "C 3", "N", "N", "N"}, rcAutoMerge)
tw.AppendRow(Row{"1.1.1.1", "Pod 1B", "NS 2", "C 4", "N", "Y", "N"}, rcAutoMerge)
tw.AppendRow(Row{"1.1.1.1", "Pod 1B", "NS 2", "C 5", "Y", "Y", "Y"}, rcAutoMerge)
tw.AppendRow(Row{"2.2.2.2", "Pod 2", "NS 3", "C 6", "N", "Y", "Y"}, rcAutoMerge)
tw.AppendRow(Row{"2.2.2.2", "Pod 2", "NS 3", "C 7", "Y", "Y", "Y"}, rcAutoMerge)
tw.AppendFooter(Row{"foo", "foo", "foo", "foo", "bar", "bar", "bar"}, rcAutoMerge)
tw.AppendFooter(Row{7, 7, 7, 7, 7, 7, 7}, rcAutoMerge)
tw.SetAutoIndex(true)
tw.SetColumnConfigs([]ColumnConfig{
{Number: 1, AutoMerge: true},
{Number: 2, AutoMerge: true},
{Number: 3, AutoMerge: true},
{Number: 5, Align: text.AlignCenter, AlignFooter: text.AlignCenter, AlignHeader: text.AlignCenter, WidthMax: 7, WidthMaxEnforcer: text.WrapHard},
{Number: 6, Align: text.AlignCenter, AlignFooter: text.AlignCenter, AlignHeader: text.AlignCenter, WidthMax: 7, WidthMaxEnforcer: text.WrapHard},
{Number: 7, Align: text.AlignCenter, AlignFooter: text.AlignCenter, AlignHeader: text.AlignCenter, WidthMax: 7, WidthMaxEnforcer: text.WrapHard},
})
tw.SetStyle(StyleLight)
tw.Style().Options.SeparateRows = true
compareOutput(t, tw.Render(), `
COLUMNS
1 1.1.1.1 Pod 1A NS 1A C 1 Y
2 C 2 Y N
3 NS 1B C 3 N
4 Pod 1B NS 2 C 4 N Y N
5 C 5 Y
6 2.2.2.2 Pod 2 NS 3 C 6 N Y
7 C 7 Y
FOO BAR
7
`)
})
}
func TestTable_Render_BorderAndSeparators(t *testing.T) {
table := Table{}
table.AppendHeader(testHeader)
table.AppendRows(testRows)
table.AppendFooter(testFooter)
compareOutput(t, table.Render(), `
+-----+------------+-----------+--------+-----------------------------+
| # | FIRST NAME | LAST NAME | SALARY | |
+-----+------------+-----------+--------+-----------------------------+
| 1 | Arya | Stark | 3000 | |
| 20 | Jon | Snow | 2000 | You know nothing, Jon Snow! |
| 300 | Tyrion | Lannister | 5000 | |
+-----+------------+-----------+--------+-----------------------------+
| | | TOTAL | 10000 | |
+-----+------------+-----------+--------+-----------------------------+`)
table.Style().Options = OptionsNoBorders
compareOutput(t, table.Render(), `
# | FIRST NAME | LAST NAME | SALARY |
-----+------------+-----------+--------+-----------------------------
1 | Arya | Stark | 3000 |
20 | Jon | Snow | 2000 | You know nothing, Jon Snow!
300 | Tyrion | Lannister | 5000 |
-----+------------+-----------+--------+-----------------------------
| | TOTAL | 10000 | `)
table.Style().Options.SeparateColumns = false
compareOutput(t, table.Render(), `
# FIRST NAME LAST NAME SALARY
-----------------------------------------------------------------
1 Arya Stark 3000
20 Jon Snow 2000 You know nothing, Jon Snow!
300 Tyrion Lannister 5000
-----------------------------------------------------------------
TOTAL 10000 `)
table.Style().Options.SeparateFooter = false
compareOutput(t, table.Render(), `
# FIRST NAME LAST NAME SALARY
-----------------------------------------------------------------
1 Arya Stark 3000
20 Jon Snow 2000 You know nothing, Jon Snow!
300 Tyrion Lannister 5000
TOTAL 10000 `)
table.Style().Options = OptionsNoBordersAndSeparators
compareOutput(t, table.Render(), `
# FIRST NAME LAST NAME SALARY
1 Arya Stark 3000
20 Jon Snow 2000 You know nothing, Jon Snow!
300 Tyrion Lannister 5000
TOTAL 10000 `)
table.Style().Options.DrawBorder = true
compareOutput(t, table.Render(), `
+-----------------------------------------------------------------+
| # FIRST NAME LAST NAME SALARY |
| 1 Arya Stark 3000 |
| 20 Jon Snow 2000 You know nothing, Jon Snow! |
| 300 Tyrion Lannister 5000 |
| TOTAL 10000 |
+-----------------------------------------------------------------+`)
table.Style().Options.SeparateFooter = true
compareOutput(t, table.Render(), `
+-----------------------------------------------------------------+
| # FIRST NAME LAST NAME SALARY |
| 1 Arya Stark 3000 |
| 20 Jon Snow 2000 You know nothing, Jon Snow! |
| 300 Tyrion Lannister 5000 |
+-----------------------------------------------------------------+
| TOTAL 10000 |
+-----------------------------------------------------------------+`)
table.Style().Options.SeparateHeader = true
compareOutput(t, table.Render(), `
+-----------------------------------------------------------------+
| # FIRST NAME LAST NAME SALARY |
+-----------------------------------------------------------------+
| 1 Arya Stark 3000 |
| 20 Jon Snow 2000 You know nothing, Jon Snow! |
| 300 Tyrion Lannister 5000 |
+-----------------------------------------------------------------+
| TOTAL 10000 |
+-----------------------------------------------------------------+`)
table.Style().Options.SeparateRows = true
compareOutput(t, table.Render(), `
+-----------------------------------------------------------------+
| # FIRST NAME LAST NAME SALARY |
+-----------------------------------------------------------------+
| 1 Arya Stark 3000 |
+-----------------------------------------------------------------+
| 20 Jon Snow 2000 You know nothing, Jon Snow! |
+-----------------------------------------------------------------+
| 300 Tyrion Lannister 5000 |
+-----------------------------------------------------------------+
| TOTAL 10000 |
+-----------------------------------------------------------------+`)
table.Style().Options.SeparateColumns = true
compareOutput(t, table.Render(), `
+-----+------------+-----------+--------+-----------------------------+
| # | FIRST NAME | LAST NAME | SALARY | |
+-----+------------+-----------+--------+-----------------------------+
| 1 | Arya | Stark | 3000 | |
+-----+------------+-----------+--------+-----------------------------+
| 20 | Jon | Snow | 2000 | You know nothing, Jon Snow! |
+-----+------------+-----------+--------+-----------------------------+
| 300 | Tyrion | Lannister | 5000 | |
+-----+------------+-----------+--------+-----------------------------+
| | | TOTAL | 10000 | |
+-----+------------+-----------+--------+-----------------------------+`)
}
func TestTable_Render_BorderAndSeparators_Colored(t *testing.T) {
table := Table{}
table.AppendHeader(testHeader)
table.AppendRows(testRows)
table.AppendFooter(testFooter)
compareOutput(t, table.Render(), `
+-----+------------+-----------+--------+-----------------------------+
| # | FIRST NAME | LAST NAME | SALARY | |
+-----+------------+-----------+--------+-----------------------------+
| 1 | Arya | Stark | 3000 | |
| 20 | Jon | Snow | 2000 | You know nothing, Jon Snow! |
| 300 | Tyrion | Lannister | 5000 | |
+-----+------------+-----------+--------+-----------------------------+
| | | TOTAL | 10000 | |
+-----+------------+-----------+--------+-----------------------------+`)
table.Style().Color.Border = text.Colors{text.FgRed}
table.Style().Color.Separator = text.Colors{text.FgYellow}
compareOutputColored(t, table.Render(), ""+
"\x1b[31m+\x1b[0m\x1b[31m-----\x1b[0m\x1b[31m+\x1b[0m\x1b[31m------------\x1b[0m\x1b[31m+\x1b[0m\x1b[31m-----------\x1b[0m\x1b[31m+\x1b[0m\x1b[31m--------\x1b[0m\x1b[31m+\x1b[0m\x1b[31m-----------------------------\x1b[0m\x1b[31m+\x1b[0m\n"+
"\x1b[31m|\x1b[0m # \x1b[33m|\x1b[0m FIRST NAME \x1b[33m|\x1b[0m LAST NAME \x1b[33m|\x1b[0m SALARY \x1b[33m|\x1b[0m \x1b[31m|\x1b[0m\n"+
"\x1b[31m+\x1b[0m\x1b[33m-----\x1b[0m\x1b[33m+\x1b[0m\x1b[33m------------\x1b[0m\x1b[33m+\x1b[0m\x1b[33m-----------\x1b[0m\x1b[33m+\x1b[0m\x1b[33m--------\x1b[0m\x1b[33m+\x1b[0m\x1b[33m-----------------------------\x1b[0m\x1b[31m+\x1b[0m\n"+
"\x1b[31m|\x1b[0m 1 \x1b[33m|\x1b[0m Arya \x1b[33m|\x1b[0m Stark \x1b[33m|\x1b[0m 3000 \x1b[33m|\x1b[0m \x1b[31m|\x1b[0m\n"+
"\x1b[31m|\x1b[0m 20 \x1b[33m|\x1b[0m Jon \x1b[33m|\x1b[0m Snow \x1b[33m|\x1b[0m 2000 \x1b[33m|\x1b[0m You know nothing, Jon Snow! \x1b[31m|\x1b[0m\n"+
"\x1b[31m|\x1b[0m 300 \x1b[33m|\x1b[0m Tyrion \x1b[33m|\x1b[0m Lannister \x1b[33m|\x1b[0m 5000 \x1b[33m|\x1b[0m \x1b[31m|\x1b[0m\n"+
"\x1b[31m+\x1b[0m\x1b[33m-----\x1b[0m\x1b[33m+\x1b[0m\x1b[33m------------\x1b[0m\x1b[33m+\x1b[0m\x1b[33m-----------\x1b[0m\x1b[33m+\x1b[0m\x1b[33m--------\x1b[0m\x1b[33m+\x1b[0m\x1b[33m-----------------------------\x1b[0m\x1b[31m+\x1b[0m\n"+
"\x1b[31m|\x1b[0m \x1b[33m|\x1b[0m \x1b[33m|\x1b[0m TOTAL \x1b[33m|\x1b[0m 10000 \x1b[33m|\x1b[0m \x1b[31m|\x1b[0m\n"+
"\x1b[31m+\x1b[0m\x1b[31m-----\x1b[0m\x1b[31m+\x1b[0m\x1b[31m------------\x1b[0m\x1b[31m+\x1b[0m\x1b[31m-----------\x1b[0m\x1b[31m+\x1b[0m\x1b[31m--------\x1b[0m\x1b[31m+\x1b[0m\x1b[31m-----------------------------\x1b[0m\x1b[31m+\x1b[0m",
)
table.SetStyle(StyleLight)
table.Style().Color.Border = text.Colors{text.FgRed}
table.Style().Color.Separator = text.Colors{text.FgYellow}
compareOutputColored(t, table.Render(), ""+
"\x1b[31m┌\x1b[0m\x1b[31m─────\x1b[0m\x1b[31m┬\x1b[0m\x1b[31m────────────\x1b[0m\x1b[31m┬\x1b[0m\x1b[31m───────────\x1b[0m\x1b[31m┬\x1b[0m\x1b[31m────────\x1b[0m\x1b[31m┬\x1b[0m\x1b[31m─────────────────────────────\x1b[0m\x1b[31m┐\x1b[0m\n"+
"\x1b[31m│\x1b[0m # \x1b[33m│\x1b[0m FIRST NAME \x1b[33m│\x1b[0m LAST NAME \x1b[33m│\x1b[0m SALARY \x1b[33m│\x1b[0m \x1b[31m│\x1b[0m\n"+
"\x1b[31m├\x1b[0m\x1b[33m─────\x1b[0m\x1b[33m┼\x1b[0m\x1b[33m────────────\x1b[0m\x1b[33m┼\x1b[0m\x1b[33m───────────\x1b[0m\x1b[33m┼\x1b[0m\x1b[33m────────\x1b[0m\x1b[33m┼\x1b[0m\x1b[33m─────────────────────────────\x1b[0m\x1b[31m┤\x1b[0m\n"+
"\x1b[31m│\x1b[0m 1 \x1b[33m│\x1b[0m Arya \x1b[33m│\x1b[0m Stark \x1b[33m│\x1b[0m 3000 \x1b[33m│\x1b[0m \x1b[31m│\x1b[0m\n"+
"\x1b[31m│\x1b[0m 20 \x1b[33m│\x1b[0m Jon \x1b[33m│\x1b[0m Snow \x1b[33m│\x1b[0m 2000 \x1b[33m│\x1b[0m You know nothing, Jon Snow! \x1b[31m│\x1b[0m\n"+
"\x1b[31m│\x1b[0m 300 \x1b[33m│\x1b[0m Tyrion \x1b[33m│\x1b[0m Lannister \x1b[33m│\x1b[0m 5000 \x1b[33m│\x1b[0m \x1b[31m│\x1b[0m\n"+
"\x1b[31m├\x1b[0m\x1b[33m─────\x1b[0m\x1b[33m┼\x1b[0m\x1b[33m────────────\x1b[0m\x1b[33m┼\x1b[0m\x1b[33m───────────\x1b[0m\x1b[33m┼\x1b[0m\x1b[33m────────\x1b[0m\x1b[33m┼\x1b[0m\x1b[33m─────────────────────────────\x1b[0m\x1b[31m┤\x1b[0m\n"+
"\x1b[31m│\x1b[0m \x1b[33m│\x1b[0m \x1b[33m│\x1b[0m TOTAL \x1b[33m│\x1b[0m 10000 \x1b[33m│\x1b[0m \x1b[31m│\x1b[0m\n"+
"\x1b[31m└\x1b[0m\x1b[31m─────\x1b[0m\x1b[31m┴\x1b[0m\x1b[31m────────────\x1b[0m\x1b[31m┴\x1b[0m\x1b[31m───────────\x1b[0m\x1b[31m┴\x1b[0m\x1b[31m────────\x1b[0m\x1b[31m┴\x1b[0m\x1b[31m─────────────────────────────\x1b[0m\x1b[31m┘\x1b[0m",
)
table.SetTitle("Game of Thrones")
compareOutputColored(t, table.Render(), ""+
"\x1b[31m┌\x1b[0m\x1b[31m─────────────────────────────────────────────────────────────────────\x1b[0m\x1b[31m┐\x1b[0m\n"+
"\x1b[31m│\x1b[0m Game of Thrones \x1b[31m│\x1b[0m\n"+
"\x1b[31m├\x1b[0m\x1b[31m─────\x1b[0m\x1b[31m┬\x1b[0m\x1b[31m────────────\x1b[0m\x1b[31m┬\x1b[0m\x1b[31m───────────\x1b[0m\x1b[31m┬\x1b[0m\x1b[31m────────\x1b[0m\x1b[31m┬\x1b[0m\x1b[31m─────────────────────────────\x1b[0m\x1b[31m┤\x1b[0m\n"+
"\x1b[31m│\x1b[0m # \x1b[33m│\x1b[0m FIRST NAME \x1b[33m│\x1b[0m LAST NAME \x1b[33m│\x1b[0m SALARY \x1b[33m│\x1b[0m \x1b[31m│\x1b[0m\n"+
"\x1b[31m├\x1b[0m\x1b[33m─────\x1b[0m\x1b[33m┼\x1b[0m\x1b[33m────────────\x1b[0m\x1b[33m┼\x1b[0m\x1b[33m───────────\x1b[0m\x1b[33m┼\x1b[0m\x1b[33m────────\x1b[0m\x1b[33m┼\x1b[0m\x1b[33m─────────────────────────────\x1b[0m\x1b[31m┤\x1b[0m\n"+
"\x1b[31m│\x1b[0m 1 \x1b[33m│\x1b[0m Arya \x1b[33m│\x1b[0m Stark \x1b[33m│\x1b[0m 3000 \x1b[33m│\x1b[0m \x1b[31m│\x1b[0m\n"+
"\x1b[31m│\x1b[0m 20 \x1b[33m│\x1b[0m Jon \x1b[33m│\x1b[0m Snow \x1b[33m│\x1b[0m 2000 \x1b[33m│\x1b[0m You know nothing, Jon Snow! \x1b[31m│\x1b[0m\n"+
"\x1b[31m│\x1b[0m 300 \x1b[33m│\x1b[0m Tyrion \x1b[33m│\x1b[0m Lannister \x1b[33m│\x1b[0m 5000 \x1b[33m│\x1b[0m \x1b[31m│\x1b[0m\n"+
"\x1b[31m├\x1b[0m\x1b[33m─────\x1b[0m\x1b[33m┼\x1b[0m\x1b[33m────────────\x1b[0m\x1b[33m┼\x1b[0m\x1b[33m───────────\x1b[0m\x1b[33m┼\x1b[0m\x1b[33m────────\x1b[0m\x1b[33m┼\x1b[0m\x1b[33m─────────────────────────────\x1b[0m\x1b[31m┤\x1b[0m\n"+
"\x1b[31m│\x1b[0m \x1b[33m│\x1b[0m \x1b[33m│\x1b[0m TOTAL \x1b[33m│\x1b[0m 10000 \x1b[33m│\x1b[0m \x1b[31m│\x1b[0m\n"+
"\x1b[31m└\x1b[0m\x1b[31m─────\x1b[0m\x1b[31m┴\x1b[0m\x1b[31m────────────\x1b[0m\x1b[31m┴\x1b[0m\x1b[31m───────────\x1b[0m\x1b[31m┴\x1b[0m\x1b[31m────────\x1b[0m\x1b[31m┴\x1b[0m\x1b[31m─────────────────────────────\x1b[0m\x1b[31m┘\x1b[0m",
)
}
func TestTable_Render_Colored(t *testing.T) {
t.Run("simple", func(t *testing.T) {
tw := NewWriter()
tw.AppendHeader(testHeader)
tw.AppendRows(testRows)
tw.AppendRow(testRowMultiLine)
tw.AppendFooter(testFooter)
tw.SetAutoIndex(true)
tw.SetStyle(StyleColoredBright)
tw.Style().Options.DrawBorder = true
tw.Style().Options.SeparateColumns = true
tw.Style().Options.SeparateFooter = true
tw.Style().Options.SeparateHeader = true
tw.Style().Options.SeparateRows = true
compareOutputColored(t, tw.Render(), ""+
"\x1b[106;30m+\x1b[0m\x1b[106;30m---\x1b[0m\x1b[106;30m+\x1b[0m\x1b[106;30m-----\x1b[0m\x1b[106;30m+\x1b[0m\x1b[106;30m------------\x1b[0m\x1b[106;30m+\x1b[0m\x1b[106;30m-----------\x1b[0m\x1b[106;30m+\x1b[0m\x1b[106;30m--------\x1b[0m\x1b[106;30m+\x1b[0m\x1b[106;30m-----------------------------\x1b[0m\x1b[106;30m+\x1b[0m\n"+
"\x1b[106;30m|\x1b[0m\x1b[106;30m \x1b[0m\x1b[106;30m|\x1b[0m\x1b[106;30m # \x1b[0m\x1b[106;30m|\x1b[0m\x1b[106;30m FIRST NAME \x1b[0m\x1b[106;30m|\x1b[0m\x1b[106;30m LAST NAME \x1b[0m\x1b[106;30m|\x1b[0m\x1b[106;30m SALARY \x1b[0m\x1b[106;30m|\x1b[0m\x1b[106;30m \x1b[0m\x1b[106;30m|\x1b[0m\n"+
"\x1b[106;30m+\x1b[0m\x1b[106;30m---\x1b[0m\x1b[106;30m+\x1b[0m\x1b[106;30m-----\x1b[0m\x1b[106;30m+\x1b[0m\x1b[106;30m------------\x1b[0m\x1b[106;30m+\x1b[0m\x1b[106;30m-----------\x1b[0m\x1b[106;30m+\x1b[0m\x1b[106;30m--------\x1b[0m\x1b[106;30m+\x1b[0m\x1b[106;30m-----------------------------\x1b[0m\x1b[106;30m+\x1b[0m\n"+
"\x1b[106;30m|\x1b[0m\x1b[106;30m 1 \x1b[0m\x1b[106;30m|\x1b[0m\x1b[107;30m 1 \x1b[0m\x1b[107;30m|\x1b[0m\x1b[107;30m Arya \x1b[0m\x1b[107;30m|\x1b[0m\x1b[107;30m Stark \x1b[0m\x1b[107;30m|\x1b[0m\x1b[107;30m 3000 \x1b[0m\x1b[107;30m|\x1b[0m\x1b[107;30m \x1b[0m\x1b[106;30m|\x1b[0m\n"+
"\x1b[106;30m+\x1b[0m\x1b[106;30m---\x1b[0m\x1b[106;30m+\x1b[0m\x1b[107;30m-----\x1b[0m\x1b[107;30m+\x1b[0m\x1b[107;30m------------\x1b[0m\x1b[107;30m+\x1b[0m\x1b[107;30m-----------\x1b[0m\x1b[107;30m+\x1b[0m\x1b[107;30m--------\x1b[0m\x1b[107;30m+\x1b[0m\x1b[107;30m-----------------------------\x1b[0m\x1b[106;30m+\x1b[0m\n"+
"\x1b[106;30m|\x1b[0m\x1b[106;30m 2 \x1b[0m\x1b[106;30m|\x1b[0m\x1b[47;30m 20 \x1b[0m\x1b[47;30m|\x1b[0m\x1b[47;30m Jon \x1b[0m\x1b[47;30m|\x1b[0m\x1b[47;30m Snow \x1b[0m\x1b[47;30m|\x1b[0m\x1b[47;30m 2000 \x1b[0m\x1b[47;30m|\x1b[0m\x1b[47;30m You know nothing, Jon Snow! \x1b[0m\x1b[106;30m|\x1b[0m\n"+
"\x1b[106;30m+\x1b[0m\x1b[106;30m---\x1b[0m\x1b[106;30m+\x1b[0m\x1b[47;30m-----\x1b[0m\x1b[47;30m+\x1b[0m\x1b[47;30m------------\x1b[0m\x1b[47;30m+\x1b[0m\x1b[47;30m-----------\x1b[0m\x1b[47;30m+\x1b[0m\x1b[47;30m--------\x1b[0m\x1b[47;30m+\x1b[0m\x1b[47;30m-----------------------------\x1b[0m\x1b[106;30m+\x1b[0m\n"+
"\x1b[106;30m|\x1b[0m\x1b[106;30m 3 \x1b[0m\x1b[106;30m|\x1b[0m\x1b[107;30m 300 \x1b[0m\x1b[107;30m|\x1b[0m\x1b[107;30m Tyrion \x1b[0m\x1b[107;30m|\x1b[0m\x1b[107;30m Lannister \x1b[0m\x1b[107;30m|\x1b[0m\x1b[107;30m 5000 \x1b[0m\x1b[107;30m|\x1b[0m\x1b[107;30m \x1b[0m\x1b[106;30m|\x1b[0m\n"+
"\x1b[106;30m+\x1b[0m\x1b[106;30m---\x1b[0m\x1b[106;30m+\x1b[0m\x1b[107;30m-----\x1b[0m\x1b[107;30m+\x1b[0m\x1b[107;30m------------\x1b[0m\x1b[107;30m+\x1b[0m\x1b[107;30m-----------\x1b[0m\x1b[107;30m+\x1b[0m\x1b[107;30m--------\x1b[0m\x1b[107;30m+\x1b[0m\x1b[107;30m-----------------------------\x1b[0m\x1b[106;30m+\x1b[0m\n"+
"\x1b[106;30m|\x1b[0m\x1b[106;30m 4 \x1b[0m\x1b[106;30m|\x1b[0m\x1b[47;30m 0 \x1b[0m\x1b[47;30m|\x1b[0m\x1b[47;30m Winter \x1b[0m\x1b[47;30m|\x1b[0m\x1b[47;30m Is \x1b[0m\x1b[47;30m|\x1b[0m\x1b[47;30m 0 \x1b[0m\x1b[47;30m|\x1b[0m\x1b[47;30m Coming. \x1b[0m\x1b[106;30m|\x1b[0m\n"+
"\x1b[106;30m|\x1b[0m\x1b[106;30m \x1b[0m\x1b[106;30m|\x1b[0m\x1b[47;30m \x1b[0m\x1b[47;30m|\x1b[0m\x1b[47;30m \x1b[0m\x1b[47;30m|\x1b[0m\x1b[47;30m \x1b[0m\x1b[47;30m|\x1b[0m\x1b[47;30m \x1b[0m\x1b[47;30m|\x1b[0m\x1b[47;30m The North Remembers! \x1b[0m\x1b[106;30m|\x1b[0m\n"+
"\x1b[106;30m|\x1b[0m\x1b[106;30m \x1b[0m\x1b[106;30m|\x1b[0m\x1b[47;30m \x1b[0m\x1b[47;30m|\x1b[0m\x1b[47;30m \x1b[0m\x1b[47;30m|\x1b[0m\x1b[47;30m \x1b[0m\x1b[47;30m|\x1b[0m\x1b[47;30m \x1b[0m\x1b[47;30m|\x1b[0m\x1b[47;30m This is known. \x1b[0m\x1b[106;30m|\x1b[0m\n"+
"\x1b[46;30m+\x1b[0m\x1b[46;30m---\x1b[0m\x1b[46;30m+\x1b[0m\x1b[46;30m-----\x1b[0m\x1b[46;30m+\x1b[0m\x1b[46;30m------------\x1b[0m\x1b[46;30m+\x1b[0m\x1b[46;30m-----------\x1b[0m\x1b[46;30m+\x1b[0m\x1b[46;30m--------\x1b[0m\x1b[46;30m+\x1b[0m\x1b[46;30m-----------------------------\x1b[0m\x1b[46;30m+\x1b[0m\n"+
"\x1b[46;30m|\x1b[0m\x1b[46;30m \x1b[0m\x1b[46;30m|\x1b[0m\x1b[46;30m \x1b[0m\x1b[46;30m|\x1b[0m\x1b[46;30m \x1b[0m\x1b[46;30m|\x1b[0m\x1b[46;30m TOTAL \x1b[0m\x1b[46;30m|\x1b[0m\x1b[46;30m 10000 \x1b[0m\x1b[46;30m|\x1b[0m\x1b[46;30m \x1b[0m\x1b[46;30m|\x1b[0m\n"+
"\x1b[46;30m+\x1b[0m\x1b[46;30m---\x1b[0m\x1b[46;30m+\x1b[0m\x1b[46;30m-----\x1b[0m\x1b[46;30m+\x1b[0m\x1b[46;30m------------\x1b[0m\x1b[46;30m+\x1b[0m\x1b[46;30m-----------\x1b[0m\x1b[46;30m+\x1b[0m\x1b[46;30m--------\x1b[0m\x1b[46;30m+\x1b[0m\x1b[46;30m-----------------------------\x1b[0m\x1b[46;30m+\x1b[0m",
)
})
t.Run("with borders", func(t *testing.T) {
tw := NewWriter()
tw.AppendHeader(testHeader)
tw.AppendRows(testRows)
tw.AppendRow(testRowMultiLine)
tw.AppendFooter(testFooter)
tw.SetTitle(testTitle1)
tw.Style().Title.Colors = text.Colors{text.FgYellow}
tw.Style().Color = ColorOptions{
Header: text.Colors{text.FgRed},
Row: text.Colors{text.FgGreen},
RowAlternate: text.Colors{text.FgHiGreen},
Footer: text.Colors{text.FgBlue},
}
compareOutputColored(t, tw.Render(), ""+
"\x1b[33m+\x1b[0m\x1b[33m---------------------------------------------------------------------\x1b[0m\x1b[33m+\x1b[0m\n"+
"\x1b[33m|\x1b[0m\x1b[33m Game of Thrones \x1b[0m\x1b[33m|\x1b[0m\n"+
"\x1b[31m+\x1b[0m\x1b[31m-----\x1b[0m\x1b[31m+\x1b[0m\x1b[31m------------\x1b[0m\x1b[31m+\x1b[0m\x1b[31m-----------\x1b[0m\x1b[31m+\x1b[0m\x1b[31m--------\x1b[0m\x1b[31m+\x1b[0m\x1b[31m-----------------------------\x1b[0m\x1b[31m+\x1b[0m\n"+
"\x1b[31m|\x1b[0m\x1b[31m # \x1b[0m\x1b[31m|\x1b[0m\x1b[31m FIRST NAME \x1b[0m\x1b[31m|\x1b[0m\x1b[31m LAST NAME \x1b[0m\x1b[31m|\x1b[0m\x1b[31m SALARY \x1b[0m\x1b[31m|\x1b[0m\x1b[31m \x1b[0m\x1b[31m|\x1b[0m\n"+
"\x1b[31m+\x1b[0m\x1b[31m-----\x1b[0m\x1b[31m+\x1b[0m\x1b[31m------------\x1b[0m\x1b[31m+\x1b[0m\x1b[31m-----------\x1b[0m\x1b[31m+\x1b[0m\x1b[31m--------\x1b[0m\x1b[31m+\x1b[0m\x1b[31m-----------------------------\x1b[0m\x1b[31m+\x1b[0m\n"+
"\x1b[32m|\x1b[0m\x1b[32m 1 \x1b[0m\x1b[32m|\x1b[0m\x1b[32m Arya \x1b[0m\x1b[32m|\x1b[0m\x1b[32m Stark \x1b[0m\x1b[32m|\x1b[0m\x1b[32m 3000 \x1b[0m\x1b[32m|\x1b[0m\x1b[32m \x1b[0m\x1b[32m|\x1b[0m\n"+
"\x1b[92m|\x1b[0m\x1b[92m 20 \x1b[0m\x1b[92m|\x1b[0m\x1b[92m Jon \x1b[0m\x1b[92m|\x1b[0m\x1b[92m Snow \x1b[0m\x1b[92m|\x1b[0m\x1b[92m 2000 \x1b[0m\x1b[92m|\x1b[0m\x1b[92m You know nothing, Jon Snow! \x1b[0m\x1b[92m|\x1b[0m\n"+
"\x1b[32m|\x1b[0m\x1b[32m 300 \x1b[0m\x1b[32m|\x1b[0m\x1b[32m Tyrion \x1b[0m\x1b[32m|\x1b[0m\x1b[32m Lannister \x1b[0m\x1b[32m|\x1b[0m\x1b[32m 5000 \x1b[0m\x1b[32m|\x1b[0m\x1b[32m \x1b[0m\x1b[32m|\x1b[0m\n"+
"\x1b[92m|\x1b[0m\x1b[92m 0 \x1b[0m\x1b[92m|\x1b[0m\x1b[92m Winter \x1b[0m\x1b[92m|\x1b[0m\x1b[92m Is \x1b[0m\x1b[92m|\x1b[0m\x1b[92m 0 \x1b[0m\x1b[92m|\x1b[0m\x1b[92m Coming. \x1b[0m\x1b[92m|\x1b[0m\n"+
"\x1b[92m|\x1b[0m\x1b[92m \x1b[0m\x1b[92m|\x1b[0m\x1b[92m \x1b[0m\x1b[92m|\x1b[0m\x1b[92m \x1b[0m\x1b[92m|\x1b[0m\x1b[92m \x1b[0m\x1b[92m|\x1b[0m\x1b[92m The North Remembers! \x1b[0m\x1b[92m|\x1b[0m\n"+
"\x1b[92m|\x1b[0m\x1b[92m \x1b[0m\x1b[92m|\x1b[0m\x1b[92m \x1b[0m\x1b[92m|\x1b[0m\x1b[92m \x1b[0m\x1b[92m|\x1b[0m\x1b[92m \x1b[0m\x1b[92m|\x1b[0m\x1b[92m This is known. \x1b[0m\x1b[92m|\x1b[0m\n"+
"\x1b[34m+\x1b[0m\x1b[34m-----\x1b[0m\x1b[34m+\x1b[0m\x1b[34m------------\x1b[0m\x1b[34m+\x1b[0m\x1b[34m-----------\x1b[0m\x1b[34m+\x1b[0m\x1b[34m--------\x1b[0m\x1b[34m+\x1b[0m\x1b[34m-----------------------------\x1b[0m\x1b[34m+\x1b[0m\n"+
"\x1b[34m|\x1b[0m\x1b[34m \x1b[0m\x1b[34m|\x1b[0m\x1b[34m \x1b[0m\x1b[34m|\x1b[0m\x1b[34m TOTAL \x1b[0m\x1b[34m|\x1b[0m\x1b[34m 10000 \x1b[0m\x1b[34m|\x1b[0m\x1b[34m \x1b[0m\x1b[34m|\x1b[0m\n"+
"\x1b[34m+\x1b[0m\x1b[34m-----\x1b[0m\x1b[34m+\x1b[0m\x1b[34m------------\x1b[0m\x1b[34m+\x1b[0m\x1b[34m-----------\x1b[0m\x1b[34m+\x1b[0m\x1b[34m--------\x1b[0m\x1b[34m+\x1b[0m\x1b[34m-----------------------------\x1b[0m\x1b[34m+\x1b[0m",
)
})
t.Run("with borders and separators not colored", func(t *testing.T) {
tw := NewWriter()
tw.AppendHeader(testHeader)
tw.AppendRows(testRows)
tw.AppendRow(testRowMultiLine)
tw.AppendFooter(testFooter)
tw.SetTitle(testTitle1)
tw.Style().Title.Colors = text.Colors{text.FgYellow}
tw.Style().Color = ColorOptions{
Header: text.Colors{text.FgRed},
Row: text.Colors{text.FgGreen},
RowAlternate: text.Colors{text.FgHiGreen},
Footer: text.Colors{text.FgBlue},
}
tw.Style().Options.DoNotColorBordersAndSeparators = true
compareOutputColored(t, tw.Render(), ""+
"+---------------------------------------------------------------------+\n"+
"|\x1b[33m Game of Thrones \x1b[0m|\n"+
"+-----+------------+-----------+--------+-----------------------------+\n"+
"|\x1b[31m # \x1b[0m|\x1b[31m FIRST NAME \x1b[0m|\x1b[31m LAST NAME \x1b[0m|\x1b[31m SALARY \x1b[0m|\x1b[31m \x1b[0m|\n"+
"+-----+------------+-----------+--------+-----------------------------+\n"+
"|\x1b[32m 1 \x1b[0m|\x1b[32m Arya \x1b[0m|\x1b[32m Stark \x1b[0m|\x1b[32m 3000 \x1b[0m|\x1b[32m \x1b[0m|\n"+
"|\x1b[92m 20 \x1b[0m|\x1b[92m Jon \x1b[0m|\x1b[92m Snow \x1b[0m|\x1b[92m 2000 \x1b[0m|\x1b[92m You know nothing, Jon Snow! \x1b[0m|\n"+
"|\x1b[32m 300 \x1b[0m|\x1b[32m Tyrion \x1b[0m|\x1b[32m Lannister \x1b[0m|\x1b[32m 5000 \x1b[0m|\x1b[32m \x1b[0m|\n"+
"|\x1b[92m 0 \x1b[0m|\x1b[92m Winter \x1b[0m|\x1b[92m Is \x1b[0m|\x1b[92m 0 \x1b[0m|\x1b[92m Coming. \x1b[0m|\n"+
"|\x1b[92m \x1b[0m|\x1b[92m \x1b[0m|\x1b[92m \x1b[0m|\x1b[92m \x1b[0m|\x1b[92m The North Remembers! \x1b[0m|\n"+
"|\x1b[92m \x1b[0m|\x1b[92m \x1b[0m|\x1b[92m \x1b[0m|\x1b[92m \x1b[0m|\x1b[92m This is known. \x1b[0m|\n"+
"+-----+------------+-----------+--------+-----------------------------+\n"+
"|\x1b[34m \x1b[0m|\x1b[34m \x1b[0m|\x1b[34m TOTAL \x1b[0m|\x1b[34m 10000 \x1b[0m|\x1b[34m \x1b[0m|\n"+
"+-----+------------+-----------+--------+-----------------------------+",
)
})
t.Run("column customizations", func(t *testing.T) {
tw := NewWriter()
tw.AppendHeader(testHeader)
tw.AppendRows(testRows)
tw.AppendRow(testRowMultiLine)
tw.AppendFooter(testFooter)
tw.SetCaption(testCaption)
tw.SetColumnConfigs([]ColumnConfig{
{Name: "#", Colors: testColor, ColorsHeader: testColorHiRedBold},
{Name: "First Name", Colors: testColor, ColorsHeader: testColorHiRedBold},
{Name: "Last Name", Colors: testColor, ColorsHeader: testColorHiRedBold, ColorsFooter: testColorHiBlueBold},
{Name: "Salary", Colors: testColor, ColorsHeader: testColorHiRedBold, ColorsFooter: testColorHiBlueBold},
{Number: 5, Colors: text.Colors{text.FgCyan}},
})
tw.SetStyle(StyleRounded)
compareOutputColored(t, tw.Render(), ""+
"╭─────┬────────────┬───────────┬────────┬─────────────────────────────╮\n"+
"│\x1b[91;1m # \x1b[0m│\x1b[91;1m FIRST NAME \x1b[0m│\x1b[91;1m LAST NAME \x1b[0m│\x1b[91;1m SALARY \x1b[0m│ │\n"+
"├─────┼────────────┼───────────┼────────┼─────────────────────────────┤\n"+
"│\x1b[32m 1 \x1b[0m│\x1b[32m Arya \x1b[0m│\x1b[32m Stark \x1b[0m│\x1b[32m 3000 \x1b[0m│\x1b[36m \x1b[0m│\n"+
"│\x1b[32m 20 \x1b[0m│\x1b[32m Jon \x1b[0m│\x1b[32m Snow \x1b[0m│\x1b[32m 2000 \x1b[0m│\x1b[36m You know nothing, Jon Snow! \x1b[0m│\n"+
"│\x1b[32m 300 \x1b[0m│\x1b[32m Tyrion \x1b[0m│\x1b[32m Lannister \x1b[0m│\x1b[32m 5000 \x1b[0m│\x1b[36m \x1b[0m│\n"+
"│\x1b[32m 0 \x1b[0m│\x1b[32m Winter \x1b[0m│\x1b[32m Is \x1b[0m│\x1b[32m 0 \x1b[0m│\x1b[36m Coming. \x1b[0m│\n"+
"│\x1b[32m \x1b[0m│\x1b[32m \x1b[0m│\x1b[32m \x1b[0m│\x1b[32m \x1b[0m│\x1b[36m The North Remembers! \x1b[0m│\n"+
"│\x1b[32m \x1b[0m│\x1b[32m \x1b[0m│\x1b[32m \x1b[0m│\x1b[32m \x1b[0m│\x1b[36m This is known. \x1b[0m│\n"+
"├─────┼────────────┼───────────┼────────┼─────────────────────────────┤\n"+
"│ │ │\x1b[94;1m TOTAL \x1b[0m│\x1b[94;1m 10000 \x1b[0m│ │\n"+
"╰─────┴────────────┴───────────┴────────┴─────────────────────────────╯\n"+
"A Song of Ice and Fire",
)
})
t.Run("colored table within table", func(t *testing.T) {
table := Table{}
table.AppendHeader(testHeader)
table.AppendRows(testRows)
table.AppendFooter(testFooter)
table.SetStyle(StyleColoredBright)
table.SetIndexColumn(1)
// colored is simple; render the colored table into another table
tableOuter := Table{}
tableOuter.AppendRow(Row{table.Render()})
tableOuter.SetStyle(StyleRounded)
compareOutputColored(t, tableOuter.Render(), ""+
"╭───────────────────────────────────────────────────────────────────╮\n"+
"│ \x1b[106;30m # \x1b[0m\x1b[106;30m FIRST NAME \x1b[0m\x1b[106;30m LAST NAME \x1b[0m\x1b[106;30m SALARY \x1b[0m\x1b[106;30m \x1b[0m │\n"+
"│ \x1b[106;30m 1 \x1b[0m\x1b[107;30m Arya \x1b[0m\x1b[107;30m Stark \x1b[0m\x1b[107;30m 3000 \x1b[0m\x1b[107;30m \x1b[0m │\n"+
"│ \x1b[106;30m 20 \x1b[0m\x1b[47;30m Jon \x1b[0m\x1b[47;30m Snow \x1b[0m\x1b[47;30m 2000 \x1b[0m\x1b[47;30m You know nothing, Jon Snow! \x1b[0m │\n"+
"│ \x1b[106;30m 300 \x1b[0m\x1b[107;30m Tyrion \x1b[0m\x1b[107;30m Lannister \x1b[0m\x1b[107;30m 5000 \x1b[0m\x1b[107;30m \x1b[0m │\n"+
"│ \x1b[46;30m \x1b[0m\x1b[46;30m \x1b[0m\x1b[46;30m TOTAL \x1b[0m\x1b[46;30m 10000 \x1b[0m\x1b[46;30m \x1b[0m │\n"+
"╰───────────────────────────────────────────────────────────────────╯",
)
})
t.Run("colored table within colored table", func(t *testing.T) {
table := Table{}
table.AppendHeader(testHeader)
table.AppendRows(testRows)
table.AppendFooter(testFooter)
table.SetStyle(StyleColoredBright)
table.SetIndexColumn(1)
// colored is simple; render the colored table into another colored table
tableOuter := Table{}
tableOuter.AppendHeader(Row{"Colored Table within a Colored Table"})
tableOuter.AppendRow(Row{"\n" + table.Render() + "\n"})
tableOuter.SetColumnConfigs([]ColumnConfig{{Number: 1, AlignHeader: text.AlignCenter}})
tableOuter.SetStyle(StyleColoredBright)
compareOutputColored(t, tableOuter.Render(), ""+
"\x1b[106;30m COLORED TABLE WITHIN A COLORED TABLE \x1b[0m\n"+
"\x1b[107;30m \x1b[0m\n"+
"\x1b[107;30m \x1b[106;30m # \x1b[0m\x1b[107;30m\x1b[106;30m FIRST NAME \x1b[0m\x1b[107;30m\x1b[106;30m LAST NAME \x1b[0m\x1b[107;30m\x1b[106;30m SALARY \x1b[0m\x1b[107;30m\x1b[106;30m \x1b[0m\x1b[107;30m \x1b[0m\n"+
"\x1b[107;30m \x1b[106;30m 1 \x1b[0m\x1b[107;30m\x1b[107;30m Arya \x1b[0m\x1b[107;30m\x1b[107;30m Stark \x1b[0m\x1b[107;30m\x1b[107;30m 3000 \x1b[0m\x1b[107;30m\x1b[107;30m \x1b[0m\x1b[107;30m \x1b[0m\n"+
"\x1b[107;30m \x1b[106;30m 20 \x1b[0m\x1b[107;30m\x1b[47;30m Jon \x1b[0m\x1b[107;30m\x1b[47;30m Snow \x1b[0m\x1b[107;30m\x1b[47;30m 2000 \x1b[0m\x1b[107;30m\x1b[47;30m You know nothing, Jon Snow! \x1b[0m\x1b[107;30m \x1b[0m\n"+
"\x1b[107;30m \x1b[106;30m 300 \x1b[0m\x1b[107;30m\x1b[107;30m Tyrion \x1b[0m\x1b[107;30m\x1b[107;30m Lannister \x1b[0m\x1b[107;30m\x1b[107;30m 5000 \x1b[0m\x1b[107;30m\x1b[107;30m \x1b[0m\x1b[107;30m \x1b[0m\n"+
"\x1b[107;30m \x1b[46;30m \x1b[0m\x1b[107;30m\x1b[46;30m \x1b[0m\x1b[107;30m\x1b[46;30m TOTAL \x1b[0m\x1b[107;30m\x1b[46;30m 10000 \x1b[0m\x1b[107;30m\x1b[46;30m \x1b[0m\x1b[107;30m \x1b[0m\n"+
"\x1b[107;30m \x1b[0m",
)
})
t.Run("colored table with auto-index", func(t *testing.T) {
table := Table{}
table.AppendHeader(testHeader)
table.AppendRows(testRows)
table.AppendFooter(testFooter)
table.SetAutoIndex(true)
table.SetStyle(StyleColoredDark)
table.SetTitle(testTitle2)
compareOutputColored(t, table.Render(), ""+
"\x1b[106;30;1m When you play the Game of Thrones, you win or you die. There is no \x1b[0m\n"+
"\x1b[106;30;1m middle ground. \x1b[0m\n"+
"\x1b[96;100m \x1b[0m\x1b[96;100m # \x1b[0m\x1b[96;100m FIRST NAME \x1b[0m\x1b[96;100m LAST NAME \x1b[0m\x1b[96;100m SALARY \x1b[0m\x1b[96;100m \x1b[0m\n"+
"\x1b[96;100m 1 \x1b[0m\x1b[97;40m 1 \x1b[0m\x1b[97;40m Arya \x1b[0m\x1b[97;40m Stark \x1b[0m\x1b[97;40m 3000 \x1b[0m\x1b[97;40m \x1b[0m\n"+
"\x1b[96;100m 2 \x1b[0m\x1b[37;40m 20 \x1b[0m\x1b[37;40m Jon \x1b[0m\x1b[37;40m Snow \x1b[0m\x1b[37;40m 2000 \x1b[0m\x1b[37;40m You know nothing, Jon Snow! \x1b[0m\n"+
"\x1b[96;100m 3 \x1b[0m\x1b[97;40m 300 \x1b[0m\x1b[97;40m Tyrion \x1b[0m\x1b[97;40m Lannister \x1b[0m\x1b[97;40m 5000 \x1b[0m\x1b[97;40m \x1b[0m\n"+
"\x1b[36;100m \x1b[0m\x1b[36;100m \x1b[0m\x1b[36;100m \x1b[0m\x1b[36;100m TOTAL \x1b[0m\x1b[36;100m 10000 \x1b[0m\x1b[36;100m \x1b[0m",
)
})
}
func TestTable_Render_ColumnConfigs(t *testing.T) {
generatePrefixTransformer := func(prefix string) text.Transformer {
return func(val interface{}) string {
return fmt.Sprintf("%s%v", prefix, val)
}
}
generateSuffixTransformer := func(suffix string) text.Transformer {
return func(val interface{}) string {
return fmt.Sprintf("%v%s", val, suffix)
}
}
salaryTransformer := text.Transformer(func(val interface{}) string {
if valInt, ok := val.(int); ok {
return fmt.Sprintf("$ %.2f", float64(valInt)+0.03)
}
return strings.Replace(fmt.Sprint(val), "ry", "riii", -1)
})
tw := NewWriter()
tw.AppendHeader(testHeaderMultiLine)
tw.AppendRows(testRows)
tw.AppendRow(testRowMultiLine)
tw.AppendFooter(testFooterMultiLine)
tw.SetAutoIndex(true)
tw.SetColumnConfigs([]ColumnConfig{
{
Name: fmt.Sprint(testHeaderMultiLine[1]), // First Name
Align: text.AlignRight,
AlignFooter: text.AlignRight,
AlignHeader: text.AlignRight,
Colors: text.Colors{text.BgBlack, text.FgRed},
ColorsHeader: text.Colors{text.BgRed, text.FgBlack, text.Bold},
ColorsFooter: text.Colors{text.BgRed, text.FgBlack},
Transformer: generatePrefixTransformer("(r_"),
TransformerFooter: generatePrefixTransformer("(f_"),
TransformerHeader: generatePrefixTransformer("(h_"),
VAlign: text.VAlignTop,
VAlignFooter: text.VAlignTop,
VAlignHeader: text.VAlignTop,
WidthMax: 10,
}, {
Name: fmt.Sprint(testHeaderMultiLine[2]), // Last Name
Align: text.AlignLeft,
AlignFooter: text.AlignLeft,
AlignHeader: text.AlignLeft,
Colors: text.Colors{text.BgBlack, text.FgGreen},
ColorsHeader: text.Colors{text.BgGreen, text.FgBlack, text.Bold},
ColorsFooter: text.Colors{text.BgGreen, text.FgBlack},
Transformer: generateSuffixTransformer("_r)"),
TransformerFooter: generateSuffixTransformer("_f)"),
TransformerHeader: generateSuffixTransformer("_h)"),
VAlign: text.VAlignMiddle,
VAlignFooter: text.VAlignMiddle,
VAlignHeader: text.VAlignMiddle,
WidthMax: 10,
}, {
Number: 4, // Salary
Colors: text.Colors{text.BgBlack, text.FgBlue},
ColorsHeader: text.Colors{text.BgBlue, text.FgBlack, text.Bold},
ColorsFooter: text.Colors{text.BgBlue, text.FgBlack},
Transformer: salaryTransformer,
TransformerFooter: salaryTransformer,
TransformerHeader: salaryTransformer,
VAlign: text.VAlignBottom,
VAlignFooter: text.VAlignBottom,
VAlignHeader: text.VAlignBottom,
WidthMin: 16,
}, {
Name: "Non-existent Column",
Colors: text.Colors{text.BgYellow, text.FgHiRed},
},
})
tw.SetStyle(styleTest)
compareOutputColored(t, tw.Render(), ""+
"(---^-----^-----------^------------^------------------^-----------------------------)\n"+
"[< >|< #>|\x1b[41;30;1m< (H_FIRST>\x1b[0m|\x1b[42;30;1m<LAST >\x1b[0m|\x1b[44;30;1m< >\x1b[0m|< >]\n"+
"[< >|< >|\x1b[41;30;1m< NAME>\x1b[0m|\x1b[42;30;1m<NAME_H) >\x1b[0m|\x1b[44;30;1m< SALARIII>\x1b[0m|< >]\n"+
"{---+-----+-----------+------------+------------------+-----------------------------}\n"+
"[<1>|< 1>|\x1b[40;31m< (r_Arya>\x1b[0m|\x1b[40;32m<Stark_r) >\x1b[0m|\x1b[40;34m< $ 3000.03>\x1b[0m|< >]\n"+
"[<2>|< 20>|\x1b[40;31m< (r_Jon>\x1b[0m|\x1b[40;32m<Snow_r) >\x1b[0m|\x1b[40;34m< $ 2000.03>\x1b[0m|<You know nothing, Jon Snow!>]\n"+
"[<3>|<300>|\x1b[40;31m<(r_Tyrion>\x1b[0m|\x1b[40;32m<Lannister_>\x1b[0m|\x1b[40;34m< >\x1b[0m|< >]\n"+
"[< >|< >|\x1b[40;31m< >\x1b[0m|\x1b[40;32m<r) >\x1b[0m|\x1b[40;34m< $ 5000.03>\x1b[0m|< >]\n"+
"[<4>|< 0>|\x1b[40;31m<(r_Winter>\x1b[0m|\x1b[40;32m< >\x1b[0m|\x1b[40;34m< >\x1b[0m|<Coming. >]\n"+
"[< >|< >|\x1b[40;31m< >\x1b[0m|\x1b[40;32m<Is_r) >\x1b[0m|\x1b[40;34m< >\x1b[0m|<The North Remembers! >]\n"+
"[< >|< >|\x1b[40;31m< >\x1b[0m|\x1b[40;32m< >\x1b[0m|\x1b[40;34m< $ 0.03>\x1b[0m|<This is known. >]\n"+
"{---+-----+-----------+------------+------------------+-----------------------------}\n"+
"[< >|< >|\x1b[41;30m< (F_>\x1b[0m|\x1b[42;30m<TOTAL >\x1b[0m|\x1b[44;30m< >\x1b[0m|< >]\n"+
"[< >|< >|\x1b[41;30m< >\x1b[0m|\x1b[42;30m<SALARY_F) >\x1b[0m|\x1b[44;30m< $ 10000.03>\x1b[0m|< >]\n"+
"\\---v-----v-----------v------------v------------------v-----------------------------/",
)
}
func TestTable_Render_Empty(t *testing.T) {
tw := NewWriter()
assert.Empty(t, tw.Render())
}
func TestTable_Render_HiddenColumns(t *testing.T) {
tw := NewWriter()
tw.AppendHeader(testHeader)
tw.AppendRows(testRows)
tw.AppendFooter(testFooter)
// ensure sorting is done before hiding the columns
tw.SortBy([]SortBy{
{Name: "Salary", Mode: DscNumeric},
})
t.Run("no columns hidden", func(t *testing.T) {
tw.SetColumnConfigs(generateColumnConfigsWithHiddenColumns(nil))
compareOutput(t, tw.Render(), `
+-----+------------+-------------+--------+-------------------------------+
| # | FIRST NAME | LAST NAME | SALARY | |
+-----+------------+-------------+--------+-------------------------------+
| 307 | >>Tyrion | Lannister<< | 5013 | |
| 8 | >>Arya | Stark<< | 3013 | |
| 27 | >>Jon | Snow<< | 2013 | ~You know nothing, Jon Snow!~ |
+-----+------------+-------------+--------+-------------------------------+
| | | TOTAL | 10000 | |
+-----+------------+-------------+--------+-------------------------------+`)
})
t.Run("every column hidden", func(t *testing.T) {
tw.SetColumnConfigs(generateColumnConfigsWithHiddenColumns([]int{0, 1, 2, 3, 4}))
compareOutput(t, tw.Render(), "")
})
t.Run("some columns hidden (1)", func(t *testing.T) {
tw.SetColumnConfigs(generateColumnConfigsWithHiddenColumns([]int{1, 2, 3, 4}))
compareOutput(t, tw.Render(), `
+-----+
| # |
+-----+
| 307 |
| 8 |
| 27 |
+-----+
| |
+-----+`)
})
t.Run("some columns hidden (2)", func(t *testing.T) {
tw.SetColumnConfigs(generateColumnConfigsWithHiddenColumns([]int{1, 2, 3}))
compareOutput(t, tw.Render(), `
+-----+-------------------------------+
| # | |
+-----+-------------------------------+
| 307 | |
| 8 | |
| 27 | ~You know nothing, Jon Snow!~ |
+-----+-------------------------------+
| | |
+-----+-------------------------------+`)
})
t.Run("some columns hidden (3)", func(t *testing.T) {
tw.SetColumnConfigs(generateColumnConfigsWithHiddenColumns([]int{0, 4}))
compareOutput(t, tw.Render(), `
+------------+-------------+--------+
| FIRST NAME | LAST NAME | SALARY |
+------------+-------------+--------+
| >>Tyrion | Lannister<< | 5013 |
| >>Arya | Stark<< | 3013 |
| >>Jon | Snow<< | 2013 |
+------------+-------------+--------+
| | TOTAL | 10000 |
+------------+-------------+--------+`)
})
t.Run("first column hidden", func(t *testing.T) {
tw.SetColumnConfigs(generateColumnConfigsWithHiddenColumns([]int{0}))
compareOutput(t, tw.Render(), `
+------------+-------------+--------+-------------------------------+
| FIRST NAME | LAST NAME | SALARY | |
+------------+-------------+--------+-------------------------------+
| >>Tyrion | Lannister<< | 5013 | |
| >>Arya | Stark<< | 3013 | |
| >>Jon | Snow<< | 2013 | ~You know nothing, Jon Snow!~ |
+------------+-------------+--------+-------------------------------+
| | TOTAL | 10000 | |
+------------+-------------+--------+-------------------------------+`)
})
t.Run("column hidden in the middle", func(t *testing.T) {
tw.SetColumnConfigs(generateColumnConfigsWithHiddenColumns([]int{1}))
compareOutput(t, tw.Render(), `
+-----+-------------+--------+-------------------------------+
| # | LAST NAME | SALARY | |
+-----+-------------+--------+-------------------------------+
| 307 | Lannister<< | 5013 | |
| 8 | Stark<< | 3013 | |
| 27 | Snow<< | 2013 | ~You know nothing, Jon Snow!~ |
+-----+-------------+--------+-------------------------------+
| | TOTAL | 10000 | |
+-----+-------------+--------+-------------------------------+`)
})
t.Run("last column hidden", func(t *testing.T) {
tw.SetColumnConfigs(generateColumnConfigsWithHiddenColumns([]int{4}))
compareOutput(t, tw.Render(), `
+-----+------------+-------------+--------+
| # | FIRST NAME | LAST NAME | SALARY |
+-----+------------+-------------+--------+
| 307 | >>Tyrion | Lannister<< | 5013 |
| 8 | >>Arya | Stark<< | 3013 |
| 27 | >>Jon | Snow<< | 2013 |
+-----+------------+-------------+--------+
| | | TOTAL | 10000 |
+-----+------------+-------------+--------+`)
})
}
func TestTable_Render_Paged(t *testing.T) {
tw := NewWriter()
tw.AppendHeader(testHeader)
tw.AppendRows(testRows)
tw.AppendRow(testRowMultiLine)
tw.AppendFooter(Row{"", "", "Total", 10000})
tw.SetPageSize(1)
compareOutput(t, tw.Render(), `
+-----+------------+-----------+--------+-----------------------------+
| # | FIRST NAME | LAST NAME | SALARY | |
+-----+------------+-----------+--------+-----------------------------+
| 1 | Arya | Stark | 3000 | |
+-----+------------+-----------+--------+-----------------------------+
| | | TOTAL | 10000 | |
+-----+------------+-----------+--------+-----------------------------+
+-----+------------+-----------+--------+-----------------------------+
| # | FIRST NAME | LAST NAME | SALARY | |
+-----+------------+-----------+--------+-----------------------------+
| 20 | Jon | Snow | 2000 | You know nothing, Jon Snow! |
+-----+------------+-----------+--------+-----------------------------+
| | | TOTAL | 10000 | |
+-----+------------+-----------+--------+-----------------------------+
+-----+------------+-----------+--------+-----------------------------+
| # | FIRST NAME | LAST NAME | SALARY | |
+-----+------------+-----------+--------+-----------------------------+
| 300 | Tyrion | Lannister | 5000 | |
+-----+------------+-----------+--------+-----------------------------+
| | | TOTAL | 10000 | |
+-----+------------+-----------+--------+-----------------------------+
+-----+------------+-----------+--------+-----------------------------+
| # | FIRST NAME | LAST NAME | SALARY | |
+-----+------------+-----------+--------+-----------------------------+
| 0 | Winter | Is | 0 | Coming. |
+-----+------------+-----------+--------+-----------------------------+
| | | TOTAL | 10000 | |
+-----+------------+-----------+--------+-----------------------------+
+-----+------------+-----------+--------+-----------------------------+
| # | FIRST NAME | LAST NAME | SALARY | |
+-----+------------+-----------+--------+-----------------------------+
| | | | | The North Remembers! |
+-----+------------+-----------+--------+-----------------------------+
| | | TOTAL | 10000 | |
+-----+------------+-----------+--------+-----------------------------+
+-----+------------+-----------+--------+-----------------------------+
| # | FIRST NAME | LAST NAME | SALARY | |
+-----+------------+-----------+--------+-----------------------------+
| | | | | This is known. |
+-----+------------+-----------+--------+-----------------------------+
| | | TOTAL | 10000 | |
+-----+------------+-----------+--------+-----------------------------+`)
}
func TestTable_Render_Reset(t *testing.T) {
tw := NewWriter()
tw.AppendHeader(testHeader)
tw.AppendRows(testRows)
tw.AppendFooter(testFooter)
tw.SetStyle(StyleLight)
compareOutput(t, tw.Render(), `
# FIRST NAME LAST NAME SALARY
1 Arya Stark 3000
20 Jon Snow 2000 You know nothing, Jon Snow!
300 Tyrion Lannister 5000
TOTAL 10000
`)
tw.ResetFooters()
compareOutput(t, tw.Render(), `
# FIRST NAME LAST NAME SALARY
1 Arya Stark 3000
20 Jon Snow 2000 You know nothing, Jon Snow!
300 Tyrion Lannister 5000
`)
tw.ResetHeaders()
compareOutput(t, tw.Render(), `
1 Arya Stark 3000
20 Jon Snow 2000 You know nothing, Jon Snow!
300 Tyrion Lannister 5000
`)
tw.ResetRows()
assert.Empty(t, tw.Render())
}
func TestTable_Render_RowPainter(t *testing.T) {
tw := NewWriter()
tw.AppendHeader(testHeader)
tw.AppendRows(testRows)
tw.AppendRow(testRowMultiLine)
tw.AppendFooter(testFooter)
tw.SetIndexColumn(1)
tw.SetRowPainter(func(row Row) text.Colors {
if salary, ok := row[3].(int); ok {
if salary > 3000 {
return text.Colors{text.BgYellow, text.FgBlack}
} else if salary < 2000 {
return text.Colors{text.BgRed, text.FgBlack}
}
}
return nil
})
tw.SetStyle(StyleLight)
tw.SortBy([]SortBy{{Name: "Salary", Mode: AscNumeric}})
expectedOutLines := []string{
"┌─────┬────────────┬───────────┬────────┬─────────────────────────────┐",
"│ # │ FIRST NAME │ LAST NAME │ SALARY │ │",
"├─────┼────────────┼───────────┼────────┼─────────────────────────────┤",
"│ 0 │\x1b[41;30m Winter \x1b[0m│\x1b[41;30m Is \x1b[0m│\x1b[41;30m 0 \x1b[0m│\x1b[41;30m Coming. \x1b[0m│",
"│ │\x1b[41;30m \x1b[0m│\x1b[41;30m \x1b[0m│\x1b[41;30m \x1b[0m│\x1b[41;30m The North Remembers! \x1b[0m│",
"│ │\x1b[41;30m \x1b[0m│\x1b[41;30m \x1b[0m│\x1b[41;30m \x1b[0m│\x1b[41;30m This is known. \x1b[0m│",
"│ 20 │ Jon │ Snow │ 2000 │ You know nothing, Jon Snow! │",
"│ 1 │ Arya │ Stark │ 3000 │ │",
"│ 300 │\x1b[43;30m Tyrion \x1b[0m│\x1b[43;30m Lannister \x1b[0m│\x1b[43;30m 5000 \x1b[0m│\x1b[43;30m \x1b[0m│",
"├─────┼────────────┼───────────┼────────┼─────────────────────────────┤",
"│ │ │ TOTAL │ 10000 │ │",
"└─────┴────────────┴───────────┴────────┴─────────────────────────────┘",
}
expectedOut := strings.Join(expectedOutLines, "\n")
assert.Equal(t, expectedOut, tw.Render())
tw.SetStyle(StyleColoredBright)
tw.Style().Color.RowAlternate = tw.Style().Color.Row
expectedOutLines = []string{
"\x1b[106;30m # \x1b[0m\x1b[106;30m FIRST NAME \x1b[0m\x1b[106;30m LAST NAME \x1b[0m\x1b[106;30m SALARY \x1b[0m\x1b[106;30m \x1b[0m",
"\x1b[106;30m 0 \x1b[0m\x1b[41;30m Winter \x1b[0m\x1b[41;30m Is \x1b[0m\x1b[41;30m 0 \x1b[0m\x1b[41;30m Coming. \x1b[0m",
"\x1b[106;30m \x1b[0m\x1b[41;30m \x1b[0m\x1b[41;30m \x1b[0m\x1b[41;30m \x1b[0m\x1b[41;30m The North Remembers! \x1b[0m",
"\x1b[106;30m \x1b[0m\x1b[41;30m \x1b[0m\x1b[41;30m \x1b[0m\x1b[41;30m \x1b[0m\x1b[41;30m This is known. \x1b[0m",
"\x1b[106;30m 20 \x1b[0m\x1b[107;30m Jon \x1b[0m\x1b[107;30m Snow \x1b[0m\x1b[107;30m 2000 \x1b[0m\x1b[107;30m You know nothing, Jon Snow! \x1b[0m",
"\x1b[106;30m 1 \x1b[0m\x1b[107;30m Arya \x1b[0m\x1b[107;30m Stark \x1b[0m\x1b[107;30m 3000 \x1b[0m\x1b[107;30m \x1b[0m",
"\x1b[106;30m 300 \x1b[0m\x1b[43;30m Tyrion \x1b[0m\x1b[43;30m Lannister \x1b[0m\x1b[43;30m 5000 \x1b[0m\x1b[43;30m \x1b[0m",
"\x1b[46;30m \x1b[0m\x1b[46;30m \x1b[0m\x1b[46;30m TOTAL \x1b[0m\x1b[46;30m 10000 \x1b[0m\x1b[46;30m \x1b[0m",
}
expectedOut = strings.Join(expectedOutLines, "\n")
assert.Equal(t, expectedOut, tw.Render())
}
func TestTable_Render_Sorted(t *testing.T) {
tw := NewWriter()
tw.AppendHeader(testHeader)
tw.AppendRows(testRows)
tw.AppendRow(Row{11, "Sansa", "Stark", 6000})
tw.AppendFooter(testFooter)
tw.SetStyle(StyleLight)
tw.SortBy([]SortBy{{Name: "Last Name", Mode: Asc}, {Name: "First Name", Mode: Asc}})
compareOutput(t, tw.Render(), `
# FIRST NAME LAST NAME SALARY
300 Tyrion Lannister 5000
20 Jon Snow 2000 You know nothing, Jon Snow!
1 Arya Stark 3000
11 Sansa Stark 6000
TOTAL 10000
`)
}
func TestTable_Render_Separator(t *testing.T) {
tw := NewWriter()
tw.AppendHeader(testHeader)
tw.AppendSeparator() // doesn't make any difference
tw.AppendRows(testRows)
tw.AppendSeparator()
tw.AppendSeparator() // doesn't make any difference
tw.AppendRow(testRowMultiLine)
tw.AppendSeparator()
tw.AppendSeparator() // doesn't make any difference
tw.AppendSeparator() // doesn't make any difference
tw.AppendRow(Row{11, "Sansa", "Stark", 6000})
tw.AppendSeparator() // doesn't make any difference
tw.AppendSeparator() // doesn't make any difference
tw.AppendSeparator() // doesn't make any difference
tw.AppendSeparator() // doesn't make any difference
tw.AppendFooter(testFooter)
tw.SetStyle(StyleLight)
compareOutput(t, tw.Render(), `
# FIRST NAME LAST NAME SALARY
1 Arya Stark 3000
20 Jon Snow 2000 You know nothing, Jon Snow!
300 Tyrion Lannister 5000
0 Winter Is 0 Coming.
The North Remembers!
This is known.
11 Sansa Stark 6000
TOTAL 10000
`)
}
func TestTable_Render_Styles(t *testing.T) {
tw := NewWriter()
tw.AppendHeader(testHeader)
tw.AppendRows(testRows)
tw.AppendFooter(testFooter)
tw.SetStyle(StyleLight)
styles := map[*Style]string{
&StyleDefault: "+-----+------------+-----------+--------+-----------------------------+\n| # | FIRST NAME | LAST NAME | SALARY | |\n+-----+------------+-----------+--------+-----------------------------+\n| 1 | Arya | Stark | 3000 | |\n| 20 | Jon | Snow | 2000 | You know nothing, Jon Snow! |\n| 300 | Tyrion | Lannister | 5000 | |\n+-----+------------+-----------+--------+-----------------------------+\n| | | TOTAL | 10000 | |\n+-----+------------+-----------+--------+-----------------------------+",
&StyleBold: "┏━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓\n┃ # ┃ FIRST NAME ┃ LAST NAME ┃ SALARY ┃ ┃\n┣━━━━━╋━━━━━━━━━━━━╋━━━━━━━━━━━╋━━━━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫\n┃ 1 ┃ Arya ┃ Stark ┃ 3000 ┃ ┃\n┃ 20 ┃ Jon ┃ Snow ┃ 2000 ┃ You know nothing, Jon Snow! ┃\n┃ 300 ┃ Tyrion ┃ Lannister ┃ 5000 ┃ ┃\n┣━━━━━╋━━━━━━━━━━━━╋━━━━━━━━━━━╋━━━━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫\n┃ ┃ ┃ TOTAL ┃ 10000 ┃ ┃\n┗━━━━━┻━━━━━━━━━━━━┻━━━━━━━━━━━┻━━━━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛",
&StyleColoredBlackOnBlueWhite: "\x1b[104;30m # \x1b[0m\x1b[104;30m FIRST NAME \x1b[0m\x1b[104;30m LAST NAME \x1b[0m\x1b[104;30m SALARY \x1b[0m\x1b[104;30m \x1b[0m\n\x1b[107;30m 1 \x1b[0m\x1b[107;30m Arya \x1b[0m\x1b[107;30m Stark \x1b[0m\x1b[107;30m 3000 \x1b[0m\x1b[107;30m \x1b[0m\n\x1b[47;30m 20 \x1b[0m\x1b[47;30m Jon \x1b[0m\x1b[47;30m Snow \x1b[0m\x1b[47;30m 2000 \x1b[0m\x1b[47;30m You know nothing, Jon Snow! \x1b[0m\n\x1b[107;30m 300 \x1b[0m\x1b[107;30m Tyrion \x1b[0m\x1b[107;30m Lannister \x1b[0m\x1b[107;30m 5000 \x1b[0m\x1b[107;30m \x1b[0m\n\x1b[44;30m \x1b[0m\x1b[44;30m \x1b[0m\x1b[44;30m TOTAL \x1b[0m\x1b[44;30m 10000 \x1b[0m\x1b[44;30m \x1b[0m",
&StyleColoredBlackOnCyanWhite: "\x1b[106;30m # \x1b[0m\x1b[106;30m FIRST NAME \x1b[0m\x1b[106;30m LAST NAME \x1b[0m\x1b[106;30m SALARY \x1b[0m\x1b[106;30m \x1b[0m\n\x1b[107;30m 1 \x1b[0m\x1b[107;30m Arya \x1b[0m\x1b[107;30m Stark \x1b[0m\x1b[107;30m 3000 \x1b[0m\x1b[107;30m \x1b[0m\n\x1b[47;30m 20 \x1b[0m\x1b[47;30m Jon \x1b[0m\x1b[47;30m Snow \x1b[0m\x1b[47;30m 2000 \x1b[0m\x1b[47;30m You know nothing, Jon Snow! \x1b[0m\n\x1b[107;30m 300 \x1b[0m\x1b[107;30m Tyrion \x1b[0m\x1b[107;30m Lannister \x1b[0m\x1b[107;30m 5000 \x1b[0m\x1b[107;30m \x1b[0m\n\x1b[46;30m \x1b[0m\x1b[46;30m \x1b[0m\x1b[46;30m TOTAL \x1b[0m\x1b[46;30m 10000 \x1b[0m\x1b[46;30m \x1b[0m",
&StyleColoredBlackOnGreenWhite: "\x1b[102;30m # \x1b[0m\x1b[102;30m FIRST NAME \x1b[0m\x1b[102;30m LAST NAME \x1b[0m\x1b[102;30m SALARY \x1b[0m\x1b[102;30m \x1b[0m\n\x1b[107;30m 1 \x1b[0m\x1b[107;30m Arya \x1b[0m\x1b[107;30m Stark \x1b[0m\x1b[107;30m 3000 \x1b[0m\x1b[107;30m \x1b[0m\n\x1b[47;30m 20 \x1b[0m\x1b[47;30m Jon \x1b[0m\x1b[47;30m Snow \x1b[0m\x1b[47;30m 2000 \x1b[0m\x1b[47;30m You know nothing, Jon Snow! \x1b[0m\n\x1b[107;30m 300 \x1b[0m\x1b[107;30m Tyrion \x1b[0m\x1b[107;30m Lannister \x1b[0m\x1b[107;30m 5000 \x1b[0m\x1b[107;30m \x1b[0m\n\x1b[42;30m \x1b[0m\x1b[42;30m \x1b[0m\x1b[42;30m TOTAL \x1b[0m\x1b[42;30m 10000 \x1b[0m\x1b[42;30m \x1b[0m",
&StyleColoredBlackOnMagentaWhite: "\x1b[105;30m # \x1b[0m\x1b[105;30m FIRST NAME \x1b[0m\x1b[105;30m LAST NAME \x1b[0m\x1b[105;30m SALARY \x1b[0m\x1b[105;30m \x1b[0m\n\x1b[107;30m 1 \x1b[0m\x1b[107;30m Arya \x1b[0m\x1b[107;30m Stark \x1b[0m\x1b[107;30m 3000 \x1b[0m\x1b[107;30m \x1b[0m\n\x1b[47;30m 20 \x1b[0m\x1b[47;30m Jon \x1b[0m\x1b[47;30m Snow \x1b[0m\x1b[47;30m 2000 \x1b[0m\x1b[47;30m You know nothing, Jon Snow! \x1b[0m\n\x1b[107;30m 300 \x1b[0m\x1b[107;30m Tyrion \x1b[0m\x1b[107;30m Lannister \x1b[0m\x1b[107;30m 5000 \x1b[0m\x1b[107;30m \x1b[0m\n\x1b[45;30m \x1b[0m\x1b[45;30m \x1b[0m\x1b[45;30m TOTAL \x1b[0m\x1b[45;30m 10000 \x1b[0m\x1b[45;30m \x1b[0m",
&StyleColoredBlackOnRedWhite: "\x1b[101;30m # \x1b[0m\x1b[101;30m FIRST NAME \x1b[0m\x1b[101;30m LAST NAME \x1b[0m\x1b[101;30m SALARY \x1b[0m\x1b[101;30m \x1b[0m\n\x1b[107;30m 1 \x1b[0m\x1b[107;30m Arya \x1b[0m\x1b[107;30m Stark \x1b[0m\x1b[107;30m 3000 \x1b[0m\x1b[107;30m \x1b[0m\n\x1b[47;30m 20 \x1b[0m\x1b[47;30m Jon \x1b[0m\x1b[47;30m Snow \x1b[0m\x1b[47;30m 2000 \x1b[0m\x1b[47;30m You know nothing, Jon Snow! \x1b[0m\n\x1b[107;30m 300 \x1b[0m\x1b[107;30m Tyrion \x1b[0m\x1b[107;30m Lannister \x1b[0m\x1b[107;30m 5000 \x1b[0m\x1b[107;30m \x1b[0m\n\x1b[41;30m \x1b[0m\x1b[41;30m \x1b[0m\x1b[41;30m TOTAL \x1b[0m\x1b[41;30m 10000 \x1b[0m\x1b[41;30m \x1b[0m",
&StyleColoredBlackOnYellowWhite: "\x1b[103;30m # \x1b[0m\x1b[103;30m FIRST NAME \x1b[0m\x1b[103;30m LAST NAME \x1b[0m\x1b[103;30m SALARY \x1b[0m\x1b[103;30m \x1b[0m\n\x1b[107;30m 1 \x1b[0m\x1b[107;30m Arya \x1b[0m\x1b[107;30m Stark \x1b[0m\x1b[107;30m 3000 \x1b[0m\x1b[107;30m \x1b[0m\n\x1b[47;30m 20 \x1b[0m\x1b[47;30m Jon \x1b[0m\x1b[47;30m Snow \x1b[0m\x1b[47;30m 2000 \x1b[0m\x1b[47;30m You know nothing, Jon Snow! \x1b[0m\n\x1b[107;30m 300 \x1b[0m\x1b[107;30m Tyrion \x1b[0m\x1b[107;30m Lannister \x1b[0m\x1b[107;30m 5000 \x1b[0m\x1b[107;30m \x1b[0m\n\x1b[43;30m \x1b[0m\x1b[43;30m \x1b[0m\x1b[43;30m TOTAL \x1b[0m\x1b[43;30m 10000 \x1b[0m\x1b[43;30m \x1b[0m",
&StyleColoredBlueWhiteOnBlack: "\x1b[94;100m # \x1b[0m\x1b[94;100m FIRST NAME \x1b[0m\x1b[94;100m LAST NAME \x1b[0m\x1b[94;100m SALARY \x1b[0m\x1b[94;100m \x1b[0m\n\x1b[97;40m 1 \x1b[0m\x1b[97;40m Arya \x1b[0m\x1b[97;40m Stark \x1b[0m\x1b[97;40m 3000 \x1b[0m\x1b[97;40m \x1b[0m\n\x1b[37;40m 20 \x1b[0m\x1b[37;40m Jon \x1b[0m\x1b[37;40m Snow \x1b[0m\x1b[37;40m 2000 \x1b[0m\x1b[37;40m You know nothing, Jon Snow! \x1b[0m\n\x1b[97;40m 300 \x1b[0m\x1b[97;40m Tyrion \x1b[0m\x1b[97;40m Lannister \x1b[0m\x1b[97;40m 5000 \x1b[0m\x1b[97;40m \x1b[0m\n\x1b[34;100m \x1b[0m\x1b[34;100m \x1b[0m\x1b[34;100m TOTAL \x1b[0m\x1b[34;100m 10000 \x1b[0m\x1b[34;100m \x1b[0m",
&StyleColoredBright: "\x1b[106;30m # \x1b[0m\x1b[106;30m FIRST NAME \x1b[0m\x1b[106;30m LAST NAME \x1b[0m\x1b[106;30m SALARY \x1b[0m\x1b[106;30m \x1b[0m\n\x1b[107;30m 1 \x1b[0m\x1b[107;30m Arya \x1b[0m\x1b[107;30m Stark \x1b[0m\x1b[107;30m 3000 \x1b[0m\x1b[107;30m \x1b[0m\n\x1b[47;30m 20 \x1b[0m\x1b[47;30m Jon \x1b[0m\x1b[47;30m Snow \x1b[0m\x1b[47;30m 2000 \x1b[0m\x1b[47;30m You know nothing, Jon Snow! \x1b[0m\n\x1b[107;30m 300 \x1b[0m\x1b[107;30m Tyrion \x1b[0m\x1b[107;30m Lannister \x1b[0m\x1b[107;30m 5000 \x1b[0m\x1b[107;30m \x1b[0m\n\x1b[46;30m \x1b[0m\x1b[46;30m \x1b[0m\x1b[46;30m TOTAL \x1b[0m\x1b[46;30m 10000 \x1b[0m\x1b[46;30m \x1b[0m",
&StyleColoredCyanWhiteOnBlack: "\x1b[96;100m # \x1b[0m\x1b[96;100m FIRST NAME \x1b[0m\x1b[96;100m LAST NAME \x1b[0m\x1b[96;100m SALARY \x1b[0m\x1b[96;100m \x1b[0m\n\x1b[97;40m 1 \x1b[0m\x1b[97;40m Arya \x1b[0m\x1b[97;40m Stark \x1b[0m\x1b[97;40m 3000 \x1b[0m\x1b[97;40m \x1b[0m\n\x1b[37;40m 20 \x1b[0m\x1b[37;40m Jon \x1b[0m\x1b[37;40m Snow \x1b[0m\x1b[37;40m 2000 \x1b[0m\x1b[37;40m You know nothing, Jon Snow! \x1b[0m\n\x1b[97;40m 300 \x1b[0m\x1b[97;40m Tyrion \x1b[0m\x1b[97;40m Lannister \x1b[0m\x1b[97;40m 5000 \x1b[0m\x1b[97;40m \x1b[0m\n\x1b[36;100m \x1b[0m\x1b[36;100m \x1b[0m\x1b[36;100m TOTAL \x1b[0m\x1b[36;100m 10000 \x1b[0m\x1b[36;100m \x1b[0m",
&StyleColoredDark: "\x1b[96;100m # \x1b[0m\x1b[96;100m FIRST NAME \x1b[0m\x1b[96;100m LAST NAME \x1b[0m\x1b[96;100m SALARY \x1b[0m\x1b[96;100m \x1b[0m\n\x1b[97;40m 1 \x1b[0m\x1b[97;40m Arya \x1b[0m\x1b[97;40m Stark \x1b[0m\x1b[97;40m 3000 \x1b[0m\x1b[97;40m \x1b[0m\n\x1b[37;40m 20 \x1b[0m\x1b[37;40m Jon \x1b[0m\x1b[37;40m Snow \x1b[0m\x1b[37;40m 2000 \x1b[0m\x1b[37;40m You know nothing, Jon Snow! \x1b[0m\n\x1b[97;40m 300 \x1b[0m\x1b[97;40m Tyrion \x1b[0m\x1b[97;40m Lannister \x1b[0m\x1b[97;40m 5000 \x1b[0m\x1b[97;40m \x1b[0m\n\x1b[36;100m \x1b[0m\x1b[36;100m \x1b[0m\x1b[36;100m TOTAL \x1b[0m\x1b[36;100m 10000 \x1b[0m\x1b[36;100m \x1b[0m",
&StyleColoredGreenWhiteOnBlack: "\x1b[92;100m # \x1b[0m\x1b[92;100m FIRST NAME \x1b[0m\x1b[92;100m LAST NAME \x1b[0m\x1b[92;100m SALARY \x1b[0m\x1b[92;100m \x1b[0m\n\x1b[97;40m 1 \x1b[0m\x1b[97;40m Arya \x1b[0m\x1b[97;40m Stark \x1b[0m\x1b[97;40m 3000 \x1b[0m\x1b[97;40m \x1b[0m\n\x1b[37;40m 20 \x1b[0m\x1b[37;40m Jon \x1b[0m\x1b[37;40m Snow \x1b[0m\x1b[37;40m 2000 \x1b[0m\x1b[37;40m You know nothing, Jon Snow! \x1b[0m\n\x1b[97;40m 300 \x1b[0m\x1b[97;40m Tyrion \x1b[0m\x1b[97;40m Lannister \x1b[0m\x1b[97;40m 5000 \x1b[0m\x1b[97;40m \x1b[0m\n\x1b[32;100m \x1b[0m\x1b[32;100m \x1b[0m\x1b[32;100m TOTAL \x1b[0m\x1b[32;100m 10000 \x1b[0m\x1b[32;100m \x1b[0m",
&StyleColoredMagentaWhiteOnBlack: "\x1b[95;100m # \x1b[0m\x1b[95;100m FIRST NAME \x1b[0m\x1b[95;100m LAST NAME \x1b[0m\x1b[95;100m SALARY \x1b[0m\x1b[95;100m \x1b[0m\n\x1b[97;40m 1 \x1b[0m\x1b[97;40m Arya \x1b[0m\x1b[97;40m Stark \x1b[0m\x1b[97;40m 3000 \x1b[0m\x1b[97;40m \x1b[0m\n\x1b[37;40m 20 \x1b[0m\x1b[37;40m Jon \x1b[0m\x1b[37;40m Snow \x1b[0m\x1b[37;40m 2000 \x1b[0m\x1b[37;40m You know nothing, Jon Snow! \x1b[0m\n\x1b[97;40m 300 \x1b[0m\x1b[97;40m Tyrion \x1b[0m\x1b[97;40m Lannister \x1b[0m\x1b[97;40m 5000 \x1b[0m\x1b[97;40m \x1b[0m\n\x1b[35;100m \x1b[0m\x1b[35;100m \x1b[0m\x1b[35;100m TOTAL \x1b[0m\x1b[35;100m 10000 \x1b[0m\x1b[35;100m \x1b[0m",
&StyleColoredRedWhiteOnBlack: "\x1b[91;100m # \x1b[0m\x1b[91;100m FIRST NAME \x1b[0m\x1b[91;100m LAST NAME \x1b[0m\x1b[91;100m SALARY \x1b[0m\x1b[91;100m \x1b[0m\n\x1b[97;40m 1 \x1b[0m\x1b[97;40m Arya \x1b[0m\x1b[97;40m Stark \x1b[0m\x1b[97;40m 3000 \x1b[0m\x1b[97;40m \x1b[0m\n\x1b[37;40m 20 \x1b[0m\x1b[37;40m Jon \x1b[0m\x1b[37;40m Snow \x1b[0m\x1b[37;40m 2000 \x1b[0m\x1b[37;40m You know nothing, Jon Snow! \x1b[0m\n\x1b[97;40m 300 \x1b[0m\x1b[97;40m Tyrion \x1b[0m\x1b[97;40m Lannister \x1b[0m\x1b[97;40m 5000 \x1b[0m\x1b[97;40m \x1b[0m\n\x1b[31;100m \x1b[0m\x1b[31;100m \x1b[0m\x1b[31;100m TOTAL \x1b[0m\x1b[31;100m 10000 \x1b[0m\x1b[31;100m \x1b[0m",
&StyleColoredYellowWhiteOnBlack: "\x1b[93;100m # \x1b[0m\x1b[93;100m FIRST NAME \x1b[0m\x1b[93;100m LAST NAME \x1b[0m\x1b[93;100m SALARY \x1b[0m\x1b[93;100m \x1b[0m\n\x1b[97;40m 1 \x1b[0m\x1b[97;40m Arya \x1b[0m\x1b[97;40m Stark \x1b[0m\x1b[97;40m 3000 \x1b[0m\x1b[97;40m \x1b[0m\n\x1b[37;40m 20 \x1b[0m\x1b[37;40m Jon \x1b[0m\x1b[37;40m Snow \x1b[0m\x1b[37;40m 2000 \x1b[0m\x1b[37;40m You know nothing, Jon Snow! \x1b[0m\n\x1b[97;40m 300 \x1b[0m\x1b[97;40m Tyrion \x1b[0m\x1b[97;40m Lannister \x1b[0m\x1b[97;40m 5000 \x1b[0m\x1b[97;40m \x1b[0m\n\x1b[33;100m \x1b[0m\x1b[33;100m \x1b[0m\x1b[33;100m TOTAL \x1b[0m\x1b[33;100m 10000 \x1b[0m\x1b[33;100m \x1b[0m",
&StyleDouble: "╔═════╦════════════╦═══════════╦════════╦═════════════════════════════╗\n║ # ║ FIRST NAME ║ LAST NAME ║ SALARY ║ ║\n╠═════╬════════════╬═══════════╬════════╬═════════════════════════════╣\n║ 1 ║ Arya ║ Stark ║ 3000 ║ ║\n║ 20 ║ Jon ║ Snow ║ 2000 ║ You know nothing, Jon Snow! ║\n║ 300 ║ Tyrion ║ Lannister ║ 5000 ║ ║\n╠═════╬════════════╬═══════════╬════════╬═════════════════════════════╣\n║ ║ ║ TOTAL ║ 10000 ║ ║\n╚═════╩════════════╩═══════════╩════════╩═════════════════════════════╝",
&StyleLight: "┌─────┬────────────┬───────────┬────────┬─────────────────────────────┐\n│ # │ FIRST NAME │ LAST NAME │ SALARY │ │\n├─────┼────────────┼───────────┼────────┼─────────────────────────────┤\n│ 1 │ Arya │ Stark │ 3000 │ │\n│ 20 │ Jon │ Snow │ 2000 │ You know nothing, Jon Snow! │\n│ 300 │ Tyrion │ Lannister │ 5000 │ │\n├─────┼────────────┼───────────┼────────┼─────────────────────────────┤\n│ │ │ TOTAL │ 10000 │ │\n└─────┴────────────┴───────────┴────────┴─────────────────────────────┘",
&StyleRounded: "╭─────┬────────────┬───────────┬────────┬─────────────────────────────╮\n│ # │ FIRST NAME │ LAST NAME │ SALARY │ │\n├─────┼────────────┼───────────┼────────┼─────────────────────────────┤\n│ 1 │ Arya │ Stark │ 3000 │ │\n│ 20 │ Jon │ Snow │ 2000 │ You know nothing, Jon Snow! │\n│ 300 │ Tyrion │ Lannister │ 5000 │ │\n├─────┼────────────┼───────────┼────────┼─────────────────────────────┤\n│ │ │ TOTAL │ 10000 │ │\n╰─────┴────────────┴───────────┴────────┴─────────────────────────────╯",
&styleTest: "(-----^------------^-----------^--------^-----------------------------)\n[< #>|<FIRST NAME>|<LAST NAME>|<SALARY>|< >]\n{-----+------------+-----------+--------+-----------------------------}\n[< 1>|<Arya >|<Stark >|< 3000>|< >]\n[< 20>|<Jon >|<Snow >|< 2000>|<You know nothing, Jon Snow!>]\n[<300>|<Tyrion >|<Lannister>|< 5000>|< >]\n{-----+------------+-----------+--------+-----------------------------}\n[< >|< >|<TOTAL >|< 10000>|< >]\n\\-----v------------v-----------v--------v-----------------------------/",
}
var mismatches []string
for style, expectedOut := range styles {
tw.SetStyle(*style)
out := tw.Render()
assert.Equal(t, expectedOut, out)
if expectedOut != out {
mismatches = append(mismatches, fmt.Sprintf("&%s: %#v,", style.Name, out))
fmt.Printf("// %s renders a Table like below:\n", style.Name)
for _, line := range strings.Split(out, "\n") {
fmt.Printf("// %s\n", line)
}
fmt.Println()
}
}
sort.Strings(mismatches)
for _, mismatch := range mismatches {
fmt.Println(mismatch)
}
}
func TestTable_Render_SuppressEmptyColumns(t *testing.T) {
tw := NewWriter()
tw.AppendHeader(testHeader)
tw.AppendRows([]Row{
{1, "Arya", "", 3000},
{20, "Jon", "", 2000, "You know nothing, Jon Snow!"},
{300, "Tyrion", "", 5000},
})
tw.AppendRow(Row{11, "Sansa", "", 6000})
tw.AppendFooter(Row{"", "", "TOTAL", 10000})
tw.SetStyle(StyleLight)
compareOutput(t, tw.Render(), `
# FIRST NAME LAST NAME SALARY
1 Arya 3000
20 Jon 2000 You know nothing, Jon Snow!
300 Tyrion 5000
11 Sansa 6000
TOTAL 10000
`)
tw.SuppressEmptyColumns()
compareOutput(t, tw.Render(), `
# FIRST NAME SALARY
1 Arya 3000
20 Jon 2000 You know nothing, Jon Snow!
300 Tyrion 5000
11 Sansa 6000
10000
`)
}
func TestTable_Render_TableWithinTable(t *testing.T) {
twInner := NewWriter()
twInner.AppendHeader(testHeader)
twInner.AppendRows(testRows)
twInner.AppendFooter(testFooter)
twInner.SetStyle(StyleLight)
twOuter := NewWriter()
twOuter.AppendHeader(Row{"Table within a Table"})
twOuter.AppendRow(Row{twInner.Render()})
twOuter.SetColumnConfigs([]ColumnConfig{{Number: 1, AlignHeader: text.AlignCenter}})
twOuter.SetStyle(StyleDouble)
compareOutput(t, twOuter.Render(), `
TABLE WITHIN A TABLE
# FIRST NAME LAST NAME SALARY
1 Arya Stark 3000
20 Jon Snow 2000 You know nothing, Jon Snow!
300 Tyrion Lannister 5000
TOTAL 10000
`)
}
func TestTable_Render_TableWithTransformers(t *testing.T) {
bolden := func(val interface{}) string {
return text.Bold.Sprint(val)
}
tw := NewWriter()
tw.AppendHeader(testHeader)
tw.AppendRows(testRows)
tw.AppendFooter(testFooter)
tw.SetColumnConfigs([]ColumnConfig{{
Name: "Salary",
Transformer: bolden,
TransformerFooter: bolden,
TransformerHeader: bolden,
}})
tw.SetStyle(StyleLight)
expectedOut := []string{
"┌─────┬────────────┬───────────┬────────┬─────────────────────────────┐",
"│ # │ FIRST NAME │ LAST NAME │ \x1b[1mSALARY\x1b[0m │ │",
"├─────┼────────────┼───────────┼────────┼─────────────────────────────┤",
"│ 1 │ Arya │ Stark │ \x1b[1m3000\x1b[0m │ │",
"│ 20 │ Jon │ Snow │ \x1b[1m2000\x1b[0m │ You know nothing, Jon Snow! │",
"│ 300 │ Tyrion │ Lannister │ \x1b[1m5000\x1b[0m │ │",
"├─────┼────────────┼───────────┼────────┼─────────────────────────────┤",
"│ │ │ TOTAL │ \x1b[1m10000\x1b[0m │ │",
"└─────┴────────────┴───────────┴────────┴─────────────────────────────┘",
}
out := tw.Render()
assert.Equal(t, strings.Join(expectedOut, "\n"), out)
if strings.Join(expectedOut, "\n") != out {
for _, line := range strings.Split(out, "\n") {
fmt.Printf("%#v,\n", line)
}
}
}
func TestTable_Render_SetWidth_Title(t *testing.T) {
tw := NewWriter()
tw.AppendHeader(testHeader)
tw.AppendRows(testRows)
tw.AppendFooter(testFooter)
tw.SetTitle("Game Of Thrones")
t.Run("length 20", func(t *testing.T) {
tw.SetAllowedRowLength(20)
expectedOut := []string{
"+------------------+",
"| Game Of Thrones |",
"+-----+----------- ~",
"| # | FIRST NAME ~",
"+-----+----------- ~",
"| 1 | Arya ~",
"| 20 | Jon ~",
"| 300 | Tyrion ~",
"+-----+----------- ~",
"| | ~",
"+-----+----------- ~",
}
assert.Equal(t, strings.Join(expectedOut, "\n"), tw.Render())
})
t.Run("length 30", func(t *testing.T) {
tw.SetAllowedRowLength(30)
expectedOut := []string{
"+----------------------------+",
"| Game Of Thrones |",
"+-----+------------+-------- ~",
"| # | FIRST NAME | LAST NA ~",
"+-----+------------+-------- ~",
"| 1 | Arya | Stark ~",
"| 20 | Jon | Snow ~",
"| 300 | Tyrion | Lannist ~",
"+-----+------------+-------- ~",
"| | | TOTAL ~",
"+-----+------------+-------- ~",
}
assert.Equal(t, strings.Join(expectedOut, "\n"), tw.Render())
})
}
func TestTable_Render_WidthEnforcer(t *testing.T) {
tw := NewWriter()
tw.AppendRows([]Row{
{"U2", "Hey", "2021-04-19 13:37", "Yuh yuh yuh"},
{"S12", "Uhhhh", "2021-04-19 13:37", "Some dummy data here"},
{"R123", "Lobsters", "2021-04-19 13:37", "I like lobsters"},
{"R123", "Some big name here and it's pretty big", "2021-04-19 13:37", "Abcdefghijklmnopqrstuvwxyz"},
{"R123", "Small name", "2021-04-19 13:37", "Abcdefghijklmnopqrstuvwxyz"},
})
tw.SetColumnConfigs([]ColumnConfig{
{Number: 2, WidthMax: 20, WidthMaxEnforcer: text.Trim},
})
compareOutput(t, tw.Render(), `
+------+----------------------+------------------+----------------------------+
| U2 | Hey | 2021-04-19 13:37 | Yuh yuh yuh |
| S12 | Uhhhh | 2021-04-19 13:37 | Some dummy data here |
| R123 | Lobsters | 2021-04-19 13:37 | I like lobsters |
| R123 | Some big name here a | 2021-04-19 13:37 | Abcdefghijklmnopqrstuvwxyz |
| R123 | Small name | 2021-04-19 13:37 | Abcdefghijklmnopqrstuvwxyz |
+------+----------------------+------------------+----------------------------+`)
}