detached 9ebcbf7

test(daemon): cover pane resize control targeting

commit
9ebcbf7ac21daeefd0a495dde78bdd4e4ccf67e7
author
brice · · 2026-07-25 04:41 UTC
parent
cbd555e
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
3 files · +49 -2
M internal/usecase/command/registry_test.go +7 -2
@@ -117,8 +117,13 @@ func TestControlHandlersValidateAndDelegate(t *testing.T) {
117117 }{
118118 {name: "zero argument mutation delegates", slug: "split-right", wantCall: "split-right"},
119119 {name: "zero argument mutation rejects args", slug: "split-right", args: []string{"extra"}, wantErr: ErrInvalidArguments},
120- {name: "resize mutation delegates", slug: "grow-pane-width", wantCall: "grow-pane-width"},
121- {name: "equalize mutation delegates", slug: "equalize-panes", wantCall: "equalize-panes"},
120+ {name: "grow width delegates", slug: "grow-pane-width", wantCall: "grow-pane-width"},
121+ {name: "shrink width delegates", slug: "shrink-pane-width", wantCall: "shrink-pane-width"},
122+ {name: "grow height delegates", slug: "grow-pane-height", wantCall: "grow-pane-height"},
123+ {name: "shrink height delegates", slug: "shrink-pane-height", wantCall: "shrink-pane-height"},
124+ {name: "equalize delegates", slug: "equalize-panes", wantCall: "equalize-panes"},
125+ {name: "resize mutation rejects arguments", slug: "grow-pane-width", args: []string{"extra"}, wantErr: ErrInvalidArguments},
126+ {name: "equalize rejects arguments", slug: "equalize-panes", args: []string{"extra"}, wantErr: ErrInvalidArguments},
122127 {name: "rename delegates name", slug: "rename-tab", args: []string{"editor"}, wantCall: "rename-tab:editor"},
123128 {name: "rename rejects empty name", slug: "rename-tab", args: []string{""}, wantErr: ErrInvalidArguments},
124129 {name: "toast delegates severity", slug: "toast", args: []string{"-l", "warn", "hello"}, wantCall: "toast:warn:hello"},
@@ -117,8 +117,13 @@ func TestControlHandlersValidateAndDelegate(t *testing.T) {
117 }{117 }{
118 {name: "zero argument mutation delegates", slug: "split-right", wantCall: "split-right"},118 {name: "zero argument mutation delegates", slug: "split-right", wantCall: "split-right"},
119 {name: "zero argument mutation rejects args", slug: "split-right", args: []string{"extra"}, wantErr: ErrInvalidArguments},119 {name: "zero argument mutation rejects args", slug: "split-right", args: []string{"extra"}, wantErr: ErrInvalidArguments},
120- {name: "resize mutation delegates", slug: "grow-pane-width", wantCall: "grow-pane-width"},120+ {name: "grow width delegates", slug: "grow-pane-width", wantCall: "grow-pane-width"},
121- {name: "equalize mutation delegates", slug: "equalize-panes", wantCall: "equalize-panes"},121+ {name: "shrink width delegates", slug: "shrink-pane-width", wantCall: "shrink-pane-width"},
122+ {name: "grow height delegates", slug: "grow-pane-height", wantCall: "grow-pane-height"},
123+ {name: "shrink height delegates", slug: "shrink-pane-height", wantCall: "shrink-pane-height"},
124+ {name: "equalize delegates", slug: "equalize-panes", wantCall: "equalize-panes"},
125+ {name: "resize mutation rejects arguments", slug: "grow-pane-width", args: []string{"extra"}, wantErr: ErrInvalidArguments},
126+ {name: "equalize rejects arguments", slug: "equalize-panes", args: []string{"extra"}, wantErr: ErrInvalidArguments},
122 {name: "rename delegates name", slug: "rename-tab", args: []string{"editor"}, wantCall: "rename-tab:editor"},127 {name: "rename delegates name", slug: "rename-tab", args: []string{"editor"}, wantCall: "rename-tab:editor"},
123 {name: "rename rejects empty name", slug: "rename-tab", args: []string{""}, wantErr: ErrInvalidArguments},128 {name: "rename rejects empty name", slug: "rename-tab", args: []string{""}, wantErr: ErrInvalidArguments},
124 {name: "toast delegates severity", slug: "toast", args: []string{"-l", "warn", "hello"}, wantCall: "toast:warn:hello"},129 {name: "toast delegates severity", slug: "toast", args: []string{"-l", "warn", "hello"}, wantCall: "toast:warn:hello"},
M internal/usecase/daemon/control_test.go +28 -0
@@ -240,6 +240,34 @@ func TestHandleCommandSelfTargetsNonActiveTabAndPane(t *testing.T) {
240240 active.mu.Unlock()
241241 invoking.mu.Lock()
242242 require.Len(t, invoking.panes, 2)
243+ beforeFocus := invoking.tree.Focus
244+ beforeGeneration := invoking.layoutGeneration
245+ invoking.mu.Unlock()
246+
247+ grow := sendCommand(t, d, ports.CommandRequest{
248+ Slug: "grow-pane-width", Self: true,
249+ TargetSession: "work", TargetTab: "t_invoking", TargetPane: "p_invoking",
250+ })
251+ require.True(t, grow.OK, grow.Text)
252+ invoking.mu.Lock()
253+ require.Equal(t, beforeFocus, invoking.tree.Focus, "targeted resize must not refocus the tab")
254+ require.Equal(t, beforeGeneration+1, invoking.layoutGeneration)
255+ require.NotZero(t, invoking.tree.Root.Children[0].Weight, "resize must update the invoking tab's shares")
256+ invoking.mu.Unlock()
257+ active.mu.Lock()
258+ require.Len(t, active.panes, 1, "--self resize must not mutate the active tab")
259+ active.mu.Unlock()
260+
261+ equalize := sendCommand(t, d, ports.CommandRequest{
262+ Slug: "equalize-panes", Self: true,
263+ TargetSession: "work", TargetTab: "t_invoking", TargetPane: "p_invoking",
264+ })
265+ require.True(t, equalize.OK, equalize.Text)
266+ invoking.mu.Lock()
267+ require.Equal(t, beforeFocus, invoking.tree.Focus, "targeted equalize must not refocus the tab")
268+ for _, child := range invoking.tree.Root.Children {
269+ require.Zero(t, child.Weight, "equalize must clear each target-tab share")
270+ }
243271 invoking.mu.Unlock()
244272 }
245273
@@ -240,6 +240,34 @@ func TestHandleCommandSelfTargetsNonActiveTabAndPane(t *testing.T) {
240 active.mu.Unlock()240 active.mu.Unlock()
241 invoking.mu.Lock()241 invoking.mu.Lock()
242 require.Len(t, invoking.panes, 2)242 require.Len(t, invoking.panes, 2)
243+ beforeFocus := invoking.tree.Focus
244+ beforeGeneration := invoking.layoutGeneration
245+ invoking.mu.Unlock()
246+
247+ grow := sendCommand(t, d, ports.CommandRequest{
248+ Slug: "grow-pane-width", Self: true,
249+ TargetSession: "work", TargetTab: "t_invoking", TargetPane: "p_invoking",
250+ })
251+ require.True(t, grow.OK, grow.Text)
252+ invoking.mu.Lock()
253+ require.Equal(t, beforeFocus, invoking.tree.Focus, "targeted resize must not refocus the tab")
254+ require.Equal(t, beforeGeneration+1, invoking.layoutGeneration)
255+ require.NotZero(t, invoking.tree.Root.Children[0].Weight, "resize must update the invoking tab's shares")
256+ invoking.mu.Unlock()
257+ active.mu.Lock()
258+ require.Len(t, active.panes, 1, "--self resize must not mutate the active tab")
259+ active.mu.Unlock()
260+
261+ equalize := sendCommand(t, d, ports.CommandRequest{
262+ Slug: "equalize-panes", Self: true,
263+ TargetSession: "work", TargetTab: "t_invoking", TargetPane: "p_invoking",
264+ })
265+ require.True(t, equalize.OK, equalize.Text)
266+ invoking.mu.Lock()
267+ require.Equal(t, beforeFocus, invoking.tree.Focus, "targeted equalize must not refocus the tab")
268+ for _, child := range invoking.tree.Root.Children {
269+ require.Zero(t, child.Weight, "equalize must clear each target-tab share")
270+ }
243 invoking.mu.Unlock()271 invoking.mu.Unlock()
244 }272 }
245 273
M internal/usecase/daemon/resize_mode_test.go +14 -0
@@ -83,6 +83,20 @@ func TestResizeControlHeadlessAndErrors(t *testing.T) {
8383 require.Empty(t, equalized.Output)
8484 }
8585
86+func TestEnterResizeModeRejectsPaneOutsideSplit(t *testing.T) {
87+ factory := &controlPTYFactory{}
88+ d := newTestDaemon(t, factory, stubClock{})
89+ t.Cleanup(func() { factory.close(); d.sessWg.Wait() })
90+ sess := addControlSession(d, "work", "t_work", "p_work")
91+ ac := &attachedClient{output: newOutputStateStream(), size: domain.Size{Cols: 80, Rows: 24}}
92+ ac.initOverlays()
93+ ac.setSession(sess)
94+
95+ err := d.enterResizeMode(sess, ac)
96+ require.ErrorIs(t, err, layout.ErrNotInSplit)
97+ require.False(t, ac.overlays.resizeModeActive())
98+}
99+
86100 func TestResizeModeStateRenderingAndMouseConsumption(t *testing.T) {
87101 factory := &controlPTYFactory{}
88102 d := newTestDaemon(t, factory, stubClock{})
@@ -83,6 +83,20 @@ func TestResizeControlHeadlessAndErrors(t *testing.T) {
83 require.Empty(t, equalized.Output)83 require.Empty(t, equalized.Output)
84 }84 }
85 85
86+func TestEnterResizeModeRejectsPaneOutsideSplit(t *testing.T) {
87+ factory := &controlPTYFactory{}
88+ d := newTestDaemon(t, factory, stubClock{})
89+ t.Cleanup(func() { factory.close(); d.sessWg.Wait() })
90+ sess := addControlSession(d, "work", "t_work", "p_work")
91+ ac := &attachedClient{output: newOutputStateStream(), size: domain.Size{Cols: 80, Rows: 24}}
92+ ac.initOverlays()
93+ ac.setSession(sess)
94+
95+ err := d.enterResizeMode(sess, ac)
96+ require.ErrorIs(t, err, layout.ErrNotInSplit)
97+ require.False(t, ac.overlays.resizeModeActive())
98+}
99+
86 func TestResizeModeStateRenderingAndMouseConsumption(t *testing.T) {100 func TestResizeModeStateRenderingAndMouseConsumption(t *testing.T) {
87 factory := &controlPTYFactory{}101 factory := &controlPTYFactory{}
88 d := newTestDaemon(t, factory, stubClock{})102 d := newTestDaemon(t, factory, stubClock{})

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 12

Tags 11

↑↓ moveenter openesc close