test(table): add style and border tests

This commit is contained in:
bashbunni
2025-04-03 16:02:50 -07:00
parent ae7fb0d592
commit 5191362e39
13 changed files with 386 additions and 118 deletions
+275 -105
View File
@@ -1,166 +1,336 @@
package table
import (
"strings"
"fmt"
"reflect"
"testing"
"github.com/charmbracelet/lipgloss/v2"
"github.com/charmbracelet/x/ansi"
"github.com/charmbracelet/lipgloss/v2/table"
"github.com/charmbracelet/x/exp/golden"
)
// Reusable inputs
var niceMargins = lipgloss.NewStyle().Padding(0, 1)
var headers = []string{"Rank", "City", "Country", "Population"}
var rows = [][]string{
{"1", "Tokyo", "Japan", "37,274,000"},
{"2", "Delhi", "India", "32,065,760"},
{"3", "Shanghai", "China", "28,516,904"},
{"4", "Dhaka", "Bangladesh", "22,478,116"},
{"5", "São Paulo", "Brazil", "22,429,800"},
{"6", "Mexico City", "Mexico", "22,085,140"},
{"7", "Cairo", "Egypt", "21,750,020"},
{"8", "Beijing", "China", "21,333,332"},
{"9", "Mumbai", "India", "20,961,472"},
{"10", "Osaka", "Japan", "19,059,856"},
{"11", "Chongqing", "China", "16,874,740"},
{"12", "Karachi", "Pakistan", "16,839,950"},
{"13", "Istanbul", "Turkey", "15,636,243"},
{"14", "Kinshasa", "DR Congo", "15,628,085"},
{"15", "Lagos", "Nigeria", "15,387,639"},
{"16", "Buenos Aires", "Argentina", "15,369,919"},
}
// Tests
func TestNew(t *testing.T) {
headers := []string{"Rank", "City", "Country", "Population"}
rows := [][]string{
{"1", "Tokyo", "Japan", "37,274,000"},
{"2", "Delhi", "India", "32,065,760"},
{"3", "Shanghai", "China", "28,516,904"},
{"4", "Dhaka", "Bangladesh", "22,478,116"},
{"5", "São Paulo", "Brazil", "22,429,800"},
{"6", "Mexico City", "Mexico", "22,085,140"},
{"7", "Cairo", "Egypt", "21,750,020"},
{"8", "Beijing", "China", "21,333,332"},
{"9", "Mumbai", "India", "20,961,472"},
{"10", "Osaka", "Japan", "19,059,856"},
}
t.Run("new with options", func(t *testing.T) {
tb := New(
WithHeaders(headers...),
WithRows(rows...),
WithHeight(10),
)
tb.View()
})
t.Run("new, no options", func(t *testing.T) {
tb := New().SetHeaders(headers...).SetRows(rows...)
tb.View()
})
}
func TestFromValues(t *testing.T) {
input := "foo1,bar1\nfoo2,bar2\nfoo3,bar3"
table := New(WithColumns([]Column{{Title: "Foo"}, {Title: "Bar"}}))
table := New(WithHeaders("Foo", "Bar"))
table.FromValues(input, ",")
if len(table.rows) != 3 {
t.Fatalf("expect table to have 3 rows but it has %d", len(table.rows))
}
expect := []Row{
expect := [][]string{
{"foo1", "bar1"},
{"foo2", "bar2"},
{"foo3", "bar3"},
}
if !deepEqual(table.rows, expect) {
if !reflect.DeepEqual(table.rows, expect) {
t.Fatal("table rows is not equals to the input")
}
}
func TestFromValuesWithTabSeparator(t *testing.T) {
input := "foo1.\tbar1\nfoo,bar,baz\tbar,2"
table := New(WithColumns([]Column{{Title: "Foo"}, {Title: "Bar"}}))
table := New(WithHeaders("Foo", "Bar"))
table.FromValues(input, "\t")
if len(table.rows) != 2 {
t.Fatalf("expect table to have 2 rows but it has %d", len(table.rows))
}
expect := []Row{
expect := [][]string{
{"foo1.", "bar1"},
{"foo,bar,baz", "bar,2"},
}
if !deepEqual(table.rows, expect) {
if !reflect.DeepEqual(table.rows, expect) {
t.Fatal("table rows is not equals to the input")
}
}
func deepEqual(a, b []Row) bool {
if len(a) != len(b) {
return false
func TestTableAlignment(t *testing.T) {
headers := []string{
"Name",
"Country of Origin",
"Dunk-able",
}
for i, r := range a {
for j, f := range r {
if f != b[i][j] {
return false
}
}
rows := [][]string{
{"Chocolate Digestives", "UK", "Yes"},
{"Tim Tams", "Australia", "No"},
{"Hobnobs", "UK", "Yes"},
}
return true
t.Run("No border", func(t *testing.T) {
biscuits := New(
WithHeaders(headers...),
WithRows(rows...),
).
// Remove default border.
SetBorder(false).
// Remove default border under header.
BorderHeader(false).
// Strip styles.
SetStyleFunc(func(_, _ int) lipgloss.Style {
return niceMargins
})
golden.RequireEqual(t, []byte(biscuits.View()))
})
t.Run("With border", func(t *testing.T) {
// TODO how do we style header border?
// s.Header = s.Header.
// BorderStyle(lipgloss.NormalBorder()).
// BorderForeground(lipgloss.Color("240")).
// Bold(false)
biscuits := New(
WithHeaders(headers...),
WithRows(rows...),
).
// Strip styles
SetStyleFunc(func(_, _ int) lipgloss.Style {
return niceMargins
})
golden.RequireEqual(t, []byte(biscuits.View()))
})
}
var cols = []Column{
{Title: "col1", Width: 10},
{Title: "col2", Width: 10},
{Title: "col3", Width: 10},
}
// Test Styles
func TestRenderRow(t *testing.T) {
func TestOverwriteStyles(t *testing.T) {
tests := []struct {
name string
table *Model
expected string
name string
styles Styles
}{
{
name: "simple row",
table: &Model{
rows: []Row{{"Foooooo", "Baaaaar", "Baaaaaz"}},
cols: cols,
styles: Styles{Cell: lipgloss.NewStyle()},
},
expected: "Foooooo Baaaaar Baaaaaz ",
},
{
name: "simple row with truncations",
table: &Model{
rows: []Row{{"Foooooooooo", "Baaaaaaaaar", "Quuuuuuuuux"}},
cols: cols,
styles: Styles{Cell: lipgloss.NewStyle()},
},
expected: "Foooooooo…Baaaaaaaa…Quuuuuuuu…",
},
{
name: "simple row avoiding truncations",
table: &Model{
rows: []Row{{"Fooooooooo", "Baaaaaaaar", "Quuuuuuuux"}},
cols: cols,
styles: Styles{Cell: lipgloss.NewStyle()},
},
expected: "FoooooooooBaaaaaaaarQuuuuuuuux",
},
{"clear styles", Styles{
Selected: lipgloss.NewStyle(),
Header: lipgloss.NewStyle(),
Cell: lipgloss.NewStyle(),
}},
{"new styles", Styles{
Selected: niceMargins.Foreground(lipgloss.Color("68")),
Header: niceMargins,
Cell: niceMargins,
}},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
row := tc.table.renderRow(0)
if row != tc.expected {
t.Fatalf("\n\nWant: \n%s\n\nGot: \n%s\n", tc.expected, row)
}
tb := New(
WithHeaders(headers...),
WithRows(rows...),
WithFocused(true),
WithHeight(10),
)
tb.OverwriteStyles(tc.styles)
golden.RequireEqual(t, []byte(tb.View()))
})
}
}
func TestTableAlignment(t *testing.T) {
t.Run("No border", func(t *testing.T) {
biscuits := New(
WithHeight(5),
WithColumns([]Column{
{Title: "Name", Width: 25},
{Title: "Country of Origin", Width: 16},
{Title: "Dunk-able", Width: 12},
}),
WithRows([]Row{
{"Chocolate Digestives", "UK", "Yes"},
{"Tim Tams", "Australia", "No"},
{"Hobnobs", "UK", "Yes"},
}),
)
got := ansiStrip(biscuits.View())
golden.RequireEqual(t, []byte(got))
})
t.Run("With border", func(t *testing.T) {
baseStyle := lipgloss.NewStyle().
BorderStyle(lipgloss.NormalBorder()).
BorderForeground(lipgloss.Color("240"))
func TestSetStyles(t *testing.T) {
tests := []struct {
name string
styles Styles
}{
{"empty styles; do nothing", Styles{
Selected: lipgloss.NewStyle(),
Header: lipgloss.NewStyle(),
Cell: lipgloss.NewStyle(),
}},
{"new styles", Styles{
Selected: niceMargins.Background(lipgloss.Color("68")),
Header: niceMargins,
Cell: niceMargins,
}},
}
s := DefaultStyles()
s.Header = s.Header.
BorderStyle(lipgloss.NormalBorder()).
BorderForeground(lipgloss.Color("240")).
BorderBottom(true).
Bold(false)
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
table := New(
WithHeaders(headers...),
WithRows(rows...),
WithFocused(true),
WithHeight(10),
)
biscuits := New(
WithHeight(5),
WithColumns([]Column{
{Title: "Name", Width: 25},
{Title: "Country of Origin", Width: 16},
{Title: "Dunk-able", Width: 12},
}),
WithRows([]Row{
{"Chocolate Digestives", "UK", "Yes"},
{"Tim Tams", "Australia", "No"},
{"Hobnobs", "UK", "Yes"},
}),
WithStyles(s),
table.SetStyles(tc.styles)
golden.RequireEqual(t, []byte(table.View()))
})
}
}
func TestSetStyleFunc(t *testing.T) {
t.Run("Clear styles with StyleFunc", func(t *testing.T) {
tb := New(
WithHeaders(headers...),
WithRows(rows...),
WithFocused(true),
WithHeight(10),
)
got := ansiStrip(baseStyle.Render(biscuits.View()))
golden.RequireEqual(t, []byte(got))
tb.SetStyleFunc(table.StyleFunc(func(row, col int) lipgloss.Style {
if row == tb.Cursor() {
return niceMargins.Background(lipgloss.Color("68"))
}
return niceMargins
}))
golden.RequireEqual(t, []byte(tb.View()))
})
}
func ansiStrip(s string) string {
// Replace all \r\n with \n
s = strings.ReplaceAll(s, "\r\n", "\n")
return ansi.Strip(s)
func TestSetBorder(t *testing.T) {
tests := []struct {
name string
borders []bool
}{
{"unset all borders", []bool{false}},
{"set all borders", []bool{true}},
{"vertical borders only", []bool{true, false}},
{"no top border", []bool{false, true, true}},
{"no left border", []bool{true, true, true, false}},
{"row separator and no right border", []bool{true, false, true, true, true}},
{"set row and column separators", []bool{false, false, false, false, true, true}},
{"invalid number of arguments", []bool{true, false, false, false, false, true, true}},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
tb := New(
WithHeaders(headers...),
WithRows(rows...),
WithFocused(true),
WithHeight(10),
).SetBorder(tc.borders...)
golden.RequireEqual(t, []byte(tb.View()))
})
}
}
// Examples
func ExampleOption() {
var niceMargins = lipgloss.NewStyle().Padding(0, 1)
var headers = []string{"Rank", "City", "Country", "Population"}
var rows = [][]string{
{"1", "Tokyo", "Japan", "37,274,000"},
{"2", "Delhi", "India", "32,065,760"},
{"3", "Shanghai", "China", "28,516,904"},
{"4", "Dhaka", "Bangladesh", "22,478,116"},
{"5", "São Paulo", "Brazil", "22,429,800"},
{"6", "Mexico City", "Mexico", "22,085,140"},
{"7", "Cairo", "Egypt", "21,750,020"},
{"8", "Beijing", "China", "21,333,332"},
{"9", "Mumbai", "India", "20,961,472"},
{"10", "Osaka", "Japan", "19,059,856"},
}
t := New(
WithHeaders(headers...),
WithRows(rows...),
WithFocused(true),
WithHeight(10),
).OverwriteStyles(Styles{
Selected: niceMargins,
Header: niceMargins,
Cell: niceMargins,
})
fmt.Println(t.View())
// Output:
//┌───────────────────────────────────────────┐
//│ Rank  City   Country   Population │
//├───────────────────────────────────────────┤
//│ 1   Tokyo   Japan   37,274,000 │
//│ 2   Delhi   India   32,065,760 │
//│ 3   Shanghai   China   28,516,904 │
//│ 4   Dhaka   Bangladesh  22,478,116 │
//│ 5   São Paulo   Brazil   22,429,800 │
//│ …   …   …   …  │
//└───────────────────────────────────────────┘
}
func ExampleModel_SetRows() {
var niceMargins = lipgloss.NewStyle().Padding(0, 1)
var headers = []string{"Rank", "City", "Country", "Population"}
var rows = [][]string{
{"1", "Tokyo", "Japan", "37,274,000"},
{"2", "Delhi", "India", "32,065,760"},
{"3", "Shanghai", "China", "28,516,904"},
{"4", "Dhaka", "Bangladesh", "22,478,116"},
{"5", "São Paulo", "Brazil", "22,429,800"},
{"6", "Mexico City", "Mexico", "22,085,140"},
{"7", "Cairo", "Egypt", "21,750,020"},
{"8", "Beijing", "China", "21,333,332"},
{"9", "Mumbai", "India", "20,961,472"},
{"10", "Osaka", "Japan", "19,059,856"},
}
tb := New().
SetHeaders(headers...).
SetRows(rows...).
SetHeight(10).
OverwriteStyles(Styles{
Selected: niceMargins,
Header: niceMargins,
Cell: niceMargins,
})
fmt.Println(tb.View())
// Output:
//┌───────────────────────────────────────────┐
//│ Rank  City   Country   Population │
//├───────────────────────────────────────────┤
//│ 1   Tokyo   Japan   37,274,000 │
//│ 2   Delhi   India   32,065,760 │
//│ 3   Shanghai   China   28,516,904 │
//│ 4   Dhaka   Bangladesh  22,478,116 │
//│ 5   São Paulo   Brazil   22,429,800 │
//│ …   …   …   …  │
//└───────────────────────────────────────────┘
}
+10
View File
@@ -0,0 +1,10 @@
┌────────────────────────────────────┐
│RankCity Country Population│
├────────────────────────────────────┤
│1 Tokyo Japan 37,274,000│
│2 Delhi India 32,065,760│
│3 Shanghai China 28,516,904│
│4 Dhaka Bangladesh22,478,116│
│5 São Paulo Brazil 22,429,800│
│… … … … │
└────────────────────────────────────┘
+10
View File
@@ -0,0 +1,10 @@
┌────────────────────────────────────────────┐
│ Rank  City   Country   Population │
├────────────────────────────────────────────┤
│ 1   Tokyo   Japan   37,274,000 │
│ 2   Delhi   India   32,065,760 │
│ 3   Shanghai   China   28,516,904 │
│ 4   Dhaka   Bangladesh  22,478,116 │
│ 5   São Paulo   Brazil   22,429,800 │
│ …   …   …   …  │
└────────────────────────────────────────────┘
@@ -0,0 +1,10 @@
┌────────────────────────────────────────────┐
│ Rank  City   Country   Population │
├────────────────────────────────────────────┤
│ 1   Tokyo   Japan   37,274,000 │
│ 2   Delhi   India   32,065,760 │
│ 3   Shanghai   China   28,516,904 │
│ 4   Dhaka   Bangladesh  22,478,116 │
│ 5   São Paulo   Brazil   22,429,800 │
│ …   …   …   …  │
└────────────────────────────────────────────┘
+10
View File
@@ -0,0 +1,10 @@
────────────────────────────────────────────┐
 Rank  City   Country   Population │
────────────────────────────────────────────┤
 1   Tokyo   Japan   37,274,000 │
 2   Delhi   India   32,065,760 │
 3   Shanghai   China   28,516,904 │
 4   Dhaka   Bangladesh  22,478,116 │
 5   São Paulo   Brazil   22,429,800 │
 …   …   …   …  │
────────────────────────────────────────────┘
+10
View File
@@ -0,0 +1,10 @@
│ Rank  City   Country   Population │
├────────────────────────────────────────────┤
│ 1   Tokyo   Japan   37,274,000 │
│ 2   Delhi   India   32,065,760 │
│ 3   Shanghai   China   28,516,904 │
│ 4   Dhaka   Bangladesh  22,478,116 │
│ 5   São Paulo   Brazil   22,429,800 │
│ 6   Mexico City   Mexico   22,085,140 │
│ …   …   …   …  │
└────────────────────────────────────────────┘
+10
View File
@@ -0,0 +1,10 @@
 Rank  City   Country   Population 
────────────────────────────────────────────
 1   Tokyo   Japan   37,274,000 
 2   Delhi   India   32,065,760 
 3   Shanghai   China   28,516,904 
 4   Dhaka   Bangladesh  22,478,116 
 5   São Paulo   Brazil   22,429,800 
 6   Mexico City   Mexico   22,085,140 
 …   …   …   … 
@@ -0,0 +1,10 @@
────────────────────────────────────────────
 Rank  City   Country   Population 
────────────────────────────────────────────
 1   Tokyo   Japan   37,274,000 
 2   Delhi   India   32,065,760 
 3   Shanghai   China   28,516,904 
 4   Dhaka   Bangladesh  22,478,116 
 5   São Paulo   Brazil   22,429,800 
 …   …   …   … 
────────────────────────────────────────────
@@ -0,0 +1,10 @@
┌────────────────────────────────────────────┐
│ Rank  City   Country   Population │
├────────────────────────────────────────────┤
 1   Tokyo   Japan   37,274,000 │
│ 2   Delhi   India   32,065,760 │
│ 3   Shanghai   China   28,516,904 │
│ 4   Dhaka   Bangladesh  22,478,116 │
│ 5   São Paulo   Brazil   22,429,800 │
│ …   …   …   …  │
└────────────────────────────────────────────┘
@@ -0,0 +1,10 @@
┌────────────────────────────────────────────┐
│ Rank  City   Country   Population │
├────────────────────────────────────────────┤
│ 1   Tokyo   Japan   37,274,000 │
│ 2   Delhi   India   32,065,760 │
│ 3   Shanghai   China   28,516,904 │
│ 4   Dhaka   Bangladesh  22,478,116 │
│ 5   São Paulo   Brazil   22,429,800 │
│ …   …   …   …  │
└────────────────────────────────────────────┘
+10
View File
@@ -0,0 +1,10 @@
┌────────────────────────────────────────────┐
│ Rank  City   Country   Population │
├────────────────────────────────────────────┤
 1   Tokyo   Japan   37,274,000 │
│ 2   Delhi   India   32,065,760 │
│ 3   Shanghai   China   28,516,904 │
│ 4   Dhaka   Bangladesh  22,478,116 │
│ 5   São Paulo   Brazil   22,429,800 │
│ …   …   …   …  │
└────────────────────────────────────────────┘
+4 -5
View File
@@ -1,5 +1,4 @@
 Name   Country of Orig  Dunk-able  
 Chocolate Digestives   UK   Yes  
 Tim Tams   Australia   No  
 Hobnobs   UK   Yes  
 Name   Country of Origin  Dunk-able 
 Chocolate Digestives  UK   Yes 
 Tim Tams   Australia   No 
 Hobnobs   UK   Yes 
+7 -8
View File
@@ -1,8 +1,7 @@
┌───────────────────────────────────────────────────────────
│ Name   Country of Orig  Dunk-able  │
│───────────────────────────────────────────────────────────
│ Chocolate Digestives   UK   Yes  
│ Tim Tams   Australia   No  
│ Hobnobs   UK   Yes  
│ │
└───────────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────┐
│ Name   Country of Origin  Dunk-able │
────────────────────────────────────────────────────
│ Chocolate Digestives  UK   Yes 
│ Tim Tams   Australia   No 
│ Hobnobs   UK   Yes 
└────────────────────────────────────────────────────┘