Skip to content

Commit fd3cd87

Browse files
authored
feat: Add DEC mode 2026 support (Synchronized Output) to Jest (#15008)
This commit introduces support for DEC private mode 2026, also known as Synchronized Output, to DefaultReporter. The Synchronized Output mode is a terminal feature that helps mitigate screen tearing effects that can occur when the terminal is rendering output while the application is still writing to the screen. Two new methods have been added to the DefaultReporter: - `__beginSynchronizedUpdate`: This method sends the control sequence to enable Synchronized Output mode to the terminal. - `__endSynchronizedUpdate`: This method sends the control sequence to disable Synchronized Output mode to the terminal. These methods are called before and after the reporter updates the status, respectively. By doing this, we ensure that the terminal renders a consistent state of the screen for each status update, even if we're writing to the screen frequently. Read more: https://gist.github.com/christianparpart/d8a62cc1ab659194337d73e399004036
1 parent 0e2145b commit fd3cd87

File tree

3 files changed

+29
-9
lines changed

3 files changed

+29
-9
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
- `[@jest/fake-timers]` Exposing new modern timers function `advanceTimersToFrame()` which advances all timers by the needed milliseconds to execute callbacks currently scheduled with `requestAnimationFrame` ([#14598](https://github.com/jestjs/jest/pull/14598))
2323
- `[jest-matcher-utils]` Add `SERIALIZABLE_PROPERTIES` to allow custom serialization of objects ([#14893](https://github.com/jestjs/jest/pull/14893))
2424
- `[jest-mock]` Add support for the Explicit Resource Management proposal to use the `using` keyword with `jest.spyOn(object, methodName)` ([#14895](https://github.com/jestjs/jest/pull/14895))
25+
- `[jest-reporters]` Add support for [DEC mode 2026](https://gist.github.com/christianparpart/d8a62cc1ab659194337d73e399004036)
2526
- `[jest-runtime]` Exposing new modern timers function `jest.advanceTimersToFrame()` from `@jest/fake-timers` ([#14598](https://github.com/jestjs/jest/pull/14598))
2627
- `[jest-runtime]` Support `import.meta.filename` and `import.meta.dirname` (available from [Node 20.11](https://nodejs.org/en/blog/release/v20.11.0)) ([#14854](https://github.com/jestjs/jest/pull/14854))
2728
- `[jest-runtime]` Support `import.meta.resolve` ([#14930](https://github.com/jestjs/jest/pull/14930))

packages/jest-reporters/src/BaseReporter.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,15 @@
55
* LICENSE file in the root directory of this source tree.
66
*/
77

8+
import type {WriteStream} from 'tty';
89
import type {
910
AggregatedResult,
1011
Test,
1112
TestCaseResult,
1213
TestContext,
1314
TestResult,
1415
} from '@jest/test-result';
15-
import {preRunMessage} from 'jest-util';
16+
import {isInteractive, preRunMessage} from 'jest-util';
1617
import type {Reporter, ReporterOnStartOptions} from './types';
1718

1819
const {remove: preRunMessageRemove} = preRunMessage;
@@ -57,4 +58,16 @@ export default class BaseReporter implements Reporter {
5758
getLastError(): Error | undefined {
5859
return this._error;
5960
}
61+
62+
protected __beginSynchronizedUpdate(write: WriteStream['write']): void {
63+
if (isInteractive) {
64+
write('\u001B[?2026h');
65+
}
66+
}
67+
68+
protected __endSynchronizedUpdate(write: WriteStream['write']): void {
69+
if (isInteractive) {
70+
write('\u001B[?2026l');
71+
}
72+
}
6073
}

packages/jest-reporters/src/DefaultReporter.ts

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,14 @@ export default class DefaultReporter extends BaseReporter {
5353
this.__wrapStdio(process.stdout);
5454
this.__wrapStdio(process.stderr);
5555
this._status.onChange(() => {
56+
this.__beginSynchronizedUpdate(
57+
this._globalConfig.useStderr ? this._err : this._out,
58+
);
5659
this.__clearStatus();
5760
this.__printStatus();
61+
this.__endSynchronizedUpdate(
62+
this._globalConfig.useStderr ? this._err : this._out,
63+
);
5864
});
5965
}
6066

@@ -69,11 +75,17 @@ export default class DefaultReporter extends BaseReporter {
6975
buffer = [];
7076

7177
// This is to avoid conflicts between random output and status text
78+
this.__beginSynchronizedUpdate(
79+
this._globalConfig.useStderr ? this._err : this._out,
80+
);
7281
this.__clearStatus();
7382
if (string) {
7483
write(string);
7584
}
7685
this.__printStatus();
86+
this.__endSynchronizedUpdate(
87+
this._globalConfig.useStderr ? this._err : this._out,
88+
);
7789

7890
this._bufferedOutput.delete(flushBufferedOutput);
7991
};
@@ -194,9 +206,7 @@ export default class DefaultReporter extends BaseReporter {
194206
const testRetryReasons = testResult.retryReasons;
195207
if (testRetryReasons && testRetryReasons.length > 0) {
196208
this.log(
197-
`${chalk.reset.inverse.bold.yellow(
198-
' LOGGING RETRY ERRORS ',
199-
)} ${chalk.bold(testResult.fullName)}`,
209+
`${chalk.reset.inverse.bold.yellow(' LOGGING RETRY ERRORS ')} ${chalk.bold(testResult.fullName)}`,
200210
);
201211
for (const [index, retryReasons] of testRetryReasons.entries()) {
202212
let {message, stack} = separateMessageFromStack(retryReasons);
@@ -219,11 +229,7 @@ export default class DefaultReporter extends BaseReporter {
219229
this.log(getResultHeader(result, this._globalConfig, config));
220230
if (result.console) {
221231
this.log(
222-
` ${TITLE_BULLET}Console\n\n${getConsoleOutput(
223-
result.console,
224-
config,
225-
this._globalConfig,
226-
)}`,
232+
` ${TITLE_BULLET}Console\n\n${getConsoleOutput(result.console, config, this._globalConfig)}`,
227233
);
228234
}
229235
}

0 commit comments

Comments
 (0)