detached eafd950

test(daemon): cover sync visibility transitions

commit
eafd950e00a1d535b893d8643726e75abe2566ac
author
brice · · 2026-07-11 19:12 UTC
parent
c6654e6
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
1 file · +38 -0
M internal/usecase/daemon/render_coordinator_test.go +38 -0
@@ -532,6 +532,44 @@ func TestRenderCoordinatorConcurrentSynchronizedPaneBatches(t *testing.T) {
532532 })
533533 }
534534
535+func TestRenderCoordinatorSyncGateFollowsDynamicRenderability(t *testing.T) {
536+ t.Run("inactive batch that becomes active gates activation output until end", func(t *testing.T) {
537+ h := newCoordinatorHarness(t)
538+ var visible atomic.Bool
539+ p := &pane{}
540+ h.rc.noteSyncBeginWithRenderability(p, 1, visible.Load)
541+ visible.Store(true)
542+ h.rc.invalidate(renderInvalidation{class: invalidateUrgent, producer: "activation"})
543+ for _, timer := range h.armedTimers(t) {
544+ timer.ch <- time.Time{}
545+ }
546+ requireNoWake(t, h.wakes)
547+
548+ h.rc.noteSyncEnd(p, 1)
549+ w := awaitWake(t, h.wakes)
550+ require.True(t, w.urgent)
551+ requireNoWake(t, h.wakes)
552+ })
553+
554+ t.Run("hidden batch does not stall newly active output or wake on late end", func(t *testing.T) {
555+ h := newCoordinatorHarness(t)
556+ var visible atomic.Bool
557+ visible.Store(true)
558+ p := &pane{}
559+ h.rc.noteSyncBeginWithRenderability(p, 1, visible.Load)
560+ h.rc.invalidate(renderInvalidation{class: invalidateOutput, producer: "batch"})
561+ visible.Store(false)
562+ h.rc.invalidate(renderInvalidation{class: invalidateUrgent, producer: "new-active-tab"})
563+ timers := h.armedTimers(t)
564+ require.NotEmpty(t, timers)
565+ timers[len(timers)-1].ch <- time.Time{}
566+ w := awaitWake(t, h.wakes)
567+ require.False(t, w.watchdog)
568+ h.rc.noteSyncEnd(p, 1)
569+ requireNoWake(t, h.wakes)
570+ })
571+}
572+
535573 func TestRenderCoordinatorSynchronizedOutput(t *testing.T) {
536574 t.Run("completion flushes pending state in one wake", func(t *testing.T) {
537575 h := newCoordinatorHarness(t)
@@ -532,6 +532,44 @@ func TestRenderCoordinatorConcurrentSynchronizedPaneBatches(t *testing.T) {
532 })532 })
533 }533 }
534 534
535+func TestRenderCoordinatorSyncGateFollowsDynamicRenderability(t *testing.T) {
536+ t.Run("inactive batch that becomes active gates activation output until end", func(t *testing.T) {
537+ h := newCoordinatorHarness(t)
538+ var visible atomic.Bool
539+ p := &pane{}
540+ h.rc.noteSyncBeginWithRenderability(p, 1, visible.Load)
541+ visible.Store(true)
542+ h.rc.invalidate(renderInvalidation{class: invalidateUrgent, producer: "activation"})
543+ for _, timer := range h.armedTimers(t) {
544+ timer.ch <- time.Time{}
545+ }
546+ requireNoWake(t, h.wakes)
547+
548+ h.rc.noteSyncEnd(p, 1)
549+ w := awaitWake(t, h.wakes)
550+ require.True(t, w.urgent)
551+ requireNoWake(t, h.wakes)
552+ })
553+
554+ t.Run("hidden batch does not stall newly active output or wake on late end", func(t *testing.T) {
555+ h := newCoordinatorHarness(t)
556+ var visible atomic.Bool
557+ visible.Store(true)
558+ p := &pane{}
559+ h.rc.noteSyncBeginWithRenderability(p, 1, visible.Load)
560+ h.rc.invalidate(renderInvalidation{class: invalidateOutput, producer: "batch"})
561+ visible.Store(false)
562+ h.rc.invalidate(renderInvalidation{class: invalidateUrgent, producer: "new-active-tab"})
563+ timers := h.armedTimers(t)
564+ require.NotEmpty(t, timers)
565+ timers[len(timers)-1].ch <- time.Time{}
566+ w := awaitWake(t, h.wakes)
567+ require.False(t, w.watchdog)
568+ h.rc.noteSyncEnd(p, 1)
569+ requireNoWake(t, h.wakes)
570+ })
571+}
572+
535 func TestRenderCoordinatorSynchronizedOutput(t *testing.T) {573 func TestRenderCoordinatorSynchronizedOutput(t *testing.T) {
536 t.Run("completion flushes pending state in one wake", func(t *testing.T) {574 t.Run("completion flushes pending state in one wake", func(t *testing.T) {
537 h := newCoordinatorHarness(t)575 h := newCoordinatorHarness(t)

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