mirror of
https://github.com/anotherhadi/ilovetui.git
synced 2026-08-21 03:55:48 +02:00
5f99fa7521
Signed-off-by: Hadi <112569860+anotherhadi@users.noreply.github.com>
278 lines
8.2 KiB
Go
278 lines
8.2 KiB
Go
package layout
|
|
|
|
import (
|
|
"testing"
|
|
|
|
tea "charm.land/bubbletea/v2"
|
|
)
|
|
|
|
func newTestModel(t *testing.T, root *Node) (Model, map[string]*stubPane) {
|
|
t.Helper()
|
|
panes := map[string]*stubPane{}
|
|
walk(root, func(n *Node) {
|
|
if s, ok := n.model.(*stubPane); ok {
|
|
panes[n.id] = s
|
|
}
|
|
})
|
|
m := New(root, AsRoot())
|
|
m.Init()
|
|
return m, panes
|
|
}
|
|
|
|
func TestInitFocusesFirstLeaf(t *testing.T) {
|
|
root := HSplit(0.5, Leaf("a", newStub()), Leaf("b", newStub()))
|
|
m, panes := newTestModel(t, root)
|
|
|
|
if m.state.id != "a" {
|
|
t.Fatalf("focusedID = %q, want %q", m.state.id, "a")
|
|
}
|
|
if panes["a"].focusN != 1 {
|
|
t.Fatalf("a.focusN = %d, want 1", panes["a"].focusN)
|
|
}
|
|
if panes["b"].focusN != 0 {
|
|
t.Fatalf("b.focusN = %d, want 0", panes["b"].focusN)
|
|
}
|
|
}
|
|
|
|
func TestWindowSizeDispatchesSizeMsg(t *testing.T) {
|
|
root := HSplit(0.5, Leaf("a", newStub()), Leaf("b", newStub()))
|
|
m, panes := newTestModel(t, root)
|
|
|
|
updated, _ := m.Update(tea.WindowSizeMsg{Width: 100, Height: 40})
|
|
m = updated.(Model)
|
|
|
|
// newTestModel builds with AsRoot(), so the tree gets 40 minus the
|
|
// 1-row help bar - see treeRect.
|
|
if panes["a"].w != 50 || panes["a"].h != 39 {
|
|
t.Fatalf("a size = %dx%d, want 50x39", panes["a"].w, panes["a"].h)
|
|
}
|
|
if panes["b"].w != 50 || panes["b"].h != 39 {
|
|
t.Fatalf("b size = %dx%d, want 50x39", panes["b"].w, panes["b"].h)
|
|
}
|
|
if got := len(m.Leaves()); got != 2 {
|
|
t.Fatalf("len(Leaves()) = %d, want 2", got)
|
|
}
|
|
}
|
|
|
|
func TestCtrlLMovesFocusAndDispatchesBlurFocus(t *testing.T) {
|
|
root := HSplit(0.5, Leaf("a", newStub()), Leaf("b", newStub()))
|
|
m, panes := newTestModel(t, root)
|
|
updated, _ := m.Update(tea.WindowSizeMsg{Width: 100, Height: 40})
|
|
m = updated.(Model)
|
|
|
|
updated, _ = m.Update(tea.KeyPressMsg{Code: 'l', Mod: tea.ModCtrl, Text: ""})
|
|
m = updated.(Model)
|
|
|
|
if m.state.id != "b" {
|
|
t.Fatalf("focusedID = %q, want %q", m.state.id, "b")
|
|
}
|
|
if panes["a"].blurN != 1 {
|
|
t.Fatalf("a.blurN = %d, want 1", panes["a"].blurN)
|
|
}
|
|
if panes["b"].focusN != 1 {
|
|
t.Fatalf("b.focusN = %d, want 1", panes["b"].focusN)
|
|
}
|
|
}
|
|
|
|
func TestKeyPressOnlyReachesFocusedPane(t *testing.T) {
|
|
root := HSplit(0.5, Leaf("a", newStub()), Leaf("b", newStub()))
|
|
m, panes := newTestModel(t, root)
|
|
updated, _ := m.Update(tea.WindowSizeMsg{Width: 100, Height: 40})
|
|
m = updated.(Model)
|
|
|
|
updated, _ = m.Update(tea.KeyPressMsg{Text: "x"})
|
|
_ = updated.(Model)
|
|
|
|
if _, ok := panes["a"].last().(tea.KeyPressMsg); !ok {
|
|
t.Fatalf("focused pane a should have received the key press, got %#v", panes["a"].last())
|
|
}
|
|
if _, ok := panes["b"].last().(tea.KeyPressMsg); ok {
|
|
t.Fatalf("blurred pane b should not have received the key press")
|
|
}
|
|
}
|
|
|
|
func TestSplitLeafAddsAndSizesNewPane(t *testing.T) {
|
|
root := Leaf("a", newStub())
|
|
m, panes := newTestModel(t, root)
|
|
updated, _ := m.Update(tea.WindowSizeMsg{Width: 100, Height: 40})
|
|
m = updated.(Model)
|
|
|
|
newPane := newStub()
|
|
m, _ = m.SplitLeaf("a", Horizontal, "b", newPane)
|
|
|
|
if got := len(m.Leaves()); got != 2 {
|
|
t.Fatalf("len(Leaves()) = %d, want 2", got)
|
|
}
|
|
if newPane.w == 0 || newPane.h == 0 {
|
|
t.Fatalf("new pane never received a SizeMsg: w=%d h=%d", newPane.w, newPane.h)
|
|
}
|
|
if panes["a"].w != 50 {
|
|
t.Fatalf("a.w = %d, want 50 after 50/50 split", panes["a"].w)
|
|
}
|
|
}
|
|
|
|
func TestSetPaneSwapsContentKeepingShape(t *testing.T) {
|
|
root := HSplit(0.5, Leaf("a", newStub()), Leaf("b", newStub()))
|
|
m, panes := newTestModel(t, root)
|
|
updated, _ := m.Update(tea.WindowSizeMsg{Width: 100, Height: 40})
|
|
m = updated.(Model)
|
|
// "a" is focused (first leaf).
|
|
|
|
replacement := newStub()
|
|
m, _ = m.SetPane("a", replacement)
|
|
|
|
if got := len(m.Leaves()); got != 2 {
|
|
t.Fatalf("len(Leaves()) = %d, want 2 (SetPane must not reshape the tree)", got)
|
|
}
|
|
if replacement.w != 50 || replacement.h != 39 {
|
|
t.Fatalf("replacement size = %dx%d, want 50x39 (a's existing Rect)", replacement.w, replacement.h)
|
|
}
|
|
if replacement.focusN != 1 {
|
|
t.Fatalf("replacement.focusN = %d, want 1: \"a\" currently holds focus", replacement.focusN)
|
|
}
|
|
if panes["b"].w != 50 {
|
|
t.Fatalf("b.w = %d, want unchanged 50", panes["b"].w)
|
|
}
|
|
}
|
|
|
|
func TestSetPaneOnBlurredLeafDoesNotFocus(t *testing.T) {
|
|
root := HSplit(0.5, Leaf("a", newStub()), Leaf("b", newStub()))
|
|
m, _ := newTestModel(t, root)
|
|
updated, _ := m.Update(tea.WindowSizeMsg{Width: 100, Height: 40})
|
|
m = updated.(Model)
|
|
// "b" is blurred (only "a" is focused initially).
|
|
|
|
replacement := newStub()
|
|
m, _ = m.SetPane("b", replacement)
|
|
|
|
if replacement.focusN != 0 {
|
|
t.Fatalf("replacement.focusN = %d, want 0: \"b\" isn't focused", replacement.focusN)
|
|
}
|
|
if replacement.w != 50 || replacement.h != 39 {
|
|
t.Fatalf("replacement size = %dx%d, want 50x39", replacement.w, replacement.h)
|
|
}
|
|
}
|
|
|
|
func TestSetPaneUnknownIDIsNoop(t *testing.T) {
|
|
root := Leaf("a", newStub())
|
|
m, _ := newTestModel(t, root)
|
|
|
|
m2, cmd := m.SetPane("nope", newStub())
|
|
if cmd != nil {
|
|
t.Fatalf("expected nil cmd for an unknown SetPane id, got %v", cmd)
|
|
}
|
|
if len(m2.Leaves()) != len(m.Leaves()) {
|
|
t.Fatalf("tree changed after a no-op SetPane")
|
|
}
|
|
}
|
|
|
|
func TestCloseLeafReassignsFocus(t *testing.T) {
|
|
root := HSplit(0.5, Leaf("a", newStub()), Leaf("b", newStub()))
|
|
m, _ := newTestModel(t, root)
|
|
updated, _ := m.Update(tea.WindowSizeMsg{Width: 100, Height: 40})
|
|
m = updated.(Model)
|
|
// focus is on "a"; closing it must move focus to what remains.
|
|
|
|
m, _ = m.CloseLeaf("a")
|
|
|
|
if got := len(m.Leaves()); got != 1 {
|
|
t.Fatalf("len(Leaves()) = %d, want 1", got)
|
|
}
|
|
if m.state.id != "b" {
|
|
t.Fatalf("focusedID = %q, want %q after closing the focused leaf", m.state.id, "b")
|
|
}
|
|
}
|
|
|
|
func TestCloseLeafRootIsNoop(t *testing.T) {
|
|
root := Leaf("only", newStub())
|
|
m, _ := newTestModel(t, root)
|
|
|
|
m2, cmd := m.CloseLeaf("only")
|
|
if cmd != nil {
|
|
t.Fatalf("expected nil cmd closing the tree's only leaf, got %v", cmd)
|
|
}
|
|
if got := len(m2.Leaves()); got != len(m.Leaves()) {
|
|
t.Fatalf("tree changed after a no-op close")
|
|
}
|
|
}
|
|
|
|
func TestResizeChangesRatio(t *testing.T) {
|
|
root := HSplit(0.5, Leaf("a", newStub()), Leaf("b", newStub())).WithID("split")
|
|
m, panes := newTestModel(t, root)
|
|
updated, _ := m.Update(tea.WindowSizeMsg{Width: 100, Height: 40})
|
|
m = updated.(Model)
|
|
|
|
m, _ = m.Resize("split", 0.8)
|
|
|
|
if panes["a"].w != 80 {
|
|
t.Fatalf("a.w = %d, want 80 after Resize to 0.8", panes["a"].w)
|
|
}
|
|
if panes["b"].w != 20 {
|
|
t.Fatalf("b.w = %d, want 20 after Resize to 0.8", panes["b"].w)
|
|
}
|
|
}
|
|
|
|
func TestSendMsgDeliversToTarget(t *testing.T) {
|
|
type payload struct{ n int }
|
|
root := HSplit(0.5, Leaf("a", newStub()), Leaf("b", newStub()))
|
|
m, panes := newTestModel(t, root)
|
|
// a is focused via Init's initial FocusMsg; b never received anything yet.
|
|
|
|
updated, _ := m.Update(SendMsg{Target: "b", Msg: payload{n: 42}})
|
|
m = updated.(Model)
|
|
|
|
if got, ok := panes["b"].last().(payload); !ok || got.n != 42 {
|
|
t.Fatalf("b should have received payload{42}, got %#v", panes["b"].last())
|
|
}
|
|
for _, msg := range panes["a"].msgs {
|
|
if _, ok := msg.(payload); ok {
|
|
t.Fatalf("a should not have received the SendMsg meant for b")
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSendMsgUnknownTargetIsIgnored(t *testing.T) {
|
|
root := Leaf("a", newStub())
|
|
m, _ := newTestModel(t, root)
|
|
|
|
updated, cmd := m.Update(SendMsg{Target: "nope", Msg: struct{}{}})
|
|
_ = updated.(Model)
|
|
if cmd != nil {
|
|
t.Fatalf("expected nil cmd for an unknown SendMsg target, got %v", cmd)
|
|
}
|
|
}
|
|
|
|
func TestRequestFocusFromFocusedPaneSucceeds(t *testing.T) {
|
|
root := HSplit(0.5, Leaf("a", newStub()), Leaf("b", newStub()))
|
|
m, panes := newTestModel(t, root)
|
|
updated, _ := m.Update(tea.WindowSizeMsg{Width: 100, Height: 40})
|
|
m = updated.(Model)
|
|
|
|
updated, _ = m.Update(RequestFocusMsg{Source: "a", Target: "b"})
|
|
m = updated.(Model)
|
|
|
|
if m.state.id != "b" {
|
|
t.Fatalf("focusedID = %q, want %q", m.state.id, "b")
|
|
}
|
|
if panes["b"].focusN != 1 {
|
|
t.Fatalf("b.focusN = %d, want 1", panes["b"].focusN)
|
|
}
|
|
}
|
|
|
|
func TestRequestFocusFromBlurredPaneIsIgnored(t *testing.T) {
|
|
root := HSplit(0.5, Leaf("a", newStub()), Leaf("b", newStub()))
|
|
third := Leaf("c", newStub())
|
|
_ = third
|
|
m, _ := newTestModel(t, root)
|
|
updated, _ := m.Update(tea.WindowSizeMsg{Width: 100, Height: 40})
|
|
m = updated.(Model)
|
|
// "a" is focused; "b" (blurred) tries to redirect focus to itself.
|
|
|
|
updated, _ = m.Update(RequestFocusMsg{Source: "b", Target: "b"})
|
|
m = updated.(Model)
|
|
|
|
if m.state.id != "a" {
|
|
t.Fatalf("focusedID = %q, want %q (unauthorized request must be ignored)", m.state.id, "a")
|
|
}
|
|
}
|