mirror of
https://github.com/zoriya/bubbles.git
synced 2026-08-05 20:56:17 +00:00
422 lines
13 KiB
Go
422 lines
13 KiB
Go
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) {
|
||
// 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()))
|
||
})
|
||
}
|
||
|
||
// 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 │
|
||
//│ … … … … │
|
||
//└───────────────────────────────────────────┘
|
||
}
|