detached d3b9acc

test(daemon): restore stack render coverage

commit
d3b9acca023bd36a83dc466774d7ed79694f13cb
author
brice · · 2026-07-15 04:01 UTC
parent
d626c6d
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
1 file · +43 -2
M internal/usecase/daemon/render_pipeline_cache_test.go +43 -2
@@ -124,6 +124,30 @@ func TestComposeFrameCacheSkipsUndamagedBlitsAndInvalidatesFocusAndLayout(t *tes
124124 require.Equal(t, []renderer.Damage{renderer.FullRedraw()}, out.damage)
125125 }
126126
127+func TestComposeFrameStackDrawsTitleBarsAndDimsCollapsed(t *testing.T) {
128+ state := cachedStackTitleState("collapsed", 1, true)
129+ state.theme = themeui.Theme{
130+ Known: true,
131+ TrueColor: true,
132+ HasFG: true,
133+ HasBG: true,
134+ Foreground: renderer.RGB{R: 220, G: 210, B: 200},
135+ Background: renderer.RGB{R: 20, G: 30, B: 40},
136+ }
137+ before := cloneCapturedStackState(state)
138+
139+ out := composeFrame(state, composeCacheInput{})
140+
141+ collapsedTitle := out.frame.At(0, 1)
142+ require.Equal(t, 'c', collapsedTitle.Rune)
143+ require.True(t, collapsedTitle.Style.HasForegroundRGB, "collapsed title bar must use dimmed chrome")
144+ focusedTitle := out.frame.At(0, 2)
145+ require.Equal(t, 's', focusedTitle.Rune)
146+ require.True(t, focusedTitle.Style.Inverse || focusedTitle.Style.HasBackgroundRGB, "focused title bar must use accent chrome")
147+ require.Equal(t, 'E', out.frame.At(0, 3).Rune, "expanded pane content must be visible below its title bar")
148+ require.Equal(t, before, state, "composition must not mutate the captured source frame")
149+}
150+
127151 func TestComposeFrameCacheRefreshesStackTitlesAndBars(t *testing.T) {
128152 initial := cachedStackTitleState("one", 1, true)
129153 committed := composeFrame(initial, composeCacheInput{})
@@ -188,14 +212,31 @@ func cachedSplitState(fingerprint string, focus layout.PaneID, direction layout.
188212
189213 func cachedStackTitleState(title string, generation uint64, reset bool) capturedRenderState {
190214 first, second := layout.PaneID("first"), layout.PaneID("second")
191- placements := []layout.Placement{{ID: first, TitleBar: domain.Rect{Width: 20, Height: 1}, Collapsed: true}, {ID: second, TitleBar: domain.Rect{Y: 1, Width: 20, Height: 1}, Collapsed: true}}
215+ placements := []layout.Placement{
216+ {ID: first, TitleBar: domain.Rect{Width: 20, Height: 1}, Collapsed: true, InStack: true},
217+ {ID: second, TitleBar: domain.Rect{Y: 1, Width: 20, Height: 1}, Content: domain.Rect{Y: 2, Width: 20, Height: 3}, InStack: true},
218+ }
192219 return capturedRenderState{
193220 reset: reset,
194221 layout: capturedTabLayout{root: &layout.Node{Kind: layout.Stack, Children: []*layout.Node{layout.NewLeaf(first), layout.NewLeaf(second)}, Expanded: second}, area: domain.Rect{Width: 20, Height: 5}, focus: second, placements: placements, fingerprint: "stack", valid: true},
195- panes: []capturedPaneRenderState{{id: first, title: title, titleGeneration: generation, placement: placements[0]}, {id: second, title: "second", titleGeneration: 1, placement: placements[1], focused: true}},
222+ panes: []capturedPaneRenderState{{id: first, title: title, titleGeneration: generation, placement: placements[0]}, {id: second, frame: cachePaneFrame(20, 3, 'E'), title: "second", titleGeneration: 1, placement: placements[1], focused: true, damage: []renderer.Damage{renderer.FullRedraw()}}},
196223 }
197224 }
198225
226+func cloneCapturedStackState(in capturedRenderState) capturedRenderState {
227+ out := in
228+ out.layout.placements = append([]layout.Placement(nil), in.layout.placements...)
229+ out.panes = append([]capturedPaneRenderState(nil), in.panes...)
230+ for i := range out.panes {
231+ if in.panes[i].frame.Cells != nil {
232+ out.panes[i].frame = in.panes[i].frame.Clone()
233+ }
234+ out.panes[i].rawDamage = append([]renderer.Damage(nil), in.panes[i].rawDamage...)
235+ out.panes[i].damage = append([]renderer.Damage(nil), in.panes[i].damage...)
236+ }
237+ return out
238+}
239+
199240 func cacheBarState(bottom string, reset bool) capturedRenderState {
200241 pane := cachePaneFrame(6, 1, 'P')
201242 placement := layout.Placement{ID: "pane", Content: domain.Rect{Width: 6, Height: 1}}
@@ -124,6 +124,30 @@ func TestComposeFrameCacheSkipsUndamagedBlitsAndInvalidatesFocusAndLayout(t *tes
124 require.Equal(t, []renderer.Damage{renderer.FullRedraw()}, out.damage)124 require.Equal(t, []renderer.Damage{renderer.FullRedraw()}, out.damage)
125 }125 }
126 126
127+func TestComposeFrameStackDrawsTitleBarsAndDimsCollapsed(t *testing.T) {
128+ state := cachedStackTitleState("collapsed", 1, true)
129+ state.theme = themeui.Theme{
130+ Known: true,
131+ TrueColor: true,
132+ HasFG: true,
133+ HasBG: true,
134+ Foreground: renderer.RGB{R: 220, G: 210, B: 200},
135+ Background: renderer.RGB{R: 20, G: 30, B: 40},
136+ }
137+ before := cloneCapturedStackState(state)
138+
139+ out := composeFrame(state, composeCacheInput{})
140+
141+ collapsedTitle := out.frame.At(0, 1)
142+ require.Equal(t, 'c', collapsedTitle.Rune)
143+ require.True(t, collapsedTitle.Style.HasForegroundRGB, "collapsed title bar must use dimmed chrome")
144+ focusedTitle := out.frame.At(0, 2)
145+ require.Equal(t, 's', focusedTitle.Rune)
146+ require.True(t, focusedTitle.Style.Inverse || focusedTitle.Style.HasBackgroundRGB, "focused title bar must use accent chrome")
147+ require.Equal(t, 'E', out.frame.At(0, 3).Rune, "expanded pane content must be visible below its title bar")
148+ require.Equal(t, before, state, "composition must not mutate the captured source frame")
149+}
150+
127 func TestComposeFrameCacheRefreshesStackTitlesAndBars(t *testing.T) {151 func TestComposeFrameCacheRefreshesStackTitlesAndBars(t *testing.T) {
128 initial := cachedStackTitleState("one", 1, true)152 initial := cachedStackTitleState("one", 1, true)
129 committed := composeFrame(initial, composeCacheInput{})153 committed := composeFrame(initial, composeCacheInput{})
@@ -188,14 +212,31 @@ func cachedSplitState(fingerprint string, focus layout.PaneID, direction layout.
188 212
189 func cachedStackTitleState(title string, generation uint64, reset bool) capturedRenderState {213 func cachedStackTitleState(title string, generation uint64, reset bool) capturedRenderState {
190 first, second := layout.PaneID("first"), layout.PaneID("second")214 first, second := layout.PaneID("first"), layout.PaneID("second")
191- placements := []layout.Placement{{ID: first, TitleBar: domain.Rect{Width: 20, Height: 1}, Collapsed: true}, {ID: second, TitleBar: domain.Rect{Y: 1, Width: 20, Height: 1}, Collapsed: true}}215+ placements := []layout.Placement{
216+ {ID: first, TitleBar: domain.Rect{Width: 20, Height: 1}, Collapsed: true, InStack: true},
217+ {ID: second, TitleBar: domain.Rect{Y: 1, Width: 20, Height: 1}, Content: domain.Rect{Y: 2, Width: 20, Height: 3}, InStack: true},
218+ }
192 return capturedRenderState{219 return capturedRenderState{
193 reset: reset,220 reset: reset,
194 layout: capturedTabLayout{root: &layout.Node{Kind: layout.Stack, Children: []*layout.Node{layout.NewLeaf(first), layout.NewLeaf(second)}, Expanded: second}, area: domain.Rect{Width: 20, Height: 5}, focus: second, placements: placements, fingerprint: "stack", valid: true},221 layout: capturedTabLayout{root: &layout.Node{Kind: layout.Stack, Children: []*layout.Node{layout.NewLeaf(first), layout.NewLeaf(second)}, Expanded: second}, area: domain.Rect{Width: 20, Height: 5}, focus: second, placements: placements, fingerprint: "stack", valid: true},
195- panes: []capturedPaneRenderState{{id: first, title: title, titleGeneration: generation, placement: placements[0]}, {id: second, title: "second", titleGeneration: 1, placement: placements[1], focused: true}},222+ panes: []capturedPaneRenderState{{id: first, title: title, titleGeneration: generation, placement: placements[0]}, {id: second, frame: cachePaneFrame(20, 3, 'E'), title: "second", titleGeneration: 1, placement: placements[1], focused: true, damage: []renderer.Damage{renderer.FullRedraw()}}},
196 }223 }
197 }224 }
198 225
226+func cloneCapturedStackState(in capturedRenderState) capturedRenderState {
227+ out := in
228+ out.layout.placements = append([]layout.Placement(nil), in.layout.placements...)
229+ out.panes = append([]capturedPaneRenderState(nil), in.panes...)
230+ for i := range out.panes {
231+ if in.panes[i].frame.Cells != nil {
232+ out.panes[i].frame = in.panes[i].frame.Clone()
233+ }
234+ out.panes[i].rawDamage = append([]renderer.Damage(nil), in.panes[i].rawDamage...)
235+ out.panes[i].damage = append([]renderer.Damage(nil), in.panes[i].damage...)
236+ }
237+ return out
238+}
239+
199 func cacheBarState(bottom string, reset bool) capturedRenderState {240 func cacheBarState(bottom string, reset bool) capturedRenderState {
200 pane := cachePaneFrame(6, 1, 'P')241 pane := cachePaneFrame(6, 1, 'P')
201 placement := layout.Placement{ID: "pane", Content: domain.Rect{Width: 6, Height: 1}}242 placement := layout.Placement{ID: "pane", Content: domain.Rect{Width: 6, Height: 1}}

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