1package component_test
2
3import (
4 "context"
5 "testing"
6
7 "github.com/stretchr/testify/assert"
8 "github.com/stretchr/testify/mock"
9 "github.com/stretchr/testify/require"
10
11 "github.com/bnema/dumber/internal/domain/entity"
12 "github.com/bnema/dumber/internal/ui/component"
13 "github.com/bnema/dumber/internal/ui/layout"
14 "github.com/bnema/dumber/internal/ui/layout/mocks"
15)
16
17func TestNewPaneView_CreatesOverlay(t *testing.T) {
18 // Arrange
19 mockFactory := mocks.NewMockWidgetFactory(t)
20 mockOverlay := mocks.NewMockOverlayWidget(t)
21 mockBorderBox := mocks.NewMockBoxWidget(t)
22 mockWebView := mocks.NewMockWidget(t)
23
24 mockLoadingContainer := mocks.NewMockBoxWidget(t)
25 mockLoadingContent := mocks.NewMockBoxWidget(t)
26 mockLoadingSpinner := mocks.NewMockSpinnerWidget(t)
27 mockLoadingLogo := mocks.NewMockImageWidget(t)
28
29 paneID := entity.PaneID("pane-1")
30
31 mockFactory.EXPECT().NewOverlay().Return(mockOverlay).Once()
32 mockOverlay.EXPECT().SetHexpand(true).Once()
33 mockOverlay.EXPECT().SetVexpand(true).Once()
34 mockOverlay.EXPECT().SetVisible(true).Once()
35 mockOverlay.EXPECT().AddCssClass("pane-overlay").Once() // Theme background
36 mockOverlay.EXPECT().SetChild(mockWebView).Once()
37
38 setupLoadingSkeletonMocks(t, mockFactory, mockOverlay, mockLoadingContainer, mockLoadingContent, mockLoadingSpinner, mockLoadingLogo)
39
40 mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBorderBox).Once()
41 mockBorderBox.EXPECT().SetCanFocus(false).Once()
42 mockBorderBox.EXPECT().SetCanTarget(false).Once()
43 mockBorderBox.EXPECT().AddCssClass("pane-border").Once()
44 mockBorderBox.EXPECT().SetHexpand(true).Once()
45 mockBorderBox.EXPECT().SetVexpand(true).Once()
46 mockOverlay.EXPECT().AddOverlay(mockBorderBox).Once()
47 mockOverlay.EXPECT().SetClipOverlay(mockBorderBox, false).Once()
48 mockOverlay.EXPECT().SetMeasureOverlay(mockBorderBox, false).Once()
49
50 // Act
51 pv := component.NewPaneView(context.Background(), mockFactory, paneID, mockWebView)
52
53 // Assert
54 require.NotNil(t, pv)
55 assert.Equal(t, paneID, pv.PaneID())
56 assert.Equal(t, mockWebView, pv.WebViewWidget())
57}
58
59func TestNewPaneView_NilWebView(t *testing.T) {
60 // Arrange
61 mockFactory := mocks.NewMockWidgetFactory(t)
62 mockOverlay := mocks.NewMockOverlayWidget(t)
63 mockBorderBox := mocks.NewMockBoxWidget(t)
64
65 mockLoadingContainer := mocks.NewMockBoxWidget(t)
66 mockLoadingContent := mocks.NewMockBoxWidget(t)
67 mockLoadingSpinner := mocks.NewMockSpinnerWidget(t)
68 mockLoadingLogo := mocks.NewMockImageWidget(t)
69
70 paneID := entity.PaneID("pane-1")
71
72 mockFactory.EXPECT().NewOverlay().Return(mockOverlay).Once()
73 mockOverlay.EXPECT().SetHexpand(true).Once()
74 mockOverlay.EXPECT().SetVexpand(true).Once()
75 mockOverlay.EXPECT().SetVisible(true).Once()
76 mockOverlay.EXPECT().AddCssClass("pane-overlay").Once() // Theme background
77 // SetChild should NOT be called when webview is nil
78
79 setupLoadingSkeletonMocks(t, mockFactory, mockOverlay, mockLoadingContainer, mockLoadingContent, mockLoadingSpinner, mockLoadingLogo)
80
81 mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBorderBox).Once()
82 mockBorderBox.EXPECT().SetCanFocus(false).Once()
83 mockBorderBox.EXPECT().SetCanTarget(false).Once()
84 mockBorderBox.EXPECT().AddCssClass("pane-border").Once()
85 mockBorderBox.EXPECT().SetHexpand(true).Once()
86 mockBorderBox.EXPECT().SetVexpand(true).Once()
87 mockOverlay.EXPECT().AddOverlay(mockBorderBox).Once()
88 mockOverlay.EXPECT().SetClipOverlay(mockBorderBox, false).Once()
89 mockOverlay.EXPECT().SetMeasureOverlay(mockBorderBox, false).Once()
90
91 // Act
92 pv := component.NewPaneView(context.Background(), mockFactory, paneID, nil)
93
94 // Assert
95 require.NotNil(t, pv)
96 assert.Nil(t, pv.WebViewWidget())
97}
98
99func TestSetActive_True_AddsCSSClass(t *testing.T) {
100 // Arrange
101 mockFactory := mocks.NewMockWidgetFactory(t)
102 mockOverlay := mocks.NewMockOverlayWidget(t)
103 mockBorderBox := mocks.NewMockBoxWidget(t)
104 mockWebView := mocks.NewMockWidget(t)
105
106 setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView)
107
108 pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView)
109
110 // Expect CSS class to be added
111 mockBorderBox.EXPECT().AddCssClass("pane-active").Once()
112
113 // Act
114 pv.SetActive(true)
115
116 // Assert
117 assert.True(t, pv.IsActive())
118}
119
120func TestSetActive_False_RemovesCSSClass(t *testing.T) {
121 // Arrange
122 mockFactory := mocks.NewMockWidgetFactory(t)
123 mockOverlay := mocks.NewMockOverlayWidget(t)
124 mockBorderBox := mocks.NewMockBoxWidget(t)
125 mockWebView := mocks.NewMockWidget(t)
126
127 setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView)
128
129 pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView)
130
131 // First activate
132 mockBorderBox.EXPECT().AddCssClass("pane-active").Once()
133 pv.SetActive(true)
134
135 // Then deactivate
136 mockBorderBox.EXPECT().RemoveCssClass("pane-active").Once()
137
138 // Act
139 pv.SetActive(false)
140
141 // Assert
142 assert.False(t, pv.IsActive())
143}
144
145func TestSetActive_NoChangeWhenSameState(t *testing.T) {
146 // Arrange
147 mockFactory := mocks.NewMockWidgetFactory(t)
148 mockOverlay := mocks.NewMockOverlayWidget(t)
149 mockBorderBox := mocks.NewMockBoxWidget(t)
150 mockWebView := mocks.NewMockWidget(t)
151
152 setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView)
153
154 pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView)
155
156 // Act - setting false when already false should not call RemoveCssClass
157 pv.SetActive(false)
158
159 // Assert - no mock expectations for CSS changes
160 assert.False(t, pv.IsActive())
161}
162
163func TestPaneID_ReturnsPaneID(t *testing.T) {
164 // Arrange
165 mockFactory := mocks.NewMockWidgetFactory(t)
166 mockOverlay := mocks.NewMockOverlayWidget(t)
167 mockBorderBox := mocks.NewMockBoxWidget(t)
168 mockWebView := mocks.NewMockWidget(t)
169
170 paneID := entity.PaneID("test-pane-123")
171 setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView)
172
173 pv := component.NewPaneView(context.Background(), mockFactory, paneID, mockWebView)
174
175 // Act
176 result := pv.PaneID()
177
178 // Assert
179 assert.Equal(t, paneID, result)
180}
181
182func TestWebViewWidget_ReturnsWebView(t *testing.T) {
183 // Arrange
184 mockFactory := mocks.NewMockWidgetFactory(t)
185 mockOverlay := mocks.NewMockOverlayWidget(t)
186 mockBorderBox := mocks.NewMockBoxWidget(t)
187 mockWebView := mocks.NewMockWidget(t)
188
189 setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView)
190
191 pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView)
192
193 // Act
194 result := pv.WebViewWidget()
195
196 // Assert
197 assert.Equal(t, mockWebView, result)
198}
199
200func TestSetWebViewWidget_ReplacesWidget(t *testing.T) {
201 // Arrange
202 mockFactory := mocks.NewMockWidgetFactory(t)
203 mockOverlay := mocks.NewMockOverlayWidget(t)
204 mockBorderBox := mocks.NewMockBoxWidget(t)
205 mockOldWebView := mocks.NewMockWidget(t)
206 mockNewWebView := mocks.NewMockWidget(t)
207
208 setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockOldWebView)
209
210 pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockOldWebView)
211
212 // Expect removal of old widget and addition of new
213 mockOverlay.EXPECT().SetChild(nil).Once()
214 mockOverlay.EXPECT().GetAllocatedWidth().Return(0).Twice()
215 mockOverlay.EXPECT().GetAllocatedHeight().Return(0).Twice()
216 mockNewWebView.EXPECT().GetParent().Return(nil).Once()
217 mockNewWebView.EXPECT().IsVisible().Return(false).Once()
218 mockNewWebView.EXPECT().GetAllocatedWidth().Return(0).Twice()
219 mockNewWebView.EXPECT().GetAllocatedHeight().Return(0).Twice()
220 mockOverlay.EXPECT().SetChild(mockNewWebView).Once()
221
222 // Act
223 pv.SetWebViewWidget(mockNewWebView)
224
225 // Assert
226 assert.Equal(t, mockNewWebView, pv.WebViewWidget())
227}
228
229func TestSetWebViewWidget_ReparentsWidgetWithExistingParent(t *testing.T) {
230 // Arrange
231 mockFactory := mocks.NewMockWidgetFactory(t)
232 mockOverlay := mocks.NewMockOverlayWidget(t)
233 mockBorderBox := mocks.NewMockBoxWidget(t)
234 mockOldWebView := mocks.NewMockWidget(t)
235 mockNewWebView := mocks.NewMockWidget(t)
236 mockParent := mocks.NewMockWidget(t)
237
238 setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockOldWebView)
239
240 pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockOldWebView)
241
242 mockOverlay.EXPECT().SetChild(nil).Once()
243 mockOverlay.EXPECT().GetAllocatedWidth().Return(0).Twice()
244 mockOverlay.EXPECT().GetAllocatedHeight().Return(0).Twice()
245 mockNewWebView.EXPECT().GetAllocatedWidth().Return(0).Twice()
246 mockNewWebView.EXPECT().GetAllocatedHeight().Return(0).Twice()
247 mockNewWebView.EXPECT().GetParent().Return(mockParent).Once()
248 mockNewWebView.EXPECT().IsVisible().Return(true).Once()
249 mockNewWebView.EXPECT().Unparent().Once()
250 mockOverlay.EXPECT().SetChild(mockNewWebView).Once()
251 mockNewWebView.EXPECT().SetVisible(true).Once()
252
253 // Loading skeleton is hidden immediately for a reparented, already-visible widget.
254
255 // Act
256 pv.SetWebViewWidget(mockNewWebView)
257
258 // Assert
259 assert.Equal(t, mockNewWebView, pv.WebViewWidget())
260}
261
262func TestSetWebViewWidget_FromNil(t *testing.T) {
263 // Arrange
264 mockFactory := mocks.NewMockWidgetFactory(t)
265 mockOverlay := mocks.NewMockOverlayWidget(t)
266 mockBorderBox := mocks.NewMockBoxWidget(t)
267 mockNewWebView := mocks.NewMockWidget(t)
268
269 setupPaneViewMocksNoWebView(t, mockFactory, mockOverlay, mockBorderBox)
270
271 pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), nil)
272
273 // Expect only setting new child (no removal since old was nil)
274 mockOverlay.EXPECT().GetAllocatedWidth().Return(0).Twice()
275 mockOverlay.EXPECT().GetAllocatedHeight().Return(0).Twice()
276 mockNewWebView.EXPECT().GetParent().Return(nil).Once()
277 mockNewWebView.EXPECT().IsVisible().Return(false).Once()
278 mockNewWebView.EXPECT().GetAllocatedWidth().Return(0).Twice()
279 mockNewWebView.EXPECT().GetAllocatedHeight().Return(0).Twice()
280 mockOverlay.EXPECT().SetChild(mockNewWebView).Once()
281
282 // Act
283 pv.SetWebViewWidget(mockNewWebView)
284
285 // Assert
286 assert.Equal(t, mockNewWebView, pv.WebViewWidget())
287}
288
289func TestGrabFocus_DelegatesToWebView(t *testing.T) {
290 // Arrange
291 mockFactory := mocks.NewMockWidgetFactory(t)
292 mockOverlay := mocks.NewMockOverlayWidget(t)
293 mockBorderBox := mocks.NewMockBoxWidget(t)
294 mockWebView := mocks.NewMockWidget(t)
295
296 setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView)
297
298 pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView)
299
300 mockWebView.EXPECT().GrabFocus().Return(true).Once()
301
302 // Act
303 result := pv.GrabFocus()
304
305 // Assert
306 assert.True(t, result)
307}
308
309func TestGrabFocus_NilWebView_ReturnsFalse(t *testing.T) {
310 // Arrange
311 mockFactory := mocks.NewMockWidgetFactory(t)
312 mockOverlay := mocks.NewMockOverlayWidget(t)
313 mockBorderBox := mocks.NewMockBoxWidget(t)
314
315 setupPaneViewMocksNoWebView(t, mockFactory, mockOverlay, mockBorderBox)
316
317 pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), nil)
318
319 // Act
320 result := pv.GrabFocus()
321
322 // Assert
323 assert.False(t, result)
324}
325
326func TestHasFocus_DelegatesToWebView(t *testing.T) {
327 // Arrange
328 mockFactory := mocks.NewMockWidgetFactory(t)
329 mockOverlay := mocks.NewMockOverlayWidget(t)
330 mockBorderBox := mocks.NewMockBoxWidget(t)
331 mockWebView := mocks.NewMockWidget(t)
332
333 setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView)
334
335 pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView)
336
337 mockWebView.EXPECT().HasFocus().Return(true).Once()
338
339 // Act
340 result := pv.HasFocus()
341
342 // Assert
343 assert.True(t, result)
344}
345
346func TestHasFocus_NilWebView_ReturnsFalse(t *testing.T) {
347 // Arrange
348 mockFactory := mocks.NewMockWidgetFactory(t)
349 mockOverlay := mocks.NewMockOverlayWidget(t)
350 mockBorderBox := mocks.NewMockBoxWidget(t)
351
352 setupPaneViewMocksNoWebView(t, mockFactory, mockOverlay, mockBorderBox)
353
354 pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), nil)
355
356 // Act
357 result := pv.HasFocus()
358
359 // Assert
360 assert.False(t, result)
361}
362
363func TestWidget_ReturnsOverlay(t *testing.T) {
364 // Arrange
365 mockFactory := mocks.NewMockWidgetFactory(t)
366 mockOverlay := mocks.NewMockOverlayWidget(t)
367 mockBorderBox := mocks.NewMockBoxWidget(t)
368 mockWebView := mocks.NewMockWidget(t)
369
370 setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView)
371
372 pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView)
373
374 // Act
375 result := pv.Widget()
376
377 // Assert
378 assert.Equal(t, mockOverlay, result)
379}
380
381func TestOverlay_ReturnsOverlayWidget(t *testing.T) {
382 // Arrange
383 mockFactory := mocks.NewMockWidgetFactory(t)
384 mockOverlay := mocks.NewMockOverlayWidget(t)
385 mockBorderBox := mocks.NewMockBoxWidget(t)
386 mockWebView := mocks.NewMockWidget(t)
387
388 setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView)
389
390 pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView)
391
392 // Act
393 result := pv.Overlay()
394
395 // Assert
396 assert.Equal(t, mockOverlay, result)
397}
398
399func TestShow_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(t, mockFactory, mockOverlay, mockBorderBox, mockWebView)
407
408 pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView)
409
410 mockOverlay.EXPECT().Show().Once()
411
412 // Act
413 pv.Show()
414
415 // Assert - verified by mock expectations
416}
417
418func TestHide_DelegatesToOverlay(t *testing.T) {
419 // Arrange
420 mockFactory := mocks.NewMockWidgetFactory(t)
421 mockOverlay := mocks.NewMockOverlayWidget(t)
422 mockBorderBox := mocks.NewMockBoxWidget(t)
423 mockWebView := mocks.NewMockWidget(t)
424
425 setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView)
426
427 pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView)
428
429 mockOverlay.EXPECT().Hide().Once()
430
431 // Act
432 pv.Hide()
433
434 // Assert - verified by mock expectations
435}
436
437func TestSetVisible_DelegatesToOverlay(t *testing.T) {
438 // Arrange
439 mockFactory := mocks.NewMockWidgetFactory(t)
440 mockOverlay := mocks.NewMockOverlayWidget(t)
441 mockBorderBox := mocks.NewMockBoxWidget(t)
442 mockWebView := mocks.NewMockWidget(t)
443
444 setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView)
445
446 pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView)
447
448 mockOverlay.EXPECT().SetVisible(true).Once()
449
450 // Act
451 pv.SetVisible(true)
452
453 // Assert - verified by mock expectations
454}
455
456func TestIsVisible_DelegatesToOverlay(t *testing.T) {
457 // Arrange
458 mockFactory := mocks.NewMockWidgetFactory(t)
459 mockOverlay := mocks.NewMockOverlayWidget(t)
460 mockBorderBox := mocks.NewMockBoxWidget(t)
461 mockWebView := mocks.NewMockWidget(t)
462
463 setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView)
464
465 pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView)
466
467 mockOverlay.EXPECT().IsVisible().Return(true).Once()
468
469 // Act
470 result := pv.IsVisible()
471
472 // Assert
473 assert.True(t, result)
474}
475
476func TestAddCssClass_DelegatesToOverlay(t *testing.T) {
477 // Arrange
478 mockFactory := mocks.NewMockWidgetFactory(t)
479 mockOverlay := mocks.NewMockOverlayWidget(t)
480 mockBorderBox := mocks.NewMockBoxWidget(t)
481 mockWebView := mocks.NewMockWidget(t)
482
483 setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView)
484
485 pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView)
486
487 mockOverlay.EXPECT().AddCssClass("custom-class").Once()
488
489 // Act
490 pv.AddCssClass("custom-class")
491
492 // Assert - verified by mock expectations
493}
494
495func TestRemoveCssClass_DelegatesToOverlay(t *testing.T) {
496 // Arrange
497 mockFactory := mocks.NewMockWidgetFactory(t)
498 mockOverlay := mocks.NewMockOverlayWidget(t)
499 mockBorderBox := mocks.NewMockBoxWidget(t)
500 mockWebView := mocks.NewMockWidget(t)
501
502 setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView)
503
504 pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView)
505
506 mockOverlay.EXPECT().RemoveCssClass("custom-class").Once()
507
508 // Act
509 pv.RemoveCssClass("custom-class")
510
511 // Assert - verified by mock expectations
512}
513
514func TestSetOnFocusIn_SetsCallback(t *testing.T) {
515 // Arrange
516 mockFactory := mocks.NewMockWidgetFactory(t)
517 mockOverlay := mocks.NewMockOverlayWidget(t)
518 mockBorderBox := mocks.NewMockBoxWidget(t)
519 mockWebView := mocks.NewMockWidget(t)
520
521 setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView)
522
523 pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView)
524
525 callback := func(paneID entity.PaneID) {}
526
527 // Act
528 pv.SetOnFocusIn(callback)
529
530 // Assert - callback is set (we can't directly verify without triggering focus)
531 assert.NotNil(t, pv)
532}
533
534func TestSetOnFocusOut_SetsCallback(t *testing.T) {
535 // Arrange
536 mockFactory := mocks.NewMockWidgetFactory(t)
537 mockOverlay := mocks.NewMockOverlayWidget(t)
538 mockBorderBox := mocks.NewMockBoxWidget(t)
539 mockWebView := mocks.NewMockWidget(t)
540
541 setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView)
542
543 pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView)
544
545 callback := func(paneID entity.PaneID) {}
546
547 // Act
548 pv.SetOnFocusOut(callback)
549
550 // Assert - callback is set (we can't directly verify without triggering focus)
551 assert.NotNil(t, pv)
552}
553
554// Helper function to setup common mock expectations for PaneView creation
555func setupLoadingSkeletonMocks(
556 t *testing.T,
557 mockFactory *mocks.MockWidgetFactory,
558 mockOverlay *mocks.MockOverlayWidget,
559 mockLoadingContainer *mocks.MockBoxWidget,
560 mockLoadingContent *mocks.MockBoxWidget,
561 mockLoadingSpinner *mocks.MockSpinnerWidget,
562 mockLoadingLogo *mocks.MockImageWidget,
563) {
564 mockLoadingVersion := mocks.NewMockLabelWidget(t)
565
566 mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockLoadingContainer).Once()
567 mockLoadingContainer.EXPECT().SetHexpand(true).Maybe()
568 mockLoadingContainer.EXPECT().SetVexpand(true).Maybe()
569 mockLoadingContainer.EXPECT().SetHalign(mock.Anything).Maybe()
570 mockLoadingContainer.EXPECT().SetValign(mock.Anything).Maybe()
571 mockLoadingContainer.EXPECT().SetCanFocus(false).Maybe()
572 mockLoadingContainer.EXPECT().SetCanTarget(false).Maybe()
573 mockLoadingContainer.EXPECT().AddCssClass("loading-skeleton").Maybe()
574 mockLoadingContainer.EXPECT().SetVisible(mock.Anything).Maybe()
575
576 mockFactory.EXPECT().NewBox(layout.OrientationVertical, 6).Return(mockLoadingContent).Once()
577 mockLoadingContent.EXPECT().SetHalign(mock.Anything).Maybe()
578 mockLoadingContent.EXPECT().SetValign(mock.Anything).Maybe()
579 mockLoadingContent.EXPECT().SetCanFocus(false).Maybe()
580 mockLoadingContent.EXPECT().SetCanTarget(false).Maybe()
581 mockLoadingContent.EXPECT().AddCssClass("loading-skeleton-content").Maybe()
582
583 mockFactory.EXPECT().NewImage().Return(mockLoadingLogo).Once()
584 mockLoadingLogo.EXPECT().SetHalign(mock.Anything).Maybe()
585 mockLoadingLogo.EXPECT().SetValign(mock.Anything).Maybe()
586 mockLoadingLogo.EXPECT().SetCanFocus(false).Maybe()
587 mockLoadingLogo.EXPECT().SetCanTarget(false).Maybe()
588 mockLoadingLogo.EXPECT().SetSizeRequest(256, 256).Maybe()
589 mockLoadingLogo.EXPECT().SetPixelSize(256).Maybe()
590 mockLoadingLogo.EXPECT().AddCssClass("loading-skeleton-logo").Maybe()
591 mockLoadingLogo.EXPECT().SetFromPaintable(mock.Anything).Maybe()
592
593 mockFactory.EXPECT().NewSpinner().Return(mockLoadingSpinner).Once()
594 mockLoadingSpinner.EXPECT().SetHalign(mock.Anything).Maybe()
595 mockLoadingSpinner.EXPECT().SetValign(mock.Anything).Maybe()
596 mockLoadingSpinner.EXPECT().SetCanFocus(false).Maybe()
597 mockLoadingSpinner.EXPECT().SetCanTarget(false).Maybe()
598 mockLoadingSpinner.EXPECT().SetSizeRequest(32, 32).Maybe()
599 mockLoadingSpinner.EXPECT().AddCssClass("loading-skeleton-spinner").Maybe()
600 mockLoadingSpinner.EXPECT().Start().Maybe()
601 mockLoadingSpinner.EXPECT().Stop().Maybe()
602
603 mockFactory.EXPECT().NewLabel(mock.Anything).Return(mockLoadingVersion).Once()
604 mockLoadingVersion.EXPECT().SetHalign(mock.Anything).Maybe()
605 mockLoadingVersion.EXPECT().SetValign(mock.Anything).Maybe()
606 mockLoadingVersion.EXPECT().SetCanFocus(false).Maybe()
607 mockLoadingVersion.EXPECT().SetCanTarget(false).Maybe()
608 mockLoadingVersion.EXPECT().SetMaxWidthChars(mock.Anything).Maybe()
609 mockLoadingVersion.EXPECT().SetEllipsize(mock.Anything).Maybe()
610 mockLoadingVersion.EXPECT().AddCssClass("loading-skeleton-version").Maybe()
611
612 mockLoadingContent.EXPECT().Append(mockLoadingLogo).Maybe()
613 mockLoadingContent.EXPECT().Append(mockLoadingSpinner).Maybe()
614 mockLoadingContent.EXPECT().Append(mockLoadingVersion).Maybe()
615 mockLoadingContainer.EXPECT().Append(mockLoadingContent).Maybe()
616
617 mockOverlay.EXPECT().AddOverlay(mockLoadingContainer).Once()
618 mockOverlay.EXPECT().SetClipOverlay(mockLoadingContainer, false).Once()
619 mockOverlay.EXPECT().SetMeasureOverlay(mockLoadingContainer, false).Once()
620}
621
622// Helper function to setup common mock expectations for PaneView creation
623func setupPaneViewMocks(
624 t *testing.T,
625 mockFactory *mocks.MockWidgetFactory,
626 mockOverlay *mocks.MockOverlayWidget,
627 mockBorderBox *mocks.MockBoxWidget,
628 mockWebView *mocks.MockWidget,
629) {
630 mockLoadingContainer := mocks.NewMockBoxWidget(t)
631 mockLoadingContent := mocks.NewMockBoxWidget(t)
632 mockLoadingSpinner := mocks.NewMockSpinnerWidget(t)
633 mockLoadingLogo := mocks.NewMockImageWidget(t)
634
635 mockFactory.EXPECT().NewOverlay().Return(mockOverlay).Once()
636 mockOverlay.EXPECT().SetHexpand(true).Once()
637 mockOverlay.EXPECT().SetVexpand(true).Once()
638 mockOverlay.EXPECT().SetVisible(true).Once()
639 mockOverlay.EXPECT().AddCssClass("pane-overlay").Once() // Theme background
640 mockOverlay.EXPECT().SetChild(mockWebView).Once()
641
642 setupLoadingSkeletonMocks(t, mockFactory, mockOverlay, mockLoadingContainer, mockLoadingContent, mockLoadingSpinner, mockLoadingLogo)
643
644 mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBorderBox).Once()
645 mockBorderBox.EXPECT().SetCanFocus(false).Once()
646 mockBorderBox.EXPECT().SetCanTarget(false).Once()
647 mockBorderBox.EXPECT().AddCssClass("pane-border").Once()
648 mockBorderBox.EXPECT().SetHexpand(true).Once()
649 mockBorderBox.EXPECT().SetVexpand(true).Once()
650 mockOverlay.EXPECT().AddOverlay(mockBorderBox).Once()
651 mockOverlay.EXPECT().SetClipOverlay(mockBorderBox, false).Once()
652 mockOverlay.EXPECT().SetMeasureOverlay(mockBorderBox, false).Once()
653}
654
655func setupPaneViewMocksNoWebView(
656 t *testing.T,
657 mockFactory *mocks.MockWidgetFactory,
658 mockOverlay *mocks.MockOverlayWidget,
659 mockBorderBox *mocks.MockBoxWidget,
660) {
661 mockLoadingContainer := mocks.NewMockBoxWidget(t)
662 mockLoadingContent := mocks.NewMockBoxWidget(t)
663 mockLoadingSpinner := mocks.NewMockSpinnerWidget(t)
664 mockLoadingLogo := mocks.NewMockImageWidget(t)
665
666 mockFactory.EXPECT().NewOverlay().Return(mockOverlay).Once()
667 mockOverlay.EXPECT().SetHexpand(true).Once()
668 mockOverlay.EXPECT().SetVexpand(true).Once()
669 mockOverlay.EXPECT().SetVisible(true).Once()
670 mockOverlay.EXPECT().AddCssClass("pane-overlay").Once() // Theme background
671
672 setupLoadingSkeletonMocks(t, mockFactory, mockOverlay, mockLoadingContainer, mockLoadingContent, mockLoadingSpinner, mockLoadingLogo)
673
674 mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBorderBox).Once()
675 mockBorderBox.EXPECT().SetCanFocus(false).Once()
676 mockBorderBox.EXPECT().SetCanTarget(false).Once()
677 mockBorderBox.EXPECT().AddCssClass("pane-border").Once()
678 mockBorderBox.EXPECT().SetHexpand(true).Once()
679 mockBorderBox.EXPECT().SetVexpand(true).Once()
680 mockOverlay.EXPECT().AddOverlay(mockBorderBox).Once()
681 mockOverlay.EXPECT().SetClipOverlay(mockBorderBox, false).Once()
682 mockOverlay.EXPECT().SetMeasureOverlay(mockBorderBox, false).Once()
683}
684
685func TestSetVimMode_TrueAddsPaneAccentWithoutCreatingLabel(t *testing.T) {
686 mockFactory := mocks.NewMockWidgetFactory(t)
687 mockOverlay := mocks.NewMockOverlayWidget(t)
688 mockBorderBox := mocks.NewMockBoxWidget(t)
689 mockWebView := mocks.NewMockWidget(t)
690 setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView)
691 pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView)
692
693 pv.SetVimMode(true)
694
695 require.True(t, pv.IsVimMode())
696}
697
698func TestSetVimMode_FalseRemovesPaneAccent(t *testing.T) {
699 mockFactory := mocks.NewMockWidgetFactory(t)
700 mockOverlay := mocks.NewMockOverlayWidget(t)
701 mockBorderBox := mocks.NewMockBoxWidget(t)
702 mockWebView := mocks.NewMockWidget(t)
703 setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView)
704 pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView)
705
706 pv.SetVimMode(true)
707 pv.SetVimMode(false)
708
709 require.False(t, pv.IsVimMode())
710}
711
712func TestSetVimMode_NoChangeWhenSameState(t *testing.T) {
713 mockFactory := mocks.NewMockWidgetFactory(t)
714 mockOverlay := mocks.NewMockOverlayWidget(t)
715 mockBorderBox := mocks.NewMockBoxWidget(t)
716 mockWebView := mocks.NewMockWidget(t)
717 setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView)
718 pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView)
719
720 pv.SetVimMode(false)
721
722 require.False(t, pv.IsVimMode())
723}
724
725func TestSetVimMode_TrueThenTrueIsIdempotent(t *testing.T) {
726 mockFactory := mocks.NewMockWidgetFactory(t)
727 mockOverlay := mocks.NewMockOverlayWidget(t)
728 mockBorderBox := mocks.NewMockBoxWidget(t)
729 mockWebView := mocks.NewMockWidget(t)
730 setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView)
731 pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView)
732
733 pv.SetVimMode(true)
734 pv.SetVimMode(true)
735
736 require.True(t, pv.IsVimMode())
737}
738
739func TestVimMode_CleanupHasNoIndicatorOverlay(t *testing.T) {
740 mockFactory := mocks.NewMockWidgetFactory(t)
741 mockOverlay := mocks.NewMockOverlayWidget(t)
742 mockBorderBox := mocks.NewMockBoxWidget(t)
743 mockWebView := mocks.NewMockWidget(t)
744 setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView)
745 pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView)
746
747 pv.SetVimMode(true)
748 mockOverlay.EXPECT().RemoveOverlay(mock.Anything).Once()
749 mockOverlay.EXPECT().SetChild(nil).Once()
750
751 pv.Cleanup()
752}
753
754func TestVimMode_NewPaneViewInactiveByDefault(t *testing.T) {
755 mockFactory := mocks.NewMockWidgetFactory(t)
756 mockOverlay := mocks.NewMockOverlayWidget(t)
757 mockBorderBox := mocks.NewMockBoxWidget(t)
758 mockWebView := mocks.NewMockWidget(t)
759 setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView)
760
761 pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView)
762
763 require.False(t, pv.IsVimMode())
764}