Data: components 069-080

This commit is contained in:
ZacharyZhang-NY committed 2026-10-05 12:27:55 -04:00
1 parent 2a232930dd
commit 16533b8f02
71 files changed
+5400 -1

No files matched your search

+222
View File
@@ -0,0 +1,222 @@
package mujica
import (
"fmt"
"testing"
"github.com/egoist/mygo/ui"
)
type treeHarness struct {
kids map[string][]string
status map[string]DataStatus
loads []string
st TreeState[string]
opts TreeOptions[string]
built map[string]bool
changes int
}
func newTreeHarness() *treeHarness {
h := &treeHarness{
kids: map[string][]string{
"Nave": {"Pulpit", "Pews"},
"Crypt": {}, // loaded lazily
"Pulpit": nil,
"Pews": nil,
"Sacristy": nil,
},
status: map[string]DataStatus{"Crypt": DataUnloaded},
}
h.opts = TreeOptions[string]{
Roots: []string{"Nave", "Crypt", "Sacristy"},
Children: func(n string) []string { return h.kids[n] },
Label: func(n string) string { return n },
ItemStatus: func(n string) DataStatus { return h.status[n] },
Load: func(n string) {
h.loads = append(h.loads, n)
h.status[n] = DataLoading
},
}
return h
}
func (h *treeHarness) view(c *ui.Context) {
Use(c, Settings{Mode: Light})
h.built = map[string]bool{}
opts := h.opts
label := opts.Label
opts.Label = func(n string) string { h.built[n] = true; return label(n) }
ui.Column(c).Padding(10).Children(func() {
v := Tree(c, &h.st, opts)
v.Size(320, 360)
if v.Changed() {
h.changes++
}
})
}
func TestTreeExpandAndKeyboard(t *testing.T) {
h := newTreeHarness()
tt := ui.NewTester(h.view, 400, 400)
if tt.HasText("Pulpit") || h.built["Pulpit"] {
t.Fatal("closed subtree built")
}
if err := tt.Click("Nave"); err != nil {
t.Fatal(err)
}
if k, _ := h.st.Selected(); k != "Nave" || h.changes != 1 {
t.Fatalf("selected %q changes %d", k, h.changes)
}
tt.Key(0, ui.KeyRight)
if !h.st.IsOpen("Nave") || !tt.HasText("Pulpit") {
t.Fatalf("Right did not open: %q", tt.Texts())
}
tt.Key(0, ui.KeyRight)
if k, _ := h.st.Selected(); k != "Pulpit" {
t.Fatalf("Right to first child chose %q", k)
}
tt.Key(0, ui.KeyDown)
if k, _ := h.st.Selected(); k != "Pews" {
t.Fatalf("Down chose %q", k)
}
tt.Key(0, ui.KeyLeft)
if k, _ := h.st.Selected(); k != "Nave" {
t.Fatalf("Left to parent chose %q", k)
}
tt.Key(0, ui.KeyLeft)
if h.st.IsOpen("Nave") || tt.HasText("Pews") {
t.Fatal("Left did not close")
}
}
func TestTreeLazyLoad(t *testing.T) {
h := newTreeHarness()
tt := ui.NewTester(h.view, 400, 400)
if len(h.loads) != 0 {
t.Fatalf("loaded before opening: %v", h.loads)
}
h.st.SetOpen("Crypt", true)
tt.Frame()
if fmt.Sprint(h.loads) != "[Crypt]" || !tt.HasText("Loading") {
t.Fatalf("loads %v texts %q", h.loads, tt.Texts())
}
tt.Frame()
if len(h.loads) != 1 {
t.Fatalf("asked twice: %v", h.loads)
}
h.status["Crypt"] = DataFailed
tt.Frame()
if err := tt.Click("Retry"); err != nil {
t.Fatal(err)
}
if len(h.loads) != 2 {
t.Fatalf("retry did not load: %v", h.loads)
}
h.kids["Crypt"] = []string{"Ossuary"}
h.status["Crypt"] = DataReady
tt.Frame()
if !tt.HasText("Ossuary") || tt.HasText("Loading") {
t.Fatalf("children missing: %q", tt.Texts())
}
}
func TestTreeMultipleAndEmpty(t *testing.T) {
h := newTreeHarness()
h.opts.Multiple = true
tt := ui.NewTester(h.view, 400, 400)
_ = tt.Click("Nave")
_ = tt.ClickWith(ui.Cmd, "Sacristy")
if h.st.Selection.Len() != 2 || !h.st.Selection.Has("Sacristy") {
t.Fatalf("chose %d", h.st.Selection.Len())
}
h.opts.Roots = nil
tt.Frame()
if !tt.HasText("No items") {
t.Fatalf("empty: %q", tt.Texts())
}
}
func TestTreeBadOptions(t *testing.T) {
defer func() {
if recover() == nil {
t.Fatal("no panic without Children")
}
}()
ui.NewTester(func(c *ui.Context) {
Use(c, Settings{})
var s TreeState[int]
Tree(c, &s, TreeOptions[int]{Roots: []int{1}})
}, 100, 100)
}
// bigTree has 100 roots of 999 children each: 100,000 nodes.
func bigTree() (roots []int, kids func(int) []int) {
for r := range 100 {
roots = append(roots, r*1000)
}
cache := map[int][]int{}
return roots, func(n int) []int {
if n%1000 != 0 {
return nil
}
if k, ok := cache[n]; ok {
return k
}
var k []int
for i := 1; i < 1000; i++ {
k = append(k, n+i)
}
cache[n] = k
return k
}
}
func TestTreeLargeIsVirtualized(t *testing.T) {
roots, kids := bigTree()
var st TreeState[int]
built := 0
view := func(c *ui.Context) {
Use(c, Settings{})
built = 0
Tree(c, &st, TreeOptions[int]{Roots: roots, Children: kids, Label: func(n int) string {
built++
return fmt.Sprintf("Node %d", n)
}}).Size(320, 360)
}
tt := ui.NewTester(view, 400, 400)
// Label runs twice per built row (row name and text). A jump builds a
// view on each side of its target: 2×10 rows of 36 DIPs, plus 2×2 overscan and 2 edges.
const bound = 2 * 26
if built == 0 || built > bound {
t.Fatalf("collapsed: label calls %d, bound %d", built, bound)
}
for _, r := range roots {
st.SetOpen(r, true)
}
tt.Frame()
if built > bound || st.tree.outline.Rows() != 100_000 {
t.Fatalf("open: label calls %d rows %d", built, st.tree.outline.Rows())
}
st.tree.outline.List.ScrollTo(64_000, ui.Start)
tt.Frame()
if built > bound || !tt.HasText(fmt.Sprintf("Node %d", st.tree.outline.Item(64_000))) {
t.Fatalf("scrolled: label calls %d texts %q", built, tt.Texts())
}
}
func TestTreeSnapshot(t *testing.T) {
snapshot(t, "tree", 400, 260, func(c *ui.Context) {
h := ui.Local(c.Root(), "h", func() *treeHarness {
h := newTreeHarness()
h.st.SetOpen("Nave", true)
h.st.SetOpen("Crypt", true)
h.status["Crypt"] = DataLoading
h.st.tree.started, h.st.tree.cursor, h.st.tree.asked = true, 1, map[string]bool{}
return h
})
ui.Column(c).Padding(16).Children(func() {
Tree(c, &(*h).st, (*h).opts).Size(360, 228)
})
})
}