manage_panes_close_stack_test.go RawHistoryFolder

1package usecase
2
3import (
4	"context"
5	"testing"
6
7	"github.com/bnema/dumber/internal/domain/entity"
8)
9
10func TestManagePanesUseCase_CloseStackedPane_RemovesFromStackWithMoreThanTwo(t *testing.T) {
11	uc := NewManagePanesUseCase(func() string { return "id" }, nil)
12	ctx := context.Background()
13
14	// Create a stack with 3 panes
15	a := leaf("a")
16	b := leaf("b")
17	c := leaf("c")
18	stackNode := stack(a, b, c)
19	stackNode.ActiveStackIndex = 1 // b is active
20
21	ws := &entity.Workspace{Root: stackNode, ActivePaneID: b.Pane.ID}
22
23	// Close pane b (the middle one)
24	result, err := uc.Close(ctx, ws, b)
25	if err != nil {
26		t.Fatalf("unexpected error: %v", err)
27	}
28
29	// Stack should remain with 2 panes
30	if !ws.Root.IsStacked {
31		t.Fatalf("root should still be a stack")
32	}
33	if len(ws.Root.Children) != 2 {
34		t.Fatalf("stack should have 2 children, got %d", len(ws.Root.Children))
35	}
36	if got := panesInOrder(ws.Root); got != "a,c" {
37		t.Fatalf("remaining panes=%s, want a,c", got)
38	}
39
40	// Result should be the stack node
41	if result != stackNode {
42		t.Fatalf("expected stack node to be returned")
43	}
44}
45
46func TestManagePanesUseCase_CloseStackedPane_DissolvesStackWithTwo(t *testing.T) {
47	uc := NewManagePanesUseCase(func() string { return "id" }, nil)
48	ctx := context.Background()
49
50	// Create a stack with 2 panes
51	a := leaf("a")
52	b := leaf("b")
53	stackNode := stack(a, b)
54	stackNode.ActiveStackIndex = 1 // b is active
55
56	ws := &entity.Workspace{Root: stackNode, ActivePaneID: b.Pane.ID}
57
58	// Close pane b
59	result, err := uc.Close(ctx, ws, b)
60	if err != nil {
61		t.Fatalf("unexpected error: %v", err)
62	}
63
64	// Stack should be dissolved, remaining pane becomes root
65	if ws.Root.IsStacked {
66		t.Fatalf("root should no longer be a stack")
67	}
68	if !ws.Root.IsLeaf() {
69		t.Fatalf("root should be a leaf")
70	}
71	if ws.Root.Pane.ID != "a" {
72		t.Fatalf("remaining pane should be a, got %s", ws.Root.Pane.ID)
73	}
74
75	// Result should be the remaining pane
76	if result.Pane.ID != "a" {
77		t.Fatalf("expected remaining pane a to be returned")
78	}
79
80	// Active pane should update to remaining pane
81	if ws.ActivePaneID != "a" {
82		t.Fatalf("active pane should be a, got %s", ws.ActivePaneID)
83	}
84}
85
86func TestManagePanesUseCase_CloseStackedPane_UpdatesActivePaneWhenClosingActive(t *testing.T) {
87	uc := NewManagePanesUseCase(func() string { return "id" }, nil)
88	ctx := context.Background()
89
90	// Create a stack with 3 panes, middle one is active
91	a := leaf("a")
92	b := leaf("b")
93	c := leaf("c")
94	stackNode := stack(a, b, c)
95	stackNode.ActiveStackIndex = 1 // b is active
96
97	ws := &entity.Workspace{Root: stackNode, ActivePaneID: b.Pane.ID}
98
99	// Close pane b (the active one)
100	_, err := uc.Close(ctx, ws, b)
101	if err != nil {
102		t.Fatalf("unexpected error: %v", err)
103	}
104
105	// Active pane should be updated (to the pane at the new active index)
106	if ws.ActivePaneID == "b" {
107		t.Fatalf("active pane should not be the closed pane b")
108	}
109	// Should be either a or c depending on new ActiveStackIndex
110	if ws.ActivePaneID != "a" && ws.ActivePaneID != "c" {
111		t.Fatalf("active pane should be a or c, got %s", ws.ActivePaneID)
112	}
113}
114
115func TestManagePanesUseCase_CloseStackedPane_PreservesActivePaneWhenClosingNonActive(t *testing.T) {
116	uc := NewManagePanesUseCase(func() string { return "id" }, nil)
117	ctx := context.Background()
118
119	// Create a stack with 3 panes, first one is active
120	a := leaf("a")
121	b := leaf("b")
122	c := leaf("c")
123	stackNode := stack(a, b, c)
124	stackNode.ActiveStackIndex = 0 // a is active
125
126	ws := &entity.Workspace{Root: stackNode, ActivePaneID: a.Pane.ID}
127
128	// Close pane c (not the active one)
129	_, err := uc.Close(ctx, ws, c)
130	if err != nil {
131		t.Fatalf("unexpected error: %v", err)
132	}
133
134	// Active pane should remain a
135	if ws.ActivePaneID != "a" {
136		t.Fatalf("active pane should still be a, got %s", ws.ActivePaneID)
137	}
138}
139
140func TestManagePanesUseCase_CloseStackedPane_DissolvesNestedStack(t *testing.T) {
141	uc := NewManagePanesUseCase(func() string { return "id" }, nil)
142	ctx := context.Background()
143
144	// Create a split with a stack on the left
145	a := leaf("a")
146	b := leaf("b")
147	stackNode := stack(a, b)
148	stackNode.ActiveStackIndex = 0
149
150	c := leaf("c")
151	root := split(entity.SplitHorizontal, stackNode, c)
152
153	ws := &entity.Workspace{Root: root, ActivePaneID: a.Pane.ID}
154
155	// Close pane a from the stack
156	_, err := uc.Close(ctx, ws, a)
157	if err != nil {
158		t.Fatalf("unexpected error: %v", err)
159	}
160
161	// Root should still be a split
162	if !ws.Root.IsSplit() {
163		t.Fatalf("root should still be a split")
164	}
165
166	// Left child should now be the remaining pane b (not a stack)
167	left := ws.Root.Left()
168	if left.IsStacked {
169		t.Fatalf("left child should no longer be a stack")
170	}
171	if !left.IsLeaf() || left.Pane.ID != "b" {
172		t.Fatalf("left child should be leaf b, got %v", left)
173	}
174
175	// Right child should still be c
176	right := ws.Root.Right()
177	if !right.IsLeaf() || right.Pane.ID != "c" {
178		t.Fatalf("right child should be leaf c")
179	}
180
181	// Active pane should update to b
182	if ws.ActivePaneID != "b" {
183		t.Fatalf("active pane should be b, got %s", ws.ActivePaneID)
184	}
185}
186
187func TestManagePanesUseCase_CloseStackedPane_ClosingFirstPaneUpdatesIndex(t *testing.T) {
188	uc := NewManagePanesUseCase(func() string { return "id" }, nil)
189	ctx := context.Background()
190
191	// Create a stack with 3 panes, first one is active
192	a := leaf("a")
193	b := leaf("b")
194	c := leaf("c")
195	stackNode := stack(a, b, c)
196	stackNode.ActiveStackIndex = 0 // a is active
197
198	ws := &entity.Workspace{Root: stackNode, ActivePaneID: a.Pane.ID}
199
200	// Close pane a (the first and active one)
201	_, err := uc.Close(ctx, ws, a)
202	if err != nil {
203		t.Fatalf("unexpected error: %v", err)
204	}
205
206	// Stack should have 2 panes remaining
207	if len(ws.Root.Children) != 2 {
208		t.Fatalf("stack should have 2 children, got %d", len(ws.Root.Children))
209	}
210	if got := panesInOrder(ws.Root); got != "b,c" {
211		t.Fatalf("remaining panes=%s, want b,c", got)
212	}
213
214	// Active pane should be updated
215	if ws.ActivePaneID == "a" {
216		t.Fatalf("active pane should not be the closed pane a")
217	}
218}
219
220func TestManagePanesUseCase_CloseStackedPane_ClosingLastPaneUpdatesIndex(t *testing.T) {
221	uc := NewManagePanesUseCase(func() string { return "id" }, nil)
222	ctx := context.Background()
223
224	// Create a stack with 3 panes, last one is active
225	a := leaf("a")
226	b := leaf("b")
227	c := leaf("c")
228	stackNode := stack(a, b, c)
229	stackNode.ActiveStackIndex = 2 // c is active
230
231	ws := &entity.Workspace{Root: stackNode, ActivePaneID: c.Pane.ID}
232
233	// Close pane c (the last and active one)
234	_, err := uc.Close(ctx, ws, c)
235	if err != nil {
236		t.Fatalf("unexpected error: %v", err)
237	}
238
239	// Stack should have 2 panes remaining
240	if len(ws.Root.Children) != 2 {
241		t.Fatalf("stack should have 2 children, got %d", len(ws.Root.Children))
242	}
243	if got := panesInOrder(ws.Root); got != "a,b" {
244		t.Fatalf("remaining panes=%s, want a,b", got)
245	}
246
247	// Active pane should be updated to the new last pane
248	if ws.ActivePaneID == "c" {
249		t.Fatalf("active pane should not be the closed pane c")
250	}
251}

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