lipgloss/tree/tree_test.go
Carlos Alexandro Becker feb42a9be4
feat: move tree to root (#342)
* feat: support any type for list Root func

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* feat: move tree to root

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* feat: more cleanup

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* docs(examples): bring back tree examples

* docs(examples): add tree toggle example

* docs(examples): fix toggle tree hierarchy; as bashbunni metadata

* fix(tree): stylize whitespace when vertically joining tree elements

* docs: get started with trees

* feat: add RootStyle with example

* test: add test for RootStyle func

* docs(godoc): replace list mentions in tree package

* refactor(examples): use Root shorthand instead of tree.New where applicable

---------

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
Co-authored-by: bashbunni <bunni@bashbunni.dev>
Co-authored-by: Christian Rocha <christian@rocha.is>
2024-08-19 15:43:29 -03:00

732 lines
13 KiB
Go

package tree_test
import (
"strings"
"testing"
"unicode"
"github.com/aymanbagabas/go-udiff"
"github.com/charmbracelet/lipgloss"
"github.com/charmbracelet/lipgloss/list"
"github.com/charmbracelet/lipgloss/table"
"github.com/charmbracelet/lipgloss/tree"
"github.com/charmbracelet/x/exp/golden"
"github.com/muesli/termenv"
)
func TestTree(t *testing.T) {
tr := tree.New().
Child(
"Foo",
tree.Root("Bar").
Child(
"Qux",
tree.Root("Quux").
Child(
"Foo",
"Bar",
),
"Quuux",
),
"Baz",
)
want := `
├── Foo
├── Bar
│ ├── Qux
│ ├── Quux
│ │ ├── Foo
│ │ └── Bar
│ └── Quuux
└── Baz
`
assertEqual(t, want, tr.String())
tr.Enumerator(tree.RoundedEnumerator)
want = `
├── Foo
├── Bar
│ ├── Qux
│ ├── Quux
│ │ ├── Foo
│ │ ╰── Bar
│ ╰── Quuux
╰── Baz
`
assertEqual(t, want, tr.String())
}
func TestTreeHidden(t *testing.T) {
tree := tree.New().
Child(
"Foo",
tree.Root("Bar").
Child(
"Qux",
tree.Root("Quux").
Child("Foo", "Bar").
Hide(true),
"Quuux",
),
"Baz",
)
want := `
├── Foo
├── Bar
│ ├── Qux
│ └── Quuux
└── Baz
`
assertEqual(t, want, tree.String())
}
func TestTreeAllHidden(t *testing.T) {
tree := tree.New().
Child(
"Foo",
tree.Root("Bar").
Child(
"Qux",
tree.Root("Quux").
Child(
"Foo",
"Bar",
),
"Quuux",
),
"Baz",
).Hide(true)
want := ``
assertEqual(t, want, tree.String())
}
func TestTreeRoot(t *testing.T) {
tree := tree.New().
Root("Root").
Child(
"Foo",
tree.Root("Bar").
Child("Qux", "Quuux"),
"Baz",
)
want := `
Root
├── Foo
├── Bar
│ ├── Qux
│ └── Quuux
└── Baz
`
assertEqual(t, want, tree.String())
}
func TestTreeStartsWithSubtree(t *testing.T) {
tree := tree.New().
Child(
tree.New().
Root("Bar").
Child("Qux", "Quuux"),
"Baz",
)
want := `
├── Bar
│ ├── Qux
│ └── Quuux
└── Baz
`
assertEqual(t, want, tree.String())
}
func TestTreeAddTwoSubTreesWithoutName(t *testing.T) {
tree := tree.New().
Child(
"Bar",
"Foo",
tree.New().
Child(
"Qux",
"Qux",
"Qux",
"Qux",
"Qux",
),
tree.New().
Child(
"Quux",
"Quux",
"Quux",
"Quux",
"Quux",
),
"Baz",
)
want := `
├── Bar
├── Foo
│ ├── Qux
│ ├── Qux
│ ├── Qux
│ ├── Qux
│ ├── Qux
│ ├── Quux
│ ├── Quux
│ ├── Quux
│ ├── Quux
│ └── Quux
└── Baz
`
assertEqual(t, want, tree.String())
}
func TestTreeLastNodeIsSubTree(t *testing.T) {
tree := tree.New().
Child(
"Foo",
tree.Root("Bar").
Child("Qux",
tree.Root("Quux").Child("Foo", "Bar"),
"Quuux",
),
)
want := `
├── Foo
└── Bar
├── Qux
├── Quux
│ ├── Foo
│ └── Bar
└── Quuux
`
assertEqual(t, want, tree.String())
}
func TestTreeNil(t *testing.T) {
tree := tree.New().
Child(
nil,
tree.Root("Bar").
Child(
"Qux",
tree.Root("Quux").
Child("Bar"),
"Quuux",
),
"Baz",
)
want := `
├── Bar
│ ├── Qux
│ ├── Quux
│ │ └── Bar
│ └── Quuux
└── Baz
`
assertEqual(t, want, tree.String())
}
func TestTreeCustom(t *testing.T) {
tree := tree.New().
Child(
"Foo",
tree.New().
Root("Bar").
Child(
"Qux",
tree.New().
Root("Quux").
Child("Foo",
"Bar",
),
"Quuux",
),
"Baz",
).
ItemStyle(lipgloss.NewStyle().
Foreground(lipgloss.Color("9"))).
EnumeratorStyle(lipgloss.NewStyle().
Foreground(lipgloss.Color("12")).
PaddingRight(1)).
Enumerator(func(tree.Children, int) string {
return "->"
}).
Indenter(func(tree.Children, int) string {
return "->"
})
want := `
-> Foo
-> Bar
-> -> Qux
-> -> Quux
-> -> -> Foo
-> -> -> Bar
-> -> Quuux
-> Baz
`
assertEqual(t, want, tree.String())
}
func TestTreeMultilineNode(t *testing.T) {
tree := tree.New().
Root("Big\nRoot\nNode").
Child(
"Foo",
tree.New().
Root("Bar").
Child(
"Line 1\nLine 2\nLine 3\nLine 4",
tree.New().
Root("Quux").
Child(
"Foo",
"Bar",
),
"Quuux",
),
"Baz\nLine 2",
)
want := `
Big
Root
Node
├── Foo
├── Bar
│ ├── Line 1
│ │ Line 2
│ │ Line 3
│ │ Line 4
│ ├── Quux
│ │ ├── Foo
│ │ └── Bar
│ └── Quuux
└── Baz
Line 2
`
assertEqual(t, want, tree.String())
}
func TestTreeSubTreeWithCustomEnumerator(t *testing.T) {
tree := tree.New().
Root("The Root Node™").
Child(
tree.New().
Root("Parent").
Child("child 1", "child 2").
ItemStyleFunc(func(tree.Children, int) lipgloss.Style {
return lipgloss.NewStyle().
SetString("*")
}).
EnumeratorStyleFunc(func(_ tree.Children, i int) lipgloss.Style {
return lipgloss.NewStyle().
SetString("+").
PaddingRight(1)
}),
"Baz",
)
want := `
The Root Node™
├── Parent
│ + ├── * child 1
│ + └── * child 2
└── Baz
`
assertEqual(t, want, tree.String())
}
func TestTreeMixedEnumeratorSize(t *testing.T) {
tree := tree.New().
Root("The Root Node™").
Child(
"Foo",
"Foo",
"Foo",
"Foo",
"Foo",
).Enumerator(func(_ tree.Children, i int) string {
romans := map[int]string{
1: "I",
2: "II",
3: "III",
4: "IV",
5: "V",
6: "VI",
}
return romans[i+1]
})
want := `
The Root Node™
I Foo
II Foo
III Foo
IV Foo
V Foo
`
assertEqual(t, want, tree.String())
}
func TestTreeStyleNilFuncs(t *testing.T) {
tree := tree.New().
Root("Silly").
Child("Willy ", "Nilly").
ItemStyleFunc(nil).
EnumeratorStyleFunc(nil)
want := `
Silly
├──Willy
└──Nilly
`
assertEqual(t, want, tree.String())
}
func TestTreeStyleAt(t *testing.T) {
tree := tree.New().
Root("Root").
Child(
"Foo",
"Baz",
).Enumerator(func(data tree.Children, i int) string {
if data.At(i).Value() == "Foo" {
return ">"
}
return "-"
})
want := `
Root
> Foo
- Baz
`
assertEqual(t, want, tree.String())
}
func TestRootStyle(t *testing.T) {
lipgloss.SetColorProfile(termenv.TrueColor)
tree := tree.New().
Root("Root").
Child(
"Foo",
"Baz",
).
RootStyle(lipgloss.NewStyle().Background(lipgloss.Color("#5A56E0"))).
ItemStyle(lipgloss.NewStyle().Background(lipgloss.Color("#04B575")))
golden.RequireEqual(t, []byte(tree.String()))
}
func TestAt(t *testing.T) {
data := tree.NewStringData("Foo", "Bar")
if s := data.At(0).String(); s != "Foo" {
t.Errorf("want 'Foo', got '%s'", s)
}
if n := data.At(10); n != nil {
t.Errorf("want nil, got '%s'", n)
}
if n := data.At(-1); n != nil {
t.Errorf("want nil, got '%s'", n)
}
}
func TestFilter(t *testing.T) {
data := tree.NewFilter(tree.NewStringData(
"Foo",
"Bar",
"Baz",
"Nope",
)).
Filter(func(index int) bool {
return index != 3
})
tree := tree.New().
Root("Root").
Child(data)
want := `
Root
├── Foo
├── Bar
└── Baz
`
assertEqual(t, want, tree.String())
if got := data.At(1); got.Value() != "Bar" {
t.Errorf("want Bar, got %v", got)
}
if got := data.At(10); got != nil {
t.Errorf("want nil, got %v", got)
}
}
func TestNodeDataRemoveOutOfBounds(t *testing.T) {
data := tree.NewStringData("a")
if l := data.Length(); l != 1 {
t.Errorf("want data to contain 1 items, has %d", l)
}
}
func TestTreeTable(t *testing.T) {
tree := tree.New().
Child(
"Foo",
tree.New().
Root("Bar").
Child(
"Baz",
"Baz",
table.New().
Width(20).
StyleFunc(func(row, col int) lipgloss.Style {
return lipgloss.NewStyle().Padding(0, 1)
}).
Headers("Foo", "Bar").
Row("Qux", "Baz").
Row("Qux", "Baz"),
"Baz",
),
"Qux",
)
want := `
├── Foo
├── Bar
│ ├── Baz
│ ├── Baz
│ ├── ╭─────────┬────────╮
│ │ │ Foo │ Bar │
│ │ ├─────────┼────────┤
│ │ │ Qux │ Baz │
│ │ │ Qux │ Baz │
│ │ ╰─────────┴────────╯
│ └── Baz
└── Qux
`
assertEqual(t, want, tree.String())
}
func TestAddItemWithAndWithoutRoot(t *testing.T) {
t1 := tree.New().
Child(
"Foo",
"Bar",
tree.New().
Child("Baz"),
"Qux",
)
t2 := tree.New().
Child(
"Foo",
tree.New().
Root("Bar").
Child("Baz"),
"Qux",
)
want := `
├── Foo
├── Bar
│ └── Baz
└── Qux
`
assertEqual(t, want, t1.String())
assertEqual(t, want, t2.String())
}
func TestEmbedListWithinTree(t *testing.T) {
t1 := tree.New().
Child(list.New("A", "B", "C").
Enumerator(list.Arabic)).
Child(list.New("1", "2", "3").
Enumerator(list.Alphabet))
want := `
├── 1. A
│ 2. B
│ 3. C
└── A. 1
B. 2
C. 3
`
assertEqual(t, want, t1.String())
}
func TestMultilinePrefix(t *testing.T) {
paddingsStyle := lipgloss.NewStyle().PaddingLeft(1).PaddingBottom(1)
tree := tree.New().
Enumerator(func(_ tree.Children, i int) string {
if i == 1 {
return "│\n│"
}
return " "
}).
Indenter(func(_ tree.Children, i int) string {
return " "
}).
ItemStyle(paddingsStyle).
Child("Foo Document\nThe Foo Files").
Child("Bar Document\nThe Bar Files").
Child("Baz Document\nThe Baz Files")
want := `
Foo Document
The Foo Files
│ Bar Document
│ The Bar Files
Baz Document
The Baz Files
`
assertEqual(t, want, tree.String())
}
func TestMultilinePrefixSubtree(t *testing.T) {
paddingsStyle := lipgloss.NewStyle().
Padding(0, 0, 1, 1)
tree := tree.New().
Child("Foo").
Child("Bar").
Child(
tree.New().
Root("Baz").
Enumerator(func(_ tree.Children, i int) string {
if i == 1 {
return "│\n│"
}
return " "
}).
Indenter(func(tree.Children, int) string {
return " "
}).
ItemStyle(paddingsStyle).
Child("Foo Document\nThe Foo Files").
Child("Bar Document\nThe Bar Files").
Child("Baz Document\nThe Baz Files"),
).
Child("Qux")
want := `
├── Foo
├── Bar
├── Baz
│ Foo Document
│ The Foo Files
│ │ Bar Document
│ │ The Bar Files
│ Baz Document
│ The Baz Files
└── Qux
`
assertEqual(t, want, tree.String())
}
func TestMultilinePrefixInception(t *testing.T) {
glowEnum := func(_ tree.Children, i int) string {
if i == 1 {
return "│\n│"
}
return " "
}
glowIndenter := func(_ tree.Children, i int) string {
return " "
}
paddingsStyle := lipgloss.NewStyle().PaddingLeft(1).PaddingBottom(1)
tree := tree.New().
Enumerator(glowEnum).
Indenter(glowIndenter).
ItemStyle(paddingsStyle).
Child("Foo Document\nThe Foo Files").
Child("Bar Document\nThe Bar Files").
Child(
tree.New().
Enumerator(glowEnum).
Indenter(glowIndenter).
ItemStyle(paddingsStyle).
Child("Qux Document\nThe Qux Files").
Child("Quux Document\nThe Quux Files").
Child("Quuux Document\nThe Quuux Files"),
).
Child("Baz Document\nThe Baz Files")
want := `
Foo Document
The Foo Files
│ Bar Document
│ The Bar Files
Qux Document
The Qux Files
│ Quux Document
│ The Quux Files
Quuux Document
The Quuux Files
Baz Document
The Baz Files
`
assertEqual(t, want, tree.String())
}
func TestTypes(t *testing.T) {
tree := tree.New().
Child(0).
Child(true).
Child([]any{"Foo", "Bar"}).
Child([]string{"Qux", "Quux", "Quuux"})
want := `
├── 0
├── true
├── Foo
├── Bar
├── Qux
├── Quux
└── Quuux
`
assertEqual(t, want, tree.String())
}
// assertEqual verifies the strings are equal, assuming its terminal output.
func assertEqual(tb testing.TB, want, got string) {
tb.Helper()
want = trimSpace(want)
got = trimSpace(got)
diff := udiff.Unified("want", "got", want, got)
if diff != "" {
tb.Fatalf("\nwant:\n\n%s\n\ngot:\n\n%s\n\ndiff:\n\n%s\n\n", want, got, diff)
}
}
func trimSpace(s string) string {
var result []string //nolint: prealloc
ss := strings.Split(s, "\n")
for i, line := range ss {
if strings.TrimSpace(line) == "" && (i == 0 || i == len(ss)-1) {
continue
}
result = append(result, strings.TrimRightFunc(line, unicode.IsSpace))
}
return strings.Join(result, "\n")
}