detached 56a05c0

refactor(tests): align UI tests with context and layout changes

commit
56a05c0012a629216626664e117d9e59feb6c2f2
author
brice · · 2025-12-16 17:04 UTC
committer
brice · · 2025-12-16 20:46 UTC
parent
79f8362
signature
unsigned
6 files · +302 -646
M internal/ui/component/pane_view_test.go +39 -37
@@ -24,10 +24,13 @@ func TestNewPaneView_CreatesOverlay(t *testing.T) {
2424 mockFactory.EXPECT().NewOverlay().Return(mockOverlay).Once()
2525 mockOverlay.EXPECT().SetHexpand(true).Once()
2626 mockOverlay.EXPECT().SetVexpand(true).Once()
27+ mockOverlay.EXPECT().SetVisible(true).Once()
28+ mockWebView.EXPECT().SetVisible(true).Once()
2729 mockOverlay.EXPECT().SetChild(mockWebView).Once()
2830
2931 mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBorderBox).Once()
3032 mockBorderBox.EXPECT().SetCanFocus(false).Once()
33+ mockBorderBox.EXPECT().SetCanTarget(false).Once()
3134 mockBorderBox.EXPECT().AddCssClass("pane-border").Once()
3235 mockBorderBox.EXPECT().SetHexpand(true).Once()
3336 mockBorderBox.EXPECT().SetVexpand(true).Once()
@@ -55,10 +58,12 @@ func TestNewPaneView_NilWebView(t *testing.T) {
5558 mockFactory.EXPECT().NewOverlay().Return(mockOverlay).Once()
5659 mockOverlay.EXPECT().SetHexpand(true).Once()
5760 mockOverlay.EXPECT().SetVexpand(true).Once()
61+ mockOverlay.EXPECT().SetVisible(true).Once()
5862 // SetChild should NOT be called when webview is nil
5963
6064 mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBorderBox).Once()
6165 mockBorderBox.EXPECT().SetCanFocus(false).Once()
66+ mockBorderBox.EXPECT().SetCanTarget(false).Once()
6267 mockBorderBox.EXPECT().AddCssClass("pane-border").Once()
6368 mockBorderBox.EXPECT().SetHexpand(true).Once()
6469 mockBorderBox.EXPECT().SetVexpand(true).Once()
@@ -86,7 +91,7 @@ func TestSetActive_True_AddsCSSClass(t *testing.T) {
8691 pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), mockWebView)
8792
8893 // Expect CSS class to be added
89- mockBorderBox.EXPECT().AddCssClass("active-pane").Once()
94+ mockBorderBox.EXPECT().AddCssClass("pane-active").Once()
9095
9196 // Act
9297 pv.SetActive(true)
@@ -107,11 +112,11 @@ func TestSetActive_False_RemovesCSSClass(t *testing.T) {
107112 pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), mockWebView)
108113
109114 // First activate
110- mockBorderBox.EXPECT().AddCssClass("active-pane").Once()
115+ mockBorderBox.EXPECT().AddCssClass("pane-active").Once()
111116 pv.SetActive(true)
112117
113118 // Then deactivate
114- mockBorderBox.EXPECT().RemoveCssClass("active-pane").Once()
119+ mockBorderBox.EXPECT().RemoveCssClass("pane-active").Once()
115120
116121 // Act
117122 pv.SetActive(false)
@@ -189,6 +194,8 @@ func TestSetWebViewWidget_ReplacesWidget(t *testing.T) {
189194
190195 // Expect removal of old widget and addition of new
191196 mockOverlay.EXPECT().SetChild(nil).Once()
197+ mockNewWebView.EXPECT().GetParent().Return(nil).Once()
198+ mockNewWebView.EXPECT().SetVisible(true).Once()
192199 mockOverlay.EXPECT().SetChild(mockNewWebView).Once()
193200
194201 // Act
@@ -205,22 +212,13 @@ func TestSetWebViewWidget_FromNil(t *testing.T) {
205212 mockBorderBox := mocks.NewMockBoxWidget(t)
206213 mockNewWebView := mocks.NewMockWidget(t)
207214
208- // Setup without webview
209- mockFactory.EXPECT().NewOverlay().Return(mockOverlay).Once()
210- mockOverlay.EXPECT().SetHexpand(true).Once()
211- mockOverlay.EXPECT().SetVexpand(true).Once()
212- mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBorderBox).Once()
213- mockBorderBox.EXPECT().SetCanFocus(false).Once()
214- mockBorderBox.EXPECT().AddCssClass("pane-border").Once()
215- mockBorderBox.EXPECT().SetHexpand(true).Once()
216- mockBorderBox.EXPECT().SetVexpand(true).Once()
217- mockOverlay.EXPECT().AddOverlay(mockBorderBox).Once()
218- mockOverlay.EXPECT().SetClipOverlay(mockBorderBox, false).Once()
219- mockOverlay.EXPECT().SetMeasureOverlay(mockBorderBox, false).Once()
215+ setupPaneViewMocksNoWebView(mockFactory, mockOverlay, mockBorderBox)
220216
221217 pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), nil)
222218
223219 // Expect only setting new child (no removal since old was nil)
220+ mockNewWebView.EXPECT().GetParent().Return(nil).Once()
221+ mockNewWebView.EXPECT().SetVisible(true).Once()
224222 mockOverlay.EXPECT().SetChild(mockNewWebView).Once()
225223
226224 // Act
@@ -256,17 +254,7 @@ func TestGrabFocus_NilWebView_ReturnsFalse(t *testing.T) {
256254 mockOverlay := mocks.NewMockOverlayWidget(t)
257255 mockBorderBox := mocks.NewMockBoxWidget(t)
258256
259- mockFactory.EXPECT().NewOverlay().Return(mockOverlay).Once()
260- mockOverlay.EXPECT().SetHexpand(true).Once()
261- mockOverlay.EXPECT().SetVexpand(true).Once()
262- mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBorderBox).Once()
263- mockBorderBox.EXPECT().SetCanFocus(false).Once()
264- mockBorderBox.EXPECT().AddCssClass("pane-border").Once()
265- mockBorderBox.EXPECT().SetHexpand(true).Once()
266- mockBorderBox.EXPECT().SetVexpand(true).Once()
267- mockOverlay.EXPECT().AddOverlay(mockBorderBox).Once()
268- mockOverlay.EXPECT().SetClipOverlay(mockBorderBox, false).Once()
269- mockOverlay.EXPECT().SetMeasureOverlay(mockBorderBox, false).Once()
257+ setupPaneViewMocksNoWebView(mockFactory, mockOverlay, mockBorderBox)
270258
271259 pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), nil)
272260
@@ -303,17 +291,7 @@ func TestHasFocus_NilWebView_ReturnsFalse(t *testing.T) {
303291 mockOverlay := mocks.NewMockOverlayWidget(t)
304292 mockBorderBox := mocks.NewMockBoxWidget(t)
305293
306- mockFactory.EXPECT().NewOverlay().Return(mockOverlay).Once()
307- mockOverlay.EXPECT().SetHexpand(true).Once()
308- mockOverlay.EXPECT().SetVexpand(true).Once()
309- mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBorderBox).Once()
310- mockBorderBox.EXPECT().SetCanFocus(false).Once()
311- mockBorderBox.EXPECT().AddCssClass("pane-border").Once()
312- mockBorderBox.EXPECT().SetHexpand(true).Once()
313- mockBorderBox.EXPECT().SetVexpand(true).Once()
314- mockOverlay.EXPECT().AddOverlay(mockBorderBox).Once()
315- mockOverlay.EXPECT().SetClipOverlay(mockBorderBox, false).Once()
316- mockOverlay.EXPECT().SetMeasureOverlay(mockBorderBox, false).Once()
294+ setupPaneViewMocksNoWebView(mockFactory, mockOverlay, mockBorderBox)
317295
318296 pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), nil)
319297
@@ -525,10 +503,34 @@ func setupPaneViewMocks(
525503 mockFactory.EXPECT().NewOverlay().Return(mockOverlay).Once()
526504 mockOverlay.EXPECT().SetHexpand(true).Once()
527505 mockOverlay.EXPECT().SetVexpand(true).Once()
506+ mockOverlay.EXPECT().SetVisible(true).Once()
507+ mockWebView.EXPECT().SetVisible(true).Once()
528508 mockOverlay.EXPECT().SetChild(mockWebView).Once()
529509
530510 mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBorderBox).Once()
531511 mockBorderBox.EXPECT().SetCanFocus(false).Once()
512+ mockBorderBox.EXPECT().SetCanTarget(false).Once()
513+ mockBorderBox.EXPECT().AddCssClass("pane-border").Once()
514+ mockBorderBox.EXPECT().SetHexpand(true).Once()
515+ mockBorderBox.EXPECT().SetVexpand(true).Once()
516+ mockOverlay.EXPECT().AddOverlay(mockBorderBox).Once()
517+ mockOverlay.EXPECT().SetClipOverlay(mockBorderBox, false).Once()
518+ mockOverlay.EXPECT().SetMeasureOverlay(mockBorderBox, false).Once()
519+}
520+
521+func setupPaneViewMocksNoWebView(
522+ mockFactory *mocks.MockWidgetFactory,
523+ mockOverlay *mocks.MockOverlayWidget,
524+ mockBorderBox *mocks.MockBoxWidget,
525+) {
526+ mockFactory.EXPECT().NewOverlay().Return(mockOverlay).Once()
527+ mockOverlay.EXPECT().SetHexpand(true).Once()
528+ mockOverlay.EXPECT().SetVexpand(true).Once()
529+ mockOverlay.EXPECT().SetVisible(true).Once()
530+
531+ mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBorderBox).Once()
532+ mockBorderBox.EXPECT().SetCanFocus(false).Once()
533+ mockBorderBox.EXPECT().SetCanTarget(false).Once()
532534 mockBorderBox.EXPECT().AddCssClass("pane-border").Once()
533535 mockBorderBox.EXPECT().SetHexpand(true).Once()
534536 mockBorderBox.EXPECT().SetVexpand(true).Once()
@@ -24,10 +24,13 @@ func TestNewPaneView_CreatesOverlay(t *testing.T) {
24 mockFactory.EXPECT().NewOverlay().Return(mockOverlay).Once()24 mockFactory.EXPECT().NewOverlay().Return(mockOverlay).Once()
25 mockOverlay.EXPECT().SetHexpand(true).Once()25 mockOverlay.EXPECT().SetHexpand(true).Once()
26 mockOverlay.EXPECT().SetVexpand(true).Once()26 mockOverlay.EXPECT().SetVexpand(true).Once()
27+ mockOverlay.EXPECT().SetVisible(true).Once()
28+ mockWebView.EXPECT().SetVisible(true).Once()
27 mockOverlay.EXPECT().SetChild(mockWebView).Once()29 mockOverlay.EXPECT().SetChild(mockWebView).Once()
28 30
29 mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBorderBox).Once()31 mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBorderBox).Once()
30 mockBorderBox.EXPECT().SetCanFocus(false).Once()32 mockBorderBox.EXPECT().SetCanFocus(false).Once()
33+ mockBorderBox.EXPECT().SetCanTarget(false).Once()
31 mockBorderBox.EXPECT().AddCssClass("pane-border").Once()34 mockBorderBox.EXPECT().AddCssClass("pane-border").Once()
32 mockBorderBox.EXPECT().SetHexpand(true).Once()35 mockBorderBox.EXPECT().SetHexpand(true).Once()
33 mockBorderBox.EXPECT().SetVexpand(true).Once()36 mockBorderBox.EXPECT().SetVexpand(true).Once()
@@ -55,10 +58,12 @@ func TestNewPaneView_NilWebView(t *testing.T) {
55 mockFactory.EXPECT().NewOverlay().Return(mockOverlay).Once()58 mockFactory.EXPECT().NewOverlay().Return(mockOverlay).Once()
56 mockOverlay.EXPECT().SetHexpand(true).Once()59 mockOverlay.EXPECT().SetHexpand(true).Once()
57 mockOverlay.EXPECT().SetVexpand(true).Once()60 mockOverlay.EXPECT().SetVexpand(true).Once()
61+ mockOverlay.EXPECT().SetVisible(true).Once()
58 // SetChild should NOT be called when webview is nil62 // SetChild should NOT be called when webview is nil
59 63
60 mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBorderBox).Once()64 mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBorderBox).Once()
61 mockBorderBox.EXPECT().SetCanFocus(false).Once()65 mockBorderBox.EXPECT().SetCanFocus(false).Once()
66+ mockBorderBox.EXPECT().SetCanTarget(false).Once()
62 mockBorderBox.EXPECT().AddCssClass("pane-border").Once()67 mockBorderBox.EXPECT().AddCssClass("pane-border").Once()
63 mockBorderBox.EXPECT().SetHexpand(true).Once()68 mockBorderBox.EXPECT().SetHexpand(true).Once()
64 mockBorderBox.EXPECT().SetVexpand(true).Once()69 mockBorderBox.EXPECT().SetVexpand(true).Once()
@@ -86,7 +91,7 @@ func TestSetActive_True_AddsCSSClass(t *testing.T) {
86 pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), mockWebView)91 pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), mockWebView)
87 92
88 // Expect CSS class to be added93 // Expect CSS class to be added
89- mockBorderBox.EXPECT().AddCssClass("active-pane").Once()94+ mockBorderBox.EXPECT().AddCssClass("pane-active").Once()
90 95
91 // Act96 // Act
92 pv.SetActive(true)97 pv.SetActive(true)
@@ -107,11 +112,11 @@ func TestSetActive_False_RemovesCSSClass(t *testing.T) {
107 pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), mockWebView)112 pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), mockWebView)
108 113
109 // First activate114 // First activate
110- mockBorderBox.EXPECT().AddCssClass("active-pane").Once()115+ mockBorderBox.EXPECT().AddCssClass("pane-active").Once()
111 pv.SetActive(true)116 pv.SetActive(true)
112 117
113 // Then deactivate118 // Then deactivate
114- mockBorderBox.EXPECT().RemoveCssClass("active-pane").Once()119+ mockBorderBox.EXPECT().RemoveCssClass("pane-active").Once()
115 120
116 // Act121 // Act
117 pv.SetActive(false)122 pv.SetActive(false)
@@ -189,6 +194,8 @@ func TestSetWebViewWidget_ReplacesWidget(t *testing.T) {
189 194
190 // Expect removal of old widget and addition of new195 // Expect removal of old widget and addition of new
191 mockOverlay.EXPECT().SetChild(nil).Once()196 mockOverlay.EXPECT().SetChild(nil).Once()
197+ mockNewWebView.EXPECT().GetParent().Return(nil).Once()
198+ mockNewWebView.EXPECT().SetVisible(true).Once()
192 mockOverlay.EXPECT().SetChild(mockNewWebView).Once()199 mockOverlay.EXPECT().SetChild(mockNewWebView).Once()
193 200
194 // Act201 // Act
@@ -205,22 +212,13 @@ func TestSetWebViewWidget_FromNil(t *testing.T) {
205 mockBorderBox := mocks.NewMockBoxWidget(t)212 mockBorderBox := mocks.NewMockBoxWidget(t)
206 mockNewWebView := mocks.NewMockWidget(t)213 mockNewWebView := mocks.NewMockWidget(t)
207 214
208- // Setup without webview215+ setupPaneViewMocksNoWebView(mockFactory, mockOverlay, mockBorderBox)
209- mockFactory.EXPECT().NewOverlay().Return(mockOverlay).Once()
210- mockOverlay.EXPECT().SetHexpand(true).Once()
211- mockOverlay.EXPECT().SetVexpand(true).Once()
212- mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBorderBox).Once()
213- mockBorderBox.EXPECT().SetCanFocus(false).Once()
214- mockBorderBox.EXPECT().AddCssClass("pane-border").Once()
215- mockBorderBox.EXPECT().SetHexpand(true).Once()
216- mockBorderBox.EXPECT().SetVexpand(true).Once()
217- mockOverlay.EXPECT().AddOverlay(mockBorderBox).Once()
218- mockOverlay.EXPECT().SetClipOverlay(mockBorderBox, false).Once()
219- mockOverlay.EXPECT().SetMeasureOverlay(mockBorderBox, false).Once()
220 216
221 pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), nil)217 pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), nil)
222 218
223 // Expect only setting new child (no removal since old was nil)219 // Expect only setting new child (no removal since old was nil)
220+ mockNewWebView.EXPECT().GetParent().Return(nil).Once()
221+ mockNewWebView.EXPECT().SetVisible(true).Once()
224 mockOverlay.EXPECT().SetChild(mockNewWebView).Once()222 mockOverlay.EXPECT().SetChild(mockNewWebView).Once()
225 223
226 // Act224 // Act
@@ -256,17 +254,7 @@ func TestGrabFocus_NilWebView_ReturnsFalse(t *testing.T) {
256 mockOverlay := mocks.NewMockOverlayWidget(t)254 mockOverlay := mocks.NewMockOverlayWidget(t)
257 mockBorderBox := mocks.NewMockBoxWidget(t)255 mockBorderBox := mocks.NewMockBoxWidget(t)
258 256
259- mockFactory.EXPECT().NewOverlay().Return(mockOverlay).Once()257+ setupPaneViewMocksNoWebView(mockFactory, mockOverlay, mockBorderBox)
260- mockOverlay.EXPECT().SetHexpand(true).Once()
261- mockOverlay.EXPECT().SetVexpand(true).Once()
262- mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBorderBox).Once()
263- mockBorderBox.EXPECT().SetCanFocus(false).Once()
264- mockBorderBox.EXPECT().AddCssClass("pane-border").Once()
265- mockBorderBox.EXPECT().SetHexpand(true).Once()
266- mockBorderBox.EXPECT().SetVexpand(true).Once()
267- mockOverlay.EXPECT().AddOverlay(mockBorderBox).Once()
268- mockOverlay.EXPECT().SetClipOverlay(mockBorderBox, false).Once()
269- mockOverlay.EXPECT().SetMeasureOverlay(mockBorderBox, false).Once()
270 258
271 pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), nil)259 pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), nil)
272 260
@@ -303,17 +291,7 @@ func TestHasFocus_NilWebView_ReturnsFalse(t *testing.T) {
303 mockOverlay := mocks.NewMockOverlayWidget(t)291 mockOverlay := mocks.NewMockOverlayWidget(t)
304 mockBorderBox := mocks.NewMockBoxWidget(t)292 mockBorderBox := mocks.NewMockBoxWidget(t)
305 293
306- mockFactory.EXPECT().NewOverlay().Return(mockOverlay).Once()294+ setupPaneViewMocksNoWebView(mockFactory, mockOverlay, mockBorderBox)
307- mockOverlay.EXPECT().SetHexpand(true).Once()
308- mockOverlay.EXPECT().SetVexpand(true).Once()
309- mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBorderBox).Once()
310- mockBorderBox.EXPECT().SetCanFocus(false).Once()
311- mockBorderBox.EXPECT().AddCssClass("pane-border").Once()
312- mockBorderBox.EXPECT().SetHexpand(true).Once()
313- mockBorderBox.EXPECT().SetVexpand(true).Once()
314- mockOverlay.EXPECT().AddOverlay(mockBorderBox).Once()
315- mockOverlay.EXPECT().SetClipOverlay(mockBorderBox, false).Once()
316- mockOverlay.EXPECT().SetMeasureOverlay(mockBorderBox, false).Once()
317 295
318 pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), nil)296 pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), nil)
319 297
@@ -525,10 +503,34 @@ func setupPaneViewMocks(
525 mockFactory.EXPECT().NewOverlay().Return(mockOverlay).Once()503 mockFactory.EXPECT().NewOverlay().Return(mockOverlay).Once()
526 mockOverlay.EXPECT().SetHexpand(true).Once()504 mockOverlay.EXPECT().SetHexpand(true).Once()
527 mockOverlay.EXPECT().SetVexpand(true).Once()505 mockOverlay.EXPECT().SetVexpand(true).Once()
506+ mockOverlay.EXPECT().SetVisible(true).Once()
507+ mockWebView.EXPECT().SetVisible(true).Once()
528 mockOverlay.EXPECT().SetChild(mockWebView).Once()508 mockOverlay.EXPECT().SetChild(mockWebView).Once()
529 509
530 mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBorderBox).Once()510 mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBorderBox).Once()
531 mockBorderBox.EXPECT().SetCanFocus(false).Once()511 mockBorderBox.EXPECT().SetCanFocus(false).Once()
512+ mockBorderBox.EXPECT().SetCanTarget(false).Once()
513+ mockBorderBox.EXPECT().AddCssClass("pane-border").Once()
514+ mockBorderBox.EXPECT().SetHexpand(true).Once()
515+ mockBorderBox.EXPECT().SetVexpand(true).Once()
516+ mockOverlay.EXPECT().AddOverlay(mockBorderBox).Once()
517+ mockOverlay.EXPECT().SetClipOverlay(mockBorderBox, false).Once()
518+ mockOverlay.EXPECT().SetMeasureOverlay(mockBorderBox, false).Once()
519+}
520+
521+func setupPaneViewMocksNoWebView(
522+ mockFactory *mocks.MockWidgetFactory,
523+ mockOverlay *mocks.MockOverlayWidget,
524+ mockBorderBox *mocks.MockBoxWidget,
525+) {
526+ mockFactory.EXPECT().NewOverlay().Return(mockOverlay).Once()
527+ mockOverlay.EXPECT().SetHexpand(true).Once()
528+ mockOverlay.EXPECT().SetVexpand(true).Once()
529+ mockOverlay.EXPECT().SetVisible(true).Once()
530+
531+ mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBorderBox).Once()
532+ mockBorderBox.EXPECT().SetCanFocus(false).Once()
533+ mockBorderBox.EXPECT().SetCanTarget(false).Once()
532 mockBorderBox.EXPECT().AddCssClass("pane-border").Once()534 mockBorderBox.EXPECT().AddCssClass("pane-border").Once()
533 mockBorderBox.EXPECT().SetHexpand(true).Once()535 mockBorderBox.EXPECT().SetHexpand(true).Once()
534 mockBorderBox.EXPECT().SetVexpand(true).Once()536 mockBorderBox.EXPECT().SetVexpand(true).Once()
M internal/ui/component/workspace_view_test.go +119 -509
@@ -1,9 +1,9 @@
11 package component_test
22
33 import (
4+ "context"
45 "testing"
56
6- "github.com/rs/zerolog"
77 "github.com/stretchr/testify/assert"
88 "github.com/stretchr/testify/mock"
99 "github.com/stretchr/testify/require"
@@ -14,65 +14,37 @@ import (
1414 "github.com/bnema/dumber/internal/ui/layout/mocks"
1515 )
1616
17-func TestNewWorkspaceView_CreatesEmptyContainer(t *testing.T) {
18- // Arrange
19- mockFactory := mocks.NewMockWidgetFactory(t)
20- mockBox := mocks.NewMockBoxWidget(t)
21-
22- mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBox).Once()
23- mockBox.EXPECT().SetHexpand(true).Once()
24- mockBox.EXPECT().SetVexpand(true).Once()
25-
26- // Act
27- wv := component.NewWorkspaceView(mockFactory, zerolog.Nop())
28-
29- // Assert
30- require.NotNil(t, wv)
31- assert.Equal(t, 0, wv.PaneCount())
32- assert.Equal(t, mockBox, wv.Container())
33-}
34-
35-func TestSetWorkspace_NilWorkspace_ReturnsError(t *testing.T) {
36- // Arrange
37- mockFactory := mocks.NewMockWidgetFactory(t)
38- mockBox := mocks.NewMockBoxWidget(t)
39-
40- mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBox).Once()
41- mockBox.EXPECT().SetHexpand(true).Once()
42- mockBox.EXPECT().SetVexpand(true).Once()
17+func setupWorkspaceViewBase(t *testing.T, mockFactory *mocks.MockWidgetFactory) (context.Context, *mocks.MockBoxWidget, *mocks.MockOverlayWidget) {
18+ ctx := context.Background()
19+ mockContainer := mocks.NewMockBoxWidget(t)
20+ mockOverlay := mocks.NewMockOverlayWidget(t)
4321
44- wv := component.NewWorkspaceView(mockFactory, zerolog.Nop())
22+ mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockContainer).Once()
23+ mockContainer.EXPECT().SetHexpand(true).Once()
24+ mockContainer.EXPECT().SetVexpand(true).Once()
25+ mockContainer.EXPECT().SetVisible(true).Once()
4526
46- // Act
47- err := wv.SetWorkspace(nil)
27+ mockFactory.EXPECT().NewOverlay().Return(mockOverlay).Once()
28+ mockOverlay.EXPECT().SetHexpand(true).Once()
29+ mockOverlay.EXPECT().SetVexpand(true).Once()
30+ mockOverlay.EXPECT().SetChild(mockContainer).Once()
31+ mockOverlay.EXPECT().SetVisible(true).Once()
4832
49- // Assert
50- assert.ErrorIs(t, err, component.ErrNilWorkspace)
33+ return ctx, mockContainer, mockOverlay
5134 }
5235
53-func TestSetWorkspace_SinglePane_CreatesPaneView(t *testing.T) {
54- // Arrange
55- mockFactory := mocks.NewMockWidgetFactory(t)
56- mockBox := mocks.NewMockBoxWidget(t)
57-
58- // Container creation
59- mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBox).Once()
60- mockBox.EXPECT().SetHexpand(true).Once()
61- mockBox.EXPECT().SetVexpand(true).Once()
62-
63- wv := component.NewWorkspaceView(mockFactory, zerolog.Nop())
64-
65- // PaneView creation (via paneViewFactoryAdapter)
36+func setupWorkspacePaneViewMocks(t *testing.T, mockFactory *mocks.MockWidgetFactory) (*mocks.MockOverlayWidget, *mocks.MockBoxWidget) {
6637 mockOverlay := mocks.NewMockOverlayWidget(t)
6738 mockBorderBox := mocks.NewMockBoxWidget(t)
6839
6940 mockFactory.EXPECT().NewOverlay().Return(mockOverlay).Once()
7041 mockOverlay.EXPECT().SetHexpand(true).Once()
7142 mockOverlay.EXPECT().SetVexpand(true).Once()
72- // No SetChild since WebView is nil
43+ mockOverlay.EXPECT().SetVisible(true).Once()
7344
7445 mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBorderBox).Once()
7546 mockBorderBox.EXPECT().SetCanFocus(false).Once()
47+ mockBorderBox.EXPECT().SetCanTarget(false).Once()
7648 mockBorderBox.EXPECT().AddCssClass("pane-border").Once()
7749 mockBorderBox.EXPECT().SetHexpand(true).Once()
7850 mockBorderBox.EXPECT().SetVexpand(true).Once()
@@ -80,268 +52,156 @@ func TestSetWorkspace_SinglePane_CreatesPaneView(t *testing.T) {
8052 mockOverlay.EXPECT().SetClipOverlay(mockBorderBox, false).Once()
8153 mockOverlay.EXPECT().SetMeasureOverlay(mockBorderBox, false).Once()
8254
83- // Widget appended to container
84- mockBox.EXPECT().Append(mockOverlay).Once()
85-
86- // Active state set
87- mockBorderBox.EXPECT().AddCssClass("active-pane").Once()
88-
89- pane := entity.NewPane(entity.PaneID("pane-1"))
90- node := &entity.PaneNode{ID: "node-1", Pane: pane}
91-
92- ws := &entity.Workspace{
93- ID: "ws-1",
94- Root: node,
95- ActivePaneID: pane.ID,
96- }
97-
98- // Act
99- err := wv.SetWorkspace(ws)
100-
101- // Assert
102- require.NoError(t, err)
103- assert.Equal(t, 1, wv.PaneCount())
104- assert.Equal(t, pane.ID, wv.GetActivePaneID())
105- assert.NotNil(t, wv.GetPaneView(pane.ID))
55+ return mockOverlay, mockBorderBox
10656 }
10757
108-func TestSetActivePaneID_UpdatesStyling(t *testing.T) {
109- // Arrange
110- mockFactory := mocks.NewMockWidgetFactory(t)
111- mockBox := mocks.NewMockBoxWidget(t)
58+func setupStackedLeafMocks(
59+ t *testing.T,
60+ mockFactory *mocks.MockWidgetFactory,
61+ container *mocks.MockOverlayWidget,
62+) (*mocks.MockBoxWidget, *mocks.MockBoxWidget) {
63+ mockStackBox := mocks.NewMockBoxWidget(t)
64+ mockTitleBar := mocks.NewMockBoxWidget(t)
65+ mockFavicon := mocks.NewMockImageWidget(t)
66+ mockLabel := mocks.NewMockLabelWidget(t)
67+ mockButton := mocks.NewMockButtonWidget(t)
11268
113- mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBox).Once()
114- mockBox.EXPECT().SetHexpand(true).Once()
115- mockBox.EXPECT().SetVexpand(true).Once()
69+ mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockStackBox).Once()
70+ mockStackBox.EXPECT().SetHexpand(true).Once()
71+ mockStackBox.EXPECT().SetVexpand(true).Once()
11672
117- wv := component.NewWorkspaceView(mockFactory, zerolog.Nop())
73+ mockFactory.EXPECT().NewBox(layout.OrientationHorizontal, 4).Return(mockTitleBar).Once()
74+ mockTitleBar.EXPECT().AddCssClass("stacked-pane-titlebar").Once()
75+ mockTitleBar.EXPECT().SetVexpand(false).Once()
11876
119- // Setup two panes
120- pane1 := entity.NewPane(entity.PaneID("pane-1"))
121- pane2 := entity.NewPane(entity.PaneID("pane-2"))
122- node1 := &entity.PaneNode{ID: "node-1", Pane: pane1}
123- node2 := &entity.PaneNode{ID: "node-2", Pane: pane2}
124-
125- // Create mock widgets for split
126- mockPaned := mocks.NewMockPanedWidget(t)
127- mockOverlay1 := mocks.NewMockOverlayWidget(t)
128- mockOverlay2 := mocks.NewMockOverlayWidget(t)
129- mockBorderBox1 := mocks.NewMockBoxWidget(t)
130- mockBorderBox2 := mocks.NewMockBoxWidget(t)
131-
132- // First pane view creation
133- mockFactory.EXPECT().NewOverlay().Return(mockOverlay1).Once()
134- mockOverlay1.EXPECT().SetHexpand(true).Once()
135- mockOverlay1.EXPECT().SetVexpand(true).Once()
136- mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBorderBox1).Once()
137- mockBorderBox1.EXPECT().SetCanFocus(false).Once()
138- mockBorderBox1.EXPECT().AddCssClass("pane-border").Once()
139- mockBorderBox1.EXPECT().SetHexpand(true).Once()
140- mockBorderBox1.EXPECT().SetVexpand(true).Once()
141- mockOverlay1.EXPECT().AddOverlay(mockBorderBox1).Once()
142- mockOverlay1.EXPECT().SetClipOverlay(mockBorderBox1, false).Once()
143- mockOverlay1.EXPECT().SetMeasureOverlay(mockBorderBox1, false).Once()
144-
145- // Second pane view creation
146- mockFactory.EXPECT().NewOverlay().Return(mockOverlay2).Once()
147- mockOverlay2.EXPECT().SetHexpand(true).Once()
148- mockOverlay2.EXPECT().SetVexpand(true).Once()
149- mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBorderBox2).Once()
150- mockBorderBox2.EXPECT().SetCanFocus(false).Once()
151- mockBorderBox2.EXPECT().AddCssClass("pane-border").Once()
152- mockBorderBox2.EXPECT().SetHexpand(true).Once()
153- mockBorderBox2.EXPECT().SetVexpand(true).Once()
154- mockOverlay2.EXPECT().AddOverlay(mockBorderBox2).Once()
155- mockOverlay2.EXPECT().SetClipOverlay(mockBorderBox2, false).Once()
156- mockOverlay2.EXPECT().SetMeasureOverlay(mockBorderBox2, false).Once()
77+ mockFactory.EXPECT().NewImage().Return(mockFavicon).Once()
78+ mockFavicon.EXPECT().SetFromIconName(mock.Anything).Once()
79+ mockFavicon.EXPECT().SetPixelSize(16).Once()
80+ mockTitleBar.EXPECT().Append(mockFavicon).Once()
15781
158- // Split view creation
159- mockFactory.EXPECT().NewPaned(layout.OrientationHorizontal).Return(mockPaned).Once()
160- mockPaned.EXPECT().SetResizeStartChild(true).Once()
161- mockPaned.EXPECT().SetResizeEndChild(true).Once()
162- mockPaned.EXPECT().SetShrinkStartChild(false).Once()
163- mockPaned.EXPECT().SetShrinkEndChild(false).Once()
164- mockPaned.EXPECT().SetStartChild(mockOverlay1).Once()
165- mockPaned.EXPECT().SetEndChild(mockOverlay2).Once()
166- mockPaned.EXPECT().GetAllocatedWidth().Return(0).Once()
167- mockPaned.EXPECT().ConnectMap(mock.Anything).Return(uint32(0)).Once()
168- mockPaned.EXPECT().AddTickCallback(mock.Anything).Return(uint(0)).Once()
82+ mockFactory.EXPECT().NewLabel(mock.Anything).Return(mockLabel).Once()
83+ mockLabel.EXPECT().SetEllipsize(layout.EllipsizeEnd).Once()
84+ mockLabel.EXPECT().SetMaxWidthChars(30).Once()
85+ mockLabel.EXPECT().SetHexpand(true).Once()
86+ mockLabel.EXPECT().SetXalign(float32(0.0)).Once()
87+ mockTitleBar.EXPECT().Append(mockLabel).Once()
16988
170- mockBox.EXPECT().Append(mockPaned).Once()
89+ mockFactory.EXPECT().NewButton().Return(mockButton).Once()
90+ mockButton.EXPECT().SetChild(mockTitleBar).Once()
91+ mockButton.EXPECT().AddCssClass("stacked-pane-title-button").Once()
92+ mockButton.EXPECT().SetFocusOnClick(false).Once()
93+ mockButton.EXPECT().SetVexpand(false).Once()
94+ mockButton.EXPECT().SetHexpand(true).Once()
95+ mockButton.EXPECT().ConnectClicked(mock.Anything).Return(uint32(1)).Once()
17196
172- // Initial active pane is pane-1
173- mockBorderBox1.EXPECT().AddCssClass("active-pane").Once()
97+ mockStackBox.EXPECT().Append(mockButton).Once()
98+ mockStackBox.EXPECT().Append(container).Once()
17499
175- splitNode := &entity.PaneNode{
176- ID: "split",
177- SplitDir: entity.SplitHorizontal,
178- SplitRatio: 0.5,
179- Children: []*entity.PaneNode{node1, node2},
180- }
100+ mockTitleBar.EXPECT().GetParent().Return(nil).Maybe()
101+ container.EXPECT().SetVisible(true).Once()
102+ mockTitleBar.EXPECT().AddCssClass("active").Once()
181103
182- ws := &entity.Workspace{
183- ID: "ws-1",
184- Root: splitNode,
185- ActivePaneID: pane1.ID,
186- }
187-
188- err := wv.SetWorkspace(ws)
189- require.NoError(t, err)
104+ return mockStackBox, mockTitleBar
105+}
190106
191- // Now change active pane
192- mockBorderBox1.EXPECT().RemoveCssClass("active-pane").Once()
193- mockBorderBox2.EXPECT().AddCssClass("active-pane").Once()
107+func TestNewWorkspaceView_CreatesEmptyContainer(t *testing.T) {
108+ // Arrange
109+ mockFactory := mocks.NewMockWidgetFactory(t)
110+ ctx, mockBox, _ := setupWorkspaceViewBase(t, mockFactory)
194111
195112 // Act
196- err = wv.SetActivePaneID(pane2.ID)
113+ wv := component.NewWorkspaceView(ctx, mockFactory)
197114
198115 // Assert
199- require.NoError(t, err)
200- assert.Equal(t, pane2.ID, wv.GetActivePaneID())
116+ require.NotNil(t, wv)
117+ assert.Equal(t, 0, wv.PaneCount())
118+ assert.Equal(t, mockBox, wv.Container())
201119 }
202120
203-func TestSetActivePaneID_InvalidID_ReturnsError(t *testing.T) {
121+func TestSetWorkspace_NilWorkspace_ReturnsError(t *testing.T) {
204122 // Arrange
205123 mockFactory := mocks.NewMockWidgetFactory(t)
206- mockBox := mocks.NewMockBoxWidget(t)
124+ ctx, _, _ := setupWorkspaceViewBase(t, mockFactory)
207125
208- mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBox).Once()
209- mockBox.EXPECT().SetHexpand(true).Once()
210- mockBox.EXPECT().SetVexpand(true).Once()
211-
212- wv := component.NewWorkspaceView(mockFactory, zerolog.Nop())
126+ wv := component.NewWorkspaceView(ctx, mockFactory)
213127
214128 // Act
215- err := wv.SetActivePaneID(entity.PaneID("non-existent"))
129+ err := wv.SetWorkspace(ctx, nil)
216130
217131 // Assert
218- assert.ErrorIs(t, err, component.ErrPaneNotFound)
132+ assert.ErrorIs(t, err, component.ErrNilWorkspace)
219133 }
220134
221-func TestGetPaneView_Existing(t *testing.T) {
135+func TestSetWorkspace_SinglePane_CreatesPaneView(t *testing.T) {
222136 // Arrange
223137 mockFactory := mocks.NewMockWidgetFactory(t)
224- mockBox := mocks.NewMockBoxWidget(t)
225- mockOverlay := mocks.NewMockOverlayWidget(t)
226- mockBorderBox := mocks.NewMockBoxWidget(t)
138+ ctx, mockBox, _ := setupWorkspaceViewBase(t, mockFactory)
227139
228- mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBox).Once()
229- mockBox.EXPECT().SetHexpand(true).Once()
230- mockBox.EXPECT().SetVexpand(true).Once()
140+ wv := component.NewWorkspaceView(ctx, mockFactory)
231141
232- wv := component.NewWorkspaceView(mockFactory, zerolog.Nop())
142+ mockOverlay, mockBorderBox := setupWorkspacePaneViewMocks(t, mockFactory)
143+ mockStackBox, _ := setupStackedLeafMocks(t, mockFactory, mockOverlay)
233144
234- // PaneView creation
235- mockFactory.EXPECT().NewOverlay().Return(mockOverlay).Once()
236- mockOverlay.EXPECT().SetHexpand(true).Once()
237- mockOverlay.EXPECT().SetVexpand(true).Once()
238- mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBorderBox).Once()
239- mockBorderBox.EXPECT().SetCanFocus(false).Once()
240- mockBorderBox.EXPECT().AddCssClass("pane-border").Once()
241- mockBorderBox.EXPECT().SetHexpand(true).Once()
242- mockBorderBox.EXPECT().SetVexpand(true).Once()
243- mockOverlay.EXPECT().AddOverlay(mockBorderBox).Once()
244- mockOverlay.EXPECT().SetClipOverlay(mockBorderBox, false).Once()
245- mockOverlay.EXPECT().SetMeasureOverlay(mockBorderBox, false).Once()
246- mockBox.EXPECT().Append(mockOverlay).Once()
247- mockBorderBox.EXPECT().AddCssClass("active-pane").Once()
145+ mockStackBox.EXPECT().SetVisible(true).Once()
146+ mockBox.EXPECT().Append(mockStackBox).Once()
147+ mockBorderBox.EXPECT().AddCssClass("pane-active").Once()
248148
249149 pane := entity.NewPane(entity.PaneID("pane-1"))
250150 node := &entity.PaneNode{ID: "node-1", Pane: pane}
151+
251152 ws := &entity.Workspace{
252153 ID: "ws-1",
253154 Root: node,
254155 ActivePaneID: pane.ID,
255156 }
256157
257- _ = wv.SetWorkspace(ws)
258-
259158 // Act
260- result := wv.GetPaneView(pane.ID)
159+ err := wv.SetWorkspace(ctx, ws)
261160
262161 // Assert
263- assert.NotNil(t, result)
162+ require.NoError(t, err)
163+ assert.Equal(t, 1, wv.PaneCount())
164+ assert.Equal(t, pane.ID, wv.GetActivePaneID())
165+ assert.NotNil(t, wv.GetPaneView(pane.ID))
264166 }
265167
266-func TestGetPaneView_NonExistent(t *testing.T) {
168+func TestSetActivePaneID_UpdatesStyling(t *testing.T) {
267169 // Arrange
268170 mockFactory := mocks.NewMockWidgetFactory(t)
269- mockBox := mocks.NewMockBoxWidget(t)
270-
271- mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBox).Once()
272- mockBox.EXPECT().SetHexpand(true).Once()
273- mockBox.EXPECT().SetVexpand(true).Once()
171+ ctx, mockBox, _ := setupWorkspaceViewBase(t, mockFactory)
274172
275- wv := component.NewWorkspaceView(mockFactory, zerolog.Nop())
173+ wv := component.NewWorkspaceView(ctx, mockFactory)
276174
277- // Act
278- result := wv.GetPaneView(entity.PaneID("non-existent"))
279-
280- // Assert
281- assert.Nil(t, result)
282-}
175+ // Setup two panes
176+ pane1 := entity.NewPane(entity.PaneID("pane-1"))
177+ pane2 := entity.NewPane(entity.PaneID("pane-2"))
178+ node1 := &entity.PaneNode{ID: "node-1", Pane: pane1}
179+ node2 := &entity.PaneNode{ID: "node-2", Pane: pane2}
283180
284-func TestGetPaneIDs_ReturnsAllIDs(t *testing.T) {
285- // Arrange
286- mockFactory := mocks.NewMockWidgetFactory(t)
287- mockBox := mocks.NewMockBoxWidget(t)
181+ // Create mock widgets for split
288182 mockPaned := mocks.NewMockPanedWidget(t)
289- mockOverlay1 := mocks.NewMockOverlayWidget(t)
290- mockOverlay2 := mocks.NewMockOverlayWidget(t)
291- mockBorderBox1 := mocks.NewMockBoxWidget(t)
292- mockBorderBox2 := mocks.NewMockBoxWidget(t)
293-
294- mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBox).Once()
295- mockBox.EXPECT().SetHexpand(true).Once()
296- mockBox.EXPECT().SetVexpand(true).Once()
297-
298- wv := component.NewWorkspaceView(mockFactory, zerolog.Nop())
299-
300- // First pane view
301- mockFactory.EXPECT().NewOverlay().Return(mockOverlay1).Once()
302- mockOverlay1.EXPECT().SetHexpand(true).Once()
303- mockOverlay1.EXPECT().SetVexpand(true).Once()
304- mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBorderBox1).Once()
305- mockBorderBox1.EXPECT().SetCanFocus(false).Once()
306- mockBorderBox1.EXPECT().AddCssClass("pane-border").Once()
307- mockBorderBox1.EXPECT().SetHexpand(true).Once()
308- mockBorderBox1.EXPECT().SetVexpand(true).Once()
309- mockOverlay1.EXPECT().AddOverlay(mockBorderBox1).Once()
310- mockOverlay1.EXPECT().SetClipOverlay(mockBorderBox1, false).Once()
311- mockOverlay1.EXPECT().SetMeasureOverlay(mockBorderBox1, false).Once()
312-
313- // Second pane view
314- mockFactory.EXPECT().NewOverlay().Return(mockOverlay2).Once()
315- mockOverlay2.EXPECT().SetHexpand(true).Once()
316- mockOverlay2.EXPECT().SetVexpand(true).Once()
317- mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBorderBox2).Once()
318- mockBorderBox2.EXPECT().SetCanFocus(false).Once()
319- mockBorderBox2.EXPECT().AddCssClass("pane-border").Once()
320- mockBorderBox2.EXPECT().SetHexpand(true).Once()
321- mockBorderBox2.EXPECT().SetVexpand(true).Once()
322- mockOverlay2.EXPECT().AddOverlay(mockBorderBox2).Once()
323- mockOverlay2.EXPECT().SetClipOverlay(mockBorderBox2, false).Once()
324- mockOverlay2.EXPECT().SetMeasureOverlay(mockBorderBox2, false).Once()
325-
326- // Split view
183+ mockOverlay1, mockBorderBox1 := setupWorkspacePaneViewMocks(t, mockFactory)
184+ mockStackBox1, _ := setupStackedLeafMocks(t, mockFactory, mockOverlay1)
185+ mockOverlay2, mockBorderBox2 := setupWorkspacePaneViewMocks(t, mockFactory)
186+ mockStackBox2, _ := setupStackedLeafMocks(t, mockFactory, mockOverlay2)
187+
188+ // Split view creation
327189 mockFactory.EXPECT().NewPaned(layout.OrientationHorizontal).Return(mockPaned).Once()
328190 mockPaned.EXPECT().SetResizeStartChild(true).Once()
329191 mockPaned.EXPECT().SetResizeEndChild(true).Once()
330- mockPaned.EXPECT().SetShrinkStartChild(false).Once()
331- mockPaned.EXPECT().SetShrinkEndChild(false).Once()
332- mockPaned.EXPECT().SetStartChild(mockOverlay1).Once()
333- mockPaned.EXPECT().SetEndChild(mockOverlay2).Once()
192+ mockPaned.EXPECT().SetVisible(true).Twice()
193+ mockStackBox1.EXPECT().SetVisible(true).Once()
194+ mockStackBox2.EXPECT().SetVisible(true).Once()
195+ mockPaned.EXPECT().SetStartChild(mockStackBox1).Once()
196+ mockPaned.EXPECT().SetEndChild(mockStackBox2).Once()
334197 mockPaned.EXPECT().GetAllocatedWidth().Return(0).Once()
335198 mockPaned.EXPECT().ConnectMap(mock.Anything).Return(uint32(0)).Once()
336199 mockPaned.EXPECT().AddTickCallback(mock.Anything).Return(uint(0)).Once()
337200
338201 mockBox.EXPECT().Append(mockPaned).Once()
339- mockBorderBox1.EXPECT().AddCssClass("active-pane").Once()
340202
341- pane1 := entity.NewPane(entity.PaneID("pane-1"))
342- pane2 := entity.NewPane(entity.PaneID("pane-2"))
343- node1 := &entity.PaneNode{ID: "node-1", Pane: pane1}
344- node2 := &entity.PaneNode{ID: "node-2", Pane: pane2}
203+ // Initial active pane is pane-1
204+ mockBorderBox1.EXPECT().AddCssClass("pane-active").Once()
345205
346206 splitNode := &entity.PaneNode{
347207 ID: "split",
@@ -356,281 +216,31 @@ func TestGetPaneIDs_ReturnsAllIDs(t *testing.T) {
356216 ActivePaneID: pane1.ID,
357217 }
358218
359- _ = wv.SetWorkspace(ws)
360-
361- // Act
362- ids := wv.GetPaneIDs()
363-
364- // Assert
365- assert.Len(t, ids, 2)
366- assert.Contains(t, ids, pane1.ID)
367- assert.Contains(t, ids, pane2.ID)
368-}
369-
370-func TestWidget_ReturnsContainer(t *testing.T) {
371- // Arrange
372- mockFactory := mocks.NewMockWidgetFactory(t)
373- mockBox := mocks.NewMockBoxWidget(t)
374-
375- mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBox).Once()
376- mockBox.EXPECT().SetHexpand(true).Once()
377- mockBox.EXPECT().SetVexpand(true).Once()
378-
379- wv := component.NewWorkspaceView(mockFactory, zerolog.Nop())
380-
381- // Act
382- widget := wv.Widget()
383-
384- // Assert
385- assert.Equal(t, mockBox, widget)
386-}
387-
388-func TestSetOnPaneFocused_SetsCallback(t *testing.T) {
389- // Arrange
390- mockFactory := mocks.NewMockWidgetFactory(t)
391- mockBox := mocks.NewMockBoxWidget(t)
392-
393- mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBox).Once()
394- mockBox.EXPECT().SetHexpand(true).Once()
395- mockBox.EXPECT().SetVexpand(true).Once()
396-
397- wv := component.NewWorkspaceView(mockFactory, zerolog.Nop())
398-
399- callback := func(paneID entity.PaneID) {}
400-
401- // Act
402- wv.SetOnPaneFocused(callback)
403-
404- // Assert - callback is set (verified by no panic)
405- assert.NotNil(t, wv)
406-}
407-
408-func TestSetWebViewWidget_AttachesToPane(t *testing.T) {
409- // Arrange
410- mockFactory := mocks.NewMockWidgetFactory(t)
411- mockBox := mocks.NewMockBoxWidget(t)
412- mockOverlay := mocks.NewMockOverlayWidget(t)
413- mockBorderBox := mocks.NewMockBoxWidget(t)
414- mockWebView := mocks.NewMockWidget(t)
415-
416- mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBox).Once()
417- mockBox.EXPECT().SetHexpand(true).Once()
418- mockBox.EXPECT().SetVexpand(true).Once()
419-
420- wv := component.NewWorkspaceView(mockFactory, zerolog.Nop())
421-
422- // PaneView creation
423- mockFactory.EXPECT().NewOverlay().Return(mockOverlay).Once()
424- mockOverlay.EXPECT().SetHexpand(true).Once()
425- mockOverlay.EXPECT().SetVexpand(true).Once()
426- mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBorderBox).Once()
427- mockBorderBox.EXPECT().SetCanFocus(false).Once()
428- mockBorderBox.EXPECT().AddCssClass("pane-border").Once()
429- mockBorderBox.EXPECT().SetHexpand(true).Once()
430- mockBorderBox.EXPECT().SetVexpand(true).Once()
431- mockOverlay.EXPECT().AddOverlay(mockBorderBox).Once()
432- mockOverlay.EXPECT().SetClipOverlay(mockBorderBox, false).Once()
433- mockOverlay.EXPECT().SetMeasureOverlay(mockBorderBox, false).Once()
434- mockBox.EXPECT().Append(mockOverlay).Once()
435- mockBorderBox.EXPECT().AddCssClass("active-pane").Once()
436-
437- pane := entity.NewPane(entity.PaneID("pane-1"))
438- node := &entity.PaneNode{ID: "node-1", Pane: pane}
439- ws := &entity.Workspace{
440- ID: "ws-1",
441- Root: node,
442- ActivePaneID: pane.ID,
443- }
444-
445- _ = wv.SetWorkspace(ws)
219+ err := wv.SetWorkspace(ctx, ws)
220+ require.NoError(t, err)
446221
447- // Expect WebView to be set
448- mockOverlay.EXPECT().SetChild(mockWebView).Once()
222+ // Now change active pane
223+ mockBorderBox1.EXPECT().RemoveCssClass("pane-active").Once()
224+ mockBorderBox2.EXPECT().AddCssClass("pane-active").Once()
449225
450226 // Act
451- err := wv.SetWebViewWidget(pane.ID, mockWebView)
227+ err = wv.SetActivePaneID(pane2.ID)
452228
453229 // Assert
454230 require.NoError(t, err)
231+ assert.Equal(t, pane2.ID, wv.GetActivePaneID())
455232 }
456233
457-func TestSetWebViewWidget_NonExistentPane_ReturnsError(t *testing.T) {
234+func TestSetActivePaneID_InvalidID_ReturnsError(t *testing.T) {
458235 // Arrange
459236 mockFactory := mocks.NewMockWidgetFactory(t)
460- mockBox := mocks.NewMockBoxWidget(t)
461- mockWebView := mocks.NewMockWidget(t)
462-
463- mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBox).Once()
464- mockBox.EXPECT().SetHexpand(true).Once()
465- mockBox.EXPECT().SetVexpand(true).Once()
237+ ctx, _, _ := setupWorkspaceViewBase(t, mockFactory)
466238
467- wv := component.NewWorkspaceView(mockFactory, zerolog.Nop())
239+ wv := component.NewWorkspaceView(ctx, mockFactory)
468240
469241 // Act
470- err := wv.SetWebViewWidget(entity.PaneID("non-existent"), mockWebView)
242+ err := wv.SetActivePaneID(entity.PaneID("non-existent"))
471243
472244 // Assert
473245 assert.ErrorIs(t, err, component.ErrPaneNotFound)
474246 }
475-
476-func TestFocusPane_DelegatesToPaneView(t *testing.T) {
477- // Arrange
478- mockFactory := mocks.NewMockWidgetFactory(t)
479- mockBox := mocks.NewMockBoxWidget(t)
480- mockOverlay := mocks.NewMockOverlayWidget(t)
481- mockBorderBox := mocks.NewMockBoxWidget(t)
482-
483- mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBox).Once()
484- mockBox.EXPECT().SetHexpand(true).Once()
485- mockBox.EXPECT().SetVexpand(true).Once()
486-
487- wv := component.NewWorkspaceView(mockFactory, zerolog.Nop())
488-
489- // PaneView creation
490- mockFactory.EXPECT().NewOverlay().Return(mockOverlay).Once()
491- mockOverlay.EXPECT().SetHexpand(true).Once()
492- mockOverlay.EXPECT().SetVexpand(true).Once()
493- mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBorderBox).Once()
494- mockBorderBox.EXPECT().SetCanFocus(false).Once()
495- mockBorderBox.EXPECT().AddCssClass("pane-border").Once()
496- mockBorderBox.EXPECT().SetHexpand(true).Once()
497- mockBorderBox.EXPECT().SetVexpand(true).Once()
498- mockOverlay.EXPECT().AddOverlay(mockBorderBox).Once()
499- mockOverlay.EXPECT().SetClipOverlay(mockBorderBox, false).Once()
500- mockOverlay.EXPECT().SetMeasureOverlay(mockBorderBox, false).Once()
501- mockBox.EXPECT().Append(mockOverlay).Once()
502- mockBorderBox.EXPECT().AddCssClass("active-pane").Once()
503-
504- pane := entity.NewPane(entity.PaneID("pane-1"))
505- node := &entity.PaneNode{ID: "node-1", Pane: pane}
506- ws := &entity.Workspace{
507- ID: "ws-1",
508- Root: node,
509- ActivePaneID: pane.ID,
510- }
511-
512- _ = wv.SetWorkspace(ws)
513-
514- // Act - FocusPane with nil WebView returns false
515- result := wv.FocusPane(pane.ID)
516-
517- // Assert
518- assert.False(t, result) // No WebView attached, so GrabFocus fails
519-}
520-
521-func TestFocusPane_NonExistentPane_ReturnsFalse(t *testing.T) {
522- // Arrange
523- mockFactory := mocks.NewMockWidgetFactory(t)
524- mockBox := mocks.NewMockBoxWidget(t)
525-
526- mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBox).Once()
527- mockBox.EXPECT().SetHexpand(true).Once()
528- mockBox.EXPECT().SetVexpand(true).Once()
529-
530- wv := component.NewWorkspaceView(mockFactory, zerolog.Nop())
531-
532- // Act
533- result := wv.FocusPane(entity.PaneID("non-existent"))
534-
535- // Assert
536- assert.False(t, result)
537-}
538-
539-func TestRebuild_NilWorkspace_ReturnsError(t *testing.T) {
540- // Arrange
541- mockFactory := mocks.NewMockWidgetFactory(t)
542- mockBox := mocks.NewMockBoxWidget(t)
543-
544- mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBox).Once()
545- mockBox.EXPECT().SetHexpand(true).Once()
546- mockBox.EXPECT().SetVexpand(true).Once()
547-
548- wv := component.NewWorkspaceView(mockFactory, zerolog.Nop())
549-
550- // Act
551- err := wv.Rebuild()
552-
553- // Assert
554- assert.ErrorIs(t, err, component.ErrNilWorkspace)
555-}
556-
557-func TestTreeRenderer_ReturnsRenderer(t *testing.T) {
558- // Arrange
559- mockFactory := mocks.NewMockWidgetFactory(t)
560- mockBox := mocks.NewMockBoxWidget(t)
561-
562- mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBox).Once()
563- mockBox.EXPECT().SetHexpand(true).Once()
564- mockBox.EXPECT().SetVexpand(true).Once()
565-
566- wv := component.NewWorkspaceView(mockFactory, zerolog.Nop())
567-
568- // Act
569- renderer := wv.TreeRenderer()
570-
571- // Assert
572- assert.NotNil(t, renderer)
573-}
574-
575-func TestWorkspace_ReturnsCurrentWorkspace(t *testing.T) {
576- // Arrange
577- mockFactory := mocks.NewMockWidgetFactory(t)
578- mockBox := mocks.NewMockBoxWidget(t)
579- mockOverlay := mocks.NewMockOverlayWidget(t)
580- mockBorderBox := mocks.NewMockBoxWidget(t)
581-
582- mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBox).Once()
583- mockBox.EXPECT().SetHexpand(true).Once()
584- mockBox.EXPECT().SetVexpand(true).Once()
585-
586- wv := component.NewWorkspaceView(mockFactory, zerolog.Nop())
587-
588- // PaneView creation
589- mockFactory.EXPECT().NewOverlay().Return(mockOverlay).Once()
590- mockOverlay.EXPECT().SetHexpand(true).Once()
591- mockOverlay.EXPECT().SetVexpand(true).Once()
592- mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBorderBox).Once()
593- mockBorderBox.EXPECT().SetCanFocus(false).Once()
594- mockBorderBox.EXPECT().AddCssClass("pane-border").Once()
595- mockBorderBox.EXPECT().SetHexpand(true).Once()
596- mockBorderBox.EXPECT().SetVexpand(true).Once()
597- mockOverlay.EXPECT().AddOverlay(mockBorderBox).Once()
598- mockOverlay.EXPECT().SetClipOverlay(mockBorderBox, false).Once()
599- mockOverlay.EXPECT().SetMeasureOverlay(mockBorderBox, false).Once()
600- mockBox.EXPECT().Append(mockOverlay).Once()
601- mockBorderBox.EXPECT().AddCssClass("active-pane").Once()
602-
603- pane := entity.NewPane(entity.PaneID("pane-1"))
604- node := &entity.PaneNode{ID: "node-1", Pane: pane}
605- ws := &entity.Workspace{
606- ID: "ws-1",
607- Root: node,
608- ActivePaneID: pane.ID,
609- }
610-
611- _ = wv.SetWorkspace(ws)
612-
613- // Act
614- result := wv.Workspace()
615-
616- // Assert
617- assert.Equal(t, ws, result)
618-}
619-
620-func TestWorkspace_NilBeforeSet(t *testing.T) {
621- // Arrange
622- mockFactory := mocks.NewMockWidgetFactory(t)
623- mockBox := mocks.NewMockBoxWidget(t)
624-
625- mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBox).Once()
626- mockBox.EXPECT().SetHexpand(true).Once()
627- mockBox.EXPECT().SetVexpand(true).Once()
628-
629- wv := component.NewWorkspaceView(mockFactory, zerolog.Nop())
630-
631- // Act
632- result := wv.Workspace()
633-
634- // Assert
635- assert.Nil(t, result)
636-}
@@ -1,9 +1,9 @@
1 package component_test1 package component_test
2 2
3 import (3 import (
4+ "context"
4 "testing"5 "testing"
5 6
6- "github.com/rs/zerolog"
7 "github.com/stretchr/testify/assert"7 "github.com/stretchr/testify/assert"
8 "github.com/stretchr/testify/mock"8 "github.com/stretchr/testify/mock"
9 "github.com/stretchr/testify/require"9 "github.com/stretchr/testify/require"
@@ -14,65 +14,37 @@ import (
14 "github.com/bnema/dumber/internal/ui/layout/mocks"14 "github.com/bnema/dumber/internal/ui/layout/mocks"
15 )15 )
16 16
17-func TestNewWorkspaceView_CreatesEmptyContainer(t *testing.T) {17+func setupWorkspaceViewBase(t *testing.T, mockFactory *mocks.MockWidgetFactory) (context.Context, *mocks.MockBoxWidget, *mocks.MockOverlayWidget) {
18- // Arrange18+ ctx := context.Background()
19- mockFactory := mocks.NewMockWidgetFactory(t)19+ mockContainer := mocks.NewMockBoxWidget(t)
20- mockBox := mocks.NewMockBoxWidget(t)20+ mockOverlay := mocks.NewMockOverlayWidget(t)
21-
22- mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBox).Once()
23- mockBox.EXPECT().SetHexpand(true).Once()
24- mockBox.EXPECT().SetVexpand(true).Once()
25-
26- // Act
27- wv := component.NewWorkspaceView(mockFactory, zerolog.Nop())
28-
29- // Assert
30- require.NotNil(t, wv)
31- assert.Equal(t, 0, wv.PaneCount())
32- assert.Equal(t, mockBox, wv.Container())
33-}
34-
35-func TestSetWorkspace_NilWorkspace_ReturnsError(t *testing.T) {
36- // Arrange
37- mockFactory := mocks.NewMockWidgetFactory(t)
38- mockBox := mocks.NewMockBoxWidget(t)
39-
40- mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBox).Once()
41- mockBox.EXPECT().SetHexpand(true).Once()
42- mockBox.EXPECT().SetVexpand(true).Once()
43 21
44- wv := component.NewWorkspaceView(mockFactory, zerolog.Nop())22+ mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockContainer).Once()
23+ mockContainer.EXPECT().SetHexpand(true).Once()
24+ mockContainer.EXPECT().SetVexpand(true).Once()
25+ mockContainer.EXPECT().SetVisible(true).Once()
45 26
46- // Act27+ mockFactory.EXPECT().NewOverlay().Return(mockOverlay).Once()
47- err := wv.SetWorkspace(nil)28+ mockOverlay.EXPECT().SetHexpand(true).Once()
29+ mockOverlay.EXPECT().SetVexpand(true).Once()
30+ mockOverlay.EXPECT().SetChild(mockContainer).Once()
31+ mockOverlay.EXPECT().SetVisible(true).Once()
48 32
49- // Assert33+ return ctx, mockContainer, mockOverlay
50- assert.ErrorIs(t, err, component.ErrNilWorkspace)
51 }34 }
52 35
53-func TestSetWorkspace_SinglePane_CreatesPaneView(t *testing.T) {36+func setupWorkspacePaneViewMocks(t *testing.T, mockFactory *mocks.MockWidgetFactory) (*mocks.MockOverlayWidget, *mocks.MockBoxWidget) {
54- // Arrange
55- mockFactory := mocks.NewMockWidgetFactory(t)
56- mockBox := mocks.NewMockBoxWidget(t)
57-
58- // Container creation
59- mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBox).Once()
60- mockBox.EXPECT().SetHexpand(true).Once()
61- mockBox.EXPECT().SetVexpand(true).Once()
62-
63- wv := component.NewWorkspaceView(mockFactory, zerolog.Nop())
64-
65- // PaneView creation (via paneViewFactoryAdapter)
66 mockOverlay := mocks.NewMockOverlayWidget(t)37 mockOverlay := mocks.NewMockOverlayWidget(t)
67 mockBorderBox := mocks.NewMockBoxWidget(t)38 mockBorderBox := mocks.NewMockBoxWidget(t)
68 39
69 mockFactory.EXPECT().NewOverlay().Return(mockOverlay).Once()40 mockFactory.EXPECT().NewOverlay().Return(mockOverlay).Once()
70 mockOverlay.EXPECT().SetHexpand(true).Once()41 mockOverlay.EXPECT().SetHexpand(true).Once()
71 mockOverlay.EXPECT().SetVexpand(true).Once()42 mockOverlay.EXPECT().SetVexpand(true).Once()
72- // No SetChild since WebView is nil43+ mockOverlay.EXPECT().SetVisible(true).Once()
73 44
74 mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBorderBox).Once()45 mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBorderBox).Once()
75 mockBorderBox.EXPECT().SetCanFocus(false).Once()46 mockBorderBox.EXPECT().SetCanFocus(false).Once()
47+ mockBorderBox.EXPECT().SetCanTarget(false).Once()
76 mockBorderBox.EXPECT().AddCssClass("pane-border").Once()48 mockBorderBox.EXPECT().AddCssClass("pane-border").Once()
77 mockBorderBox.EXPECT().SetHexpand(true).Once()49 mockBorderBox.EXPECT().SetHexpand(true).Once()
78 mockBorderBox.EXPECT().SetVexpand(true).Once()50 mockBorderBox.EXPECT().SetVexpand(true).Once()
@@ -80,268 +52,156 @@ func TestSetWorkspace_SinglePane_CreatesPaneView(t *testing.T) {
80 mockOverlay.EXPECT().SetClipOverlay(mockBorderBox, false).Once()52 mockOverlay.EXPECT().SetClipOverlay(mockBorderBox, false).Once()
81 mockOverlay.EXPECT().SetMeasureOverlay(mockBorderBox, false).Once()53 mockOverlay.EXPECT().SetMeasureOverlay(mockBorderBox, false).Once()
82 54
83- // Widget appended to container55+ return mockOverlay, mockBorderBox
84- mockBox.EXPECT().Append(mockOverlay).Once()
85-
86- // Active state set
87- mockBorderBox.EXPECT().AddCssClass("active-pane").Once()
88-
89- pane := entity.NewPane(entity.PaneID("pane-1"))
90- node := &entity.PaneNode{ID: "node-1", Pane: pane}
91-
92- ws := &entity.Workspace{
93- ID: "ws-1",
94- Root: node,
95- ActivePaneID: pane.ID,
96- }
97-
98- // Act
99- err := wv.SetWorkspace(ws)
100-
101- // Assert
102- require.NoError(t, err)
103- assert.Equal(t, 1, wv.PaneCount())
104- assert.Equal(t, pane.ID, wv.GetActivePaneID())
105- assert.NotNil(t, wv.GetPaneView(pane.ID))
106 }56 }
107 57
108-func TestSetActivePaneID_UpdatesStyling(t *testing.T) {58+func setupStackedLeafMocks(
109- // Arrange59+ t *testing.T,
110- mockFactory := mocks.NewMockWidgetFactory(t)60+ mockFactory *mocks.MockWidgetFactory,
111- mockBox := mocks.NewMockBoxWidget(t)61+ container *mocks.MockOverlayWidget,
62+) (*mocks.MockBoxWidget, *mocks.MockBoxWidget) {
63+ mockStackBox := mocks.NewMockBoxWidget(t)
64+ mockTitleBar := mocks.NewMockBoxWidget(t)
65+ mockFavicon := mocks.NewMockImageWidget(t)
66+ mockLabel := mocks.NewMockLabelWidget(t)
67+ mockButton := mocks.NewMockButtonWidget(t)
112 68
113- mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBox).Once()69+ mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockStackBox).Once()
114- mockBox.EXPECT().SetHexpand(true).Once()70+ mockStackBox.EXPECT().SetHexpand(true).Once()
115- mockBox.EXPECT().SetVexpand(true).Once()71+ mockStackBox.EXPECT().SetVexpand(true).Once()
116 72
117- wv := component.NewWorkspaceView(mockFactory, zerolog.Nop())73+ mockFactory.EXPECT().NewBox(layout.OrientationHorizontal, 4).Return(mockTitleBar).Once()
74+ mockTitleBar.EXPECT().AddCssClass("stacked-pane-titlebar").Once()
75+ mockTitleBar.EXPECT().SetVexpand(false).Once()
118 76
119- // Setup two panes77+ mockFactory.EXPECT().NewImage().Return(mockFavicon).Once()
120- pane1 := entity.NewPane(entity.PaneID("pane-1"))78+ mockFavicon.EXPECT().SetFromIconName(mock.Anything).Once()
121- pane2 := entity.NewPane(entity.PaneID("pane-2"))79+ mockFavicon.EXPECT().SetPixelSize(16).Once()
122- node1 := &entity.PaneNode{ID: "node-1", Pane: pane1}80+ mockTitleBar.EXPECT().Append(mockFavicon).Once()
123- node2 := &entity.PaneNode{ID: "node-2", Pane: pane2}
124-
125- // Create mock widgets for split
126- mockPaned := mocks.NewMockPanedWidget(t)
127- mockOverlay1 := mocks.NewMockOverlayWidget(t)
128- mockOverlay2 := mocks.NewMockOverlayWidget(t)
129- mockBorderBox1 := mocks.NewMockBoxWidget(t)
130- mockBorderBox2 := mocks.NewMockBoxWidget(t)
131-
132- // First pane view creation
133- mockFactory.EXPECT().NewOverlay().Return(mockOverlay1).Once()
134- mockOverlay1.EXPECT().SetHexpand(true).Once()
135- mockOverlay1.EXPECT().SetVexpand(true).Once()
136- mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBorderBox1).Once()
137- mockBorderBox1.EXPECT().SetCanFocus(false).Once()
138- mockBorderBox1.EXPECT().AddCssClass("pane-border").Once()
139- mockBorderBox1.EXPECT().SetHexpand(true).Once()
140- mockBorderBox1.EXPECT().SetVexpand(true).Once()
141- mockOverlay1.EXPECT().AddOverlay(mockBorderBox1).Once()
142- mockOverlay1.EXPECT().SetClipOverlay(mockBorderBox1, false).Once()
143- mockOverlay1.EXPECT().SetMeasureOverlay(mockBorderBox1, false).Once()
144-
145- // Second pane view creation
146- mockFactory.EXPECT().NewOverlay().Return(mockOverlay2).Once()
147- mockOverlay2.EXPECT().SetHexpand(true).Once()
148- mockOverlay2.EXPECT().SetVexpand(true).Once()
149- mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBorderBox2).Once()
150- mockBorderBox2.EXPECT().SetCanFocus(false).Once()
151- mockBorderBox2.EXPECT().AddCssClass("pane-border").Once()
152- mockBorderBox2.EXPECT().SetHexpand(true).Once()
153- mockBorderBox2.EXPECT().SetVexpand(true).Once()
154- mockOverlay2.EXPECT().AddOverlay(mockBorderBox2).Once()
155- mockOverlay2.EXPECT().SetClipOverlay(mockBorderBox2, false).Once()
156- mockOverlay2.EXPECT().SetMeasureOverlay(mockBorderBox2, false).Once()
157 81
158- // Split view creation82+ mockFactory.EXPECT().NewLabel(mock.Anything).Return(mockLabel).Once()
159- mockFactory.EXPECT().NewPaned(layout.OrientationHorizontal).Return(mockPaned).Once()83+ mockLabel.EXPECT().SetEllipsize(layout.EllipsizeEnd).Once()
160- mockPaned.EXPECT().SetResizeStartChild(true).Once()84+ mockLabel.EXPECT().SetMaxWidthChars(30).Once()
161- mockPaned.EXPECT().SetResizeEndChild(true).Once()85+ mockLabel.EXPECT().SetHexpand(true).Once()
162- mockPaned.EXPECT().SetShrinkStartChild(false).Once()86+ mockLabel.EXPECT().SetXalign(float32(0.0)).Once()
163- mockPaned.EXPECT().SetShrinkEndChild(false).Once()87+ mockTitleBar.EXPECT().Append(mockLabel).Once()
164- mockPaned.EXPECT().SetStartChild(mockOverlay1).Once()
165- mockPaned.EXPECT().SetEndChild(mockOverlay2).Once()
166- mockPaned.EXPECT().GetAllocatedWidth().Return(0).Once()
167- mockPaned.EXPECT().ConnectMap(mock.Anything).Return(uint32(0)).Once()
168- mockPaned.EXPECT().AddTickCallback(mock.Anything).Return(uint(0)).Once()
169 88
170- mockBox.EXPECT().Append(mockPaned).Once()89+ mockFactory.EXPECT().NewButton().Return(mockButton).Once()
90+ mockButton.EXPECT().SetChild(mockTitleBar).Once()
91+ mockButton.EXPECT().AddCssClass("stacked-pane-title-button").Once()
92+ mockButton.EXPECT().SetFocusOnClick(false).Once()
93+ mockButton.EXPECT().SetVexpand(false).Once()
94+ mockButton.EXPECT().SetHexpand(true).Once()
95+ mockButton.EXPECT().ConnectClicked(mock.Anything).Return(uint32(1)).Once()
171 96
172- // Initial active pane is pane-197+ mockStackBox.EXPECT().Append(mockButton).Once()
173- mockBorderBox1.EXPECT().AddCssClass("active-pane").Once()98+ mockStackBox.EXPECT().Append(container).Once()
174 99
175- splitNode := &entity.PaneNode{100+ mockTitleBar.EXPECT().GetParent().Return(nil).Maybe()
176- ID: "split",101+ container.EXPECT().SetVisible(true).Once()
177- SplitDir: entity.SplitHorizontal,102+ mockTitleBar.EXPECT().AddCssClass("active").Once()
178- SplitRatio: 0.5,
179- Children: []*entity.PaneNode{node1, node2},
180- }
181 103
182- ws := &entity.Workspace{104+ return mockStackBox, mockTitleBar
183- ID: "ws-1",105+}
184- Root: splitNode,
185- ActivePaneID: pane1.ID,
186- }
187-
188- err := wv.SetWorkspace(ws)
189- require.NoError(t, err)
190 106
191- // Now change active pane107+func TestNewWorkspaceView_CreatesEmptyContainer(t *testing.T) {
192- mockBorderBox1.EXPECT().RemoveCssClass("active-pane").Once()108+ // Arrange
193- mockBorderBox2.EXPECT().AddCssClass("active-pane").Once()109+ mockFactory := mocks.NewMockWidgetFactory(t)
110+ ctx, mockBox, _ := setupWorkspaceViewBase(t, mockFactory)
194 111
195 // Act112 // Act
196- err = wv.SetActivePaneID(pane2.ID)113+ wv := component.NewWorkspaceView(ctx, mockFactory)
197 114
198 // Assert115 // Assert
199- require.NoError(t, err)116+ require.NotNil(t, wv)
200- assert.Equal(t, pane2.ID, wv.GetActivePaneID())117+ assert.Equal(t, 0, wv.PaneCount())
118+ assert.Equal(t, mockBox, wv.Container())
201 }119 }
202 120
203-func TestSetActivePaneID_InvalidID_ReturnsError(t *testing.T) {121+func TestSetWorkspace_NilWorkspace_ReturnsError(t *testing.T) {
204 // Arrange122 // Arrange
205 mockFactory := mocks.NewMockWidgetFactory(t)123 mockFactory := mocks.NewMockWidgetFactory(t)
206- mockBox := mocks.NewMockBoxWidget(t)124+ ctx, _, _ := setupWorkspaceViewBase(t, mockFactory)
207 125
208- mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBox).Once()126+ wv := component.NewWorkspaceView(ctx, mockFactory)
209- mockBox.EXPECT().SetHexpand(true).Once()
210- mockBox.EXPECT().SetVexpand(true).Once()
211-
212- wv := component.NewWorkspaceView(mockFactory, zerolog.Nop())
213 127
214 // Act128 // Act
215- err := wv.SetActivePaneID(entity.PaneID("non-existent"))129+ err := wv.SetWorkspace(ctx, nil)
216 130
217 // Assert131 // Assert
218- assert.ErrorIs(t, err, component.ErrPaneNotFound)132+ assert.ErrorIs(t, err, component.ErrNilWorkspace)
219 }133 }
220 134
221-func TestGetPaneView_Existing(t *testing.T) {135+func TestSetWorkspace_SinglePane_CreatesPaneView(t *testing.T) {
222 // Arrange136 // Arrange
223 mockFactory := mocks.NewMockWidgetFactory(t)137 mockFactory := mocks.NewMockWidgetFactory(t)
224- mockBox := mocks.NewMockBoxWidget(t)138+ ctx, mockBox, _ := setupWorkspaceViewBase(t, mockFactory)
225- mockOverlay := mocks.NewMockOverlayWidget(t)
226- mockBorderBox := mocks.NewMockBoxWidget(t)
227 139
228- mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBox).Once()140+ wv := component.NewWorkspaceView(ctx, mockFactory)
229- mockBox.EXPECT().SetHexpand(true).Once()
230- mockBox.EXPECT().SetVexpand(true).Once()
231 141
232- wv := component.NewWorkspaceView(mockFactory, zerolog.Nop())142+ mockOverlay, mockBorderBox := setupWorkspacePaneViewMocks(t, mockFactory)
143+ mockStackBox, _ := setupStackedLeafMocks(t, mockFactory, mockOverlay)
233 144
234- // PaneView creation145+ mockStackBox.EXPECT().SetVisible(true).Once()
235- mockFactory.EXPECT().NewOverlay().Return(mockOverlay).Once()146+ mockBox.EXPECT().Append(mockStackBox).Once()
236- mockOverlay.EXPECT().SetHexpand(true).Once()147+ mockBorderBox.EXPECT().AddCssClass("pane-active").Once()
237- mockOverlay.EXPECT().SetVexpand(true).Once()
238- mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBorderBox).Once()
239- mockBorderBox.EXPECT().SetCanFocus(false).Once()
240- mockBorderBox.EXPECT().AddCssClass("pane-border").Once()
241- mockBorderBox.EXPECT().SetHexpand(true).Once()
242- mockBorderBox.EXPECT().SetVexpand(true).Once()
243- mockOverlay.EXPECT().AddOverlay(mockBorderBox).Once()
244- mockOverlay.EXPECT().SetClipOverlay(mockBorderBox, false).Once()
245- mockOverlay.EXPECT().SetMeasureOverlay(mockBorderBox, false).Once()
246- mockBox.EXPECT().Append(mockOverlay).Once()
247- mockBorderBox.EXPECT().AddCssClass("active-pane").Once()
248 148
249 pane := entity.NewPane(entity.PaneID("pane-1"))149 pane := entity.NewPane(entity.PaneID("pane-1"))
250 node := &entity.PaneNode{ID: "node-1", Pane: pane}150 node := &entity.PaneNode{ID: "node-1", Pane: pane}
151+
251 ws := &entity.Workspace{152 ws := &entity.Workspace{
252 ID: "ws-1",153 ID: "ws-1",
253 Root: node,154 Root: node,
254 ActivePaneID: pane.ID,155 ActivePaneID: pane.ID,
255 }156 }
256 157
257- _ = wv.SetWorkspace(ws)
258-
259 // Act158 // Act
260- result := wv.GetPaneView(pane.ID)159+ err := wv.SetWorkspace(ctx, ws)
261 160
262 // Assert161 // Assert
263- assert.NotNil(t, result)162+ require.NoError(t, err)
163+ assert.Equal(t, 1, wv.PaneCount())
164+ assert.Equal(t, pane.ID, wv.GetActivePaneID())
165+ assert.NotNil(t, wv.GetPaneView(pane.ID))
264 }166 }
265 167
266-func TestGetPaneView_NonExistent(t *testing.T) {168+func TestSetActivePaneID_UpdatesStyling(t *testing.T) {
267 // Arrange169 // Arrange
268 mockFactory := mocks.NewMockWidgetFactory(t)170 mockFactory := mocks.NewMockWidgetFactory(t)
269- mockBox := mocks.NewMockBoxWidget(t)171+ ctx, mockBox, _ := setupWorkspaceViewBase(t, mockFactory)
270-
271- mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBox).Once()
272- mockBox.EXPECT().SetHexpand(true).Once()
273- mockBox.EXPECT().SetVexpand(true).Once()
274 172
275- wv := component.NewWorkspaceView(mockFactory, zerolog.Nop())173+ wv := component.NewWorkspaceView(ctx, mockFactory)
276 174
277- // Act175+ // Setup two panes
278- result := wv.GetPaneView(entity.PaneID("non-existent"))176+ pane1 := entity.NewPane(entity.PaneID("pane-1"))
279-177+ pane2 := entity.NewPane(entity.PaneID("pane-2"))
280- // Assert178+ node1 := &entity.PaneNode{ID: "node-1", Pane: pane1}
281- assert.Nil(t, result)179+ node2 := &entity.PaneNode{ID: "node-2", Pane: pane2}
282-}
283 180
284-func TestGetPaneIDs_ReturnsAllIDs(t *testing.T) {181+ // Create mock widgets for split
285- // Arrange
286- mockFactory := mocks.NewMockWidgetFactory(t)
287- mockBox := mocks.NewMockBoxWidget(t)
288 mockPaned := mocks.NewMockPanedWidget(t)182 mockPaned := mocks.NewMockPanedWidget(t)
289- mockOverlay1 := mocks.NewMockOverlayWidget(t)183+ mockOverlay1, mockBorderBox1 := setupWorkspacePaneViewMocks(t, mockFactory)
290- mockOverlay2 := mocks.NewMockOverlayWidget(t)184+ mockStackBox1, _ := setupStackedLeafMocks(t, mockFactory, mockOverlay1)
291- mockBorderBox1 := mocks.NewMockBoxWidget(t)185+ mockOverlay2, mockBorderBox2 := setupWorkspacePaneViewMocks(t, mockFactory)
292- mockBorderBox2 := mocks.NewMockBoxWidget(t)186+ mockStackBox2, _ := setupStackedLeafMocks(t, mockFactory, mockOverlay2)
293-187+
294- mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBox).Once()188+ // Split view creation
295- mockBox.EXPECT().SetHexpand(true).Once()
296- mockBox.EXPECT().SetVexpand(true).Once()
297-
298- wv := component.NewWorkspaceView(mockFactory, zerolog.Nop())
299-
300- // First pane view
301- mockFactory.EXPECT().NewOverlay().Return(mockOverlay1).Once()
302- mockOverlay1.EXPECT().SetHexpand(true).Once()
303- mockOverlay1.EXPECT().SetVexpand(true).Once()
304- mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBorderBox1).Once()
305- mockBorderBox1.EXPECT().SetCanFocus(false).Once()
306- mockBorderBox1.EXPECT().AddCssClass("pane-border").Once()
307- mockBorderBox1.EXPECT().SetHexpand(true).Once()
308- mockBorderBox1.EXPECT().SetVexpand(true).Once()
309- mockOverlay1.EXPECT().AddOverlay(mockBorderBox1).Once()
310- mockOverlay1.EXPECT().SetClipOverlay(mockBorderBox1, false).Once()
311- mockOverlay1.EXPECT().SetMeasureOverlay(mockBorderBox1, false).Once()
312-
313- // Second pane view
314- mockFactory.EXPECT().NewOverlay().Return(mockOverlay2).Once()
315- mockOverlay2.EXPECT().SetHexpand(true).Once()
316- mockOverlay2.EXPECT().SetVexpand(true).Once()
317- mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBorderBox2).Once()
318- mockBorderBox2.EXPECT().SetCanFocus(false).Once()
319- mockBorderBox2.EXPECT().AddCssClass("pane-border").Once()
320- mockBorderBox2.EXPECT().SetHexpand(true).Once()
321- mockBorderBox2.EXPECT().SetVexpand(true).Once()
322- mockOverlay2.EXPECT().AddOverlay(mockBorderBox2).Once()
323- mockOverlay2.EXPECT().SetClipOverlay(mockBorderBox2, false).Once()
324- mockOverlay2.EXPECT().SetMeasureOverlay(mockBorderBox2, false).Once()
325-
326- // Split view
327 mockFactory.EXPECT().NewPaned(layout.OrientationHorizontal).Return(mockPaned).Once()189 mockFactory.EXPECT().NewPaned(layout.OrientationHorizontal).Return(mockPaned).Once()
328 mockPaned.EXPECT().SetResizeStartChild(true).Once()190 mockPaned.EXPECT().SetResizeStartChild(true).Once()
329 mockPaned.EXPECT().SetResizeEndChild(true).Once()191 mockPaned.EXPECT().SetResizeEndChild(true).Once()
330- mockPaned.EXPECT().SetShrinkStartChild(false).Once()192+ mockPaned.EXPECT().SetVisible(true).Twice()
331- mockPaned.EXPECT().SetShrinkEndChild(false).Once()193+ mockStackBox1.EXPECT().SetVisible(true).Once()
332- mockPaned.EXPECT().SetStartChild(mockOverlay1).Once()194+ mockStackBox2.EXPECT().SetVisible(true).Once()
333- mockPaned.EXPECT().SetEndChild(mockOverlay2).Once()195+ mockPaned.EXPECT().SetStartChild(mockStackBox1).Once()
196+ mockPaned.EXPECT().SetEndChild(mockStackBox2).Once()
334 mockPaned.EXPECT().GetAllocatedWidth().Return(0).Once()197 mockPaned.EXPECT().GetAllocatedWidth().Return(0).Once()
335 mockPaned.EXPECT().ConnectMap(mock.Anything).Return(uint32(0)).Once()198 mockPaned.EXPECT().ConnectMap(mock.Anything).Return(uint32(0)).Once()
336 mockPaned.EXPECT().AddTickCallback(mock.Anything).Return(uint(0)).Once()199 mockPaned.EXPECT().AddTickCallback(mock.Anything).Return(uint(0)).Once()
337 200
338 mockBox.EXPECT().Append(mockPaned).Once()201 mockBox.EXPECT().Append(mockPaned).Once()
339- mockBorderBox1.EXPECT().AddCssClass("active-pane").Once()
340 202
341- pane1 := entity.NewPane(entity.PaneID("pane-1"))203+ // Initial active pane is pane-1
342- pane2 := entity.NewPane(entity.PaneID("pane-2"))204+ mockBorderBox1.EXPECT().AddCssClass("pane-active").Once()
343- node1 := &entity.PaneNode{ID: "node-1", Pane: pane1}
344- node2 := &entity.PaneNode{ID: "node-2", Pane: pane2}
345 205
346 splitNode := &entity.PaneNode{206 splitNode := &entity.PaneNode{
347 ID: "split",207 ID: "split",
@@ -356,281 +216,31 @@ func TestGetPaneIDs_ReturnsAllIDs(t *testing.T) {
356 ActivePaneID: pane1.ID,216 ActivePaneID: pane1.ID,
357 }217 }
358 218
359- _ = wv.SetWorkspace(ws)219+ err := wv.SetWorkspace(ctx, ws)
360-220+ require.NoError(t, err)
361- // Act
362- ids := wv.GetPaneIDs()
363-
364- // Assert
365- assert.Len(t, ids, 2)
366- assert.Contains(t, ids, pane1.ID)
367- assert.Contains(t, ids, pane2.ID)
368-}
369-
370-func TestWidget_ReturnsContainer(t *testing.T) {
371- // Arrange
372- mockFactory := mocks.NewMockWidgetFactory(t)
373- mockBox := mocks.NewMockBoxWidget(t)
374-
375- mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBox).Once()
376- mockBox.EXPECT().SetHexpand(true).Once()
377- mockBox.EXPECT().SetVexpand(true).Once()
378-
379- wv := component.NewWorkspaceView(mockFactory, zerolog.Nop())
380-
381- // Act
382- widget := wv.Widget()
383-
384- // Assert
385- assert.Equal(t, mockBox, widget)
386-}
387-
388-func TestSetOnPaneFocused_SetsCallback(t *testing.T) {
389- // Arrange
390- mockFactory := mocks.NewMockWidgetFactory(t)
391- mockBox := mocks.NewMockBoxWidget(t)
392-
393- mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBox).Once()
394- mockBox.EXPECT().SetHexpand(true).Once()
395- mockBox.EXPECT().SetVexpand(true).Once()
396-
397- wv := component.NewWorkspaceView(mockFactory, zerolog.Nop())
398-
399- callback := func(paneID entity.PaneID) {}
400-
401- // Act
402- wv.SetOnPaneFocused(callback)
403-
404- // Assert - callback is set (verified by no panic)
405- assert.NotNil(t, wv)
406-}
407-
408-func TestSetWebViewWidget_AttachesToPane(t *testing.T) {
409- // Arrange
410- mockFactory := mocks.NewMockWidgetFactory(t)
411- mockBox := mocks.NewMockBoxWidget(t)
412- mockOverlay := mocks.NewMockOverlayWidget(t)
413- mockBorderBox := mocks.NewMockBoxWidget(t)
414- mockWebView := mocks.NewMockWidget(t)
415-
416- mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBox).Once()
417- mockBox.EXPECT().SetHexpand(true).Once()
418- mockBox.EXPECT().SetVexpand(true).Once()
419-
420- wv := component.NewWorkspaceView(mockFactory, zerolog.Nop())
421-
422- // PaneView creation
423- mockFactory.EXPECT().NewOverlay().Return(mockOverlay).Once()
424- mockOverlay.EXPECT().SetHexpand(true).Once()
425- mockOverlay.EXPECT().SetVexpand(true).Once()
426- mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBorderBox).Once()
427- mockBorderBox.EXPECT().SetCanFocus(false).Once()
428- mockBorderBox.EXPECT().AddCssClass("pane-border").Once()
429- mockBorderBox.EXPECT().SetHexpand(true).Once()
430- mockBorderBox.EXPECT().SetVexpand(true).Once()
431- mockOverlay.EXPECT().AddOverlay(mockBorderBox).Once()
432- mockOverlay.EXPECT().SetClipOverlay(mockBorderBox, false).Once()
433- mockOverlay.EXPECT().SetMeasureOverlay(mockBorderBox, false).Once()
434- mockBox.EXPECT().Append(mockOverlay).Once()
435- mockBorderBox.EXPECT().AddCssClass("active-pane").Once()
436-
437- pane := entity.NewPane(entity.PaneID("pane-1"))
438- node := &entity.PaneNode{ID: "node-1", Pane: pane}
439- ws := &entity.Workspace{
440- ID: "ws-1",
441- Root: node,
442- ActivePaneID: pane.ID,
443- }
444-
445- _ = wv.SetWorkspace(ws)
446 221
447- // Expect WebView to be set222+ // Now change active pane
448- mockOverlay.EXPECT().SetChild(mockWebView).Once()223+ mockBorderBox1.EXPECT().RemoveCssClass("pane-active").Once()
224+ mockBorderBox2.EXPECT().AddCssClass("pane-active").Once()
449 225
450 // Act226 // Act
451- err := wv.SetWebViewWidget(pane.ID, mockWebView)227+ err = wv.SetActivePaneID(pane2.ID)
452 228
453 // Assert229 // Assert
454 require.NoError(t, err)230 require.NoError(t, err)
231+ assert.Equal(t, pane2.ID, wv.GetActivePaneID())
455 }232 }
456 233
457-func TestSetWebViewWidget_NonExistentPane_ReturnsError(t *testing.T) {234+func TestSetActivePaneID_InvalidID_ReturnsError(t *testing.T) {
458 // Arrange235 // Arrange
459 mockFactory := mocks.NewMockWidgetFactory(t)236 mockFactory := mocks.NewMockWidgetFactory(t)
460- mockBox := mocks.NewMockBoxWidget(t)237+ ctx, _, _ := setupWorkspaceViewBase(t, mockFactory)
461- mockWebView := mocks.NewMockWidget(t)
462-
463- mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBox).Once()
464- mockBox.EXPECT().SetHexpand(true).Once()
465- mockBox.EXPECT().SetVexpand(true).Once()
466 238
467- wv := component.NewWorkspaceView(mockFactory, zerolog.Nop())239+ wv := component.NewWorkspaceView(ctx, mockFactory)
468 240
469 // Act241 // Act
470- err := wv.SetWebViewWidget(entity.PaneID("non-existent"), mockWebView)242+ err := wv.SetActivePaneID(entity.PaneID("non-existent"))
471 243
472 // Assert244 // Assert
473 assert.ErrorIs(t, err, component.ErrPaneNotFound)245 assert.ErrorIs(t, err, component.ErrPaneNotFound)
474 }246 }
475-
476-func TestFocusPane_DelegatesToPaneView(t *testing.T) {
477- // Arrange
478- mockFactory := mocks.NewMockWidgetFactory(t)
479- mockBox := mocks.NewMockBoxWidget(t)
480- mockOverlay := mocks.NewMockOverlayWidget(t)
481- mockBorderBox := mocks.NewMockBoxWidget(t)
482-
483- mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBox).Once()
484- mockBox.EXPECT().SetHexpand(true).Once()
485- mockBox.EXPECT().SetVexpand(true).Once()
486-
487- wv := component.NewWorkspaceView(mockFactory, zerolog.Nop())
488-
489- // PaneView creation
490- mockFactory.EXPECT().NewOverlay().Return(mockOverlay).Once()
491- mockOverlay.EXPECT().SetHexpand(true).Once()
492- mockOverlay.EXPECT().SetVexpand(true).Once()
493- mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBorderBox).Once()
494- mockBorderBox.EXPECT().SetCanFocus(false).Once()
495- mockBorderBox.EXPECT().AddCssClass("pane-border").Once()
496- mockBorderBox.EXPECT().SetHexpand(true).Once()
497- mockBorderBox.EXPECT().SetVexpand(true).Once()
498- mockOverlay.EXPECT().AddOverlay(mockBorderBox).Once()
499- mockOverlay.EXPECT().SetClipOverlay(mockBorderBox, false).Once()
500- mockOverlay.EXPECT().SetMeasureOverlay(mockBorderBox, false).Once()
501- mockBox.EXPECT().Append(mockOverlay).Once()
502- mockBorderBox.EXPECT().AddCssClass("active-pane").Once()
503-
504- pane := entity.NewPane(entity.PaneID("pane-1"))
505- node := &entity.PaneNode{ID: "node-1", Pane: pane}
506- ws := &entity.Workspace{
507- ID: "ws-1",
508- Root: node,
509- ActivePaneID: pane.ID,
510- }
511-
512- _ = wv.SetWorkspace(ws)
513-
514- // Act - FocusPane with nil WebView returns false
515- result := wv.FocusPane(pane.ID)
516-
517- // Assert
518- assert.False(t, result) // No WebView attached, so GrabFocus fails
519-}
520-
521-func TestFocusPane_NonExistentPane_ReturnsFalse(t *testing.T) {
522- // Arrange
523- mockFactory := mocks.NewMockWidgetFactory(t)
524- mockBox := mocks.NewMockBoxWidget(t)
525-
526- mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBox).Once()
527- mockBox.EXPECT().SetHexpand(true).Once()
528- mockBox.EXPECT().SetVexpand(true).Once()
529-
530- wv := component.NewWorkspaceView(mockFactory, zerolog.Nop())
531-
532- // Act
533- result := wv.FocusPane(entity.PaneID("non-existent"))
534-
535- // Assert
536- assert.False(t, result)
537-}
538-
539-func TestRebuild_NilWorkspace_ReturnsError(t *testing.T) {
540- // Arrange
541- mockFactory := mocks.NewMockWidgetFactory(t)
542- mockBox := mocks.NewMockBoxWidget(t)
543-
544- mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBox).Once()
545- mockBox.EXPECT().SetHexpand(true).Once()
546- mockBox.EXPECT().SetVexpand(true).Once()
547-
548- wv := component.NewWorkspaceView(mockFactory, zerolog.Nop())
549-
550- // Act
551- err := wv.Rebuild()
552-
553- // Assert
554- assert.ErrorIs(t, err, component.ErrNilWorkspace)
555-}
556-
557-func TestTreeRenderer_ReturnsRenderer(t *testing.T) {
558- // Arrange
559- mockFactory := mocks.NewMockWidgetFactory(t)
560- mockBox := mocks.NewMockBoxWidget(t)
561-
562- mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBox).Once()
563- mockBox.EXPECT().SetHexpand(true).Once()
564- mockBox.EXPECT().SetVexpand(true).Once()
565-
566- wv := component.NewWorkspaceView(mockFactory, zerolog.Nop())
567-
568- // Act
569- renderer := wv.TreeRenderer()
570-
571- // Assert
572- assert.NotNil(t, renderer)
573-}
574-
575-func TestWorkspace_ReturnsCurrentWorkspace(t *testing.T) {
576- // Arrange
577- mockFactory := mocks.NewMockWidgetFactory(t)
578- mockBox := mocks.NewMockBoxWidget(t)
579- mockOverlay := mocks.NewMockOverlayWidget(t)
580- mockBorderBox := mocks.NewMockBoxWidget(t)
581-
582- mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBox).Once()
583- mockBox.EXPECT().SetHexpand(true).Once()
584- mockBox.EXPECT().SetVexpand(true).Once()
585-
586- wv := component.NewWorkspaceView(mockFactory, zerolog.Nop())
587-
588- // PaneView creation
589- mockFactory.EXPECT().NewOverlay().Return(mockOverlay).Once()
590- mockOverlay.EXPECT().SetHexpand(true).Once()
591- mockOverlay.EXPECT().SetVexpand(true).Once()
592- mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBorderBox).Once()
593- mockBorderBox.EXPECT().SetCanFocus(false).Once()
594- mockBorderBox.EXPECT().AddCssClass("pane-border").Once()
595- mockBorderBox.EXPECT().SetHexpand(true).Once()
596- mockBorderBox.EXPECT().SetVexpand(true).Once()
597- mockOverlay.EXPECT().AddOverlay(mockBorderBox).Once()
598- mockOverlay.EXPECT().SetClipOverlay(mockBorderBox, false).Once()
599- mockOverlay.EXPECT().SetMeasureOverlay(mockBorderBox, false).Once()
600- mockBox.EXPECT().Append(mockOverlay).Once()
601- mockBorderBox.EXPECT().AddCssClass("active-pane").Once()
602-
603- pane := entity.NewPane(entity.PaneID("pane-1"))
604- node := &entity.PaneNode{ID: "node-1", Pane: pane}
605- ws := &entity.Workspace{
606- ID: "ws-1",
607- Root: node,
608- ActivePaneID: pane.ID,
609- }
610-
611- _ = wv.SetWorkspace(ws)
612-
613- // Act
614- result := wv.Workspace()
615-
616- // Assert
617- assert.Equal(t, ws, result)
618-}
619-
620-func TestWorkspace_NilBeforeSet(t *testing.T) {
621- // Arrange
622- mockFactory := mocks.NewMockWidgetFactory(t)
623- mockBox := mocks.NewMockBoxWidget(t)
624-
625- mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBox).Once()
626- mockBox.EXPECT().SetHexpand(true).Once()
627- mockBox.EXPECT().SetVexpand(true).Once()
628-
629- wv := component.NewWorkspaceView(mockFactory, zerolog.Nop())
630-
631- // Act
632- result := wv.Workspace()
633-
634- // Assert
635- assert.Nil(t, result)
636-}
M internal/ui/input/modal_test.go +46 -33
@@ -1,13 +1,15 @@
11 package input
22
33 import (
4+ "context"
45 "sync"
56 "testing"
67 "time"
78 )
89
910 func TestModalState_InitialState(t *testing.T) {
10- ms := NewModalState(nil)
11+ ctx := context.Background()
12+ ms := NewModalState(ctx)
1113
1214 if ms.Mode() != ModeNormal {
1315 t.Errorf("initial mode = %v, want ModeNormal", ms.Mode())
@@ -15,9 +17,10 @@ func TestModalState_InitialState(t *testing.T) {
1517 }
1618
1719 func TestModalState_EnterTabMode(t *testing.T) {
18- ms := NewModalState(nil)
20+ ctx := context.Background()
21+ ms := NewModalState(ctx)
1922
20- ms.EnterTabMode(0)
23+ ms.EnterTabMode(ctx, 0)
2124
2225 if ms.Mode() != ModeTab {
2326 t.Errorf("mode after EnterTabMode = %v, want ModeTab", ms.Mode())
@@ -25,9 +28,10 @@ func TestModalState_EnterTabMode(t *testing.T) {
2528 }
2629
2730 func TestModalState_EnterPaneMode(t *testing.T) {
28- ms := NewModalState(nil)
31+ ctx := context.Background()
32+ ms := NewModalState(ctx)
2933
30- ms.EnterPaneMode(0)
34+ ms.EnterPaneMode(ctx, 0)
3135
3236 if ms.Mode() != ModePane {
3337 t.Errorf("mode after EnterPaneMode = %v, want ModePane", ms.Mode())
@@ -35,26 +39,28 @@ func TestModalState_EnterPaneMode(t *testing.T) {
3539 }
3640
3741 func TestModalState_ExitMode(t *testing.T) {
38- ms := NewModalState(nil)
42+ ctx := context.Background()
43+ ms := NewModalState(ctx)
3944
4045 // Enter tab mode
41- ms.EnterTabMode(0)
46+ ms.EnterTabMode(ctx, 0)
4247 if ms.Mode() != ModeTab {
4348 t.Fatalf("mode should be ModeTab")
4449 }
4550
4651 // Exit mode
47- ms.ExitMode()
52+ ms.ExitMode(ctx)
4853 if ms.Mode() != ModeNormal {
4954 t.Errorf("mode after ExitMode = %v, want ModeNormal", ms.Mode())
5055 }
5156 }
5257
5358 func TestModalState_ExitMode_FromNormal(t *testing.T) {
54- ms := NewModalState(nil)
59+ ctx := context.Background()
60+ ms := NewModalState(ctx)
5561
5662 // ExitMode from normal should be no-op
57- ms.ExitMode()
63+ ms.ExitMode(ctx)
5864
5965 if ms.Mode() != ModeNormal {
6066 t.Errorf("mode = %v, want ModeNormal", ms.Mode())
@@ -62,29 +68,31 @@ func TestModalState_ExitMode_FromNormal(t *testing.T) {
6268 }
6369
6470 func TestModalState_SwitchModes(t *testing.T) {
65- ms := NewModalState(nil)
71+ ctx := context.Background()
72+ ms := NewModalState(ctx)
6673
6774 // Enter tab mode
68- ms.EnterTabMode(0)
75+ ms.EnterTabMode(ctx, 0)
6976 if ms.Mode() != ModeTab {
7077 t.Fatalf("expected ModeTab, got %v", ms.Mode())
7178 }
7279
7380 // Switch to pane mode directly
74- ms.EnterPaneMode(0)
81+ ms.EnterPaneMode(ctx, 0)
7582 if ms.Mode() != ModePane {
7683 t.Errorf("mode after switch to pane = %v, want ModePane", ms.Mode())
7784 }
7885
7986 // Switch back to tab mode
80- ms.EnterTabMode(0)
87+ ms.EnterTabMode(ctx, 0)
8188 if ms.Mode() != ModeTab {
8289 t.Errorf("mode after switch to tab = %v, want ModeTab", ms.Mode())
8390 }
8491 }
8592
8693 func TestModalState_ModeChangeCallback(t *testing.T) {
87- ms := NewModalState(nil)
94+ ctx := context.Background()
95+ ms := NewModalState(ctx)
8896
8997 var callCount int
9098 var lastFrom, lastTo Mode
@@ -99,7 +107,7 @@ func TestModalState_ModeChangeCallback(t *testing.T) {
99107 })
100108
101109 // Enter tab mode
102- ms.EnterTabMode(0)
110+ ms.EnterTabMode(ctx, 0)
103111
104112 mu.Lock()
105113 if callCount != 1 {
@@ -111,7 +119,7 @@ func TestModalState_ModeChangeCallback(t *testing.T) {
111119 mu.Unlock()
112120
113121 // Exit mode
114- ms.ExitMode()
122+ ms.ExitMode(ctx)
115123
116124 mu.Lock()
117125 if callCount != 2 {
@@ -124,7 +132,8 @@ func TestModalState_ModeChangeCallback(t *testing.T) {
124132 }
125133
126134 func TestModalState_NoCallbackWhenAlreadyInMode(t *testing.T) {
127- ms := NewModalState(nil)
135+ ctx := context.Background()
136+ ms := NewModalState(ctx)
128137
129138 var callCount int
130139 ms.SetOnModeChange(func(from, to Mode) {
@@ -132,23 +141,24 @@ func TestModalState_NoCallbackWhenAlreadyInMode(t *testing.T) {
132141 })
133142
134143 // Enter tab mode
135- ms.EnterTabMode(0)
144+ ms.EnterTabMode(ctx, 0)
136145 if callCount != 1 {
137146 t.Fatalf("expected 1 callback, got %d", callCount)
138147 }
139148
140149 // Enter tab mode again (should not trigger callback)
141- ms.EnterTabMode(0)
150+ ms.EnterTabMode(ctx, 0)
142151 if callCount != 1 {
143152 t.Errorf("callback count = %d, want 1 (no new call)", callCount)
144153 }
145154 }
146155
147156 func TestModalState_Timeout(t *testing.T) {
148- ms := NewModalState(nil)
157+ ctx := context.Background()
158+ ms := NewModalState(ctx)
149159
150160 // Enter with short timeout
151- ms.EnterTabMode(50 * time.Millisecond)
161+ ms.EnterTabMode(ctx, 50*time.Millisecond)
152162
153163 if ms.Mode() != ModeTab {
154164 t.Fatalf("mode should be ModeTab")
@@ -163,14 +173,15 @@ func TestModalState_Timeout(t *testing.T) {
163173 }
164174
165175 func TestModalState_TimeoutReset(t *testing.T) {
166- ms := NewModalState(nil)
176+ ctx := context.Background()
177+ ms := NewModalState(ctx)
167178
168179 // Enter with timeout
169- ms.EnterTabMode(100 * time.Millisecond)
180+ ms.EnterTabMode(ctx, 100*time.Millisecond)
170181
171182 // Wait a bit, then reset
172183 time.Sleep(50 * time.Millisecond)
173- ms.ResetTimeout()
184+ ms.ResetTimeout(ctx)
174185
175186 // Wait a bit more (original timeout would have expired)
176187 time.Sleep(80 * time.Millisecond)
@@ -189,7 +200,8 @@ func TestModalState_TimeoutReset(t *testing.T) {
189200 }
190201
191202 func TestModalState_ExitCancelsTimeout(t *testing.T) {
192- ms := NewModalState(nil)
203+ ctx := context.Background()
204+ ms := NewModalState(ctx)
193205
194206 var callCount int
195207 ms.SetOnModeChange(func(from, to Mode) {
@@ -197,13 +209,13 @@ func TestModalState_ExitCancelsTimeout(t *testing.T) {
197209 })
198210
199211 // Enter with timeout
200- ms.EnterTabMode(100 * time.Millisecond)
212+ ms.EnterTabMode(ctx, 100*time.Millisecond)
201213 if callCount != 1 {
202214 t.Fatalf("expected 1 callback")
203215 }
204216
205217 // Exit immediately
206- ms.ExitMode()
218+ ms.ExitMode(ctx)
207219 if callCount != 2 {
208220 t.Fatalf("expected 2 callbacks")
209221 }
@@ -218,7 +230,8 @@ func TestModalState_ExitCancelsTimeout(t *testing.T) {
218230 }
219231
220232 func TestModalState_ConcurrentAccess(t *testing.T) {
221- ms := NewModalState(nil)
233+ ctx := context.Background()
234+ ms := NewModalState(ctx)
222235
223236 var wg sync.WaitGroup
224237 iterations := 100
@@ -229,12 +242,12 @@ func TestModalState_ConcurrentAccess(t *testing.T) {
229242 go func() {
230243 defer wg.Done()
231244 for j := 0; j < iterations; j++ {
232- ms.EnterTabMode(0)
245+ ms.EnterTabMode(ctx, 0)
233246 _ = ms.Mode()
234- ms.ExitMode()
235- ms.EnterPaneMode(0)
247+ ms.ExitMode(ctx)
248+ ms.EnterPaneMode(ctx, 0)
236249 _ = ms.Mode()
237- ms.ExitMode()
250+ ms.ExitMode(ctx)
238251 }
239252 }()
240253 }
@@ -1,13 +1,15 @@
1 package input1 package input
2 2
3 import (3 import (
4+ "context"
4 "sync"5 "sync"
5 "testing"6 "testing"
6 "time"7 "time"
7 )8 )
8 9
9 func TestModalState_InitialState(t *testing.T) {10 func TestModalState_InitialState(t *testing.T) {
10- ms := NewModalState(nil)11+ ctx := context.Background()
12+ ms := NewModalState(ctx)
11 13
12 if ms.Mode() != ModeNormal {14 if ms.Mode() != ModeNormal {
13 t.Errorf("initial mode = %v, want ModeNormal", ms.Mode())15 t.Errorf("initial mode = %v, want ModeNormal", ms.Mode())
@@ -15,9 +17,10 @@ func TestModalState_InitialState(t *testing.T) {
15 }17 }
16 18
17 func TestModalState_EnterTabMode(t *testing.T) {19 func TestModalState_EnterTabMode(t *testing.T) {
18- ms := NewModalState(nil)20+ ctx := context.Background()
21+ ms := NewModalState(ctx)
19 22
20- ms.EnterTabMode(0)23+ ms.EnterTabMode(ctx, 0)
21 24
22 if ms.Mode() != ModeTab {25 if ms.Mode() != ModeTab {
23 t.Errorf("mode after EnterTabMode = %v, want ModeTab", ms.Mode())26 t.Errorf("mode after EnterTabMode = %v, want ModeTab", ms.Mode())
@@ -25,9 +28,10 @@ func TestModalState_EnterTabMode(t *testing.T) {
25 }28 }
26 29
27 func TestModalState_EnterPaneMode(t *testing.T) {30 func TestModalState_EnterPaneMode(t *testing.T) {
28- ms := NewModalState(nil)31+ ctx := context.Background()
32+ ms := NewModalState(ctx)
29 33
30- ms.EnterPaneMode(0)34+ ms.EnterPaneMode(ctx, 0)
31 35
32 if ms.Mode() != ModePane {36 if ms.Mode() != ModePane {
33 t.Errorf("mode after EnterPaneMode = %v, want ModePane", ms.Mode())37 t.Errorf("mode after EnterPaneMode = %v, want ModePane", ms.Mode())
@@ -35,26 +39,28 @@ func TestModalState_EnterPaneMode(t *testing.T) {
35 }39 }
36 40
37 func TestModalState_ExitMode(t *testing.T) {41 func TestModalState_ExitMode(t *testing.T) {
38- ms := NewModalState(nil)42+ ctx := context.Background()
43+ ms := NewModalState(ctx)
39 44
40 // Enter tab mode45 // Enter tab mode
41- ms.EnterTabMode(0)46+ ms.EnterTabMode(ctx, 0)
42 if ms.Mode() != ModeTab {47 if ms.Mode() != ModeTab {
43 t.Fatalf("mode should be ModeTab")48 t.Fatalf("mode should be ModeTab")
44 }49 }
45 50
46 // Exit mode51 // Exit mode
47- ms.ExitMode()52+ ms.ExitMode(ctx)
48 if ms.Mode() != ModeNormal {53 if ms.Mode() != ModeNormal {
49 t.Errorf("mode after ExitMode = %v, want ModeNormal", ms.Mode())54 t.Errorf("mode after ExitMode = %v, want ModeNormal", ms.Mode())
50 }55 }
51 }56 }
52 57
53 func TestModalState_ExitMode_FromNormal(t *testing.T) {58 func TestModalState_ExitMode_FromNormal(t *testing.T) {
54- ms := NewModalState(nil)59+ ctx := context.Background()
60+ ms := NewModalState(ctx)
55 61
56 // ExitMode from normal should be no-op62 // ExitMode from normal should be no-op
57- ms.ExitMode()63+ ms.ExitMode(ctx)
58 64
59 if ms.Mode() != ModeNormal {65 if ms.Mode() != ModeNormal {
60 t.Errorf("mode = %v, want ModeNormal", ms.Mode())66 t.Errorf("mode = %v, want ModeNormal", ms.Mode())
@@ -62,29 +68,31 @@ func TestModalState_ExitMode_FromNormal(t *testing.T) {
62 }68 }
63 69
64 func TestModalState_SwitchModes(t *testing.T) {70 func TestModalState_SwitchModes(t *testing.T) {
65- ms := NewModalState(nil)71+ ctx := context.Background()
72+ ms := NewModalState(ctx)
66 73
67 // Enter tab mode74 // Enter tab mode
68- ms.EnterTabMode(0)75+ ms.EnterTabMode(ctx, 0)
69 if ms.Mode() != ModeTab {76 if ms.Mode() != ModeTab {
70 t.Fatalf("expected ModeTab, got %v", ms.Mode())77 t.Fatalf("expected ModeTab, got %v", ms.Mode())
71 }78 }
72 79
73 // Switch to pane mode directly80 // Switch to pane mode directly
74- ms.EnterPaneMode(0)81+ ms.EnterPaneMode(ctx, 0)
75 if ms.Mode() != ModePane {82 if ms.Mode() != ModePane {
76 t.Errorf("mode after switch to pane = %v, want ModePane", ms.Mode())83 t.Errorf("mode after switch to pane = %v, want ModePane", ms.Mode())
77 }84 }
78 85
79 // Switch back to tab mode86 // Switch back to tab mode
80- ms.EnterTabMode(0)87+ ms.EnterTabMode(ctx, 0)
81 if ms.Mode() != ModeTab {88 if ms.Mode() != ModeTab {
82 t.Errorf("mode after switch to tab = %v, want ModeTab", ms.Mode())89 t.Errorf("mode after switch to tab = %v, want ModeTab", ms.Mode())
83 }90 }
84 }91 }
85 92
86 func TestModalState_ModeChangeCallback(t *testing.T) {93 func TestModalState_ModeChangeCallback(t *testing.T) {
87- ms := NewModalState(nil)94+ ctx := context.Background()
95+ ms := NewModalState(ctx)
88 96
89 var callCount int97 var callCount int
90 var lastFrom, lastTo Mode98 var lastFrom, lastTo Mode
@@ -99,7 +107,7 @@ func TestModalState_ModeChangeCallback(t *testing.T) {
99 })107 })
100 108
101 // Enter tab mode109 // Enter tab mode
102- ms.EnterTabMode(0)110+ ms.EnterTabMode(ctx, 0)
103 111
104 mu.Lock()112 mu.Lock()
105 if callCount != 1 {113 if callCount != 1 {
@@ -111,7 +119,7 @@ func TestModalState_ModeChangeCallback(t *testing.T) {
111 mu.Unlock()119 mu.Unlock()
112 120
113 // Exit mode121 // Exit mode
114- ms.ExitMode()122+ ms.ExitMode(ctx)
115 123
116 mu.Lock()124 mu.Lock()
117 if callCount != 2 {125 if callCount != 2 {
@@ -124,7 +132,8 @@ func TestModalState_ModeChangeCallback(t *testing.T) {
124 }132 }
125 133
126 func TestModalState_NoCallbackWhenAlreadyInMode(t *testing.T) {134 func TestModalState_NoCallbackWhenAlreadyInMode(t *testing.T) {
127- ms := NewModalState(nil)135+ ctx := context.Background()
136+ ms := NewModalState(ctx)
128 137
129 var callCount int138 var callCount int
130 ms.SetOnModeChange(func(from, to Mode) {139 ms.SetOnModeChange(func(from, to Mode) {
@@ -132,23 +141,24 @@ func TestModalState_NoCallbackWhenAlreadyInMode(t *testing.T) {
132 })141 })
133 142
134 // Enter tab mode143 // Enter tab mode
135- ms.EnterTabMode(0)144+ ms.EnterTabMode(ctx, 0)
136 if callCount != 1 {145 if callCount != 1 {
137 t.Fatalf("expected 1 callback, got %d", callCount)146 t.Fatalf("expected 1 callback, got %d", callCount)
138 }147 }
139 148
140 // Enter tab mode again (should not trigger callback)149 // Enter tab mode again (should not trigger callback)
141- ms.EnterTabMode(0)150+ ms.EnterTabMode(ctx, 0)
142 if callCount != 1 {151 if callCount != 1 {
143 t.Errorf("callback count = %d, want 1 (no new call)", callCount)152 t.Errorf("callback count = %d, want 1 (no new call)", callCount)
144 }153 }
145 }154 }
146 155
147 func TestModalState_Timeout(t *testing.T) {156 func TestModalState_Timeout(t *testing.T) {
148- ms := NewModalState(nil)157+ ctx := context.Background()
158+ ms := NewModalState(ctx)
149 159
150 // Enter with short timeout160 // Enter with short timeout
151- ms.EnterTabMode(50 * time.Millisecond)161+ ms.EnterTabMode(ctx, 50*time.Millisecond)
152 162
153 if ms.Mode() != ModeTab {163 if ms.Mode() != ModeTab {
154 t.Fatalf("mode should be ModeTab")164 t.Fatalf("mode should be ModeTab")
@@ -163,14 +173,15 @@ func TestModalState_Timeout(t *testing.T) {
163 }173 }
164 174
165 func TestModalState_TimeoutReset(t *testing.T) {175 func TestModalState_TimeoutReset(t *testing.T) {
166- ms := NewModalState(nil)176+ ctx := context.Background()
177+ ms := NewModalState(ctx)
167 178
168 // Enter with timeout179 // Enter with timeout
169- ms.EnterTabMode(100 * time.Millisecond)180+ ms.EnterTabMode(ctx, 100*time.Millisecond)
170 181
171 // Wait a bit, then reset182 // Wait a bit, then reset
172 time.Sleep(50 * time.Millisecond)183 time.Sleep(50 * time.Millisecond)
173- ms.ResetTimeout()184+ ms.ResetTimeout(ctx)
174 185
175 // Wait a bit more (original timeout would have expired)186 // Wait a bit more (original timeout would have expired)
176 time.Sleep(80 * time.Millisecond)187 time.Sleep(80 * time.Millisecond)
@@ -189,7 +200,8 @@ func TestModalState_TimeoutReset(t *testing.T) {
189 }200 }
190 201
191 func TestModalState_ExitCancelsTimeout(t *testing.T) {202 func TestModalState_ExitCancelsTimeout(t *testing.T) {
192- ms := NewModalState(nil)203+ ctx := context.Background()
204+ ms := NewModalState(ctx)
193 205
194 var callCount int206 var callCount int
195 ms.SetOnModeChange(func(from, to Mode) {207 ms.SetOnModeChange(func(from, to Mode) {
@@ -197,13 +209,13 @@ func TestModalState_ExitCancelsTimeout(t *testing.T) {
197 })209 })
198 210
199 // Enter with timeout211 // Enter with timeout
200- ms.EnterTabMode(100 * time.Millisecond)212+ ms.EnterTabMode(ctx, 100*time.Millisecond)
201 if callCount != 1 {213 if callCount != 1 {
202 t.Fatalf("expected 1 callback")214 t.Fatalf("expected 1 callback")
203 }215 }
204 216
205 // Exit immediately217 // Exit immediately
206- ms.ExitMode()218+ ms.ExitMode(ctx)
207 if callCount != 2 {219 if callCount != 2 {
208 t.Fatalf("expected 2 callbacks")220 t.Fatalf("expected 2 callbacks")
209 }221 }
@@ -218,7 +230,8 @@ func TestModalState_ExitCancelsTimeout(t *testing.T) {
218 }230 }
219 231
220 func TestModalState_ConcurrentAccess(t *testing.T) {232 func TestModalState_ConcurrentAccess(t *testing.T) {
221- ms := NewModalState(nil)233+ ctx := context.Background()
234+ ms := NewModalState(ctx)
222 235
223 var wg sync.WaitGroup236 var wg sync.WaitGroup
224 iterations := 100237 iterations := 100
@@ -229,12 +242,12 @@ func TestModalState_ConcurrentAccess(t *testing.T) {
229 go func() {242 go func() {
230 defer wg.Done()243 defer wg.Done()
231 for j := 0; j < iterations; j++ {244 for j := 0; j < iterations; j++ {
232- ms.EnterTabMode(0)245+ ms.EnterTabMode(ctx, 0)
233 _ = ms.Mode()246 _ = ms.Mode()
234- ms.ExitMode()247+ ms.ExitMode(ctx)
235- ms.EnterPaneMode(0)248+ ms.EnterPaneMode(ctx, 0)
236 _ = ms.Mode()249 _ = ms.Mode()
237- ms.ExitMode()250+ ms.ExitMode(ctx)
238 }251 }
239 }()252 }()
240 }253 }
M internal/ui/layout/split_test.go +8 -0
@@ -18,6 +18,7 @@ import (
1818 func setupSplitViewMocks(mockPaned *mocks.MockPanedWidget, orientation layout.Orientation) {
1919 mockPaned.EXPECT().SetResizeStartChild(true).Once()
2020 mockPaned.EXPECT().SetResizeEndChild(true).Once()
21+ mockPaned.EXPECT().SetVisible(true).Once()
2122 // Note: SetShrinkStartChild/SetShrinkEndChild are NOT called - allowing shrink
2223 // enables GTK to respect the 50/50 ratio even if children have larger natural sizes
2324
@@ -43,6 +44,8 @@ func TestNewSplitView_Horizontal(t *testing.T) {
4344
4445 mockFactory.EXPECT().NewPaned(layout.OrientationHorizontal).Return(mockPaned).Once()
4546 setupSplitViewMocks(mockPaned, layout.OrientationHorizontal)
47+ mockStartChild.EXPECT().SetVisible(true).Once()
48+ mockEndChild.EXPECT().SetVisible(true).Once()
4649 mockPaned.EXPECT().SetStartChild(mockStartChild).Once()
4750 mockPaned.EXPECT().SetEndChild(mockEndChild).Once()
4851
@@ -67,6 +70,8 @@ func TestNewSplitView_Vertical(t *testing.T) {
6770
6871 mockFactory.EXPECT().NewPaned(layout.OrientationVertical).Return(mockPaned).Once()
6972 setupSplitViewMocks(mockPaned, layout.OrientationVertical)
73+ mockStartChild.EXPECT().SetVisible(true).Once()
74+ mockEndChild.EXPECT().SetVisible(true).Once()
7075 mockPaned.EXPECT().SetStartChild(mockStartChild).Once()
7176 mockPaned.EXPECT().SetEndChild(mockEndChild).Once()
7277
@@ -174,6 +179,7 @@ func TestSwapStart_ReplacesChild(t *testing.T) {
174179
175180 mockFactory.EXPECT().NewPaned(mock.Anything).Return(mockPaned).Once()
176181 setupSplitViewMocks(mockPaned, layout.OrientationHorizontal)
182+ mockOldChild.EXPECT().SetVisible(true).Once()
177183 mockPaned.EXPECT().SetStartChild(mockOldChild).Once()
178184
179185 sv := layout.NewSplitView(ctx, mockFactory, layout.OrientationHorizontal, mockOldChild, nil, 0.5)
@@ -199,6 +205,7 @@ func TestSwapEnd_ReplacesChild(t *testing.T) {
199205
200206 mockFactory.EXPECT().NewPaned(mock.Anything).Return(mockPaned).Once()
201207 setupSplitViewMocks(mockPaned, layout.OrientationHorizontal)
208+ mockOldChild.EXPECT().SetVisible(true).Once()
202209 mockPaned.EXPECT().SetEndChild(mockOldChild).Once()
203210
204211 sv := layout.NewSplitView(ctx, mockFactory, layout.OrientationHorizontal, nil, mockOldChild, 0.5)
@@ -245,6 +252,7 @@ func TestSwapEnd_ToNil(t *testing.T) {
245252
246253 mockFactory.EXPECT().NewPaned(mock.Anything).Return(mockPaned).Once()
247254 setupSplitViewMocks(mockPaned, layout.OrientationHorizontal)
255+ mockOldChild.EXPECT().SetVisible(true).Once()
248256 mockPaned.EXPECT().SetEndChild(mockOldChild).Once()
249257
250258 sv := layout.NewSplitView(ctx, mockFactory, layout.OrientationHorizontal, nil, mockOldChild, 0.5)
@@ -18,6 +18,7 @@ import (
18 func setupSplitViewMocks(mockPaned *mocks.MockPanedWidget, orientation layout.Orientation) {18 func setupSplitViewMocks(mockPaned *mocks.MockPanedWidget, orientation layout.Orientation) {
19 mockPaned.EXPECT().SetResizeStartChild(true).Once()19 mockPaned.EXPECT().SetResizeStartChild(true).Once()
20 mockPaned.EXPECT().SetResizeEndChild(true).Once()20 mockPaned.EXPECT().SetResizeEndChild(true).Once()
21+ mockPaned.EXPECT().SetVisible(true).Once()
21 // Note: SetShrinkStartChild/SetShrinkEndChild are NOT called - allowing shrink22 // Note: SetShrinkStartChild/SetShrinkEndChild are NOT called - allowing shrink
22 // enables GTK to respect the 50/50 ratio even if children have larger natural sizes23 // enables GTK to respect the 50/50 ratio even if children have larger natural sizes
23 24
@@ -43,6 +44,8 @@ func TestNewSplitView_Horizontal(t *testing.T) {
43 44
44 mockFactory.EXPECT().NewPaned(layout.OrientationHorizontal).Return(mockPaned).Once()45 mockFactory.EXPECT().NewPaned(layout.OrientationHorizontal).Return(mockPaned).Once()
45 setupSplitViewMocks(mockPaned, layout.OrientationHorizontal)46 setupSplitViewMocks(mockPaned, layout.OrientationHorizontal)
47+ mockStartChild.EXPECT().SetVisible(true).Once()
48+ mockEndChild.EXPECT().SetVisible(true).Once()
46 mockPaned.EXPECT().SetStartChild(mockStartChild).Once()49 mockPaned.EXPECT().SetStartChild(mockStartChild).Once()
47 mockPaned.EXPECT().SetEndChild(mockEndChild).Once()50 mockPaned.EXPECT().SetEndChild(mockEndChild).Once()
48 51
@@ -67,6 +70,8 @@ func TestNewSplitView_Vertical(t *testing.T) {
67 70
68 mockFactory.EXPECT().NewPaned(layout.OrientationVertical).Return(mockPaned).Once()71 mockFactory.EXPECT().NewPaned(layout.OrientationVertical).Return(mockPaned).Once()
69 setupSplitViewMocks(mockPaned, layout.OrientationVertical)72 setupSplitViewMocks(mockPaned, layout.OrientationVertical)
73+ mockStartChild.EXPECT().SetVisible(true).Once()
74+ mockEndChild.EXPECT().SetVisible(true).Once()
70 mockPaned.EXPECT().SetStartChild(mockStartChild).Once()75 mockPaned.EXPECT().SetStartChild(mockStartChild).Once()
71 mockPaned.EXPECT().SetEndChild(mockEndChild).Once()76 mockPaned.EXPECT().SetEndChild(mockEndChild).Once()
72 77
@@ -174,6 +179,7 @@ func TestSwapStart_ReplacesChild(t *testing.T) {
174 179
175 mockFactory.EXPECT().NewPaned(mock.Anything).Return(mockPaned).Once()180 mockFactory.EXPECT().NewPaned(mock.Anything).Return(mockPaned).Once()
176 setupSplitViewMocks(mockPaned, layout.OrientationHorizontal)181 setupSplitViewMocks(mockPaned, layout.OrientationHorizontal)
182+ mockOldChild.EXPECT().SetVisible(true).Once()
177 mockPaned.EXPECT().SetStartChild(mockOldChild).Once()183 mockPaned.EXPECT().SetStartChild(mockOldChild).Once()
178 184
179 sv := layout.NewSplitView(ctx, mockFactory, layout.OrientationHorizontal, mockOldChild, nil, 0.5)185 sv := layout.NewSplitView(ctx, mockFactory, layout.OrientationHorizontal, mockOldChild, nil, 0.5)
@@ -199,6 +205,7 @@ func TestSwapEnd_ReplacesChild(t *testing.T) {
199 205
200 mockFactory.EXPECT().NewPaned(mock.Anything).Return(mockPaned).Once()206 mockFactory.EXPECT().NewPaned(mock.Anything).Return(mockPaned).Once()
201 setupSplitViewMocks(mockPaned, layout.OrientationHorizontal)207 setupSplitViewMocks(mockPaned, layout.OrientationHorizontal)
208+ mockOldChild.EXPECT().SetVisible(true).Once()
202 mockPaned.EXPECT().SetEndChild(mockOldChild).Once()209 mockPaned.EXPECT().SetEndChild(mockOldChild).Once()
203 210
204 sv := layout.NewSplitView(ctx, mockFactory, layout.OrientationHorizontal, nil, mockOldChild, 0.5)211 sv := layout.NewSplitView(ctx, mockFactory, layout.OrientationHorizontal, nil, mockOldChild, 0.5)
@@ -245,6 +252,7 @@ func TestSwapEnd_ToNil(t *testing.T) {
245 252
246 mockFactory.EXPECT().NewPaned(mock.Anything).Return(mockPaned).Once()253 mockFactory.EXPECT().NewPaned(mock.Anything).Return(mockPaned).Once()
247 setupSplitViewMocks(mockPaned, layout.OrientationHorizontal)254 setupSplitViewMocks(mockPaned, layout.OrientationHorizontal)
255+ mockOldChild.EXPECT().SetVisible(true).Once()
248 mockPaned.EXPECT().SetEndChild(mockOldChild).Once()256 mockPaned.EXPECT().SetEndChild(mockOldChild).Once()
249 257
250 sv := layout.NewSplitView(ctx, mockFactory, layout.OrientationHorizontal, nil, mockOldChild, 0.5)258 sv := layout.NewSplitView(ctx, mockFactory, layout.OrientationHorizontal, nil, mockOldChild, 0.5)
M internal/ui/layout/stacked_test.go +3 -0
@@ -37,6 +37,7 @@ func setupPaneMocks(t *testing.T, mockFactory *mocks.MockWidgetFactory, mockBox
3737 // Title bar creation
3838 mockFactory.EXPECT().NewBox(layout.OrientationHorizontal, 4).Return(mockTitleBar).Once()
3939 mockTitleBar.EXPECT().AddCssClass("stacked-pane-titlebar").Once()
40+ mockTitleBar.EXPECT().SetVexpand(false).Once()
4041
4142 // Favicon
4243 mockFactory.EXPECT().NewImage().Return(mockFavicon).Once()
@@ -57,6 +58,8 @@ func setupPaneMocks(t *testing.T, mockFactory *mocks.MockWidgetFactory, mockBox
5758 mockButton.EXPECT().SetChild(mockTitleBar).Once()
5859 mockButton.EXPECT().AddCssClass("stacked-pane-title-button").Once()
5960 mockButton.EXPECT().SetFocusOnClick(false).Once()
61+ mockButton.EXPECT().SetVexpand(false).Once()
62+ mockButton.EXPECT().SetHexpand(true).Once()
6063 mockButton.EXPECT().ConnectClicked(mock.Anything).Return(uint32(1)).Once()
6164
6265 // Adding to main box
@@ -37,6 +37,7 @@ func setupPaneMocks(t *testing.T, mockFactory *mocks.MockWidgetFactory, mockBox
37 // Title bar creation37 // Title bar creation
38 mockFactory.EXPECT().NewBox(layout.OrientationHorizontal, 4).Return(mockTitleBar).Once()38 mockFactory.EXPECT().NewBox(layout.OrientationHorizontal, 4).Return(mockTitleBar).Once()
39 mockTitleBar.EXPECT().AddCssClass("stacked-pane-titlebar").Once()39 mockTitleBar.EXPECT().AddCssClass("stacked-pane-titlebar").Once()
40+ mockTitleBar.EXPECT().SetVexpand(false).Once()
40 41
41 // Favicon42 // Favicon
42 mockFactory.EXPECT().NewImage().Return(mockFavicon).Once()43 mockFactory.EXPECT().NewImage().Return(mockFavicon).Once()
@@ -57,6 +58,8 @@ func setupPaneMocks(t *testing.T, mockFactory *mocks.MockWidgetFactory, mockBox
57 mockButton.EXPECT().SetChild(mockTitleBar).Once()58 mockButton.EXPECT().SetChild(mockTitleBar).Once()
58 mockButton.EXPECT().AddCssClass("stacked-pane-title-button").Once()59 mockButton.EXPECT().AddCssClass("stacked-pane-title-button").Once()
59 mockButton.EXPECT().SetFocusOnClick(false).Once()60 mockButton.EXPECT().SetFocusOnClick(false).Once()
61+ mockButton.EXPECT().SetVexpand(false).Once()
62+ mockButton.EXPECT().SetHexpand(true).Once()
60 mockButton.EXPECT().ConnectClicked(mock.Anything).Return(uint32(1)).Once()63 mockButton.EXPECT().ConnectClicked(mock.Anything).Return(uint32(1)).Once()
61 64
62 // Adding to main box65 // Adding to main box
M internal/ui/layout/tree_test.go +87 -67
@@ -13,6 +13,20 @@ import (
1313 "github.com/bnema/dumber/internal/ui/layout/mocks"
1414 )
1515
16+func 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, _, _, _, mockContainer := setupPaneMocks(t, mockFactory, mockStackBox)
23+ mockTitleBar.EXPECT().GetParent().Return(nil).Maybe()
24+ mockContainer.EXPECT().SetVisible(true).Once()
25+ mockTitleBar.EXPECT().AddCssClass("active").Once()
26+
27+ return mockStackBox, mockContainer
28+}
29+
1630 func TestNewTreeRenderer_CreatesRenderer(t *testing.T) {
1731 // Arrange
1832 ctx := context.Background()
@@ -48,7 +62,7 @@ func TestBuild_SingleLeafPane(t *testing.T) {
4862 ctx := context.Background()
4963 mockFactory := mocks.NewMockWidgetFactory(t)
5064 mockPaneViewFactory := mocks.NewMockPaneViewFactory(t)
51- mockWidget := mocks.NewMockWidget(t)
65+ mockStackBox, mockContainer := setupStackedLeafMocks(t, mockFactory)
5266
5367 pane := entity.NewPane(entity.PaneID("pane-1"))
5468 node := &entity.PaneNode{
@@ -56,7 +70,7 @@ func TestBuild_SingleLeafPane(t *testing.T) {
5670 Pane: pane,
5771 }
5872
59- mockPaneViewFactory.EXPECT().CreatePaneView(node).Return(mockWidget).Once()
73+ mockPaneViewFactory.EXPECT().CreatePaneView(node).Return(mockContainer).Once()
6074
6175 renderer := layout.NewTreeRenderer(ctx, mockFactory, mockPaneViewFactory)
6276
@@ -65,11 +79,11 @@ func TestBuild_SingleLeafPane(t *testing.T) {
6579
6680 // Assert
6781 require.NoError(t, err)
68- assert.Equal(t, mockWidget, widget)
82+ assert.Equal(t, mockStackBox, widget)
6983 assert.Equal(t, 1, renderer.NodeCount())
7084
7185 // Verify lookup works
72- assert.Equal(t, mockWidget, renderer.Lookup("node-1"))
86+ assert.Equal(t, mockStackBox, renderer.Lookup("node-1"))
7387 }
7488
7589 func TestBuild_HorizontalSplit(t *testing.T) {
@@ -78,8 +92,8 @@ func TestBuild_HorizontalSplit(t *testing.T) {
7892 mockFactory := mocks.NewMockWidgetFactory(t)
7993 mockPaneViewFactory := mocks.NewMockPaneViewFactory(t)
8094 mockPaned := mocks.NewMockPanedWidget(t)
81- mockLeftWidget := mocks.NewMockWidget(t)
82- mockRightWidget := mocks.NewMockWidget(t)
95+ mockLeftStackBox, mockLeftContainer := setupStackedLeafMocks(t, mockFactory)
96+ mockRightStackBox, mockRightContainer := setupStackedLeafMocks(t, mockFactory)
8397
8498 leftPane := entity.NewPane(entity.PaneID("pane-left"))
8599 rightPane := entity.NewPane(entity.PaneID("pane-right"))
@@ -94,17 +108,18 @@ func TestBuild_HorizontalSplit(t *testing.T) {
94108 Children: []*entity.PaneNode{leftNode, rightNode},
95109 }
96110
97- mockPaneViewFactory.EXPECT().CreatePaneView(leftNode).Return(mockLeftWidget).Once()
98- mockPaneViewFactory.EXPECT().CreatePaneView(rightNode).Return(mockRightWidget).Once()
111+ mockPaneViewFactory.EXPECT().CreatePaneView(leftNode).Return(mockLeftContainer).Once()
112+ mockPaneViewFactory.EXPECT().CreatePaneView(rightNode).Return(mockRightContainer).Once()
99113
100114 // SplitView creation expectations
101115 mockFactory.EXPECT().NewPaned(layout.OrientationHorizontal).Return(mockPaned).Once()
102116 mockPaned.EXPECT().SetResizeStartChild(true).Once()
103117 mockPaned.EXPECT().SetResizeEndChild(true).Once()
104- mockPaned.EXPECT().SetShrinkStartChild(false).Once()
105- mockPaned.EXPECT().SetShrinkEndChild(false).Once()
106- mockPaned.EXPECT().SetStartChild(mockLeftWidget).Once()
107- mockPaned.EXPECT().SetEndChild(mockRightWidget).Once()
118+ mockPaned.EXPECT().SetVisible(true).Once()
119+ mockLeftStackBox.EXPECT().SetVisible(true).Once()
120+ mockRightStackBox.EXPECT().SetVisible(true).Once()
121+ mockPaned.EXPECT().SetStartChild(mockLeftStackBox).Once()
122+ mockPaned.EXPECT().SetEndChild(mockRightStackBox).Once()
108123 mockPaned.EXPECT().GetAllocatedWidth().Return(0).Once()
109124 mockPaned.EXPECT().ConnectMap(mock.Anything).Return(uint32(0)).Once()
110125 mockPaned.EXPECT().AddTickCallback(mock.Anything).Return(uint(0)).Once()
@@ -126,8 +141,8 @@ func TestBuild_VerticalSplit(t *testing.T) {
126141 mockFactory := mocks.NewMockWidgetFactory(t)
127142 mockPaneViewFactory := mocks.NewMockPaneViewFactory(t)
128143 mockPaned := mocks.NewMockPanedWidget(t)
129- mockTopWidget := mocks.NewMockWidget(t)
130- mockBottomWidget := mocks.NewMockWidget(t)
144+ mockTopStackBox, mockTopContainer := setupStackedLeafMocks(t, mockFactory)
145+ mockBottomStackBox, mockBottomContainer := setupStackedLeafMocks(t, mockFactory)
131146
132147 topPane := entity.NewPane(entity.PaneID("pane-top"))
133148 bottomPane := entity.NewPane(entity.PaneID("pane-bottom"))
@@ -142,17 +157,18 @@ func TestBuild_VerticalSplit(t *testing.T) {
142157 Children: []*entity.PaneNode{topNode, bottomNode},
143158 }
144159
145- mockPaneViewFactory.EXPECT().CreatePaneView(topNode).Return(mockTopWidget).Once()
146- mockPaneViewFactory.EXPECT().CreatePaneView(bottomNode).Return(mockBottomWidget).Once()
160+ mockPaneViewFactory.EXPECT().CreatePaneView(topNode).Return(mockTopContainer).Once()
161+ mockPaneViewFactory.EXPECT().CreatePaneView(bottomNode).Return(mockBottomContainer).Once()
147162
148163 // SplitView creation expectations - should be vertical
149164 mockFactory.EXPECT().NewPaned(layout.OrientationVertical).Return(mockPaned).Once()
150165 mockPaned.EXPECT().SetResizeStartChild(true).Once()
151166 mockPaned.EXPECT().SetResizeEndChild(true).Once()
152- mockPaned.EXPECT().SetShrinkStartChild(false).Once()
153- mockPaned.EXPECT().SetShrinkEndChild(false).Once()
154- mockPaned.EXPECT().SetStartChild(mockTopWidget).Once()
155- mockPaned.EXPECT().SetEndChild(mockBottomWidget).Once()
167+ mockPaned.EXPECT().SetVisible(true).Once()
168+ mockTopStackBox.EXPECT().SetVisible(true).Once()
169+ mockBottomStackBox.EXPECT().SetVisible(true).Once()
170+ mockPaned.EXPECT().SetStartChild(mockTopStackBox).Once()
171+ mockPaned.EXPECT().SetEndChild(mockBottomStackBox).Once()
156172 mockPaned.EXPECT().GetAllocatedHeight().Return(0).Once()
157173 mockPaned.EXPECT().ConnectMap(mock.Anything).Return(uint32(0)).Once()
158174 mockPaned.EXPECT().AddTickCallback(mock.Anything).Return(uint(0)).Once()
@@ -174,9 +190,9 @@ func TestBuild_NestedSplits(t *testing.T) {
174190 mockPaneViewFactory := mocks.NewMockPaneViewFactory(t)
175191 mockOuterPaned := mocks.NewMockPanedWidget(t)
176192 mockInnerPaned := mocks.NewMockPanedWidget(t)
177- mockWidget1 := mocks.NewMockWidget(t)
178- mockWidget2 := mocks.NewMockWidget(t)
179- mockWidget3 := mocks.NewMockWidget(t)
193+ mockStackBox1, mockContainer1 := setupStackedLeafMocks(t, mockFactory)
194+ mockStackBox2, mockContainer2 := setupStackedLeafMocks(t, mockFactory)
195+ mockStackBox3, mockContainer3 := setupStackedLeafMocks(t, mockFactory)
180196
181197 // Create tree structure:
182198 // outer (horizontal)
@@ -208,18 +224,19 @@ func TestBuild_NestedSplits(t *testing.T) {
208224 }
209225
210226 // Setup expectations for leaf nodes
211- mockPaneViewFactory.EXPECT().CreatePaneView(node1).Return(mockWidget1).Once()
212- mockPaneViewFactory.EXPECT().CreatePaneView(node2).Return(mockWidget2).Once()
213- mockPaneViewFactory.EXPECT().CreatePaneView(node3).Return(mockWidget3).Once()
227+ mockPaneViewFactory.EXPECT().CreatePaneView(node1).Return(mockContainer1).Once()
228+ mockPaneViewFactory.EXPECT().CreatePaneView(node2).Return(mockContainer2).Once()
229+ mockPaneViewFactory.EXPECT().CreatePaneView(node3).Return(mockContainer3).Once()
214230
215231 // Inner split (vertical) is created first
216232 mockFactory.EXPECT().NewPaned(layout.OrientationVertical).Return(mockInnerPaned).Once()
217233 mockInnerPaned.EXPECT().SetResizeStartChild(true).Once()
218234 mockInnerPaned.EXPECT().SetResizeEndChild(true).Once()
219- mockInnerPaned.EXPECT().SetShrinkStartChild(false).Once()
220- mockInnerPaned.EXPECT().SetShrinkEndChild(false).Once()
221- mockInnerPaned.EXPECT().SetStartChild(mockWidget1).Once()
222- mockInnerPaned.EXPECT().SetEndChild(mockWidget2).Once()
235+ mockInnerPaned.EXPECT().SetVisible(true).Once()
236+ mockStackBox1.EXPECT().SetVisible(true).Once()
237+ mockStackBox2.EXPECT().SetVisible(true).Once()
238+ mockInnerPaned.EXPECT().SetStartChild(mockStackBox1).Once()
239+ mockInnerPaned.EXPECT().SetEndChild(mockStackBox2).Once()
223240 mockInnerPaned.EXPECT().GetAllocatedHeight().Return(0).Once()
224241 mockInnerPaned.EXPECT().ConnectMap(mock.Anything).Return(uint32(0)).Once()
225242 mockInnerPaned.EXPECT().AddTickCallback(mock.Anything).Return(uint(0)).Once()
@@ -228,10 +245,11 @@ func TestBuild_NestedSplits(t *testing.T) {
228245 mockFactory.EXPECT().NewPaned(layout.OrientationHorizontal).Return(mockOuterPaned).Once()
229246 mockOuterPaned.EXPECT().SetResizeStartChild(true).Once()
230247 mockOuterPaned.EXPECT().SetResizeEndChild(true).Once()
231- mockOuterPaned.EXPECT().SetShrinkStartChild(false).Once()
232- mockOuterPaned.EXPECT().SetShrinkEndChild(false).Once()
248+ mockOuterPaned.EXPECT().SetVisible(true).Once()
249+ mockInnerPaned.EXPECT().SetVisible(true).Once()
250+ mockStackBox3.EXPECT().SetVisible(true).Once()
233251 mockOuterPaned.EXPECT().SetStartChild(mockInnerPaned).Once()
234- mockOuterPaned.EXPECT().SetEndChild(mockWidget3).Once()
252+ mockOuterPaned.EXPECT().SetEndChild(mockStackBox3).Once()
235253 mockOuterPaned.EXPECT().GetAllocatedWidth().Return(0).Once()
236254 mockOuterPaned.EXPECT().ConnectMap(mock.Anything).Return(uint32(0)).Once()
237255 mockOuterPaned.EXPECT().AddTickCallback(mock.Anything).Return(uint(0)).Once()
@@ -257,12 +275,12 @@ func TestLookup_ExistingNode(t *testing.T) {
257275 ctx := context.Background()
258276 mockFactory := mocks.NewMockWidgetFactory(t)
259277 mockPaneViewFactory := mocks.NewMockPaneViewFactory(t)
260- mockWidget := mocks.NewMockWidget(t)
278+ mockStackBox, mockContainer := setupStackedLeafMocks(t, mockFactory)
261279
262280 pane := entity.NewPane(entity.PaneID("pane-1"))
263281 node := &entity.PaneNode{ID: "test-node-id", Pane: pane}
264282
265- mockPaneViewFactory.EXPECT().CreatePaneView(node).Return(mockWidget).Once()
283+ mockPaneViewFactory.EXPECT().CreatePaneView(node).Return(mockContainer).Once()
266284
267285 renderer := layout.NewTreeRenderer(ctx, mockFactory, mockPaneViewFactory)
268286 _, _ = renderer.Build(ctx, node)
@@ -271,7 +289,7 @@ func TestLookup_ExistingNode(t *testing.T) {
271289 result := renderer.Lookup("test-node-id")
272290
273291 // Assert
274- assert.Equal(t, mockWidget, result)
292+ assert.Equal(t, mockStackBox, result)
275293 }
276294
277295 func TestLookup_NonExistentNode(t *testing.T) {
@@ -279,12 +297,12 @@ func TestLookup_NonExistentNode(t *testing.T) {
279297 ctx := context.Background()
280298 mockFactory := mocks.NewMockWidgetFactory(t)
281299 mockPaneViewFactory := mocks.NewMockPaneViewFactory(t)
282- mockWidget := mocks.NewMockWidget(t)
300+ _, mockContainer := setupStackedLeafMocks(t, mockFactory)
283301
284302 pane := entity.NewPane(entity.PaneID("pane-1"))
285303 node := &entity.PaneNode{ID: "node-1", Pane: pane}
286304
287- mockPaneViewFactory.EXPECT().CreatePaneView(node).Return(mockWidget).Once()
305+ mockPaneViewFactory.EXPECT().CreatePaneView(node).Return(mockContainer).Once()
288306
289307 renderer := layout.NewTreeRenderer(ctx, mockFactory, mockPaneViewFactory)
290308 _, _ = renderer.Build(ctx, node)
@@ -301,12 +319,12 @@ func TestLookupNode_Existing(t *testing.T) {
301319 ctx := context.Background()
302320 mockFactory := mocks.NewMockWidgetFactory(t)
303321 mockPaneViewFactory := mocks.NewMockPaneViewFactory(t)
304- mockWidget := mocks.NewMockWidget(t)
322+ mockStackBox, mockContainer := setupStackedLeafMocks(t, mockFactory)
305323
306324 pane := entity.NewPane(entity.PaneID("pane-1"))
307325 node := &entity.PaneNode{ID: "node-1", Pane: pane}
308326
309- mockPaneViewFactory.EXPECT().CreatePaneView(node).Return(mockWidget).Once()
327+ mockPaneViewFactory.EXPECT().CreatePaneView(node).Return(mockContainer).Once()
310328
311329 renderer := layout.NewTreeRenderer(ctx, mockFactory, mockPaneViewFactory)
312330 _, _ = renderer.Build(ctx, node)
@@ -316,7 +334,7 @@ func TestLookupNode_Existing(t *testing.T) {
316334
317335 // Assert
318336 assert.True(t, found)
319- assert.Equal(t, mockWidget, widget)
337+ assert.Equal(t, mockStackBox, widget)
320338 }
321339
322340 func TestLookupNode_NonExistent(t *testing.T) {
@@ -340,12 +358,12 @@ func TestClear_RemovesAllMappings(t *testing.T) {
340358 ctx := context.Background()
341359 mockFactory := mocks.NewMockWidgetFactory(t)
342360 mockPaneViewFactory := mocks.NewMockPaneViewFactory(t)
343- mockWidget := mocks.NewMockWidget(t)
361+ _, mockContainer := setupStackedLeafMocks(t, mockFactory)
344362
345363 pane := entity.NewPane(entity.PaneID("pane-1"))
346364 node := &entity.PaneNode{ID: "node-1", Pane: pane}
347365
348- mockPaneViewFactory.EXPECT().CreatePaneView(node).Return(mockWidget).Once()
366+ mockPaneViewFactory.EXPECT().CreatePaneView(node).Return(mockContainer).Once()
349367
350368 renderer := layout.NewTreeRenderer(ctx, mockFactory, mockPaneViewFactory)
351369 _, _ = renderer.Build(ctx, node)
@@ -366,8 +384,8 @@ func TestGetNodeIDs_ReturnsAllIDs(t *testing.T) {
366384 ctx := context.Background()
367385 mockPaneViewFactory := mocks.NewMockPaneViewFactory(t)
368386 mockPaned := mocks.NewMockPanedWidget(t)
369- mockWidget1 := mocks.NewMockWidget(t)
370- mockWidget2 := mocks.NewMockWidget(t)
387+ mockLeftStackBox, mockLeftContainer := setupStackedLeafMocks(t, mockFactory)
388+ mockRightStackBox, mockRightContainer := setupStackedLeafMocks(t, mockFactory)
371389
372390 pane1 := entity.NewPane(entity.PaneID("pane-1"))
373391 pane2 := entity.NewPane(entity.PaneID("pane-2"))
@@ -382,16 +400,17 @@ func TestGetNodeIDs_ReturnsAllIDs(t *testing.T) {
382400 Children: []*entity.PaneNode{node1, node2},
383401 }
384402
385- mockPaneViewFactory.EXPECT().CreatePaneView(node1).Return(mockWidget1).Once()
386- mockPaneViewFactory.EXPECT().CreatePaneView(node2).Return(mockWidget2).Once()
403+ mockPaneViewFactory.EXPECT().CreatePaneView(node1).Return(mockLeftContainer).Once()
404+ mockPaneViewFactory.EXPECT().CreatePaneView(node2).Return(mockRightContainer).Once()
387405
388406 mockFactory.EXPECT().NewPaned(layout.OrientationHorizontal).Return(mockPaned).Once()
389407 mockPaned.EXPECT().SetResizeStartChild(true).Once()
390408 mockPaned.EXPECT().SetResizeEndChild(true).Once()
391- mockPaned.EXPECT().SetShrinkStartChild(false).Once()
392- mockPaned.EXPECT().SetShrinkEndChild(false).Once()
393- mockPaned.EXPECT().SetStartChild(mockWidget1).Once()
394- mockPaned.EXPECT().SetEndChild(mockWidget2).Once()
409+ mockPaned.EXPECT().SetVisible(true).Once()
410+ mockLeftStackBox.EXPECT().SetVisible(true).Once()
411+ mockRightStackBox.EXPECT().SetVisible(true).Once()
412+ mockPaned.EXPECT().SetStartChild(mockLeftStackBox).Once()
413+ mockPaned.EXPECT().SetEndChild(mockRightStackBox).Once()
395414 mockPaned.EXPECT().GetAllocatedWidth().Return(0).Once()
396415 mockPaned.EXPECT().ConnectMap(mock.Anything).Return(uint32(0)).Once()
397416 mockPaned.EXPECT().AddTickCallback(mock.Anything).Return(uint(0)).Once()
@@ -414,8 +433,8 @@ func TestBuild_ClearsPreviousMappings(t *testing.T) {
414433 ctx := context.Background()
415434 mockFactory := mocks.NewMockWidgetFactory(t)
416435 mockPaneViewFactory := mocks.NewMockPaneViewFactory(t)
417- mockWidget1 := mocks.NewMockWidget(t)
418- mockWidget2 := mocks.NewMockWidget(t)
436+ mockOldStackBox, mockOldContainer := setupStackedLeafMocks(t, mockFactory)
437+ mockNewStackBox, mockNewContainer := setupStackedLeafMocks(t, mockFactory)
419438
420439 pane1 := entity.NewPane(entity.PaneID("pane-1"))
421440 node1 := &entity.PaneNode{ID: "old-node", Pane: pane1}
@@ -423,15 +442,15 @@ func TestBuild_ClearsPreviousMappings(t *testing.T) {
423442 pane2 := entity.NewPane(entity.PaneID("pane-2"))
424443 node2 := &entity.PaneNode{ID: "new-node", Pane: pane2}
425444
426- mockPaneViewFactory.EXPECT().CreatePaneView(node1).Return(mockWidget1).Once()
427- mockPaneViewFactory.EXPECT().CreatePaneView(node2).Return(mockWidget2).Once()
445+ mockPaneViewFactory.EXPECT().CreatePaneView(node1).Return(mockOldContainer).Once()
446+ mockPaneViewFactory.EXPECT().CreatePaneView(node2).Return(mockNewContainer).Once()
428447
429448 renderer := layout.NewTreeRenderer(ctx, mockFactory, mockPaneViewFactory)
430449
431450 // First build
432451 _, _ = renderer.Build(ctx, node1)
433452 require.Equal(t, 1, renderer.NodeCount())
434- require.NotNil(t, renderer.Lookup("old-node"))
453+ require.Equal(t, mockOldStackBox, renderer.Lookup("old-node"))
435454
436455 // Act - second build should clear old mappings
437456 _, _ = renderer.Build(ctx, node2)
@@ -439,7 +458,7 @@ func TestBuild_ClearsPreviousMappings(t *testing.T) {
439458 // Assert
440459 assert.Equal(t, 1, renderer.NodeCount())
441460 assert.Nil(t, renderer.Lookup("old-node"))
442- assert.NotNil(t, renderer.Lookup("new-node"))
461+ assert.Equal(t, mockNewStackBox, renderer.Lookup("new-node"))
443462 }
444463
445464 func TestBuild_NilPaneViewFactory_ReturnsNilForLeaves(t *testing.T) {
@@ -465,12 +484,12 @@ func TestBuild_EmptyNodeID_NotStored(t *testing.T) {
465484 ctx := context.Background()
466485 mockFactory := mocks.NewMockWidgetFactory(t)
467486 mockPaneViewFactory := mocks.NewMockPaneViewFactory(t)
468- mockWidget := mocks.NewMockWidget(t)
487+ _, mockContainer := setupStackedLeafMocks(t, mockFactory)
469488
470489 pane := entity.NewPane(entity.PaneID("pane-1"))
471490 node := &entity.PaneNode{ID: "", Pane: pane} // Empty ID
472491
473- mockPaneViewFactory.EXPECT().CreatePaneView(node).Return(mockWidget).Once()
492+ mockPaneViewFactory.EXPECT().CreatePaneView(node).Return(mockContainer).Once()
474493
475494 renderer := layout.NewTreeRenderer(ctx, mockFactory, mockPaneViewFactory)
476495
@@ -489,8 +508,8 @@ func TestUpdateSplitRatio_ExistingNode(t *testing.T) {
489508 ctx := context.Background()
490509 mockPaneViewFactory := mocks.NewMockPaneViewFactory(t)
491510 mockPaned := mocks.NewMockPanedWidget(t)
492- mockWidget1 := mocks.NewMockWidget(t)
493- mockWidget2 := mocks.NewMockWidget(t)
511+ mockLeftStackBox, mockLeftContainer := setupStackedLeafMocks(t, mockFactory)
512+ mockRightStackBox, mockRightContainer := setupStackedLeafMocks(t, mockFactory)
494513
495514 pane1 := entity.NewPane(entity.PaneID("pane-1"))
496515 pane2 := entity.NewPane(entity.PaneID("pane-2"))
@@ -505,16 +524,17 @@ func TestUpdateSplitRatio_ExistingNode(t *testing.T) {
505524 Children: []*entity.PaneNode{node1, node2},
506525 }
507526
508- mockPaneViewFactory.EXPECT().CreatePaneView(node1).Return(mockWidget1).Once()
509- mockPaneViewFactory.EXPECT().CreatePaneView(node2).Return(mockWidget2).Once()
527+ mockPaneViewFactory.EXPECT().CreatePaneView(node1).Return(mockLeftContainer).Once()
528+ mockPaneViewFactory.EXPECT().CreatePaneView(node2).Return(mockRightContainer).Once()
510529
511530 mockFactory.EXPECT().NewPaned(layout.OrientationHorizontal).Return(mockPaned).Once()
512531 mockPaned.EXPECT().SetResizeStartChild(true).Once()
513532 mockPaned.EXPECT().SetResizeEndChild(true).Once()
514- mockPaned.EXPECT().SetShrinkStartChild(false).Once()
515- mockPaned.EXPECT().SetShrinkEndChild(false).Once()
516- mockPaned.EXPECT().SetStartChild(mockWidget1).Once()
517- mockPaned.EXPECT().SetEndChild(mockWidget2).Once()
533+ mockPaned.EXPECT().SetVisible(true).Once()
534+ mockLeftStackBox.EXPECT().SetVisible(true).Once()
535+ mockRightStackBox.EXPECT().SetVisible(true).Once()
536+ mockPaned.EXPECT().SetStartChild(mockLeftStackBox).Once()
537+ mockPaned.EXPECT().SetEndChild(mockRightStackBox).Once()
518538 mockPaned.EXPECT().GetAllocatedWidth().Return(0).Once()
519539 mockPaned.EXPECT().ConnectMap(mock.Anything).Return(uint32(0)).Once()
520540 mockPaned.EXPECT().AddTickCallback(mock.Anything).Return(uint(0)).Once()
@@ -13,6 +13,20 @@ import (
13 "github.com/bnema/dumber/internal/ui/layout/mocks"13 "github.com/bnema/dumber/internal/ui/layout/mocks"
14 )14 )
15 15
16+func 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, _, _, _, mockContainer := setupPaneMocks(t, mockFactory, mockStackBox)
23+ mockTitleBar.EXPECT().GetParent().Return(nil).Maybe()
24+ mockContainer.EXPECT().SetVisible(true).Once()
25+ mockTitleBar.EXPECT().AddCssClass("active").Once()
26+
27+ return mockStackBox, mockContainer
28+}
29+
16 func TestNewTreeRenderer_CreatesRenderer(t *testing.T) {30 func TestNewTreeRenderer_CreatesRenderer(t *testing.T) {
17 // Arrange31 // Arrange
18 ctx := context.Background()32 ctx := context.Background()
@@ -48,7 +62,7 @@ func TestBuild_SingleLeafPane(t *testing.T) {
48 ctx := context.Background()62 ctx := context.Background()
49 mockFactory := mocks.NewMockWidgetFactory(t)63 mockFactory := mocks.NewMockWidgetFactory(t)
50 mockPaneViewFactory := mocks.NewMockPaneViewFactory(t)64 mockPaneViewFactory := mocks.NewMockPaneViewFactory(t)
51- mockWidget := mocks.NewMockWidget(t)65+ mockStackBox, mockContainer := setupStackedLeafMocks(t, mockFactory)
52 66
53 pane := entity.NewPane(entity.PaneID("pane-1"))67 pane := entity.NewPane(entity.PaneID("pane-1"))
54 node := &entity.PaneNode{68 node := &entity.PaneNode{
@@ -56,7 +70,7 @@ func TestBuild_SingleLeafPane(t *testing.T) {
56 Pane: pane,70 Pane: pane,
57 }71 }
58 72
59- mockPaneViewFactory.EXPECT().CreatePaneView(node).Return(mockWidget).Once()73+ mockPaneViewFactory.EXPECT().CreatePaneView(node).Return(mockContainer).Once()
60 74
61 renderer := layout.NewTreeRenderer(ctx, mockFactory, mockPaneViewFactory)75 renderer := layout.NewTreeRenderer(ctx, mockFactory, mockPaneViewFactory)
62 76
@@ -65,11 +79,11 @@ func TestBuild_SingleLeafPane(t *testing.T) {
65 79
66 // Assert80 // Assert
67 require.NoError(t, err)81 require.NoError(t, err)
68- assert.Equal(t, mockWidget, widget)82+ assert.Equal(t, mockStackBox, widget)
69 assert.Equal(t, 1, renderer.NodeCount())83 assert.Equal(t, 1, renderer.NodeCount())
70 84
71 // Verify lookup works85 // Verify lookup works
72- assert.Equal(t, mockWidget, renderer.Lookup("node-1"))86+ assert.Equal(t, mockStackBox, renderer.Lookup("node-1"))
73 }87 }
74 88
75 func TestBuild_HorizontalSplit(t *testing.T) {89 func TestBuild_HorizontalSplit(t *testing.T) {
@@ -78,8 +92,8 @@ func TestBuild_HorizontalSplit(t *testing.T) {
78 mockFactory := mocks.NewMockWidgetFactory(t)92 mockFactory := mocks.NewMockWidgetFactory(t)
79 mockPaneViewFactory := mocks.NewMockPaneViewFactory(t)93 mockPaneViewFactory := mocks.NewMockPaneViewFactory(t)
80 mockPaned := mocks.NewMockPanedWidget(t)94 mockPaned := mocks.NewMockPanedWidget(t)
81- mockLeftWidget := mocks.NewMockWidget(t)95+ mockLeftStackBox, mockLeftContainer := setupStackedLeafMocks(t, mockFactory)
82- mockRightWidget := mocks.NewMockWidget(t)96+ mockRightStackBox, mockRightContainer := setupStackedLeafMocks(t, mockFactory)
83 97
84 leftPane := entity.NewPane(entity.PaneID("pane-left"))98 leftPane := entity.NewPane(entity.PaneID("pane-left"))
85 rightPane := entity.NewPane(entity.PaneID("pane-right"))99 rightPane := entity.NewPane(entity.PaneID("pane-right"))
@@ -94,17 +108,18 @@ func TestBuild_HorizontalSplit(t *testing.T) {
94 Children: []*entity.PaneNode{leftNode, rightNode},108 Children: []*entity.PaneNode{leftNode, rightNode},
95 }109 }
96 110
97- mockPaneViewFactory.EXPECT().CreatePaneView(leftNode).Return(mockLeftWidget).Once()111+ mockPaneViewFactory.EXPECT().CreatePaneView(leftNode).Return(mockLeftContainer).Once()
98- mockPaneViewFactory.EXPECT().CreatePaneView(rightNode).Return(mockRightWidget).Once()112+ mockPaneViewFactory.EXPECT().CreatePaneView(rightNode).Return(mockRightContainer).Once()
99 113
100 // SplitView creation expectations114 // SplitView creation expectations
101 mockFactory.EXPECT().NewPaned(layout.OrientationHorizontal).Return(mockPaned).Once()115 mockFactory.EXPECT().NewPaned(layout.OrientationHorizontal).Return(mockPaned).Once()
102 mockPaned.EXPECT().SetResizeStartChild(true).Once()116 mockPaned.EXPECT().SetResizeStartChild(true).Once()
103 mockPaned.EXPECT().SetResizeEndChild(true).Once()117 mockPaned.EXPECT().SetResizeEndChild(true).Once()
104- mockPaned.EXPECT().SetShrinkStartChild(false).Once()118+ mockPaned.EXPECT().SetVisible(true).Once()
105- mockPaned.EXPECT().SetShrinkEndChild(false).Once()119+ mockLeftStackBox.EXPECT().SetVisible(true).Once()
106- mockPaned.EXPECT().SetStartChild(mockLeftWidget).Once()120+ mockRightStackBox.EXPECT().SetVisible(true).Once()
107- mockPaned.EXPECT().SetEndChild(mockRightWidget).Once()121+ mockPaned.EXPECT().SetStartChild(mockLeftStackBox).Once()
122+ mockPaned.EXPECT().SetEndChild(mockRightStackBox).Once()
108 mockPaned.EXPECT().GetAllocatedWidth().Return(0).Once()123 mockPaned.EXPECT().GetAllocatedWidth().Return(0).Once()
109 mockPaned.EXPECT().ConnectMap(mock.Anything).Return(uint32(0)).Once()124 mockPaned.EXPECT().ConnectMap(mock.Anything).Return(uint32(0)).Once()
110 mockPaned.EXPECT().AddTickCallback(mock.Anything).Return(uint(0)).Once()125 mockPaned.EXPECT().AddTickCallback(mock.Anything).Return(uint(0)).Once()
@@ -126,8 +141,8 @@ func TestBuild_VerticalSplit(t *testing.T) {
126 mockFactory := mocks.NewMockWidgetFactory(t)141 mockFactory := mocks.NewMockWidgetFactory(t)
127 mockPaneViewFactory := mocks.NewMockPaneViewFactory(t)142 mockPaneViewFactory := mocks.NewMockPaneViewFactory(t)
128 mockPaned := mocks.NewMockPanedWidget(t)143 mockPaned := mocks.NewMockPanedWidget(t)
129- mockTopWidget := mocks.NewMockWidget(t)144+ mockTopStackBox, mockTopContainer := setupStackedLeafMocks(t, mockFactory)
130- mockBottomWidget := mocks.NewMockWidget(t)145+ mockBottomStackBox, mockBottomContainer := setupStackedLeafMocks(t, mockFactory)
131 146
132 topPane := entity.NewPane(entity.PaneID("pane-top"))147 topPane := entity.NewPane(entity.PaneID("pane-top"))
133 bottomPane := entity.NewPane(entity.PaneID("pane-bottom"))148 bottomPane := entity.NewPane(entity.PaneID("pane-bottom"))
@@ -142,17 +157,18 @@ func TestBuild_VerticalSplit(t *testing.T) {
142 Children: []*entity.PaneNode{topNode, bottomNode},157 Children: []*entity.PaneNode{topNode, bottomNode},
143 }158 }
144 159
145- mockPaneViewFactory.EXPECT().CreatePaneView(topNode).Return(mockTopWidget).Once()160+ mockPaneViewFactory.EXPECT().CreatePaneView(topNode).Return(mockTopContainer).Once()
146- mockPaneViewFactory.EXPECT().CreatePaneView(bottomNode).Return(mockBottomWidget).Once()161+ mockPaneViewFactory.EXPECT().CreatePaneView(bottomNode).Return(mockBottomContainer).Once()
147 162
148 // SplitView creation expectations - should be vertical163 // SplitView creation expectations - should be vertical
149 mockFactory.EXPECT().NewPaned(layout.OrientationVertical).Return(mockPaned).Once()164 mockFactory.EXPECT().NewPaned(layout.OrientationVertical).Return(mockPaned).Once()
150 mockPaned.EXPECT().SetResizeStartChild(true).Once()165 mockPaned.EXPECT().SetResizeStartChild(true).Once()
151 mockPaned.EXPECT().SetResizeEndChild(true).Once()166 mockPaned.EXPECT().SetResizeEndChild(true).Once()
152- mockPaned.EXPECT().SetShrinkStartChild(false).Once()167+ mockPaned.EXPECT().SetVisible(true).Once()
153- mockPaned.EXPECT().SetShrinkEndChild(false).Once()168+ mockTopStackBox.EXPECT().SetVisible(true).Once()
154- mockPaned.EXPECT().SetStartChild(mockTopWidget).Once()169+ mockBottomStackBox.EXPECT().SetVisible(true).Once()
155- mockPaned.EXPECT().SetEndChild(mockBottomWidget).Once()170+ mockPaned.EXPECT().SetStartChild(mockTopStackBox).Once()
171+ mockPaned.EXPECT().SetEndChild(mockBottomStackBox).Once()
156 mockPaned.EXPECT().GetAllocatedHeight().Return(0).Once()172 mockPaned.EXPECT().GetAllocatedHeight().Return(0).Once()
157 mockPaned.EXPECT().ConnectMap(mock.Anything).Return(uint32(0)).Once()173 mockPaned.EXPECT().ConnectMap(mock.Anything).Return(uint32(0)).Once()
158 mockPaned.EXPECT().AddTickCallback(mock.Anything).Return(uint(0)).Once()174 mockPaned.EXPECT().AddTickCallback(mock.Anything).Return(uint(0)).Once()
@@ -174,9 +190,9 @@ func TestBuild_NestedSplits(t *testing.T) {
174 mockPaneViewFactory := mocks.NewMockPaneViewFactory(t)190 mockPaneViewFactory := mocks.NewMockPaneViewFactory(t)
175 mockOuterPaned := mocks.NewMockPanedWidget(t)191 mockOuterPaned := mocks.NewMockPanedWidget(t)
176 mockInnerPaned := mocks.NewMockPanedWidget(t)192 mockInnerPaned := mocks.NewMockPanedWidget(t)
177- mockWidget1 := mocks.NewMockWidget(t)193+ mockStackBox1, mockContainer1 := setupStackedLeafMocks(t, mockFactory)
178- mockWidget2 := mocks.NewMockWidget(t)194+ mockStackBox2, mockContainer2 := setupStackedLeafMocks(t, mockFactory)
179- mockWidget3 := mocks.NewMockWidget(t)195+ mockStackBox3, mockContainer3 := setupStackedLeafMocks(t, mockFactory)
180 196
181 // Create tree structure:197 // Create tree structure:
182 // outer (horizontal)198 // outer (horizontal)
@@ -208,18 +224,19 @@ func TestBuild_NestedSplits(t *testing.T) {
208 }224 }
209 225
210 // Setup expectations for leaf nodes226 // Setup expectations for leaf nodes
211- mockPaneViewFactory.EXPECT().CreatePaneView(node1).Return(mockWidget1).Once()227+ mockPaneViewFactory.EXPECT().CreatePaneView(node1).Return(mockContainer1).Once()
212- mockPaneViewFactory.EXPECT().CreatePaneView(node2).Return(mockWidget2).Once()228+ mockPaneViewFactory.EXPECT().CreatePaneView(node2).Return(mockContainer2).Once()
213- mockPaneViewFactory.EXPECT().CreatePaneView(node3).Return(mockWidget3).Once()229+ mockPaneViewFactory.EXPECT().CreatePaneView(node3).Return(mockContainer3).Once()
214 230
215 // Inner split (vertical) is created first231 // Inner split (vertical) is created first
216 mockFactory.EXPECT().NewPaned(layout.OrientationVertical).Return(mockInnerPaned).Once()232 mockFactory.EXPECT().NewPaned(layout.OrientationVertical).Return(mockInnerPaned).Once()
217 mockInnerPaned.EXPECT().SetResizeStartChild(true).Once()233 mockInnerPaned.EXPECT().SetResizeStartChild(true).Once()
218 mockInnerPaned.EXPECT().SetResizeEndChild(true).Once()234 mockInnerPaned.EXPECT().SetResizeEndChild(true).Once()
219- mockInnerPaned.EXPECT().SetShrinkStartChild(false).Once()235+ mockInnerPaned.EXPECT().SetVisible(true).Once()
220- mockInnerPaned.EXPECT().SetShrinkEndChild(false).Once()236+ mockStackBox1.EXPECT().SetVisible(true).Once()
221- mockInnerPaned.EXPECT().SetStartChild(mockWidget1).Once()237+ mockStackBox2.EXPECT().SetVisible(true).Once()
222- mockInnerPaned.EXPECT().SetEndChild(mockWidget2).Once()238+ mockInnerPaned.EXPECT().SetStartChild(mockStackBox1).Once()
239+ mockInnerPaned.EXPECT().SetEndChild(mockStackBox2).Once()
223 mockInnerPaned.EXPECT().GetAllocatedHeight().Return(0).Once()240 mockInnerPaned.EXPECT().GetAllocatedHeight().Return(0).Once()
224 mockInnerPaned.EXPECT().ConnectMap(mock.Anything).Return(uint32(0)).Once()241 mockInnerPaned.EXPECT().ConnectMap(mock.Anything).Return(uint32(0)).Once()
225 mockInnerPaned.EXPECT().AddTickCallback(mock.Anything).Return(uint(0)).Once()242 mockInnerPaned.EXPECT().AddTickCallback(mock.Anything).Return(uint(0)).Once()
@@ -228,10 +245,11 @@ func TestBuild_NestedSplits(t *testing.T) {
228 mockFactory.EXPECT().NewPaned(layout.OrientationHorizontal).Return(mockOuterPaned).Once()245 mockFactory.EXPECT().NewPaned(layout.OrientationHorizontal).Return(mockOuterPaned).Once()
229 mockOuterPaned.EXPECT().SetResizeStartChild(true).Once()246 mockOuterPaned.EXPECT().SetResizeStartChild(true).Once()
230 mockOuterPaned.EXPECT().SetResizeEndChild(true).Once()247 mockOuterPaned.EXPECT().SetResizeEndChild(true).Once()
231- mockOuterPaned.EXPECT().SetShrinkStartChild(false).Once()248+ mockOuterPaned.EXPECT().SetVisible(true).Once()
232- mockOuterPaned.EXPECT().SetShrinkEndChild(false).Once()249+ mockInnerPaned.EXPECT().SetVisible(true).Once()
250+ mockStackBox3.EXPECT().SetVisible(true).Once()
233 mockOuterPaned.EXPECT().SetStartChild(mockInnerPaned).Once()251 mockOuterPaned.EXPECT().SetStartChild(mockInnerPaned).Once()
234- mockOuterPaned.EXPECT().SetEndChild(mockWidget3).Once()252+ mockOuterPaned.EXPECT().SetEndChild(mockStackBox3).Once()
235 mockOuterPaned.EXPECT().GetAllocatedWidth().Return(0).Once()253 mockOuterPaned.EXPECT().GetAllocatedWidth().Return(0).Once()
236 mockOuterPaned.EXPECT().ConnectMap(mock.Anything).Return(uint32(0)).Once()254 mockOuterPaned.EXPECT().ConnectMap(mock.Anything).Return(uint32(0)).Once()
237 mockOuterPaned.EXPECT().AddTickCallback(mock.Anything).Return(uint(0)).Once()255 mockOuterPaned.EXPECT().AddTickCallback(mock.Anything).Return(uint(0)).Once()
@@ -257,12 +275,12 @@ func TestLookup_ExistingNode(t *testing.T) {
257 ctx := context.Background()275 ctx := context.Background()
258 mockFactory := mocks.NewMockWidgetFactory(t)276 mockFactory := mocks.NewMockWidgetFactory(t)
259 mockPaneViewFactory := mocks.NewMockPaneViewFactory(t)277 mockPaneViewFactory := mocks.NewMockPaneViewFactory(t)
260- mockWidget := mocks.NewMockWidget(t)278+ mockStackBox, mockContainer := setupStackedLeafMocks(t, mockFactory)
261 279
262 pane := entity.NewPane(entity.PaneID("pane-1"))280 pane := entity.NewPane(entity.PaneID("pane-1"))
263 node := &entity.PaneNode{ID: "test-node-id", Pane: pane}281 node := &entity.PaneNode{ID: "test-node-id", Pane: pane}
264 282
265- mockPaneViewFactory.EXPECT().CreatePaneView(node).Return(mockWidget).Once()283+ mockPaneViewFactory.EXPECT().CreatePaneView(node).Return(mockContainer).Once()
266 284
267 renderer := layout.NewTreeRenderer(ctx, mockFactory, mockPaneViewFactory)285 renderer := layout.NewTreeRenderer(ctx, mockFactory, mockPaneViewFactory)
268 _, _ = renderer.Build(ctx, node)286 _, _ = renderer.Build(ctx, node)
@@ -271,7 +289,7 @@ func TestLookup_ExistingNode(t *testing.T) {
271 result := renderer.Lookup("test-node-id")289 result := renderer.Lookup("test-node-id")
272 290
273 // Assert291 // Assert
274- assert.Equal(t, mockWidget, result)292+ assert.Equal(t, mockStackBox, result)
275 }293 }
276 294
277 func TestLookup_NonExistentNode(t *testing.T) {295 func TestLookup_NonExistentNode(t *testing.T) {
@@ -279,12 +297,12 @@ func TestLookup_NonExistentNode(t *testing.T) {
279 ctx := context.Background()297 ctx := context.Background()
280 mockFactory := mocks.NewMockWidgetFactory(t)298 mockFactory := mocks.NewMockWidgetFactory(t)
281 mockPaneViewFactory := mocks.NewMockPaneViewFactory(t)299 mockPaneViewFactory := mocks.NewMockPaneViewFactory(t)
282- mockWidget := mocks.NewMockWidget(t)300+ _, mockContainer := setupStackedLeafMocks(t, mockFactory)
283 301
284 pane := entity.NewPane(entity.PaneID("pane-1"))302 pane := entity.NewPane(entity.PaneID("pane-1"))
285 node := &entity.PaneNode{ID: "node-1", Pane: pane}303 node := &entity.PaneNode{ID: "node-1", Pane: pane}
286 304
287- mockPaneViewFactory.EXPECT().CreatePaneView(node).Return(mockWidget).Once()305+ mockPaneViewFactory.EXPECT().CreatePaneView(node).Return(mockContainer).Once()
288 306
289 renderer := layout.NewTreeRenderer(ctx, mockFactory, mockPaneViewFactory)307 renderer := layout.NewTreeRenderer(ctx, mockFactory, mockPaneViewFactory)
290 _, _ = renderer.Build(ctx, node)308 _, _ = renderer.Build(ctx, node)
@@ -301,12 +319,12 @@ func TestLookupNode_Existing(t *testing.T) {
301 ctx := context.Background()319 ctx := context.Background()
302 mockFactory := mocks.NewMockWidgetFactory(t)320 mockFactory := mocks.NewMockWidgetFactory(t)
303 mockPaneViewFactory := mocks.NewMockPaneViewFactory(t)321 mockPaneViewFactory := mocks.NewMockPaneViewFactory(t)
304- mockWidget := mocks.NewMockWidget(t)322+ mockStackBox, mockContainer := setupStackedLeafMocks(t, mockFactory)
305 323
306 pane := entity.NewPane(entity.PaneID("pane-1"))324 pane := entity.NewPane(entity.PaneID("pane-1"))
307 node := &entity.PaneNode{ID: "node-1", Pane: pane}325 node := &entity.PaneNode{ID: "node-1", Pane: pane}
308 326
309- mockPaneViewFactory.EXPECT().CreatePaneView(node).Return(mockWidget).Once()327+ mockPaneViewFactory.EXPECT().CreatePaneView(node).Return(mockContainer).Once()
310 328
311 renderer := layout.NewTreeRenderer(ctx, mockFactory, mockPaneViewFactory)329 renderer := layout.NewTreeRenderer(ctx, mockFactory, mockPaneViewFactory)
312 _, _ = renderer.Build(ctx, node)330 _, _ = renderer.Build(ctx, node)
@@ -316,7 +334,7 @@ func TestLookupNode_Existing(t *testing.T) {
316 334
317 // Assert335 // Assert
318 assert.True(t, found)336 assert.True(t, found)
319- assert.Equal(t, mockWidget, widget)337+ assert.Equal(t, mockStackBox, widget)
320 }338 }
321 339
322 func TestLookupNode_NonExistent(t *testing.T) {340 func TestLookupNode_NonExistent(t *testing.T) {
@@ -340,12 +358,12 @@ func TestClear_RemovesAllMappings(t *testing.T) {
340 ctx := context.Background()358 ctx := context.Background()
341 mockFactory := mocks.NewMockWidgetFactory(t)359 mockFactory := mocks.NewMockWidgetFactory(t)
342 mockPaneViewFactory := mocks.NewMockPaneViewFactory(t)360 mockPaneViewFactory := mocks.NewMockPaneViewFactory(t)
343- mockWidget := mocks.NewMockWidget(t)361+ _, mockContainer := setupStackedLeafMocks(t, mockFactory)
344 362
345 pane := entity.NewPane(entity.PaneID("pane-1"))363 pane := entity.NewPane(entity.PaneID("pane-1"))
346 node := &entity.PaneNode{ID: "node-1", Pane: pane}364 node := &entity.PaneNode{ID: "node-1", Pane: pane}
347 365
348- mockPaneViewFactory.EXPECT().CreatePaneView(node).Return(mockWidget).Once()366+ mockPaneViewFactory.EXPECT().CreatePaneView(node).Return(mockContainer).Once()
349 367
350 renderer := layout.NewTreeRenderer(ctx, mockFactory, mockPaneViewFactory)368 renderer := layout.NewTreeRenderer(ctx, mockFactory, mockPaneViewFactory)
351 _, _ = renderer.Build(ctx, node)369 _, _ = renderer.Build(ctx, node)
@@ -366,8 +384,8 @@ func TestGetNodeIDs_ReturnsAllIDs(t *testing.T) {
366 ctx := context.Background()384 ctx := context.Background()
367 mockPaneViewFactory := mocks.NewMockPaneViewFactory(t)385 mockPaneViewFactory := mocks.NewMockPaneViewFactory(t)
368 mockPaned := mocks.NewMockPanedWidget(t)386 mockPaned := mocks.NewMockPanedWidget(t)
369- mockWidget1 := mocks.NewMockWidget(t)387+ mockLeftStackBox, mockLeftContainer := setupStackedLeafMocks(t, mockFactory)
370- mockWidget2 := mocks.NewMockWidget(t)388+ mockRightStackBox, mockRightContainer := setupStackedLeafMocks(t, mockFactory)
371 389
372 pane1 := entity.NewPane(entity.PaneID("pane-1"))390 pane1 := entity.NewPane(entity.PaneID("pane-1"))
373 pane2 := entity.NewPane(entity.PaneID("pane-2"))391 pane2 := entity.NewPane(entity.PaneID("pane-2"))
@@ -382,16 +400,17 @@ func TestGetNodeIDs_ReturnsAllIDs(t *testing.T) {
382 Children: []*entity.PaneNode{node1, node2},400 Children: []*entity.PaneNode{node1, node2},
383 }401 }
384 402
385- mockPaneViewFactory.EXPECT().CreatePaneView(node1).Return(mockWidget1).Once()403+ mockPaneViewFactory.EXPECT().CreatePaneView(node1).Return(mockLeftContainer).Once()
386- mockPaneViewFactory.EXPECT().CreatePaneView(node2).Return(mockWidget2).Once()404+ mockPaneViewFactory.EXPECT().CreatePaneView(node2).Return(mockRightContainer).Once()
387 405
388 mockFactory.EXPECT().NewPaned(layout.OrientationHorizontal).Return(mockPaned).Once()406 mockFactory.EXPECT().NewPaned(layout.OrientationHorizontal).Return(mockPaned).Once()
389 mockPaned.EXPECT().SetResizeStartChild(true).Once()407 mockPaned.EXPECT().SetResizeStartChild(true).Once()
390 mockPaned.EXPECT().SetResizeEndChild(true).Once()408 mockPaned.EXPECT().SetResizeEndChild(true).Once()
391- mockPaned.EXPECT().SetShrinkStartChild(false).Once()409+ mockPaned.EXPECT().SetVisible(true).Once()
392- mockPaned.EXPECT().SetShrinkEndChild(false).Once()410+ mockLeftStackBox.EXPECT().SetVisible(true).Once()
393- mockPaned.EXPECT().SetStartChild(mockWidget1).Once()411+ mockRightStackBox.EXPECT().SetVisible(true).Once()
394- mockPaned.EXPECT().SetEndChild(mockWidget2).Once()412+ mockPaned.EXPECT().SetStartChild(mockLeftStackBox).Once()
413+ mockPaned.EXPECT().SetEndChild(mockRightStackBox).Once()
395 mockPaned.EXPECT().GetAllocatedWidth().Return(0).Once()414 mockPaned.EXPECT().GetAllocatedWidth().Return(0).Once()
396 mockPaned.EXPECT().ConnectMap(mock.Anything).Return(uint32(0)).Once()415 mockPaned.EXPECT().ConnectMap(mock.Anything).Return(uint32(0)).Once()
397 mockPaned.EXPECT().AddTickCallback(mock.Anything).Return(uint(0)).Once()416 mockPaned.EXPECT().AddTickCallback(mock.Anything).Return(uint(0)).Once()
@@ -414,8 +433,8 @@ func TestBuild_ClearsPreviousMappings(t *testing.T) {
414 ctx := context.Background()433 ctx := context.Background()
415 mockFactory := mocks.NewMockWidgetFactory(t)434 mockFactory := mocks.NewMockWidgetFactory(t)
416 mockPaneViewFactory := mocks.NewMockPaneViewFactory(t)435 mockPaneViewFactory := mocks.NewMockPaneViewFactory(t)
417- mockWidget1 := mocks.NewMockWidget(t)436+ mockOldStackBox, mockOldContainer := setupStackedLeafMocks(t, mockFactory)
418- mockWidget2 := mocks.NewMockWidget(t)437+ mockNewStackBox, mockNewContainer := setupStackedLeafMocks(t, mockFactory)
419 438
420 pane1 := entity.NewPane(entity.PaneID("pane-1"))439 pane1 := entity.NewPane(entity.PaneID("pane-1"))
421 node1 := &entity.PaneNode{ID: "old-node", Pane: pane1}440 node1 := &entity.PaneNode{ID: "old-node", Pane: pane1}
@@ -423,15 +442,15 @@ func TestBuild_ClearsPreviousMappings(t *testing.T) {
423 pane2 := entity.NewPane(entity.PaneID("pane-2"))442 pane2 := entity.NewPane(entity.PaneID("pane-2"))
424 node2 := &entity.PaneNode{ID: "new-node", Pane: pane2}443 node2 := &entity.PaneNode{ID: "new-node", Pane: pane2}
425 444
426- mockPaneViewFactory.EXPECT().CreatePaneView(node1).Return(mockWidget1).Once()445+ mockPaneViewFactory.EXPECT().CreatePaneView(node1).Return(mockOldContainer).Once()
427- mockPaneViewFactory.EXPECT().CreatePaneView(node2).Return(mockWidget2).Once()446+ mockPaneViewFactory.EXPECT().CreatePaneView(node2).Return(mockNewContainer).Once()
428 447
429 renderer := layout.NewTreeRenderer(ctx, mockFactory, mockPaneViewFactory)448 renderer := layout.NewTreeRenderer(ctx, mockFactory, mockPaneViewFactory)
430 449
431 // First build450 // First build
432 _, _ = renderer.Build(ctx, node1)451 _, _ = renderer.Build(ctx, node1)
433 require.Equal(t, 1, renderer.NodeCount())452 require.Equal(t, 1, renderer.NodeCount())
434- require.NotNil(t, renderer.Lookup("old-node"))453+ require.Equal(t, mockOldStackBox, renderer.Lookup("old-node"))
435 454
436 // Act - second build should clear old mappings455 // Act - second build should clear old mappings
437 _, _ = renderer.Build(ctx, node2)456 _, _ = renderer.Build(ctx, node2)
@@ -439,7 +458,7 @@ func TestBuild_ClearsPreviousMappings(t *testing.T) {
439 // Assert458 // Assert
440 assert.Equal(t, 1, renderer.NodeCount())459 assert.Equal(t, 1, renderer.NodeCount())
441 assert.Nil(t, renderer.Lookup("old-node"))460 assert.Nil(t, renderer.Lookup("old-node"))
442- assert.NotNil(t, renderer.Lookup("new-node"))461+ assert.Equal(t, mockNewStackBox, renderer.Lookup("new-node"))
443 }462 }
444 463
445 func TestBuild_NilPaneViewFactory_ReturnsNilForLeaves(t *testing.T) {464 func TestBuild_NilPaneViewFactory_ReturnsNilForLeaves(t *testing.T) {
@@ -465,12 +484,12 @@ func TestBuild_EmptyNodeID_NotStored(t *testing.T) {
465 ctx := context.Background()484 ctx := context.Background()
466 mockFactory := mocks.NewMockWidgetFactory(t)485 mockFactory := mocks.NewMockWidgetFactory(t)
467 mockPaneViewFactory := mocks.NewMockPaneViewFactory(t)486 mockPaneViewFactory := mocks.NewMockPaneViewFactory(t)
468- mockWidget := mocks.NewMockWidget(t)487+ _, mockContainer := setupStackedLeafMocks(t, mockFactory)
469 488
470 pane := entity.NewPane(entity.PaneID("pane-1"))489 pane := entity.NewPane(entity.PaneID("pane-1"))
471 node := &entity.PaneNode{ID: "", Pane: pane} // Empty ID490 node := &entity.PaneNode{ID: "", Pane: pane} // Empty ID
472 491
473- mockPaneViewFactory.EXPECT().CreatePaneView(node).Return(mockWidget).Once()492+ mockPaneViewFactory.EXPECT().CreatePaneView(node).Return(mockContainer).Once()
474 493
475 renderer := layout.NewTreeRenderer(ctx, mockFactory, mockPaneViewFactory)494 renderer := layout.NewTreeRenderer(ctx, mockFactory, mockPaneViewFactory)
476 495
@@ -489,8 +508,8 @@ func TestUpdateSplitRatio_ExistingNode(t *testing.T) {
489 ctx := context.Background()508 ctx := context.Background()
490 mockPaneViewFactory := mocks.NewMockPaneViewFactory(t)509 mockPaneViewFactory := mocks.NewMockPaneViewFactory(t)
491 mockPaned := mocks.NewMockPanedWidget(t)510 mockPaned := mocks.NewMockPanedWidget(t)
492- mockWidget1 := mocks.NewMockWidget(t)511+ mockLeftStackBox, mockLeftContainer := setupStackedLeafMocks(t, mockFactory)
493- mockWidget2 := mocks.NewMockWidget(t)512+ mockRightStackBox, mockRightContainer := setupStackedLeafMocks(t, mockFactory)
494 513
495 pane1 := entity.NewPane(entity.PaneID("pane-1"))514 pane1 := entity.NewPane(entity.PaneID("pane-1"))
496 pane2 := entity.NewPane(entity.PaneID("pane-2"))515 pane2 := entity.NewPane(entity.PaneID("pane-2"))
@@ -505,16 +524,17 @@ func TestUpdateSplitRatio_ExistingNode(t *testing.T) {
505 Children: []*entity.PaneNode{node1, node2},524 Children: []*entity.PaneNode{node1, node2},
506 }525 }
507 526
508- mockPaneViewFactory.EXPECT().CreatePaneView(node1).Return(mockWidget1).Once()527+ mockPaneViewFactory.EXPECT().CreatePaneView(node1).Return(mockLeftContainer).Once()
509- mockPaneViewFactory.EXPECT().CreatePaneView(node2).Return(mockWidget2).Once()528+ mockPaneViewFactory.EXPECT().CreatePaneView(node2).Return(mockRightContainer).Once()
510 529
511 mockFactory.EXPECT().NewPaned(layout.OrientationHorizontal).Return(mockPaned).Once()530 mockFactory.EXPECT().NewPaned(layout.OrientationHorizontal).Return(mockPaned).Once()
512 mockPaned.EXPECT().SetResizeStartChild(true).Once()531 mockPaned.EXPECT().SetResizeStartChild(true).Once()
513 mockPaned.EXPECT().SetResizeEndChild(true).Once()532 mockPaned.EXPECT().SetResizeEndChild(true).Once()
514- mockPaned.EXPECT().SetShrinkStartChild(false).Once()533+ mockPaned.EXPECT().SetVisible(true).Once()
515- mockPaned.EXPECT().SetShrinkEndChild(false).Once()534+ mockLeftStackBox.EXPECT().SetVisible(true).Once()
516- mockPaned.EXPECT().SetStartChild(mockWidget1).Once()535+ mockRightStackBox.EXPECT().SetVisible(true).Once()
517- mockPaned.EXPECT().SetEndChild(mockWidget2).Once()536+ mockPaned.EXPECT().SetStartChild(mockLeftStackBox).Once()
537+ mockPaned.EXPECT().SetEndChild(mockRightStackBox).Once()
518 mockPaned.EXPECT().GetAllocatedWidth().Return(0).Once()538 mockPaned.EXPECT().GetAllocatedWidth().Return(0).Once()
519 mockPaned.EXPECT().ConnectMap(mock.Anything).Return(uint32(0)).Once()539 mockPaned.EXPECT().ConnectMap(mock.Anything).Return(uint32(0)).Once()
520 mockPaned.EXPECT().AddTickCallback(mock.Anything).Return(uint(0)).Once()540 mockPaned.EXPECT().AddTickCallback(mock.Anything).Return(uint(0)).Once()

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