Files
bubbles/table/table_test.go
T
2025-04-04 14:51:38 -07:00

416 lines
13 KiB
Go
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package table
import (
"fmt"
"image/color"
"testing"
"github.com/charmbracelet/lipgloss/v2"
"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 TestTableAlignment(t *testing.T) {
headers := []string{
"Name",
"Country of Origin",
"Dunk-able",
}
rows := [][]string{
{"Chocolate Digestives", "UK", "Yes"},
{"Tim Tams", "Australia", "No"},
{"Hobnobs", "UK", "Yes"},
}
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) {
biscuits := New(
WithHeaders(headers...),
WithRows(rows...),
).
// Strip styles
SetStyleFunc(func(_, _ int) lipgloss.Style {
return niceMargins
})
golden.RequireEqual(t, []byte(biscuits.View()))
})
}
// Test Styles
func TestOverwriteStyles(t *testing.T) {
tests := []struct {
name string
styles Styles
}{
{"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) {
tb := New(
WithHeaders(headers...),
WithRows(rows...),
WithFocused(true),
WithHeight(10),
)
tb.OverwriteStyles(tc.styles)
golden.RequireEqual(t, []byte(tb.View()))
})
}
}
func TestSetStyles(t *testing.T) {
tests := []struct {
name string
styles Styles
}{
{"empty styles", Styles{
Selected: lipgloss.NewStyle(),
Header: lipgloss.NewStyle(),
Cell: lipgloss.NewStyle(),
}},
{"new styles", Styles{
Selected: niceMargins.Background(lipgloss.Color("68")),
Header: niceMargins,
Cell: niceMargins,
}},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
table := New(
WithHeaders(headers...),
WithRows(rows...),
WithFocused(true),
WithHeight(10),
)
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),
)
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 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()))
})
}
}
func TestNewFromTemplate(t *testing.T) {
// Using Pokemon example from https://github.com/charmbracelet/lipgloss.
baseStyle := lipgloss.NewStyle().Padding(0, 1)
headerStyle := baseStyle.Foreground(lipgloss.Color("252")).Bold(true)
selectedStyle := baseStyle.Foreground(lipgloss.Color("#01BE85")).Background(lipgloss.Color("#00432F"))
typeColors := map[string]color.Color{
"Bug": lipgloss.Color("#D7FF87"),
"Electric": lipgloss.Color("#FDFF90"),
"Fire": lipgloss.Color("#FF7698"),
"Flying": lipgloss.Color("#FF87D7"),
"Grass": lipgloss.Color("#75FBAB"),
"Ground": lipgloss.Color("#FF875F"),
"Normal": lipgloss.Color("#929292"),
"Poison": lipgloss.Color("#7D5AFC"),
"Water": lipgloss.Color("#00E2C7"),
}
dimTypeColors := map[string]color.Color{
"Bug": lipgloss.Color("#97AD64"),
"Electric": lipgloss.Color("#FCFF5F"),
"Fire": lipgloss.Color("#BA5F75"),
"Flying": lipgloss.Color("#C97AB2"),
"Grass": lipgloss.Color("#59B980"),
"Ground": lipgloss.Color("#C77252"),
"Normal": lipgloss.Color("#727272"),
"Poison": lipgloss.Color("#634BD0"),
"Water": lipgloss.Color("#439F8E"),
}
pokemonHeaders := []string{"#", "Name", "Type 1", "Type 2", "Japanese", "Official Rom."}
pokemonData := [][]string{
{"1", "Bulbasaur", "Grass", "Poison", "フシギダネ", "Fushigidane"},
{"2", "Ivysaur", "Grass", "Poison", "フシギソウ", "Fushigisou"},
{"3", "Venusaur", "Grass", "Poison", "フシギバナ", "Fushigibana"},
{"4", "Charmander", "Fire", "", "ヒトカゲ", "Hitokage"},
{"5", "Charmeleon", "Fire", "", "リザード", "Lizardo"},
{"6", "Charizard", "Fire", "Flying", "リザードン", "Lizardon"},
{"7", "Squirtle", "Water", "", "ゼニガメ", "Zenigame"},
{"8", "Wartortle", "Water", "", "カメール", "Kameil"},
{"9", "Blastoise", "Water", "", "カメックス", "Kamex"},
{"10", "Caterpie", "Bug", "", "キャタピー", "Caterpie"},
{"11", "Metapod", "Bug", "", "トランセル", "Trancell"},
{"12", "Butterfree", "Bug", "Flying", "バタフリー", "Butterfree"},
{"13", "Weedle", "Bug", "Poison", "ビードル", "Beedle"},
{"14", "Kakuna", "Bug", "Poison", "コクーン", "Cocoon"},
{"15", "Beedrill", "Bug", "Poison", "スピアー", "Spear"},
{"16", "Pidgey", "Normal", "Flying", "ポッポ", "Poppo"},
{"17", "Pidgeotto", "Normal", "Flying", "ピジョン", "Pigeon"},
{"18", "Pidgeot", "Normal", "Flying", "ピジョット", "Pigeot"},
{"19", "Rattata", "Normal", "", "コラッタ", "Koratta"},
{"20", "Raticate", "Normal", "", "ラッタ", "Ratta"},
{"21", "Spearow", "Normal", "Flying", "オニスズメ", "Onisuzume"},
{"22", "Fearow", "Normal", "Flying", "オニドリル", "Onidrill"},
{"23", "Ekans", "Poison", "", "アーボ", "Arbo"},
{"24", "Arbok", "Poison", "", "アーボック", "Arbok"},
{"25", "Pikachu", "Electric", "", "ピカチュウ", "Pikachu"},
{"26", "Raichu", "Electric", "", "ライチュウ", "Raichu"},
{"27", "Sandshrew", "Ground", "", "サンド", "Sand"},
{"28", "Sandslash", "Ground", "", "サンドパン", "Sandpan"},
}
lipglossTable := table.New().
Border(lipgloss.NormalBorder()).
BorderStyle(lipgloss.NewStyle().Foreground(lipgloss.Color("238"))).
Headers(pokemonHeaders...).
Width(80).
Rows(pokemonData...).
StyleFunc(func(row, col int) lipgloss.Style {
if row == table.HeaderRow {
return headerStyle
}
if pokemonData[row][1] == "Pikachu" {
return selectedStyle
}
even := row%2 == 0
switch col {
case 2, 3: // Type 1 + 2
c := typeColors
if even {
c = dimTypeColors
}
color := c[fmt.Sprint(pokemonData[row][col])]
return baseStyle.Foreground(color)
}
if even {
return baseStyle.Foreground(lipgloss.Color("245"))
}
return baseStyle.Foreground(lipgloss.Color("252"))
})
bubblesTable := NewFromTemplate(lipglossTable, headers, pokemonData)
golden.RequireEqual(t, []byte(bubblesTable.View()))
}
func TestOverwriteStylesFromLipgloss(t *testing.T) {
baseStyle := lipgloss.NewStyle().Padding(0, 1)
headerStyle := baseStyle.Foreground(lipgloss.Color("252")).Bold(true)
lipglossTable := table.New().
Border(lipgloss.NormalBorder()).
BorderStyle(lipgloss.NewStyle().Foreground(lipgloss.Color("238"))).
Width(80).
StyleFunc(func(row, col int) lipgloss.Style {
if row == table.HeaderRow {
return headerStyle
}
even := row%2 == 0
if even {
return baseStyle.Foreground(lipgloss.Color("245"))
}
return baseStyle.Foreground(lipgloss.Color("252"))
})
bubblesTable := New().SetHeaders(headers...).SetRows(rows...)
bubblesTable.OverwriteStylesFromLipgloss(lipglossTable)
golden.RequireEqual(t, []byte(bubblesTable.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 │
//│ …   …   …   …  │
//└───────────────────────────────────────────┘
}