Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 37 additions & 12 deletions lib/internal/test_runner/mock/mock_timers.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,9 @@ function abortIt(signal) {
}

/**
* @enum {('setTimeout'|'setInterval'|'setImmediate'|'Date')[]} Supported timers
* @enum {('setTimeout'|'setInterval'|'setImmediate'|'Date', 'scheduler.wait')[]} Supported timers
*/
const SUPPORTED_APIS = ['setTimeout', 'setInterval', 'setImmediate', 'Date'];
const SUPPORTED_APIS = ['setTimeout', 'setInterval', 'setImmediate', 'Date', 'scheduler.wait'];
const TIMERS_DEFAULT_INTERVAL = {
__proto__: null,
setImmediate: -1,
Expand Down Expand Up @@ -106,6 +106,7 @@ class MockTimers {

#realPromisifiedSetTimeout;
#realPromisifiedSetInterval;
#realTimersPromisifiedSchedulerWait;

#realTimersSetTimeout;
#realTimersClearTimeout;
Expand Down Expand Up @@ -190,6 +191,13 @@ class MockTimers {
);
}

#restoreOriginalSchedulerWait() {
nodeTimersPromises.scheduler.wait = FunctionPrototypeBind(
this.#realTimersPromisifiedSchedulerWait,
this,
);
}

#restoreOriginalSetTimeout() {
ObjectDefineProperty(
globalThis,
Expand Down Expand Up @@ -264,6 +272,14 @@ class MockTimers {
);
}

#storeOriginalSchedulerWait() {

this.#realTimersPromisifiedSchedulerWait = FunctionPrototypeBind(
nodeTimersPromises.scheduler.wait,
this,
);
}

#storeOriginalSetTimeout() {
this.#realSetTimeout = ObjectGetOwnPropertyDescriptor(
globalThis,
Expand Down Expand Up @@ -556,8 +572,14 @@ class MockTimers {
const options = {
__proto__: null,
toFake: {
__proto__: null,
setTimeout: () => {
'__proto__': null,
'scheduler.wait': () => {
this.#storeOriginalSchedulerWait();

nodeTimersPromises.scheduler.wait = (delay, options) =>
this.#setTimeoutPromisified(delay, undefined, options);
},
'setTimeout': () => {
this.#storeOriginalSetTimeout();

globalThis.setTimeout = this.#setTimeout;
Expand All @@ -571,7 +593,7 @@ class MockTimers {
this,
);
},
setInterval: () => {
'setInterval': () => {
this.#storeOriginalSetInterval();

globalThis.setInterval = this.#setInterval;
Expand All @@ -585,7 +607,7 @@ class MockTimers {
this,
);
},
setImmediate: () => {
'setImmediate': () => {
this.#storeOriginalSetImmediate();

// setImmediate functions needs to bind MockTimers
Expand All @@ -609,23 +631,26 @@ class MockTimers {
this,
);
},
Date: () => {
'Date': () => {
this.#nativeDateDescriptor = ObjectGetOwnPropertyDescriptor(globalThis, 'Date');
globalThis.Date = this.#createDate();
},
},
toReal: {
__proto__: null,
setTimeout: () => {
'__proto__': null,
'scheduler.wait': () => {
this.#restoreOriginalSchedulerWait();
},
'setTimeout': () => {
this.#restoreOriginalSetTimeout();
},
setInterval: () => {
'setInterval': () => {
this.#restoreOriginalSetInterval();
},
setImmediate: () => {
'setImmediate': () => {
this.#restoreSetImmediate();
},
Date: () => {
'Date': () => {
ObjectDefineProperty(globalThis, 'Date', this.#nativeDateDescriptor);
},
},
Expand Down
120 changes: 120 additions & 0 deletions test/parallel/test-runner-mock-timers.js
Original file line number Diff line number Diff line change
Expand Up @@ -775,6 +775,126 @@ describe('Mock Timers Test Suite', () => {
});
});

describe('scheduler Suite', () => {
describe('scheduler.wait', () => {
it('should advance in time and trigger timers when calling the .tick function', (t) => {
t.mock.timers.enable({ apis: ['scheduler.wait'] });

const now = Date.now();
const durationAtMost = 100;

const p = nodeTimersPromises.scheduler.wait(4000);
t.mock.timers.tick(4000);

return p.then(common.mustCall((result) => {
assert.strictEqual(result, undefined);
assert.ok(
Date.now() - now < durationAtMost,
`time should be advanced less than the ${durationAtMost}ms`
);
}));
});

it('should advance in time and trigger timers when calling the .tick function multiple times', async (t) => {
t.mock.timers.enable({ apis: ['scheduler.wait'] });

const fn = t.mock.fn();

nodeTimersPromises.scheduler.wait(9999).then(fn);

t.mock.timers.tick(8999);
assert.strictEqual(fn.mock.callCount(), 0);
t.mock.timers.tick(500);

await nodeTimersPromises.setImmediate();

assert.strictEqual(fn.mock.callCount(), 0);
t.mock.timers.tick(500);

await nodeTimersPromises.setImmediate();
assert.strictEqual(fn.mock.callCount(), 1);
});

it('should work with the same params as the original timers/promises/scheduler.wait', async (t) => {
t.mock.timers.enable({ apis: ['scheduler.wait'] });
const controller = new AbortController();
const p = nodeTimersPromises.scheduler.wait(2000, {
ref: true,
signal: controller.signal,
});

t.mock.timers.tick(1000);
t.mock.timers.tick(500);
t.mock.timers.tick(500);
t.mock.timers.tick(500);

const result = await p;
assert.strictEqual(result, undefined);
});

it('should abort operation if timers/promises/scheduler.wait received an aborted signal', async (t) => {
t.mock.timers.enable({ apis: ['scheduler.wait'] });
const controller = new AbortController();
const p = nodeTimersPromises.scheduler.wait(2000, {
ref: true,
signal: controller.signal,
});

t.mock.timers.tick(1000);
controller.abort();
t.mock.timers.tick(500);
t.mock.timers.tick(500);
t.mock.timers.tick(500);

await assert.rejects(() => p, {
name: 'AbortError',
});
});
it('should abort operation even if the .tick was not called', async (t) => {
t.mock.timers.enable({ apis: ['scheduler.wait'] });
const controller = new AbortController();
const p = nodeTimersPromises.scheduler.wait(2000, {
ref: true,
signal: controller.signal,
});

controller.abort();

await assert.rejects(() => p, {
name: 'AbortError',
});
});

it('should abort operation when .abort is called before calling setInterval', async (t) => {
t.mock.timers.enable({ apis: ['scheduler.wait'] });
const controller = new AbortController();
controller.abort();
const p = nodeTimersPromises.scheduler.wait(2000, {
ref: true,
signal: controller.signal,
});

await assert.rejects(() => p, {
name: 'AbortError',
});
});

it('should reject given an an invalid signal instance', async (t) => {
t.mock.timers.enable({ apis: ['scheduler.wait'] });
const p = nodeTimersPromises.scheduler.wait(2000, {
ref: true,
signal: {},
});

await assert.rejects(() => p, {
name: 'TypeError',
code: 'ERR_INVALID_ARG_TYPE',
});
});

});
});

describe('Date Suite', () => {
it('should return the initial UNIX epoch if not specified', (t) => {
t.mock.timers.enable({ apis: ['Date'] });
Expand Down
Loading