detached 8138266

test(daemon): restore attention pane coverage

commit
8138266bbb2a3dd42f2c5d7f01ec88d951a87f7a
author
brice · · 2026-07-15 04:32 UTC
parent
d3b9acc
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
1 file · +25 -4
M internal/usecase/daemon/attention_test.go +25 -4
@@ -14,6 +14,7 @@ import (
1414 "github.com/bnema/vev/internal/ports"
1515 portsmocks "github.com/bnema/vev/internal/ports/mocks"
1616 "github.com/bnema/vev/internal/usecase/keys"
17+ "github.com/bnema/vev/internal/usecase/layout"
1718 "github.com/bnema/vev/internal/usecase/ui"
1819 "github.com/bnema/vev/pkg/renderer"
1920 )
@@ -380,19 +381,39 @@ func TestComposeFrameAttentionFrameChangeDamagesOnlyBars(t *testing.T) {
380381 sess.mu.Unlock()
381382
382383 tb := sess.tabs[0]
384+ pane := tb.focusedPane()
385+ pane.screen.Write([]byte("ATTENTION-PANE-CONTENT"))
383386 bars := d.barStateFor(sess, "")
384- area := domain.Rect{Width: tb.focusedPane().screen.Frame.Width, Height: tb.focusedPane().screen.Frame.Height}
385- state := capturedRenderState{reset: true, layout: capturedTabLayout{area: area, valid: true}, bars: bars}
387+ area := domain.Rect{Width: pane.screen.Frame.Width, Height: pane.screen.Frame.Height}
388+ placement := layout.Placement{ID: pane.id, Content: area}
389+ state := capturedRenderState{
390+ reset: true,
391+ layout: capturedTabLayout{area: area, focus: pane.id, placements: []layout.Placement{placement}, valid: true},
392+ panes: []capturedPaneRenderState{{
393+ id: pane.id, frame: pane.screen.Frame.Clone(), placement: placement, focused: true,
394+ damage: []renderer.Damage{renderer.FullRedraw()},
395+ }},
396+ bars: bars,
397+ }
386398 out := composeFrame(state, composeCacheInput{})
387399 require.Equal(t, []renderer.Damage{renderer.FullRedraw()}, out.damage)
388- tb.focusedPane().screen.ClearDamage()
400+ require.Len(t, state.panes, 1)
401+ require.Contains(t, rowText(out.frame.Row(1)), "ATTENTION-PANE-CONTENT")
402+ firstContent := rowText(out.frame.Row(1))
389403
404+ // The pane was included in the warmed cache; it has no new damage when the
405+ // attention pulse advances, so the subsequent compose must retain it.
390406 bars.attentionFrame++
391407 state.reset, state.bars = false, bars
408+ state.panes[0].damage = nil
392409 out = composeFrame(state, out.cache)
393410 damage := out.damage
411+ require.Len(t, state.panes, 1, "pane state must remain present when it has no damage")
412+ require.Empty(t, state.panes[0].damage)
413+ require.Equal(t, firstContent, rowText(out.frame.Row(1)), "unchanged pane content must remain in the composed frame")
414+ require.Contains(t, rowText(out.frame.Row(1)), "ATTENTION-PANE-CONTENT")
394415 require.NotEmpty(t, damage)
395- lastRow := tb.focusedPane().screen.Frame.Height + 1
416+ lastRow := pane.screen.Frame.Height + 1
396417 for _, dmg := range damage {
397418 require.Contains(t, []int{0, lastRow}, dmg.Y, "expected damage confined to bar rows, got y=%d", dmg.Y)
398419 }
@@ -14,6 +14,7 @@ import (
14 "github.com/bnema/vev/internal/ports"14 "github.com/bnema/vev/internal/ports"
15 portsmocks "github.com/bnema/vev/internal/ports/mocks"15 portsmocks "github.com/bnema/vev/internal/ports/mocks"
16 "github.com/bnema/vev/internal/usecase/keys"16 "github.com/bnema/vev/internal/usecase/keys"
17+ "github.com/bnema/vev/internal/usecase/layout"
17 "github.com/bnema/vev/internal/usecase/ui"18 "github.com/bnema/vev/internal/usecase/ui"
18 "github.com/bnema/vev/pkg/renderer"19 "github.com/bnema/vev/pkg/renderer"
19 )20 )
@@ -380,19 +381,39 @@ func TestComposeFrameAttentionFrameChangeDamagesOnlyBars(t *testing.T) {
380 sess.mu.Unlock()381 sess.mu.Unlock()
381 382
382 tb := sess.tabs[0]383 tb := sess.tabs[0]
384+ pane := tb.focusedPane()
385+ pane.screen.Write([]byte("ATTENTION-PANE-CONTENT"))
383 bars := d.barStateFor(sess, "")386 bars := d.barStateFor(sess, "")
384- area := domain.Rect{Width: tb.focusedPane().screen.Frame.Width, Height: tb.focusedPane().screen.Frame.Height}387+ area := domain.Rect{Width: pane.screen.Frame.Width, Height: pane.screen.Frame.Height}
385- state := capturedRenderState{reset: true, layout: capturedTabLayout{area: area, valid: true}, bars: bars}388+ placement := layout.Placement{ID: pane.id, Content: area}
389+ state := capturedRenderState{
390+ reset: true,
391+ layout: capturedTabLayout{area: area, focus: pane.id, placements: []layout.Placement{placement}, valid: true},
392+ panes: []capturedPaneRenderState{{
393+ id: pane.id, frame: pane.screen.Frame.Clone(), placement: placement, focused: true,
394+ damage: []renderer.Damage{renderer.FullRedraw()},
395+ }},
396+ bars: bars,
397+ }
386 out := composeFrame(state, composeCacheInput{})398 out := composeFrame(state, composeCacheInput{})
387 require.Equal(t, []renderer.Damage{renderer.FullRedraw()}, out.damage)399 require.Equal(t, []renderer.Damage{renderer.FullRedraw()}, out.damage)
388- tb.focusedPane().screen.ClearDamage()400+ require.Len(t, state.panes, 1)
401+ require.Contains(t, rowText(out.frame.Row(1)), "ATTENTION-PANE-CONTENT")
402+ firstContent := rowText(out.frame.Row(1))
389 403
404+ // The pane was included in the warmed cache; it has no new damage when the
405+ // attention pulse advances, so the subsequent compose must retain it.
390 bars.attentionFrame++406 bars.attentionFrame++
391 state.reset, state.bars = false, bars407 state.reset, state.bars = false, bars
408+ state.panes[0].damage = nil
392 out = composeFrame(state, out.cache)409 out = composeFrame(state, out.cache)
393 damage := out.damage410 damage := out.damage
411+ require.Len(t, state.panes, 1, "pane state must remain present when it has no damage")
412+ require.Empty(t, state.panes[0].damage)
413+ require.Equal(t, firstContent, rowText(out.frame.Row(1)), "unchanged pane content must remain in the composed frame")
414+ require.Contains(t, rowText(out.frame.Row(1)), "ATTENTION-PANE-CONTENT")
394 require.NotEmpty(t, damage)415 require.NotEmpty(t, damage)
395- lastRow := tb.focusedPane().screen.Frame.Height + 1416+ lastRow := pane.screen.Frame.Height + 1
396 for _, dmg := range damage {417 for _, dmg := range damage {
397 require.Contains(t, []int{0, lastRow}, dmg.Y, "expected damage confined to bar rows, got y=%d", dmg.Y)418 require.Contains(t, []int{0, lastRow}, dmg.Y, "expected damage confined to bar rows, got y=%d", dmg.Y)
398 }419 }

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