test(daemon): cover sync visibility transitions
- commit
- eafd950e00a1d535b893d8643726e75abe2566ac
- author
- brice · · 2026-07-11 19:12 UTC
- parent
- c6654e6
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· 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) { | |||
| 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 | 573 | func TestRenderCoordinatorSynchronizedOutput(t *testing.T) { | |
| 536 | 574 | t.Run("completion flushes pending state in one wake", func(t *testing.T) { | |
| 537 | 575 | 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) | ||