Skip to content

Commit c30dce3

Browse files
test: add tests for mock timers support for AbortSignal.timeout
1 parent 0694adb commit c30dce3

File tree

3 files changed

+185
-22
lines changed

3 files changed

+185
-22
lines changed

 true

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
doc/api/test.md:750: mock.timers.enable({ apis: ['setTimeout'] });
2+
doc/api/test.md:755: mock.timers.tick(9999);
3+
doc/api/test.md:759: mock.timers.reset();
4+
doc/api/test.md:774: mock.timers.enable({ apis: ['setTimeout'] });
5+
doc/api/test.md:779: mock.timers.tick(9999);
6+
doc/api/test.md:783: mock.timers.reset();
7+
doc/api/test.md:803: context.mock.timers.enable({ apis: ['setTimeout'] });
8+
doc/api/test.md:808: context.mock.timers.tick(9999);
9+
doc/api/test.md:821: context.mock.timers.enable({ apis: ['setTimeout'] });
10+
doc/api/test.md:826: context.mock.timers.tick(9999);
11+
doc/api/test.md:833:The mock timers API also allows the mocking of the `Date` object. This is a
12+
doc/api/test.md:853: context.mock.timers.enable({ apis: ['Date'] });
13+
doc/api/test.md:858: context.mock.timers.tick(9999);
14+
doc/api/test.md:869: context.mock.timers.enable({ apis: ['Date'] });
15+
doc/api/test.md:874: context.mock.timers.tick(9999);
16+
doc/api/test.md:891: context.mock.timers.enable({ apis: ['Date'], now: 100 });
17+
doc/api/test.md:895: context.mock.timers.tick(200);
18+
doc/api/test.md:906: context.mock.timers.enable({ apis: ['Date'], now: 100 });
19+
doc/api/test.md:910: context.mock.timers.tick(200);
20+
doc/api/test.md:929: context.mock.timers.enable({ apis: ['Date'], now: 100 });
21+
doc/api/test.md:933: context.mock.timers.setTime(1000);
22+
doc/api/test.md:934: context.mock.timers.tick(200);
23+
doc/api/test.md:945: context.mock.timers.enable({ apis: ['Date'], now: 100 });
24+
doc/api/test.md:949: context.mock.timers.setTime(1000);
25+
doc/api/test.md:950: context.mock.timers.tick(200);
26+
doc/api/test.md:965: context.mock.timers.enable({ apis: ['setTimeout', 'Date'] });
27+
doc/api/test.md:969: context.mock.timers.setTime(800);
28+
doc/api/test.md:974: context.mock.timers.setTime(1200);
29+
doc/api/test.md:987: context.mock.timers.enable({ apis: ['setTimeout', 'Date'] });
30+
doc/api/test.md:991: context.mock.timers.setTime(800);
31+
doc/api/test.md:996: context.mock.timers.setTime(1200);
32+
doc/api/test.md:1013: context.mock.timers.enable({ apis: ['setTimeout', 'Date'] });
33+
doc/api/test.md:1019: context.mock.timers.runAll();
34+
doc/api/test.md:1032: context.mock.timers.enable({ apis: ['setTimeout', 'Date'] });
35+
doc/api/test.md:1038: context.mock.timers.runAll();
36+
doc/api/test.md:2563:mock.timers.enable({ apis: ['setInterval'] });
37+
doc/api/test.md:2568:mock.timers.enable({ apis: ['setInterval'] });
38+
doc/api/test.md:2581:mock.timers.enable({ apis: ['Date'], now: 1000 });
39+
doc/api/test.md:2586:mock.timers.enable({ apis: ['Date'], now: 1000 });
40+
doc/api/test.md:2593:mock.timers.enable({ apis: ['Date'], now: new Date() });
41+
doc/api/test.md:2598:mock.timers.enable({ apis: ['Date'], now: new Date() });
42+
doc/api/test.md:2601:Alternatively, if you call `mock.timers.enable()` without any parameters:
43+
doc/api/test.md:2626:mock.timers.reset();
44+
doc/api/test.md:2631:mock.timers.reset();
45+
doc/api/test.md:2666: context.mock.timers.enable({ apis: ['setTimeout'] });
46+
doc/api/test.md:2673: context.mock.timers.tick(9999);
47+
doc/api/test.md:2685: context.mock.timers.enable({ apis: ['setTimeout'] });
48+
doc/api/test.md:2691: context.mock.timers.tick(9999);
49+
doc/api/test.md:2705: context.mock.timers.enable({ apis: ['setTimeout'] });
50+
doc/api/test.md:2710: context.mock.timers.tick(threeSeconds);
51+
doc/api/test.md:2711: context.mock.timers.tick(threeSeconds);
52+
doc/api/test.md:2712: context.mock.timers.tick(threeSeconds);
53+
doc/api/test.md:2724: context.mock.timers.enable({ apis: ['setTimeout'] });
54+
doc/api/test.md:2729: context.mock.timers.tick(threeSeconds);
55+
doc/api/test.md:2730: context.mock.timers.tick(threeSeconds);
56+
doc/api/test.md:2731: context.mock.timers.tick(threeSeconds);
57+
doc/api/test.md:2747: context.mock.timers.enable({ apis: ['setTimeout', 'Date'] });
58+
doc/api/test.md:2754: context.mock.timers.tick(9999);
59+
doc/api/test.md:2766: context.mock.timers.enable({ apis: ['setTimeout', 'Date'] });
60+
doc/api/test.md:2773: context.mock.timers.tick(9999);
61+
doc/api/test.md:2792: context.mock.timers.enable({ apis: ['setTimeout'] });
62+
doc/api/test.md:2797: context.mock.timers.tick(9999);
63+
doc/api/test.md:2812: context.mock.timers.enable({ apis: ['setTimeout'] });
64+
doc/api/test.md:2817: context.mock.timers.tick(9999);
65+
doc/api/test.md:2846: context.mock.timers.enable({ apis: ['setTimeout'] });
66+
doc/api/test.md:2853: context.mock.timers.tick(9999);
67+
doc/api/test.md:2873: context.mock.timers.enable({ apis: ['setTimeout'] });
68+
doc/api/test.md:2880: context.mock.timers.tick(9999);
69+
doc/api/test.md:2896: context.mock.timers.enable({ apis: ['setInterval'] });
70+
doc/api/test.md:2911: context.mock.timers.tick(interval);
71+
doc/api/test.md:2912: context.mock.timers.tick(interval);
72+
doc/api/test.md:2913: context.mock.timers.tick(interval);
73+
doc/api/test.md:2928: context.mock.timers.enable({ apis: ['setInterval'] });
74+
doc/api/test.md:2943: context.mock.timers.tick(interval);
75+
doc/api/test.md:2944: context.mock.timers.tick(interval);
76+
doc/api/test.md:2945: context.mock.timers.tick(interval);
77+
doc/api/test.md:

