diff --git a/docs/api/vi.md b/docs/api/vi.md index 3d84ea781bd9..913251988ef9 100644 --- a/docs/api/vi.md +++ b/docs/api/vi.md @@ -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. diff --git a/docs/guide/migration.md b/docs/guide/migration.md index 972aa5927f57..0e7cb33258f5 100644 --- a/docs/guide/migration.md +++ b/docs/guide/migration.md @@ -147,6 +147,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: diff --git a/docs/guide/mocking.md b/docs/guide/mocking.md index 57d2abb22bf0..6f93ec314bb5 100644 --- a/docs/guide/mocking.md +++ b/docs/guide/mocking.md @@ -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() ``` diff --git a/packages/vitest/src/integrations/mock/date.ts b/packages/vitest/src/integrations/mock/date.ts deleted file mode 100644 index 97cbb1241fb6..000000000000 --- a/packages/vitest/src/integrations/mock/date.ts +++ /dev/null @@ -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 -} diff --git a/packages/vitest/src/integrations/mock/timers.ts b/packages/vitest/src/integrations/mock/timers.ts index 688441eb08cb..89e502814c5a 100644 --- a/packages/vitest/src/integrations/mock/timers.ts +++ b/packages/vitest/src/integrations/mock/timers.ts @@ -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, + }) + } } } diff --git a/packages/vitest/src/runtime/console.ts b/packages/vitest/src/runtime/console.ts index ff4b6aa8cb47..ca805d491e8e 100644 --- a/packages/vitest/src/runtime/console.ts +++ b/packages/vitest/src/runtime/console.ts @@ -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) { diff --git a/test/unit/test/timers-temporal.test.ts b/test/unit/test/timers-temporal.test.ts index e10eaac66674..697e8e012da9 100644 --- a/test/unit/test/timers-temporal.test.ts +++ b/test/unit/test/timers-temporal.test.ts @@ -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) +})