v0.12.0 d035193

workspace_popup_test.go RawHistoryFolder

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}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close