feat!: enable mocking Temporal without fake timers (#10757)

Co-authored-by: Hiroshi Ogawa <[email protected]>
Co-authored-by: OpenCode <[email protected]>
This commit is contained in:
fabon
2026-07-29 09:32:29 +02:00
committed by GitHub
co-authored by Hiroshi Ogawa OpenCode
parent b3b6df68b9
commit ec0bf4499a
7 changed files with 52 additions and 118 deletions
+1 -1
View File
@@ -1159,7 +1159,7 @@ await vi.runOnlyPendingTimersAsync()
function setSystemTime(date: string | number | Date): Vitest
```
If fake timers are enabled, this method simulates a user changing the system clock (will affect date related API like `hrtime`, `performance.now` or `new Date()`) - however, it will not fire any timers. If fake timers are not enabled, this method will only mock `Date.*` calls.
If fake timers are enabled, this method simulates a user changing the system clock (will affect date related API like `hrtime`, `performance.now` or `new Date()`) - however, it will not fire any timers. If fake timers are not enabled, this method will only mock `Date.*` and `Temporal.Now.*` calls.
Useful if you need to test anything that depends on the current date - for example [Luxon](https://github.com/moment/luxon/) calls inside your code.
+9
View File
@@ -201,6 +201,15 @@ Temporal.Now.instant().epochMilliseconds // 0 (was the real time in v4)
vi.useFakeTimers({ toNotFake: ['Temporal'] })
```
### `setSystemTime` Now Mocks Temporal
Previously `vi.setSystemTime` mocked only `Date` without fake timers, but now it also mocks methods of `Temporal.Now`.
```ts
vi.setSystemTime(0)
Temporal.Now.instant().epochMilliseconds // 0 (was the real time in v4)
```
### `toThrow("")` Matches Any Error Message
[`toThrow`](/api/expect#tothrow) (and its alias `toThrowError`) treats a string argument as a substring of the error message. In Vitest 4 an empty string was special-cased to the `/^$/` pattern, so it matched only an error whose message was empty. It now behaves like any other substring, and an empty string is contained in every message:
+3 -1
View File
@@ -171,7 +171,7 @@ Don't forget that this only [mocks _external_ access](/guide/mocking/modules#moc
### Mock the current date
To mock `Date`'s time, you can use `vi.setSystemTime` helper function. This value will **not** automatically reset between different tests.
To mock `Date` and `Temporal`'s time, you can use `vi.setSystemTime` helper function. This value will **not** automatically reset between different tests.
Beware that using `vi.useFakeTimers` also changes the `Date`'s time.
@@ -180,6 +180,8 @@ const mockDate = new Date(2022, 0, 1)
vi.setSystemTime(mockDate)
const now = new Date()
expect(now.valueOf()).toBe(mockDate.valueOf())
const nowInstant = Temporal.Now.instant()
expect(nowInstant.epochMilliseconds).toBe(mockDate.valueOf())
// reset mocked time
vi.useRealTimers()
```
@@ -1,110 +0,0 @@
/* Ported from https://github.com/boblauer/MockDate/blob/master/src/mockdate.ts */
/*
The MIT License (MIT)
Copyright (c) 2014 Bob Lauer
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
export const RealDate: DateConstructor = Date
let now: null | number = null
class MockDate extends RealDate {
constructor()
constructor(value: number | string)
constructor(
year: number,
month: number,
date?: number,
hours?: number,
minutes?: number,
seconds?: number,
ms?: number
)
constructor(
y?: number | string,
m?: number,
d?: number,
h?: number,
M?: number,
s?: number,
ms?: number,
) {
super()
let date: any
switch (arguments.length) {
case 0:
if (now !== null) {
date = new RealDate(now.valueOf())
}
else {
date = new RealDate()
}
break
case 1:
date = new RealDate(y!)
break
default:
d = typeof d === 'undefined' ? 1 : d
h = h || 0
M = M || 0
s = s || 0
ms = ms || 0
date = new RealDate(y as number, m!, d, h, M, s, ms)
break
}
Object.setPrototypeOf(date, MockDate.prototype)
return date
}
}
MockDate.UTC = RealDate.UTC
MockDate.now = function () {
return new MockDate().valueOf()
}
MockDate.parse = function (dateString) {
return RealDate.parse(dateString)
}
MockDate.toString = function () {
return RealDate.toString()
}
export function mockDate(date: string | number | Date): void {
const dateObj = new RealDate(date.valueOf())
if (Number.isNaN(dateObj.getTime())) {
throw new TypeError(`mockdate: The time set is an invalid date: ${date}`)
}
// @ts-expect-error global
globalThis.Date = MockDate
now = dateObj.valueOf()
}
export function resetDate(): void {
globalThis.Date = RealDate
}
@@ -13,7 +13,8 @@ import type {
} from '@sinonjs/fake-timers'
import { withGlobal } from '@sinonjs/fake-timers'
import { isChildProcess } from '../../runtime/utils'
import { mockDate, RealDate, resetDate } from './date'
const RealDate = globalThis.Date
export class FakeTimers {
private _global: typeof globalThis
@@ -134,7 +135,7 @@ export class FakeTimers {
useRealTimers(): void {
if (this._fakingDate) {
resetDate()
this._clock.uninstall()
this._fakingDate = null
}
@@ -147,7 +148,7 @@ export class FakeTimers {
useFakeTimers(): void {
const fakeDate = this._fakingDate || Date.now()
if (this._fakingDate) {
resetDate()
this._clock.uninstall()
this._fakingDate = null
}
@@ -197,8 +198,19 @@ export class FakeTimers {
this._clock.setSystemTime(date)
}
else {
this._fakingDate = date ?? new Date(this.getRealSystemTime())
mockDate(this._fakingDate)
const newFakingDate = date ?? new Date(this.getRealSystemTime())
if (this._fakingDate) {
this._fakingDate = newFakingDate
this._clock.setSystemTime(newFakingDate)
}
else {
this._fakingDate = newFakingDate
this._clock = this._fakeTimers.install({
now: newFakingDate,
toFake: ['Date', 'Temporal'],
ignoreMissingTimers: true,
})
}
}
}
+2 -1
View File
@@ -4,9 +4,10 @@ import { relative } from 'node:path'
import { Writable } from 'node:stream'
import { getSafeTimers } from '@vitest/utils/timers'
import c from 'tinyrainbow'
import { RealDate } from '../integrations/mock/date'
import { getWorkerState } from './utils'
const RealDate = globalThis.Date
export const UNKNOWN_TEST_ID = '__vitest__unknown_test__'
function getTaskIdByStack(root: string) {
+20
View File
@@ -24,3 +24,23 @@ it('Temporal.Now follows fake timers', () => {
expect(globalThis.Temporal).toBe(real)
expect(globalThis.Temporal.Now.instant().epochMilliseconds).not.toBe(1234)
})
it('Temporal.Now follows setSystemTime without fake timers', () => {
const real = globalThis.Temporal
expect(vi.isFakeTimers()).toBe(false)
vi.setSystemTime(0)
expect(vi.isFakeTimers()).toBe(false)
expect(globalThis.Temporal).not.toBe(real)
expect(globalThis.Temporal.Now.instant().epochMilliseconds).toBe(0)
vi.setSystemTime(1234)
expect(vi.isFakeTimers()).toBe(false)
expect(globalThis.Temporal.Now.instant().epochMilliseconds).toBe(1234)
// restore
vi.useRealTimers()
expect(vi.isFakeTimers()).toBe(false)
expect(globalThis.Temporal).toBe(real)
expect(globalThis.Temporal.Now.instant().epochMilliseconds).not.toBe(1234)
})