test(core): cover column edges across overflow modes and border pair limits
- commit
- 9e988afa14dd29c28be7f9c5e72ad8ccd58fb82d
- author
- brice · · 2026-10-21 08:21 UTC
- parent
- 12f4382
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +91 -1
M internal/core/border_test.go +39 -0
| @@ -283,3 +283,42 @@ func TestBorderMotionPublishesOnlyChanges(t *testing.T) { | |||
| 283 | 283 | t.Fatalf("motion clamped at the minimum published %+v", cmds) | |
| 284 | 284 | } | |
| 285 | 285 | } | |
| 286 | + | ||
| 287 | + | func TestSetBorderUndersizedPairReportsStampOnce(t *testing.T) { | |
| 288 | + | t.Run("row", func(t *testing.T) { | |
| 289 | + | w := spanWorkspace(OverflowFixed, 1) | |
| 290 | + | w.Columns[0].Windows = []WindowID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} | |
| 291 | + | // Ten rows is the most that fit; pixel rounding leaves the last pair | |
| 292 | + | // just under twice the minimum share. | |
| 293 | + | b := border{kind: borderRow, col: 0, row: 8} | |
| 294 | + | rows := rowRects(w.columnRects()[0], w.Columns[0], w.gap()) | |
| 295 | + | pos := rows[8].Y + 1 | |
| 296 | + | if !w.setBorder(b, pos) { | |
| 297 | + | t.Fatal("stamping an undersized row pair must report a change") | |
| 298 | + | } | |
| 299 | + | if c := w.Columns[0]; c.Shares[8]+c.Shares[9] >= 2*minShare { | |
| 300 | + | t.Fatal("row pair not undersized", c.Shares) | |
| 301 | + | } | |
| 302 | + | if w.setBorder(b, pos) { | |
| 303 | + | t.Fatal("repeat on an undersized row pair reported a change") | |
| 304 | + | } | |
| 305 | + | }) | |
| 306 | + | for _, mode := range []Overflow{OverflowFixed, OverflowCascade} { | |
| 307 | + | t.Run(string(mode), func(t *testing.T) { | |
| 308 | + | w := spanWorkspace(mode, 3) | |
| 309 | + | w.border = 300 | |
| 310 | + | b := border{kind: borderColumn, col: 0} | |
| 311 | + | pos := w.columnRects()[0].X + 100 | |
| 312 | + | if !w.setBorder(b, pos) { | |
| 313 | + | t.Fatal("stamping an undersized column pair must report a change") | |
| 314 | + | } | |
| 315 | + | avail := w.Usable.W - w.gap()*4 | |
| 316 | + | if pair := w.Columns[0].Span + w.Columns[1].Span; pair >= 2*pixelShare(4*w.border, avail) { | |
| 317 | + | t.Fatal("column pair not undersized", pair) | |
| 318 | + | } | |
| 319 | + | if w.setBorder(b, pos) { | |
| 320 | + | t.Fatal("repeat on an undersized column pair reported a change") | |
| 321 | + | } | |
| 322 | + | }) | |
| 323 | + | } | |
| 324 | + | } | |
| @@ -283,3 +283,42 @@ func TestBorderMotionPublishesOnlyChanges(t *testing.T) { | |||||
| 283 | t.Fatalf("motion clamped at the minimum published %+v", cmds) | 283 | t.Fatalf("motion clamped at the minimum published %+v", cmds) | ||
| 284 | } | 284 | } | ||
| 285 | } | 285 | } | ||
| 286 | + | ||||
| 287 | + | func TestSetBorderUndersizedPairReportsStampOnce(t *testing.T) { | |||
| 288 | + | t.Run("row", func(t *testing.T) { | |||
| 289 | + | w := spanWorkspace(OverflowFixed, 1) | |||
| 290 | + | w.Columns[0].Windows = []WindowID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} | |||
| 291 | + | // Ten rows is the most that fit; pixel rounding leaves the last pair | |||
| 292 | + | // just under twice the minimum share. | |||
| 293 | + | b := border{kind: borderRow, col: 0, row: 8} | |||
| 294 | + | rows := rowRects(w.columnRects()[0], w.Columns[0], w.gap()) | |||
| 295 | + | pos := rows[8].Y + 1 | |||
| 296 | + | if !w.setBorder(b, pos) { | |||
| 297 | + | t.Fatal("stamping an undersized row pair must report a change") | |||
| 298 | + | } | |||
| 299 | + | if c := w.Columns[0]; c.Shares[8]+c.Shares[9] >= 2*minShare { | |||
| 300 | + | t.Fatal("row pair not undersized", c.Shares) | |||
| 301 | + | } | |||
| 302 | + | if w.setBorder(b, pos) { | |||
| 303 | + | t.Fatal("repeat on an undersized row pair reported a change") | |||
| 304 | + | } | |||
| 305 | + | }) | |||
| 306 | + | for _, mode := range []Overflow{OverflowFixed, OverflowCascade} { | |||
| 307 | + | t.Run(string(mode), func(t *testing.T) { | |||
| 308 | + | w := spanWorkspace(mode, 3) | |||
| 309 | + | w.border = 300 | |||
| 310 | + | b := border{kind: borderColumn, col: 0} | |||
| 311 | + | pos := w.columnRects()[0].X + 100 | |||
| 312 | + | if !w.setBorder(b, pos) { | |||
| 313 | + | t.Fatal("stamping an undersized column pair must report a change") | |||
| 314 | + | } | |||
| 315 | + | avail := w.Usable.W - w.gap()*4 | |||
| 316 | + | if pair := w.Columns[0].Span + w.Columns[1].Span; pair >= 2*pixelShare(4*w.border, avail) { | |||
| 317 | + | t.Fatal("column pair not undersized", pair) | |||
| 318 | + | } | |||
| 319 | + | if w.setBorder(b, pos) { | |||
| 320 | + | t.Fatal("repeat on an undersized column pair reported a change") | |||
| 321 | + | } | |||
| 322 | + | }) | |||
| 323 | + | } | |||
| 324 | + | } | |||
M internal/core/layout_policy_test.go +52 -1
| @@ -90,7 +90,10 @@ func TestScrollWidthsMatchPerColumnValidation(t *testing.T) { | |||
| 90 | 90 | ||
| 91 | 91 | func TestDropStaleSpansKeepsValidLanes(t *testing.T) { | |
| 92 | 92 | w := spanWorkspace(OverflowScroll, 4) | |
| 93 | - | w.dropStaleSpans() | |
| 93 | + | // An unstamped workspace returns before any lane is measured. | |
| 94 | + | if n := testing.AllocsPerRun(10, w.dropStaleSpans); n != 0 { | |
| 95 | + | t.Fatalf("unstamped workspace allocates %v", n) | |
| 96 | + | } | |
| 94 | 97 | if w.Columns[0].Span != 0 || w.Columns[0].spanStamp != 0 { | |
| 95 | 98 | t.Fatal("unstamped workspace changed") | |
| 96 | 99 | } | |
| @@ -110,3 +113,51 @@ func TestDropStaleSpansKeepsValidLanes(t *testing.T) { | |||
| 110 | 113 | } | |
| 111 | 114 | } | |
| 112 | 115 | } | |
| 116 | + | ||
| 117 | + | // TestColumnEdgesMatchColumnRects checks columnEdges against columnRects, an | |
| 118 | + | // independent path that builds the same row with the view shift applied. | |
| 119 | + | func TestColumnEdgesMatchColumnRects(t *testing.T) { | |
| 120 | + | for _, tc := range []struct { | |
| 121 | + | name string | |
| 122 | + | mode Overflow | |
| 123 | + | setup func(w *Workspace) | |
| 124 | + | // xs is compared where the rects keep the row's x (not cascade bands, | |
| 125 | + | // nor the focused maximized column of fixed overflow). | |
| 126 | + | xs bool | |
| 127 | + | }{ | |
| 128 | + | {"scroll plain", OverflowScroll, func(w *Workspace) {}, true}, | |
| 129 | + | {"scroll stamped", OverflowScroll, func(w *Workspace) { stampSpans(w, 0, len(w.Columns)-1) }, true}, | |
| 130 | + | {"scroll full width", OverflowScroll, func(w *Workspace) { w.Columns[1].FullWidth = true }, true}, | |
| 131 | + | {"scroll fullscreen", OverflowScroll, func(w *Workspace) { w.SetFullscreen(w.Columns[2].Windows[0], true) }, true}, | |
| 132 | + | {"scroll viewed", OverflowScroll, func(w *Workspace) { w.View = 137 }, true}, | |
| 133 | + | {"fixed", OverflowFixed, func(w *Workspace) {}, true}, | |
| 134 | + | {"fixed full width", OverflowFixed, func(w *Workspace) { w.Columns[1].FullWidth = true; w.Focus = 0 }, true}, | |
| 135 | + | {"fixed focused full width", OverflowFixed, func(w *Workspace) { w.Columns[1].FullWidth = true; w.Focus = 1 }, false}, | |
| 136 | + | {"fixed fullscreen", OverflowFixed, func(w *Workspace) { w.SetFullscreen(w.Columns[2].Windows[0], true) }, true}, | |
| 137 | + | {"fixed stamped", OverflowFixed, func(w *Workspace) { stampSpans(w, 0, len(w.Columns)-1) }, true}, | |
| 138 | + | {"cascade", OverflowCascade, func(w *Workspace) {}, false}, | |
| 139 | + | {"cascade full width", OverflowCascade, func(w *Workspace) { w.Columns[1].FullWidth = true }, false}, | |
| 140 | + | {"cascade stamped", OverflowCascade, func(w *Workspace) { stampSpans(w, 0, len(w.Columns)-1) }, false}, | |
| 141 | + | } { | |
| 142 | + | t.Run(tc.name, func(t *testing.T) { | |
| 143 | + | w := spanWorkspace(tc.mode, 3) | |
| 144 | + | tc.setup(w) | |
| 145 | + | if tc.mode != OverflowScroll && w.scrollSpansValid() { | |
| 146 | + | t.Fatal("equal-cell workspace must not take the scroll span path") | |
| 147 | + | } | |
| 148 | + | xs, ws := w.columnEdges() | |
| 149 | + | rs := w.columnRectsAt(nil, false, 0) | |
| 150 | + | if len(xs) != len(rs) || len(ws) != len(rs) { | |
| 151 | + | t.Fatal("length", len(xs), len(ws), len(rs)) | |
| 152 | + | } | |
| 153 | + | for i, r := range rs { | |
| 154 | + | if ws[i] != r.W { | |
| 155 | + | t.Errorf("column %d width %d, rect %d", i, ws[i], r.W) | |
| 156 | + | } | |
| 157 | + | if tc.xs && xs[i] != r.X+w.View { | |
| 158 | + | t.Errorf("column %d x %d, rect %d + view %d", i, xs[i], r.X, w.View) | |
| 159 | + | } | |
| 160 | + | } | |
| 161 | + | }) | |
| 162 | + | } | |
| 163 | + | } | |
| @@ -90,7 +90,10 @@ func TestScrollWidthsMatchPerColumnValidation(t *testing.T) { | |||||
| 90 | 90 | ||||
| 91 | func TestDropStaleSpansKeepsValidLanes(t *testing.T) { | 91 | func TestDropStaleSpansKeepsValidLanes(t *testing.T) { | ||
| 92 | w := spanWorkspace(OverflowScroll, 4) | 92 | w := spanWorkspace(OverflowScroll, 4) | ||
| 93 | - | w.dropStaleSpans() | 93 | + | // An unstamped workspace returns before any lane is measured. |
| 94 | + | if n := testing.AllocsPerRun(10, w.dropStaleSpans); n != 0 { | |||
| 95 | + | t.Fatalf("unstamped workspace allocates %v", n) | |||
| 96 | + | } | |||
| 94 | if w.Columns[0].Span != 0 || w.Columns[0].spanStamp != 0 { | 97 | if w.Columns[0].Span != 0 || w.Columns[0].spanStamp != 0 { | ||
| 95 | t.Fatal("unstamped workspace changed") | 98 | t.Fatal("unstamped workspace changed") | ||
| 96 | } | 99 | } | ||
| @@ -110,3 +113,51 @@ func TestDropStaleSpansKeepsValidLanes(t *testing.T) { | |||||
| 110 | } | 113 | } | ||
| 111 | } | 114 | } | ||
| 112 | } | 115 | } | ||
| 116 | + | ||||
| 117 | + | // TestColumnEdgesMatchColumnRects checks columnEdges against columnRects, an | |||
| 118 | + | // independent path that builds the same row with the view shift applied. | |||
| 119 | + | func TestColumnEdgesMatchColumnRects(t *testing.T) { | |||
| 120 | + | for _, tc := range []struct { | |||
| 121 | + | name string | |||
| 122 | + | mode Overflow | |||
| 123 | + | setup func(w *Workspace) | |||
| 124 | + | // xs is compared where the rects keep the row's x (not cascade bands, | |||
| 125 | + | // nor the focused maximized column of fixed overflow). | |||
| 126 | + | xs bool | |||
| 127 | + | }{ | |||
| 128 | + | {"scroll plain", OverflowScroll, func(w *Workspace) {}, true}, | |||
| 129 | + | {"scroll stamped", OverflowScroll, func(w *Workspace) { stampSpans(w, 0, len(w.Columns)-1) }, true}, | |||
| 130 | + | {"scroll full width", OverflowScroll, func(w *Workspace) { w.Columns[1].FullWidth = true }, true}, | |||
| 131 | + | {"scroll fullscreen", OverflowScroll, func(w *Workspace) { w.SetFullscreen(w.Columns[2].Windows[0], true) }, true}, | |||
| 132 | + | {"scroll viewed", OverflowScroll, func(w *Workspace) { w.View = 137 }, true}, | |||
| 133 | + | {"fixed", OverflowFixed, func(w *Workspace) {}, true}, | |||
| 134 | + | {"fixed full width", OverflowFixed, func(w *Workspace) { w.Columns[1].FullWidth = true; w.Focus = 0 }, true}, | |||
| 135 | + | {"fixed focused full width", OverflowFixed, func(w *Workspace) { w.Columns[1].FullWidth = true; w.Focus = 1 }, false}, | |||
| 136 | + | {"fixed fullscreen", OverflowFixed, func(w *Workspace) { w.SetFullscreen(w.Columns[2].Windows[0], true) }, true}, | |||
| 137 | + | {"fixed stamped", OverflowFixed, func(w *Workspace) { stampSpans(w, 0, len(w.Columns)-1) }, true}, | |||
| 138 | + | {"cascade", OverflowCascade, func(w *Workspace) {}, false}, | |||
| 139 | + | {"cascade full width", OverflowCascade, func(w *Workspace) { w.Columns[1].FullWidth = true }, false}, | |||
| 140 | + | {"cascade stamped", OverflowCascade, func(w *Workspace) { stampSpans(w, 0, len(w.Columns)-1) }, false}, | |||
| 141 | + | } { | |||
| 142 | + | t.Run(tc.name, func(t *testing.T) { | |||
| 143 | + | w := spanWorkspace(tc.mode, 3) | |||
| 144 | + | tc.setup(w) | |||
| 145 | + | if tc.mode != OverflowScroll && w.scrollSpansValid() { | |||
| 146 | + | t.Fatal("equal-cell workspace must not take the scroll span path") | |||
| 147 | + | } | |||
| 148 | + | xs, ws := w.columnEdges() | |||
| 149 | + | rs := w.columnRectsAt(nil, false, 0) | |||
| 150 | + | if len(xs) != len(rs) || len(ws) != len(rs) { | |||
| 151 | + | t.Fatal("length", len(xs), len(ws), len(rs)) | |||
| 152 | + | } | |||
| 153 | + | for i, r := range rs { | |||
| 154 | + | if ws[i] != r.W { | |||
| 155 | + | t.Errorf("column %d width %d, rect %d", i, ws[i], r.W) | |||
| 156 | + | } | |||
| 157 | + | if tc.xs && xs[i] != r.X+w.View { | |||
| 158 | + | t.Errorf("column %d x %d, rect %d + view %d", i, xs[i], r.X, w.View) | |||
| 159 | + | } | |||
| 160 | + | } | |||
| 161 | + | }) | |||
| 162 | + | } | |||
| 163 | + | } | |||