detached 84bb9d4

test(panes): satisfy eject lint checks

commit
84bb9d408e6f6b33591da3481d4d9c180e9a4578
author
brice · · 2026-06-18 11:17 UTC
parent
54d60a9
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
2 files · +74 -51
M internal/application/usecase/extract_pane_to_tab_list_test.go +6 -6
@@ -15,7 +15,7 @@ func TestExtractPaneToTabList_FromThreePaneStack(t *testing.T) {
1515 paneA := entity.NewPane(entity.PaneID("pA"))
1616 paneB := entity.NewPane(entity.PaneID("pB"))
1717 paneC := entity.NewPane(entity.PaneID("pC"))
18- sourceTab := newStackedTab("tA", "wA", paneA, paneB, paneC)
18+ sourceTab := newStackedTab(paneA, paneB, paneC)
1919 sourceTab.Workspace.ActivePaneID = paneB.ID
2020 sourceTabs.Add(sourceTab)
2121
@@ -53,7 +53,7 @@ func TestExtractPaneToTabList_FromThreePaneStackUsesWorkspaceActivePaneWhenStack
5353 paneA := entity.NewPane(entity.PaneID("pA"))
5454 paneB := entity.NewPane(entity.PaneID("pB"))
5555 paneC := entity.NewPane(entity.PaneID("pC"))
56- sourceTab := newStackedTab("tA", "wA", paneA, paneB, paneC)
56+ sourceTab := newStackedTab(paneA, paneB, paneC)
5757 // ActivePaneID is the workspace source of truth, but stacked UI state can lag.
5858 // Eject should remove pB deterministically and activate the pane that takes its slot.
5959 sourceTab.Workspace.ActivePaneID = paneB.ID
@@ -85,7 +85,7 @@ func TestExtractPaneToTabList_FromTwoPaneStackDissolvesSource(t *testing.T) {
8585
8686 paneA := entity.NewPane(entity.PaneID("pA"))
8787 paneB := entity.NewPane(entity.PaneID("pB"))
88- sourceTab := newStackedTab("tA", "wA", paneA, paneB)
88+ sourceTab := newStackedTab(paneA, paneB)
8989 sourceTab.Workspace.ActivePaneID = paneB.ID
9090 sourceTabs.Add(sourceTab)
9191
@@ -333,7 +333,7 @@ func TestExtractPaneToTabList_ParentPointerTreeIntegrity(t *testing.T) {
333333 paneA := entity.NewPane(entity.PaneID("pA"))
334334 paneB := entity.NewPane(entity.PaneID("pB"))
335335 paneC := entity.NewPane(entity.PaneID("pC"))
336- sourceTab := newStackedTab("tA", "wA", paneA, paneB, paneC)
336+ sourceTab := newStackedTab(paneA, paneB, paneC)
337337 sourceTabs.Add(sourceTab)
338338
339339 out, err := uc.Execute(ExtractPaneToTabListInput{
@@ -349,8 +349,8 @@ func TestExtractPaneToTabList_ParentPointerTreeIntegrity(t *testing.T) {
349349 require.Nil(t, out.MovedPaneNode.Parent)
350350 }
351351
352-func newStackedTab(tabID entity.TabID, workspaceID entity.WorkspaceID, panes ...*entity.Pane) *entity.Tab {
353- tab := entity.NewTab(tabID, workspaceID, panes[0])
352+func newStackedTab(panes ...*entity.Pane) *entity.Tab {
353+ tab := entity.NewTab("tA", "wA", panes[0])
354354 stack := &entity.PaneNode{ID: "stack", IsStacked: true, ActiveStackIndex: 0}
355355 for _, pane := range panes {
356356 child := &entity.PaneNode{ID: string(pane.ID), Pane: pane, Parent: stack}
@@ -15,7 +15,7 @@ func TestExtractPaneToTabList_FromThreePaneStack(t *testing.T) {
15 paneA := entity.NewPane(entity.PaneID("pA"))15 paneA := entity.NewPane(entity.PaneID("pA"))
16 paneB := entity.NewPane(entity.PaneID("pB"))16 paneB := entity.NewPane(entity.PaneID("pB"))
17 paneC := entity.NewPane(entity.PaneID("pC"))17 paneC := entity.NewPane(entity.PaneID("pC"))
18- sourceTab := newStackedTab("tA", "wA", paneA, paneB, paneC)18+ sourceTab := newStackedTab(paneA, paneB, paneC)
19 sourceTab.Workspace.ActivePaneID = paneB.ID19 sourceTab.Workspace.ActivePaneID = paneB.ID
20 sourceTabs.Add(sourceTab)20 sourceTabs.Add(sourceTab)
21 21
@@ -53,7 +53,7 @@ func TestExtractPaneToTabList_FromThreePaneStackUsesWorkspaceActivePaneWhenStack
53 paneA := entity.NewPane(entity.PaneID("pA"))53 paneA := entity.NewPane(entity.PaneID("pA"))
54 paneB := entity.NewPane(entity.PaneID("pB"))54 paneB := entity.NewPane(entity.PaneID("pB"))
55 paneC := entity.NewPane(entity.PaneID("pC"))55 paneC := entity.NewPane(entity.PaneID("pC"))
56- sourceTab := newStackedTab("tA", "wA", paneA, paneB, paneC)56+ sourceTab := newStackedTab(paneA, paneB, paneC)
57 // ActivePaneID is the workspace source of truth, but stacked UI state can lag.57 // ActivePaneID is the workspace source of truth, but stacked UI state can lag.
58 // Eject should remove pB deterministically and activate the pane that takes its slot.58 // Eject should remove pB deterministically and activate the pane that takes its slot.
59 sourceTab.Workspace.ActivePaneID = paneB.ID59 sourceTab.Workspace.ActivePaneID = paneB.ID
@@ -85,7 +85,7 @@ func TestExtractPaneToTabList_FromTwoPaneStackDissolvesSource(t *testing.T) {
85 85
86 paneA := entity.NewPane(entity.PaneID("pA"))86 paneA := entity.NewPane(entity.PaneID("pA"))
87 paneB := entity.NewPane(entity.PaneID("pB"))87 paneB := entity.NewPane(entity.PaneID("pB"))
88- sourceTab := newStackedTab("tA", "wA", paneA, paneB)88+ sourceTab := newStackedTab(paneA, paneB)
89 sourceTab.Workspace.ActivePaneID = paneB.ID89 sourceTab.Workspace.ActivePaneID = paneB.ID
90 sourceTabs.Add(sourceTab)90 sourceTabs.Add(sourceTab)
91 91
@@ -333,7 +333,7 @@ func TestExtractPaneToTabList_ParentPointerTreeIntegrity(t *testing.T) {
333 paneA := entity.NewPane(entity.PaneID("pA"))333 paneA := entity.NewPane(entity.PaneID("pA"))
334 paneB := entity.NewPane(entity.PaneID("pB"))334 paneB := entity.NewPane(entity.PaneID("pB"))
335 paneC := entity.NewPane(entity.PaneID("pC"))335 paneC := entity.NewPane(entity.PaneID("pC"))
336- sourceTab := newStackedTab("tA", "wA", paneA, paneB, paneC)336+ sourceTab := newStackedTab(paneA, paneB, paneC)
337 sourceTabs.Add(sourceTab)337 sourceTabs.Add(sourceTab)
338 338
339 out, err := uc.Execute(ExtractPaneToTabListInput{339 out, err := uc.Execute(ExtractPaneToTabListInput{
@@ -349,8 +349,8 @@ func TestExtractPaneToTabList_ParentPointerTreeIntegrity(t *testing.T) {
349 require.Nil(t, out.MovedPaneNode.Parent)349 require.Nil(t, out.MovedPaneNode.Parent)
350 }350 }
351 351
352-func newStackedTab(tabID entity.TabID, workspaceID entity.WorkspaceID, panes ...*entity.Pane) *entity.Tab {352+func newStackedTab(panes ...*entity.Pane) *entity.Tab {
353- tab := entity.NewTab(tabID, workspaceID, panes[0])353+ tab := entity.NewTab("tA", "wA", panes[0])
354 stack := &entity.PaneNode{ID: "stack", IsStacked: true, ActiveStackIndex: 0}354 stack := &entity.PaneNode{ID: "stack", IsStacked: true, ActiveStackIndex: 0}
355 for _, pane := range panes {355 for _, pane := range panes {
356 child := &entity.PaneNode{ID: string(pane.ID), Pane: pane, Parent: stack}356 child := &entity.PaneNode{ID: string(pane.ID), Pane: pane, Parent: stack}
M internal/ui/app_eject_pane_to_window_test.go +68 -45
@@ -139,51 +139,9 @@ func TestApp_EjectActivePaneToWindowHandlesThreePaneSplitTree(t *testing.T) {
139139 t.Fatalf("EjectActivePaneToWindow returned error: %v", err)
140140 }
141141
142- if got := len(app.browserWindows); got != 2 {
143- t.Fatalf("browserWindows length = %d, want source and target", got)
144- }
145- if app.browserWindows[sourceWindow.id] == nil {
146- t.Fatal("source window should remain registered")
147- }
148- if got := sourceTab.Workspace.PaneCount(); got != 2 {
149- t.Fatalf("source pane count = %d, want 2", got)
150- }
151- if sourceTab.Workspace.FindPane("pane-a") == nil || sourceTab.Workspace.FindPane("pane-b") == nil {
152- t.Fatal("source workspace missing remaining split panes")
153- }
154- if got := sourceTab.Workspace.FindPane("pane-c"); got != nil {
155- t.Fatal("source workspace still contains moved split pane")
156- }
157- if got := sourceTab.Workspace.ActivePaneID; got != entity.PaneID("pane-b") {
158- t.Fatalf("source active pane = %q, want pane-b", got)
159- }
160- if sourceTab.Workspace.Root == nil || sourceTab.Workspace.Root.ID != "left-split" || !sourceTab.Workspace.Root.IsSplit() {
161- t.Fatalf("source root = %#v, want promoted left split", sourceTab.Workspace.Root)
162- }
163- if got := sourceTab.Workspace.Root.Parent; got != nil {
164- t.Fatalf("source root parent = %#v, want nil", got)
165- }
166- if got := sourceTab.Workspace.Root.Left(); got == nil || got.Pane == nil || got.Pane.ID != entity.PaneID("pane-a") {
167- t.Fatalf("source left leaf = %#v, want pane-a", got)
168- }
169- if got := sourceTab.Workspace.Root.Right(); got == nil || got.Pane == nil || got.Pane.ID != entity.PaneID("pane-b") {
170- t.Fatalf("source right leaf = %#v, want pane-b", got)
171- }
172- assertPaneTreeParents(t, sourceTab.Workspace.Root, nil)
173- targetWindow := app.browserWindows["target-window"]
174- if targetWindow == nil || targetWindow.tabs.Count() != 1 {
175- t.Fatalf("target window/tabs not created correctly: %#v", targetWindow)
176- }
177- newTab := targetWindow.tabs.Tabs[0]
178- if got := newTab.Workspace.ActivePaneID; got != entity.PaneID("pane-c") {
179- t.Fatalf("target active pane = %q, want pane-c", got)
180- }
181- if got := newTab.Workspace.Root.Parent; got != nil {
182- t.Fatalf("target root parent = %#v, want nil", got)
183- }
184- if got := app.windowForTab[newTab.ID]; got != targetWindow {
185- t.Fatalf("target owner = %p, want target window %p", got, targetWindow)
186- }
142+ assertEjectWindowCount(t, app, sourceWindow)
143+ assertEjectedThreePaneSplitSource(t, sourceTab)
144+ assertEjectedThreePaneSplitTarget(t, app, "pane-c")
187145 }
188146
189147 func TestApp_EjectActivePaneToWindowSnapshotReplacesEmptySourceWindow(t *testing.T) {
@@ -342,6 +300,71 @@ func stackedTab(tabID entity.TabID, workspaceID entity.WorkspaceID, paneIDs ...e
342300 return &entity.Tab{ID: tabID, Workspace: workspace}
343301 }
344302
303+func assertEjectWindowCount(t *testing.T, app *App, sourceWindow *browserWindow) {
304+ t.Helper()
305+ if got := len(app.browserWindows); got != 2 {
306+ t.Fatalf("browserWindows length = %d, want source and target", got)
307+ }
308+ if app.browserWindows[sourceWindow.id] == nil {
309+ t.Fatal("source window should remain registered")
310+ }
311+}
312+
313+func assertEjectedThreePaneSplitSource(t *testing.T, sourceTab *entity.Tab) {
314+ t.Helper()
315+ if got := sourceTab.Workspace.PaneCount(); got != 2 {
316+ t.Fatalf("source pane count = %d, want 2", got)
317+ }
318+ if sourceTab.Workspace.FindPane("pane-a") == nil || sourceTab.Workspace.FindPane("pane-b") == nil {
319+ t.Fatal("source workspace missing remaining split panes")
320+ }
321+ if got := sourceTab.Workspace.FindPane("pane-c"); got != nil {
322+ t.Fatal("source workspace still contains moved split pane")
323+ }
324+ if got := sourceTab.Workspace.ActivePaneID; got != entity.PaneID("pane-b") {
325+ t.Fatalf("source active pane = %q, want pane-b", got)
326+ }
327+ assertPromotedLeftSplitRoot(t, sourceTab.Workspace.Root)
328+}
329+
330+func assertPromotedLeftSplitRoot(t *testing.T, root *entity.PaneNode) {
331+ t.Helper()
332+ if root == nil || root.ID != "left-split" || !root.IsSplit() {
333+ t.Fatalf("source root = %#v, want promoted left split", root)
334+ }
335+ if got := root.Parent; got != nil {
336+ t.Fatalf("source root parent = %#v, want nil", got)
337+ }
338+ assertLeafPaneID(t, root.Left(), "pane-a")
339+ assertLeafPaneID(t, root.Right(), "pane-b")
340+ assertPaneTreeParents(t, root, nil)
341+}
342+
343+func assertEjectedThreePaneSplitTarget(t *testing.T, app *App, paneID entity.PaneID) {
344+ t.Helper()
345+ targetWindow := app.browserWindows["target-window"]
346+ if targetWindow == nil || targetWindow.tabs.Count() != 1 {
347+ t.Fatalf("target window/tabs not created correctly: %#v", targetWindow)
348+ }
349+ newTab := targetWindow.tabs.Tabs[0]
350+ if got := newTab.Workspace.ActivePaneID; got != paneID {
351+ t.Fatalf("target active pane = %q, want %q", got, paneID)
352+ }
353+ if got := newTab.Workspace.Root.Parent; got != nil {
354+ t.Fatalf("target root parent = %#v, want nil", got)
355+ }
356+ if got := app.windowForTab[newTab.ID]; got != targetWindow {
357+ t.Fatalf("target owner = %p, want target window %p", got, targetWindow)
358+ }
359+}
360+
361+func assertLeafPaneID(t *testing.T, node *entity.PaneNode, paneID entity.PaneID) {
362+ t.Helper()
363+ if node == nil || node.Pane == nil || node.Pane.ID != paneID {
364+ t.Fatalf("leaf = %#v, want %s", node, paneID)
365+ }
366+}
367+
345368 func splitTreeTab(tabID entity.TabID, workspaceID entity.WorkspaceID, paneAID, paneBID, paneCID entity.PaneID) *entity.Tab {
346369 paneA := entity.NewPane(paneAID)
347370 paneB := entity.NewPane(paneBID)
@@ -139,51 +139,9 @@ func TestApp_EjectActivePaneToWindowHandlesThreePaneSplitTree(t *testing.T) {
139 t.Fatalf("EjectActivePaneToWindow returned error: %v", err)139 t.Fatalf("EjectActivePaneToWindow returned error: %v", err)
140 }140 }
141 141
142- if got := len(app.browserWindows); got != 2 {142+ assertEjectWindowCount(t, app, sourceWindow)
143- t.Fatalf("browserWindows length = %d, want source and target", got)143+ assertEjectedThreePaneSplitSource(t, sourceTab)
144- }144+ assertEjectedThreePaneSplitTarget(t, app, "pane-c")
145- if app.browserWindows[sourceWindow.id] == nil {
146- t.Fatal("source window should remain registered")
147- }
148- if got := sourceTab.Workspace.PaneCount(); got != 2 {
149- t.Fatalf("source pane count = %d, want 2", got)
150- }
151- if sourceTab.Workspace.FindPane("pane-a") == nil || sourceTab.Workspace.FindPane("pane-b") == nil {
152- t.Fatal("source workspace missing remaining split panes")
153- }
154- if got := sourceTab.Workspace.FindPane("pane-c"); got != nil {
155- t.Fatal("source workspace still contains moved split pane")
156- }
157- if got := sourceTab.Workspace.ActivePaneID; got != entity.PaneID("pane-b") {
158- t.Fatalf("source active pane = %q, want pane-b", got)
159- }
160- if sourceTab.Workspace.Root == nil || sourceTab.Workspace.Root.ID != "left-split" || !sourceTab.Workspace.Root.IsSplit() {
161- t.Fatalf("source root = %#v, want promoted left split", sourceTab.Workspace.Root)
162- }
163- if got := sourceTab.Workspace.Root.Parent; got != nil {
164- t.Fatalf("source root parent = %#v, want nil", got)
165- }
166- if got := sourceTab.Workspace.Root.Left(); got == nil || got.Pane == nil || got.Pane.ID != entity.PaneID("pane-a") {
167- t.Fatalf("source left leaf = %#v, want pane-a", got)
168- }
169- if got := sourceTab.Workspace.Root.Right(); got == nil || got.Pane == nil || got.Pane.ID != entity.PaneID("pane-b") {
170- t.Fatalf("source right leaf = %#v, want pane-b", got)
171- }
172- assertPaneTreeParents(t, sourceTab.Workspace.Root, nil)
173- targetWindow := app.browserWindows["target-window"]
174- if targetWindow == nil || targetWindow.tabs.Count() != 1 {
175- t.Fatalf("target window/tabs not created correctly: %#v", targetWindow)
176- }
177- newTab := targetWindow.tabs.Tabs[0]
178- if got := newTab.Workspace.ActivePaneID; got != entity.PaneID("pane-c") {
179- t.Fatalf("target active pane = %q, want pane-c", got)
180- }
181- if got := newTab.Workspace.Root.Parent; got != nil {
182- t.Fatalf("target root parent = %#v, want nil", got)
183- }
184- if got := app.windowForTab[newTab.ID]; got != targetWindow {
185- t.Fatalf("target owner = %p, want target window %p", got, targetWindow)
186- }
187 }145 }
188 146
189 func TestApp_EjectActivePaneToWindowSnapshotReplacesEmptySourceWindow(t *testing.T) {147 func TestApp_EjectActivePaneToWindowSnapshotReplacesEmptySourceWindow(t *testing.T) {
@@ -342,6 +300,71 @@ func stackedTab(tabID entity.TabID, workspaceID entity.WorkspaceID, paneIDs ...e
342 return &entity.Tab{ID: tabID, Workspace: workspace}300 return &entity.Tab{ID: tabID, Workspace: workspace}
343 }301 }
344 302
303+func assertEjectWindowCount(t *testing.T, app *App, sourceWindow *browserWindow) {
304+ t.Helper()
305+ if got := len(app.browserWindows); got != 2 {
306+ t.Fatalf("browserWindows length = %d, want source and target", got)
307+ }
308+ if app.browserWindows[sourceWindow.id] == nil {
309+ t.Fatal("source window should remain registered")
310+ }
311+}
312+
313+func assertEjectedThreePaneSplitSource(t *testing.T, sourceTab *entity.Tab) {
314+ t.Helper()
315+ if got := sourceTab.Workspace.PaneCount(); got != 2 {
316+ t.Fatalf("source pane count = %d, want 2", got)
317+ }
318+ if sourceTab.Workspace.FindPane("pane-a") == nil || sourceTab.Workspace.FindPane("pane-b") == nil {
319+ t.Fatal("source workspace missing remaining split panes")
320+ }
321+ if got := sourceTab.Workspace.FindPane("pane-c"); got != nil {
322+ t.Fatal("source workspace still contains moved split pane")
323+ }
324+ if got := sourceTab.Workspace.ActivePaneID; got != entity.PaneID("pane-b") {
325+ t.Fatalf("source active pane = %q, want pane-b", got)
326+ }
327+ assertPromotedLeftSplitRoot(t, sourceTab.Workspace.Root)
328+}
329+
330+func assertPromotedLeftSplitRoot(t *testing.T, root *entity.PaneNode) {
331+ t.Helper()
332+ if root == nil || root.ID != "left-split" || !root.IsSplit() {
333+ t.Fatalf("source root = %#v, want promoted left split", root)
334+ }
335+ if got := root.Parent; got != nil {
336+ t.Fatalf("source root parent = %#v, want nil", got)
337+ }
338+ assertLeafPaneID(t, root.Left(), "pane-a")
339+ assertLeafPaneID(t, root.Right(), "pane-b")
340+ assertPaneTreeParents(t, root, nil)
341+}
342+
343+func assertEjectedThreePaneSplitTarget(t *testing.T, app *App, paneID entity.PaneID) {
344+ t.Helper()
345+ targetWindow := app.browserWindows["target-window"]
346+ if targetWindow == nil || targetWindow.tabs.Count() != 1 {
347+ t.Fatalf("target window/tabs not created correctly: %#v", targetWindow)
348+ }
349+ newTab := targetWindow.tabs.Tabs[0]
350+ if got := newTab.Workspace.ActivePaneID; got != paneID {
351+ t.Fatalf("target active pane = %q, want %q", got, paneID)
352+ }
353+ if got := newTab.Workspace.Root.Parent; got != nil {
354+ t.Fatalf("target root parent = %#v, want nil", got)
355+ }
356+ if got := app.windowForTab[newTab.ID]; got != targetWindow {
357+ t.Fatalf("target owner = %p, want target window %p", got, targetWindow)
358+ }
359+}
360+
361+func assertLeafPaneID(t *testing.T, node *entity.PaneNode, paneID entity.PaneID) {
362+ t.Helper()
363+ if node == nil || node.Pane == nil || node.Pane.ID != paneID {
364+ t.Fatalf("leaf = %#v, want %s", node, paneID)
365+ }
366+}
367+
345 func splitTreeTab(tabID entity.TabID, workspaceID entity.WorkspaceID, paneAID, paneBID, paneCID entity.PaneID) *entity.Tab {368 func splitTreeTab(tabID entity.TabID, workspaceID entity.WorkspaceID, paneAID, paneBID, paneCID entity.PaneID) *entity.Tab {
346 paneA := entity.NewPane(paneAID)369 paneA := entity.NewPane(paneAID)
347 paneB := entity.NewPane(paneBID)370 paneB := entity.NewPane(paneBID)

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