1package content
2
3import (
4 "context"
5 "errors"
6 "testing"
7
8 "github.com/stretchr/testify/assert"
9 "github.com/stretchr/testify/mock"
10 "github.com/stretchr/testify/require"
11
12 "github.com/bnema/dumber/internal/application/port"
13 "github.com/bnema/dumber/internal/application/port/mocks"
14 "github.com/bnema/dumber/internal/domain/entity"
15 "github.com/bnema/dumber/internal/ui/component"
16 "github.com/bnema/dumber/internal/ui/layout"
17 layoutmocks "github.com/bnema/dumber/internal/ui/layout/mocks"
18)
19
20// newMinimalCoordinator returns a Coordinator with the maps required by
21// ReleaseWebView initialized so that calls to clearPendingAppearance,
22// titleMu / navOriginMu locks do not panic.
23func newMinimalCoordinator() *Coordinator {
24 return &Coordinator{
25 webViews: make(map[entity.PaneID]port.WebView),
26 paneTitles: make(map[entity.PaneID]string),
27 navOrigins: make(map[entity.PaneID]string),
28 }
29}
30
31// ---------------------------------------------------------------------------
32// EnsureWebView
33// ---------------------------------------------------------------------------
34
35func TestLifecycle_EnsureWebView_ReusesExistingNonDestroyed(t *testing.T) {
36 t.Parallel()
37
38 wv := mocks.NewMockWebView(t)
39 wv.EXPECT().IsDestroyed().Return(false)
40 wv.EXPECT().ID().Return(port.WebViewID(101)).Once()
41
42 pool := mocks.NewMockWebViewPool(t)
43 // pool.Acquire must NOT be called
44
45 c := newMinimalCoordinator()
46 c.pool = pool
47 c.webViews[entity.PaneID("pane-1")] = wv
48
49 got, err := c.EnsureWebView(context.Background(), "pane-1")
50
51 require.NoError(t, err)
52 assert.Equal(t, wv, got)
53}
54
55func TestLifecycle_EnsureWebView_AcquiresFromPoolWhenNoneExists(t *testing.T) {
56 t.Parallel()
57
58 newWV := mocks.NewMockWebView(t)
59 newWV.EXPECT().SetCallbacks(mock.Anything).Maybe()
60 newWV.EXPECT().Generation().Return(uint64(0)).Maybe()
61 newWV.EXPECT().ID().Return(port.WebViewID(102)).Maybe()
62
63 pool := mocks.NewMockWebViewPool(t)
64 pool.EXPECT().Acquire(mock.Anything).Return(newWV, nil)
65
66 c := newMinimalCoordinator()
67 c.pool = pool
68
69 got, err := c.EnsureWebView(context.Background(), "pane-1")
70
71 require.NoError(t, err)
72 assert.Equal(t, newWV, got)
73 assert.Equal(t, newWV, c.webViews[entity.PaneID("pane-1")])
74}
75
76func TestLifecycle_EnsureWebView_AcquiresFromPoolWhenExistingIsDestroyed(t *testing.T) {
77 t.Parallel()
78
79 oldWV := mocks.NewMockWebView(t)
80 oldWV.EXPECT().IsDestroyed().Return(true)
81
82 newWV := mocks.NewMockWebView(t)
83 newWV.EXPECT().SetCallbacks(mock.Anything).Maybe()
84 newWV.EXPECT().Generation().Return(uint64(0)).Maybe()
85 newWV.EXPECT().ID().Return(port.WebViewID(103)).Maybe()
86
87 pool := mocks.NewMockWebViewPool(t)
88 pool.EXPECT().Acquire(mock.Anything).Return(newWV, nil)
89
90 c := newMinimalCoordinator()
91 c.pool = pool
92 c.webViews[entity.PaneID("pane-1")] = oldWV
93
94 got, err := c.EnsureWebView(context.Background(), "pane-1")
95
96 require.NoError(t, err)
97 assert.Equal(t, newWV, got)
98}
99
100func TestLifecycle_EnsureWebView_ErrorWhenPoolIsNil(t *testing.T) {
101 t.Parallel()
102
103 c := newMinimalCoordinator()
104 // pool remains nil
105
106 _, err := c.EnsureWebView(context.Background(), "pane-1")
107
108 require.Error(t, err)
109 assert.Contains(t, err.Error(), "webview pool not configured")
110}
111
112func TestLifecycle_EnsureWebView_ErrorWhenAcquireFails(t *testing.T) {
113 t.Parallel()
114
115 acquireErr := errors.New("pool exhausted")
116
117 pool := mocks.NewMockWebViewPool(t)
118 pool.EXPECT().Acquire(mock.Anything).Return(nil, acquireErr)
119
120 c := newMinimalCoordinator()
121 c.pool = pool
122
123 _, err := c.EnsureWebView(context.Background(), "pane-1")
124
125 require.Error(t, err)
126 assert.Equal(t, acquireErr, err)
127}
128
129func TestLifecycle_AttachToWorkspace_ReusesRegisteredWebViewByPaneID(t *testing.T) {
130 t.Parallel()
131
132 paneID := entity.PaneID("pane-1")
133 wv := mocks.NewMockWebView(t)
134 wv.EXPECT().IsDestroyed().Return(false).Once()
135 wv.EXPECT().ID().Return(port.WebViewID(104)).Once()
136 wv.EXPECT().URI().Return("").Maybe()
137
138 pool := mocks.NewMockWebViewPool(t)
139 widgetFactory := layoutmocks.NewMockWidgetFactory(t)
140 ctx, wsView := newAttachWorkspaceView(t, widgetFactory)
141 workspace := &entity.Workspace{
142 ID: "workspace-1",
143 Root: &entity.PaneNode{ID: "node-1", Pane: entity.NewPane(paneID)},
144 ActivePaneID: paneID,
145 }
146
147 c := newMinimalCoordinator()
148 c.pool = pool
149 c.widgetFactory = widgetFactory
150 c.webViews[paneID] = wv
151
152 c.AttachToWorkspace(ctx, workspace, wsView)
153
154 assert.Same(t, wv, c.webViews[paneID])
155}
156
157func newAttachWorkspaceView(t *testing.T, factory *layoutmocks.MockWidgetFactory) (context.Context, *component.WorkspaceView) {
158 t.Helper()
159 ctx := context.Background()
160 container := layoutmocks.NewMockBoxWidget(t)
161 overlay := layoutmocks.NewMockOverlayWidget(t)
162
163 factory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(container).Once()
164 container.EXPECT().SetHexpand(true).Once()
165 container.EXPECT().SetVexpand(true).Once()
166 container.EXPECT().SetVisible(true).Once()
167 factory.EXPECT().NewOverlay().Return(overlay).Once()
168 overlay.EXPECT().SetHexpand(true).Once()
169 overlay.EXPECT().SetVexpand(true).Once()
170 overlay.EXPECT().SetChild(container).Once()
171 overlay.EXPECT().SetVisible(true).Once()
172
173 return ctx, component.NewWorkspaceView(ctx, factory)
174}
175
176// ---------------------------------------------------------------------------
177// ReleaseWebView
178// ---------------------------------------------------------------------------
179
180func TestLifecycle_ReleaseWebView_ReleasesToPool(t *testing.T) {
181 t.Parallel()
182
183 wv := mocks.NewMockWebView(t)
184 wv.EXPECT().ID().Return(port.WebViewID(11)).Maybe()
185 // idleInhibitor is nil so these won't be called
186 wv.EXPECT().IsFullscreen().Return(false).Maybe()
187 wv.EXPECT().IsPlayingAudio().Return(false).Maybe()
188
189 pool := mocks.NewMockWebViewPool(t)
190 pool.EXPECT().Release(wv)
191
192 c := newMinimalCoordinator()
193 c.pool = pool
194 c.webViews[entity.PaneID("pane-1")] = wv
195
196 c.ReleaseWebView(context.Background(), "pane-1")
197
198 _, exists := c.webViews[entity.PaneID("pane-1")]
199 assert.False(t, exists, "webview should be removed from map after release")
200}
201
202func TestLifecycle_ReleaseWebView_ClearsNamedBrowsingContextRegistration(t *testing.T) {
203 t.Parallel()
204
205 paneID := entity.PaneID("pane-1")
206 windowID := "window-1"
207 wv := mocks.NewMockWebView(t)
208 wv.EXPECT().ID().Return(port.WebViewID(15)).Maybe()
209 wv.EXPECT().IsDestroyed().Return(false).Maybe()
210 wv.EXPECT().IsFullscreen().Return(false).Maybe()
211 wv.EXPECT().IsPlayingAudio().Return(false).Maybe()
212
213 pool := mocks.NewMockWebViewPool(t)
214 pool.EXPECT().Release(wv)
215
216 c := newMinimalCoordinator()
217 c.pool = pool
218 c.popups = newPopupManager()
219 c.popups.setWindowIDResolver(func(id entity.PaneID) (string, bool) {
220 if id == paneID {
221 return windowID, true
222 }
223 return "", false
224 })
225 c.webViews[paneID] = wv
226 c.popups.namedContexts.Register(windowID, windowID, "named-popup", paneID, wv.ID())
227
228 _, _, okBefore := c.popups.namedContexts.Lookup(windowID, "named-popup", c.getWebViewLocked, c.popups.windowIDForPane)
229 require.True(t, okBefore)
230
231 c.ReleaseWebView(context.Background(), paneID)
232
233 _, _, okAfter := c.popups.namedContexts.Lookup(windowID, "named-popup", c.getWebViewLocked, c.popups.windowIDForPane)
234 assert.False(t, okAfter)
235}
236
237func TestLifecycle_ReleaseWebView_NoopForUnknownPane(t *testing.T) {
238 t.Parallel()
239
240 pool := mocks.NewMockWebViewPool(t)
241 // pool.Release must NOT be called
242
243 c := newMinimalCoordinator()
244 c.pool = pool
245
246 // Should not panic
247 c.ReleaseWebView(context.Background(), "unknown-pane")
248}
249
250func TestLifecycle_ReleaseWebView_UninhibitsIdleIfFullscreen(t *testing.T) {
251 t.Parallel()
252
253 wv := mocks.NewMockWebView(t)
254 wv.EXPECT().ID().Return(port.WebViewID(12)).Maybe()
255 wv.EXPECT().IsFullscreen().Return(true)
256 wv.EXPECT().IsPlayingAudio().Return(false)
257
258 inhibitor := mocks.NewMockIdleInhibitor(t)
259 inhibitor.EXPECT().Uninhibit(mock.Anything).Return(nil)
260
261 pool := mocks.NewMockWebViewPool(t)
262 pool.EXPECT().Release(wv)
263
264 c := newMinimalCoordinator()
265 c.pool = pool
266 c.idleInhibitor = inhibitor
267 c.webViews[entity.PaneID("pane-1")] = wv
268
269 c.ReleaseWebView(context.Background(), "pane-1")
270}
271
272func TestLifecycle_ReleaseWebView_UninhibitsIdleIfPlayingAudio(t *testing.T) {
273 t.Parallel()
274
275 wv := mocks.NewMockWebView(t)
276 wv.EXPECT().ID().Return(port.WebViewID(13)).Maybe()
277 wv.EXPECT().IsFullscreen().Return(false)
278 wv.EXPECT().IsPlayingAudio().Return(true)
279
280 inhibitor := mocks.NewMockIdleInhibitor(t)
281 inhibitor.EXPECT().Uninhibit(mock.Anything).Return(nil)
282
283 pool := mocks.NewMockWebViewPool(t)
284 pool.EXPECT().Release(wv)
285
286 c := newMinimalCoordinator()
287 c.pool = pool
288 c.idleInhibitor = inhibitor
289 c.webViews[entity.PaneID("pane-1")] = wv
290
291 c.ReleaseWebView(context.Background(), "pane-1")
292}
293
294func TestLifecycle_ReleaseWebView_DestroysWebViewWhenPoolIsNil(t *testing.T) {
295 t.Parallel()
296
297 wv := mocks.NewMockWebView(t)
298 wv.EXPECT().ID().Return(port.WebViewID(14)).Maybe()
299 // idleInhibitor is nil so these won't be called
300 wv.EXPECT().IsFullscreen().Return(false).Maybe()
301 wv.EXPECT().IsPlayingAudio().Return(false).Maybe()
302 wv.EXPECT().Destroy()
303
304 c := newMinimalCoordinator()
305 // pool remains nil
306 c.webViews[entity.PaneID("pane-1")] = wv
307
308 c.ReleaseWebView(context.Background(), "pane-1")
309}
310
311// ---------------------------------------------------------------------------
312// GetWebView / RegisterPopupWebView
313// ---------------------------------------------------------------------------
314
315func TestLifecycle_GetWebView_ReturnsRegisteredWebView(t *testing.T) {
316 t.Parallel()
317
318 wv := mocks.NewMockWebView(t)
319
320 c := newMinimalCoordinator()
321 c.webViews[entity.PaneID("pane-1")] = wv
322
323 got := c.GetWebView("pane-1")
324
325 assert.Equal(t, wv, got)
326}
327
328func TestLifecycle_RegisterPopupWebView_StoresWebView(t *testing.T) {
329 t.Parallel()
330
331 wv := mocks.NewMockWebView(t)
332
333 c := newMinimalCoordinator()
334
335 c.RegisterPopupWebView("popup-1", wv)
336
337 assert.Equal(t, wv, c.GetWebView("popup-1"))
338}
339
340func TestLifecycle_RegisterPopupWebView_IgnoresNil(t *testing.T) {
341 t.Parallel()
342
343 c := newMinimalCoordinator()
344
345 // Should not panic
346 c.RegisterPopupWebView("popup-1", nil)
347
348 assert.Nil(t, c.GetWebView("popup-1"))
349}
350
351type nativeWebViewStub struct {
352 port.WebView
353 ptr uintptr
354}
355
356func (s *nativeWebViewStub) NativeWidget() uintptr {
357 return s.ptr
358}
359
360func TestLifecycle_WrapWidget_ReturnsNilForNilWebView(t *testing.T) {
361 t.Parallel()
362
363 factory := layoutmocks.NewMockWidgetFactory(t)
364 c := newMinimalCoordinator()
365 c.widgetFactory = factory
366
367 assert.Nil(t, c.WrapWidget(context.Background(), nil))
368}
369
370func TestLifecycle_WrapWidget_ReturnsNilForMissingNativeProvider(t *testing.T) {
371 t.Parallel()
372
373 wv := mocks.NewMockWebView(t)
374 factory := layoutmocks.NewMockWidgetFactory(t)
375 c := newMinimalCoordinator()
376 c.widgetFactory = factory
377
378 assert.Nil(t, c.WrapWidget(context.Background(), wv))
379}
380
381func TestLifecycle_WrapWidget_ReturnsNilForZeroNativePointer(t *testing.T) {
382 t.Parallel()
383
384 base := mocks.NewMockWebView(t)
385 wv := &nativeWebViewStub{WebView: base}
386 factory := layoutmocks.NewMockWidgetFactory(t)
387 c := newMinimalCoordinator()
388 c.widgetFactory = factory
389
390 assert.Nil(t, c.WrapWidget(context.Background(), wv))
391}
392
393func TestLifecycle_WrapWidget_ReturnsNilForNilWrappedWidget(t *testing.T) {
394 t.Parallel()
395
396 base := mocks.NewMockWebView(t)
397 wv := &nativeWebViewStub{WebView: base, ptr: 123}
398 factory := layoutmocks.NewMockWidgetFactory(t)
399 factory.EXPECT().WrapNativeWidget(uintptr(123)).Return(nil).Once()
400 c := newMinimalCoordinator()
401 c.widgetFactory = factory
402
403 assert.Nil(t, c.WrapWidget(context.Background(), wv))
404}
405
406func TestLifecycle_WrapWidget_RequestsGtkWidgetForGestureSetup(t *testing.T) {
407 t.Parallel()
408
409 base := mocks.NewMockWebView(t)
410 wv := &nativeWebViewStub{WebView: base, ptr: 456}
411 factory := layoutmocks.NewMockWidgetFactory(t)
412 widget := layoutmocks.NewMockWidget(t)
413 factory.EXPECT().WrapNativeWidget(uintptr(456)).Return(widget).Once()
414 widget.EXPECT().GtkWidget().Return(nil).Once()
415 c := newMinimalCoordinator()
416 c.widgetFactory = factory
417
418 assert.Same(t, widget, c.WrapWidget(context.Background(), wv))
419}
420
421type syncViewportContextKey struct{}
422
423type syncViewportCapableStub struct {
424 port.WebView
425 called bool
426 ctx context.Context
427 reason string
428}
429
430func (s *syncViewportCapableStub) SyncViewport(ctx context.Context, reason string) {
431 s.called = true
432 s.ctx = ctx
433 s.reason = reason
434}
435
436func TestLifecycle_SyncWebViewViewport_DelegatesWhenCapabilityPresent(t *testing.T) {
437 t.Parallel()
438
439 base := mocks.NewMockWebView(t)
440 wv := &syncViewportCapableStub{WebView: base}
441 c := newMinimalCoordinator()
442 c.webViews[entity.PaneID("pane-1")] = wv
443
444 ctx := context.WithValue(context.Background(), syncViewportContextKey{}, "marker")
445 c.SyncWebViewViewport(ctx, "pane-1", "unit-test")
446
447 require.True(t, wv.called)
448 require.Equal(t, "unit-test", wv.reason)
449 require.Same(t, ctx, wv.ctx)
450}
451
452func TestLifecycle_SyncWebViewViewport_NoopWithoutCapability(t *testing.T) {
453 t.Parallel()
454
455 wv := mocks.NewMockWebView(t)
456 c := newMinimalCoordinator()
457 c.webViews[entity.PaneID("pane-1")] = wv
458
459 c.SyncWebViewViewport(context.Background(), "pane-1", "unit-test")
460}
461
462type nativeLifecycleWebView struct {
463 port.WebView
464 widget uintptr
465}
466
467func (w *nativeLifecycleWebView) NativeWidget() uintptr { return w.widget }
468
469func TestLifecycle_AttachToWorkspace_RebuildPropagatesRevealStateToFreshPaneView(t *testing.T) {
470 for _, tc := range []struct {
471 name string
472 revealed bool
473 }{
474 {name: "revealed current identity hides and stops skeleton", revealed: true},
475 {name: "unrevealed current identity shows and starts skeleton", revealed: false},
476 } {
477 t.Run(tc.name, func(t *testing.T) {
478 paneID := entity.PaneID("pane-1")
479 ctx := context.Background()
480 factory := layoutmocks.NewMockWidgetFactory(t)
481 wsView, workspace, paneOverlay, loadingContainer, spinner := newLifecycleRebuildWorkspaceView(t, ctx, factory, paneID)
482 wrappedWidget := layoutmocks.NewMockWidget(t)
483 webViewMock := mocks.NewMockWebView(t)
484 webViewMock.EXPECT().ID().Return(port.WebViewID(201)).Maybe()
485 webViewMock.EXPECT().Generation().Return(uint64(3)).Maybe()
486 webViewMock.EXPECT().IsDestroyed().Return(false).Once()
487 webView := &nativeLifecycleWebView{WebView: webViewMock, widget: 0x351}
488
489 factory.EXPECT().WrapNativeWidget(uintptr(0x351)).Return(wrappedWidget).Once()
490 wrappedWidget.EXPECT().GtkWidget().Return(nil).Once()
491 wrappedWidget.EXPECT().GetAllocatedWidth().Return(0).Times(3)
492 wrappedWidget.EXPECT().GetAllocatedHeight().Return(0).Times(3)
493 wrappedWidget.EXPECT().GetParent().Return(nil).Once()
494 wrappedWidget.EXPECT().IsVisible().Return(true).Once()
495 paneOverlay.EXPECT().GetAllocatedWidth().Return(0).Twice()
496 paneOverlay.EXPECT().GetAllocatedHeight().Return(0).Twice()
497 paneOverlay.EXPECT().SetChild(wrappedWidget).Once()
498 loadingContainer.EXPECT().SetVisible(!tc.revealed).Once()
499 if tc.revealed {
500 spinner.EXPECT().Stop().Once()
501 } else {
502 spinner.EXPECT().Start().Once()
503 }
504
505 coordinator := newRevealTestCoordinator()
506 coordinator.widgetFactory = factory
507 coordinator.setWebViewLocked(paneID, webView)
508 if tc.revealed {
509 identity, ok := identityForWebView(webView)
510 require.True(t, ok)
511 coordinator.markWebViewRevealed(paneID, identity)
512 }
513
514 coordinator.AttachToWorkspace(ctx, workspace, wsView)
515
516 assert.Same(t, wrappedWidget, wsView.GetPaneView(paneID).WebViewWidget())
517 })
518 }
519}
520
521func newLifecycleRebuildWorkspaceView(
522 t *testing.T,
523 ctx context.Context,
524 factory *layoutmocks.MockWidgetFactory,
525 paneID entity.PaneID,
526) (*component.WorkspaceView, *entity.Workspace, *layoutmocks.MockOverlayWidget, *layoutmocks.MockBoxWidget, *layoutmocks.MockSpinnerWidget) {
527 t.Helper()
528 workspaceContainer := layoutmocks.NewMockBoxWidget(t)
529 workspaceOverlay := layoutmocks.NewMockOverlayWidget(t)
530 factory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(workspaceContainer).Once()
531 workspaceContainer.EXPECT().SetHexpand(true).Once()
532 workspaceContainer.EXPECT().SetVexpand(true).Once()
533 workspaceContainer.EXPECT().SetVisible(true).Once()
534 factory.EXPECT().NewOverlay().Return(workspaceOverlay).Once()
535 workspaceOverlay.EXPECT().SetHexpand(true).Once()
536 workspaceOverlay.EXPECT().SetVexpand(true).Once()
537 workspaceOverlay.EXPECT().SetChild(workspaceContainer).Once()
538 workspaceOverlay.EXPECT().SetVisible(true).Once()
539 wsView := component.NewWorkspaceView(ctx, factory)
540
541 paneOverlay := layoutmocks.NewMockOverlayWidget(t)
542 loadingContainer := layoutmocks.NewMockBoxWidget(t)
543 loadingContent := layoutmocks.NewMockBoxWidget(t)
544 spinner := layoutmocks.NewMockSpinnerWidget(t)
545 logo := layoutmocks.NewMockImageWidget(t)
546 version := layoutmocks.NewMockLabelWidget(t)
547 border := layoutmocks.NewMockBoxWidget(t)
548
549 factory.EXPECT().NewOverlay().Return(paneOverlay).Once()
550 paneOverlay.EXPECT().SetHexpand(true).Once()
551 paneOverlay.EXPECT().SetVexpand(true).Once()
552 paneOverlay.EXPECT().SetVisible(true).Once()
553 paneOverlay.EXPECT().AddCssClass("pane-overlay").Once()
554 factory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(loadingContainer).Once()
555 loadingContainer.EXPECT().SetHexpand(true).Once()
556 loadingContainer.EXPECT().SetVexpand(true).Once()
557 loadingContainer.EXPECT().SetHalign(mock.Anything).Once()
558 loadingContainer.EXPECT().SetValign(mock.Anything).Once()
559 loadingContainer.EXPECT().SetCanFocus(false).Times(2)
560 loadingContainer.EXPECT().SetCanTarget(false).Times(2)
561 loadingContainer.EXPECT().AddCssClass("loading-skeleton").Once()
562 factory.EXPECT().NewBox(layout.OrientationVertical, 6).Return(loadingContent).Once()
563 loadingContent.EXPECT().SetHalign(mock.Anything).Once()
564 loadingContent.EXPECT().SetValign(mock.Anything).Once()
565 loadingContent.EXPECT().SetCanFocus(false).Once()
566 loadingContent.EXPECT().SetCanTarget(false).Once()
567 loadingContent.EXPECT().AddCssClass("loading-skeleton-content").Once()
568 factory.EXPECT().NewImage().Return(logo).Once()
569 logo.EXPECT().SetHalign(mock.Anything).Once()
570 logo.EXPECT().SetValign(mock.Anything).Once()
571 logo.EXPECT().SetCanFocus(false).Once()
572 logo.EXPECT().SetCanTarget(false).Once()
573 logo.EXPECT().SetSizeRequest(256, 256).Once()
574 logo.EXPECT().SetPixelSize(256).Once()
575 logo.EXPECT().AddCssClass("loading-skeleton-logo").Once()
576 logo.EXPECT().SetFromPaintable(mock.Anything).Maybe()
577 factory.EXPECT().NewSpinner().Return(spinner).Once()
578 spinner.EXPECT().SetHalign(mock.Anything).Once()
579 spinner.EXPECT().SetValign(mock.Anything).Once()
580 spinner.EXPECT().SetCanFocus(false).Once()
581 spinner.EXPECT().SetCanTarget(false).Once()
582 spinner.EXPECT().SetSizeRequest(32, 32).Once()
583 spinner.EXPECT().AddCssClass("loading-skeleton-spinner").Once()
584 factory.EXPECT().NewLabel(mock.Anything).Return(version).Once()
585 version.EXPECT().SetHalign(mock.Anything).Once()
586 version.EXPECT().SetValign(mock.Anything).Once()
587 version.EXPECT().SetCanFocus(false).Once()
588 version.EXPECT().SetCanTarget(false).Once()
589 version.EXPECT().SetMaxWidthChars(mock.Anything).Once()
590 version.EXPECT().SetEllipsize(mock.Anything).Once()
591 version.EXPECT().AddCssClass("loading-skeleton-version").Once()
592 loadingContent.EXPECT().Append(logo).Once()
593 loadingContent.EXPECT().Append(spinner).Once()
594 loadingContent.EXPECT().Append(version).Once()
595 loadingContainer.EXPECT().Append(loadingContent).Once()
596 loadingContainer.EXPECT().SetVisible(true).Once()
597 spinner.EXPECT().Start().Once()
598 paneOverlay.EXPECT().AddOverlay(loadingContainer).Once()
599 paneOverlay.EXPECT().SetClipOverlay(loadingContainer, false).Once()
600 paneOverlay.EXPECT().SetMeasureOverlay(loadingContainer, false).Once()
601 factory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(border).Once()
602 border.EXPECT().SetCanFocus(false).Once()
603 border.EXPECT().SetCanTarget(false).Once()
604 border.EXPECT().AddCssClass("pane-border").Once()
605 border.EXPECT().SetHexpand(true).Once()
606 border.EXPECT().SetVexpand(true).Once()
607 paneOverlay.EXPECT().AddOverlay(border).Once()
608 paneOverlay.EXPECT().SetClipOverlay(border, false).Once()
609 paneOverlay.EXPECT().SetMeasureOverlay(border, false).Once()
610
611 stackBox := layoutmocks.NewMockBoxWidget(t)
612 titleBar := layoutmocks.NewMockBoxWidget(t)
613 favicon := layoutmocks.NewMockImageWidget(t)
614 title := layoutmocks.NewMockLabelWidget(t)
615 closeButton := layoutmocks.NewMockButtonWidget(t)
616 factory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(stackBox).Once()
617 stackBox.EXPECT().SetHexpand(true).Once()
618 stackBox.EXPECT().SetVexpand(true).Once()
619 factory.EXPECT().NewBox(layout.OrientationHorizontal, 4).Return(titleBar).Once()
620 titleBar.EXPECT().AddCssClass("stacked-pane-titlebar").Once()
621 titleBar.EXPECT().AddCssClass("stacked-pane-title-clickable").Once()
622 titleBar.EXPECT().SetVexpand(false).Once()
623 titleBar.EXPECT().SetHexpand(true).Once()
624 factory.EXPECT().NewImage().Return(favicon).Once()
625 favicon.EXPECT().SetFromIconName(mock.Anything).Once()
626 favicon.EXPECT().SetPixelSize(16).Once()
627 titleBar.EXPECT().Append(favicon).Once()
628 factory.EXPECT().NewLabel(mock.Anything).Return(title).Once()
629 title.EXPECT().SetEllipsize(layout.EllipsizeEnd).Once()
630 title.EXPECT().SetMaxWidthChars(30).Once()
631 title.EXPECT().SetHexpand(true).Once()
632 title.EXPECT().SetXalign(float32(0)).Once()
633 titleBar.EXPECT().Append(title).Once()
634 factory.EXPECT().NewButton().Return(closeButton).Once()
635 closeButton.EXPECT().SetIconName("window-close-symbolic").Once()
636 closeButton.EXPECT().AddCssClass("stacked-pane-close-button").Once()
637 closeButton.EXPECT().SetFocusOnClick(false).Once()
638 closeButton.EXPECT().SetVexpand(false).Once()
639 closeButton.EXPECT().SetHexpand(false).Once()
640 titleBar.EXPECT().Append(closeButton).Once()
641 titleBar.EXPECT().AddController(mock.Anything).Once()
642 closeButton.EXPECT().ConnectClicked(mock.Anything).Return(uint(2)).Once()
643 closeButton.EXPECT().GtkWidget().Return(nil).Maybe()
644 stackBox.EXPECT().Append(titleBar).Once()
645 stackBox.EXPECT().Append(paneOverlay).Once()
646 titleBar.EXPECT().SetVisible(false).Once()
647 paneOverlay.EXPECT().SetVisible(true).Once()
648 titleBar.EXPECT().AddCssClass("active").Once()
649 paneOverlay.EXPECT().GtkWidget().Return(nil).Maybe()
650 stackBox.EXPECT().SetVisible(true).Once()
651 workspaceContainer.EXPECT().Append(stackBox).Once()
652 workspaceContainer.EXPECT().AddCssClass("single-pane").Once()
653 border.EXPECT().AddCssClass("pane-active").Once()
654
655 workspace := &entity.Workspace{
656 ID: "workspace-1",
657 Root: &entity.PaneNode{ID: "node-1", Pane: entity.NewPane(paneID)},
658 ActivePaneID: paneID,
659 }
660 require.NoError(t, wsView.SetWorkspace(ctx, workspace))
661 return wsView, workspace, paneOverlay, loadingContainer, spinner
662}