1package ui
2
3import (
4 "context"
5 "errors"
6 "testing"
7
8 "github.com/stretchr/testify/assert"
9 "github.com/stretchr/testify/require"
10
11 "github.com/bnema/dumber/internal/application/port"
12 portmocks "github.com/bnema/dumber/internal/application/port/mocks"
13 "github.com/bnema/dumber/internal/application/usecase"
14 "github.com/bnema/dumber/internal/domain/entity"
15 "github.com/bnema/dumber/internal/ui/component"
16 "github.com/bnema/dumber/internal/ui/coordinator"
17 "github.com/bnema/dumber/internal/ui/coordinator/content"
18)
19
20func newPopupAdoptionApp(t *testing.T, attach func(context.Context, entity.TabID, *entity.Pane, port.WebView) error) (*App, *browserWindow) {
21 t.Helper()
22 bw := &browserWindow{id: "detached-window", tabs: entity.NewTabList()}
23 app := &App{
24 tabs: entity.NewTabList(), tabsUC: usecase.NewManageTabsUseCase(func() string { return "adopted-tab" }, nil),
25 browserWindows: map[string]*browserWindow{}, windowForTab: map[entity.TabID]*browserWindow{},
26 workspaceViews: map[entity.TabID]*component.WorkspaceView{},
27 browserWindowFactory: func(context.Context, string) (*browserWindow, error) { return bw, nil },
28 }
29 app.tabCoord = coordinator.NewTabCoordinator(context.Background(), coordinator.TabCoordinatorConfig{TabsUC: app.tabsUC, Tabs: app.tabs})
30 app.tabCoord.SetOnAttachPopupToTab(attach)
31 app.tabCoord.SetOnTabClosed(func(_ context.Context, _ coordinator.TabTarget, tab *entity.Tab) {
32 delete(app.windowForTab, tab.ID)
33 delete(app.workspaceViews, tab.ID)
34 app.tabs.Remove(tab.ID)
35 })
36 app.tabCoord.SetOnCurrentWindowEmpty(func(context.Context, coordinator.TabTarget) {
37 app.cleanupEjectTargetWindow(bw)
38 })
39 return app, bw
40}
41
42type popupLifecycleWebView struct {
43 *portmocks.MockWebView
44 onReady func()
45 onClose func()
46 showCalls int
47}
48
49func (w *popupLifecycleWebView) SetOnReadyToShow(fn func()) { w.onReady = fn }
50func (w *popupLifecycleWebView) SetOnClose(fn func()) { w.onClose = fn }
51func (w *popupLifecycleWebView) Show() { w.showCalls++ }
52func (w *popupLifecycleWebView) PrimePopupNavigation(string) {}
53
54func TestOpenPopupBrowserWindowAdoptsProvidedWebView(t *testing.T) {
55 wv := portmocks.NewMockWebView(t)
56 var adopted port.WebView
57 app, bw := newPopupAdoptionApp(t, func(_ context.Context, _ entity.TabID, _ *entity.Pane, got port.WebView) error {
58 adopted = got
59 return nil
60 })
61
62 result, err := app.openPopupBrowserWindow(context.Background(), content.BrowserWindowInput{
63 PopupPane: entity.NewPane("popup-pane"), PopupWebView: wv, TargetURI: "https://example.com", Ready: true,
64 })
65
66 require.NoError(t, err)
67 assert.Same(t, wv, adopted)
68 assert.Equal(t, bw.id, result.WindowID)
69 assert.Same(t, bw, app.windowForTab["adopted-tab"])
70}
71
72func TestOpenPopupBrowserWindowWaitsForReadySignalAndShowsExactlyOnce(t *testing.T) {
73 previousShow := showPopupBrowserWindow
74 showCalls := 0
75 showPopupBrowserWindow = func(*browserWindow) { showCalls++ }
76 t.Cleanup(func() { showPopupBrowserWindow = previousShow })
77
78 wv := &popupLifecycleWebView{MockWebView: portmocks.NewMockWebView(t)}
79 app, _ := newPopupAdoptionApp(t, func(context.Context, entity.TabID, *entity.Pane, port.WebView) error { return nil })
80
81 _, err := app.openPopupBrowserWindow(context.Background(), content.BrowserWindowInput{
82 PopupPane: entity.NewPane("popup-pane"), PopupWebView: wv, Ready: false,
83 })
84
85 require.NoError(t, err)
86 require.NotNil(t, wv.onReady)
87 assert.NotNil(t, wv.onClose)
88 assert.Zero(t, showCalls)
89 wv.onReady()
90 wv.onReady()
91 assert.Equal(t, 1, showCalls)
92}
93
94func TestOpenPopupBrowserWindowShowsImmediatelyWhenAlreadyReady(t *testing.T) {
95 previousShow := showPopupBrowserWindow
96 showCalls := 0
97 showPopupBrowserWindow = func(*browserWindow) { showCalls++ }
98 t.Cleanup(func() { showPopupBrowserWindow = previousShow })
99
100 wv := &popupLifecycleWebView{MockWebView: portmocks.NewMockWebView(t)}
101 app, _ := newPopupAdoptionApp(t, func(context.Context, entity.TabID, *entity.Pane, port.WebView) error { return nil })
102
103 _, err := app.openPopupBrowserWindow(context.Background(), content.BrowserWindowInput{
104 PopupPane: entity.NewPane("popup-pane"), PopupWebView: wv, Ready: true,
105 })
106
107 require.NoError(t, err)
108 assert.Nil(t, wv.onReady)
109 assert.NotNil(t, wv.onClose)
110 assert.Equal(t, 1, showCalls)
111}
112
113func TestOpenPopupBrowserWindowTransferredWebViewIsNotDestroyed(t *testing.T) {
114 wv := &recordingWebView{id: 99}
115 app, _ := newPopupAdoptionApp(t, func(context.Context, entity.TabID, *entity.Pane, port.WebView) error { return nil })
116
117 _, err := app.openPopupBrowserWindow(context.Background(), content.BrowserWindowInput{
118 PopupPane: entity.NewPane("popup-pane"), PopupWebView: wv, Ready: true,
119 })
120
121 require.NoError(t, err)
122 assert.Zero(t, wv.destroyCalls)
123}
124
125func TestOpenPopupBrowserWindowCloseRequestClosesAdoptedLastTabAndWindow(t *testing.T) {
126 wv := &popupLifecycleWebView{MockWebView: portmocks.NewMockWebView(t)}
127 app, bw := newPopupAdoptionApp(t, func(context.Context, entity.TabID, *entity.Pane, port.WebView) error { return nil })
128
129 _, err := app.openPopupBrowserWindow(context.Background(), content.BrowserWindowInput{
130 PopupPane: entity.NewPane("popup-pane"), PopupWebView: wv, Ready: true,
131 })
132 require.NoError(t, err)
133 require.NotNil(t, wv.onClose)
134
135 wv.onClose()
136
137 assert.Zero(t, bw.tabs.Count())
138 assert.False(t, app.hasBrowserWindow(bw))
139 assert.Empty(t, app.windowForTab)
140}
141
142func TestOpenPopupBrowserWindowRollsBackShellWhenCreateWithPaneFails(t *testing.T) {
143 wv := portmocks.NewMockWebView(t)
144 app, bw := newPopupAdoptionApp(t, func(context.Context, entity.TabID, *entity.Pane, port.WebView) error {
145 return errors.New("attachment failed")
146 })
147
148 _, err := app.openPopupBrowserWindow(context.Background(), content.BrowserWindowInput{
149 PopupPane: entity.NewPane("popup-pane"), PopupWebView: wv, TargetURI: "https://example.com", Ready: true,
150 })
151
152 require.Error(t, err)
153 assert.False(t, app.hasBrowserWindow(bw))
154 assert.Zero(t, bw.tabs.Count())
155 assert.Empty(t, app.windowForTab)
156 assert.Empty(t, app.workspaceViews)
157}