v0.23.2 bfd5eb4

manager_test.go RawHistoryFolder

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

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close