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}