detached d8754cd

test(render): cover local alternate-screen cleanup

commit
d8754cdebc736af30d9755510169f617a3616d24
author
brice · · 2026-07-13 06:21 UTC
parent
588d04f
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
1 file · +21 -10
M internal/usecase/daemon/render_test.go +21 -10
@@ -257,21 +257,23 @@ func TestPTYReaderSyncVisibilityTransitions(t *testing.T) {
257257 func TestPTYReaderRestoresPrimaryScreenImmediatelyAfterDEC1049Exit(t *testing.T) {
258258 for _, tc := range []struct {
259259 name string
260+ window uint64
260261 exitBeforeACK bool
261262 exitSuffix string
262263 overlay bool
263264 }{
264- {name: "exit waits for local ACK"},
265- {name: "exit arrives before local ACK", exitBeforeACK: true},
266- {name: "prompt bytes follow exit before local ACK", exitBeforeACK: true, exitSuffix: "PROMPT> "},
267- {name: "palette overlay with bars", overlay: true},
265+ {name: "datagram output window (1): exit waits for ACK", window: 1},
266+ {name: "datagram output window (1): exit and prompt arrive before ACK", window: 1, exitBeforeACK: true, exitSuffix: "PROMPT> "},
267+ {name: "local default output window (8): exit waits for ACK", window: maxUnackedOutputStates},
268+ {name: "local default output window (8): exit and prompt arrive before ACK", window: maxUnackedOutputStates, exitBeforeACK: true, exitSuffix: "PROMPT> "},
269+ {name: "local default output window (8): palette overlay with bars", window: maxUnackedOutputStates, overlay: true},
268270 } {
269271 t.Run(tc.name, func(t *testing.T) {
270272 pty, steps, processed := newChannelPTY(t)
271273 d, sess, ac, sends := newManualSessionWithPTYs(t, pty)
272- // A one-frame local output window forces the replay to exercise both
273- // ACK orderings without relying on elapsed time.
274- ac.output.maxOutstanding = 1
274+ // Datagram clients use a one-frame window; local clients retain the
275+ // default eight-frame window. Both ACK orderings stay deterministic.
276+ ac.output.maxOutstanding = tc.window
275277 clock := newCoordinatorMockClock(t, 8)
276278 d.clock = clock.clock
277279 rc := d.attachCoordinator(sess, nil, ac, true)
@@ -315,19 +317,28 @@ func TestPTYReaderRestoresPrimaryScreenImmediatelyAfterDEC1049Exit(t *testing.T)
315317 pane.mu.Unlock()
316318
317319 exitData := []byte("\x1b[?1049l" + tc.exitSuffix)
320+ var exit ports.Output
318321 if tc.exitBeforeACK {
319322 steps <- channelPTYStep{data: exitData}
320323 awaitPTYReadProcessed(t, processed)
321324 fireCoordinatorTimer(t, rc, drainCoordinatorTimers(clock), minOutputRenderDeadline)
322- requireNoCoordinatorOutputFrame(t, sends)
323- acknowledge(alternate)
325+ if tc.window == 1 {
326+ requireNoCoordinatorOutputFrame(t, sends)
327+ acknowledge(alternate)
328+ exit = mustApplyOutput(t, client, awaitOutputFrameWithoutSleep(t, sends))
329+ } else {
330+ exit = mustApplyOutput(t, client, awaitOutputFrameWithoutSleep(t, sends))
331+ require.Equal(t, alternate.NewStateNum, exit.BaseStateNum,
332+ "local exit must be rendered before the alternate-frame ACK")
333+ acknowledge(alternate)
334+ }
324335 } else {
325336 acknowledge(alternate)
326337 steps <- channelPTYStep{data: exitData}
327338 awaitPTYReadProcessed(t, processed)
328339 fireCoordinatorTimer(t, rc, drainCoordinatorTimers(clock), minOutputRenderDeadline)
340+ exit = mustApplyOutput(t, client, awaitOutputFrameWithoutSleep(t, sends))
329341 }
330- exit := mustApplyOutput(t, client, awaitOutputFrameWithoutSleep(t, sends))
331342 require.NotEmpty(t, exit.Data, "the first post-exit output must actively remove alternate cells")
332343 rendered := strings.Join(frameRows(client.Frame), "\n")
333344 require.NotContains(t, rendered, "MATRIX", "the first post-exit frame must not retain alternate cells")
@@ -257,21 +257,23 @@ func TestPTYReaderSyncVisibilityTransitions(t *testing.T) {
257 func TestPTYReaderRestoresPrimaryScreenImmediatelyAfterDEC1049Exit(t *testing.T) {257 func TestPTYReaderRestoresPrimaryScreenImmediatelyAfterDEC1049Exit(t *testing.T) {
258 for _, tc := range []struct {258 for _, tc := range []struct {
259 name string259 name string
260+ window uint64
260 exitBeforeACK bool261 exitBeforeACK bool
261 exitSuffix string262 exitSuffix string
262 overlay bool263 overlay bool
263 }{264 }{
264- {name: "exit waits for local ACK"},265+ {name: "datagram output window (1): exit waits for ACK", window: 1},
265- {name: "exit arrives before local ACK", exitBeforeACK: true},266+ {name: "datagram output window (1): exit and prompt arrive before ACK", window: 1, exitBeforeACK: true, exitSuffix: "PROMPT> "},
266- {name: "prompt bytes follow exit before local ACK", exitBeforeACK: true, exitSuffix: "PROMPT> "},267+ {name: "local default output window (8): exit waits for ACK", window: maxUnackedOutputStates},
267- {name: "palette overlay with bars", overlay: true},268+ {name: "local default output window (8): exit and prompt arrive before ACK", window: maxUnackedOutputStates, exitBeforeACK: true, exitSuffix: "PROMPT> "},
269+ {name: "local default output window (8): palette overlay with bars", window: maxUnackedOutputStates, overlay: true},
268 } {270 } {
269 t.Run(tc.name, func(t *testing.T) {271 t.Run(tc.name, func(t *testing.T) {
270 pty, steps, processed := newChannelPTY(t)272 pty, steps, processed := newChannelPTY(t)
271 d, sess, ac, sends := newManualSessionWithPTYs(t, pty)273 d, sess, ac, sends := newManualSessionWithPTYs(t, pty)
272- // A one-frame local output window forces the replay to exercise both274+ // Datagram clients use a one-frame window; local clients retain the
273- // ACK orderings without relying on elapsed time.275+ // default eight-frame window. Both ACK orderings stay deterministic.
274- ac.output.maxOutstanding = 1276+ ac.output.maxOutstanding = tc.window
275 clock := newCoordinatorMockClock(t, 8)277 clock := newCoordinatorMockClock(t, 8)
276 d.clock = clock.clock278 d.clock = clock.clock
277 rc := d.attachCoordinator(sess, nil, ac, true)279 rc := d.attachCoordinator(sess, nil, ac, true)
@@ -315,19 +317,28 @@ func TestPTYReaderRestoresPrimaryScreenImmediatelyAfterDEC1049Exit(t *testing.T)
315 pane.mu.Unlock()317 pane.mu.Unlock()
316 318
317 exitData := []byte("\x1b[?1049l" + tc.exitSuffix)319 exitData := []byte("\x1b[?1049l" + tc.exitSuffix)
320+ var exit ports.Output
318 if tc.exitBeforeACK {321 if tc.exitBeforeACK {
319 steps <- channelPTYStep{data: exitData}322 steps <- channelPTYStep{data: exitData}
320 awaitPTYReadProcessed(t, processed)323 awaitPTYReadProcessed(t, processed)
321 fireCoordinatorTimer(t, rc, drainCoordinatorTimers(clock), minOutputRenderDeadline)324 fireCoordinatorTimer(t, rc, drainCoordinatorTimers(clock), minOutputRenderDeadline)
322- requireNoCoordinatorOutputFrame(t, sends)325+ if tc.window == 1 {
323- acknowledge(alternate)326+ requireNoCoordinatorOutputFrame(t, sends)
327+ acknowledge(alternate)
328+ exit = mustApplyOutput(t, client, awaitOutputFrameWithoutSleep(t, sends))
329+ } else {
330+ exit = mustApplyOutput(t, client, awaitOutputFrameWithoutSleep(t, sends))
331+ require.Equal(t, alternate.NewStateNum, exit.BaseStateNum,
332+ "local exit must be rendered before the alternate-frame ACK")
333+ acknowledge(alternate)
334+ }
324 } else {335 } else {
325 acknowledge(alternate)336 acknowledge(alternate)
326 steps <- channelPTYStep{data: exitData}337 steps <- channelPTYStep{data: exitData}
327 awaitPTYReadProcessed(t, processed)338 awaitPTYReadProcessed(t, processed)
328 fireCoordinatorTimer(t, rc, drainCoordinatorTimers(clock), minOutputRenderDeadline)339 fireCoordinatorTimer(t, rc, drainCoordinatorTimers(clock), minOutputRenderDeadline)
340+ exit = mustApplyOutput(t, client, awaitOutputFrameWithoutSleep(t, sends))
329 }341 }
330- exit := mustApplyOutput(t, client, awaitOutputFrameWithoutSleep(t, sends))
331 require.NotEmpty(t, exit.Data, "the first post-exit output must actively remove alternate cells")342 require.NotEmpty(t, exit.Data, "the first post-exit output must actively remove alternate cells")
332 rendered := strings.Join(frameRows(client.Frame), "\n")343 rendered := strings.Join(frameRows(client.Frame), "\n")
333 require.NotContains(t, rendered, "MATRIX", "the first post-exit frame must not retain alternate cells")344 require.NotContains(t, rendered, "MATRIX", "the first post-exit frame must not retain alternate cells")

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