1package focus
2
3import (
4 "testing"
5
6 "github.com/stretchr/testify/assert"
7
8 "github.com/bnema/dumber/internal/application/usecase"
9 "github.com/bnema/dumber/internal/domain/entity"
10)
11
12// TestNavigateWithinStack_UsesActiveStackIndex tests that navigation uses
13// ActiveStackIndex from the domain model instead of pointer comparison.
14// Note: navigateWithinStack is a pure function that returns the target pane ID
15// without modifying state. The actual state mutation happens in ApplyFocusChange.
16func TestNavigateWithinStack_UsesActiveStackIndex(t *testing.T) {
17 // Create a stack with 3 children
18 pane0 := &entity.Pane{ID: "pane0"}
19 pane1 := &entity.Pane{ID: "pane1"}
20 pane2 := &entity.Pane{ID: "pane2"}
21
22 child0 := &entity.PaneNode{ID: "child0", Pane: pane0}
23 child1 := &entity.PaneNode{ID: "child1", Pane: pane1}
24 child2 := &entity.PaneNode{ID: "child2", Pane: pane2}
25
26 stackNode := &entity.PaneNode{
27 ID: "stack",
28 IsStacked: true,
29 Children: []*entity.PaneNode{child0, child1, child2},
30 ActiveStackIndex: 1, // Currently at middle pane
31 }
32
33 // Set parents
34 child0.Parent = stackNode
35 child1.Parent = stackNode
36 child2.Parent = stackNode
37
38 m := NewManager(nil)
39
40 tests := []struct {
41 name string
42 activeIndex int
43 direction usecase.NavigateDirection
44 wantCanNav bool
45 wantPaneID entity.PaneID
46 }{
47 {
48 name: "navigate down from middle",
49 activeIndex: 1,
50 direction: usecase.NavDown,
51 wantCanNav: true,
52 wantPaneID: "pane2",
53 },
54 {
55 name: "navigate up from middle",
56 activeIndex: 1,
57 direction: usecase.NavUp,
58 wantCanNav: true,
59 wantPaneID: "pane0",
60 },
61 {
62 name: "navigate down from last - boundary",
63 activeIndex: 2,
64 direction: usecase.NavDown,
65 wantCanNav: false,
66 wantPaneID: "",
67 },
68 {
69 name: "navigate up from first - boundary",
70 activeIndex: 0,
71 direction: usecase.NavUp,
72 wantCanNav: false,
73 wantPaneID: "",
74 },
75 {
76 name: "navigate down from first",
77 activeIndex: 0,
78 direction: usecase.NavDown,
79 wantCanNav: true,
80 wantPaneID: "pane1",
81 },
82 {
83 name: "navigate up from last",
84 activeIndex: 2,
85 direction: usecase.NavUp,
86 wantCanNav: true,
87 wantPaneID: "pane1",
88 },
89 }
90
91 for _, tt := range tests {
92 t.Run(tt.name, func(t *testing.T) {
93 // Set up the active index
94 stackNode.ActiveStackIndex = tt.activeIndex
95
96 // navigateWithinStack is a pure function - it does not modify state
97 canNav, paneID := m.navigateWithinStack(stackNode, stackNode.Children[tt.activeIndex], tt.direction)
98
99 assert.Equal(t, tt.wantCanNav, canNav)
100 assert.Equal(t, tt.wantPaneID, paneID)
101 })
102 }
103}
104
105// TestNavigateWithinStack_EmptyStack tests navigation in empty stack returns false.
106func TestNavigateWithinStack_EmptyStack(t *testing.T) {
107 stackNode := &entity.PaneNode{
108 ID: "stack",
109 IsStacked: true,
110 Children: []*entity.PaneNode{},
111 }
112
113 m := NewManager(nil)
114
115 canNav, paneID := m.navigateWithinStack(stackNode, nil, usecase.NavDown)
116
117 assert.False(t, canNav)
118 assert.Empty(t, paneID)
119}
120
121// TestNavigateWithinStack_NotStacked tests navigation on non-stacked node returns false.
122func TestNavigateWithinStack_NotStacked(t *testing.T) {
123 node := &entity.PaneNode{
124 ID: "leaf",
125 IsStacked: false,
126 }
127
128 m := NewManager(nil)
129
130 canNav, paneID := m.navigateWithinStack(node, nil, usecase.NavDown)
131
132 assert.False(t, canNav)
133 assert.Empty(t, paneID)
134}
135
136// TestNavigateWithinStack_InvalidActiveIndex tests navigation with invalid ActiveStackIndex
137// falls back to pointer search.
138func TestNavigateWithinStack_InvalidActiveIndexFallback(t *testing.T) {
139 pane0 := &entity.Pane{ID: "pane0"}
140 pane1 := &entity.Pane{ID: "pane1"}
141
142 child0 := &entity.PaneNode{ID: "child0", Pane: pane0}
143 child1 := &entity.PaneNode{ID: "child1", Pane: pane1}
144
145 stackNode := &entity.PaneNode{
146 ID: "stack",
147 IsStacked: true,
148 Children: []*entity.PaneNode{child0, child1},
149 ActiveStackIndex: -1, // Invalid index
150 }
151
152 child0.Parent = stackNode
153 child1.Parent = stackNode
154
155 m := NewManager(nil)
156
157 // With invalid ActiveStackIndex, should fall back to pointer search
158 // Pass child0 as current node
159 canNav, paneID := m.navigateWithinStack(stackNode, child0, usecase.NavDown)
160
161 assert.True(t, canNav)
162 assert.Equal(t, entity.PaneID("pane1"), paneID)
163}
164
165// TestNavigateWithinStack_InvalidDirection tests navigation with invalid direction returns false.
166func TestNavigateWithinStack_InvalidDirection(t *testing.T) {
167 pane0 := &entity.Pane{ID: "pane0"}
168 child0 := &entity.PaneNode{ID: "child0", Pane: pane0}
169
170 stackNode := &entity.PaneNode{
171 ID: "stack",
172 IsStacked: true,
173 Children: []*entity.PaneNode{child0},
174 ActiveStackIndex: 0,
175 }
176
177 m := NewManager(nil)
178
179 // NavLeft and NavRight should not work for stack navigation
180 canNav, paneID := m.navigateWithinStack(stackNode, child0, usecase.NavLeft)
181
182 assert.False(t, canNav)
183 assert.Empty(t, paneID)
184}
185
186// TestNavigateWithinStack_ReturnsCorrectPaneID tests that navigation returns
187// the correct target pane ID for sequential navigation.
188// Note: navigateWithinStack is now a pure function - state updates happen in ApplyFocusChange.
189func TestNavigateWithinStack_ReturnsCorrectPaneID(t *testing.T) {
190 pane0 := &entity.Pane{ID: "pane0"}
191 pane1 := &entity.Pane{ID: "pane1"}
192 pane2 := &entity.Pane{ID: "pane2"}
193
194 child0 := &entity.PaneNode{ID: "child0", Pane: pane0}
195 child1 := &entity.PaneNode{ID: "child1", Pane: pane1}
196 child2 := &entity.PaneNode{ID: "child2", Pane: pane2}
197
198 stackNode := &entity.PaneNode{
199 ID: "stack",
200 IsStacked: true,
201 Children: []*entity.PaneNode{child0, child1, child2},
202 ActiveStackIndex: 0,
203 }
204
205 m := NewManager(nil)
206
207 // Navigate down from first - should return pane1
208 stackNode.ActiveStackIndex = 0
209 canNav, paneID := m.navigateWithinStack(stackNode, child0, usecase.NavDown)
210 assert.True(t, canNav)
211 assert.Equal(t, entity.PaneID("pane1"), paneID)
212
213 // Navigate down from middle - should return pane2
214 stackNode.ActiveStackIndex = 1
215 canNav, paneID = m.navigateWithinStack(stackNode, child1, usecase.NavDown)
216 assert.True(t, canNav)
217 assert.Equal(t, entity.PaneID("pane2"), paneID)
218
219 // Navigate up from last - should return pane1
220 stackNode.ActiveStackIndex = 2
221 canNav, paneID = m.navigateWithinStack(stackNode, child2, usecase.NavUp)
222 assert.True(t, canNav)
223 assert.Equal(t, entity.PaneID("pane1"), paneID)
224
225 // Navigate up from middle - should return pane0
226 stackNode.ActiveStackIndex = 1
227 canNav, paneID = m.navigateWithinStack(stackNode, child1, usecase.NavUp)
228 assert.True(t, canNav)
229 assert.Equal(t, entity.PaneID("pane0"), paneID)
230}