test(core): cover cascade move-column-right across bands and a real lone float
- commit
- e0bc7dc73316befb0a8419bc052806ce0fb637de
- author
- brice · · 2026-10-12 12:48 UTC
- parent
- 9c3cc20
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
4 files · +17 -12
M docs/config.md +1 -1
| @@ -267,7 +267,7 @@ In a row, the auto columns share the cells the set ones leave; when no cell is l | |||
| 267 | 267 | | `maximize-column` | Toggle full usable width for the focused column, preserving gaps and its saved width; in fixed overflow, other columns are hidden until focus moves or it is toggled off. On a window that made itself fullscreen (e.g. a Wine app at monitor size), it first returns the window to its column | | |
| 268 | 268 | | `cycle-column-width` | Grow the focused column by one cell, taking it from the widest other column of its row (see [Column widths](#column-widths)). When no column has a cell to give, every column of the row goes back to equal shares. In scroll overflow, the column grows to `max-columns` cells, then back to auto. A lone column or a full row cannot grow: it goes back to equal shares if its cells were set, and otherwise nothing changes. A spiral never changes | | |
| 269 | 269 | | `focus-column-left/right` | Focus the neighbor column; at the edge, the neighbor monitor. In cascade overflow the edge is the band's, and past it with no monitor the previous or next workspace. In fixed overflow, the neighbor on screen. From a fullscreen window, the neighbor column comes first and fullscreen ends; at the edge it stays | | |
| 270 | - | | `focus-window-up/down` | Focus in the column; past the edge, the next workspace (in cascade overflow, the adjacent band, stopping at the first and last). In fixed overflow, the column on screen above or below comes first. Up at the top brings back a covering float put behind the columns. From a fullscreen window, a window above or below comes first and fullscreen ends | | |
| 270 | + | | `focus-window-up/down` | Focus in the column; past the edge, the previous or next workspace (in cascade overflow, the adjacent band, stopping at the first and last). In fixed overflow, the column on screen above or below comes first. Up at the top brings back a covering float put behind the columns. From a fullscreen window, a window above or below comes first and fullscreen ends | | |
| 271 | 271 | | `move-column-left/right` | Move the focused column; at the edge, to the neighbor monitor, focus following. In cascade overflow the edge is the band's; with no monitor there, the column crosses into the adjacent band. Fixed overflow follows column order, not the screen | | |
| 272 | 272 | | `move-window-up/down` | Move the focused window up or down in its column; focus follows it. In cascade overflow, past the column, the column moves to the adjacent band, where `focus-window-up/down` goes | | |
| 273 | 273 | | `set-column-width <+N\|-N>` | Widen or narrow the focused column by N cells (1-16), never below one cell and without cycling back to equal shares. In `cascade` and `fixed` overflow the cells come from, or go to, the other columns of the row; in `scroll` overflow the column grows on its own, up to `max-columns` cells | | |
| @@ -267,7 +267,7 @@ In a row, the auto columns share the cells the set ones leave; when no cell is l | |||||
| 267 | | `maximize-column` | Toggle full usable width for the focused column, preserving gaps and its saved width; in fixed overflow, other columns are hidden until focus moves or it is toggled off. On a window that made itself fullscreen (e.g. a Wine app at monitor size), it first returns the window to its column | | 267 | | `maximize-column` | Toggle full usable width for the focused column, preserving gaps and its saved width; in fixed overflow, other columns are hidden until focus moves or it is toggled off. On a window that made itself fullscreen (e.g. a Wine app at monitor size), it first returns the window to its column | | ||
| 268 | | `cycle-column-width` | Grow the focused column by one cell, taking it from the widest other column of its row (see [Column widths](#column-widths)). When no column has a cell to give, every column of the row goes back to equal shares. In scroll overflow, the column grows to `max-columns` cells, then back to auto. A lone column or a full row cannot grow: it goes back to equal shares if its cells were set, and otherwise nothing changes. A spiral never changes | | 268 | | `cycle-column-width` | Grow the focused column by one cell, taking it from the widest other column of its row (see [Column widths](#column-widths)). When no column has a cell to give, every column of the row goes back to equal shares. In scroll overflow, the column grows to `max-columns` cells, then back to auto. A lone column or a full row cannot grow: it goes back to equal shares if its cells were set, and otherwise nothing changes. A spiral never changes | | ||
| 269 | | `focus-column-left/right` | Focus the neighbor column; at the edge, the neighbor monitor. In cascade overflow the edge is the band's, and past it with no monitor the previous or next workspace. In fixed overflow, the neighbor on screen. From a fullscreen window, the neighbor column comes first and fullscreen ends; at the edge it stays | | 269 | | `focus-column-left/right` | Focus the neighbor column; at the edge, the neighbor monitor. In cascade overflow the edge is the band's, and past it with no monitor the previous or next workspace. In fixed overflow, the neighbor on screen. From a fullscreen window, the neighbor column comes first and fullscreen ends; at the edge it stays | | ||
| 270 | - | | `focus-window-up/down` | Focus in the column; past the edge, the next workspace (in cascade overflow, the adjacent band, stopping at the first and last). In fixed overflow, the column on screen above or below comes first. Up at the top brings back a covering float put behind the columns. From a fullscreen window, a window above or below comes first and fullscreen ends | | 270 | + | | `focus-window-up/down` | Focus in the column; past the edge, the previous or next workspace (in cascade overflow, the adjacent band, stopping at the first and last). In fixed overflow, the column on screen above or below comes first. Up at the top brings back a covering float put behind the columns. From a fullscreen window, a window above or below comes first and fullscreen ends | |
| 271 | | `move-column-left/right` | Move the focused column; at the edge, to the neighbor monitor, focus following. In cascade overflow the edge is the band's; with no monitor there, the column crosses into the adjacent band. Fixed overflow follows column order, not the screen | | 271 | | `move-column-left/right` | Move the focused column; at the edge, to the neighbor monitor, focus following. In cascade overflow the edge is the band's; with no monitor there, the column crosses into the adjacent band. Fixed overflow follows column order, not the screen | | ||
| 272 | | `move-window-up/down` | Move the focused window up or down in its column; focus follows it. In cascade overflow, past the column, the column moves to the adjacent band, where `focus-window-up/down` goes | | 272 | | `move-window-up/down` | Move the focused window up or down in its column; focus follows it. In cascade overflow, past the column, the column moves to the adjacent band, where `focus-window-up/down` goes | | ||
| 273 | | `set-column-width <+N\|-N>` | Widen or narrow the focused column by N cells (1-16), never below one cell and without cycling back to equal shares. In `cascade` and `fixed` overflow the cells come from, or go to, the other columns of the row; in `scroll` overflow the column grows on its own, up to `max-columns` cells | | 273 | | `set-column-width <+N\|-N>` | Widen or narrow the focused column by N cells (1-16), never below one cell and without cycling back to equal shares. In `cascade` and `fixed` overflow the cells come from, or go to, the other columns of the row; in `scroll` overflow the column grows on its own, up to `max-columns` cells | | ||
M internal/core/action.go +3 -2
| @@ -197,8 +197,9 @@ type Effect struct { | |||
| 197 | 197 | } | |
| 198 | 198 | ||
| 199 | 199 | // applyAction runs a bind action. Monitor actions change the focused | |
| 200 | - | // screen; column focus past the edge column moves to the neighbor screen; | |
| 201 | - | // everything else applies to the focused screen's monitor. | |
| 200 | + | // screen; column focus past the edge column moves to the neighbor screen, | |
| 201 | + | // else along the layout's workspace axis; everything else applies to the | |
| 202 | + | // focused screen's monitor. | |
| 202 | 203 | func (c *Core) applyAction(a Action) Effect { | |
| 203 | 204 | if e, ok := c.cur().mon.overviewAction(a); ok { | |
| 204 | 205 | return e | |
| @@ -197,8 +197,9 @@ type Effect struct { | |||||
| 197 | } | 197 | } | ||
| 198 | 198 | ||||
| 199 | // applyAction runs a bind action. Monitor actions change the focused | 199 | // applyAction runs a bind action. Monitor actions change the focused | ||
| 200 | - | // screen; column focus past the edge column moves to the neighbor screen; | 200 | + | // screen; column focus past the edge column moves to the neighbor screen, |
| 201 | - | // everything else applies to the focused screen's monitor. | 201 | + | // else along the layout's workspace axis; everything else applies to the |
| 202 | + | // focused screen's monitor. | |||
| 202 | func (c *Core) applyAction(a Action) Effect { | 203 | func (c *Core) applyAction(a Action) Effect { | ||
| 203 | if e, ok := c.cur().mon.overviewAction(a); ok { | 204 | if e, ok := c.cur().mon.overviewAction(a); ok { | ||
| 204 | return e | 205 | return e | ||
M internal/core/cascade_moves_test.go +5 -1
| @@ -104,7 +104,7 @@ func TestCascadeInsideBandStaysOnMonitor(t *testing.T) { | |||
| 104 | 104 | // band: at the band edge they step the workspace (the first has none before | |
| 105 | 105 | // it; the last is the trailing empty one). up/down cross bands, and | |
| 106 | 106 | // move-column still crosses in column order. | |
| 107 | - | func TestCascadeBandEdgeWithoutMonitorCrossesBands(t *testing.T) { | |
| 107 | + | func TestCascadeBandEdgeWithoutMonitorFocusStepsMoveCrosses(t *testing.T) { | |
| 108 | 108 | c, w := cascadeMovesCore(t, 5, 3, false) | |
| 109 | 109 | m := c.cur().mon | |
| 110 | 110 | w.FocusID(4) | |
| @@ -123,6 +123,10 @@ func TestCascadeBandEdgeWithoutMonitorCrossesBands(t *testing.T) { | |||
| 123 | 123 | c.applyAction(ActionMoveColumnLeft) | |
| 124 | 124 | wantColumns(t, w, 1, 2, 4, 3, 5) | |
| 125 | 125 | wantFocus(t, c, "M", 4) | |
| 126 | + | w.FocusID(4) | |
| 127 | + | c.applyAction(ActionMoveColumnRight) | |
| 128 | + | wantColumns(t, w, 1, 2, 3, 4, 5) | |
| 129 | + | wantFocus(t, c, "M", 4) | |
| 126 | 130 | } | |
| 127 | 131 | ||
| 128 | 132 | // With one column per band, every column is at both edges of its band. | |
| @@ -104,7 +104,7 @@ func TestCascadeInsideBandStaysOnMonitor(t *testing.T) { | |||||
| 104 | // band: at the band edge they step the workspace (the first has none before | 104 | // band: at the band edge they step the workspace (the first has none before | ||
| 105 | // it; the last is the trailing empty one). up/down cross bands, and | 105 | // it; the last is the trailing empty one). up/down cross bands, and | ||
| 106 | // move-column still crosses in column order. | 106 | // move-column still crosses in column order. | ||
| 107 | - | func TestCascadeBandEdgeWithoutMonitorCrossesBands(t *testing.T) { | 107 | + | func TestCascadeBandEdgeWithoutMonitorFocusStepsMoveCrosses(t *testing.T) { |
| 108 | c, w := cascadeMovesCore(t, 5, 3, false) | 108 | c, w := cascadeMovesCore(t, 5, 3, false) | ||
| 109 | m := c.cur().mon | 109 | m := c.cur().mon | ||
| 110 | w.FocusID(4) | 110 | w.FocusID(4) | ||
| @@ -123,6 +123,10 @@ func TestCascadeBandEdgeWithoutMonitorCrossesBands(t *testing.T) { | |||||
| 123 | c.applyAction(ActionMoveColumnLeft) | 123 | c.applyAction(ActionMoveColumnLeft) | ||
| 124 | wantColumns(t, w, 1, 2, 4, 3, 5) | 124 | wantColumns(t, w, 1, 2, 4, 3, 5) | ||
| 125 | wantFocus(t, c, "M", 4) | 125 | wantFocus(t, c, "M", 4) | ||
| 126 | + | w.FocusID(4) | |||
| 127 | + | c.applyAction(ActionMoveColumnRight) | |||
| 128 | + | wantColumns(t, w, 1, 2, 3, 4, 5) | |||
| 129 | + | wantFocus(t, c, "M", 4) | |||
| 126 | } | 130 | } | ||
| 127 | 131 | ||||
| 128 | // With one column per band, every column is at both edges of its band. | 132 | // With one column per band, every column is at both edges of its band. | ||
M internal/core/workspace_axis_test.go +8 -8
| @@ -155,17 +155,17 @@ func TestCascadeLoneFloatAndEmptyWorkspaceStepWorkspace(t *testing.T) { | |||
| 155 | 155 | c.applyAction(ActionFocusColumnLeft) | |
| 156 | 156 | wantActive(t, m, 2) | |
| 157 | 157 | ||
| 158 | - | c, m = axisCore(t, false, OverflowCascade, 3) | |
| 159 | - | m.Focus(0) | |
| 160 | - | m.Current().AddFloating(9, 100, 100) | |
| 158 | + | // A lone native float on its own workspace, past the trailing one. | |
| 161 | 159 | for _, tc := range []struct { | |
| 162 | 160 | a Action | |
| 163 | 161 | want int | |
| 164 | - | }{{ActionFocusColumnLeft, 0}, {ActionFocusColumnRight, 1}} { | |
| 165 | - | m.Focus(0) | |
| 166 | - | w := m.Current() | |
| 167 | - | w.Columns = nil // a lone float: no columns under it | |
| 168 | - | w.floatFocus = true | |
| 162 | + | }{{ActionFocusColumnLeft, 2}, {ActionFocusColumnRight, 4}} { | |
| 163 | + | c, m = axisCore(t, false, OverflowCascade, 3) | |
| 164 | + | m.Focus(3) | |
| 165 | + | m.AddFloating(9, 100, 100) | |
| 166 | + | if w := m.Current(); len(w.Columns) != 0 || !w.floatFocus { | |
| 167 | + | t.Fatalf("columns %d floatFocus %v, want a lone focused float", len(w.Columns), w.floatFocus) | |
| 168 | + | } | |
| 169 | 169 | c.applyAction(tc.a) | |
| 170 | 170 | wantActive(t, m, tc.want) | |
| 171 | 171 | } | |
| @@ -155,17 +155,17 @@ func TestCascadeLoneFloatAndEmptyWorkspaceStepWorkspace(t *testing.T) { | |||||
| 155 | c.applyAction(ActionFocusColumnLeft) | 155 | c.applyAction(ActionFocusColumnLeft) | ||
| 156 | wantActive(t, m, 2) | 156 | wantActive(t, m, 2) | ||
| 157 | 157 | ||||
| 158 | - | c, m = axisCore(t, false, OverflowCascade, 3) | 158 | + | // A lone native float on its own workspace, past the trailing one. |
| 159 | - | m.Focus(0) | |||
| 160 | - | m.Current().AddFloating(9, 100, 100) | |||
| 161 | for _, tc := range []struct { | 159 | for _, tc := range []struct { | ||
| 162 | a Action | 160 | a Action | ||
| 163 | want int | 161 | want int | ||
| 164 | - | }{{ActionFocusColumnLeft, 0}, {ActionFocusColumnRight, 1}} { | 162 | + | }{{ActionFocusColumnLeft, 2}, {ActionFocusColumnRight, 4}} { |
| 165 | - | m.Focus(0) | 163 | + | c, m = axisCore(t, false, OverflowCascade, 3) |
| 166 | - | w := m.Current() | 164 | + | m.Focus(3) |
| 167 | - | w.Columns = nil // a lone float: no columns under it | 165 | + | m.AddFloating(9, 100, 100) |
| 168 | - | w.floatFocus = true | 166 | + | if w := m.Current(); len(w.Columns) != 0 || !w.floatFocus { |
| 167 | + | t.Fatalf("columns %d floatFocus %v, want a lone focused float", len(w.Columns), w.floatFocus) | |||
| 168 | + | } | |||
| 169 | c.applyAction(tc.a) | 169 | c.applyAction(tc.a) | ||
| 170 | wantActive(t, m, tc.want) | 170 | wantActive(t, m, tc.want) | ||
| 171 | } | 171 | } | ||