1package layout_test
2
3import (
4 "context"
5 "testing"
6
7 "github.com/stretchr/testify/assert"
8 "github.com/stretchr/testify/mock"
9 "github.com/stretchr/testify/require"
10
11 "github.com/bnema/dumber/internal/ui/layout"
12 "github.com/bnema/dumber/internal/ui/layout/mocks"
13)
14
15// setupSplitViewMocks configures the common mock expectations for NewSplitView.
16// Since the mock paned returns 0 for GetAllocatedWidth/Height, ApplyRatio will
17// return false and trigger the ConnectMap and AddTickCallback registrations.
18func setupSplitViewMocks(mockPaned *mocks.MockPanedWidget, orientation layout.Orientation) {
19 mockPaned.EXPECT().SetResizeStartChild(true).Once()
20 mockPaned.EXPECT().SetResizeEndChild(true).Once()
21 mockPaned.EXPECT().SetVisible(true).Once()
22 // Note: SetShrinkStartChild/SetShrinkEndChild are NOT called - allowing shrink
23 // enables GTK to respect the 50/50 ratio even if children have larger natural sizes
24
25 // ApplyRatio is called immediately, which calls GetAllocatedWidth/Height
26 if orientation == layout.OrientationHorizontal {
27 mockPaned.EXPECT().GetAllocatedWidth().Return(0).Once()
28 } else {
29 mockPaned.EXPECT().GetAllocatedHeight().Return(0).Once()
30 }
31
32 // Since allocation is 0, ApplyRatio returns false and callbacks are registered
33 mockPaned.EXPECT().ConnectMap(mock.Anything).Return(uint32(0)).Once()
34 mockPaned.EXPECT().AddTickCallback(mock.Anything).Return(uint(0)).Once()
35}
36
37func TestNewSplitView_Horizontal(t *testing.T) {
38 // Arrange
39 ctx := context.Background()
40 mockFactory := mocks.NewMockWidgetFactory(t)
41 mockPaned := mocks.NewMockPanedWidget(t)
42 mockStartChild := mocks.NewMockWidget(t)
43 mockEndChild := mocks.NewMockWidget(t)
44
45 mockFactory.EXPECT().NewPaned(layout.OrientationHorizontal).Return(mockPaned).Once()
46 setupSplitViewMocks(mockPaned, layout.OrientationHorizontal)
47 mockStartChild.EXPECT().SetVisible(true).Once()
48 mockEndChild.EXPECT().SetVisible(true).Once()
49 mockPaned.EXPECT().SetStartChild(mockStartChild).Once()
50 mockPaned.EXPECT().SetEndChild(mockEndChild).Once()
51
52 // Act
53 sv := layout.NewSplitView(ctx, mockFactory, layout.OrientationHorizontal, mockStartChild, mockEndChild, 0.5)
54
55 // Assert
56 require.NotNil(t, sv)
57 assert.Equal(t, layout.OrientationHorizontal, sv.Orientation())
58 assert.InDelta(t, 0.5, sv.GetRatio(), 0.0001)
59 assert.Equal(t, mockStartChild, sv.StartChild())
60 assert.Equal(t, mockEndChild, sv.EndChild())
61}
62
63func TestNewSplitView_Vertical(t *testing.T) {
64 // Arrange
65 ctx := context.Background()
66 mockFactory := mocks.NewMockWidgetFactory(t)
67 mockPaned := mocks.NewMockPanedWidget(t)
68 mockStartChild := mocks.NewMockWidget(t)
69 mockEndChild := mocks.NewMockWidget(t)
70
71 mockFactory.EXPECT().NewPaned(layout.OrientationVertical).Return(mockPaned).Once()
72 setupSplitViewMocks(mockPaned, layout.OrientationVertical)
73 mockStartChild.EXPECT().SetVisible(true).Once()
74 mockEndChild.EXPECT().SetVisible(true).Once()
75 mockPaned.EXPECT().SetStartChild(mockStartChild).Once()
76 mockPaned.EXPECT().SetEndChild(mockEndChild).Once()
77
78 // Act
79 sv := layout.NewSplitView(ctx, mockFactory, layout.OrientationVertical, mockStartChild, mockEndChild, 0.5)
80
81 // Assert
82 require.NotNil(t, sv)
83 assert.Equal(t, layout.OrientationVertical, sv.Orientation())
84 assert.InDelta(t, 0.5, sv.GetRatio(), 0.0001)
85 assert.Equal(t, mockStartChild, sv.StartChild())
86 assert.Equal(t, mockEndChild, sv.EndChild())
87}
88
89func TestNewSplitView_NilChildren(t *testing.T) {
90 // Arrange
91 ctx := context.Background()
92 mockFactory := mocks.NewMockWidgetFactory(t)
93 mockPaned := mocks.NewMockPanedWidget(t)
94
95 mockFactory.EXPECT().NewPaned(layout.OrientationHorizontal).Return(mockPaned).Once()
96 setupSplitViewMocks(mockPaned, layout.OrientationHorizontal)
97 // SetStartChild and SetEndChild should NOT be called when children are nil
98
99 // Act
100 sv := layout.NewSplitView(ctx, mockFactory, layout.OrientationHorizontal, nil, nil, 0.5)
101
102 // Assert
103 require.NotNil(t, sv)
104 assert.Nil(t, sv.StartChild())
105 assert.Nil(t, sv.EndChild())
106}
107
108func TestSetRatio_ValidRange(t *testing.T) {
109 tests := []struct {
110 name string
111 input float64
112 expected float64
113 }{
114 {"zero", 0.0, 0.0},
115 {"one", 1.0, 1.0},
116 {"middle", 0.5, 0.5},
117 {"quarter", 0.25, 0.25},
118 {"three_quarters", 0.75, 0.75},
119 }
120
121 for _, tt := range tests {
122 t.Run(tt.name, func(t *testing.T) {
123 ctx := context.Background()
124 // Arrange
125 mockFactory := mocks.NewMockWidgetFactory(t)
126 mockPaned := mocks.NewMockPanedWidget(t)
127
128 mockFactory.EXPECT().NewPaned(mock.Anything).Return(mockPaned).Once()
129 setupSplitViewMocks(mockPaned, layout.OrientationHorizontal)
130
131 sv := layout.NewSplitView(ctx, mockFactory, layout.OrientationHorizontal, nil, nil, 0.5)
132
133 // Act
134 sv.SetRatio(tt.input)
135
136 // Assert
137 assert.InDelta(t, tt.expected, sv.GetRatio(), 0.0001)
138 })
139 }
140}
141
142func TestSetRatio_OutOfRange_Clamped(t *testing.T) {
143 tests := []struct {
144 name string
145 input float64
146 expected float64
147 }{
148 {"negative", -0.5, 0.0},
149 {"very_negative", -100.0, 0.0},
150 {"greater_than_one", 1.5, 1.0},
151 {"much_greater", 100.0, 1.0},
152 }
153
154 for _, tt := range tests {
155 t.Run(tt.name, func(t *testing.T) {
156 ctx := context.Background()
157 // Arrange
158 mockFactory := mocks.NewMockWidgetFactory(t)
159 mockPaned := mocks.NewMockPanedWidget(t)
160
161 mockFactory.EXPECT().NewPaned(mock.Anything).Return(mockPaned).Once()
162 setupSplitViewMocks(mockPaned, layout.OrientationHorizontal)
163
164 sv := layout.NewSplitView(ctx, mockFactory, layout.OrientationHorizontal, nil, nil, 0.5)
165
166 // Act
167 sv.SetRatio(tt.input)
168
169 // Assert
170 assert.InDelta(t, tt.expected, sv.GetRatio(), 0.0001)
171 })
172 }
173}
174
175func TestSwapStart_ReplacesChild(t *testing.T) {
176 // Arrange
177 ctx := context.Background()
178 mockFactory := mocks.NewMockWidgetFactory(t)
179 mockPaned := mocks.NewMockPanedWidget(t)
180 mockOldChild := mocks.NewMockWidget(t)
181 mockNewChild := mocks.NewMockWidget(t)
182
183 mockFactory.EXPECT().NewPaned(mock.Anything).Return(mockPaned).Once()
184 setupSplitViewMocks(mockPaned, layout.OrientationHorizontal)
185 mockOldChild.EXPECT().SetVisible(true).Once()
186 mockPaned.EXPECT().SetStartChild(mockOldChild).Once()
187
188 sv := layout.NewSplitView(ctx, mockFactory, layout.OrientationHorizontal, mockOldChild, nil, 0.5)
189
190 // Expect swap operations
191 mockPaned.EXPECT().SetStartChild(nil).Once() // Remove old
192 mockPaned.EXPECT().SetStartChild(mockNewChild).Once() // Add new
193
194 // Act
195 sv.SwapStart(mockNewChild)
196
197 // Assert
198 assert.Equal(t, mockNewChild, sv.StartChild())
199}
200
201func TestSwapEnd_ReplacesChild(t *testing.T) {
202 // Arrange
203 ctx := context.Background()
204 mockFactory := mocks.NewMockWidgetFactory(t)
205 mockPaned := mocks.NewMockPanedWidget(t)
206 mockOldChild := mocks.NewMockWidget(t)
207 mockNewChild := mocks.NewMockWidget(t)
208
209 mockFactory.EXPECT().NewPaned(mock.Anything).Return(mockPaned).Once()
210 setupSplitViewMocks(mockPaned, layout.OrientationHorizontal)
211 mockOldChild.EXPECT().SetVisible(true).Once()
212 mockPaned.EXPECT().SetEndChild(mockOldChild).Once()
213
214 sv := layout.NewSplitView(ctx, mockFactory, layout.OrientationHorizontal, nil, mockOldChild, 0.5)
215
216 // Expect swap operations
217 mockPaned.EXPECT().SetEndChild(nil).Once() // Remove old
218 mockPaned.EXPECT().SetEndChild(mockNewChild).Once() // Add new
219
220 // Act
221 sv.SwapEnd(mockNewChild)
222
223 // Assert
224 assert.Equal(t, mockNewChild, sv.EndChild())
225}
226
227func TestSwapStart_FromNil(t *testing.T) {
228 // Arrange
229 ctx := context.Background()
230 mockFactory := mocks.NewMockWidgetFactory(t)
231 mockPaned := mocks.NewMockPanedWidget(t)
232 mockNewChild := mocks.NewMockWidget(t)
233
234 mockFactory.EXPECT().NewPaned(mock.Anything).Return(mockPaned).Once()
235 setupSplitViewMocks(mockPaned, layout.OrientationHorizontal)
236
237 sv := layout.NewSplitView(ctx, mockFactory, layout.OrientationHorizontal, nil, nil, 0.5)
238
239 // Expect only adding new child (no removal since old was nil)
240 mockPaned.EXPECT().SetStartChild(mockNewChild).Once()
241
242 // Act
243 sv.SwapStart(mockNewChild)
244
245 // Assert
246 assert.Equal(t, mockNewChild, sv.StartChild())
247}
248
249func TestSwapEnd_ToNil(t *testing.T) {
250 // Arrange
251 ctx := context.Background()
252 mockFactory := mocks.NewMockWidgetFactory(t)
253 mockPaned := mocks.NewMockPanedWidget(t)
254 mockOldChild := mocks.NewMockWidget(t)
255
256 mockFactory.EXPECT().NewPaned(mock.Anything).Return(mockPaned).Once()
257 setupSplitViewMocks(mockPaned, layout.OrientationHorizontal)
258 mockOldChild.EXPECT().SetVisible(true).Once()
259 mockPaned.EXPECT().SetEndChild(mockOldChild).Once()
260
261 sv := layout.NewSplitView(ctx, mockFactory, layout.OrientationHorizontal, nil, mockOldChild, 0.5)
262
263 // Expect removal (SetEndChild(nil) called for clearing old)
264 mockPaned.EXPECT().SetEndChild(nil).Once()
265
266 // Act
267 sv.SwapEnd(nil)
268
269 // Assert
270 assert.Nil(t, sv.EndChild())
271}
272
273func TestWidget_ReturnsPanedWidget(t *testing.T) {
274 // Arrange
275 ctx := context.Background()
276 mockFactory := mocks.NewMockWidgetFactory(t)
277 mockPaned := mocks.NewMockPanedWidget(t)
278
279 mockFactory.EXPECT().NewPaned(mock.Anything).Return(mockPaned).Once()
280 setupSplitViewMocks(mockPaned, layout.OrientationHorizontal)
281
282 sv := layout.NewSplitView(ctx, mockFactory, layout.OrientationHorizontal, nil, nil, 0.5)
283
284 // Act
285 widget := sv.Widget()
286
287 // Assert
288 assert.Equal(t, mockPaned, widget)
289}
290
291func TestSetPosition_DelegatesToPaned(t *testing.T) {
292 // Arrange
293 ctx := context.Background()
294 mockFactory := mocks.NewMockWidgetFactory(t)
295 mockPaned := mocks.NewMockPanedWidget(t)
296
297 mockFactory.EXPECT().NewPaned(mock.Anything).Return(mockPaned).Once()
298 setupSplitViewMocks(mockPaned, layout.OrientationHorizontal)
299
300 sv := layout.NewSplitView(ctx, mockFactory, layout.OrientationHorizontal, nil, nil, 0.5)
301
302 mockPaned.EXPECT().SetPosition(400).Once()
303
304 // Act
305 sv.SetPosition(400)
306
307 // Assert - verified by mock expectations
308}
309
310func TestGetPosition_DelegatesToPaned(t *testing.T) {
311 // Arrange
312 ctx := context.Background()
313 mockFactory := mocks.NewMockWidgetFactory(t)
314 mockPaned := mocks.NewMockPanedWidget(t)
315
316 mockFactory.EXPECT().NewPaned(mock.Anything).Return(mockPaned).Once()
317 setupSplitViewMocks(mockPaned, layout.OrientationHorizontal)
318
319 sv := layout.NewSplitView(ctx, mockFactory, layout.OrientationHorizontal, nil, nil, 0.5)
320
321 mockPaned.EXPECT().GetPosition().Return(400).Once()
322
323 // Act
324 pos := sv.GetPosition()
325
326 // Assert
327 assert.Equal(t, 400, pos)
328}
329
330func TestSetWideHandle_DelegatesToPaned(t *testing.T) {
331 // Arrange
332 ctx := context.Background()
333 mockFactory := mocks.NewMockWidgetFactory(t)
334 mockPaned := mocks.NewMockPanedWidget(t)
335
336 mockFactory.EXPECT().NewPaned(mock.Anything).Return(mockPaned).Once()
337 setupSplitViewMocks(mockPaned, layout.OrientationHorizontal)
338
339 sv := layout.NewSplitView(ctx, mockFactory, layout.OrientationHorizontal, nil, nil, 0.5)
340
341 mockPaned.EXPECT().SetWideHandle(true).Once()
342
343 // Act
344 sv.SetWideHandle(true)
345
346 // Assert - verified by mock expectations
347}
348
349func TestNewSplitView_InitialRatio_Clamped(t *testing.T) {
350 tests := []struct {
351 name string
352 input float64
353 expected float64
354 }{
355 {"negative_clamped_to_zero", -0.5, 0.0},
356 {"greater_than_one_clamped", 1.5, 1.0},
357 {"valid_preserved", 0.3, 0.3},
358 }
359
360 for _, tt := range tests {
361 t.Run(tt.name, func(t *testing.T) {
362 ctx := context.Background()
363 // Arrange
364 mockFactory := mocks.NewMockWidgetFactory(t)
365 mockPaned := mocks.NewMockPanedWidget(t)
366
367 mockFactory.EXPECT().NewPaned(mock.Anything).Return(mockPaned).Once()
368 setupSplitViewMocks(mockPaned, layout.OrientationHorizontal)
369
370 // Act
371 sv := layout.NewSplitView(ctx, mockFactory, layout.OrientationHorizontal, nil, nil, tt.input)
372
373 // Assert
374 assert.InDelta(t, tt.expected, sv.GetRatio(), 0.0001)
375 })
376 }
377}