Skip to content

Commit ef06137

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

File tree

2 files changed

+81
-6
lines changed

2 files changed

+81
-6
lines changed

lib/internal/test_runner/mock/mock_timers.js

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ const {
2323
validateAbortSignal,
2424
validateNumber,
2525
validateStringArray,
26+
validateUint32,
2627
} = require('internal/validators');
2728

2829
const {
@@ -34,6 +35,7 @@ const {
3435
} = require('internal/errors');
3536

3637
const { addAbortListener } = require('internal/events/abort_listener');
38+
const { AbortController, AbortSignal } = require('internal/abort_controller');
3739

3840
const { TIMEOUT_MAX } = require('internal/timers');
3941

@@ -60,9 +62,17 @@ function abortIt(signal) {
6062
}
6163

6264
/**
63-
* @enum {('setTimeout'|'setInterval'|'setImmediate'|'Date'|'scheduler.wait')[]} Supported timers
65+
* @typedef {('setTimeout'|'setInterval'|'setImmediate'|'Date'|'scheduler.wait'|'AbortSignal.timeout')[]} SupportedApis
66+
* Supported timers that can be enabled via MockTimers.enable({ apis: [...] })
6467
*/
65-
const SUPPORTED_APIS = ['setTimeout', 'setInterval', 'setImmediate', 'Date', 'scheduler.wait'];
68+
const SUPPORTED_APIS = [
69+
'setTimeout',
70+
'setInterval',
71+
'setImmediate',
72+
'Date',
73+
'scheduler.wait',
74+
'AbortSignal.timeout',
75+
];
6676
const TIMERS_DEFAULT_INTERVAL = {
6777
__proto__: null,
6878
setImmediate: -1,
@@ -115,6 +125,7 @@ class MockTimers {
115125
#realPromisifiedSetImmediate;
116126

117127
#nativeDateDescriptor;
128+
#realAbortSignalTimeout;
118129

119130
#timersInContext = [];
120131
#isEnabled = false;
@@ -297,6 +308,18 @@ class MockTimers {
297308
);
298309
}
299310

311+
#storeOriginalAbortSignalTimeout() {
312+
this.#realAbortSignalTimeout = ObjectGetOwnPropertyDescriptor(AbortSignal, 'timeout');
313+
}
314+
315+
#restoreOriginalAbortSignalTimeout() {
316+
if (this.#realAbortSignalTimeout) {
317+
ObjectDefineProperty(AbortSignal, 'timeout', this.#realAbortSignalTimeout);
318+
} else {
319+
delete AbortSignal.timeout;
320+
}
321+
}
322+
300323
#createTimer(isInterval, callback, delay, ...args) {
301324
if (delay > TIMEOUT_MAX) {
302325
delay = 1;
@@ -604,6 +627,27 @@ class MockTimers {
604627
this.#nativeDateDescriptor = ObjectGetOwnPropertyDescriptor(globalThis, 'Date');
605628
globalThis.Date = this.#createDate();
606629
},
630+
'AbortSignal.timeout': () => {
631+
this.#storeOriginalAbortSignalTimeout();
632+
const mock = this;
633+
ObjectDefineProperty(AbortSignal, 'timeout', {
634+
__proto__: null,
635+
configurable: true,
636+
writable: true,
637+
value: function value(delay) {
638+
validateUint32(delay, 'delay', false);
639+
const controller = new AbortController();
640+
// Don't keep an unused binding to the timer; mock tick controls it
641+
mock.#setTimeout(
642+
() => {
643+
controller.abort();
644+
},
645+
delay,
646+
);
647+
return controller.signal;
648+
},
649+
});
650+
},
607651
},
608652
toReal: {
609653
'__proto__': null,
@@ -622,6 +666,9 @@ class MockTimers {
622666
'Date': () => {
623667
ObjectDefineProperty(globalThis, 'Date', this.#nativeDateDescriptor);
624668
},
669+
'AbortSignal.timeout': () => {
670+
this.#restoreOriginalAbortSignalTimeout();
671+
},
625672
},
626673
};
627674

@@ -664,10 +711,12 @@ class MockTimers {
664711
}
665712

666713
/**
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
714+
* EnableOptions type:
715+
* @typedef {object} EnableOptions
716+
* @property {Array<string>} apis List of timers to enable, defaults to all
717+
* @property {number|Date} now The epoch to which the timers should be set, defaults to 0
670718
*/
719+
671720
/**
672721
* Enables the MockTimers replacing the native timers with the fake ones.
673722
* @param {EnableOptions} [options]
@@ -686,8 +735,8 @@ class MockTimers {
686735

687736
internalOptions.apis ||= SUPPORTED_APIS;
688737

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

0 commit comments

Comments
 (0)