1package layout
2
3import (
4 "testing"
5
6 "github.com/bnema/vev/internal/domain"
7 "github.com/stretchr/testify/require"
8)
9
10func TestFocusSpan(t *testing.T) {
11 t.Parallel()
12
13 area := domain.Rect{X: 5, Y: 7, Width: 41, Height: 5}
14 tests := []struct {
15 name string
16 tree *Tree
17 area domain.Rect
18 want domain.Rect
19 wantErr error
20 }{
21 {
22 name: "nil root",
23 tree: &Tree{},
24 area: area,
25 wantErr: ErrNotFound,
26 },
27 {
28 name: "layout too small",
29 tree: NewTree("a"),
30 area: domain.Rect{Width: MinPaneCols - 1, Height: MinPaneRows},
31 wantErr: ErrTooSmall,
32 },
33 {
34 name: "focused pane unknown",
35 tree: &Tree{Root: NewLeaf("a"), Focus: "missing"},
36 area: area,
37 wantErr: ErrNotFound,
38 },
39 {
40 name: "returns collapsed stack title bar",
41 tree: &Tree{
42 Root: &Node{Kind: Stack, Children: []*Node{NewLeaf("a"), NewLeaf("b")}, Expanded: "a"},
43 Focus: "b",
44 },
45 area: area,
46 want: domain.Rect{X: 5, Y: 11, Width: 41, Height: 1},
47 },
48 }
49
50 for _, tt := range tests {
51 t.Run(tt.name, func(t *testing.T) {
52 got, err := tt.tree.FocusSpan(tt.area)
53 require.ErrorIs(t, err, tt.wantErr)
54 require.Equal(t, tt.want, got)
55 })
56 }
57}
58
59func TestEntryPane(t *testing.T) {
60 t.Parallel()
61
62 area := domain.Rect{Width: 41, Height: 5}
63 tests := []struct {
64 name string
65 tree *Tree
66 dir Direction
67 span domain.Rect
68 area domain.Rect
69 want PaneID
70 wantErr error
71 }{
72 {
73 name: "nil root",
74 tree: &Tree{},
75 dir: Right,
76 area: area,
77 wantErr: ErrNotFound,
78 },
79 {
80 name: "layout too small",
81 tree: NewTree("a"),
82 dir: Right,
83 area: domain.Rect{Width: MinPaneCols - 1, Height: MinPaneRows},
84 wantErr: ErrTooSmall,
85 },
86 {
87 name: "unknown direction has no pane",
88 tree: NewTree("a"),
89 dir: Direction(99),
90 area: area,
91 wantErr: ErrNoPane,
92 },
93 {
94 name: "right enters left edge",
95 tree: &Tree{Root: &Node{Kind: Split, Dir: Horizontal, Children: []*Node{NewLeaf("left"), NewLeaf("right")}}, Focus: "right"},
96 dir: Right,
97 span: domain.Rect{Y: 1, Width: 20, Height: 2},
98 area: area,
99 want: "left",
100 },
101 {
102 name: "left enters right edge",
103 tree: &Tree{Root: &Node{Kind: Split, Dir: Horizontal, Children: []*Node{NewLeaf("left"), NewLeaf("right")}}, Focus: "left"},
104 dir: Left,
105 span: domain.Rect{Y: 1, Width: 20, Height: 2},
106 area: area,
107 want: "right",
108 },
109 {
110 name: "down enters top edge",
111 tree: &Tree{Root: &Node{Kind: Split, Dir: Vertical, Children: []*Node{NewLeaf("top"), NewLeaf("bottom")}}, Focus: "bottom"},
112 dir: Down,
113 span: domain.Rect{X: 5, Width: 10, Height: 2},
114 area: area,
115 want: "top",
116 },
117 {
118 name: "up enters bottom edge",
119 tree: &Tree{Root: &Node{Kind: Split, Dir: Vertical, Children: []*Node{NewLeaf("top"), NewLeaf("bottom")}}, Focus: "top"},
120 dir: Up,
121 span: domain.Rect{X: 5, Width: 10, Height: 2},
122 area: area,
123 want: "bottom",
124 },
125 {
126 name: "minimum edge gap precedes overlap",
127 tree: &Tree{Root: &Node{Kind: Split, Dir: Horizontal, Children: []*Node{
128 {Kind: Split, Dir: Vertical, Children: []*Node{NewLeaf("edge-top"), NewLeaf("edge-bottom")}},
129 NewLeaf("far"),
130 }}},
131 dir: Right,
132 span: domain.Rect{Y: 1, Width: 20, Height: 4},
133 area: domain.Rect{Width: 62, Height: 5},
134 want: "edge-bottom",
135 },
136 {
137 name: "maximum perpendicular overlap breaks equal gap",
138 tree: &Tree{Root: &Node{Kind: Split, Dir: Vertical, Children: []*Node{NewLeaf("top"), NewLeaf("bottom")}}},
139 dir: Right,
140 span: domain.Rect{Y: 3, Width: 20, Height: 2},
141 area: domain.Rect{Width: 20, Height: 5},
142 want: "bottom",
143 },
144 {
145 name: "pane id deterministically breaks full tie",
146 tree: &Tree{Root: &Node{Kind: Stack, Children: []*Node{NewLeaf("z"), NewLeaf("a")}, Expanded: "z"}},
147 dir: Right,
148 span: domain.Rect{Y: 10, Width: 20, Height: 1},
149 area: domain.Rect{Width: 20, Height: 4},
150 want: "a",
151 },
152 }
153
154 for _, tt := range tests {
155 t.Run(tt.name, func(t *testing.T) {
156 before := tt.tree.Clone()
157 got, err := tt.tree.EntryPane(tt.dir, tt.span, tt.area)
158 require.ErrorIs(t, err, tt.wantErr)
159 require.Equal(t, tt.want, got)
160 require.Equal(t, before, tt.tree, "EntryPane must be pure")
161 })
162 }
163}
164
165func TestFocusEnterExpandsCollapsedStackMember(t *testing.T) {
166 t.Parallel()
167
168 area := domain.Rect{Width: 20, Height: 4}
169 tr := &Tree{
170 Root: &Node{Kind: Stack, Children: []*Node{NewLeaf("expanded"), NewLeaf("collapsed")}, Expanded: "expanded"},
171 Focus: "expanded",
172 }
173
174 err := tr.FocusEnter(Right, domain.Rect{Y: 3, Width: 20, Height: 1}, area)
175 require.NoError(t, err)
176 require.Equal(t, PaneID("collapsed"), tr.Focus)
177 require.Equal(t, PaneID("collapsed"), tr.Root.Expanded)
178}