1package component_test
2
3import (
4 "testing"
5
6 "github.com/stretchr/testify/assert"
7 "github.com/stretchr/testify/require"
8
9 "github.com/bnema/dumber/internal/domain/entity"
10 "github.com/bnema/dumber/internal/ui/component"
11 "github.com/bnema/dumber/internal/ui/layout"
12 "github.com/bnema/dumber/internal/ui/layout/mocks"
13)
14
15func TestNewPaneView_CreatesOverlay(t *testing.T) {
16 // Arrange
17 mockFactory := mocks.NewMockWidgetFactory(t)
18 mockOverlay := mocks.NewMockOverlayWidget(t)
19 mockBorderBox := mocks.NewMockBoxWidget(t)
20 mockWebView := mocks.NewMockWidget(t)
21
22 paneID := entity.PaneID("pane-1")
23
24 mockFactory.EXPECT().NewOverlay().Return(mockOverlay).Once()
25 mockOverlay.EXPECT().SetHexpand(true).Once()
26 mockOverlay.EXPECT().SetVexpand(true).Once()
27 mockOverlay.EXPECT().SetVisible(true).Once()
28 mockOverlay.EXPECT().AddCssClass("pane-overlay").Once() // Theme background
29 mockOverlay.EXPECT().SetChild(mockWebView).Once()
30
31 mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBorderBox).Once()
32 mockBorderBox.EXPECT().SetCanFocus(false).Once()
33 mockBorderBox.EXPECT().SetCanTarget(false).Once()
34 mockBorderBox.EXPECT().AddCssClass("pane-border").Once()
35 mockBorderBox.EXPECT().SetHexpand(true).Once()
36 mockBorderBox.EXPECT().SetVexpand(true).Once()
37 mockOverlay.EXPECT().AddOverlay(mockBorderBox).Once()
38 mockOverlay.EXPECT().SetClipOverlay(mockBorderBox, false).Once()
39 mockOverlay.EXPECT().SetMeasureOverlay(mockBorderBox, false).Once()
40
41 // Act
42 pv := component.NewPaneView(mockFactory, paneID, mockWebView)
43
44 // Assert
45 require.NotNil(t, pv)
46 assert.Equal(t, paneID, pv.PaneID())
47 assert.Equal(t, mockWebView, pv.WebViewWidget())
48}
49
50func TestNewPaneView_NilWebView(t *testing.T) {
51 // Arrange
52 mockFactory := mocks.NewMockWidgetFactory(t)
53 mockOverlay := mocks.NewMockOverlayWidget(t)
54 mockBorderBox := mocks.NewMockBoxWidget(t)
55
56 paneID := entity.PaneID("pane-1")
57
58 mockFactory.EXPECT().NewOverlay().Return(mockOverlay).Once()
59 mockOverlay.EXPECT().SetHexpand(true).Once()
60 mockOverlay.EXPECT().SetVexpand(true).Once()
61 mockOverlay.EXPECT().SetVisible(true).Once()
62 mockOverlay.EXPECT().AddCssClass("pane-overlay").Once() // Theme background
63 // SetChild should NOT be called when webview is nil
64
65 mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBorderBox).Once()
66 mockBorderBox.EXPECT().SetCanFocus(false).Once()
67 mockBorderBox.EXPECT().SetCanTarget(false).Once()
68 mockBorderBox.EXPECT().AddCssClass("pane-border").Once()
69 mockBorderBox.EXPECT().SetHexpand(true).Once()
70 mockBorderBox.EXPECT().SetVexpand(true).Once()
71 mockOverlay.EXPECT().AddOverlay(mockBorderBox).Once()
72 mockOverlay.EXPECT().SetClipOverlay(mockBorderBox, false).Once()
73 mockOverlay.EXPECT().SetMeasureOverlay(mockBorderBox, false).Once()
74
75 // Act
76 pv := component.NewPaneView(mockFactory, paneID, nil)
77
78 // Assert
79 require.NotNil(t, pv)
80 assert.Nil(t, pv.WebViewWidget())
81}
82
83func TestSetActive_True_AddsCSSClass(t *testing.T) {
84 // Arrange
85 mockFactory := mocks.NewMockWidgetFactory(t)
86 mockOverlay := mocks.NewMockOverlayWidget(t)
87 mockBorderBox := mocks.NewMockBoxWidget(t)
88 mockWebView := mocks.NewMockWidget(t)
89
90 setupPaneViewMocks(mockFactory, mockOverlay, mockBorderBox, mockWebView)
91
92 pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), mockWebView)
93
94 // Expect CSS class to be added
95 mockBorderBox.EXPECT().AddCssClass("pane-active").Once()
96
97 // Act
98 pv.SetActive(true)
99
100 // Assert
101 assert.True(t, pv.IsActive())
102}
103
104func TestSetActive_False_RemovesCSSClass(t *testing.T) {
105 // Arrange
106 mockFactory := mocks.NewMockWidgetFactory(t)
107 mockOverlay := mocks.NewMockOverlayWidget(t)
108 mockBorderBox := mocks.NewMockBoxWidget(t)
109 mockWebView := mocks.NewMockWidget(t)
110
111 setupPaneViewMocks(mockFactory, mockOverlay, mockBorderBox, mockWebView)
112
113 pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), mockWebView)
114
115 // First activate
116 mockBorderBox.EXPECT().AddCssClass("pane-active").Once()
117 pv.SetActive(true)
118
119 // Then deactivate
120 mockBorderBox.EXPECT().RemoveCssClass("pane-active").Once()
121
122 // Act
123 pv.SetActive(false)
124
125 // Assert
126 assert.False(t, pv.IsActive())
127}
128
129func TestSetActive_NoChangeWhenSameState(t *testing.T) {
130 // Arrange
131 mockFactory := mocks.NewMockWidgetFactory(t)
132 mockOverlay := mocks.NewMockOverlayWidget(t)
133 mockBorderBox := mocks.NewMockBoxWidget(t)
134 mockWebView := mocks.NewMockWidget(t)
135
136 setupPaneViewMocks(mockFactory, mockOverlay, mockBorderBox, mockWebView)
137
138 pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), mockWebView)
139
140 // Act - setting false when already false should not call RemoveCssClass
141 pv.SetActive(false)
142
143 // Assert - no mock expectations for CSS changes
144 assert.False(t, pv.IsActive())
145}
146
147func TestPaneID_ReturnsPaneID(t *testing.T) {
148 // Arrange
149 mockFactory := mocks.NewMockWidgetFactory(t)
150 mockOverlay := mocks.NewMockOverlayWidget(t)
151 mockBorderBox := mocks.NewMockBoxWidget(t)
152 mockWebView := mocks.NewMockWidget(t)
153
154 paneID := entity.PaneID("test-pane-123")
155 setupPaneViewMocks(mockFactory, mockOverlay, mockBorderBox, mockWebView)
156
157 pv := component.NewPaneView(mockFactory, paneID, mockWebView)
158
159 // Act
160 result := pv.PaneID()
161
162 // Assert
163 assert.Equal(t, paneID, result)
164}
165
166func TestWebViewWidget_ReturnsWebView(t *testing.T) {
167 // Arrange
168 mockFactory := mocks.NewMockWidgetFactory(t)
169 mockOverlay := mocks.NewMockOverlayWidget(t)
170 mockBorderBox := mocks.NewMockBoxWidget(t)
171 mockWebView := mocks.NewMockWidget(t)
172
173 setupPaneViewMocks(mockFactory, mockOverlay, mockBorderBox, mockWebView)
174
175 pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), mockWebView)
176
177 // Act
178 result := pv.WebViewWidget()
179
180 // Assert
181 assert.Equal(t, mockWebView, result)
182}
183
184func TestSetWebViewWidget_ReplacesWidget(t *testing.T) {
185 // Arrange
186 mockFactory := mocks.NewMockWidgetFactory(t)
187 mockOverlay := mocks.NewMockOverlayWidget(t)
188 mockBorderBox := mocks.NewMockBoxWidget(t)
189 mockOldWebView := mocks.NewMockWidget(t)
190 mockNewWebView := mocks.NewMockWidget(t)
191
192 setupPaneViewMocks(mockFactory, mockOverlay, mockBorderBox, mockOldWebView)
193
194 pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), mockOldWebView)
195
196 // Expect removal of old widget and addition of new
197 mockOverlay.EXPECT().SetChild(nil).Once()
198 mockNewWebView.EXPECT().GetParent().Return(nil).Once()
199 mockNewWebView.EXPECT().SetVisible(true).Once()
200 mockOverlay.EXPECT().SetChild(mockNewWebView).Once()
201
202 // Act
203 pv.SetWebViewWidget(mockNewWebView)
204
205 // Assert
206 assert.Equal(t, mockNewWebView, pv.WebViewWidget())
207}
208
209func TestSetWebViewWidget_FromNil(t *testing.T) {
210 // Arrange
211 mockFactory := mocks.NewMockWidgetFactory(t)
212 mockOverlay := mocks.NewMockOverlayWidget(t)
213 mockBorderBox := mocks.NewMockBoxWidget(t)
214 mockNewWebView := mocks.NewMockWidget(t)
215
216 setupPaneViewMocksNoWebView(mockFactory, mockOverlay, mockBorderBox)
217
218 pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), nil)
219
220 // Expect only setting new child (no removal since old was nil)
221 mockNewWebView.EXPECT().GetParent().Return(nil).Once()
222 mockNewWebView.EXPECT().SetVisible(true).Once()
223 mockOverlay.EXPECT().SetChild(mockNewWebView).Once()
224
225 // Act
226 pv.SetWebViewWidget(mockNewWebView)
227
228 // Assert
229 assert.Equal(t, mockNewWebView, pv.WebViewWidget())
230}
231
232func TestGrabFocus_DelegatesToWebView(t *testing.T) {
233 // Arrange
234 mockFactory := mocks.NewMockWidgetFactory(t)
235 mockOverlay := mocks.NewMockOverlayWidget(t)
236 mockBorderBox := mocks.NewMockBoxWidget(t)
237 mockWebView := mocks.NewMockWidget(t)
238
239 setupPaneViewMocks(mockFactory, mockOverlay, mockBorderBox, mockWebView)
240
241 pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), mockWebView)
242
243 mockWebView.EXPECT().GrabFocus().Return(true).Once()
244
245 // Act
246 result := pv.GrabFocus()
247
248 // Assert
249 assert.True(t, result)
250}
251
252func TestGrabFocus_NilWebView_ReturnsFalse(t *testing.T) {
253 // Arrange
254 mockFactory := mocks.NewMockWidgetFactory(t)
255 mockOverlay := mocks.NewMockOverlayWidget(t)
256 mockBorderBox := mocks.NewMockBoxWidget(t)
257
258 setupPaneViewMocksNoWebView(mockFactory, mockOverlay, mockBorderBox)
259
260 pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), nil)
261
262 // Act
263 result := pv.GrabFocus()
264
265 // Assert
266 assert.False(t, result)
267}
268
269func TestHasFocus_DelegatesToWebView(t *testing.T) {
270 // Arrange
271 mockFactory := mocks.NewMockWidgetFactory(t)
272 mockOverlay := mocks.NewMockOverlayWidget(t)
273 mockBorderBox := mocks.NewMockBoxWidget(t)
274 mockWebView := mocks.NewMockWidget(t)
275
276 setupPaneViewMocks(mockFactory, mockOverlay, mockBorderBox, mockWebView)
277
278 pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), mockWebView)
279
280 mockWebView.EXPECT().HasFocus().Return(true).Once()
281
282 // Act
283 result := pv.HasFocus()
284
285 // Assert
286 assert.True(t, result)
287}
288
289func TestHasFocus_NilWebView_ReturnsFalse(t *testing.T) {
290 // Arrange
291 mockFactory := mocks.NewMockWidgetFactory(t)
292 mockOverlay := mocks.NewMockOverlayWidget(t)
293 mockBorderBox := mocks.NewMockBoxWidget(t)
294
295 setupPaneViewMocksNoWebView(mockFactory, mockOverlay, mockBorderBox)
296
297 pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), nil)
298
299 // Act
300 result := pv.HasFocus()
301
302 // Assert
303 assert.False(t, result)
304}
305
306func TestWidget_ReturnsOverlay(t *testing.T) {
307 // Arrange
308 mockFactory := mocks.NewMockWidgetFactory(t)
309 mockOverlay := mocks.NewMockOverlayWidget(t)
310 mockBorderBox := mocks.NewMockBoxWidget(t)
311 mockWebView := mocks.NewMockWidget(t)
312
313 setupPaneViewMocks(mockFactory, mockOverlay, mockBorderBox, mockWebView)
314
315 pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), mockWebView)
316
317 // Act
318 result := pv.Widget()
319
320 // Assert
321 assert.Equal(t, mockOverlay, result)
322}
323
324func TestOverlay_ReturnsOverlayWidget(t *testing.T) {
325 // Arrange
326 mockFactory := mocks.NewMockWidgetFactory(t)
327 mockOverlay := mocks.NewMockOverlayWidget(t)
328 mockBorderBox := mocks.NewMockBoxWidget(t)
329 mockWebView := mocks.NewMockWidget(t)
330
331 setupPaneViewMocks(mockFactory, mockOverlay, mockBorderBox, mockWebView)
332
333 pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), mockWebView)
334
335 // Act
336 result := pv.Overlay()
337
338 // Assert
339 assert.Equal(t, mockOverlay, result)
340}
341
342func TestShow_DelegatesToOverlay(t *testing.T) {
343 // Arrange
344 mockFactory := mocks.NewMockWidgetFactory(t)
345 mockOverlay := mocks.NewMockOverlayWidget(t)
346 mockBorderBox := mocks.NewMockBoxWidget(t)
347 mockWebView := mocks.NewMockWidget(t)
348
349 setupPaneViewMocks(mockFactory, mockOverlay, mockBorderBox, mockWebView)
350
351 pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), mockWebView)
352
353 mockOverlay.EXPECT().Show().Once()
354
355 // Act
356 pv.Show()
357
358 // Assert - verified by mock expectations
359}
360
361func TestHide_DelegatesToOverlay(t *testing.T) {
362 // Arrange
363 mockFactory := mocks.NewMockWidgetFactory(t)
364 mockOverlay := mocks.NewMockOverlayWidget(t)
365 mockBorderBox := mocks.NewMockBoxWidget(t)
366 mockWebView := mocks.NewMockWidget(t)
367
368 setupPaneViewMocks(mockFactory, mockOverlay, mockBorderBox, mockWebView)
369
370 pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), mockWebView)
371
372 mockOverlay.EXPECT().Hide().Once()
373
374 // Act
375 pv.Hide()
376
377 // Assert - verified by mock expectations
378}
379
380func TestSetVisible_DelegatesToOverlay(t *testing.T) {
381 // Arrange
382 mockFactory := mocks.NewMockWidgetFactory(t)
383 mockOverlay := mocks.NewMockOverlayWidget(t)
384 mockBorderBox := mocks.NewMockBoxWidget(t)
385 mockWebView := mocks.NewMockWidget(t)
386
387 setupPaneViewMocks(mockFactory, mockOverlay, mockBorderBox, mockWebView)
388
389 pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), mockWebView)
390
391 mockOverlay.EXPECT().SetVisible(true).Once()
392
393 // Act
394 pv.SetVisible(true)
395
396 // Assert - verified by mock expectations
397}
398
399func TestIsVisible_DelegatesToOverlay(t *testing.T) {
400 // Arrange
401 mockFactory := mocks.NewMockWidgetFactory(t)
402 mockOverlay := mocks.NewMockOverlayWidget(t)
403 mockBorderBox := mocks.NewMockBoxWidget(t)
404 mockWebView := mocks.NewMockWidget(t)
405
406 setupPaneViewMocks(mockFactory, mockOverlay, mockBorderBox, mockWebView)
407
408 pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), mockWebView)
409
410 mockOverlay.EXPECT().IsVisible().Return(true).Once()
411
412 // Act
413 result := pv.IsVisible()
414
415 // Assert
416 assert.True(t, result)
417}
418
419func TestAddCssClass_DelegatesToOverlay(t *testing.T) {
420 // Arrange
421 mockFactory := mocks.NewMockWidgetFactory(t)
422 mockOverlay := mocks.NewMockOverlayWidget(t)
423 mockBorderBox := mocks.NewMockBoxWidget(t)
424 mockWebView := mocks.NewMockWidget(t)
425
426 setupPaneViewMocks(mockFactory, mockOverlay, mockBorderBox, mockWebView)
427
428 pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), mockWebView)
429
430 mockOverlay.EXPECT().AddCssClass("custom-class").Once()
431
432 // Act
433 pv.AddCssClass("custom-class")
434
435 // Assert - verified by mock expectations
436}
437
438func TestRemoveCssClass_DelegatesToOverlay(t *testing.T) {
439 // Arrange
440 mockFactory := mocks.NewMockWidgetFactory(t)
441 mockOverlay := mocks.NewMockOverlayWidget(t)
442 mockBorderBox := mocks.NewMockBoxWidget(t)
443 mockWebView := mocks.NewMockWidget(t)
444
445 setupPaneViewMocks(mockFactory, mockOverlay, mockBorderBox, mockWebView)
446
447 pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), mockWebView)
448
449 mockOverlay.EXPECT().RemoveCssClass("custom-class").Once()
450
451 // Act
452 pv.RemoveCssClass("custom-class")
453
454 // Assert - verified by mock expectations
455}
456
457func TestSetOnFocusIn_SetsCallback(t *testing.T) {
458 // Arrange
459 mockFactory := mocks.NewMockWidgetFactory(t)
460 mockOverlay := mocks.NewMockOverlayWidget(t)
461 mockBorderBox := mocks.NewMockBoxWidget(t)
462 mockWebView := mocks.NewMockWidget(t)
463
464 setupPaneViewMocks(mockFactory, mockOverlay, mockBorderBox, mockWebView)
465
466 pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), mockWebView)
467
468 callback := func(paneID entity.PaneID) {}
469
470 // Act
471 pv.SetOnFocusIn(callback)
472
473 // Assert - callback is set (we can't directly verify without triggering focus)
474 assert.NotNil(t, pv)
475}
476
477func TestSetOnFocusOut_SetsCallback(t *testing.T) {
478 // Arrange
479 mockFactory := mocks.NewMockWidgetFactory(t)
480 mockOverlay := mocks.NewMockOverlayWidget(t)
481 mockBorderBox := mocks.NewMockBoxWidget(t)
482 mockWebView := mocks.NewMockWidget(t)
483
484 setupPaneViewMocks(mockFactory, mockOverlay, mockBorderBox, mockWebView)
485
486 pv := component.NewPaneView(mockFactory, entity.PaneID("pane-1"), mockWebView)
487
488 callback := func(paneID entity.PaneID) {}
489
490 // Act
491 pv.SetOnFocusOut(callback)
492
493 // Assert - callback is set (we can't directly verify without triggering focus)
494 assert.NotNil(t, pv)
495}
496
497// Helper function to setup common mock expectations for PaneView creation
498func setupPaneViewMocks(
499 mockFactory *mocks.MockWidgetFactory,
500 mockOverlay *mocks.MockOverlayWidget,
501 mockBorderBox *mocks.MockBoxWidget,
502 mockWebView *mocks.MockWidget,
503) {
504 mockFactory.EXPECT().NewOverlay().Return(mockOverlay).Once()
505 mockOverlay.EXPECT().SetHexpand(true).Once()
506 mockOverlay.EXPECT().SetVexpand(true).Once()
507 mockOverlay.EXPECT().SetVisible(true).Once()
508 mockOverlay.EXPECT().AddCssClass("pane-overlay").Once() // Theme background
509 mockOverlay.EXPECT().SetChild(mockWebView).Once()
510
511 mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBorderBox).Once()
512 mockBorderBox.EXPECT().SetCanFocus(false).Once()
513 mockBorderBox.EXPECT().SetCanTarget(false).Once()
514 mockBorderBox.EXPECT().AddCssClass("pane-border").Once()
515 mockBorderBox.EXPECT().SetHexpand(true).Once()
516 mockBorderBox.EXPECT().SetVexpand(true).Once()
517 mockOverlay.EXPECT().AddOverlay(mockBorderBox).Once()
518 mockOverlay.EXPECT().SetClipOverlay(mockBorderBox, false).Once()
519 mockOverlay.EXPECT().SetMeasureOverlay(mockBorderBox, false).Once()
520}
521
522func setupPaneViewMocksNoWebView(
523 mockFactory *mocks.MockWidgetFactory,
524 mockOverlay *mocks.MockOverlayWidget,
525 mockBorderBox *mocks.MockBoxWidget,
526) {
527 mockFactory.EXPECT().NewOverlay().Return(mockOverlay).Once()
528 mockOverlay.EXPECT().SetHexpand(true).Once()
529 mockOverlay.EXPECT().SetVexpand(true).Once()
530 mockOverlay.EXPECT().SetVisible(true).Once()
531 mockOverlay.EXPECT().AddCssClass("pane-overlay").Once() // Theme background
532
533 mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBorderBox).Once()
534 mockBorderBox.EXPECT().SetCanFocus(false).Once()
535 mockBorderBox.EXPECT().SetCanTarget(false).Once()
536 mockBorderBox.EXPECT().AddCssClass("pane-border").Once()
537 mockBorderBox.EXPECT().SetHexpand(true).Once()
538 mockBorderBox.EXPECT().SetVexpand(true).Once()
539 mockOverlay.EXPECT().AddOverlay(mockBorderBox).Once()
540 mockOverlay.EXPECT().SetClipOverlay(mockBorderBox, false).Once()
541 mockOverlay.EXPECT().SetMeasureOverlay(mockBorderBox, false).Once()
542}