package table import ( "fmt" "sort" "strings" "testing" "github.com/jedib0t/go-pretty/v6/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(), ` (---------------------------------------------------------------------) [] [] {-----^------------^-----------^--------^-----------------------------} [< #>||||< >] {-----+------------+-----------+--------+-----------------------------} [< 1>|||< 3000>|< >] [< 20>|||< 2000>|] [<300>|||< 5000>|< >] [< 0>|||< 0>|] [< >|< >|< >|< >|] [< >|< >|< >|< >|] {-----+------------+-----------+--------+-----------------------------} [< >|< >||< 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.1│1.2│1.3│ │2.1│2.2│2.3│ │1.1│1.2│1.3│ │2.1│2.2│2.3│ ├───┼───┼───┤ ├───┼───┼───┤ │1.4│1.5│1.6│ │2.4│2.5│2.6│ │1.4│1.5│1.6│ │2.4│2.5│2.6│ ├───┼───┼───┼───┼───┼───┼───┤ │1.7│1.8│1.9│0.2│2.7│2.8│2.9│ │1.7│1.8│0.1│0.2│0.3│2.8│2.9│ ├───┴───┼───┼───┼───┼───┴───┤ │ │0.4│0.5│0.6│ │ │ │0.4│0.5│0.6│ │ ├───┬───┼───┼───┼───┼───┬───┤ │3.1│3.2│3.3│0.8│4.1│4.2│4.3│ │3.1│3.2│0.7│0.8│0.9│4.2│4.3│ ├───┼───┼───┼───┼───┼───┼───┤ │3.4│3.5│3.6│ │4.4│4.5│4.6│ │3.4│3.5│3.6│ │4.4│4.5│4.6│ ├───┼───┼───┤ ├───┼───┼───┤ │3.7│3.8│3.9│ │4.7│4.8│4.9│ │3.7│3.8│3.9│ │4.7│4.8│4.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\x1b[0m|\x1b[44;30;1m< >\x1b[0m|< >]\n"+ "[< >|< >|\x1b[41;30;1m< NAME>\x1b[0m|\x1b[42;30;1m\x1b[0m|\x1b[44;30;1m< SALARIII>\x1b[0m|< >]\n"+ "{---+-----+-----------+------------+------------------+-----------------------------}\n"+ "[<1>|< 1>|\x1b[40;31m< (r_Arya>\x1b[0m|\x1b[40;32m\x1b[0m|\x1b[40;34m< $ 3000.03>\x1b[0m|< >]\n"+ "[<2>|< 20>|\x1b[40;31m< (r_Jon>\x1b[0m|\x1b[40;32m\x1b[0m|\x1b[40;34m< $ 2000.03>\x1b[0m|]\n"+ "[<3>|<300>|\x1b[40;31m<(r_Tyrion>\x1b[0m|\x1b[40;32m\x1b[0m|\x1b[40;34m< >\x1b[0m|< >]\n"+ "[< >|< >|\x1b[40;31m< >\x1b[0m|\x1b[40;32m\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|]\n"+ "[< >|< >|\x1b[40;31m< >\x1b[0m|\x1b[40;32m\x1b[0m|\x1b[40;34m< >\x1b[0m|]\n"+ "[< >|< >|\x1b[40;31m< >\x1b[0m|\x1b[40;32m< >\x1b[0m|\x1b[40;34m< $ 0.03>\x1b[0m|]\n"+ "{---+-----+-----------+------------+------------------+-----------------------------}\n"+ "[< >|< >|\x1b[41;30m< (F_>\x1b[0m|\x1b[42;30m\x1b[0m|\x1b[44;30m< >\x1b[0m|< >]\n"+ "[< >|< >|\x1b[41;30m< >\x1b[0m|\x1b[42;30m\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[< #>||||< >]\n{-----+------------+-----------+--------+-----------------------------}\n[< 1>|||< 3000>|< >]\n[< 20>|||< 2000>|]\n[<300>|||< 5000>|< >]\n{-----+------------+-----------+--------+-----------------------------}\n[< >|< >||< 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 | +------+----------------------+------------------+----------------------------+`) }