1package browser
2
3import (
4 "testing"
5
6 "github.com/bnema/dumber/internal/config"
7 "github.com/bnema/dumber/pkg/webkit"
8)
9
10// TestPendingPopupStorage tests the pendingPopups map operations
11func TestPendingPopupStorage(t *testing.T) {
12 wm := &WorkspaceManager{
13 pendingPopups: make(map[uint64]*pendingPopup),
14 }
15
16 // Test adding a pending popup
17 popupID := uint64(1)
18 pending := &pendingPopup{
19 url: "https://example.com",
20 }
21
22 wm.pendingPopups[popupID] = pending
23
24 // Verify storage
25 retrieved, ok := wm.pendingPopups[popupID]
26 if !ok {
27 t.Errorf("Expected popup ID %d to be stored", popupID)
28 }
29
30 if retrieved.url != "https://example.com" {
31 t.Errorf("Expected URL 'https://example.com', got %s", retrieved.url)
32 }
33
34 // Test deletion
35 delete(wm.pendingPopups, popupID)
36 _, ok = wm.pendingPopups[popupID]
37 if ok {
38 t.Errorf("Expected popup ID %d to be deleted", popupID)
39 }
40}
41
42// TestPendingPopupCleanup tests cleanup of pending popups that never showed
43func TestPendingPopupCleanup(t *testing.T) {
44 wm := &WorkspaceManager{
45 pendingPopups: make(map[uint64]*pendingPopup),
46 }
47
48 // Add multiple pending popups
49 for i := uint64(1); i <= 5; i++ {
50 wm.pendingPopups[i] = &pendingPopup{
51 url: "https://example.com",
52 }
53 }
54
55 // Verify all added
56 if len(wm.pendingPopups) != 5 {
57 t.Errorf("Expected 5 pending popups, got %d", len(wm.pendingPopups))
58 }
59
60 // Simulate close before ready-to-show for popup 3
61 popupID := uint64(3)
62 if _, ok := wm.pendingPopups[popupID]; ok {
63 delete(wm.pendingPopups, popupID)
64 }
65
66 // Verify cleanup
67 if len(wm.pendingPopups) != 4 {
68 t.Errorf("Expected 4 pending popups after cleanup, got %d", len(wm.pendingPopups))
69 }
70
71 _, ok := wm.pendingPopups[popupID]
72 if ok {
73 t.Errorf("Expected popup ID %d to be removed", popupID)
74 }
75}
76
77// TestPopupIDTracking tests that popup IDs are tracked correctly in paneNode
78func TestPopupIDTracking(t *testing.T) {
79 node := &paneNode{
80 isLeaf: true,
81 windowType: webkit.WindowTypePopup,
82 isPopup: true,
83 popupID: 42,
84 }
85
86 // Verify popup ID is stored
87 if node.popupID != 42 {
88 t.Errorf("Expected popupID 42, got %d", node.popupID)
89 }
90
91 // Verify window type
92 if node.windowType != webkit.WindowTypePopup {
93 t.Errorf("Expected WindowTypePopup, got %d", node.windowType)
94 }
95
96 // Verify popup flag
97 if !node.isPopup {
98 t.Errorf("Expected isPopup to be true")
99 }
100}
101
102// TestActivePopupChildren tests tracking of active popup children in parent pane
103func TestActivePopupChildren(t *testing.T) {
104 parentNode := &paneNode{
105 isLeaf: true,
106 activePopupChildren: make([]string, 0),
107 }
108
109 // Add popup children
110 popupID1 := "1"
111 popupID2 := "2"
112 popupID3 := "3"
113
114 parentNode.activePopupChildren = append(parentNode.activePopupChildren, popupID1)
115 parentNode.activePopupChildren = append(parentNode.activePopupChildren, popupID2)
116 parentNode.activePopupChildren = append(parentNode.activePopupChildren, popupID3)
117
118 // Verify count
119 if len(parentNode.activePopupChildren) != 3 {
120 t.Errorf("Expected 3 active popup children, got %d", len(parentNode.activePopupChildren))
121 }
122
123 // Test removal of middle child
124 for i, childID := range parentNode.activePopupChildren {
125 if childID == popupID2 {
126 parentNode.activePopupChildren = append(
127 parentNode.activePopupChildren[:i],
128 parentNode.activePopupChildren[i+1:]...,
129 )
130 break
131 }
132 }
133
134 // Verify removal
135 if len(parentNode.activePopupChildren) != 2 {
136 t.Errorf("Expected 2 active popup children after removal, got %d", len(parentNode.activePopupChildren))
137 }
138
139 // Verify correct children remain
140 if parentNode.activePopupChildren[0] != popupID1 || parentNode.activePopupChildren[1] != popupID3 {
141 t.Errorf("Expected children [%s, %s], got %v", popupID1, popupID3, parentNode.activePopupChildren)
142 }
143}
144
145// TestShouldAutoClose tests OAuth URL detection logic
146func TestShouldAutoClose(t *testing.T) {
147 tests := []struct {
148 name string
149 url string
150 enabled bool
151 expected bool
152 }{
153 {
154 name: "OAuth authorize URL",
155 url: "https://accounts.google.com/o/oauth2/v2/auth?client_id=...",
156 enabled: true,
157 expected: true,
158 },
159 {
160 name: "OAuth callback URL",
161 url: "https://example.com/auth/callback?code=xyz",
162 enabled: true,
163 expected: true,
164 },
165 {
166 name: "OAuth token response",
167 url: "https://example.com/redirect?access_token=xyz",
168 enabled: true,
169 expected: true,
170 },
171 {
172 name: "Regular URL - auto-close enabled",
173 url: "https://example.com/page",
174 enabled: true,
175 expected: false,
176 },
177 {
178 name: "OAuth URL - auto-close disabled",
179 url: "https://accounts.google.com/o/oauth2/v2/auth?client_id=...",
180 enabled: false,
181 expected: false,
182 },
183 {
184 name: "OpenID Connect URL",
185 url: "https://example.com/oidc/authorize",
186 enabled: true,
187 expected: true,
188 },
189 {
190 name: "Error response",
191 url: "https://example.com/callback?error=access_denied",
192 enabled: true,
193 expected: true,
194 },
195 }
196
197 for _, tt := range tests {
198 t.Run(tt.name, func(t *testing.T) {
199 wm := &WorkspaceManager{
200 app: &BrowserApp{
201 config: &config.Config{
202 Workspace: config.WorkspaceConfig{
203 Popups: config.PopupBehaviorConfig{
204 OAuthAutoClose: tt.enabled,
205 },
206 },
207 },
208 },
209 }
210
211 result := wm.shouldAutoClose(tt.url)
212 if result != tt.expected {
213 t.Errorf("shouldAutoClose(%q) with enabled=%v = %v, want %v",
214 tt.url, tt.enabled, result, tt.expected)
215 }
216 })
217 }
218}
219
220// TestPopupBehaviorConfig tests popup behavior configuration
221func TestPopupBehaviorConfig(t *testing.T) {
222 tests := []struct {
223 name string
224 behavior config.PopupBehavior
225 }{
226 {"Split behavior", config.PopupBehaviorSplit},
227 {"Stacked behavior", config.PopupBehaviorStacked},
228 {"Tabbed behavior", config.PopupBehaviorTabbed},
229 {"Windowed behavior", config.PopupBehaviorWindowed},
230 }
231
232 for _, tt := range tests {
233 t.Run(tt.name, func(t *testing.T) {
234 cfg := &config.Config{
235 Workspace: config.WorkspaceConfig{
236 Popups: config.PopupBehaviorConfig{
237 Behavior: tt.behavior,
238 },
239 },
240 }
241
242 // Verify behavior is stored correctly
243 if cfg.Workspace.Popups.Behavior != tt.behavior {
244 t.Errorf("Expected behavior %s, got %s",
245 tt.behavior, cfg.Workspace.Popups.Behavior)
246 }
247 })
248 }
249}
250
251// TestPopupWindowTypeTracking tests window type tracking in paneNode
252func TestPopupWindowTypeTracking(t *testing.T) {
253 tests := []struct {
254 name string
255 windowType webkit.WindowType
256 isPopup bool
257 isRelated bool
258 }{
259 {
260 name: "Regular tab",
261 windowType: webkit.WindowTypeTab,
262 isPopup: false,
263 isRelated: false,
264 },
265 {
266 name: "Popup window",
267 windowType: webkit.WindowTypePopup,
268 isPopup: true,
269 isRelated: true,
270 },
271 }
272
273 for _, tt := range tests {
274 t.Run(tt.name, func(t *testing.T) {
275 node := &paneNode{
276 windowType: tt.windowType,
277 isPopup: tt.isPopup,
278 isRelated: tt.isRelated,
279 }
280
281 if node.windowType != tt.windowType {
282 t.Errorf("Expected windowType %d, got %d", tt.windowType, node.windowType)
283 }
284
285 if node.isPopup != tt.isPopup {
286 t.Errorf("Expected isPopup %v, got %v", tt.isPopup, node.isPopup)
287 }
288
289 if node.isRelated != tt.isRelated {
290 t.Errorf("Expected isRelated %v, got %v", tt.isRelated, node.isRelated)
291 }
292 })
293 }
294}
295
296// TestPopupParentRelationship tests parent-child relationship tracking
297func TestPopupParentRelationship(t *testing.T) {
298 parentNode := &paneNode{
299 isLeaf: true,
300 activePopupChildren: make([]string, 0),
301 }
302
303 popupNode := &paneNode{
304 isLeaf: true,
305 windowType: webkit.WindowTypePopup,
306 isPopup: true,
307 parentPane: parentNode,
308 isRelated: true,
309 }
310
311 // Verify parent relationship
312 if popupNode.parentPane != parentNode {
313 t.Errorf("Expected popup's parent to be parentNode")
314 }
315
316 // Verify popup is marked as related
317 if !popupNode.isRelated {
318 t.Errorf("Expected popup to be marked as related")
319 }
320
321 // Add popup to parent's children
322 popupWebViewID := "test-popup-id"
323 parentNode.activePopupChildren = append(parentNode.activePopupChildren, popupWebViewID)
324
325 // Verify parent tracks popup
326 if len(parentNode.activePopupChildren) != 1 {
327 t.Errorf("Expected parent to track 1 popup child, got %d", len(parentNode.activePopupChildren))
328 }
329
330 if parentNode.activePopupChildren[0] != popupWebViewID {
331 t.Errorf("Expected parent to track popup ID %s, got %s",
332 popupWebViewID, parentNode.activePopupChildren[0])
333 }
334}
335
336// TestPopupCleanupGeneration tests cleanup generation tracking
337func TestPopupCleanupGeneration(t *testing.T) {
338 node := &paneNode{
339 isLeaf: true,
340 windowType: webkit.WindowTypePopup,
341 isPopup: true,
342 widgetValid: true,
343 cleanupGeneration: 0,
344 }
345
346 // Simulate lifecycle progression
347 node.cleanupGeneration = 1
348 if node.cleanupGeneration != 1 {
349 t.Errorf("Expected cleanupGeneration 1, got %d", node.cleanupGeneration)
350 }
351
352 // Simulate widget becoming invalid
353 node.widgetValid = false
354 if node.widgetValid {
355 t.Errorf("Expected widgetValid to be false")
356 }
357
358 // Increment generation for new lifecycle
359 node.cleanupGeneration = 2
360 if node.cleanupGeneration != 2 {
361 t.Errorf("Expected cleanupGeneration 2, got %d", node.cleanupGeneration)
362 }
363}
364
365// TestMultiplePendingPopups tests handling multiple pending popups simultaneously
366func TestMultiplePendingPopups(t *testing.T) {
367 wm := &WorkspaceManager{
368 pendingPopups: make(map[uint64]*pendingPopup),
369 }
370
371 // Add multiple pending popups
372 popups := map[uint64]string{
373 1: "https://popup1.com",
374 2: "https://popup2.com",
375 3: "https://popup3.com",
376 }
377
378 for id, url := range popups {
379 wm.pendingPopups[id] = &pendingPopup{
380 url: url,
381 }
382 }
383
384 // Verify all stored
385 if len(wm.pendingPopups) != 3 {
386 t.Errorf("Expected 3 pending popups, got %d", len(wm.pendingPopups))
387 }
388
389 // Verify each URL
390 for id, expectedURL := range popups {
391 pending, ok := wm.pendingPopups[id]
392 if !ok {
393 t.Errorf("Expected popup ID %d to be stored", id)
394 continue
395 }
396 if pending.url != expectedURL {
397 t.Errorf("Expected URL %s for popup %d, got %s", expectedURL, id, pending.url)
398 }
399 }
400
401 // Simulate ready-to-show for popup 2 (remove from pending)
402 delete(wm.pendingPopups, 2)
403
404 // Verify remaining
405 if len(wm.pendingPopups) != 2 {
406 t.Errorf("Expected 2 pending popups after ready-to-show, got %d", len(wm.pendingPopups))
407 }
408
409 // Verify popup 2 is gone but 1 and 3 remain
410 if _, ok := wm.pendingPopups[2]; ok {
411 t.Errorf("Expected popup 2 to be removed after ready-to-show")
412 }
413 if _, ok := wm.pendingPopups[1]; !ok {
414 t.Errorf("Expected popup 1 to still be pending")
415 }
416 if _, ok := wm.pendingPopups[3]; !ok {
417 t.Errorf("Expected popup 3 to still be pending")
418 }
419}