detached 9e988af

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 VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · 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) {
283283 t.Fatalf("motion clamped at the minimum published %+v", cmds)
284284 }
285285 }
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) {
9090
9191 func TestDropStaleSpansKeepsValidLanes(t *testing.T) {
9292 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+ }
9497 if w.Columns[0].Span != 0 || w.Columns[0].spanStamp != 0 {
9598 t.Fatal("unstamped workspace changed")
9699 }
@@ -110,3 +113,51 @@ func TestDropStaleSpansKeepsValidLanes(t *testing.T) {
110113 }
111114 }
112115 }
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+}

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 25

Tags 10

↑↓ moveenter openesc close