v0.9.0 636c44c

workspace_manager_recursion_test.go RawHistoryFolder

1package browser
2
3import (
4	"testing"
5	"time"
6
7	"github.com/bnema/dumber/pkg/webkit"
8)
9
10func TestInfiniteRecursionProtection(t *testing.T) {
11	t.Run("BoundaryFallback with deep tree", func(t *testing.T) {
12		wm := newTestWorkspaceManagerWithMocksForTree(t)
13
14		// Create a pathologically deep tree
15		current := wm.root
16		for i := 0; i < 20; i++ {
17			newNode, err := wm.splitNode(current, "right")
18			if err != nil {
19				t.Fatalf("Split %d failed: %v", i, err)
20			}
21			current = newNode
22		}
23
24		// Test that boundaryFallback doesn't hang even with deep trees
25		done := make(chan bool, 1)
26		go func() {
27			// This should complete quickly without infinite recursion
28			result := wm.boundaryFallback(wm.root, "right")
29			_ = result // We just care that it returns
30			done <- true
31		}()
32
33		select {
34		case <-done:
35			// Good - function completed
36		case <-time.After(1 * time.Second):
37			t.Fatal("boundaryFallback took too long - likely infinite recursion")
38		}
39	})
40
41	t.Run("CollectLeaves with complex tree", func(t *testing.T) {
42		wm := newTestWorkspaceManagerWithMocksForTree(t)
43
44		// Build a complex tree that could potentially cause issues
45		leaves := []*paneNode{wm.root}
46		for i := 0; i < 15; i++ {
47			if len(leaves) > 0 {
48				target := leaves[i%len(leaves)]
49				newNode, err := wm.splitNode(target, "right")
50				if err != nil {
51					continue
52				}
53				leaves = append(leaves, newNode)
54			}
55		}
56
57		// Test that collectLeaves doesn't hang
58		done := make(chan []*paneNode, 1)
59		go func() {
60			result := wm.collectLeaves()
61			done <- result
62		}()
63
64		select {
65		case result := <-done:
66			// Verify we got a reasonable result
67			if len(result) == 0 {
68				t.Error("Expected some leaves to be collected")
69			}
70			t.Logf("Collected %d leaves from complex tree", len(result))
71		case <-time.After(1 * time.Second):
72			t.Fatal("collectLeaves took too long - likely infinite recursion")
73		}
74	})
75
76	t.Run("Focus calculation performance", func(t *testing.T) {
77		wm := newTestWorkspaceManagerWithMocksForTree(t)
78
79		// Create a tree with geometry
80		right, err := wm.splitNode(wm.root, "right")
81		if err != nil {
82			t.Fatalf("Failed to split right: %v", err)
83		}
84		bottom, err := wm.splitNode(right, "down")
85		if err != nil {
86			t.Fatalf("Failed to split down: %v", err)
87		}
88
89		// Set widget bounds
90		webkit.SetWidgetBoundsForTesting(wm.root.container, webkit.WidgetBounds{X: 0, Y: 0, Width: 100, Height: 100})
91		if right != nil {
92			webkit.SetWidgetBoundsForTesting(right.container, webkit.WidgetBounds{X: 120, Y: 0, Width: 100, Height: 100})
93		}
94		if bottom != nil {
95			webkit.SetWidgetBoundsForTesting(bottom.container, webkit.WidgetBounds{X: 0, Y: 120, Width: 100, Height: 100})
96		}
97
98		// Test that focus neighbor calculations complete quickly
99		start := time.Now()
100
101		// Perform multiple focus operations
102		for i := 0; i < 10; i++ {
103			wm.FocusNeighbor("right")
104			wm.FocusNeighbor("left")
105			wm.FocusNeighbor("down")
106			wm.FocusNeighbor("up")
107		}
108
109		elapsed := time.Since(start)
110		if elapsed > 100*time.Millisecond {
111			t.Errorf("Focus operations took too long: %v", elapsed)
112		}
113		t.Logf("40 focus operations completed in %v", elapsed)
114	})
115
116	t.Run("StructuralNeighbor depth protection", func(t *testing.T) {
117		wm := newTestWorkspaceManagerWithMocksForTree(t)
118
119		// Create nested structure
120		node1, _ := wm.splitNode(wm.root, "right")
121		node2, _ := wm.splitNode(node1, "down")
122		node3, _ := wm.splitNode(node2, "right")
123
124		// Set some bounds for the calculation
125		webkit.SetWidgetBoundsForTesting(node3.container, webkit.WidgetBounds{X: 0, Y: 0, Width: 100, Height: 100})
126
127		// Test structural neighbor calculation doesn't hang
128		done := make(chan *paneNode, 1)
129		go func() {
130			result := wm.structuralNeighbor(node3, "left")
131			done <- result
132		}()
133
134		select {
135		case result := <-done:
136			t.Logf("Structural neighbor calculation completed, result: %v", result != nil)
137		case <-time.After(500 * time.Millisecond):
138			t.Fatal("structuralNeighbor took too long")
139		}
140	})
141}
142
143func TestRecursionLimits(t *testing.T) {
144	t.Run("MaxDepth protection", func(t *testing.T) {
145		wm := newTestWorkspaceManagerWithMocksForTree(t)
146
147		// Create an artificially corrupted node for testing
148		// (This would never happen in normal operation)
149		corruptedNode := &paneNode{
150			isLeaf: false,
151			left:   nil, // This creates a problematic state
152			right:  nil, // No children but not a leaf
153		}
154
155		// Test that boundaryFallback handles this gracefully
156		result := wm.boundaryFallback(corruptedNode, "right")
157		if result != nil {
158			t.Error("Expected nil result for corrupted node")
159		}
160
161		// The function should return quickly, not hang
162		start := time.Now()
163		for i := 0; i < 10; i++ {
164			wm.boundaryFallback(corruptedNode, "left")
165		}
166		elapsed := time.Since(start)
167
168		if elapsed > 10*time.Millisecond {
169			t.Errorf("Corrupted node handling took too long: %v", elapsed)
170		}
171	})
172}

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