mirror of
https://github.com/hanzoai/vitest.git
synced 2026-09-24 17:04:25 +00:00
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:
committed by
GitHub
co-authored by
Hiroshi Ogawa
OpenCode
parent
b3b6df68b9
commit
ec0bf4499a
+1
-1
@@ -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.
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user