lib/internal/test_runner/mock/mock_timers.js

Lines changed: 72 additions & 22 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,
@@ -116,6 +121,8 @@ class MockTimers {
116121

117122
#nativeDateDescriptor;
118123

124+
#realAbortSignalTimeout;
125+
119126
#timersInContext = [];
120127
#isEnabled = false;
121128
#currentTimer = 1;
@@ -297,6 +304,26 @@ class MockTimers {
297304
);
298305
}
299306

307+
#storeOriginalAbortSignalTimeout() {
308+
try {
309+
this.#realAbortSignalTimeout = ObjectGetOwnPropertyDescriptor(AbortSignal, 'timeout');
310+
} catch {
311+
this.#realAbortSignalTimeout = undefined;
312+
}
313+
}
314+
315+
#restoreOriginalAbortSignalTimeout() {
316+
try {
317+
if (this.#realAbortSignalTimeout) {
318+
ObjectDefineProperty(AbortSignal, 'timeout', this.#realAbortSignalTimeout);
319+
} else {
320+
try {
321+
delete AbortSignal.timeout;
322+
} catch {}
323+
}
324+
} catch {}
325+
}
326+
300327
#createTimer(isInterval, callback, delay, ...args) {
301328
if (delay > TIMEOUT_MAX) {
302329
delay = 1;
@@ -604,6 +631,44 @@ class MockTimers {
604631
this.#nativeDateDescriptor = ObjectGetOwnPropertyDescriptor(globalThis, 'Date');
605632
globalThis.Date = this.#createDate();
606633
},
634+
'AbortSignal.timeout': () => {
635+
this.#storeOriginalAbortSignalTimeout();
636+
637+
const mock = this;
638+
639+
ObjectDefineProperty(AbortSignal, 'timeout', {
640+
__proto__: null,
641+
configurable: true,
642+
writable: true,
643+
value: function mockableAbortSignalTimeout(delay) {
644+
if (NumberIsNaN(delay)) {
645+
throw new ERR_INVALID_ARG_VALUE('delay', delay, 'delay must be a number');
646+
}
647+
648+
const controller = new AbortController();
649+
650+
const timer = mock.#setTimeout(
651+
() => {
652+
try {
653+
controller.abort();
654+
} catch {}
655+
},
656+
delay
657+
);
658+
659+
try {
660+
ObjectDefineProperty(controller.signal, '__mockTimer', {
661+
__proto__: null,
662+
configurable: true,
663+
writable: true,
664+
value: timer,
665+
});
666+
} catch {}
667+
668+
return controller.signal;
669+
},
670+
});
671+
},
607672
},
608673
toReal: {
609674
'__proto__': null,
@@ -622,6 +687,9 @@ class MockTimers {
622687
'Date': () => {
623688
ObjectDefineProperty(globalThis, 'Date', this.#nativeDateDescriptor);
624689
},
690+
'AbortSignal.timeout': () => {
691+
this.#restoreOriginalAbortSignalTimeout();
692+
},
625693
},
626694
};
627695

@@ -630,13 +698,6 @@ class MockTimers {
630698
this.#isEnabled = activate;
631699
}
632700

633-
/**
634-
* Advances the virtual time of MockTimers by the specified duration (in milliseconds).
635-
* This method simulates the passage of time and triggers any scheduled timers that are due.
636-
* @param {number} [time] - The amount of time (in milliseconds) to advance the virtual time.
637-
* @throws {ERR_INVALID_STATE} If MockTimers are not enabled.
638-
* @throws {ERR_INVALID_ARG_VALUE} If a negative time value is provided.
639-
*/
640701
tick(time = 1) {
641702
this.#assertTimersAreEnabled();
642703
this.#assertTimeArg(time);
@@ -647,7 +708,6 @@ class MockTimers {
647708
if (timer.runAt > this.#now) break;
648709
FunctionPrototypeApply(timer.callback, undefined, timer.args);
649710

650-
// Check if the timeout was cleared by calling clearTimeout inside its own callback
651711
const afterCallback = this.#executionQueue.peek();
652712
if (afterCallback?.id === timer.id) {
653713
this.#executionQueue.shift();
@@ -663,15 +723,6 @@ class MockTimers {
663723
}
664724
}
665725

666-
/**
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
670-
*/
671-
/**
672-
* Enables the MockTimers replacing the native timers with the fake ones.
673-
* @param {EnableOptions} [options]
674-
*/
675726
enable(options = { __proto__: null, apis: SUPPORTED_APIS, now: 0 }) {
676727
const internalOptions = { __proto__: null, ...options };
677728
if (this.#isEnabled) {
@@ -687,7 +738,6 @@ class MockTimers {
687738
internalOptions.apis ||= SUPPORTED_APIS;
688739

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

0 commit comments

Comments
 (0)