v0.33.1 9577fee

tree_test.go RawHistoryFolder

1package layout_test
2
3import (
4	"context"
5	"testing"
6
7	"github.com/stretchr/testify/assert"
8	"github.com/stretchr/testify/mock"
9	"github.com/stretchr/testify/require"
10
11	"github.com/bnema/dumber/internal/domain/entity"
12	"github.com/bnema/dumber/internal/ui/layout"
13	"github.com/bnema/dumber/internal/ui/layout/mocks"
14)
15
16func setupStackedLeafMocks(t *testing.T, mockFactory *mocks.MockWidgetFactory) (*mocks.MockBoxWidget, *mocks.MockWidget) {
17	mockStackBox := mocks.NewMockBoxWidget(t)
18	mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockStackBox).Once()
19	mockStackBox.EXPECT().SetHexpand(true).Once()
20	mockStackBox.EXPECT().SetVexpand(true).Once()
21
22	mockTitleBar, mockFavicon, mockLabel, mockContainer := setupPaneMocks(t, mockFactory, mockStackBox)
23	_ = mockFavicon
24	_ = mockLabel
25	mockTitleBar.EXPECT().SetVisible(false).Once() // Active pane hides its title bar
26	mockContainer.EXPECT().SetVisible(true).Once()
27	mockTitleBar.EXPECT().AddCssClass("active").Once()
28
29	return mockStackBox, mockContainer
30}
31
32func TestNewTreeRenderer_CreatesRenderer(t *testing.T) {
33	// Arrange
34	ctx := context.Background()
35	mockFactory := mocks.NewMockWidgetFactory(t)
36	mockPaneViewFactory := mocks.NewMockPaneViewFactory(t)
37
38	// Act
39	renderer := layout.NewTreeRenderer(ctx, mockFactory, mockPaneViewFactory)
40
41	// Assert
42	require.NotNil(t, renderer)
43	assert.Equal(t, 0, renderer.NodeCount())
44}
45
46func TestBuild_NilRoot_ReturnsError(t *testing.T) {
47	// Arrange
48	ctx := context.Background()
49	mockFactory := mocks.NewMockWidgetFactory(t)
50	mockPaneViewFactory := mocks.NewMockPaneViewFactory(t)
51
52	renderer := layout.NewTreeRenderer(ctx, mockFactory, mockPaneViewFactory)
53
54	// Act
55	widget, err := renderer.Build(ctx, nil)
56
57	// Assert
58	assert.Nil(t, widget)
59	assert.ErrorIs(t, err, layout.ErrNilRoot)
60}
61
62func TestBuild_SingleLeafPane(t *testing.T) {
63	// Arrange
64	ctx := context.Background()
65	mockFactory := mocks.NewMockWidgetFactory(t)
66	mockPaneViewFactory := mocks.NewMockPaneViewFactory(t)
67	mockStackBox, mockContainer := setupStackedLeafMocks(t, mockFactory)
68
69	pane := entity.NewPane(entity.PaneID("pane-1"))
70	node := &entity.PaneNode{
71		ID:   "node-1",
72		Pane: pane,
73	}
74
75	mockPaneViewFactory.EXPECT().CreatePaneView(node).Return(mockContainer).Once()
76
77	renderer := layout.NewTreeRenderer(ctx, mockFactory, mockPaneViewFactory)
78
79	// Act
80	widget, err := renderer.Build(ctx, node)
81
82	// Assert
83	require.NoError(t, err)
84	assert.Equal(t, mockStackBox, widget)
85	assert.Equal(t, 1, renderer.NodeCount())
86
87	// Verify lookup works
88	assert.Equal(t, mockStackBox, renderer.Lookup("node-1"))
89}
90
91func TestBuild_HorizontalSplit(t *testing.T) {
92	// Arrange
93	ctx := context.Background()
94	mockFactory := mocks.NewMockWidgetFactory(t)
95	mockPaneViewFactory := mocks.NewMockPaneViewFactory(t)
96	mockPaned := mocks.NewMockPanedWidget(t)
97	mockLeftStackBox, mockLeftContainer := setupStackedLeafMocks(t, mockFactory)
98	mockRightStackBox, mockRightContainer := setupStackedLeafMocks(t, mockFactory)
99
100	leftPane := entity.NewPane(entity.PaneID("pane-left"))
101	rightPane := entity.NewPane(entity.PaneID("pane-right"))
102
103	leftNode := &entity.PaneNode{ID: "node-left", Pane: leftPane}
104	rightNode := &entity.PaneNode{ID: "node-right", Pane: rightPane}
105
106	splitNode := &entity.PaneNode{
107		ID:         "node-split",
108		SplitDir:   entity.SplitHorizontal,
109		SplitRatio: 0.5,
110		Children:   []*entity.PaneNode{leftNode, rightNode},
111	}
112
113	mockPaneViewFactory.EXPECT().CreatePaneView(leftNode).Return(mockLeftContainer).Once()
114	mockPaneViewFactory.EXPECT().CreatePaneView(rightNode).Return(mockRightContainer).Once()
115
116	// SplitView creation expectations
117	mockFactory.EXPECT().NewPaned(layout.OrientationHorizontal).Return(mockPaned).Once()
118	mockPaned.EXPECT().SetResizeStartChild(true).Once()
119	mockPaned.EXPECT().SetResizeEndChild(true).Once()
120	mockPaned.EXPECT().SetVisible(true).Once()
121	mockLeftStackBox.EXPECT().SetVisible(true).Once()
122	mockRightStackBox.EXPECT().SetVisible(true).Once()
123	mockPaned.EXPECT().SetStartChild(mockLeftStackBox).Once()
124	mockPaned.EXPECT().SetEndChild(mockRightStackBox).Once()
125	mockPaned.EXPECT().ConnectNotifyPosition(mock.Anything).Return(uint(0)).Once()
126	mockPaned.EXPECT().GetAllocatedWidth().Return(0).Once()
127	mockPaned.EXPECT().ConnectMap(mock.Anything).Return(uint(0)).Once()
128	mockPaned.EXPECT().AddTickCallback(mock.Anything).Return(uint(0)).Once()
129
130	renderer := layout.NewTreeRenderer(ctx, mockFactory, mockPaneViewFactory)
131
132	// Act
133	widget, err := renderer.Build(ctx, splitNode)
134
135	// Assert
136	require.NoError(t, err)
137	assert.NotNil(t, widget)
138	assert.Equal(t, 3, renderer.NodeCount()) // split + 2 leaves
139}
140
141func TestBuild_VerticalSplit(t *testing.T) {
142	// Arrange
143	ctx := context.Background()
144	mockFactory := mocks.NewMockWidgetFactory(t)
145	mockPaneViewFactory := mocks.NewMockPaneViewFactory(t)
146	mockPaned := mocks.NewMockPanedWidget(t)
147	mockTopStackBox, mockTopContainer := setupStackedLeafMocks(t, mockFactory)
148	mockBottomStackBox, mockBottomContainer := setupStackedLeafMocks(t, mockFactory)
149
150	topPane := entity.NewPane(entity.PaneID("pane-top"))
151	bottomPane := entity.NewPane(entity.PaneID("pane-bottom"))
152
153	topNode := &entity.PaneNode{ID: "node-top", Pane: topPane}
154	bottomNode := &entity.PaneNode{ID: "node-bottom", Pane: bottomPane}
155
156	splitNode := &entity.PaneNode{
157		ID:         "node-split",
158		SplitDir:   entity.SplitVertical,
159		SplitRatio: 0.3,
160		Children:   []*entity.PaneNode{topNode, bottomNode},
161	}
162
163	mockPaneViewFactory.EXPECT().CreatePaneView(topNode).Return(mockTopContainer).Once()
164	mockPaneViewFactory.EXPECT().CreatePaneView(bottomNode).Return(mockBottomContainer).Once()
165
166	// SplitView creation expectations - should be vertical
167	mockFactory.EXPECT().NewPaned(layout.OrientationVertical).Return(mockPaned).Once()
168	mockPaned.EXPECT().SetResizeStartChild(true).Once()
169	mockPaned.EXPECT().SetResizeEndChild(true).Once()
170	mockPaned.EXPECT().SetVisible(true).Once()
171	mockTopStackBox.EXPECT().SetVisible(true).Once()
172	mockBottomStackBox.EXPECT().SetVisible(true).Once()
173	mockPaned.EXPECT().SetStartChild(mockTopStackBox).Once()
174	mockPaned.EXPECT().SetEndChild(mockBottomStackBox).Once()
175	mockPaned.EXPECT().ConnectNotifyPosition(mock.Anything).Return(uint(0)).Once()
176	mockPaned.EXPECT().GetAllocatedHeight().Return(0).Once()
177	mockPaned.EXPECT().ConnectMap(mock.Anything).Return(uint(0)).Once()
178	mockPaned.EXPECT().AddTickCallback(mock.Anything).Return(uint(0)).Once()
179
180	renderer := layout.NewTreeRenderer(ctx, mockFactory, mockPaneViewFactory)
181
182	// Act
183	widget, err := renderer.Build(ctx, splitNode)
184
185	// Assert
186	require.NoError(t, err)
187	assert.NotNil(t, widget)
188}
189
190func TestBuild_NestedSplits(t *testing.T) {
191	// Arrange
192	ctx := context.Background()
193	mockFactory := mocks.NewMockWidgetFactory(t)
194	mockPaneViewFactory := mocks.NewMockPaneViewFactory(t)
195	mockOuterPaned := mocks.NewMockPanedWidget(t)
196	mockInnerPaned := mocks.NewMockPanedWidget(t)
197	mockStackBox1, mockContainer1 := setupStackedLeafMocks(t, mockFactory)
198	mockStackBox2, mockContainer2 := setupStackedLeafMocks(t, mockFactory)
199	mockStackBox3, mockContainer3 := setupStackedLeafMocks(t, mockFactory)
200
201	// Create tree structure:
202	//       outer (horizontal)
203	//      /                \
204	//   inner (vertical)    pane3
205	//   /           \
206	// pane1       pane2
207
208	pane1 := entity.NewPane(entity.PaneID("pane-1"))
209	pane2 := entity.NewPane(entity.PaneID("pane-2"))
210	pane3 := entity.NewPane(entity.PaneID("pane-3"))
211
212	node1 := &entity.PaneNode{ID: "node-1", Pane: pane1}
213	node2 := &entity.PaneNode{ID: "node-2", Pane: pane2}
214	node3 := &entity.PaneNode{ID: "node-3", Pane: pane3}
215
216	innerSplit := &entity.PaneNode{
217		ID:         "inner-split",
218		SplitDir:   entity.SplitVertical,
219		SplitRatio: 0.5,
220		Children:   []*entity.PaneNode{node1, node2},
221	}
222
223	outerSplit := &entity.PaneNode{
224		ID:         "outer-split",
225		SplitDir:   entity.SplitHorizontal,
226		SplitRatio: 0.6,
227		Children:   []*entity.PaneNode{innerSplit, node3},
228	}
229
230	// Setup expectations for leaf nodes
231	mockPaneViewFactory.EXPECT().CreatePaneView(node1).Return(mockContainer1).Once()
232	mockPaneViewFactory.EXPECT().CreatePaneView(node2).Return(mockContainer2).Once()
233	mockPaneViewFactory.EXPECT().CreatePaneView(node3).Return(mockContainer3).Once()
234
235	// Inner split (vertical) is created first
236	mockFactory.EXPECT().NewPaned(layout.OrientationVertical).Return(mockInnerPaned).Once()
237	mockInnerPaned.EXPECT().SetResizeStartChild(true).Once()
238	mockInnerPaned.EXPECT().SetResizeEndChild(true).Once()
239	mockInnerPaned.EXPECT().SetVisible(true).Once()
240	mockStackBox1.EXPECT().SetVisible(true).Once()
241	mockStackBox2.EXPECT().SetVisible(true).Once()
242	mockInnerPaned.EXPECT().SetStartChild(mockStackBox1).Once()
243	mockInnerPaned.EXPECT().SetEndChild(mockStackBox2).Once()
244	mockInnerPaned.EXPECT().ConnectNotifyPosition(mock.Anything).Return(uint(0)).Once()
245	mockInnerPaned.EXPECT().GetAllocatedHeight().Return(0).Once()
246	mockInnerPaned.EXPECT().ConnectMap(mock.Anything).Return(uint(0)).Once()
247	mockInnerPaned.EXPECT().AddTickCallback(mock.Anything).Return(uint(0)).Once()
248
249	// Outer split (horizontal) gets inner paned and pane3
250	mockFactory.EXPECT().NewPaned(layout.OrientationHorizontal).Return(mockOuterPaned).Once()
251	mockOuterPaned.EXPECT().SetResizeStartChild(true).Once()
252	mockOuterPaned.EXPECT().SetResizeEndChild(true).Once()
253	mockOuterPaned.EXPECT().SetVisible(true).Once()
254	mockInnerPaned.EXPECT().SetVisible(true).Once()
255	mockStackBox3.EXPECT().SetVisible(true).Once()
256	mockOuterPaned.EXPECT().SetStartChild(mockInnerPaned).Once()
257	mockOuterPaned.EXPECT().SetEndChild(mockStackBox3).Once()
258	mockOuterPaned.EXPECT().ConnectNotifyPosition(mock.Anything).Return(uint(0)).Once()
259	mockOuterPaned.EXPECT().GetAllocatedWidth().Return(0).Once()
260	mockOuterPaned.EXPECT().ConnectMap(mock.Anything).Return(uint(0)).Once()
261	mockOuterPaned.EXPECT().AddTickCallback(mock.Anything).Return(uint(0)).Once()
262
263	renderer := layout.NewTreeRenderer(ctx, mockFactory, mockPaneViewFactory)
264
265	// Act
266	widget, err := renderer.Build(ctx, outerSplit)
267
268	// Assert
269	require.NoError(t, err)
270	assert.NotNil(t, widget)
271	assert.Equal(t, 5, renderer.NodeCount()) // 2 splits + 3 leaves
272}
273
274// Note: TestBuild_StackedPanes is omitted because StackedView has complex internal
275// widget creation that makes mocking unwieldy. StackedView is thoroughly tested
276// in stacked_test.go. TreeRenderer's stacked rendering is verified implicitly
277// through integration tests.
278
279func TestLookup_ExistingNode(t *testing.T) {
280	// Arrange
281	ctx := context.Background()
282	mockFactory := mocks.NewMockWidgetFactory(t)
283	mockPaneViewFactory := mocks.NewMockPaneViewFactory(t)
284	mockStackBox, mockContainer := setupStackedLeafMocks(t, mockFactory)
285
286	pane := entity.NewPane(entity.PaneID("pane-1"))
287	node := &entity.PaneNode{ID: "test-node-id", Pane: pane}
288
289	mockPaneViewFactory.EXPECT().CreatePaneView(node).Return(mockContainer).Once()
290
291	renderer := layout.NewTreeRenderer(ctx, mockFactory, mockPaneViewFactory)
292	_, _ = renderer.Build(ctx, node)
293
294	// Act
295	result := renderer.Lookup("test-node-id")
296
297	// Assert
298	assert.Equal(t, mockStackBox, result)
299}
300
301func TestLookup_NonExistentNode(t *testing.T) {
302	// Arrange
303	ctx := context.Background()
304	mockFactory := mocks.NewMockWidgetFactory(t)
305	mockPaneViewFactory := mocks.NewMockPaneViewFactory(t)
306	_, mockContainer := setupStackedLeafMocks(t, mockFactory)
307
308	pane := entity.NewPane(entity.PaneID("pane-1"))
309	node := &entity.PaneNode{ID: "node-1", Pane: pane}
310
311	mockPaneViewFactory.EXPECT().CreatePaneView(node).Return(mockContainer).Once()
312
313	renderer := layout.NewTreeRenderer(ctx, mockFactory, mockPaneViewFactory)
314	_, _ = renderer.Build(ctx, node)
315
316	// Act
317	result := renderer.Lookup("non-existent-id")
318
319	// Assert
320	assert.Nil(t, result)
321}
322
323func TestLookupNode_Existing(t *testing.T) {
324	// Arrange
325	ctx := context.Background()
326	mockFactory := mocks.NewMockWidgetFactory(t)
327	mockPaneViewFactory := mocks.NewMockPaneViewFactory(t)
328	mockStackBox, mockContainer := setupStackedLeafMocks(t, mockFactory)
329
330	pane := entity.NewPane(entity.PaneID("pane-1"))
331	node := &entity.PaneNode{ID: "node-1", Pane: pane}
332
333	mockPaneViewFactory.EXPECT().CreatePaneView(node).Return(mockContainer).Once()
334
335	renderer := layout.NewTreeRenderer(ctx, mockFactory, mockPaneViewFactory)
336	_, _ = renderer.Build(ctx, node)
337
338	// Act
339	widget, found := renderer.LookupNode("node-1")
340
341	// Assert
342	assert.True(t, found)
343	assert.Equal(t, mockStackBox, widget)
344}
345
346func TestLookupNode_NonExistent(t *testing.T) {
347	// Arrange
348	ctx := context.Background()
349	mockFactory := mocks.NewMockWidgetFactory(t)
350	mockPaneViewFactory := mocks.NewMockPaneViewFactory(t)
351
352	renderer := layout.NewTreeRenderer(ctx, mockFactory, mockPaneViewFactory)
353
354	// Act
355	widget, found := renderer.LookupNode("non-existent")
356
357	// Assert
358	assert.False(t, found)
359	assert.Nil(t, widget)
360}
361
362func TestClear_RemovesAllMappings(t *testing.T) {
363	// Arrange
364	ctx := context.Background()
365	mockFactory := mocks.NewMockWidgetFactory(t)
366	mockPaneViewFactory := mocks.NewMockPaneViewFactory(t)
367	_, mockContainer := setupStackedLeafMocks(t, mockFactory)
368
369	pane := entity.NewPane(entity.PaneID("pane-1"))
370	node := &entity.PaneNode{ID: "node-1", Pane: pane}
371
372	mockPaneViewFactory.EXPECT().CreatePaneView(node).Return(mockContainer).Once()
373
374	renderer := layout.NewTreeRenderer(ctx, mockFactory, mockPaneViewFactory)
375	_, _ = renderer.Build(ctx, node)
376
377	require.Equal(t, 1, renderer.NodeCount())
378
379	// Act
380	renderer.Clear()
381
382	// Assert
383	assert.Equal(t, 0, renderer.NodeCount())
384	assert.Nil(t, renderer.Lookup("node-1"))
385}
386
387func TestGetNodeIDs_ReturnsAllIDs(t *testing.T) {
388	// Arrange
389	mockFactory := mocks.NewMockWidgetFactory(t)
390	ctx := context.Background()
391	mockPaneViewFactory := mocks.NewMockPaneViewFactory(t)
392	mockPaned := mocks.NewMockPanedWidget(t)
393	mockLeftStackBox, mockLeftContainer := setupStackedLeafMocks(t, mockFactory)
394	mockRightStackBox, mockRightContainer := setupStackedLeafMocks(t, mockFactory)
395
396	pane1 := entity.NewPane(entity.PaneID("pane-1"))
397	pane2 := entity.NewPane(entity.PaneID("pane-2"))
398
399	node1 := &entity.PaneNode{ID: "left-node", Pane: pane1}
400	node2 := &entity.PaneNode{ID: "right-node", Pane: pane2}
401
402	splitNode := &entity.PaneNode{
403		ID:         "split-node",
404		SplitDir:   entity.SplitHorizontal,
405		SplitRatio: 0.5,
406		Children:   []*entity.PaneNode{node1, node2},
407	}
408
409	mockPaneViewFactory.EXPECT().CreatePaneView(node1).Return(mockLeftContainer).Once()
410	mockPaneViewFactory.EXPECT().CreatePaneView(node2).Return(mockRightContainer).Once()
411
412	mockFactory.EXPECT().NewPaned(layout.OrientationHorizontal).Return(mockPaned).Once()
413	mockPaned.EXPECT().SetResizeStartChild(true).Once()
414	mockPaned.EXPECT().SetResizeEndChild(true).Once()
415	mockPaned.EXPECT().SetVisible(true).Once()
416	mockLeftStackBox.EXPECT().SetVisible(true).Once()
417	mockRightStackBox.EXPECT().SetVisible(true).Once()
418	mockPaned.EXPECT().SetStartChild(mockLeftStackBox).Once()
419	mockPaned.EXPECT().SetEndChild(mockRightStackBox).Once()
420	mockPaned.EXPECT().ConnectNotifyPosition(mock.Anything).Return(uint(0)).Once()
421	mockPaned.EXPECT().GetAllocatedWidth().Return(0).Once()
422	mockPaned.EXPECT().ConnectMap(mock.Anything).Return(uint(0)).Once()
423	mockPaned.EXPECT().AddTickCallback(mock.Anything).Return(uint(0)).Once()
424
425	renderer := layout.NewTreeRenderer(ctx, mockFactory, mockPaneViewFactory)
426	_, _ = renderer.Build(ctx, splitNode)
427
428	// Act
429	ids := renderer.GetNodeIDs()
430
431	// Assert
432	assert.Len(t, ids, 3)
433	assert.Contains(t, ids, "split-node")
434	assert.Contains(t, ids, "left-node")
435	assert.Contains(t, ids, "right-node")
436}
437
438func TestBuild_ClearsPreviousMappings(t *testing.T) {
439	// Arrange
440	ctx := context.Background()
441	mockFactory := mocks.NewMockWidgetFactory(t)
442	mockPaneViewFactory := mocks.NewMockPaneViewFactory(t)
443	mockOldStackBox, mockOldContainer := setupStackedLeafMocks(t, mockFactory)
444	mockNewStackBox, mockNewContainer := setupStackedLeafMocks(t, mockFactory)
445
446	pane1 := entity.NewPane(entity.PaneID("pane-1"))
447	node1 := &entity.PaneNode{ID: "old-node", Pane: pane1}
448
449	pane2 := entity.NewPane(entity.PaneID("pane-2"))
450	node2 := &entity.PaneNode{ID: "new-node", Pane: pane2}
451
452	mockPaneViewFactory.EXPECT().CreatePaneView(node1).Return(mockOldContainer).Once()
453	mockPaneViewFactory.EXPECT().CreatePaneView(node2).Return(mockNewContainer).Once()
454
455	renderer := layout.NewTreeRenderer(ctx, mockFactory, mockPaneViewFactory)
456
457	// First build
458	_, _ = renderer.Build(ctx, node1)
459	require.Equal(t, 1, renderer.NodeCount())
460	require.Equal(t, mockOldStackBox, renderer.Lookup("old-node"))
461
462	// Act - second build should clear old mappings
463	_, _ = renderer.Build(ctx, node2)
464
465	// Assert
466	assert.Equal(t, 1, renderer.NodeCount())
467	assert.Nil(t, renderer.Lookup("old-node"))
468	assert.Equal(t, mockNewStackBox, renderer.Lookup("new-node"))
469}
470
471func TestBuild_NilPaneViewFactory_ReturnsNilForLeaves(t *testing.T) {
472	// Arrange
473	ctx := context.Background()
474	mockFactory := mocks.NewMockWidgetFactory(t)
475
476	pane := entity.NewPane(entity.PaneID("pane-1"))
477	node := &entity.PaneNode{ID: "node-1", Pane: pane}
478
479	renderer := layout.NewTreeRenderer(ctx, mockFactory, nil)
480
481	// Act
482	widget, err := renderer.Build(ctx, node)
483
484	// Assert
485	require.NoError(t, err)
486	assert.Nil(t, widget) // nil because paneViewFactory is nil
487}
488
489func TestBuild_EmptyNodeID_NotStored(t *testing.T) {
490	// Arrange
491	ctx := context.Background()
492	mockFactory := mocks.NewMockWidgetFactory(t)
493	mockPaneViewFactory := mocks.NewMockPaneViewFactory(t)
494	_, mockContainer := setupStackedLeafMocks(t, mockFactory)
495
496	pane := entity.NewPane(entity.PaneID("pane-1"))
497	node := &entity.PaneNode{ID: "", Pane: pane} // Empty ID
498
499	mockPaneViewFactory.EXPECT().CreatePaneView(node).Return(mockContainer).Once()
500
501	renderer := layout.NewTreeRenderer(ctx, mockFactory, mockPaneViewFactory)
502
503	// Act
504	widget, err := renderer.Build(ctx, node)
505
506	// Assert
507	require.NoError(t, err)
508	assert.NotNil(t, widget)
509	assert.Equal(t, 0, renderer.NodeCount()) // Empty ID nodes aren't stored
510}
511
512func TestUpdateSplitRatio_ExistingNode(t *testing.T) {
513	// Arrange
514	mockFactory := mocks.NewMockWidgetFactory(t)
515	ctx := context.Background()
516	mockPaneViewFactory := mocks.NewMockPaneViewFactory(t)
517	mockPaned := mocks.NewMockPanedWidget(t)
518	mockLeftStackBox, mockLeftContainer := setupStackedLeafMocks(t, mockFactory)
519	mockRightStackBox, mockRightContainer := setupStackedLeafMocks(t, mockFactory)
520
521	pane1 := entity.NewPane(entity.PaneID("pane-1"))
522	pane2 := entity.NewPane(entity.PaneID("pane-2"))
523
524	node1 := &entity.PaneNode{ID: "node-1", Pane: pane1}
525	node2 := &entity.PaneNode{ID: "node-2", Pane: pane2}
526
527	splitNode := &entity.PaneNode{
528		ID:         "split-node",
529		SplitDir:   entity.SplitHorizontal,
530		SplitRatio: 0.5,
531		Children:   []*entity.PaneNode{node1, node2},
532	}
533
534	mockPaneViewFactory.EXPECT().CreatePaneView(node1).Return(mockLeftContainer).Once()
535	mockPaneViewFactory.EXPECT().CreatePaneView(node2).Return(mockRightContainer).Once()
536
537	mockFactory.EXPECT().NewPaned(layout.OrientationHorizontal).Return(mockPaned).Once()
538	mockPaned.EXPECT().SetResizeStartChild(true).Once()
539	mockPaned.EXPECT().SetResizeEndChild(true).Once()
540	mockPaned.EXPECT().SetVisible(true).Once()
541	mockLeftStackBox.EXPECT().SetVisible(true).Once()
542	mockRightStackBox.EXPECT().SetVisible(true).Once()
543	mockPaned.EXPECT().SetStartChild(mockLeftStackBox).Once()
544	mockPaned.EXPECT().SetEndChild(mockRightStackBox).Once()
545	mockPaned.EXPECT().ConnectNotifyPosition(mock.Anything).Return(uint(0)).Once()
546	mockPaned.EXPECT().GetAllocatedWidth().Return(0).Twice()
547	mockPaned.EXPECT().ConnectMap(mock.Anything).Return(uint(0)).Once()
548
549	mockPaned.EXPECT().AddTickCallback(mock.Anything).Return(uint(0)).Once()
550
551	renderer := layout.NewTreeRenderer(ctx, mockFactory, mockPaneViewFactory)
552	_, _ = renderer.Build(ctx, splitNode)
553
554	// Act
555	err := renderer.UpdateSplitRatio("split-node", 0.7)
556
557	// Assert
558	assert.NoError(t, err)
559}
560
561func TestUpdateSplitRatio_NonExistentNode(t *testing.T) {
562	// Arrange
563	mockFactory := mocks.NewMockWidgetFactory(t)
564	ctx := context.Background()
565	mockPaneViewFactory := mocks.NewMockPaneViewFactory(t)
566
567	renderer := layout.NewTreeRenderer(ctx, mockFactory, mockPaneViewFactory)
568
569	// Act
570	err := renderer.UpdateSplitRatio("non-existent", 0.7)
571
572	// Assert
573	assert.ErrorIs(t, err, layout.ErrNodeNotFound)
574}

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