Public: anyone can read this; the crew can change it.
test/tui-size.test.ts waits for the frame a resize draws, not a timer, and passes on a loaded check machine #3018
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/3008-test-tui-size-test-ts-waits-for-the-fram → main 1 commit fork 1 thread · 1 unresolved
1 file
changed +44
−4
test/tui-size.test.ts +44 −4
| @@ -55,6 +55,9 @@ const SIZES: readonly [number, number][] = [ | ||
| 55 | 55 | [200, 60], |
| 56 | 56 | ]; |
| 57 | 57 | |
| 58 | /** How long a harness step waits for the frame it caused. */ | |
| 59 | const FRAME_WAIT_MS = 10_000; | |
| 60 | ||
| 58 | 61 | interface Harness { |
| 59 | 62 | app: App; |
| 60 | 63 | session: Session; |
| @@ -81,13 +84,48 @@ describe("the terminal lays out for the terminal it is in", () => { | ||
| 81 | 84 | ); |
| 82 | 85 | app.start(); |
| 83 | 86 | await settleApp(app); |
| 84 | const settle = (): Promise<void> => settleApp(app); | |
| 87 | /** | |
| 88 | * Settle, then wait for a frame drawn after `since` at the wanted size. | |
| 89 | * Bounded: it fails naming the size it was waiting for, not whatever | |
| 90 | * assertion follows a half-drawn screen. | |
| 91 | */ | |
| 92 | const framed = async ( | |
| 93 | since: number, | |
| 94 | want: { cols: number; rows: number }, | |
| 95 | ): Promise<void> => { | |
| 96 | const deadline = Date.now() + FRAME_WAIT_MS; | |
| 97 | await settleApp(app); | |
| 98 | for (;;) { | |
| 99 | app.frame(); | |
| 100 | const { cols, rows } = app.screen; | |
| 101 | if ( | |
| 102 | app.frameCount > since && | |
| 103 | cols === want.cols && | |
| 104 | rows === want.rows && | |
| 105 | terminal.toText() === app.screen.toText() | |
| 106 | ) | |
| 107 | return; | |
| 108 | if (Date.now() > deadline) | |
| 109 | throw new Error( | |
| 110 | `no frame at ${want.cols}×${want.rows} within ${FRAME_WAIT_MS}ms ` + | |
| 111 | `(have ${cols}×${rows}, frames ${since}→${app.frameCount})\n${app.screen.toText()}`, | |
| 112 | ); | |
| 113 | await settleApp(app); | |
| 114 | } | |
| 115 | }; | |
| 116 | const here = (): { cols: number; rows: number } => ({ | |
| 117 | cols: app.screen.cols, | |
| 118 | rows: app.screen.rows, | |
| 119 | }); | |
| 85 | 120 | return { |
| 86 | 121 | app, |
| 87 | 122 | session, |
| 88 | 123 | terminal, |
| 89 | 124 | lines: () => app.screen.toText().split("\n"), |
| 90 | 125 | async press(...keys) { |
| 126 | // A key that changes nothing draws nothing, so a press waits for | |
| 127 | // the app to settle and the grid to agree, not for a new frame. | |
| 128 | const since = -1; | |
| 91 | 129 | for (const key of keys) |
| 92 | 130 | app.dispatch({ |
| 93 | 131 | type: "key", |
| @@ -96,15 +134,17 @@ describe("the terminal lays out for the terminal it is in", () => { | ||
| 96 | 134 | alt: false, |
| 97 | 135 | shift: false, |
| 98 | 136 | }); |
| 99 | await settle(); | |
| 137 | await framed(since, here()); | |
| 100 | 138 | }, |
| 101 | 139 | async resize(c, r) { |
| 140 | const since = app.frameCount; | |
| 102 | 141 | terminal.resize(c, r); |
| 103 | await settle(); | |
| 142 | await framed(since, { cols: c, rows: r }); | |
| 104 | 143 | }, |
| 105 | 144 | async push(screen) { |
| 145 | const since = app.frameCount; | |
| 106 | 146 | app.push(screen(session, app.ctx)); |
| 107 | await settle(); | |
| 147 | await framed(since, here()); | |
| 108 | 148 | }, |
| 109 | 149 | stop: () => app.stop(), |
| 110 | 150 | }; |
new above
offline
since = -1makesframeCount > sincevacuously true. Makesinceoptional in framed() instead of passing a sentinel.