Skip to content
Open
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
40 changes: 40 additions & 0 deletions docs/api/vi.md
Original file line number Diff line number Diff line change
Expand Up @@ -1034,6 +1034,46 @@ The implementation is based internally on [`@sinonjs/fake-timers`](https://githu
But you can enable it by specifying the option in `toFake` argument: `vi.useFakeTimers({ toFake: ['nextTick', 'queueMicrotask'] })`.
:::

### vi.setTimerTickMode

- **Type:** `(mode: 'manual' | 'nextTimerAsync') => Vitest | (mode: 'interval', interval?: number) => Vitest`

Controls how fake timers are advanced.

- `manual`: The default behavior. Timers will only advance when you call one of `vi.advanceTimers...()` methods.
- `nextTimerAsync`: Timers will be advanced automatically to the next available timer after each macrotask.
- `interval`: Timers are advanced automatically by a specified interval.

When `mode` is `'interval'`, you can also provide an `interval` in milliseconds.

**Example:**

```ts
import { vi } from 'vitest'

vi.useFakeTimers()

// Manual mode (default)
vi.setTimerTickMode({ mode: 'manual' })

let i = 0
setInterval(() => console.log(++i), 50)

vi.advanceTimersByTime(150) // logs 1, 2, 3

// nextTimerAsync mode
vi.setTimerTickMode({ mode: 'nextTimerAsync' })

// Timers will advance automatically after each macrotask
await new Promise(resolve => setTimeout(resolve, 150)) // logs 4, 5, 6

// interval mode (default when 'fakeTimers.shouldAdvanceTime' is `true`)
vi.setTimerTickMode({ mode: 'interval', interval: 50 })

// Timers will advance automatically every 50ms
await new Promise(resolve => setTimeout(resolve, 150)) // logs 7, 8, 9
```

### vi.isFakeTimers {#vi-isfaketimers}

```ts
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@
]
},
"patchedDependencies": {
"@sinonjs/fake-timers@14.0.0": "patches/@sinonjs__fake-timers@14.0.0.patch",
"@sinonjs/fake-timers@15.0.0": "patches/@sinonjs__fake-timers@15.0.0.patch",
"[email protected]": "patches/[email protected]",
"@types/[email protected]": "patches/@[email protected]",
"[email protected]": "patches/[email protected]"
Expand Down
2 changes: 1 addition & 1 deletion packages/vitest/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@
"@antfu/install-pkg": "^1.1.0",
"@edge-runtime/vm": "^5.0.0",
"@jridgewell/trace-mapping": "catalog:",
"@sinonjs/fake-timers": "14.0.0",
"@sinonjs/fake-timers": "15.0.0",
"@types/debug": "catalog:",
"@types/estree": "catalog:",
"@types/istanbul-lib-coverage": "catalog:",
Expand Down
17 changes: 17 additions & 0 deletions packages/vitest/src/integrations/mock/timers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,23 @@ export class FakeTimers {
return 0
}

setTimerTickMode(mode: 'manual' | 'nextTimerAsync' | 'interval', interval?: number): void {
if (this._checkFakeTimers()) {
if (mode === 'manual') {
this._clock.setTickMode({ mode: 'manual' })
}
else if (mode === 'nextTimerAsync') {
this._clock.setTickMode({ mode: 'nextAsync' })
}
else if (mode === 'interval') {
this._clock.setTickMode({ mode: 'interval', delta: interval })
}
else {
throw new Error(`Invalid tick mode: ${mode}`)
}
}
}

configure(config: FakeTimerInstallOpts): void {
this._userConfig = config
}
Expand Down
15 changes: 15 additions & 0 deletions packages/vitest/src/integrations/vi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,16 @@ export interface VitestUtils {
*/
clearAllTimers: () => VitestUtils

/**
* Controls how fake timers are advanced.
* @param mode The mode to use for advancing timers.
* - `manual`: The default behavior. Timers will only advance when you call one of `vi.advanceTimers...()` methods.
* - `nextTimerAsync`: Timers will be advanced automatically to the next available timer after each macrotask.
* - `interval`: Timers are advanced automatically by a specified interval.
* @param interval The interval in milliseconds to use when `mode` is `'interval'`.
*/
setTimerTickMode: ((mode: 'manual' | 'nextTimerAsync') => VitestUtils) & ((mode: 'interval', interval?: number) => VitestUtils)

/**
* Creates a spy on a method or getter/setter of an object similar to [`vi.fn()`](https://vitest.dev/api/vi#vi-fn). It returns a [mock function](https://vitest.dev/api/mock).
* @example
Expand Down Expand Up @@ -553,6 +563,11 @@ function createVitest(): VitestUtils {
return utils
},

setTimerTickMode(mode: 'manual' | 'nextTimerAsync' | 'interval', interval?: number) {
timers().setTimerTickMode(mode, interval)
return utils
},

// mocks

spyOn,
Expand Down
4 changes: 2 additions & 2 deletions patches/@[email protected] → patches/@[email protected]
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
diff --git a/src/fake-timers-src.js b/src/fake-timers-src.js
index 11dab90bd4bafd8c3a232df20f82ec5bcf06e76d..1f633e6293bc4bff97ccf9a23214944c0f6f8395 100644
index a9bcfd1ca..942539085 100644
--- a/src/fake-timers-src.js
+++ b/src/fake-timers-src.js
@@ -2,14 +2,14 @@
Expand All @@ -20,7 +20,7 @@ index 11dab90bd4bafd8c3a232df20f82ec5bcf06e76d..1f633e6293bc4bff97ccf9a23214944c
} catch (e) {
// ignored
}
@@ -172,7 +172,7 @@ function withGlobal(_global) {
@@ -197,7 +197,7 @@ function withGlobal(_global) {
isPresent.hrtime && typeof _global.process.hrtime.bigint === "function";
isPresent.nextTick =
_global.process && typeof _global.process.nextTick === "function";
Expand Down
96 changes: 74 additions & 22 deletions patches/@[email protected]
Original file line number Diff line number Diff line change
@@ -1,40 +1,92 @@
diff --git a/index.d.ts b/index.d.ts
index 5aa018cde4336aca4dadefb8338549c378792e14..1b8136e5fb4c6666a46dbef765c9624d62fdb3a5 100644
index 5aa018cde4336aca4dadefb8338549c378792e14..d3a07cc966f10c70950fc8e2e8b897038a7a6a11 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -329,13 +329,15 @@ export interface FakeTimerInstallOpts {
now?: number | Date | undefined;

@@ -84,12 +84,12 @@ export interface GlobalTimers<TTimerId extends TimerId> {
*/
export interface NodeTimer {
/**
- * An array with names of global methods and APIs to fake. By default, `@sinonjs/fake-timers` does not replace `nextTick()` and `queueMicrotask()`.
- * For instance, `FakeTimers.install({ toFake: ['setTimeout', 'nextTick'] })` will fake only `setTimeout()` and `nextTick()`
+ * An array with names of global methods and APIs to fake.
+ * For instance, `vi.useFakeTimer({ toFake: ['setTimeout', 'performance'] })` will fake only `setTimeout()` and `performance.now()`
+ * @default everything available globally except `nextTick`
- * Stub method call. Does nothing.
+ * Marks the timer as referenced.
*/
toFake?: FakeMethod[] | undefined;
ref(): NodeTimer;

/**
- * The maximum number of timers that will be run when calling runAll() (default: 1000)
+ * The maximum number of timers that will be run when calling runAll()
+ * @default 10000
- * Stub method call. Does nothing.
+ * Marks the timer as unreferenced.
*/
loopLimit?: number | undefined;
unref(): NodeTimer;

@@ -352,10 +354,16 @@ export interface FakeTimerInstallOpts {
advanceTimeDelta?: number | undefined;
@@ -97,6 +97,11 @@ export interface NodeTimer {
* Refreshes the timer.
*/
refresh(): NodeTimer;
+
+ /**
+ * Returns true if the timer will keep the event loop active.
+ */
+ hasRef(): boolean;
}

/**
@@ -104,6 +109,18 @@ export interface NodeTimer {
*/
export type TimerId = number | NodeTimer;

+/**
+ * Allows configuring how the clock advances time, automatically or manually.
+ *
+ * - `manual`: Timers do not advance without explicit, manual calls to the tick APIs (`clock.nextAsync`, `clock.runAllAsync`, etc).
+ * - `nextAsync`: The clock will continuously break the event loop, then run the next timer until the mode changes.
+ * - `interval`: This is the same as specifying `shouldAdvanceTime: true` with an `advanceTimeDelta`. If the delta is not specified, 20 will be used by default.
+ */
+export type TimerTickMode =
+ | { mode: "manual" }
+ | { mode: "nextAsync" }
+ | { mode: "interval"; delta?: number };
+
/**
* Controls the flow of time.
*/
@@ -246,6 +263,18 @@ export interface FakeClock<TTimerId extends TimerId> extends GlobalTimers<TTimer
*/
runToLastAsync: () => Promise<number>;

+ /**
+ * Advances the clock by `time` milliseconds.
+ *
+ * Any timers within the affected range will be moved to the end of the range.
+ * Then, the clock will be advanced by `time` milliseconds, firing any timers
+ * that are now due.
+ *
+ * @param time How many ticks to advance by.
+ * @returns Fake milliseconds since the unix epoch.
+ */
+ jump: (time: number | string) => number;
+
/**
- * Tells FakeTimers to clear 'native' (i.e. not fake) timers by delegating to their respective handlers. These are not cleared by
- * default, leading to potentially unexpected behavior if timers existed prior to installing FakeTimers. (default: false)
+ * Tells FakeTimers to clear 'native' (i.e. not fake) timers by delegating to their respective handlers.
+ * @default true
* Simulates a user changing the system clock.
*
@@ -253,6 +282,12 @@ export interface FakeClock<TTimerId extends TimerId> extends GlobalTimers<TTimer
* @remarks This affects the current time but it does not in itself cause timers to fire.
*/
setSystemTime: (now?: number | Date) => void;
+
+ /**
+ * Allows configuring how the clock advances time, automatically or manually.
+ * @param tickModeConfig The new configuration for how the clock should tick.
+ */
+ setTickMode: (tickModeConfig: TimerTickMode) => void;
}

/**
@@ -356,6 +391,11 @@ export interface FakeTimerInstallOpts {
* default, leading to potentially unexpected behavior if timers existed prior to installing FakeTimers. (default: false)
*/
shouldClearNativeTimers?: boolean | undefined;
+
+ /**
+ * Don't throw error when asked to fake timers that are not present.
+ * @default false
+ * Tells FakeTimers to not throw an error when faking a timer that does not exist in the global object. (default: false)
+ */
+ ignoreMissingTimers?: boolean | undefined;
}
Expand Down
24 changes: 12 additions & 12 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading