1package component_test
2
3import (
4 "context"
5 "errors"
6 "sync"
7 "testing"
8
9 "github.com/bnema/dumber/internal/ui/component"
10 layoutmocks "github.com/bnema/dumber/internal/ui/layout/mocks"
11 "github.com/stretchr/testify/assert"
12 "github.com/stretchr/testify/require"
13)
14
15func TestFloatingPane_HideShowPreservesState(t *testing.T) {
16 overlay := layoutmocks.NewMockOverlayWidget(t)
17 overlay.EXPECT().GetAllocatedWidth().Return(1200).Maybe()
18 overlay.EXPECT().GetAllocatedHeight().Return(800).Maybe()
19
20 navigated := make([]string, 0)
21 fp := component.NewFloatingPane(overlay, component.FloatingPaneOptions{
22 WidthPct: 0.82,
23 HeightPct: 0.72,
24 FallbackWidth: 1200,
25 FallbackHeight: 800,
26 OnNavigate: func(_ context.Context, url string) error {
27 navigated = append(navigated, url)
28 return nil
29 },
30 })
31
32 require.NoError(t, fp.ShowToggle(context.Background()))
33 require.Equal(t, "about:blank", fp.CurrentURL())
34
35 require.NoError(t, fp.Navigate(context.Background(), "https://google.com"))
36 fp.Hide(context.Background())
37 require.NoError(t, fp.ShowToggle(context.Background()))
38
39 assert.Equal(t, "https://google.com", fp.CurrentURL())
40 assert.False(t, fp.IsOmniboxVisible())
41 assert.Equal(t, []string{"about:blank", "https://google.com"}, navigated)
42}
43
44func TestFloatingPane_ShowURLLoadsTarget(t *testing.T) {
45 overlay := layoutmocks.NewMockOverlayWidget(t)
46 overlay.EXPECT().GetAllocatedWidth().Return(1000).Maybe()
47 overlay.EXPECT().GetAllocatedHeight().Return(700).Maybe()
48
49 fp := component.NewFloatingPane(overlay, component.FloatingPaneOptions{
50 WidthPct: 0.82,
51 HeightPct: 0.72,
52 FallbackWidth: 1000,
53 FallbackHeight: 700,
54 })
55
56 require.NoError(t, fp.ShowURL(context.Background(), "https://github.com"))
57
58 assert.True(t, fp.IsVisible())
59 assert.False(t, fp.IsOmniboxVisible())
60 assert.Equal(t, "https://github.com", fp.CurrentURL())
61}
62
63func TestFloatingPane_ResizeUsesWorkspacePercentages(t *testing.T) {
64 overlay := layoutmocks.NewMockOverlayWidget(t)
65 overlay.EXPECT().GetAllocatedWidth().Return(1000).Maybe()
66 overlay.EXPECT().GetAllocatedHeight().Return(700).Maybe()
67
68 fp := component.NewFloatingPane(overlay, component.FloatingPaneOptions{
69 WidthPct: 0.82,
70 HeightPct: 0.72,
71 FallbackWidth: 1000,
72 FallbackHeight: 700,
73 })
74
75 fp.Resize()
76 width, height := fp.Dimensions()
77
78 assert.Equal(t, 820, width)
79 assert.Equal(t, 504, height)
80}
81
82func TestFloatingPane_FirstToggleOpensBlankAndOmnibox(t *testing.T) {
83 overlay := layoutmocks.NewMockOverlayWidget(t)
84 overlay.EXPECT().GetAllocatedWidth().Return(1200).Maybe()
85 overlay.EXPECT().GetAllocatedHeight().Return(800).Maybe()
86
87 fp := component.NewFloatingPane(overlay, component.FloatingPaneOptions{
88 WidthPct: 0.82,
89 HeightPct: 0.72,
90 FallbackWidth: 1200,
91 FallbackHeight: 800,
92 })
93
94 require.NoError(t, fp.ShowToggle(context.Background()))
95
96 assert.True(t, fp.IsVisible())
97 assert.True(t, fp.IsOmniboxVisible())
98 assert.Equal(t, "about:blank", fp.CurrentURL())
99}
100
101func TestFloatingPane_SetOmniboxVisible(t *testing.T) {
102 fp := component.NewFloatingPane(nil, component.FloatingPaneOptions{
103 WidthPct: 0.82,
104 HeightPct: 0.72,
105 FallbackWidth: 1200,
106 FallbackHeight: 800,
107 })
108
109 fp.SetOmniboxVisible(true)
110 assert.True(t, fp.IsOmniboxVisible())
111
112 fp.SetOmniboxVisible(false)
113 assert.False(t, fp.IsOmniboxVisible())
114}
115
116func TestFloatingPane_ConcurrentResizeAndParentSwitch(t *testing.T) {
117 overlayA := layoutmocks.NewMockOverlayWidget(t)
118 overlayA.EXPECT().GetAllocatedWidth().Return(1200).Maybe()
119 overlayA.EXPECT().GetAllocatedHeight().Return(800).Maybe()
120
121 overlayB := layoutmocks.NewMockOverlayWidget(t)
122 overlayB.EXPECT().GetAllocatedWidth().Return(1000).Maybe()
123 overlayB.EXPECT().GetAllocatedHeight().Return(700).Maybe()
124
125 fp := component.NewFloatingPane(overlayA, component.FloatingPaneOptions{
126 WidthPct: 0.82,
127 HeightPct: 0.72,
128 FallbackWidth: 1200,
129 FallbackHeight: 800,
130 })
131
132 var wg sync.WaitGroup
133 for i := range 200 {
134 wg.Add(2)
135 go func() {
136 defer wg.Done()
137 fp.Resize()
138 }()
139 go func(iter int) {
140 defer wg.Done()
141 if iter%2 == 0 {
142 fp.SetParentOverlay(overlayA)
143 return
144 }
145 fp.SetParentOverlay(overlayB)
146 }(i)
147 }
148 wg.Wait()
149
150 width, height := fp.Dimensions()
151 assert.Positive(t, width)
152 assert.Positive(t, height)
153}
154
155func TestFloatingPane_ShowURL_RollsBackVisibilityOnNavigateError(t *testing.T) {
156 expectedErr := errors.New("navigate failed")
157 fp := component.NewFloatingPane(nil, component.FloatingPaneOptions{
158 WidthPct: 0.82,
159 HeightPct: 0.72,
160 FallbackWidth: 1200,
161 FallbackHeight: 800,
162 OnNavigate: func(context.Context, string) error {
163 return expectedErr
164 },
165 })
166
167 err := fp.ShowURL(context.Background(), "https://example.com")
168 require.ErrorIs(t, err, expectedErr)
169 assert.False(t, fp.IsVisible())
170 assert.False(t, fp.IsOmniboxVisible())
171 assert.Empty(t, fp.CurrentURL())
172}
173
174func TestFloatingPane_ShowToggleNavigateError_DoesNotClobberConcurrentStateChange(t *testing.T) {
175 navigationStarted := make(chan struct{})
176 releaseNavigation := make(chan struct{})
177 expectedErr := errors.New("navigate failed")
178
179 fp := component.NewFloatingPane(nil, component.FloatingPaneOptions{
180 WidthPct: 0.82,
181 HeightPct: 0.72,
182 FallbackWidth: 1200,
183 FallbackHeight: 800,
184 OnNavigate: func(context.Context, string) error {
185 close(navigationStarted)
186 <-releaseNavigation
187 return expectedErr
188 },
189 })
190
191 errCh := make(chan error, 1)
192 go func() {
193 errCh <- fp.ShowToggle(context.Background())
194 }()
195
196 <-navigationStarted
197 fp.Show()
198 close(releaseNavigation)
199
200 err := <-errCh
201 require.ErrorIs(t, err, expectedErr)
202 assert.True(t, fp.IsVisible())
203 assert.False(t, fp.IsOmniboxVisible())
204}