Skip to content

Commit 1b2d32f

Browse files
test_runner: add mock-timers support for AbortSignal.timeout
1 parent 3330e5c commit 1b2d32f

File tree

2 files changed

+89
-8
lines changed

2 files changed

+89
-8
lines changed

lib/internal/test_runner/mock/mock_timers.js

Lines changed: 55 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ const {
3434
} = require('internal/errors');
3535

3636
const { addAbortListener } = require('internal/events/abort_listener');
37+
const { AbortController, AbortSignal } = require('internal/abort_controller');
3738

3839
const { TIMEOUT_MAX } = require('internal/timers');
3940

@@ -59,10 +60,14 @@ function abortIt(signal) {
5960
return new AbortError(undefined, { __proto__: null, cause: signal.reason });
6061
}
6162

62-
/**
63-
* @enum {('setTimeout'|'setInterval'|'setImmediate'|'Date'|'scheduler.wait')[]} Supported timers
64-
*/
65-
const SUPPORTED_APIS = ['setTimeout', 'setInterval', 'setImmediate', 'Date', 'scheduler.wait'];
63+
const SUPPORTED_APIS = [
64+
'setTimeout',
65+
'setInterval',
66+
'setImmediate',
67+
'Date',
68+
'scheduler.wait',
69+
'AbortSignal.timeout',
70+
];
6671
const TIMERS_DEFAULT_INTERVAL = {
6772
__proto__: null,
6873
setImmediate: -1,
@@ -115,6 +120,7 @@ class MockTimers {
115120
#realPromisifiedSetImmediate;
116121

117122
#nativeDateDescriptor;
123+
#realAbortSignalTimeout;
118124

119125
#timersInContext = [];
120126
#isEnabled = false;
@@ -297,6 +303,18 @@ class MockTimers {
297303
);
298304
}
299305

306+
#storeOriginalAbortSignalTimeout() {
307+
this.#realAbortSignalTimeout = ObjectGetOwnPropertyDescriptor(AbortSignal, 'timeout');
308+
}
309+
310+
#restoreOriginalAbortSignalTimeout() {
311+
if (this.#realAbortSignalTimeout) {
312+
ObjectDefineProperty(AbortSignal, 'timeout', this.#realAbortSignalTimeout);
313+
} else {
314+
delete AbortSignal.timeout;
315+
}
316+
}
317+
300318
#createTimer(isInterval, callback, delay, ...args) {
301319
if (delay > TIMEOUT_MAX) {
302320
delay = 1;
@@ -604,6 +622,29 @@ class MockTimers {
604622
this.#nativeDateDescriptor = ObjectGetOwnPropertyDescriptor(globalThis, 'Date');
605623
globalThis.Date = this.#createDate();
606624
},
625+
'AbortSignal.timeout': () => {
626+
this.#storeOriginalAbortSignalTimeout();
627+
const mock = this;
628+
ObjectDefineProperty(AbortSignal, 'timeout', {
629+
__proto__: null,
630+
configurable: true,
631+
writable: true,
632+
value: function value(delay) {
633+
if (NumberIsNaN(delay)) {
634+
throw new ERR_INVALID_ARG_VALUE('delay', delay, 'delay must be a number');
635+
}
636+
const controller = new AbortController();
637+
// don't keep an unused binding to the timer; mock tick controls it
638+
mock.#setTimeout(
639+
() => {
640+
controller.abort();
641+
},
642+
delay,
643+
);
644+
return controller.signal;
645+
},
646+
});
647+
},
607648
},
608649
toReal: {
609650
'__proto__': null,
@@ -622,6 +663,9 @@ class MockTimers {
622663
'Date': () => {
623664
ObjectDefineProperty(globalThis, 'Date', this.#nativeDateDescriptor);
624665
},
666+
'AbortSignal.timeout': () => {
667+
this.#restoreOriginalAbortSignalTimeout();
668+
},
625669
},
626670
};
627671

@@ -664,10 +708,13 @@ class MockTimers {
664708
}
665709

666710
/**
667-
* @typedef {{apis: SUPPORTED_APIS;now: number | Date;}} EnableOptions Options to enable the timers
668-
* @property {SUPPORTED_APIS} apis List of timers to enable, defaults to all
669-
* @property {number | Date} now The epoch to which the timers should be set to, defaults to 0
711+
* EnableOptions type:
712+
*
713+
* @typedef {object} EnableOptions
714+
* @property {Array<string>} apis List of timers to enable, defaults to all
715+
* @property {number|Date} now The epoch to which the timers should be set, defaults to 0
670716
*/
717+
671718
/**
672719
* Enables the MockTimers replacing the native timers with the fake ones.
673720
* @param {EnableOptions} [options]
@@ -686,8 +733,8 @@ class MockTimers {
686733

687734
internalOptions.apis ||= SUPPORTED_APIS;
688735

689-
validateStringArray(internalOptions.apis, 'options.apis');
690736
// Check that the timers passed are supported
737+
validateStringArray(internalOptions.apis, 'options.apis');
691738
ArrayPrototypeForEach(internalOptions.apis, (timer) => {
692739
if (!ArrayPrototypeIncludes(SUPPORTED_APIS, timer)) {
693740
throw new ERR_INVALID_ARG_VALUE(
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const assert = require('assert');
5+
const { MockTimers } = require('internal/test_runner/mock/mock_timers');
6+
const { AbortSignal } = require('internal/abort_controller');
7+
8+
{
9+
const mock = new MockTimers();
10+
mock.enable({ apis: ['AbortSignal.timeout'] });
11+
12+
try {
13+
const signal = AbortSignal.timeout(50);
14+
15+
assert.strictEqual(
16+
signal.aborted,
17+
false,
18+
);
19+
20+
mock.tick(49);
21+
assert.strictEqual(
22+
signal.aborted,
23+
false,
24+
);
25+
26+
mock.tick(1);
27+
assert.strictEqual(
28+
signal.aborted,
29+
true,
30+
);
31+
} finally {
32+
mock.reset();
33+
}
34+
}

0 commit comments

Comments
 (0)