1package browser
2
3import (
4 "testing"
5
6 "github.com/bnema/dumber/pkg/webkit"
7)
8
9func newTestWorkspaceManager(t *testing.T) *WorkspaceManager {
10 t.Helper()
11 webkit.ResetWidgetStubsForTesting()
12
13 rootView, err := webkit.NewWebView(&webkit.Config{})
14 if err != nil {
15 t.Fatalf("failed to create root webview: %v", err)
16 }
17
18 rootPane := &BrowserPane{webView: rootView}
19
20 // Create mock window shortcut handler
21 mockWindowShortcutHandler := &mockWindowShortcutHandler{}
22
23 app := &BrowserApp{}
24 app.panes = []*BrowserPane{rootPane}
25 app.activePane = rootPane
26 app.webView = rootView
27 app.windowShortcutHandler = mockWindowShortcutHandler
28
29 wm := NewWorkspaceManager(app, rootPane)
30 wm.createWebViewFn = func() (*webkit.WebView, error) {
31 return webkit.NewWebView(&webkit.Config{})
32 }
33 wm.createPaneFn = func(view *webkit.WebView) (*BrowserPane, error) {
34 return &BrowserPane{webView: view}, nil
35 }
36
37 return wm
38}
39
40func TestSplitNodeCreatesExpectedTree(t *testing.T) {
41 cases := []struct {
42 name string
43 direction string
44 expectOrientation webkit.Orientation
45 existingIsStart bool
46 }{
47 {name: "Right", direction: "right", expectOrientation: webkit.OrientationHorizontal, existingIsStart: true},
48 {name: "Left", direction: "left", expectOrientation: webkit.OrientationHorizontal, existingIsStart: false},
49 {name: "Up", direction: "up", expectOrientation: webkit.OrientationVertical, existingIsStart: false},
50 {name: "Down", direction: "down", expectOrientation: webkit.OrientationVertical, existingIsStart: true},
51 }
52
53 for _, tc := range cases {
54 t.Run(tc.name, func(t *testing.T) {
55 wm := newTestWorkspaceManager(t)
56 original := wm.active
57
58 newLeaf, err := wm.splitNode(original, tc.direction)
59 if err != nil {
60 t.Fatalf("splitNode(%s) returned error: %v", tc.direction, err)
61 }
62
63 parent := newLeaf.parent
64 if parent == nil {
65 t.Fatalf("expected new leaf to have parent")
66 }
67
68 if parent.orientation != tc.expectOrientation {
69 t.Fatalf("expected orientation %v got %v", tc.expectOrientation, parent.orientation)
70 }
71
72 if tc.existingIsStart {
73 if parent.left != original {
74 t.Fatalf("expected original pane to remain as start child")
75 }
76 if parent.right != newLeaf {
77 t.Fatalf("expected new pane to be end child")
78 }
79 } else {
80 if parent.left != newLeaf {
81 t.Fatalf("expected new pane to be start child")
82 }
83 if parent.right != original {
84 t.Fatalf("expected original pane to be end child")
85 }
86 }
87
88 if wm.root != parent {
89 t.Fatalf("expected parent to become new root")
90 }
91
92 if len(wm.app.panes) != 2 {
93 t.Fatalf("expected 2 panes registered, got %d", len(wm.app.panes))
94 }
95
96 mapped := wm.viewToNode[newLeaf.pane.webView]
97 if mapped != newLeaf {
98 t.Fatalf("expected new webview to map to new leaf")
99 }
100 })
101 }
102}
103
104func TestClosePanePromotesSibling(t *testing.T) {
105 wm := newTestWorkspaceManager(t)
106 original := wm.active
107
108 newLeaf, err := wm.splitNode(original, "right")
109 if err != nil {
110 t.Fatalf("split failed: %v", err)
111 }
112
113 if err := wm.closePane(newLeaf); err != nil {
114 t.Fatalf("closePane failed: %v", err)
115 }
116
117 if wm.root != original {
118 t.Fatalf("expected original leaf to become root after closing sibling")
119 }
120
121 if original.parent != nil {
122 t.Fatalf("expected original leaf to have no parent after promotion")
123 }
124
125 if len(wm.app.panes) != 1 {
126 t.Fatalf("expected single pane remaining, got %d", len(wm.app.panes))
127 }
128
129 if wm.app.panes[0] != original.pane {
130 t.Fatalf("expected remaining pane to match original")
131 }
132
133 if wm.active != original {
134 t.Fatalf("expected focus to move to promoted pane")
135 }
136}
137
138func TestFocusNeighborWithBounds(t *testing.T) {
139 wm := newTestWorkspaceManager(t)
140 left := wm.active
141
142 right, err := wm.splitNode(left, "right")
143 if err != nil {
144 t.Fatalf("split failed: %v", err)
145 }
146
147 if right == nil {
148 t.Fatalf("expected new right leaf")
149 }
150
151 // Provide geometry hints for focus calculations.
152 webkit.SetWidgetBoundsForTesting(left.container, webkit.WidgetBounds{X: 0, Y: 0, Width: 100, Height: 100})
153 webkit.SetWidgetBoundsForTesting(right.container, webkit.WidgetBounds{X: 120, Y: 0, Width: 100, Height: 100})
154
155 if !wm.FocusNeighbor("left") {
156 t.Fatalf("expected focus neighbor left to succeed")
157 }
158
159 if wm.active != left {
160 t.Fatalf("expected focus to move to left pane")
161 }
162
163 // Vertical split from the left pane to test up/down logic.
164 bottom, err := wm.splitNode(left, "down")
165 if err != nil {
166 t.Fatalf("vertical split failed: %v", err)
167 }
168
169 top := bottom.parent.left
170
171 webkit.SetWidgetBoundsForTesting(top.container, webkit.WidgetBounds{X: 0, Y: 0, Width: 100, Height: 90})
172 webkit.SetWidgetBoundsForTesting(bottom.container, webkit.WidgetBounds{X: 0, Y: 120, Width: 100, Height: 90})
173
174 // Active pane should be bottom after split. Move focus up.
175 if wm.active != bottom {
176 t.Fatalf("expected bottom pane to be active after split")
177 }
178
179 if !wm.FocusNeighbor("up") {
180 t.Fatalf("expected focus neighbor up to succeed")
181 }
182
183 if wm.active != top {
184 t.Fatalf("expected focus to move to top pane")
185 }
186}
187
188func TestFocusNeighborWithoutPeer(t *testing.T) {
189 wm := newTestWorkspaceManager(t)
190 if wm.FocusNeighbor("left") {
191 t.Fatalf("expected focus move to fail when only one pane exists")
192 }
193}