mirror of
https://github.com/zebrajr/node.git
synced 2026-01-15 12:15:26 +00:00
test_runner: add Date to the supported mock APIs
signed-off-by: Lucas Santos <lhs.santoss@gmail.com> PR-URL: https://github.com/nodejs/node/pull/48638 Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Moshe Atlow <moshe@atlow.co.il> Reviewed-By: Erick Wendel <erick.workspace@gmail.com> Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com> Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
This commit is contained in:
444
doc/api/test.md
444
doc/api/test.md
@@ -505,7 +505,7 @@ This allows developers to write more reliable and
|
||||
predictable tests for time-dependent functionality.
|
||||
|
||||
The example below shows how to mock `setTimeout`.
|
||||
Using `.enable(['setTimeout']);`
|
||||
Using `.enable({ apis: ['setTimeout'] });`
|
||||
it will mock the `setTimeout` functions in the [node:timers](./timers.md) and
|
||||
[node:timers/promises](./timers.md#timers-promises-api) modules,
|
||||
as well as from the Node.js global context.
|
||||
@@ -522,7 +522,7 @@ test('mocks setTimeout to be executed synchronously without having to actually w
|
||||
const fn = mock.fn();
|
||||
|
||||
// Optionally choose what to mock
|
||||
mock.timers.enable(['setTimeout']);
|
||||
mock.timers.enable({ apis: ['setTimeout'] });
|
||||
setTimeout(fn, 9999);
|
||||
assert.strictEqual(fn.mock.callCount(), 0);
|
||||
|
||||
@@ -546,7 +546,7 @@ test('mocks setTimeout to be executed synchronously without having to actually w
|
||||
const fn = mock.fn();
|
||||
|
||||
// Optionally choose what to mock
|
||||
mock.timers.enable(['setTimeout']);
|
||||
mock.timers.enable({ apis: ['setTimeout'] });
|
||||
setTimeout(fn, 9999);
|
||||
assert.strictEqual(fn.mock.callCount(), 0);
|
||||
|
||||
@@ -575,7 +575,7 @@ test('mocks setTimeout to be executed synchronously without having to actually w
|
||||
const fn = context.mock.fn();
|
||||
|
||||
// Optionally choose what to mock
|
||||
context.mock.timers.enable(['setTimeout']);
|
||||
context.mock.timers.enable({ apis: ['setTimeout'] });
|
||||
setTimeout(fn, 9999);
|
||||
assert.strictEqual(fn.mock.callCount(), 0);
|
||||
|
||||
@@ -593,7 +593,7 @@ test('mocks setTimeout to be executed synchronously without having to actually w
|
||||
const fn = context.mock.fn();
|
||||
|
||||
// Optionally choose what to mock
|
||||
context.mock.timers.enable(['setTimeout']);
|
||||
context.mock.timers.enable({ apis: ['setTimeout'] });
|
||||
setTimeout(fn, 9999);
|
||||
assert.strictEqual(fn.mock.callCount(), 0);
|
||||
|
||||
@@ -603,6 +603,220 @@ test('mocks setTimeout to be executed synchronously without having to actually w
|
||||
});
|
||||
```
|
||||
|
||||
### Dates
|
||||
|
||||
The mock timers API also allows the mocking of the `Date` object. This is a
|
||||
useful feature for testing time-dependent functionality, or to simulate
|
||||
internal calendar functions such as `Date.now()`.
|
||||
|
||||
The dates implementation is also part of the [`MockTimers`][] class. Refer to it
|
||||
for a full list of methods and features.
|
||||
|
||||
**Note:** Dates and timers are dependent when mocked together. This means that
|
||||
if you have both the `Date` and `setTimeout` mocked, advancing the time will
|
||||
also advance the mocked date as they simulate a single internal clock.
|
||||
|
||||
The example below show how to mock the `Date` object and obtain the current
|
||||
`Date.now()` value.
|
||||
|
||||
```mjs
|
||||
import assert from 'node:assert';
|
||||
import { test } from 'node:test';
|
||||
|
||||
test('mocks the Date object', (context) => {
|
||||
// Optionally choose what to mock
|
||||
context.mock.timers.enable({ apis: ['Date'] });
|
||||
// If not specified, the initial date will be based on 0 in the UNIX epoch
|
||||
assert.strictEqual(Date.now(), 0);
|
||||
|
||||
// Advance in time will also advance the date
|
||||
context.mock.timers.tick(9999);
|
||||
assert.strictEqual(Date.now(), 9999);
|
||||
});
|
||||
```
|
||||
|
||||
```cjs
|
||||
const assert = require('node:assert');
|
||||
const { test } = require('node:test');
|
||||
|
||||
test('mocks the Date object', (context) => {
|
||||
// Optionally choose what to mock
|
||||
context.mock.timers.enable({ apis: ['Date'] });
|
||||
// If not specified, the initial date will be based on 0 in the UNIX epoch
|
||||
assert.strictEqual(Date.now(), 0);
|
||||
|
||||
// Advance in time will also advance the date
|
||||
context.mock.timers.tick(9999);
|
||||
assert.strictEqual(Date.now(), 9999);
|
||||
});
|
||||
```
|
||||
|
||||
If there is no initial epoch set, the initial date will be based on 0 in the
|
||||
Unix epoch. This is January 1st, 1970, 00:00:00 UTC. You can set an initial date
|
||||
by passing a `now` property to the `.enable()` method. This value will be used
|
||||
as the initial date for the mocked `Date` object. It can either be a positive
|
||||
integer, or another Date object.
|
||||
|
||||
```mjs
|
||||
import assert from 'node:assert';
|
||||
import { test } from 'node:test';
|
||||
|
||||
test('mocks the Date object with initial time', (context) => {
|
||||
// Optionally choose what to mock
|
||||
context.mock.timers.enable({ apis: ['Date'], now: 100 });
|
||||
assert.strictEqual(Date.now(), 100);
|
||||
|
||||
// Advance in time will also advance the date
|
||||
context.mock.timers.tick(200);
|
||||
assert.strictEqual(Date.now(), 300);
|
||||
});
|
||||
```
|
||||
|
||||
```cjs
|
||||
const assert = require('node:assert');
|
||||
const { test } = require('node:test');
|
||||
|
||||
test('mocks the Date object with initial time', (context) => {
|
||||
// Optionally choose what to mock
|
||||
context.mock.timers.enable({ apis: ['Date'], now: 100 });
|
||||
assert.strictEqual(Date.now(), 100);
|
||||
|
||||
// Advance in time will also advance the date
|
||||
context.mock.timers.tick(200);
|
||||
assert.strictEqual(Date.now(), 300);
|
||||
});
|
||||
```
|
||||
|
||||
You can use the `.setTime()` method to manually move the mocked date to another
|
||||
time. This method only accepts a positive integer.
|
||||
|
||||
**Note:** This method will execute any mocked timers that are in the past
|
||||
from the new time.
|
||||
|
||||
In the below example we are setting a new time for the mocked date.
|
||||
|
||||
```mjs
|
||||
import assert from 'node:assert';
|
||||
import { test } from 'node:test';
|
||||
|
||||
test('sets the time of a date object', (context) => {
|
||||
// Optionally choose what to mock
|
||||
context.mock.timers.enable({ apis: ['Date'], now: 100 });
|
||||
assert.strictEqual(Date.now(), 100);
|
||||
|
||||
// Advance in time will also advance the date
|
||||
context.mock.timers.setTime(1000);
|
||||
context.mock.timers.tick(200);
|
||||
assert.strictEqual(Date.now(), 1200);
|
||||
});
|
||||
```
|
||||
|
||||
```cjs
|
||||
const assert = require('node:assert');
|
||||
const { test } = require('node:test');
|
||||
|
||||
test('sets the time of a date object', (context) => {
|
||||
// Optionally choose what to mock
|
||||
context.mock.timers.enable({ apis: ['Date'], now: 100 });
|
||||
assert.strictEqual(Date.now(), 100);
|
||||
|
||||
// Advance in time will also advance the date
|
||||
context.mock.timers.setTime(1000);
|
||||
context.mock.timers.tick(200);
|
||||
assert.strictEqual(Date.now(), 1200);
|
||||
});
|
||||
```
|
||||
|
||||
If you have any timer that's set to run in the past, it will be executed as if
|
||||
the `.tick()` method has been called. This is useful if you want to test
|
||||
time-dependent functionality that's already in the past.
|
||||
|
||||
```mjs
|
||||
import assert from 'node:assert';
|
||||
import { test } from 'node:test';
|
||||
|
||||
test('runs timers as setTime passes ticks', (context) => {
|
||||
// Optionally choose what to mock
|
||||
context.mock.timers.enable({ apis: ['setTimeout', 'Date'] });
|
||||
const fn = context.mock.fn();
|
||||
setTimeout(fn, 1000);
|
||||
|
||||
context.mock.timers.setTime(800);
|
||||
// Timer is not executed as the time is not yet reached
|
||||
assert.strictEqual(fn.mock.callCount(), 0);
|
||||
assert.strictEqual(Date.now(), 800);
|
||||
|
||||
context.mock.timers.setTime(1200);
|
||||
// Timer is executed as the time is now reached
|
||||
assert.strictEqual(fn.mock.callCount(), 1);
|
||||
assert.strictEqual(Date.now(), 1200);
|
||||
});
|
||||
```
|
||||
|
||||
```cjs
|
||||
const assert = require('node:assert');
|
||||
const { test } = require('node:test');
|
||||
|
||||
test('runs timers as setTime passes ticks', (context) => {
|
||||
// Optionally choose what to mock
|
||||
context.mock.timers.enable({ apis: ['setTimeout', 'Date'] });
|
||||
const fn = context.mock.fn();
|
||||
setTimeout(fn, 1000);
|
||||
|
||||
context.mock.timers.setTime(800);
|
||||
// Timer is not executed as the time is not yet reached
|
||||
assert.strictEqual(fn.mock.callCount(), 0);
|
||||
assert.strictEqual(Date.now(), 800);
|
||||
|
||||
context.mock.timers.setTime(1200);
|
||||
// Timer is executed as the time is now reached
|
||||
assert.strictEqual(fn.mock.callCount(), 1);
|
||||
assert.strictEqual(Date.now(), 1200);
|
||||
});
|
||||
```
|
||||
|
||||
Using `.runAll()` will execute all timers that are currently in the queue. This
|
||||
will also advance the mocked date to the time of the last timer that was
|
||||
executed as if the time has passed.
|
||||
|
||||
```mjs
|
||||
import assert from 'node:assert';
|
||||
import { test } from 'node:test';
|
||||
|
||||
test('runs timers as setTime passes ticks', (context) => {
|
||||
// Optionally choose what to mock
|
||||
context.mock.timers.enable({ apis: ['setTimeout', 'Date'] });
|
||||
const fn = context.mock.fn();
|
||||
setTimeout(fn, 1000);
|
||||
setTimeout(fn, 2000);
|
||||
setTimeout(fn, 3000);
|
||||
|
||||
context.mock.timers.runAll();
|
||||
// All timers are executed as the time is now reached
|
||||
assert.strictEqual(fn.mock.callCount(), 3);
|
||||
assert.strictEqual(Date.now(), 3000);
|
||||
});
|
||||
```
|
||||
|
||||
```cjs
|
||||
const assert = require('node:assert');
|
||||
const { test } = require('node:test');
|
||||
|
||||
test('runs timers as setTime passes ticks', (context) => {
|
||||
// Optionally choose what to mock
|
||||
context.mock.timers.enable({ apis: ['setTimeout', 'Date'] });
|
||||
const fn = context.mock.fn();
|
||||
setTimeout(fn, 1000);
|
||||
setTimeout(fn, 2000);
|
||||
setTimeout(fn, 3000);
|
||||
|
||||
context.mock.timers.runAll();
|
||||
// All timers are executed as the time is now reached
|
||||
assert.strictEqual(fn.mock.callCount(), 3);
|
||||
assert.strictEqual(Date.now(), 3000);
|
||||
});
|
||||
```
|
||||
|
||||
## Test reporters
|
||||
|
||||
<!-- YAML
|
||||
@@ -1576,37 +1790,52 @@ Mocking timers is a technique commonly used in software testing to simulate and
|
||||
control the behavior of timers, such as `setInterval` and `setTimeout`,
|
||||
without actually waiting for the specified time intervals.
|
||||
|
||||
MockTimers is also able to mock the `Date` object.
|
||||
|
||||
The [`MockTracker`][] provides a top-level `timers` export
|
||||
which is a `MockTimers` instance.
|
||||
|
||||
### `timers.enable([timers])`
|
||||
### `timers.enable([enableOptions])`
|
||||
|
||||
<!-- YAML
|
||||
added:
|
||||
- v20.4.0
|
||||
changes:
|
||||
- version: REPLACEME
|
||||
pr-url: https://github.com/nodejs/node/pull/48638
|
||||
description: Updated parameters to be an option object with available APIs
|
||||
and the default initial epoch.
|
||||
-->
|
||||
|
||||
Enables timer mocking for the specified timers.
|
||||
|
||||
* `timers` {Array} An optional array containing the timers to mock.
|
||||
The currently supported timer values are `'setInterval'`, `'setTimeout'`,
|
||||
and `'setImmediate'`. If no value is provided, all timers (`'setInterval'`,
|
||||
`'clearInterval'`, `'setTimeout'`, `'clearTimeout'`, `'setImmediate'`,
|
||||
and `'clearImmediate'`) will be mocked by default.
|
||||
* `enableOptions` {Object} Optional configuration options for enabling timer
|
||||
mocking. The following properties are supported:
|
||||
* `apis` {Array} An optional array containing the timers to mock.
|
||||
The currently supported timer values are `'setInterval'`, `'setTimeout'`, `'setImmediate'`,
|
||||
and `'Date'`. **Default:** `['setInterval', 'setTimeout', 'setImmediate', 'Date']`.
|
||||
If no array is provided, all time related APIs (`'setInterval'`, `'clearInterval'`,
|
||||
`'setTimeout'`, `'clearTimeout'`, and `'Date'`) will be mocked by default.
|
||||
* `now` {number | Date} An optional number or Date object representing the
|
||||
initial time (in milliseconds) to use as the value
|
||||
for `Date.now()`. **Default:** `0`.
|
||||
|
||||
**Note:** When you enable mocking for a specific timer, its associated
|
||||
clear function will also be implicitly mocked.
|
||||
|
||||
Example usage:
|
||||
**Note:** Mocking `Date` will affect the behavior of the mocked timers
|
||||
as they use the same internal clock.
|
||||
|
||||
Example usage without setting initial time:
|
||||
|
||||
```mjs
|
||||
import { mock } from 'node:test';
|
||||
mock.timers.enable(['setInterval']);
|
||||
mock.timers.enable({ apis: ['setInterval'] });
|
||||
```
|
||||
|
||||
```cjs
|
||||
const { mock } = require('node:test');
|
||||
mock.timers.enable(['setInterval']);
|
||||
mock.timers.enable({ apis: ['setInterval'] });
|
||||
```
|
||||
|
||||
The above example enables mocking for the `setInterval` timer and
|
||||
@@ -1615,12 +1844,36 @@ and `clearInterval` functions from [node:timers](./timers.md),
|
||||
[node:timers/promises](./timers.md#timers-promises-api), and
|
||||
`globalThis` will be mocked.
|
||||
|
||||
Example usage with initial time set
|
||||
|
||||
```mjs
|
||||
import { mock } from 'node:test';
|
||||
mock.timers.enable({ apis: ['Date'], now: 1000 });
|
||||
```
|
||||
|
||||
```cjs
|
||||
const { mock } = require('node:test');
|
||||
mock.timers.enable({ apis: ['Date'], now: 1000 });
|
||||
```
|
||||
|
||||
Example usage with initial Date object as time set
|
||||
|
||||
```mjs
|
||||
import { mock } from 'node:test';
|
||||
mock.timers.enable({ apis: ['Date'], now: new Date() });
|
||||
```
|
||||
|
||||
```cjs
|
||||
const { mock } = require('node:test');
|
||||
mock.timers.enable({ apis: ['Date'], now: new Date() });
|
||||
```
|
||||
|
||||
Alternatively, if you call `mock.timers.enable()` without any parameters:
|
||||
|
||||
All timers (`'setInterval'`, `'clearInterval'`, `'setTimeout'`, and `'clearTimeout'`)
|
||||
will be mocked. The `setInterval`, `clearInterval`, `setTimeout`, and `clearTimeout`
|
||||
functions from `node:timers`, `node:timers/promises`,
|
||||
and `globalThis` will be mocked.
|
||||
and `globalThis` will be mocked. As well as the global `Date` object.
|
||||
|
||||
### `timers.reset()`
|
||||
|
||||
@@ -1677,7 +1930,7 @@ import { test } from 'node:test';
|
||||
test('mocks setTimeout to be executed synchronously without having to actually wait for it', (context) => {
|
||||
const fn = context.mock.fn();
|
||||
|
||||
context.mock.timers.enable(['setTimeout']);
|
||||
context.mock.timers.enable({ apis: ['setTimeout'] });
|
||||
|
||||
setTimeout(fn, 9999);
|
||||
|
||||
@@ -1696,7 +1949,7 @@ const { test } = require('node:test');
|
||||
|
||||
test('mocks setTimeout to be executed synchronously without having to actually wait for it', (context) => {
|
||||
const fn = context.mock.fn();
|
||||
context.mock.timers.enable(['setTimeout']);
|
||||
context.mock.timers.enable({ apis: ['setTimeout'] });
|
||||
|
||||
setTimeout(fn, 9999);
|
||||
assert.strictEqual(fn.mock.callCount(), 0);
|
||||
@@ -1716,7 +1969,7 @@ import { test } from 'node:test';
|
||||
|
||||
test('mocks setTimeout to be executed synchronously without having to actually wait for it', (context) => {
|
||||
const fn = context.mock.fn();
|
||||
context.mock.timers.enable(['setTimeout']);
|
||||
context.mock.timers.enable({ apis: ['setTimeout'] });
|
||||
const nineSecs = 9000;
|
||||
setTimeout(fn, nineSecs);
|
||||
|
||||
@@ -1735,7 +1988,7 @@ const { test } = require('node:test');
|
||||
|
||||
test('mocks setTimeout to be executed synchronously without having to actually wait for it', (context) => {
|
||||
const fn = context.mock.fn();
|
||||
context.mock.timers.enable(['setTimeout']);
|
||||
context.mock.timers.enable({ apis: ['setTimeout'] });
|
||||
const nineSecs = 9000;
|
||||
setTimeout(fn, nineSecs);
|
||||
|
||||
@@ -1748,6 +2001,48 @@ test('mocks setTimeout to be executed synchronously without having to actually w
|
||||
});
|
||||
```
|
||||
|
||||
Advancing time using `.tick` will also advance the time for any `Date` object
|
||||
created after the mock was enabled (if `Date` was also set to be mocked).
|
||||
|
||||
```mjs
|
||||
import assert from 'node:assert';
|
||||
import { test } from 'node:test';
|
||||
|
||||
test('mocks setTimeout to be executed synchronously without having to actually wait for it', (context) => {
|
||||
const fn = context.mock.fn();
|
||||
|
||||
context.mock.timers.enable({ apis: ['setTimeout', 'Date'] });
|
||||
setTimeout(fn, 9999);
|
||||
|
||||
assert.strictEqual(fn.mock.callCount(), 0);
|
||||
assert.strictEqual(Date.now(), 0);
|
||||
|
||||
// Advance in time
|
||||
context.mock.timers.tick(9999);
|
||||
assert.strictEqual(fn.mock.callCount(), 1);
|
||||
assert.strictEqual(Date.now(), 9999);
|
||||
});
|
||||
```
|
||||
|
||||
```cjs
|
||||
const assert = require('node:assert');
|
||||
const { test } = require('node:test');
|
||||
|
||||
test('mocks setTimeout to be executed synchronously without having to actually wait for it', (context) => {
|
||||
const fn = context.mock.fn();
|
||||
context.mock.timers.enable({ apis: ['setTimeout', 'Date'] });
|
||||
|
||||
setTimeout(fn, 9999);
|
||||
assert.strictEqual(fn.mock.callCount(), 0);
|
||||
assert.strictEqual(Date.now(), 0);
|
||||
|
||||
// Advance in time
|
||||
context.mock.timers.tick(9999);
|
||||
assert.strictEqual(fn.mock.callCount(), 1);
|
||||
assert.strictEqual(Date.now(), 9999);
|
||||
});
|
||||
```
|
||||
|
||||
#### Using clear functions
|
||||
|
||||
As mentioned, all clear functions from timers (`clearTimeout` and `clearInterval`)
|
||||
@@ -1761,7 +2056,7 @@ test('mocks setTimeout to be executed synchronously without having to actually w
|
||||
const fn = context.mock.fn();
|
||||
|
||||
// Optionally choose what to mock
|
||||
context.mock.timers.enable(['setTimeout']);
|
||||
context.mock.timers.enable({ apis: ['setTimeout'] });
|
||||
const id = setTimeout(fn, 9999);
|
||||
|
||||
// Implicity mocked as well
|
||||
@@ -1781,7 +2076,7 @@ test('mocks setTimeout to be executed synchronously without having to actually w
|
||||
const fn = context.mock.fn();
|
||||
|
||||
// Optionally choose what to mock
|
||||
context.mock.timers.enable(['setTimeout']);
|
||||
context.mock.timers.enable({ apis: ['setTimeout'] });
|
||||
const id = setTimeout(fn, 9999);
|
||||
|
||||
// Implicity mocked as well
|
||||
@@ -1815,7 +2110,7 @@ test('mocks setTimeout to be executed synchronously without having to actually w
|
||||
const nodeTimerPromiseSpy = context.mock.fn();
|
||||
|
||||
// Optionally choose what to mock
|
||||
context.mock.timers.enable(['setTimeout']);
|
||||
context.mock.timers.enable({ apis: ['setTimeout'] });
|
||||
setTimeout(globalTimeoutObjectSpy, 9999);
|
||||
nodeTimers.setTimeout(nodeTimerSpy, 9999);
|
||||
|
||||
@@ -1842,7 +2137,7 @@ test('mocks setTimeout to be executed synchronously without having to actually w
|
||||
const nodeTimerPromiseSpy = context.mock.fn();
|
||||
|
||||
// Optionally choose what to mock
|
||||
context.mock.timers.enable(['setTimeout']);
|
||||
context.mock.timers.enable({ apis: ['setTimeout'] });
|
||||
setTimeout(globalTimeoutObjectSpy, 9999);
|
||||
nodeTimers.setTimeout(nodeTimerSpy, 9999);
|
||||
|
||||
@@ -1865,7 +2160,7 @@ import assert from 'node:assert';
|
||||
import { test } from 'node:test';
|
||||
import nodeTimersPromises from 'node:timers/promises';
|
||||
test('should tick five times testing a real use case', async (context) => {
|
||||
context.mock.timers.enable(['setInterval']);
|
||||
context.mock.timers.enable({ apis: ['setInterval'] });
|
||||
|
||||
const expectedIterations = 3;
|
||||
const interval = 1000;
|
||||
@@ -1897,7 +2192,7 @@ const assert = require('node:assert');
|
||||
const { test } = require('node:test');
|
||||
const nodeTimersPromises = require('node:timers/promises');
|
||||
test('should tick five times testing a real use case', async (context) => {
|
||||
context.mock.timers.enable(['setInterval']);
|
||||
context.mock.timers.enable({ apis: ['setInterval'] });
|
||||
|
||||
const expectedIterations = 3;
|
||||
const interval = 1000;
|
||||
@@ -1931,7 +2226,8 @@ added:
|
||||
- v20.4.0
|
||||
-->
|
||||
|
||||
Triggers all pending mocked timers immediately.
|
||||
Triggers all pending mocked timers immediately. If the `Date` object is also
|
||||
mocked, it will also advance the `Date` object to the furthest timer's time.
|
||||
|
||||
The example below triggers all pending timers immediately,
|
||||
causing them to execute without any delay.
|
||||
@@ -1941,7 +2237,7 @@ import assert from 'node:assert';
|
||||
import { test } from 'node:test';
|
||||
|
||||
test('runAll functions following the given order', (context) => {
|
||||
context.mock.timers.enable(['setTimeout']);
|
||||
context.mock.timers.enable({ apis: ['setTimeout', 'Date'] });
|
||||
const results = [];
|
||||
setTimeout(() => results.push(1), 9999);
|
||||
|
||||
@@ -1953,8 +2249,9 @@ test('runAll functions following the given order', (context) => {
|
||||
assert.deepStrictEqual(results, []);
|
||||
|
||||
context.mock.timers.runAll();
|
||||
|
||||
assert.deepStrictEqual(results, [3, 2, 1]);
|
||||
// The Date object is also advanced to the furthest timer's time
|
||||
assert.strictEqual(Date.now(), 9999);
|
||||
});
|
||||
```
|
||||
|
||||
@@ -1963,7 +2260,7 @@ const assert = require('node:assert');
|
||||
const { test } = require('node:test');
|
||||
|
||||
test('runAll functions following the given order', (context) => {
|
||||
context.mock.timers.enable(['setTimeout']);
|
||||
context.mock.timers.enable({ apis: ['setTimeout', 'Date'] });
|
||||
const results = [];
|
||||
setTimeout(() => results.push(1), 9999);
|
||||
|
||||
@@ -1975,8 +2272,9 @@ test('runAll functions following the given order', (context) => {
|
||||
assert.deepStrictEqual(results, []);
|
||||
|
||||
context.mock.timers.runAll();
|
||||
|
||||
assert.deepStrictEqual(results, [3, 2, 1]);
|
||||
// The Date object is also advanced to the furthest timer's time
|
||||
assert.strictEqual(Date.now(), 9999);
|
||||
});
|
||||
```
|
||||
|
||||
@@ -1985,6 +2283,92 @@ triggering timers in the context of timer mocking.
|
||||
It does not have any effect on real-time system
|
||||
clocks or actual timers outside of the mocking environment.
|
||||
|
||||
### `timers.setTime(milliseconds)`
|
||||
|
||||
<!-- YAML
|
||||
added:
|
||||
- REPLACEME
|
||||
-->
|
||||
|
||||
Sets the current Unix timestamp that will be used as reference for any mocked
|
||||
`Date` objects.
|
||||
|
||||
```mjs
|
||||
import assert from 'node:assert';
|
||||
import { test } from 'node:test';
|
||||
|
||||
test('runAll functions following the given order', (context) => {
|
||||
const now = Date.now();
|
||||
const setTime = 1000;
|
||||
// Date.now is not mocked
|
||||
assert.deepStrictEqual(Date.now(), now);
|
||||
|
||||
context.mock.timers.enable({ apis: ['Date'] });
|
||||
context.mock.timers.setTime(setTime);
|
||||
// Date.now is now 1000
|
||||
assert.strictEqual(Date.now(), setTime);
|
||||
});
|
||||
```
|
||||
|
||||
```cjs
|
||||
const assert = require('node:assert');
|
||||
const { test } = require('node:test');
|
||||
|
||||
test('setTime replaces current time', (context) => {
|
||||
const now = Date.now();
|
||||
const setTime = 1000;
|
||||
// Date.now is not mocked
|
||||
assert.deepStrictEqual(Date.now(), now);
|
||||
|
||||
context.mock.timers.enable({ apis: ['Date'] });
|
||||
context.mock.timers.setTime(setTime);
|
||||
// Date.now is now 1000
|
||||
assert.strictEqual(Date.now(), setTime);
|
||||
});
|
||||
```
|
||||
|
||||
#### Dates and Timers working together
|
||||
|
||||
Dates and timer objects are dependent on each other. If you use `setTime()` to
|
||||
pass the current time to the mocked `Date` object, the set timers with
|
||||
`setTimeout` and `setInterval` will **not** be affected.
|
||||
|
||||
However, the `tick` method **will** advanced the mocked `Date` object.
|
||||
|
||||
```mjs
|
||||
import assert from 'node:assert';
|
||||
import { test } from 'node:test';
|
||||
|
||||
test('runAll functions following the given order', (context) => {
|
||||
context.mock.timers.enable({ apis: ['setTimeout', 'Date'] });
|
||||
const results = [];
|
||||
setTimeout(() => results.push(1), 9999);
|
||||
|
||||
assert.deepStrictEqual(results, []);
|
||||
context.mock.timers.setTime(12000);
|
||||
assert.deepStrictEqual(results, []);
|
||||
// The date is advanced but the timers don't tick
|
||||
assert.strictEqual(Date.now(), 12000);
|
||||
});
|
||||
```
|
||||
|
||||
```cjs
|
||||
const assert = require('node:assert');
|
||||
const { test } = require('node:test');
|
||||
|
||||
test('runAll functions following the given order', (context) => {
|
||||
context.mock.timers.enable({ apis: ['setTimeout', 'Date'] });
|
||||
const results = [];
|
||||
setTimeout(() => results.push(1), 9999);
|
||||
|
||||
assert.deepStrictEqual(results, []);
|
||||
context.mock.timers.setTime(12000);
|
||||
assert.deepStrictEqual(results, []);
|
||||
// The date is advanced but the timers don't tick
|
||||
assert.strictEqual(Date.now(), 12000);
|
||||
});
|
||||
```
|
||||
|
||||
## Class: `TestsStream`
|
||||
|
||||
<!-- YAML
|
||||
|
||||
@@ -38,6 +38,10 @@ module.exports = class PriorityQueue {
|
||||
return this.#heap[1];
|
||||
}
|
||||
|
||||
peekBottom() {
|
||||
return this.#heap[this.#size];
|
||||
}
|
||||
|
||||
percolateDown(pos) {
|
||||
const compare = this.#compare;
|
||||
const setPosition = this.#setPosition;
|
||||
|
||||
@@ -8,27 +8,31 @@ const {
|
||||
ArrayPrototypeAt,
|
||||
ArrayPrototypeForEach,
|
||||
ArrayPrototypeIncludes,
|
||||
DateNow,
|
||||
DatePrototypeGetTime,
|
||||
DatePrototypeToString,
|
||||
FunctionPrototypeApply,
|
||||
FunctionPrototypeBind,
|
||||
FunctionPrototypeToString,
|
||||
globalThis,
|
||||
NumberIsNaN,
|
||||
ObjectDefineProperty,
|
||||
ObjectDefineProperties,
|
||||
ObjectGetOwnPropertyDescriptor,
|
||||
ObjectGetOwnPropertyDescriptors,
|
||||
Promise,
|
||||
Symbol,
|
||||
SymbolAsyncIterator,
|
||||
SymbolDispose,
|
||||
globalThis,
|
||||
} = primordials;
|
||||
const {
|
||||
validateAbortSignal,
|
||||
validateArray,
|
||||
validateNumber,
|
||||
} = require('internal/validators');
|
||||
|
||||
const {
|
||||
AbortError,
|
||||
codes: {
|
||||
ERR_INVALID_STATE,
|
||||
ERR_INVALID_ARG_VALUE,
|
||||
},
|
||||
codes: { ERR_INVALID_STATE, ERR_INVALID_ARG_VALUE },
|
||||
} = require('internal/errors');
|
||||
|
||||
const PriorityQueue = require('internal/priority_queue');
|
||||
@@ -37,6 +41,10 @@ const nodeTimersPromises = require('timers/promises');
|
||||
const EventEmitter = require('events');
|
||||
|
||||
let kResistStopPropagation;
|
||||
// Internal reference to the MockTimers class inside MockDate
|
||||
let kMock;
|
||||
// Initial epoch to which #now should be set to
|
||||
const kInitialEpoch = 0;
|
||||
|
||||
function compareTimersLists(a, b) {
|
||||
return (a.runAt - b.runAt) || (a.id - b.id);
|
||||
@@ -50,7 +58,10 @@ function abortIt(signal) {
|
||||
return new AbortError(undefined, { __proto__: null, cause: signal.reason });
|
||||
}
|
||||
|
||||
const SUPPORTED_TIMERS = ['setTimeout', 'setInterval', 'setImmediate'];
|
||||
/**
|
||||
* @enum {('setTimeout'|'setInterval'|'setImmediate'|'Date')[]} Supported timers
|
||||
*/
|
||||
const SUPPORTED_APIS = ['setTimeout', 'setInterval', 'setImmediate', 'Date'];
|
||||
const TIMERS_DEFAULT_INTERVAL = {
|
||||
__proto__: null,
|
||||
setImmediate: -1,
|
||||
@@ -75,10 +86,12 @@ class MockTimers {
|
||||
#realTimersClearImmediate;
|
||||
#realPromisifiedSetImmediate;
|
||||
|
||||
#nativeDateDescriptor;
|
||||
|
||||
#timersInContext = [];
|
||||
#isEnabled = false;
|
||||
#currentTimer = 1;
|
||||
#now = DateNow();
|
||||
#now = kInitialEpoch;
|
||||
|
||||
#executionQueue = new PriorityQueue(compareTimersLists, setPosition);
|
||||
|
||||
@@ -86,222 +99,12 @@ class MockTimers {
|
||||
#clearTimeout = FunctionPrototypeBind(this.#clearTimer, this);
|
||||
#setInterval = FunctionPrototypeBind(this.#createTimer, this, true);
|
||||
#clearInterval = FunctionPrototypeBind(this.#clearTimer, this);
|
||||
|
||||
#setImmediate = (callback, ...args) => {
|
||||
return this.#createTimer(
|
||||
false,
|
||||
callback,
|
||||
TIMERS_DEFAULT_INTERVAL.setImmediate,
|
||||
...args,
|
||||
);
|
||||
};
|
||||
|
||||
#clearImmediate = FunctionPrototypeBind(this.#clearTimer, this);
|
||||
|
||||
constructor() {
|
||||
emitExperimentalWarning('The MockTimers API');
|
||||
}
|
||||
|
||||
#createTimer(isInterval, callback, delay, ...args) {
|
||||
const timerId = this.#currentTimer++;
|
||||
this.#executionQueue.insert({
|
||||
__proto__: null,
|
||||
id: timerId,
|
||||
callback,
|
||||
runAt: this.#now + delay,
|
||||
interval: isInterval,
|
||||
args,
|
||||
});
|
||||
|
||||
return timerId;
|
||||
}
|
||||
|
||||
#clearTimer(position) {
|
||||
this.#executionQueue.removeAt(position);
|
||||
}
|
||||
|
||||
async * #setIntervalPromisified(interval, startTime, options) {
|
||||
const context = this;
|
||||
const emitter = new EventEmitter();
|
||||
if (options?.signal) {
|
||||
validateAbortSignal(options.signal, 'options.signal');
|
||||
|
||||
if (options.signal.aborted) {
|
||||
throw abortIt(options.signal);
|
||||
}
|
||||
|
||||
const onAbort = (reason) => {
|
||||
emitter.emit('data', { __proto__: null, aborted: true, reason });
|
||||
};
|
||||
|
||||
kResistStopPropagation ??= require('internal/event_target').kResistStopPropagation;
|
||||
options.signal.addEventListener('abort', onAbort, {
|
||||
__proto__: null,
|
||||
once: true,
|
||||
[kResistStopPropagation]: true,
|
||||
});
|
||||
}
|
||||
|
||||
const eventIt = EventEmitter.on(emitter, 'data');
|
||||
const callback = () => {
|
||||
startTime += interval;
|
||||
emitter.emit('data', startTime);
|
||||
};
|
||||
|
||||
const timerId = this.#createTimer(true, callback, interval, options);
|
||||
const clearListeners = () => {
|
||||
emitter.removeAllListeners();
|
||||
context.#clearTimer(timerId);
|
||||
};
|
||||
const iterator = {
|
||||
__proto__: null,
|
||||
[SymbolAsyncIterator]() {
|
||||
return this;
|
||||
},
|
||||
async next() {
|
||||
const result = await eventIt.next();
|
||||
const value = ArrayPrototypeAt(result.value, 0);
|
||||
if (value?.aborted) {
|
||||
iterator.return();
|
||||
throw abortIt(options.signal);
|
||||
}
|
||||
|
||||
return {
|
||||
__proto__: null,
|
||||
done: result.done,
|
||||
value,
|
||||
};
|
||||
},
|
||||
async return() {
|
||||
clearListeners();
|
||||
return eventIt.return();
|
||||
},
|
||||
};
|
||||
yield* iterator;
|
||||
}
|
||||
|
||||
#promisifyTimer({ timerFn, clearFn, ms, result, options }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (options?.signal) {
|
||||
try {
|
||||
validateAbortSignal(options.signal, 'options.signal');
|
||||
} catch (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
if (options.signal.aborted) {
|
||||
return reject(abortIt(options.signal));
|
||||
}
|
||||
}
|
||||
|
||||
const onabort = () => {
|
||||
clearFn(id);
|
||||
return reject(abortIt(options.signal));
|
||||
};
|
||||
|
||||
const id = timerFn(() => {
|
||||
return resolve(result);
|
||||
}, ms);
|
||||
|
||||
if (options?.signal) {
|
||||
kResistStopPropagation ??= require('internal/event_target').kResistStopPropagation;
|
||||
options.signal.addEventListener('abort', onabort, {
|
||||
__proto__: null,
|
||||
once: true,
|
||||
[kResistStopPropagation]: true,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#setImmediatePromisified(result, options) {
|
||||
return this.#promisifyTimer({
|
||||
__proto__: null,
|
||||
timerFn: FunctionPrototypeBind(this.#setImmediate, this),
|
||||
clearFn: FunctionPrototypeBind(this.#clearImmediate, this),
|
||||
ms: TIMERS_DEFAULT_INTERVAL.setImmediate,
|
||||
result,
|
||||
options,
|
||||
});
|
||||
}
|
||||
|
||||
#setTimeoutPromisified(ms, result, options) {
|
||||
return this.#promisifyTimer({
|
||||
__proto__: null,
|
||||
timerFn: FunctionPrototypeBind(this.#setTimeout, this),
|
||||
clearFn: FunctionPrototypeBind(this.#clearTimeout, this),
|
||||
ms,
|
||||
result,
|
||||
options,
|
||||
});
|
||||
}
|
||||
|
||||
#toggleEnableTimers(activate) {
|
||||
const options = {
|
||||
__proto__: null,
|
||||
toFake: {
|
||||
__proto__: null,
|
||||
setTimeout: () => {
|
||||
this.#storeOriginalSetTimeout();
|
||||
|
||||
globalThis.setTimeout = this.#setTimeout;
|
||||
globalThis.clearTimeout = this.#clearTimeout;
|
||||
|
||||
nodeTimers.setTimeout = this.#setTimeout;
|
||||
nodeTimers.clearTimeout = this.#clearTimeout;
|
||||
|
||||
nodeTimersPromises.setTimeout = FunctionPrototypeBind(
|
||||
this.#setTimeoutPromisified,
|
||||
this,
|
||||
);
|
||||
},
|
||||
setInterval: () => {
|
||||
this.#storeOriginalSetInterval();
|
||||
|
||||
globalThis.setInterval = this.#setInterval;
|
||||
globalThis.clearInterval = this.#clearInterval;
|
||||
|
||||
nodeTimers.setInterval = this.#setInterval;
|
||||
nodeTimers.clearInterval = this.#clearInterval;
|
||||
|
||||
nodeTimersPromises.setInterval = FunctionPrototypeBind(
|
||||
this.#setIntervalPromisified,
|
||||
this,
|
||||
);
|
||||
},
|
||||
setImmediate: () => {
|
||||
this.#storeOriginalSetImmediate();
|
||||
|
||||
globalThis.setImmediate = this.#setImmediate;
|
||||
globalThis.clearImmediate = this.#clearImmediate;
|
||||
|
||||
nodeTimers.setImmediate = this.#setImmediate;
|
||||
nodeTimers.clearImmediate = this.#clearImmediate;
|
||||
|
||||
nodeTimersPromises.setImmediate = FunctionPrototypeBind(
|
||||
this.#setImmediatePromisified,
|
||||
this,
|
||||
);
|
||||
},
|
||||
},
|
||||
toReal: {
|
||||
__proto__: null,
|
||||
setTimeout: () => {
|
||||
this.#restoreOriginalSetTimeout();
|
||||
},
|
||||
setInterval: () => {
|
||||
this.#restoreOriginalSetInterval();
|
||||
},
|
||||
setImmediate: () => {
|
||||
this.#restoreSetImmediate();
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const target = activate ? options.toFake : options.toReal;
|
||||
ArrayPrototypeForEach(this.#timersInContext, (timer) => target[timer]());
|
||||
this.#isEnabled = activate;
|
||||
}
|
||||
|
||||
#restoreSetImmediate() {
|
||||
ObjectDefineProperty(
|
||||
globalThis,
|
||||
@@ -455,6 +258,351 @@ class MockTimers {
|
||||
);
|
||||
}
|
||||
|
||||
#createTimer(isInterval, callback, delay, ...args) {
|
||||
const timerId = this.#currentTimer++;
|
||||
this.#executionQueue.insert({
|
||||
__proto__: null,
|
||||
id: timerId,
|
||||
callback,
|
||||
runAt: this.#now + delay,
|
||||
interval: isInterval,
|
||||
args,
|
||||
});
|
||||
|
||||
return timerId;
|
||||
}
|
||||
|
||||
#clearTimer(position) {
|
||||
this.#executionQueue.removeAt(position);
|
||||
}
|
||||
|
||||
#createDate() {
|
||||
kMock ??= Symbol('MockTimers');
|
||||
const NativeDateConstructor = this.#nativeDateDescriptor.value;
|
||||
/**
|
||||
* Function to mock the Date constructor, treats cases as per ECMA-262
|
||||
* and returns a Date object with a mocked implementation
|
||||
* @typedef {Date} MockDate
|
||||
* @returns {MockDate} a mocked Date object
|
||||
*/
|
||||
function MockDate(year, month, date, hours, minutes, seconds, ms) {
|
||||
const mockTimersSource = MockDate[kMock];
|
||||
const nativeDate = mockTimersSource.#nativeDateDescriptor.value;
|
||||
|
||||
// As of the fake-timers implementation for Sinon
|
||||
// ref https://github.com/sinonjs/fake-timers/blob/a4c757f80840829e45e0852ea1b17d87a998388e/src/fake-timers-src.js#L456
|
||||
// This covers the Date constructor called as a function ref.
|
||||
// ECMA-262 Edition 5.1 section 15.9.2.
|
||||
// and ECMA-262 Edition 14 Section 21.4.2.1
|
||||
// replaces 'this instanceof MockDate' with a more reliable check
|
||||
// from ECMA-262 Edition 14 Section 13.3.12.1 NewTarget
|
||||
if (!new.target) {
|
||||
return DatePrototypeToString(new nativeDate(mockTimersSource.#now));
|
||||
}
|
||||
|
||||
// Cases where Date is called as a constructor
|
||||
// This is intended as a defensive implementation to avoid
|
||||
// having unexpected returns
|
||||
switch (arguments.length) {
|
||||
case 0:
|
||||
return new nativeDate(MockDate[kMock].#now);
|
||||
case 1:
|
||||
return new nativeDate(year);
|
||||
case 2:
|
||||
return new nativeDate(year, month);
|
||||
case 3:
|
||||
return new nativeDate(year, month, date);
|
||||
case 4:
|
||||
return new nativeDate(year, month, date, hours);
|
||||
case 5:
|
||||
return new nativeDate(year, month, date, hours, minutes);
|
||||
case 6:
|
||||
return new nativeDate(year, month, date, hours, minutes, seconds);
|
||||
default:
|
||||
return new nativeDate(year, month, date, hours, minutes, seconds, ms);
|
||||
}
|
||||
}
|
||||
|
||||
// Prototype is read-only, and non assignable through Object.defineProperties
|
||||
// eslint-disable-next-line no-unused-vars -- used to get the prototype out of the object
|
||||
const { prototype, ...dateProps } = ObjectGetOwnPropertyDescriptors(NativeDateConstructor);
|
||||
|
||||
// Binds all the properties of Date to the MockDate function
|
||||
ObjectDefineProperties(
|
||||
MockDate,
|
||||
dateProps,
|
||||
);
|
||||
|
||||
MockDate.now = function now() {
|
||||
return MockDate[kMock].#now;
|
||||
};
|
||||
|
||||
// This is just to print the function { native code } in the console
|
||||
// when the user prints the function and not the internal code
|
||||
MockDate.toString = function toString() {
|
||||
return FunctionPrototypeToString(MockDate[kMock].#nativeDateDescriptor.value);
|
||||
};
|
||||
|
||||
// We need to polute the prototype of this
|
||||
ObjectDefineProperties(MockDate, {
|
||||
__proto__: null,
|
||||
[kMock]: {
|
||||
__proto__: null,
|
||||
enumerable: false,
|
||||
configurable: false,
|
||||
writable: false,
|
||||
value: this,
|
||||
},
|
||||
|
||||
isMock: {
|
||||
__proto__: null,
|
||||
enumerable: true,
|
||||
configurable: false,
|
||||
writable: false,
|
||||
value: true,
|
||||
},
|
||||
});
|
||||
|
||||
MockDate.prototype = NativeDateConstructor.prototype;
|
||||
MockDate.parse = NativeDateConstructor.parse;
|
||||
MockDate.UTC = NativeDateConstructor.UTC;
|
||||
MockDate.prototype.toUTCString = NativeDateConstructor.prototype.toUTCString;
|
||||
return MockDate;
|
||||
}
|
||||
|
||||
async * #setIntervalPromisified(interval, startTime, options) {
|
||||
const context = this;
|
||||
const emitter = new EventEmitter();
|
||||
if (options?.signal) {
|
||||
validateAbortSignal(options.signal, 'options.signal');
|
||||
|
||||
if (options.signal.aborted) {
|
||||
throw abortIt(options.signal);
|
||||
}
|
||||
|
||||
const onAbort = (reason) => {
|
||||
emitter.emit('data', { __proto__: null, aborted: true, reason });
|
||||
};
|
||||
|
||||
kResistStopPropagation ??= require('internal/event_target').kResistStopPropagation;
|
||||
options.signal.addEventListener('abort', onAbort, {
|
||||
__proto__: null,
|
||||
once: true,
|
||||
[kResistStopPropagation]: true,
|
||||
});
|
||||
}
|
||||
|
||||
const eventIt = EventEmitter.on(emitter, 'data');
|
||||
const callback = () => {
|
||||
startTime += interval;
|
||||
emitter.emit('data', startTime);
|
||||
};
|
||||
|
||||
const timerId = this.#createTimer(true, callback, interval, options);
|
||||
const clearListeners = () => {
|
||||
emitter.removeAllListeners();
|
||||
context.#clearTimer(timerId);
|
||||
};
|
||||
const iterator = {
|
||||
__proto__: null,
|
||||
[SymbolAsyncIterator]() {
|
||||
return this;
|
||||
},
|
||||
async next() {
|
||||
const result = await eventIt.next();
|
||||
const value = ArrayPrototypeAt(result.value, 0);
|
||||
if (value?.aborted) {
|
||||
iterator.return();
|
||||
throw abortIt(options.signal);
|
||||
}
|
||||
|
||||
return {
|
||||
__proto__: null,
|
||||
done: result.done,
|
||||
value,
|
||||
};
|
||||
},
|
||||
async return() {
|
||||
clearListeners();
|
||||
return eventIt.return();
|
||||
},
|
||||
};
|
||||
yield* iterator;
|
||||
}
|
||||
|
||||
#setImmediate(callback, ...args) {
|
||||
return this.#createTimer(
|
||||
false,
|
||||
callback,
|
||||
TIMERS_DEFAULT_INTERVAL.setImmediate,
|
||||
...args,
|
||||
);
|
||||
}
|
||||
|
||||
#promisifyTimer({ timerFn, clearFn, ms, result, options }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (options?.signal) {
|
||||
try {
|
||||
validateAbortSignal(options.signal, 'options.signal');
|
||||
} catch (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
if (options.signal.aborted) {
|
||||
return reject(abortIt(options.signal));
|
||||
}
|
||||
}
|
||||
|
||||
const onabort = () => {
|
||||
clearFn(id);
|
||||
return reject(abortIt(options.signal));
|
||||
};
|
||||
|
||||
const id = timerFn(() => {
|
||||
return resolve(result);
|
||||
}, ms);
|
||||
|
||||
if (options?.signal) {
|
||||
kResistStopPropagation ??= require('internal/event_target').kResistStopPropagation;
|
||||
options.signal.addEventListener('abort', onabort, {
|
||||
__proto__: null,
|
||||
once: true,
|
||||
[kResistStopPropagation]: true,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#setImmediatePromisified(result, options) {
|
||||
return this.#promisifyTimer({
|
||||
__proto__: null,
|
||||
timerFn: FunctionPrototypeBind(this.#setImmediate, this),
|
||||
clearFn: FunctionPrototypeBind(this.#clearImmediate, this),
|
||||
ms: TIMERS_DEFAULT_INTERVAL.setImmediate,
|
||||
result,
|
||||
options,
|
||||
});
|
||||
}
|
||||
|
||||
#setTimeoutPromisified(ms, result, options) {
|
||||
return this.#promisifyTimer({
|
||||
__proto__: null,
|
||||
timerFn: FunctionPrototypeBind(this.#setTimeout, this),
|
||||
clearFn: FunctionPrototypeBind(this.#clearTimeout, this),
|
||||
ms,
|
||||
result,
|
||||
options,
|
||||
});
|
||||
}
|
||||
|
||||
#assertTimersAreEnabled() {
|
||||
if (!this.#isEnabled) {
|
||||
throw new ERR_INVALID_STATE(
|
||||
'You should enable MockTimers first by calling the .enable function',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#assertTimeArg(time) {
|
||||
if (time < 0) {
|
||||
throw new ERR_INVALID_ARG_VALUE('time', 'positive integer', time);
|
||||
}
|
||||
}
|
||||
|
||||
#isValidDateWithGetTime(maybeDate) {
|
||||
// Validation inspired on https://github.com/inspect-js/is-date-object/blob/main/index.js#L3-L11
|
||||
try {
|
||||
DatePrototypeGetTime(maybeDate);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#toggleEnableTimers(activate) {
|
||||
const options = {
|
||||
__proto__: null,
|
||||
toFake: {
|
||||
__proto__: null,
|
||||
setTimeout: () => {
|
||||
this.#storeOriginalSetTimeout();
|
||||
|
||||
globalThis.setTimeout = this.#setTimeout;
|
||||
globalThis.clearTimeout = this.#clearTimeout;
|
||||
|
||||
nodeTimers.setTimeout = this.#setTimeout;
|
||||
nodeTimers.clearTimeout = this.#clearTimeout;
|
||||
|
||||
nodeTimersPromises.setTimeout = FunctionPrototypeBind(
|
||||
this.#setTimeoutPromisified,
|
||||
this,
|
||||
);
|
||||
},
|
||||
setInterval: () => {
|
||||
this.#storeOriginalSetInterval();
|
||||
|
||||
globalThis.setInterval = this.#setInterval;
|
||||
globalThis.clearInterval = this.#clearInterval;
|
||||
|
||||
nodeTimers.setInterval = this.#setInterval;
|
||||
nodeTimers.clearInterval = this.#clearInterval;
|
||||
|
||||
nodeTimersPromises.setInterval = FunctionPrototypeBind(
|
||||
this.#setIntervalPromisified,
|
||||
this,
|
||||
);
|
||||
},
|
||||
setImmediate: () => {
|
||||
this.#storeOriginalSetImmediate();
|
||||
|
||||
// setImmediate functions needs to bind MockTimers
|
||||
// otherwise it will throw an error when called
|
||||
// "Receiver must be an instance of MockTimers"
|
||||
// because #setImmediate is the only function here
|
||||
// that calls #createTimer and it's not bound to MockTimers
|
||||
globalThis.setImmediate = FunctionPrototypeBind(
|
||||
this.#setImmediate,
|
||||
this,
|
||||
);
|
||||
globalThis.clearImmediate = this.#clearImmediate;
|
||||
|
||||
nodeTimers.setImmediate = FunctionPrototypeBind(
|
||||
this.#setImmediate,
|
||||
this,
|
||||
);
|
||||
nodeTimers.clearImmediate = this.#clearImmediate;
|
||||
nodeTimersPromises.setImmediate = FunctionPrototypeBind(
|
||||
this.#setImmediatePromisified,
|
||||
this,
|
||||
);
|
||||
},
|
||||
Date: () => {
|
||||
this.#nativeDateDescriptor = ObjectGetOwnPropertyDescriptor(globalThis, 'Date');
|
||||
globalThis.Date = this.#createDate();
|
||||
},
|
||||
},
|
||||
toReal: {
|
||||
__proto__: null,
|
||||
setTimeout: () => {
|
||||
this.#restoreOriginalSetTimeout();
|
||||
},
|
||||
setInterval: () => {
|
||||
this.#restoreOriginalSetInterval();
|
||||
},
|
||||
setImmediate: () => {
|
||||
this.#restoreSetImmediate();
|
||||
},
|
||||
Date: () => {
|
||||
ObjectDefineProperty(globalThis, 'Date', this.#nativeDateDescriptor);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const target = activate ? options.toFake : options.toReal;
|
||||
ArrayPrototypeForEach(this.#timersInContext, (timer) => target[timer]());
|
||||
this.#isEnabled = activate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Advances the virtual time of MockTimers by the specified duration (in milliseconds).
|
||||
* This method simulates the passage of time and triggers any scheduled timers that are due.
|
||||
@@ -463,19 +611,8 @@ class MockTimers {
|
||||
* @throws {ERR_INVALID_ARG_VALUE} If a negative time value is provided.
|
||||
*/
|
||||
tick(time = 1) {
|
||||
if (!this.#isEnabled) {
|
||||
throw new ERR_INVALID_STATE(
|
||||
'You should enable MockTimers first by calling the .enable function',
|
||||
);
|
||||
}
|
||||
|
||||
if (time < 0) {
|
||||
throw new ERR_INVALID_ARG_VALUE(
|
||||
'time',
|
||||
'positive integer',
|
||||
time,
|
||||
);
|
||||
}
|
||||
this.#assertTimersAreEnabled();
|
||||
this.#assertTimeArg(time);
|
||||
|
||||
this.#now += time;
|
||||
let timer = this.#executionQueue.peek();
|
||||
@@ -496,36 +633,68 @@ class MockTimers {
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables MockTimers for the specified timers.
|
||||
* @param {string[]} timers - An array of timer types to enable, e.g., ['setTimeout', 'setInterval'].
|
||||
* @throws {ERR_INVALID_STATE} If MockTimers are already enabled.
|
||||
* @throws {ERR_INVALID_ARG_VALUE} If an unsupported timer type is specified.
|
||||
* @typedef {{apis: SUPPORTED_APIS;now: number | Date;}} EnableOptions Options to enable the timers
|
||||
* @property {SUPPORTED_APIS} apis List of timers to enable, defaults to all
|
||||
* @property {number | Date} now The epoch to which the timers should be set to, defaults to 0
|
||||
*/
|
||||
enable(timers = SUPPORTED_TIMERS) {
|
||||
/**
|
||||
* Enables the MockTimers replacing the native timers with the fake ones.
|
||||
* @param {EnableOptions} options
|
||||
*/
|
||||
enable(options = { __proto__: null, apis: SUPPORTED_APIS, now: 0 }) {
|
||||
const internalOptions = { __proto__: null, ...options };
|
||||
if (this.#isEnabled) {
|
||||
throw new ERR_INVALID_STATE(
|
||||
'MockTimers is already enabled!',
|
||||
);
|
||||
throw new ERR_INVALID_STATE('MockTimers is already enabled!');
|
||||
}
|
||||
|
||||
validateArray(timers, 'timers');
|
||||
if (NumberIsNaN(internalOptions.now)) {
|
||||
throw new ERR_INVALID_ARG_VALUE('now', internalOptions.now, `epoch must be a positive integer received ${internalOptions.now}`);
|
||||
}
|
||||
|
||||
if (!internalOptions.now) {
|
||||
internalOptions.now = 0;
|
||||
}
|
||||
|
||||
if (!internalOptions.apis) {
|
||||
internalOptions.apis = SUPPORTED_APIS;
|
||||
}
|
||||
|
||||
validateArray(internalOptions.apis, 'options.apis');
|
||||
// Check that the timers passed are supported
|
||||
ArrayPrototypeForEach(timers, (timer) => {
|
||||
if (!ArrayPrototypeIncludes(SUPPORTED_TIMERS, timer)) {
|
||||
ArrayPrototypeForEach(internalOptions.apis, (timer) => {
|
||||
if (!ArrayPrototypeIncludes(SUPPORTED_APIS, timer)) {
|
||||
throw new ERR_INVALID_ARG_VALUE(
|
||||
'timers',
|
||||
'options.apis',
|
||||
timer,
|
||||
`option ${timer} is not supported`,
|
||||
);
|
||||
}
|
||||
});
|
||||
this.#timersInContext = internalOptions.apis;
|
||||
|
||||
// Checks if the second argument is the initial time
|
||||
if (this.#isValidDateWithGetTime(internalOptions.now)) {
|
||||
this.#now = DatePrototypeGetTime(internalOptions.now);
|
||||
} else if (validateNumber(internalOptions.now, 'initialTime') === undefined) {
|
||||
this.#assertTimeArg(internalOptions.now);
|
||||
this.#now = internalOptions.now;
|
||||
}
|
||||
|
||||
this.#timersInContext = timers;
|
||||
this.#now = DateNow();
|
||||
this.#toggleEnableTimers(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current time to the given epoch.
|
||||
* @param {number} time The epoch to set the current time to.
|
||||
*/
|
||||
setTime(time = kInitialEpoch) {
|
||||
validateNumber(time, 'time');
|
||||
this.#assertTimeArg(time);
|
||||
this.#assertTimersAreEnabled();
|
||||
|
||||
this.#now = time;
|
||||
}
|
||||
|
||||
/**
|
||||
* An alias for `this.reset()`, allowing the disposal of the `MockTimers` instance.
|
||||
*/
|
||||
@@ -543,6 +712,7 @@ class MockTimers {
|
||||
|
||||
this.#toggleEnableTimers(false);
|
||||
this.#timersInContext = [];
|
||||
this.#now = kInitialEpoch;
|
||||
|
||||
let timer = this.#executionQueue.peek();
|
||||
while (timer) {
|
||||
@@ -556,13 +726,10 @@ class MockTimers {
|
||||
* @throws {ERR_INVALID_STATE} If MockTimers are not enabled.
|
||||
*/
|
||||
runAll() {
|
||||
if (!this.#isEnabled) {
|
||||
throw new ERR_INVALID_STATE(
|
||||
'You should enable MockTimers first by calling the .enable function',
|
||||
);
|
||||
}
|
||||
|
||||
this.tick(Infinity);
|
||||
this.#assertTimersAreEnabled();
|
||||
const longestTimer = this.#executionQueue.peekBottom();
|
||||
if (!longestTimer) return;
|
||||
this.tick(longestTimer.runAt - this.#now);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ describe('Mock Timers Test Suite', () => {
|
||||
describe('MockTimers API', () => {
|
||||
it('should throw an error if trying to enable a timer that is not supported', (t) => {
|
||||
assert.throws(() => {
|
||||
t.mock.timers.enable(['DOES_NOT_EXIST']);
|
||||
t.mock.timers.enable({ apis: ['DOES_NOT_EXIST'] });
|
||||
}, {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
});
|
||||
@@ -46,6 +46,7 @@ describe('Mock Timers Test Suite', () => {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
});
|
||||
});
|
||||
|
||||
it('should check that propertyDescriptor gets back after resetting timers', (t) => {
|
||||
const getDescriptor = (ctx, fn) => Object.getOwnPropertyDescriptor(ctx, fn);
|
||||
const getCurrentTimersDescriptors = () => {
|
||||
@@ -107,6 +108,7 @@ describe('Mock Timers Test Suite', () => {
|
||||
const fn = t.mock.fn();
|
||||
global.setTimeout(fn, 1000);
|
||||
t.mock.timers.reset();
|
||||
assert.deepStrictEqual(Date.now, globalThis.Date.now);
|
||||
assert.throws(() => {
|
||||
t.mock.timers.tick(1000);
|
||||
}, {
|
||||
@@ -166,14 +168,34 @@ describe('Mock Timers Test Suite', () => {
|
||||
assert.strictEqual(timeoutFn.mock.callCount(), 1);
|
||||
assert.strictEqual(intervalFn.mock.callCount(), 1);
|
||||
});
|
||||
});
|
||||
|
||||
it('should increase the epoch as the tick run for runAll', async (t) => {
|
||||
const timeoutFn = t.mock.fn();
|
||||
const intervalFn = t.mock.fn();
|
||||
|
||||
t.mock.timers.enable();
|
||||
global.setTimeout(timeoutFn, 1111);
|
||||
const id = global.setInterval(intervalFn, 9999);
|
||||
t.mock.timers.runAll();
|
||||
|
||||
global.clearInterval(id);
|
||||
assert.strictEqual(timeoutFn.mock.callCount(), 1);
|
||||
assert.strictEqual(intervalFn.mock.callCount(), 1);
|
||||
assert.strictEqual(Date.now(), 9999);
|
||||
});
|
||||
|
||||
it('should not error if there are not timers to run', (t) => {
|
||||
t.mock.timers.enable();
|
||||
t.mock.timers.runAll();
|
||||
// Should not throw
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('globals/timers', () => {
|
||||
describe('setTimeout Suite', () => {
|
||||
it('should advance in time and trigger timers when calling the .tick function', (t) => {
|
||||
mock.timers.enable(['setTimeout']);
|
||||
mock.timers.enable({ apis: ['setTimeout'] });
|
||||
|
||||
const fn = mock.fn();
|
||||
|
||||
@@ -185,7 +207,7 @@ describe('Mock Timers Test Suite', () => {
|
||||
});
|
||||
|
||||
it('should advance in time and trigger timers when calling the .tick function multiple times', (t) => {
|
||||
t.mock.timers.enable(['setTimeout']);
|
||||
t.mock.timers.enable({ apis: ['setTimeout'] });
|
||||
const fn = t.mock.fn();
|
||||
|
||||
global.setTimeout(fn, 2000);
|
||||
@@ -199,7 +221,7 @@ describe('Mock Timers Test Suite', () => {
|
||||
});
|
||||
|
||||
it('should work with the same params as the original setTimeout', (t) => {
|
||||
t.mock.timers.enable(['setTimeout']);
|
||||
t.mock.timers.enable({ apis: ['setTimeout'] });
|
||||
const fn = t.mock.fn();
|
||||
const args = ['a', 'b', 'c'];
|
||||
global.setTimeout(fn, 2000, ...args);
|
||||
@@ -221,12 +243,11 @@ describe('Mock Timers Test Suite', () => {
|
||||
done();
|
||||
}), timeout);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('clearTimeout Suite', () => {
|
||||
it('should not advance in time if clearTimeout was invoked', (t) => {
|
||||
t.mock.timers.enable(['setTimeout']);
|
||||
t.mock.timers.enable({ apis: ['setTimeout'] });
|
||||
|
||||
const fn = mock.fn();
|
||||
|
||||
@@ -240,7 +261,7 @@ describe('Mock Timers Test Suite', () => {
|
||||
|
||||
describe('setInterval Suite', () => {
|
||||
it('should tick three times using fake setInterval', (t) => {
|
||||
t.mock.timers.enable(['setInterval']);
|
||||
t.mock.timers.enable({ apis: ['setInterval'] });
|
||||
const fn = t.mock.fn();
|
||||
|
||||
const id = global.setInterval(fn, 200);
|
||||
@@ -255,7 +276,7 @@ describe('Mock Timers Test Suite', () => {
|
||||
});
|
||||
|
||||
it('should work with the same params as the original setInterval', (t) => {
|
||||
t.mock.timers.enable(['setInterval']);
|
||||
t.mock.timers.enable({ apis: ['setInterval'] });
|
||||
const fn = t.mock.fn();
|
||||
const args = ['a', 'b', 'c'];
|
||||
const id = global.setInterval(fn, 200, ...args);
|
||||
@@ -270,13 +291,12 @@ describe('Mock Timers Test Suite', () => {
|
||||
assert.deepStrictEqual(fn.mock.calls[0].arguments, args);
|
||||
assert.deepStrictEqual(fn.mock.calls[1].arguments, args);
|
||||
assert.deepStrictEqual(fn.mock.calls[2].arguments, args);
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
describe('clearInterval Suite', () => {
|
||||
it('should not advance in time if clearInterval was invoked', (t) => {
|
||||
t.mock.timers.enable(['setInterval']);
|
||||
t.mock.timers.enable({ apis: ['setInterval'] });
|
||||
|
||||
const fn = mock.fn();
|
||||
const id = global.setInterval(fn, 200);
|
||||
@@ -299,7 +319,7 @@ describe('Mock Timers Test Suite', () => {
|
||||
});
|
||||
|
||||
it('should work with the same params as the original setImmediate', (t) => {
|
||||
t.mock.timers.enable(['setImmediate']);
|
||||
t.mock.timers.enable({ apis: ['setImmediate'] });
|
||||
const fn = t.mock.fn();
|
||||
const args = ['a', 'b', 'c'];
|
||||
global.setImmediate(fn, ...args);
|
||||
@@ -310,7 +330,7 @@ describe('Mock Timers Test Suite', () => {
|
||||
});
|
||||
|
||||
it('should not advance in time if clearImmediate was invoked', (t) => {
|
||||
t.mock.timers.enable(['setImmediate']);
|
||||
t.mock.timers.enable({ apis: ['setImmediate'] });
|
||||
|
||||
const id = global.setImmediate(common.mustNotCall());
|
||||
global.clearImmediate(id);
|
||||
@@ -318,13 +338,13 @@ describe('Mock Timers Test Suite', () => {
|
||||
});
|
||||
|
||||
it('should advance in time and trigger timers when calling the .tick function', (t) => {
|
||||
t.mock.timers.enable(['setImmediate']);
|
||||
t.mock.timers.enable({ apis: ['setImmediate'] });
|
||||
global.setImmediate(common.mustCall(1));
|
||||
t.mock.timers.tick(0);
|
||||
});
|
||||
|
||||
it('should execute in order if setImmediate is called multiple times', (t) => {
|
||||
t.mock.timers.enable(['setImmediate']);
|
||||
t.mock.timers.enable({ apis: ['setImmediate'] });
|
||||
const order = [];
|
||||
const fn1 = t.mock.fn(common.mustCall(() => order.push('f1'), 1));
|
||||
const fn2 = t.mock.fn(common.mustCall(() => order.push('f2'), 1));
|
||||
@@ -338,7 +358,7 @@ describe('Mock Timers Test Suite', () => {
|
||||
});
|
||||
|
||||
it('should execute setImmediate first if setTimeout was also called', (t) => {
|
||||
t.mock.timers.enable(['setImmediate', 'setTimeout']);
|
||||
t.mock.timers.enable({ apis: ['setImmediate', 'setTimeout'] });
|
||||
const order = [];
|
||||
const fn1 = t.mock.fn(common.mustCall(() => order.push('f1'), 1));
|
||||
const fn2 = t.mock.fn(common.mustCall(() => order.push('f2'), 1));
|
||||
@@ -351,524 +371,357 @@ describe('Mock Timers Test Suite', () => {
|
||||
assert.deepStrictEqual(order, ['f1', 'f2']);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('timers Suite', () => {
|
||||
describe('setTimeout Suite', () => {
|
||||
it('should advance in time and trigger timers when calling the .tick function multiple times', (t) => {
|
||||
t.mock.timers.enable(['setTimeout']);
|
||||
const fn = t.mock.fn();
|
||||
const { setTimeout } = nodeTimers;
|
||||
setTimeout(fn, 2000);
|
||||
describe('timers/promises', () => {
|
||||
describe('setTimeout Suite', () => {
|
||||
it('should advance in time and trigger timers when calling the .tick function multiple times', async (t) => {
|
||||
t.mock.timers.enable({ apis: ['setTimeout'] });
|
||||
|
||||
t.mock.timers.tick(1000);
|
||||
t.mock.timers.tick(500);
|
||||
t.mock.timers.tick(500);
|
||||
const p = nodeTimersPromises.setTimeout(2000);
|
||||
|
||||
assert.strictEqual(fn.mock.callCount(), 1);
|
||||
t.mock.timers.tick(1000);
|
||||
t.mock.timers.tick(500);
|
||||
t.mock.timers.tick(500);
|
||||
t.mock.timers.tick(500);
|
||||
|
||||
p.then(common.mustCall((result) => {
|
||||
assert.strictEqual(result, undefined);
|
||||
}));
|
||||
});
|
||||
|
||||
it('should work with the same params as the original timers/promises/setTimeout', async (t) => {
|
||||
t.mock.timers.enable({ apis: ['setTimeout'] });
|
||||
const expectedResult = 'result';
|
||||
const controller = new AbortController();
|
||||
const p = nodeTimersPromises.setTimeout(2000, expectedResult, {
|
||||
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, expectedResult);
|
||||
});
|
||||
|
||||
it('should abort operation if timers/promises/setTimeout received an aborted signal', async (t) => {
|
||||
t.mock.timers.enable({ apis: ['setTimeout'] });
|
||||
const expectedResult = 'result';
|
||||
const controller = new AbortController();
|
||||
const p = nodeTimersPromises.setTimeout(2000, expectedResult, {
|
||||
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: ['setTimeout'] });
|
||||
const expectedResult = 'result';
|
||||
const controller = new AbortController();
|
||||
const p = nodeTimersPromises.setTimeout(2000, expectedResult, {
|
||||
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: ['setTimeout'] });
|
||||
const expectedResult = 'result';
|
||||
const controller = new AbortController();
|
||||
controller.abort();
|
||||
const p = nodeTimersPromises.setTimeout(2000, expectedResult, {
|
||||
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: ['setTimeout'] });
|
||||
const expectedResult = 'result';
|
||||
const p = nodeTimersPromises.setTimeout(2000, expectedResult, {
|
||||
ref: true,
|
||||
signal: {},
|
||||
});
|
||||
|
||||
await assert.rejects(() => p, {
|
||||
name: 'TypeError',
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should work with the same params as the original timers.setTimeout', (t) => {
|
||||
t.mock.timers.enable(['setTimeout']);
|
||||
const fn = t.mock.fn();
|
||||
const { setTimeout } = nodeTimers;
|
||||
const args = ['a', 'b', 'c'];
|
||||
setTimeout(fn, 2000, ...args);
|
||||
describe('setInterval Suite', () => {
|
||||
it('should tick three times using fake setInterval', async (t) => {
|
||||
t.mock.timers.enable({ apis: ['setInterval'] });
|
||||
|
||||
t.mock.timers.tick(1000);
|
||||
t.mock.timers.tick(500);
|
||||
t.mock.timers.tick(500);
|
||||
const interval = 100;
|
||||
const intervalIterator = nodeTimersPromises.setInterval(interval, Date.now());
|
||||
|
||||
assert.strictEqual(fn.mock.callCount(), 1);
|
||||
assert.deepStrictEqual(fn.mock.calls[0].arguments, args);
|
||||
});
|
||||
});
|
||||
const first = intervalIterator.next();
|
||||
const second = intervalIterator.next();
|
||||
const third = intervalIterator.next();
|
||||
|
||||
describe('clearTimeout Suite', () => {
|
||||
it('should not advance in time if clearTimeout was invoked', (t) => {
|
||||
t.mock.timers.enable(['setTimeout']);
|
||||
t.mock.timers.tick(interval);
|
||||
t.mock.timers.tick(interval);
|
||||
t.mock.timers.tick(interval);
|
||||
t.mock.timers.tick(interval);
|
||||
|
||||
const fn = mock.fn();
|
||||
const { setTimeout, clearTimeout } = nodeTimers;
|
||||
const id = setTimeout(fn, 2000);
|
||||
clearTimeout(id);
|
||||
t.mock.timers.tick(2000);
|
||||
const results = await Promise.all([
|
||||
first,
|
||||
second,
|
||||
third,
|
||||
]);
|
||||
|
||||
assert.strictEqual(fn.mock.callCount(), 0);
|
||||
});
|
||||
});
|
||||
const finished = await intervalIterator.return();
|
||||
assert.deepStrictEqual(finished, { done: true, value: undefined });
|
||||
results.forEach((result) => {
|
||||
assert.strictEqual(typeof result.value, 'number');
|
||||
assert.strictEqual(result.done, false);
|
||||
});
|
||||
});
|
||||
it('should tick five times testing a real use case', async (t) => {
|
||||
t.mock.timers.enable({ apis: ['setInterval'] });
|
||||
|
||||
describe('setInterval Suite', () => {
|
||||
it('should tick three times using fake setInterval', (t) => {
|
||||
t.mock.timers.enable(['setInterval']);
|
||||
const fn = t.mock.fn();
|
||||
const expectedIterations = 5;
|
||||
const interval = 1000;
|
||||
const startedAt = Date.now();
|
||||
async function run() {
|
||||
const times = [];
|
||||
for await (const time of nodeTimersPromises.setInterval(interval, startedAt)) {
|
||||
times.push(time);
|
||||
if (times.length === expectedIterations) break;
|
||||
}
|
||||
return times;
|
||||
}
|
||||
|
||||
const id = nodeTimers.setInterval(fn, 200);
|
||||
const r = run();
|
||||
t.mock.timers.tick(interval);
|
||||
t.mock.timers.tick(interval);
|
||||
t.mock.timers.tick(interval);
|
||||
t.mock.timers.tick(interval);
|
||||
t.mock.timers.tick(interval);
|
||||
|
||||
t.mock.timers.tick(200);
|
||||
t.mock.timers.tick(200);
|
||||
t.mock.timers.tick(200);
|
||||
t.mock.timers.tick(200);
|
||||
const timeResults = await r;
|
||||
assert.strictEqual(timeResults.length, expectedIterations);
|
||||
for (let it = 1; it < expectedIterations; it++) {
|
||||
assert.strictEqual(timeResults[it - 1], startedAt + (interval * it));
|
||||
}
|
||||
});
|
||||
|
||||
nodeTimers.clearInterval(id);
|
||||
it('should abort operation given an abort controller signal', async (t) => {
|
||||
t.mock.timers.enable({ apis: ['setInterval'] });
|
||||
|
||||
assert.strictEqual(fn.mock.callCount(), 4);
|
||||
});
|
||||
const interval = 100;
|
||||
const abortController = new AbortController();
|
||||
const intervalIterator = nodeTimersPromises.setInterval(interval, Date.now(), {
|
||||
signal: abortController.signal,
|
||||
});
|
||||
|
||||
it('should work with the same params as the original timers.setInterval', (t) => {
|
||||
t.mock.timers.enable(['setInterval']);
|
||||
const fn = t.mock.fn();
|
||||
const args = ['a', 'b', 'c'];
|
||||
const id = nodeTimers.setInterval(fn, 200, ...args);
|
||||
const first = intervalIterator.next();
|
||||
const second = intervalIterator.next();
|
||||
|
||||
t.mock.timers.tick(200);
|
||||
t.mock.timers.tick(200);
|
||||
t.mock.timers.tick(200);
|
||||
t.mock.timers.tick(200);
|
||||
t.mock.timers.tick(interval);
|
||||
abortController.abort();
|
||||
t.mock.timers.tick(interval);
|
||||
|
||||
nodeTimers.clearInterval(id);
|
||||
const firstResult = await first;
|
||||
// Interval * 2 because value can be a little bit greater than interval
|
||||
assert.ok(firstResult.value < Date.now() + interval * 2);
|
||||
assert.strictEqual(firstResult.done, false);
|
||||
|
||||
assert.strictEqual(fn.mock.callCount(), 4);
|
||||
assert.deepStrictEqual(fn.mock.calls[0].arguments, args);
|
||||
assert.deepStrictEqual(fn.mock.calls[1].arguments, args);
|
||||
assert.deepStrictEqual(fn.mock.calls[2].arguments, args);
|
||||
assert.deepStrictEqual(fn.mock.calls[3].arguments, args);
|
||||
await assert.rejects(() => second, {
|
||||
name: 'AbortError',
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
it('should abort operation when .abort is called before calling setInterval', async (t) => {
|
||||
t.mock.timers.enable({ apis: ['setInterval'] });
|
||||
|
||||
describe('clearInterval Suite', () => {
|
||||
it('should not advance in time if clearInterval was invoked', (t) => {
|
||||
t.mock.timers.enable(['setInterval']);
|
||||
const interval = 100;
|
||||
const abortController = new AbortController();
|
||||
abortController.abort();
|
||||
const intervalIterator = nodeTimersPromises.setInterval(interval, Date.now(), {
|
||||
signal: abortController.signal,
|
||||
});
|
||||
|
||||
const fn = mock.fn();
|
||||
const { setInterval, clearInterval } = nodeTimers;
|
||||
const id = setInterval(fn, 200);
|
||||
clearInterval(id);
|
||||
t.mock.timers.tick(200);
|
||||
const first = intervalIterator.next();
|
||||
t.mock.timers.tick(interval);
|
||||
|
||||
assert.strictEqual(fn.mock.callCount(), 0);
|
||||
});
|
||||
});
|
||||
await assert.rejects(() => first, {
|
||||
name: 'AbortError',
|
||||
});
|
||||
});
|
||||
|
||||
describe('setImmediate Suite', () => {
|
||||
it('should keep setImmediate working if timers are disabled', (t, done) => {
|
||||
const now = Date.now();
|
||||
const timeout = 2;
|
||||
const expected = () => now - timeout;
|
||||
nodeTimers.setImmediate(common.mustCall(() => {
|
||||
assert.strictEqual(now - timeout, expected());
|
||||
done();
|
||||
}, 1));
|
||||
});
|
||||
it('should abort operation given an abort controller signal on a real use case', async (t) => {
|
||||
t.mock.timers.enable({ apis: ['setInterval'] });
|
||||
const controller = new AbortController();
|
||||
const signal = controller.signal;
|
||||
const interval = 200;
|
||||
const expectedIterations = 2;
|
||||
const startedAt = Date.now();
|
||||
const timeResults = [];
|
||||
async function run() {
|
||||
const it = nodeTimersPromises.setInterval(interval, startedAt, { signal });
|
||||
for await (const time of it) {
|
||||
timeResults.push(time);
|
||||
if (timeResults.length === 5) break;
|
||||
}
|
||||
}
|
||||
|
||||
it('should work with the same params as the original setImmediate', (t) => {
|
||||
t.mock.timers.enable(['setImmediate']);
|
||||
const fn = t.mock.fn();
|
||||
const args = ['a', 'b', 'c'];
|
||||
nodeTimers.setImmediate(fn, ...args);
|
||||
t.mock.timers.tick(9999);
|
||||
const r = run();
|
||||
t.mock.timers.tick(interval);
|
||||
t.mock.timers.tick(interval);
|
||||
controller.abort();
|
||||
t.mock.timers.tick(interval);
|
||||
t.mock.timers.tick(interval);
|
||||
t.mock.timers.tick(interval);
|
||||
t.mock.timers.tick(interval);
|
||||
|
||||
assert.strictEqual(fn.mock.callCount(), 1);
|
||||
assert.deepStrictEqual(fn.mock.calls[0].arguments, args);
|
||||
});
|
||||
await assert.rejects(() => r, {
|
||||
name: 'AbortError',
|
||||
});
|
||||
assert.strictEqual(timeResults.length, expectedIterations);
|
||||
|
||||
it('should not advance in time if clearImmediate was invoked', (t) => {
|
||||
t.mock.timers.enable(['setImmediate']);
|
||||
|
||||
const id = nodeTimers.setImmediate(common.mustNotCall());
|
||||
nodeTimers.clearImmediate(id);
|
||||
t.mock.timers.tick(200);
|
||||
});
|
||||
|
||||
it('should advance in time and trigger timers when calling the .tick function', (t) => {
|
||||
t.mock.timers.enable(['setImmediate']);
|
||||
nodeTimers.setImmediate(common.mustCall(1));
|
||||
t.mock.timers.tick(0);
|
||||
});
|
||||
|
||||
it('should execute in order if setImmediate is called multiple times', (t) => {
|
||||
t.mock.timers.enable(['setImmediate']);
|
||||
const order = [];
|
||||
const fn1 = t.mock.fn(common.mustCall(() => order.push('f1'), 1));
|
||||
const fn2 = t.mock.fn(common.mustCall(() => order.push('f2'), 1));
|
||||
|
||||
nodeTimers.setImmediate(fn1);
|
||||
nodeTimers.setImmediate(fn2);
|
||||
|
||||
t.mock.timers.tick(0);
|
||||
|
||||
assert.deepStrictEqual(order, ['f1', 'f2']);
|
||||
});
|
||||
|
||||
it('should execute setImmediate first if setTimeout was also called', (t) => {
|
||||
t.mock.timers.enable(['setImmediate', 'setTimeout']);
|
||||
const order = [];
|
||||
const fn1 = t.mock.fn(common.mustCall(() => order.push('f1'), 1));
|
||||
const fn2 = t.mock.fn(common.mustCall(() => order.push('f2'), 1));
|
||||
|
||||
nodeTimers.setTimeout(fn2, 0);
|
||||
nodeTimers.setImmediate(fn1);
|
||||
|
||||
t.mock.timers.tick(100);
|
||||
|
||||
assert.deepStrictEqual(order, ['f1', 'f2']);
|
||||
for (let it = 1; it < expectedIterations; it++) {
|
||||
assert.strictEqual(timeResults[it - 1], startedAt + (interval * it));
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('timers/promises', () => {
|
||||
describe('setTimeout Suite', () => {
|
||||
it('should advance in time and trigger timers when calling the .tick function multiple times', async (t) => {
|
||||
t.mock.timers.enable(['setTimeout']);
|
||||
|
||||
const p = nodeTimersPromises.setTimeout(2000);
|
||||
|
||||
t.mock.timers.tick(1000);
|
||||
t.mock.timers.tick(500);
|
||||
t.mock.timers.tick(500);
|
||||
t.mock.timers.tick(500);
|
||||
|
||||
p.then(common.mustCall((result) => {
|
||||
assert.strictEqual(result, undefined);
|
||||
}));
|
||||
});
|
||||
|
||||
it('should work with the same params as the original timers/promises/setTimeout', async (t) => {
|
||||
t.mock.timers.enable(['setTimeout']);
|
||||
const expectedResult = 'result';
|
||||
const controller = new AbortController();
|
||||
const p = nodeTimersPromises.setTimeout(2000, expectedResult, {
|
||||
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, expectedResult);
|
||||
});
|
||||
|
||||
it('should abort operation if timers/promises/setTimeout received an aborted signal', async (t) => {
|
||||
t.mock.timers.enable(['setTimeout']);
|
||||
const expectedResult = 'result';
|
||||
const controller = new AbortController();
|
||||
const p = nodeTimersPromises.setTimeout(2000, expectedResult, {
|
||||
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 wasn\'t called', async (t) => {
|
||||
t.mock.timers.enable(['setTimeout']);
|
||||
const expectedResult = 'result';
|
||||
const controller = new AbortController();
|
||||
const p = nodeTimersPromises.setTimeout(2000, expectedResult, {
|
||||
ref: true,
|
||||
signal: controller.signal
|
||||
});
|
||||
|
||||
controller.abort();
|
||||
|
||||
await assert.rejects(() => p, {
|
||||
name: 'AbortError',
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('should abort operation when .abort is called before calling setTimeout', async (t) => {
|
||||
t.mock.timers.enable(['setTimeout']);
|
||||
const expectedResult = 'result';
|
||||
const controller = new AbortController();
|
||||
controller.abort();
|
||||
const p = nodeTimersPromises.setTimeout(2000, expectedResult, {
|
||||
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(['setTimeout']);
|
||||
const expectedResult = 'result';
|
||||
const p = nodeTimersPromises.setTimeout(2000, expectedResult, {
|
||||
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'] });
|
||||
const date = new Date();
|
||||
assert.strictEqual(date.getTime(), 0);
|
||||
assert.strictEqual(Date.now(), 0);
|
||||
});
|
||||
|
||||
describe('setInterval Suite', () => {
|
||||
it('should tick three times using fake setInterval', async (t) => {
|
||||
t.mock.timers.enable(['setInterval']);
|
||||
|
||||
const interval = 100;
|
||||
const intervalIterator = nodeTimersPromises.setInterval(interval, Date.now());
|
||||
|
||||
const first = intervalIterator.next();
|
||||
const second = intervalIterator.next();
|
||||
const third = intervalIterator.next();
|
||||
|
||||
t.mock.timers.tick(interval);
|
||||
t.mock.timers.tick(interval);
|
||||
t.mock.timers.tick(interval);
|
||||
t.mock.timers.tick(interval);
|
||||
|
||||
const results = await Promise.all([
|
||||
first,
|
||||
second,
|
||||
third,
|
||||
]);
|
||||
|
||||
const finished = await intervalIterator.return();
|
||||
assert.deepStrictEqual(finished, { done: true, value: undefined });
|
||||
|
||||
results.forEach((result) => {
|
||||
assert.strictEqual(typeof result.value, 'number');
|
||||
assert.strictEqual(result.done, false);
|
||||
});
|
||||
|
||||
});
|
||||
it('should tick five times testing a real use case', async (t) => {
|
||||
|
||||
t.mock.timers.enable(['setInterval']);
|
||||
|
||||
const expectedIterations = 5;
|
||||
const interval = 1000;
|
||||
const startedAt = Date.now();
|
||||
async function run() {
|
||||
const times = [];
|
||||
for await (const time of nodeTimersPromises.setInterval(interval, startedAt)) {
|
||||
times.push(time);
|
||||
if (times.length === expectedIterations) break;
|
||||
|
||||
}
|
||||
return times;
|
||||
}
|
||||
|
||||
const r = run();
|
||||
t.mock.timers.tick(interval);
|
||||
t.mock.timers.tick(interval);
|
||||
t.mock.timers.tick(interval);
|
||||
t.mock.timers.tick(interval);
|
||||
t.mock.timers.tick(interval);
|
||||
|
||||
const timeResults = await r;
|
||||
assert.strictEqual(timeResults.length, expectedIterations);
|
||||
for (let it = 1; it < expectedIterations; it++) {
|
||||
assert.strictEqual(timeResults[it - 1], startedAt + (interval * it));
|
||||
}
|
||||
});
|
||||
|
||||
it('should abort operation given an abort controller signal', async (t) => {
|
||||
t.mock.timers.enable(['setInterval']);
|
||||
|
||||
const interval = 100;
|
||||
const abortController = new AbortController();
|
||||
const intervalIterator = nodeTimersPromises.setInterval(interval, Date.now(), {
|
||||
signal: abortController.signal
|
||||
});
|
||||
|
||||
const first = intervalIterator.next();
|
||||
const second = intervalIterator.next();
|
||||
|
||||
t.mock.timers.tick(interval);
|
||||
abortController.abort();
|
||||
t.mock.timers.tick(interval);
|
||||
|
||||
const firstResult = await first;
|
||||
// Interval * 2 because value can be a little bit greater than interval
|
||||
assert.ok(firstResult.value < Date.now() + interval * 2);
|
||||
assert.strictEqual(firstResult.done, false);
|
||||
|
||||
await assert.rejects(() => second, {
|
||||
name: 'AbortError',
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('should abort operation when .abort is called before calling setInterval', async (t) => {
|
||||
t.mock.timers.enable(['setInterval']);
|
||||
|
||||
const interval = 100;
|
||||
const abortController = new AbortController();
|
||||
abortController.abort();
|
||||
const intervalIterator = nodeTimersPromises.setInterval(interval, Date.now(), {
|
||||
signal: abortController.signal
|
||||
});
|
||||
|
||||
const first = intervalIterator.next();
|
||||
t.mock.timers.tick(interval);
|
||||
|
||||
await assert.rejects(() => first, {
|
||||
name: 'AbortError',
|
||||
});
|
||||
});
|
||||
|
||||
it('should abort operation given an abort controller signal on a real use case', async (t) => {
|
||||
t.mock.timers.enable(['setInterval']);
|
||||
const controller = new AbortController();
|
||||
const signal = controller.signal;
|
||||
const interval = 200;
|
||||
const expectedIterations = 2;
|
||||
const startedAt = Date.now();
|
||||
const timeResults = [];
|
||||
async function run() {
|
||||
const it = nodeTimersPromises.setInterval(interval, startedAt, { signal });
|
||||
for await (const time of it) {
|
||||
timeResults.push(time);
|
||||
if (timeResults.length === 5) break;
|
||||
}
|
||||
}
|
||||
|
||||
const r = run();
|
||||
t.mock.timers.tick(interval);
|
||||
t.mock.timers.tick(interval);
|
||||
controller.abort();
|
||||
t.mock.timers.tick(interval);
|
||||
t.mock.timers.tick(interval);
|
||||
t.mock.timers.tick(interval);
|
||||
t.mock.timers.tick(interval);
|
||||
|
||||
await assert.rejects(() => r, {
|
||||
name: 'AbortError',
|
||||
});
|
||||
assert.strictEqual(timeResults.length, expectedIterations);
|
||||
|
||||
for (let it = 1; it < expectedIterations; it++) {
|
||||
assert.strictEqual(timeResults[it - 1], startedAt + (interval * it));
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
it('should throw an error if setTime is called without enabling timers', (t) => {
|
||||
assert.throws(
|
||||
() => {
|
||||
t.mock.timers.setTime(100);
|
||||
},
|
||||
{ code: 'ERR_INVALID_STATE' }
|
||||
);
|
||||
});
|
||||
|
||||
describe('setImmediate Suite', () => {
|
||||
it('should advance in time and trigger timers when calling the .tick function multiple times', (t, done) => {
|
||||
t.mock.timers.enable(['setImmediate']);
|
||||
const p = nodeTimersPromises.setImmediate();
|
||||
it('should throw an error if epoch passed to enable is not valid', (t) => {
|
||||
assert.throws(
|
||||
() => {
|
||||
t.mock.timers.enable({ now: -1 });
|
||||
},
|
||||
{ code: 'ERR_INVALID_ARG_VALUE' }
|
||||
);
|
||||
|
||||
t.mock.timers.tick(5555);
|
||||
assert.throws(
|
||||
() => {
|
||||
t.mock.timers.enable({ now: 'string' });
|
||||
},
|
||||
{ code: 'ERR_INVALID_ARG_TYPE' }
|
||||
);
|
||||
|
||||
p.then(common.mustCall((result) => {
|
||||
assert.strictEqual(result, undefined);
|
||||
done();
|
||||
}, 1));
|
||||
});
|
||||
assert.throws(
|
||||
() => {
|
||||
t.mock.timers.enable({ now: NaN });
|
||||
},
|
||||
{ code: 'ERR_INVALID_ARG_VALUE' }
|
||||
);
|
||||
});
|
||||
|
||||
it('should work with the same params as the original timers/promises/setImmediate', async (t) => {
|
||||
t.mock.timers.enable(['setImmediate']);
|
||||
const expectedResult = 'result';
|
||||
const controller = new AbortController();
|
||||
const p = nodeTimersPromises.setImmediate(expectedResult, {
|
||||
ref: true,
|
||||
signal: controller.signal
|
||||
});
|
||||
it('should replace the original Date with the mocked one', (t) => {
|
||||
t.mock.timers.enable({ apis: ['Date'] });
|
||||
assert.ok(Date.isMock);
|
||||
});
|
||||
|
||||
t.mock.timers.tick(500);
|
||||
it('should return the ticked time when calling Date.now after tick', (t) => {
|
||||
t.mock.timers.enable({ apis: ['Date'] });
|
||||
const time = 100;
|
||||
t.mock.timers.tick(time);
|
||||
assert.strictEqual(Date.now(), time);
|
||||
});
|
||||
|
||||
const result = await p;
|
||||
assert.strictEqual(result, expectedResult);
|
||||
});
|
||||
it('should return the Date as string when calling it as a function', (t) => {
|
||||
t.mock.timers.enable({ apis: ['Date'] });
|
||||
const returned = Date();
|
||||
// Matches the format: 'Mon Jan 01 1970 00:00:00'
|
||||
// We don't care about the date, just the format
|
||||
assert.ok(/\w{3}\s\w{3}\s\d{1,2}\s\d{2,4}\s\d{1,2}:\d{2}:\d{2}/.test(returned));
|
||||
});
|
||||
|
||||
it('should abort operation if timers/promises/setImmediate received an aborted signal', async (t) => {
|
||||
t.mock.timers.enable(['setImmediate']);
|
||||
const expectedResult = 'result';
|
||||
const controller = new AbortController();
|
||||
const p = nodeTimersPromises.setImmediate(expectedResult, {
|
||||
ref: true,
|
||||
signal: controller.signal
|
||||
});
|
||||
it('should return the date with different argument calls', (t) => {
|
||||
t.mock.timers.enable({ apis: ['Date'] });
|
||||
assert.strictEqual(new Date(0).getTime(), 0);
|
||||
assert.strictEqual(new Date(100).getTime(), 100);
|
||||
assert.strictEqual(new Date('1970-01-01T00:00:00.000Z').getTime(), 0);
|
||||
assert.strictEqual(new Date(1970, 0).getFullYear(), 1970);
|
||||
assert.strictEqual(new Date(1970, 0).getMonth(), 0);
|
||||
assert.strictEqual(new Date(1970, 0, 1).getDate(), 1);
|
||||
assert.strictEqual(new Date(1970, 0, 1, 11).getHours(), 11);
|
||||
assert.strictEqual(new Date(1970, 0, 1, 11, 10).getMinutes(), 10);
|
||||
assert.strictEqual(new Date(1970, 0, 1, 11, 10, 45).getSeconds(), 45);
|
||||
assert.strictEqual(new Date(1970, 0, 1, 11, 10, 45, 898).getMilliseconds(), 898);
|
||||
assert.strictEqual(new Date(1970, 0, 1, 11, 10, 45, 898).toDateString(), 'Thu Jan 01 1970');
|
||||
});
|
||||
|
||||
controller.abort();
|
||||
t.mock.timers.tick(0);
|
||||
it('should return native code when calling Date.toString', (t) => {
|
||||
t.mock.timers.enable({ apis: ['Date'] });
|
||||
assert.strictEqual(Date.toString(), 'function Date() { [native code] }');
|
||||
});
|
||||
|
||||
await assert.rejects(() => p, {
|
||||
name: 'AbortError',
|
||||
});
|
||||
it('should start with a custom epoch if the second argument is specified', (t) => {
|
||||
t.mock.timers.enable({ apis: ['Date'], now: 100 });
|
||||
const date1 = new Date();
|
||||
assert.strictEqual(date1.getTime(), 100);
|
||||
|
||||
});
|
||||
it('should abort operation even if the .tick wasn\'t called', async (t) => {
|
||||
t.mock.timers.enable(['setImmediate']);
|
||||
const expectedResult = 'result';
|
||||
const controller = new AbortController();
|
||||
const p = nodeTimersPromises.setImmediate(expectedResult, {
|
||||
ref: true,
|
||||
signal: controller.signal
|
||||
});
|
||||
t.mock.timers.reset();
|
||||
t.mock.timers.enable({ apis: ['Date'], now: new Date(200) });
|
||||
const date2 = new Date();
|
||||
assert.strictEqual(date2.getTime(), 200);
|
||||
});
|
||||
|
||||
controller.abort();
|
||||
it('should replace epoch if setTime is lesser than now and not tick', (t) => {
|
||||
t.mock.timers.enable();
|
||||
const fn = t.mock.fn();
|
||||
const id = setTimeout(fn, 1000);
|
||||
t.mock.timers.setTime(800);
|
||||
assert.strictEqual(Date.now(), 800);
|
||||
t.mock.timers.setTime(500);
|
||||
assert.strictEqual(Date.now(), 500);
|
||||
assert.strictEqual(fn.mock.callCount(), 0);
|
||||
clearTimeout(id);
|
||||
});
|
||||
|
||||
await assert.rejects(() => p, {
|
||||
name: 'AbortError',
|
||||
});
|
||||
});
|
||||
|
||||
it('should abort operation when .abort is called before calling setImmediate', async (t) => {
|
||||
t.mock.timers.enable(['setImmediate']);
|
||||
const expectedResult = 'result';
|
||||
const controller = new AbortController();
|
||||
controller.abort();
|
||||
const p = nodeTimersPromises.setImmediate(expectedResult, {
|
||||
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(['setImmediate']);
|
||||
const expectedResult = 'result';
|
||||
const p = nodeTimersPromises.setImmediate(expectedResult, {
|
||||
ref: true,
|
||||
signal: {}
|
||||
});
|
||||
|
||||
await assert.rejects(() => p, {
|
||||
name: 'TypeError',
|
||||
code: 'ERR_INVALID_ARG_TYPE'
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('should execute in order if setImmediate is called multiple times', async (t) => {
|
||||
t.mock.timers.enable(['setImmediate']);
|
||||
|
||||
const p1 = nodeTimersPromises.setImmediate('fn1');
|
||||
const p2 = nodeTimersPromises.setImmediate('fn2');
|
||||
|
||||
t.mock.timers.tick(0);
|
||||
|
||||
const results = await Promise.race([p1, p2]);
|
||||
|
||||
assert.strictEqual(results, 'fn1');
|
||||
});
|
||||
it('should not tick time when setTime is called', (t) => {
|
||||
t.mock.timers.enable();
|
||||
const fn = t.mock.fn();
|
||||
const id = setTimeout(fn, 1000);
|
||||
t.mock.timers.setTime(1200);
|
||||
assert.strictEqual(Date.now(), 1200);
|
||||
assert.strictEqual(fn.mock.callCount(), 0);
|
||||
clearTimeout(id);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user