v0.20.0 c741a39

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().GetAllocatedWidth().Return(0).Once()
127	mockPaned.EXPECT().ConnectMap(mock.Anything).Return(uint32(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().GetAllocatedHeight().Return(0).Once()
176	mockPaned.EXPECT().ConnectMap(mock.Anything).Return(uint32(0)).Once()
177	mockPaned.EXPECT().AddTickCallback(mock.Anything).Return(uint(0)).Once()
178
179	renderer := layout.NewTreeRenderer(ctx, mockFactory, mockPaneViewFactory)
180
181	// Act
182	widget, err := renderer.Build(ctx, splitNode)
183
184	// Assert
185	require.NoError(t, err)
186	assert.NotNil(t, widget)
187}
188
189func TestBuild_NestedSplits(t *testing.T) {
190	// Arrange
191	ctx := context.Background()
192	mockFactory := mocks.NewMockWidgetFactory(t)
193	mockPaneViewFactory := mocks.NewMockPaneViewFactory(t)
194	mockOuterPaned := mocks.NewMockPanedWidget(t)
195	mockInnerPaned := mocks.NewMockPanedWidget(t)
196	mockStackBox1, mockContainer1 := setupStackedLeafMocks(t, mockFactory)
197	mockStackBox2, mockContainer2 := setupStackedLeafMocks(t, mockFactory)
198	mockStackBox3, mockContainer3 := setupStackedLeafMocks(t, mockFactory)
199
200	// Create tree structure:
201	//       outer (horizontal)
202	//      /                \
203	//   inner (vertical)    pane3
204	//   /           \
205	// pane1       pane2
206
207	pane1 := entity.NewPane(entity.PaneID("pane-1"))
208	pane2 := entity.NewPane(entity.PaneID("pane-2"))
209	pane3 := entity.NewPane(entity.PaneID("pane-3"))
210
211	node1 := &entity.PaneNode{ID: "node-1", Pane: pane1}
212	node2 := &entity.PaneNode{ID: "node-2", Pane: pane2}
213	node3 := &entity.PaneNode{ID: "node-3", Pane: pane3}
214
215	innerSplit := &entity.PaneNode{
216		ID:         "inner-split",
217		SplitDir:   entity.SplitVertical,
218		SplitRatio: 0.5,
219		Children:   []*entity.PaneNode{node1, node2},
220	}
221
222	outerSplit := &entity.PaneNode{
223		ID:         "outer-split",
224		SplitDir:   entity.SplitHorizontal,
225		SplitRatio: 0.6,
226		Children:   []*entity.PaneNode{innerSplit, node3},
227	}
228
229	// Setup expectations for leaf nodes
230	mockPaneViewFactory.EXPECT().CreatePaneView(node1).Return(mockContainer1).Once()
231	mockPaneViewFactory.EXPECT().CreatePaneView(node2).Return(mockContainer2).Once()
232	mockPaneViewFactory.EXPECT().CreatePaneView(node3).Return(mockContainer3).Once()
233
234	// Inner split (vertical) is created first
235	mockFactory.EXPECT().NewPaned(layout.OrientationVertical).Return(mockInnerPaned).Once()
236	mockInnerPaned.EXPECT().SetResizeStartChild(true).Once()
237	mockInnerPaned.EXPECT().SetResizeEndChild(true).Once()
238	mockInnerPaned.EXPECT().SetVisible(true).Once()
239	mockStackBox1.EXPECT().SetVisible(true).Once()
240	mockStackBox2.EXPECT().SetVisible(true).Once()
241	mockInnerPaned.EXPECT().SetStartChild(mockStackBox1).Once()
242	mockInnerPaned.EXPECT().SetEndChild(mockStackBox2).Once()
243	mockInnerPaned.EXPECT().GetAllocatedHeight().Return(0).Once()
244	mockInnerPaned.EXPECT().ConnectMap(mock.Anything).Return(uint32(0)).Once()
245	mockInnerPaned.EXPECT().AddTickCallback(mock.Anything).Return(uint(0)).Once()
246
247	// Outer split (horizontal) gets inner paned and pane3
248	mockFactory.EXPECT().NewPaned(layout.OrientationHorizontal).Return(mockOuterPaned).Once()
249	mockOuterPaned.EXPECT().SetResizeStartChild(true).Once()
250	mockOuterPaned.EXPECT().SetResizeEndChild(true).Once()
251	mockOuterPaned.EXPECT().SetVisible(true).Once()
252	mockInnerPaned.EXPECT().SetVisible(true).Once()
253	mockStackBox3.EXPECT().SetVisible(true).Once()
254	mockOuterPaned.EXPECT().SetStartChild(mockInnerPaned).Once()
255	mockOuterPaned.EXPECT().SetEndChild(mockStackBox3).Once()
256	mockOuterPaned.EXPECT().GetAllocatedWidth().Return(0).Once()
257	mockOuterPaned.EXPECT().ConnectMap(mock.Anything).Return(uint32(0)).Once()
258	mockOuterPaned.EXPECT().AddTickCallback(mock.Anything).Return(uint(0)).Once()
259
260	renderer := layout.NewTreeRenderer(ctx, mockFactory, mockPaneViewFactory)
261
262	// Act
263	widget, err := renderer.Build(ctx, outerSplit)
264
265	// Assert
266	require.NoError(t, err)
267	assert.NotNil(t, widget)
268	assert.Equal(t, 5, renderer.NodeCount()) // 2 splits + 3 leaves
269}
270
271// Note: TestBuild_StackedPanes is omitted because StackedView has complex internal
272// widget creation that makes mocking unwieldy. StackedView is thoroughly tested
273// in stacked_test.go. TreeRenderer's stacked rendering is verified implicitly
274// through integration tests.
275
276func TestLookup_ExistingNode(t *testing.T) {
277	// Arrange
278	ctx := context.Background()
279	mockFactory := mocks.NewMockWidgetFactory(t)
280	mockPaneViewFactory := mocks.NewMockPaneViewFactory(t)
281	mockStackBox, mockContainer := setupStackedLeafMocks(t, mockFactory)
282
283	pane := entity.NewPane(entity.PaneID("pane-1"))
284	node := &entity.PaneNode{ID: "test-node-id", Pane: pane}
285
286	mockPaneViewFactory.EXPECT().CreatePaneView(node).Return(mockContainer).Once()
287
288	renderer := layout.NewTreeRenderer(ctx, mockFactory, mockPaneViewFactory)
289	_, _ = renderer.Build(ctx, node)
290
291	// Act
292	result := renderer.Lookup("test-node-id")
293
294	// Assert
295	assert.Equal(t, mockStackBox, result)
296}
297
298func TestLookup_NonExistentNode(t *testing.T) {
299	// Arrange
300	ctx := context.Background()
301	mockFactory := mocks.NewMockWidgetFactory(t)
302	mockPaneViewFactory := mocks.NewMockPaneViewFactory(t)
303	_, mockContainer := setupStackedLeafMocks(t, mockFactory)
304
305	pane := entity.NewPane(entity.PaneID("pane-1"))
306	node := &entity.PaneNode{ID: "node-1", Pane: pane}
307
308	mockPaneViewFactory.EXPECT().CreatePaneView(node).Return(mockContainer).Once()
309
310	renderer := layout.NewTreeRenderer(ctx, mockFactory, mockPaneViewFactory)
311	_, _ = renderer.Build(ctx, node)
312
313	// Act
314	result := renderer.Lookup("non-existent-id")
315
316	// Assert
317	assert.Nil(t, result)
318}
319
320func TestLookupNode_Existing(t *testing.T) {
321	// Arrange
322	ctx := context.Background()
323	mockFactory := mocks.NewMockWidgetFactory(t)
324	mockPaneViewFactory := mocks.NewMockPaneViewFactory(t)
325	mockStackBox, mockContainer := setupStackedLeafMocks(t, mockFactory)
326
327	pane := entity.NewPane(entity.PaneID("pane-1"))
328	node := &entity.PaneNode{ID: "node-1", Pane: pane}
329
330	mockPaneViewFactory.EXPECT().CreatePaneView(node).Return(mockContainer).Once()
331
332	renderer := layout.NewTreeRenderer(ctx, mockFactory, mockPaneViewFactory)
333	_, _ = renderer.Build(ctx, node)
334
335	// Act
336	widget, found := renderer.LookupNode("node-1")
337
338	// Assert
339	assert.True(t, found)
340	assert.Equal(t, mockStackBox, widget)
341}
342
343func TestLookupNode_NonExistent(t *testing.T) {
344	// Arrange
345	ctx := context.Background()
346	mockFactory := mocks.NewMockWidgetFactory(t)
347	mockPaneViewFactory := mocks.NewMockPaneViewFactory(t)
348
349	renderer := layout.NewTreeRenderer(ctx, mockFactory, mockPaneViewFactory)
350
351	// Act
352	widget, found := renderer.LookupNode("non-existent")
353
354	// Assert
355	assert.False(t, found)
356	assert.Nil(t, widget)
357}
358
359func TestClear_RemovesAllMappings(t *testing.T) {
360	// Arrange
361	ctx := context.Background()
362	mockFactory := mocks.NewMockWidgetFactory(t)
363	mockPaneViewFactory := mocks.NewMockPaneViewFactory(t)
364	_, mockContainer := setupStackedLeafMocks(t, mockFactory)
365
366	pane := entity.NewPane(entity.PaneID("pane-1"))
367	node := &entity.PaneNode{ID: "node-1", Pane: pane}
368
369	mockPaneViewFactory.EXPECT().CreatePaneView(node).Return(mockContainer).Once()
370
371	renderer := layout.NewTreeRenderer(ctx, mockFactory, mockPaneViewFactory)
372	_, _ = renderer.Build(ctx, node)
373
374	require.Equal(t, 1, renderer.NodeCount())
375
376	// Act
377	renderer.Clear()
378
379	// Assert
380	assert.Equal(t, 0, renderer.NodeCount())
381	assert.Nil(t, renderer.Lookup("node-1"))
382}
383
384func TestGetNodeIDs_ReturnsAllIDs(t *testing.T) {
385	// Arrange
386	mockFactory := mocks.NewMockWidgetFactory(t)
387	ctx := context.Background()
388	mockPaneViewFactory := mocks.NewMockPaneViewFactory(t)
389	mockPaned := mocks.NewMockPanedWidget(t)
390	mockLeftStackBox, mockLeftContainer := setupStackedLeafMocks(t, mockFactory)
391	mockRightStackBox, mockRightContainer := setupStackedLeafMocks(t, mockFactory)
392
393	pane1 := entity.NewPane(entity.PaneID("pane-1"))
394	pane2 := entity.NewPane(entity.PaneID("pane-2"))
395
396	node1 := &entity.PaneNode{ID: "left-node", Pane: pane1}
397	node2 := &entity.PaneNode{ID: "right-node", Pane: pane2}
398
399	splitNode := &entity.PaneNode{
400		ID:         "split-node",
401		SplitDir:   entity.SplitHorizontal,
402		SplitRatio: 0.5,
403		Children:   []*entity.PaneNode{node1, node2},
404	}
405
406	mockPaneViewFactory.EXPECT().CreatePaneView(node1).Return(mockLeftContainer).Once()
407	mockPaneViewFactory.EXPECT().CreatePaneView(node2).Return(mockRightContainer).Once()
408
409	mockFactory.EXPECT().NewPaned(layout.OrientationHorizontal).Return(mockPaned).Once()
410	mockPaned.EXPECT().SetResizeStartChild(true).Once()
411	mockPaned.EXPECT().SetResizeEndChild(true).Once()
412	mockPaned.EXPECT().SetVisible(true).Once()
413	mockLeftStackBox.EXPECT().SetVisible(true).Once()
414	mockRightStackBox.EXPECT().SetVisible(true).Once()
415	mockPaned.EXPECT().SetStartChild(mockLeftStackBox).Once()
416	mockPaned.EXPECT().SetEndChild(mockRightStackBox).Once()
417	mockPaned.EXPECT().GetAllocatedWidth().Return(0).Once()
418	mockPaned.EXPECT().ConnectMap(mock.Anything).Return(uint32(0)).Once()
419	mockPaned.EXPECT().AddTickCallback(mock.Anything).Return(uint(0)).Once()
420
421	renderer := layout.NewTreeRenderer(ctx, mockFactory, mockPaneViewFactory)
422	_, _ = renderer.Build(ctx, splitNode)
423
424	// Act
425	ids := renderer.GetNodeIDs()
426
427	// Assert
428	assert.Len(t, ids, 3)
429	assert.Contains(t, ids, "split-node")
430	assert.Contains(t, ids, "left-node")
431	assert.Contains(t, ids, "right-node")
432}
433
434func TestBuild_ClearsPreviousMappings(t *testing.T) {
435	// Arrange
436	ctx := context.Background()
437	mockFactory := mocks.NewMockWidgetFactory(t)
438	mockPaneViewFactory := mocks.NewMockPaneViewFactory(t)
439	mockOldStackBox, mockOldContainer := setupStackedLeafMocks(t, mockFactory)
440	mockNewStackBox, mockNewContainer := setupStackedLeafMocks(t, mockFactory)
441
442	pane1 := entity.NewPane(entity.PaneID("pane-1"))
443	node1 := &entity.PaneNode{ID: "old-node", Pane: pane1}
444
445	pane2 := entity.NewPane(entity.PaneID("pane-2"))
446	node2 := &entity.PaneNode{ID: "new-node", Pane: pane2}
447
448	mockPaneViewFactory.EXPECT().CreatePaneView(node1).Return(mockOldContainer).Once()
449	mockPaneViewFactory.EXPECT().CreatePaneView(node2).Return(mockNewContainer).Once()
450
451	renderer := layout.NewTreeRenderer(ctx, mockFactory, mockPaneViewFactory)
452
453	// First build
454	_, _ = renderer.Build(ctx, node1)
455	require.Equal(t, 1, renderer.NodeCount())
456	require.Equal(t, mockOldStackBox, renderer.Lookup("old-node"))
457
458	// Act - second build should clear old mappings
459	_, _ = renderer.Build(ctx, node2)
460
461	// Assert
462	assert.Equal(t, 1, renderer.NodeCount())
463	assert.Nil(t, renderer.Lookup("old-node"))
464	assert.Equal(t, mockNewStackBox, renderer.Lookup("new-node"))
465}
466
467func TestBuild_NilPaneViewFactory_ReturnsNilForLeaves(t *testing.T) {
468	// Arrange
469	ctx := context.Background()
470	mockFactory := mocks.NewMockWidgetFactory(t)
471
472	pane := entity.NewPane(entity.PaneID("pane-1"))
473	node := &entity.PaneNode{ID: "node-1", Pane: pane}
474
475	renderer := layout.NewTreeRenderer(ctx, mockFactory, nil)
476
477	// Act
478	widget, err := renderer.Build(ctx, node)
479
480	// Assert
481	require.NoError(t, err)
482	assert.Nil(t, widget) // nil because paneViewFactory is nil
483}
484
485func TestBuild_EmptyNodeID_NotStored(t *testing.T) {
486	// Arrange
487	ctx := context.Background()
488	mockFactory := mocks.NewMockWidgetFactory(t)
489	mockPaneViewFactory := mocks.NewMockPaneViewFactory(t)
490	_, mockContainer := setupStackedLeafMocks(t, mockFactory)
491
492	pane := entity.NewPane(entity.PaneID("pane-1"))
493	node := &entity.PaneNode{ID: "", Pane: pane} // Empty ID
494
495	mockPaneViewFactory.EXPECT().CreatePaneView(node).Return(mockContainer).Once()
496
497	renderer := layout.NewTreeRenderer(ctx, mockFactory, mockPaneViewFactory)
498
499	// Act
500	widget, err := renderer.Build(ctx, node)
501
502	// Assert
503	require.NoError(t, err)
504	assert.NotNil(t, widget)
505	assert.Equal(t, 0, renderer.NodeCount()) // Empty ID nodes aren't stored
506}
507
508func TestUpdateSplitRatio_ExistingNode(t *testing.T) {
509	// Arrange
510	mockFactory := mocks.NewMockWidgetFactory(t)
511	ctx := context.Background()
512	mockPaneViewFactory := mocks.NewMockPaneViewFactory(t)
513	mockPaned := mocks.NewMockPanedWidget(t)
514	mockLeftStackBox, mockLeftContainer := setupStackedLeafMocks(t, mockFactory)
515	mockRightStackBox, mockRightContainer := setupStackedLeafMocks(t, mockFactory)
516
517	pane1 := entity.NewPane(entity.PaneID("pane-1"))
518	pane2 := entity.NewPane(entity.PaneID("pane-2"))
519
520	node1 := &entity.PaneNode{ID: "node-1", Pane: pane1}
521	node2 := &entity.PaneNode{ID: "node-2", Pane: pane2}
522
523	splitNode := &entity.PaneNode{
524		ID:         "split-node",
525		SplitDir:   entity.SplitHorizontal,
526		SplitRatio: 0.5,
527		Children:   []*entity.PaneNode{node1, node2},
528	}
529
530	mockPaneViewFactory.EXPECT().CreatePaneView(node1).Return(mockLeftContainer).Once()
531	mockPaneViewFactory.EXPECT().CreatePaneView(node2).Return(mockRightContainer).Once()
532
533	mockFactory.EXPECT().NewPaned(layout.OrientationHorizontal).Return(mockPaned).Once()
534	mockPaned.EXPECT().SetResizeStartChild(true).Once()
535	mockPaned.EXPECT().SetResizeEndChild(true).Once()
536	mockPaned.EXPECT().SetVisible(true).Once()
537	mockLeftStackBox.EXPECT().SetVisible(true).Once()
538	mockRightStackBox.EXPECT().SetVisible(true).Once()
539	mockPaned.EXPECT().SetStartChild(mockLeftStackBox).Once()
540	mockPaned.EXPECT().SetEndChild(mockRightStackBox).Once()
541	mockPaned.EXPECT().GetAllocatedWidth().Return(0).Once()
542	mockPaned.EXPECT().ConnectMap(mock.Anything).Return(uint32(0)).Once()
543	mockPaned.EXPECT().AddTickCallback(mock.Anything).Return(uint(0)).Once()
544
545	renderer := layout.NewTreeRenderer(ctx, mockFactory, mockPaneViewFactory)
546	_, _ = renderer.Build(ctx, splitNode)
547
548	// Act
549	err := renderer.UpdateSplitRatio("split-node", 0.7)
550
551	// Assert
552	assert.NoError(t, err)
553}
554
555func TestUpdateSplitRatio_NonExistentNode(t *testing.T) {
556	// Arrange
557	mockFactory := mocks.NewMockWidgetFactory(t)
558	ctx := context.Background()
559	mockPaneViewFactory := mocks.NewMockPaneViewFactory(t)
560
561	renderer := layout.NewTreeRenderer(ctx, mockFactory, mockPaneViewFactory)
562
563	// Act
564	err := renderer.UpdateSplitRatio("non-existent", 0.7)
565
566	// Assert
567	assert.ErrorIs(t, err, layout.ErrNodeNotFound)
568}

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