v0.9.0 636c44c

workspace_manager_test.go RawHistoryFolder

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}

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