v0.21.0 73bf644

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

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