test(ui): cover rebuilt pane reveal state
- commit
- 47e07ec6af753736022fe394e025b3dd8c1e7569
- author
- brice · · 2026-07-15 04:41 UTC
- committer
- brice · · 2026-07-15 04:42 UTC
- parent
- 9baacd8
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +224 -57
M internal/ui/component/pane_view_internal_test.go +22 -57
| @@ -3,6 +3,7 @@ package component | |||
| 3 | 3 | import ( | |
| 4 | 4 | "testing" | |
| 5 | 5 | ||
| 6 | + | "github.com/stretchr/testify/assert" | |
| 6 | 7 | "github.com/stretchr/testify/mock" | |
| 7 | 8 | ||
| 8 | 9 | "github.com/bnema/dumber/internal/ui/layout" | |
| @@ -81,66 +82,26 @@ func TestPaneView_HideLoadingSkeleton_NoOpWhenNil(t *testing.T) { | |||
| 81 | 82 | pv.HideLoadingSkeleton() | |
| 82 | 83 | } | |
| 83 | 84 | ||
| 84 | - | func TestPaneView_AttachWebViewWidget_UsesExplicitRevealStateAfterCleanup(t *testing.T) { | |
| 85 | - | widget := mocks.NewMockWidget(t) | |
| 86 | - | oldOverlay := mocks.NewMockOverlayWidget(t) | |
| 87 | - | oldOverlay.EXPECT().SetChild(nil).Once() | |
| 88 | - | (&PaneView{overlay: oldOverlay, webViewWidget: widget}).Cleanup() | |
| 89 | - | ||
| 90 | - | t.Run("revealed widget detached by cleanup hides fresh skeleton", func(t *testing.T) { | |
| 91 | - | overlay := mocks.NewMockOverlayWidget(t) | |
| 92 | - | loadingContainer := mocks.NewMockBoxWidget(t) | |
| 93 | - | spinner := mocks.NewMockSpinnerWidget(t) | |
| 94 | - | overlay.EXPECT().GetAllocatedWidth().Return(0).Twice() | |
| 95 | - | overlay.EXPECT().GetAllocatedHeight().Return(0).Twice() | |
| 96 | - | widget.EXPECT().GetAllocatedWidth().Return(0).Twice() | |
| 97 | - | widget.EXPECT().GetAllocatedHeight().Return(0).Twice() | |
| 98 | - | widget.EXPECT().GetParent().Return(nil).Once() | |
| 99 | - | widget.EXPECT().IsVisible().Return(true).Once() | |
| 100 | - | overlay.EXPECT().SetChild(widget).Once() | |
| 101 | - | loadingContainer.EXPECT().SetVisible(false).Once() | |
| 102 | - | spinner.EXPECT().Stop().Once() | |
| 103 | - | ||
| 104 | - | pv := &PaneView{ | |
| 105 | - | overlay: overlay, | |
| 106 | - | loading: &LoadingSkeleton{container: loadingContainer, spinner: spinner}, | |
| 107 | - | } | |
| 108 | - | pv.AttachWebViewWidget(widget, true) | |
| 109 | - | }) | |
| 110 | - | ||
| 111 | - | t.Run("new unrevealed widget keeps fresh skeleton visible", func(t *testing.T) { | |
| 112 | - | overlay := mocks.NewMockOverlayWidget(t) | |
| 113 | - | loadingContainer := mocks.NewMockBoxWidget(t) | |
| 114 | - | overlay.EXPECT().GetAllocatedWidth().Return(0).Twice() | |
| 115 | - | overlay.EXPECT().GetAllocatedHeight().Return(0).Twice() | |
| 116 | - | widget.EXPECT().GetAllocatedWidth().Return(0).Twice() | |
| 117 | - | widget.EXPECT().GetAllocatedHeight().Return(0).Twice() | |
| 118 | - | widget.EXPECT().GetParent().Return(nil).Once() | |
| 119 | - | widget.EXPECT().IsVisible().Return(true).Once() | |
| 120 | - | overlay.EXPECT().SetChild(widget).Once() | |
| 121 | - | loadingContainer.EXPECT().SetVisible(true).Once() | |
| 122 | - | ||
| 123 | - | pv := &PaneView{ | |
| 124 | - | overlay: overlay, | |
| 125 | - | loading: &LoadingSkeleton{container: loadingContainer}, | |
| 126 | - | } | |
| 127 | - | pv.AttachWebViewWidget(widget, false) | |
| 128 | - | }) | |
| 129 | - | } | |
| 130 | - | ||
| 131 | - | func TestPaneView_AttachWebViewWidget_UnrevealedReplacementRestartsLoadingSkeleton(t *testing.T) { | |
| 85 | + | func TestPaneView_AttachWebViewWidget_RevealedThenUnrevealedReplacementRestartsSkeleton(t *testing.T) { | |
| 132 | 86 | overlay := mocks.NewMockOverlayWidget(t) | |
| 133 | - | widget := mocks.NewMockWidget(t) | |
| 87 | + | revealedWidget := mocks.NewMockWidget(t) | |
| 88 | + | unrevealedWidget := mocks.NewMockWidget(t) | |
| 134 | 89 | loadingContainer := mocks.NewMockBoxWidget(t) | |
| 135 | 90 | spinner := mocks.NewMockSpinnerWidget(t) | |
| 136 | 91 | ||
| 137 | - | overlay.EXPECT().GetAllocatedWidth().Return(0).Twice() | |
| 138 | - | overlay.EXPECT().GetAllocatedHeight().Return(0).Twice() | |
| 139 | - | widget.EXPECT().GetAllocatedWidth().Return(0).Twice() | |
| 140 | - | widget.EXPECT().GetAllocatedHeight().Return(0).Twice() | |
| 141 | - | widget.EXPECT().GetParent().Return(nil).Once() | |
| 142 | - | widget.EXPECT().IsVisible().Return(true).Once() | |
| 143 | - | overlay.EXPECT().SetChild(widget).Once() | |
| 92 | + | overlay.EXPECT().GetAllocatedWidth().Return(0).Times(4) | |
| 93 | + | overlay.EXPECT().GetAllocatedHeight().Return(0).Times(4) | |
| 94 | + | for _, widget := range []*mocks.MockWidget{revealedWidget, unrevealedWidget} { | |
| 95 | + | widget.EXPECT().GetAllocatedWidth().Return(0).Twice() | |
| 96 | + | widget.EXPECT().GetAllocatedHeight().Return(0).Twice() | |
| 97 | + | widget.EXPECT().GetParent().Return(nil).Once() | |
| 98 | + | widget.EXPECT().IsVisible().Return(true).Once() | |
| 99 | + | } | |
| 100 | + | overlay.EXPECT().SetChild(revealedWidget).Once() | |
| 101 | + | loadingContainer.EXPECT().SetVisible(false).Once() | |
| 102 | + | spinner.EXPECT().Stop().Once() | |
| 103 | + | overlay.EXPECT().SetChild(nil).Once() | |
| 104 | + | overlay.EXPECT().SetChild(unrevealedWidget).Once() | |
| 144 | 105 | loadingContainer.EXPECT().SetVisible(true).Once() | |
| 145 | 106 | spinner.EXPECT().Start().Once() | |
| 146 | 107 | ||
| @@ -148,5 +109,9 @@ func TestPaneView_AttachWebViewWidget_UnrevealedReplacementRestartsLoadingSkelet | |||
| 148 | 109 | overlay: overlay, | |
| 149 | 110 | loading: &LoadingSkeleton{container: loadingContainer, spinner: spinner}, | |
| 150 | 111 | } | |
| 151 | - | pv.AttachWebViewWidget(widget, false) | |
| 112 | + | ||
| 113 | + | pv.AttachWebViewWidget(revealedWidget, true) | |
| 114 | + | pv.AttachWebViewWidget(unrevealedWidget, false) | |
| 115 | + | ||
| 116 | + | assert.Same(t, unrevealedWidget, pv.WebViewWidget()) | |
| 152 | 117 | } | |
| @@ -3,6 +3,7 @@ package component | |||||
| 3 | import ( | 3 | import ( | ||
| 4 | "testing" | 4 | "testing" | ||
| 5 | 5 | ||||
| 6 | + | "github.com/stretchr/testify/assert" | |||
| 6 | "github.com/stretchr/testify/mock" | 7 | "github.com/stretchr/testify/mock" | ||
| 7 | 8 | ||||
| 8 | "github.com/bnema/dumber/internal/ui/layout" | 9 | "github.com/bnema/dumber/internal/ui/layout" | ||
| @@ -81,66 +82,26 @@ func TestPaneView_HideLoadingSkeleton_NoOpWhenNil(t *testing.T) { | |||||
| 81 | pv.HideLoadingSkeleton() | 82 | pv.HideLoadingSkeleton() | ||
| 82 | } | 83 | } | ||
| 83 | 84 | ||||
| 84 | - | func TestPaneView_AttachWebViewWidget_UsesExplicitRevealStateAfterCleanup(t *testing.T) { | 85 | + | func TestPaneView_AttachWebViewWidget_RevealedThenUnrevealedReplacementRestartsSkeleton(t *testing.T) { |
| 85 | - | widget := mocks.NewMockWidget(t) | |||
| 86 | - | oldOverlay := mocks.NewMockOverlayWidget(t) | |||
| 87 | - | oldOverlay.EXPECT().SetChild(nil).Once() | |||
| 88 | - | (&PaneView{overlay: oldOverlay, webViewWidget: widget}).Cleanup() | |||
| 89 | - | ||||
| 90 | - | t.Run("revealed widget detached by cleanup hides fresh skeleton", func(t *testing.T) { | |||
| 91 | - | overlay := mocks.NewMockOverlayWidget(t) | |||
| 92 | - | loadingContainer := mocks.NewMockBoxWidget(t) | |||
| 93 | - | spinner := mocks.NewMockSpinnerWidget(t) | |||
| 94 | - | overlay.EXPECT().GetAllocatedWidth().Return(0).Twice() | |||
| 95 | - | overlay.EXPECT().GetAllocatedHeight().Return(0).Twice() | |||
| 96 | - | widget.EXPECT().GetAllocatedWidth().Return(0).Twice() | |||
| 97 | - | widget.EXPECT().GetAllocatedHeight().Return(0).Twice() | |||
| 98 | - | widget.EXPECT().GetParent().Return(nil).Once() | |||
| 99 | - | widget.EXPECT().IsVisible().Return(true).Once() | |||
| 100 | - | overlay.EXPECT().SetChild(widget).Once() | |||
| 101 | - | loadingContainer.EXPECT().SetVisible(false).Once() | |||
| 102 | - | spinner.EXPECT().Stop().Once() | |||
| 103 | - | ||||
| 104 | - | pv := &PaneView{ | |||
| 105 | - | overlay: overlay, | |||
| 106 | - | loading: &LoadingSkeleton{container: loadingContainer, spinner: spinner}, | |||
| 107 | - | } | |||
| 108 | - | pv.AttachWebViewWidget(widget, true) | |||
| 109 | - | }) | |||
| 110 | - | ||||
| 111 | - | t.Run("new unrevealed widget keeps fresh skeleton visible", func(t *testing.T) { | |||
| 112 | - | overlay := mocks.NewMockOverlayWidget(t) | |||
| 113 | - | loadingContainer := mocks.NewMockBoxWidget(t) | |||
| 114 | - | overlay.EXPECT().GetAllocatedWidth().Return(0).Twice() | |||
| 115 | - | overlay.EXPECT().GetAllocatedHeight().Return(0).Twice() | |||
| 116 | - | widget.EXPECT().GetAllocatedWidth().Return(0).Twice() | |||
| 117 | - | widget.EXPECT().GetAllocatedHeight().Return(0).Twice() | |||
| 118 | - | widget.EXPECT().GetParent().Return(nil).Once() | |||
| 119 | - | widget.EXPECT().IsVisible().Return(true).Once() | |||
| 120 | - | overlay.EXPECT().SetChild(widget).Once() | |||
| 121 | - | loadingContainer.EXPECT().SetVisible(true).Once() | |||
| 122 | - | ||||
| 123 | - | pv := &PaneView{ | |||
| 124 | - | overlay: overlay, | |||
| 125 | - | loading: &LoadingSkeleton{container: loadingContainer}, | |||
| 126 | - | } | |||
| 127 | - | pv.AttachWebViewWidget(widget, false) | |||
| 128 | - | }) | |||
| 129 | - | } | |||
| 130 | - | ||||
| 131 | - | func TestPaneView_AttachWebViewWidget_UnrevealedReplacementRestartsLoadingSkeleton(t *testing.T) { | |||
| 132 | overlay := mocks.NewMockOverlayWidget(t) | 86 | overlay := mocks.NewMockOverlayWidget(t) | ||
| 133 | - | widget := mocks.NewMockWidget(t) | 87 | + | revealedWidget := mocks.NewMockWidget(t) |
| 88 | + | unrevealedWidget := mocks.NewMockWidget(t) | |||
| 134 | loadingContainer := mocks.NewMockBoxWidget(t) | 89 | loadingContainer := mocks.NewMockBoxWidget(t) | ||
| 135 | spinner := mocks.NewMockSpinnerWidget(t) | 90 | spinner := mocks.NewMockSpinnerWidget(t) | ||
| 136 | 91 | ||||
| 137 | - | overlay.EXPECT().GetAllocatedWidth().Return(0).Twice() | 92 | + | overlay.EXPECT().GetAllocatedWidth().Return(0).Times(4) |
| 138 | - | overlay.EXPECT().GetAllocatedHeight().Return(0).Twice() | 93 | + | overlay.EXPECT().GetAllocatedHeight().Return(0).Times(4) |
| 139 | - | widget.EXPECT().GetAllocatedWidth().Return(0).Twice() | 94 | + | for _, widget := range []*mocks.MockWidget{revealedWidget, unrevealedWidget} { |
| 140 | - | widget.EXPECT().GetAllocatedHeight().Return(0).Twice() | 95 | + | widget.EXPECT().GetAllocatedWidth().Return(0).Twice() |
| 141 | - | widget.EXPECT().GetParent().Return(nil).Once() | 96 | + | widget.EXPECT().GetAllocatedHeight().Return(0).Twice() |
| 142 | - | widget.EXPECT().IsVisible().Return(true).Once() | 97 | + | widget.EXPECT().GetParent().Return(nil).Once() |
| 143 | - | overlay.EXPECT().SetChild(widget).Once() | 98 | + | widget.EXPECT().IsVisible().Return(true).Once() |
| 99 | + | } | |||
| 100 | + | overlay.EXPECT().SetChild(revealedWidget).Once() | |||
| 101 | + | loadingContainer.EXPECT().SetVisible(false).Once() | |||
| 102 | + | spinner.EXPECT().Stop().Once() | |||
| 103 | + | overlay.EXPECT().SetChild(nil).Once() | |||
| 104 | + | overlay.EXPECT().SetChild(unrevealedWidget).Once() | |||
| 144 | loadingContainer.EXPECT().SetVisible(true).Once() | 105 | loadingContainer.EXPECT().SetVisible(true).Once() | ||
| 145 | spinner.EXPECT().Start().Once() | 106 | spinner.EXPECT().Start().Once() | ||
| 146 | 107 | ||||
| @@ -148,5 +109,9 @@ func TestPaneView_AttachWebViewWidget_UnrevealedReplacementRestartsLoadingSkelet | |||||
| 148 | overlay: overlay, | 109 | overlay: overlay, | ||
| 149 | loading: &LoadingSkeleton{container: loadingContainer, spinner: spinner}, | 110 | loading: &LoadingSkeleton{container: loadingContainer, spinner: spinner}, | ||
| 150 | } | 111 | } | ||
| 151 | - | pv.AttachWebViewWidget(widget, false) | 112 | + | |
| 113 | + | pv.AttachWebViewWidget(revealedWidget, true) | |||
| 114 | + | pv.AttachWebViewWidget(unrevealedWidget, false) | |||
| 115 | + | ||||
| 116 | + | assert.Same(t, unrevealedWidget, pv.WebViewWidget()) | |||
| 152 | } | 117 | } | ||
M internal/ui/coordinator/content/lifecycle_test.go +202 -0
| @@ -458,3 +458,205 @@ func TestLifecycle_SyncWebViewViewport_NoopWithoutCapability(t *testing.T) { | |||
| 458 | 458 | ||
| 459 | 459 | c.SyncWebViewViewport(context.Background(), "pane-1", "unit-test") | |
| 460 | 460 | } | |
| 461 | + | ||
| 462 | + | type nativeLifecycleWebView struct { | |
| 463 | + | port.WebView | |
| 464 | + | widget uintptr | |
| 465 | + | } | |
| 466 | + | ||
| 467 | + | func (w *nativeLifecycleWebView) NativeWidget() uintptr { return w.widget } | |
| 468 | + | ||
| 469 | + | func 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 | + | ||
| 521 | + | func 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 | + | } | |
| @@ -458,3 +458,205 @@ func TestLifecycle_SyncWebViewViewport_NoopWithoutCapability(t *testing.T) { | |||||
| 458 | 458 | ||||
| 459 | c.SyncWebViewViewport(context.Background(), "pane-1", "unit-test") | 459 | c.SyncWebViewViewport(context.Background(), "pane-1", "unit-test") | ||
| 460 | } | 460 | } | ||
| 461 | + | ||||
| 462 | + | type nativeLifecycleWebView struct { | |||
| 463 | + | port.WebView | |||
| 464 | + | widget uintptr | |||
| 465 | + | } | |||
| 466 | + | ||||
| 467 | + | func (w *nativeLifecycleWebView) NativeWidget() uintptr { return w.widget } | |||
| 468 | + | ||||
| 469 | + | func 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 | + | ||||
| 521 | + | func 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 | + | } | |||