mirror of
https://github.com/anotherhadi/ilovetui.git
synced 2026-08-21 12:05:49 +02:00
5f99fa7521
Signed-off-by: Hadi <112569860+anotherhadi@users.noreply.github.com>
151 lines
5.5 KiB
Go
151 lines
5.5 KiB
Go
package layout
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"charm.land/bubbles/v2/key"
|
|
tea "charm.land/bubbletea/v2"
|
|
)
|
|
|
|
// buildNested returns an outer Model with two direct leaves: "sidebar" and
|
|
// "inner-root", the latter being a nested layout.Model (itself split into
|
|
// "inner-a"/"inner-b") embedded as an ordinary Pane. Both models are sized
|
|
// before being handed back so their leaf registries are populated.
|
|
func buildNested(t *testing.T) (outer Model, sidebar *stubPane, innerA, innerB *stubPane) {
|
|
t.Helper()
|
|
sidebar = newStub()
|
|
innerA = newStub()
|
|
innerB = newStub()
|
|
|
|
// inner is deliberately built WITHOUT AsRoot(): it's embedded, so it
|
|
// must not act focused on its own until outer actually focuses the
|
|
// leaf that hosts it (see Model.Init's asRoot guard).
|
|
inner := New(HSplit(0.5, Leaf("inner-a", innerA), Leaf("inner-b", innerB)))
|
|
|
|
root := HSplit(0.3, Leaf("sidebar", sidebar), Leaf("inner-root", inner))
|
|
outer = New(root, AsRoot())
|
|
outer.Init()
|
|
updated, _ := outer.Update(tea.WindowSizeMsg{Width: 100, Height: 40})
|
|
outer = updated.(Model)
|
|
return outer, sidebar, innerA, innerB
|
|
}
|
|
|
|
// Regression test: a nested Model used to fire its own initial FocusMsg to
|
|
// its first leaf unconditionally on Init, regardless of whether the outer
|
|
// tree's actual initial focus ever lands on the leaf hosting it - so a
|
|
// leaf buried in a subtree that isn't even initially focused would still
|
|
// show up as focused, alongside whatever the outer tree really focused.
|
|
func TestEmbeddedModelDoesNotSelfFocusOnInit(t *testing.T) {
|
|
outer, sidebar, innerA, innerB := buildNested(t)
|
|
|
|
if sidebar.focusN != 1 {
|
|
t.Fatalf("sidebar.focusN = %d, want 1 (it's the outer tree's real initial focus)", sidebar.focusN)
|
|
}
|
|
if innerA.focusN != 0 || innerB.focusN != 0 {
|
|
t.Fatalf("innerA.focusN=%d innerB.focusN=%d, want 0/0: the nested tree isn't focused yet", innerA.focusN, innerB.focusN)
|
|
}
|
|
if outer.state.id != "sidebar" {
|
|
t.Fatalf("outer focusedID = %q, want %q", outer.state.id, "sidebar")
|
|
}
|
|
}
|
|
|
|
func TestSendMsgReachesNestedLeaf(t *testing.T) {
|
|
type payload struct{ n int }
|
|
outer, _, innerA, _ := buildNested(t)
|
|
|
|
updated, _ := outer.Update(SendMsg{Target: "inner-a", Msg: payload{n: 7}})
|
|
_ = updated.(Model)
|
|
|
|
if got, ok := innerA.last().(payload); !ok || got.n != 7 {
|
|
t.Fatalf("inner-a should have received payload{7}, got %#v", innerA.last())
|
|
}
|
|
}
|
|
|
|
func TestFocusJumpsIntoNestedSubtreeAndUpdatesOuterFocus(t *testing.T) {
|
|
outer, _, _, innerB := buildNested(t)
|
|
// Outer's own focus starts on "sidebar" (first leaf, depth-first).
|
|
|
|
handled, _ := outer.Focus("inner-b")
|
|
if !handled {
|
|
t.Fatal("Focus(\"inner-b\") should have been handled")
|
|
}
|
|
if outer.state.id != "inner-root" {
|
|
t.Fatalf("outer focusedID = %q, want %q (the leaf hosting the nested tree)", outer.state.id, "inner-root")
|
|
}
|
|
// At-least-once, not exactly-once (see Focus's doc comment): the outer
|
|
// leaf's own re-notification can duplicate the nested tree's own
|
|
// dispatch, so only assert inner-b actually got notified, not a count.
|
|
if innerB.focusN < 1 {
|
|
t.Fatalf("inner-b.focusN = %d, want at least 1", innerB.focusN)
|
|
}
|
|
}
|
|
|
|
func TestRequestFocusAuthorizedThroughNestedChain(t *testing.T) {
|
|
outer, _, innerA, _ := buildNested(t)
|
|
|
|
// Move outer focus onto the nested subtree, and its own internal focus
|
|
// onto inner-a, so inner-a is genuinely the focused leaf end-to-end.
|
|
outer.Focus("inner-a")
|
|
if innerA.focusN != 1 {
|
|
t.Fatalf("inner-a.focusN = %d, want 1 before the request", innerA.focusN)
|
|
}
|
|
|
|
updated, _ := outer.Update(RequestFocusMsg{Source: "inner-a", Target: "sidebar"})
|
|
outer = updated.(Model)
|
|
|
|
if outer.state.id != "sidebar" {
|
|
t.Fatalf("focusedID = %q, want %q: inner-a is genuinely focused, its request should be honored", outer.state.id, "sidebar")
|
|
}
|
|
}
|
|
|
|
func TestRequestFocusFromNonFocusedNestedLeafIsIgnored(t *testing.T) {
|
|
outer, _, _, innerB := buildNested(t)
|
|
// Outer focus is on "sidebar"; the nested tree isn't even the focused
|
|
// branch, so nothing inside it - including inner-b - is authorized.
|
|
_ = innerB
|
|
|
|
updated, _ := outer.Update(RequestFocusMsg{Source: "inner-b", Target: "sidebar"})
|
|
outer = updated.(Model)
|
|
|
|
if outer.state.id != "sidebar" {
|
|
t.Fatalf("focusedID = %q, want unchanged %q", outer.state.id, "sidebar")
|
|
}
|
|
}
|
|
|
|
func TestMoveFocusDelegatesToNestedBeforeGeometry(t *testing.T) {
|
|
outer, _, _, innerB := buildNested(t)
|
|
outer.Focus("inner-a")
|
|
|
|
if ok := outer.MoveFocus(FocusRight); !ok {
|
|
t.Fatal("MoveFocus(FocusRight) should have been handled by the nested tree (inner-a -> inner-b)")
|
|
}
|
|
if innerB.focusN != 1 {
|
|
t.Fatalf("inner-b.focusN = %d, want 1 (moved within the nested tree)", innerB.focusN)
|
|
}
|
|
if outer.state.id != "inner-root" {
|
|
t.Fatalf("outer focusedID changed to %q, should have stayed on the nested leaf", outer.state.id)
|
|
}
|
|
|
|
// Now at inner-b, the nested tree's own rightmost leaf: pressing right
|
|
// again must bubble up and move the OUTER focus instead.
|
|
if ok := outer.MoveFocus(FocusRight); ok {
|
|
// buildNested's outer split is only sidebar|inner-root left-to-right,
|
|
// so there's nothing further right at the outer level either -
|
|
// MoveFocus should report false all the way up.
|
|
t.Fatalf("expected no further neighbor to the right at either level")
|
|
}
|
|
}
|
|
|
|
func TestHelpBindingsDelegatesThroughNesting(t *testing.T) {
|
|
outer, _, innerA, _ := buildNested(t)
|
|
outer.Focus("inner-a")
|
|
|
|
want := key.NewBinding(key.WithKeys("x"), key.WithHelp("x", "do x"))
|
|
innerA.help = []key.Binding{want}
|
|
|
|
got := outer.HelpBindings()
|
|
if len(got) != 1 || got[0].Help().Key != "x" {
|
|
t.Fatalf("HelpBindings() = %#v, want the focused inner leaf's bindings", got)
|
|
}
|
|
}
|