feat!: update @sinonjs/fake-timers and support mocking Temporal#10654
Conversation
Bump @sinonjs/fake-timers to 15.4.0, which adds support for mocking the Temporal API. When Temporal is present on the global object (Node.js >= 26 by default, or via a globally installed polyfill), vi.useFakeTimers() now fakes Temporal.Now alongside Date. - regenerate the __vitest_required__ patch against 15.4.0 - document the behavior change in the v5 migration guide - add Temporal/Intl to the documented toFake/toNotFake unions - add a test using temporal-polyfill/global Closes vitest-dev#10345 Co-authored-by: OpenCode (claude-opus-4-8) <[email protected]>
✅ Deploy Preview for vitest-dev ready!Built without sensitive environment variables
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
|
||
| ### Fake Timers Now Mock `Temporal` | ||
|
|
||
| Vitest now mocks the [`Temporal`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal) API alongside `Date` when fake timers are enabled, following the [`@sinonjs/fake-timers` v15.4 update](https://github.com/sinonjs/fake-timers/blob/main/CHANGELOG.md#1540--2026-05-05). This only takes effect when `Temporal` is available on the global object — either natively (Node.js >= 26 by default, behind `--harmony-temporal` on older versions, and supporting browsers) or through a globally installed polyfill such as `import 'temporal-polyfill/global'`. |
There was a problem hiding this comment.
@js-temporal/polyfill global installation breaks useFakeTimers and appears it's due to @js-temporal/polyfill being not fully compliant about configurable property. I'll report to there later.
issue summary
Title: Temporal.Now is non-configurable (frozen namespace) — deviates from spec, breaks reconfiguration tools
Summary
The global Temporal namespace produced by this polyfill is frozen, so Temporal.Now is non-reconfigurable. This deviates from the Temporal spec and breaks tooling that legitimately redefines Temporal.Now (e.g. @sinonjs/fake-timers ≥15.4, and therefore Vitest/Jest fake timers).
Observed descriptor
Object.getOwnPropertyDescriptor(Temporal, 'Now')
// { value: …, writable: false, enumerable: true, configurable: false }
Object.isExtensible(Temporal) // false(Measured on @js-temporal/[email protected].)
Expected per spec
Temporal proposal §1.3.1 assigns Temporal.Now no custom attributes, so the standard built-in default applies: { writable: true, enumerable: false, configurable: true }. Two mismatches:
configurableshould betrue(this is the breaking one)enumerableshould befalse(currentlytrue)
For comparison, native V8 (Node ≥26) and temporal-polyfill both expose Temporal.Now as configurable: true.
Impact / repro
import '@js-temporal/polyfill' // installs frozen Temporal globally
import { install } from '@sinonjs/fake-timers'
install() // throws:
// TypeError: Cannot redefine property: Nowfake-timers builds a clock-backed Temporal by copying the namespace's own props and then Object.defineProperty(…, 'Now', …). Because the copied Now is non-configurable, the redefine throws. Any consumer that relies on the spec-guaranteed configurability hits this.
Suggested fix
Define Temporal.Now with { writable: true, enumerable: false, configurable: true } and avoid Object.freeze/non-extensible on the Temporal namespace (or at least leave Now reconfigurable), matching the spec and native engines.
Notes
- Only affects the polyfill installed on the global object; local
import { Temporal } from '@js-temporal/polyfill'is unaffected. - Environment:
@js-temporal/[email protected], Node v24.
There was a problem hiding this comment.
Reported to js-temporal/temporal-polyfill#363
| ## fakeTimers.toFake | ||
|
|
||
| - **Type:** `('setTimeout' | 'clearTimeout' | 'setImmediate' | 'clearImmediate' | 'setInterval' | 'clearInterval' | 'Date' | 'nextTick' | 'hrtime' | 'requestAnimationFrame' | 'cancelAnimationFrame' | 'requestIdleCallback' | 'cancelIdleCallback' | 'performance' | 'queueMicrotask')[]` | ||
| - **Type:** `('setTimeout' | 'clearTimeout' | 'setImmediate' | 'clearImmediate' | 'setInterval' | 'clearInterval' | 'Date' | 'nextTick' | 'hrtime' | 'requestAnimationFrame' | 'cancelAnimationFrame' | 'requestIdleCallback' | 'cancelIdleCallback' | 'performance' | 'queueMicrotask' | 'Intl' | 'Temporal')[]` |
There was a problem hiding this comment.
Drive-by fix to add also Intl that has been available for some time.
Seed the clock via the `now` option instead of a separate vi.setSystemTime() call in the Temporal test and migration doc. Co-authored-by: OpenCode (claude-opus-4-8) <[email protected]>
This reverts commit f9527e8.
|
Would be nice to also support |
|
Could you resolve the conflict? |
# Conflicts: # pnpm-lock.yaml
Bump the `sinon` catalog entry to ^22.0.0 so its `@sinonjs/fake-timers` dependency resolves to the same patched 15.4.0 used by vitest, removing the duplicate 15.3.2 install. Co-authored-by: OpenCode (claude-opus-4-8) <[email protected]>
| rollup: ^4.59.0 | ||
| semver: ^7.8.3 | ||
| sinon: ^21.0.3 | ||
| sinon: ^22.0.0 |
There was a problem hiding this comment.
bumped sion to dedupe @sinonjs/fake-timers
(sinon is used just for internal test)
Description
Temporal.Nowis not mocked when timer mocks are enabled #10345Bump @sinonjs/fake-timers to 15.4.0, which adds support for mocking the Temporal API. When Temporal is present on the global object (Node.js >= 26 by default, or via a globally installed polyfill), vi.useFakeTimers() now fakes Temporal.Now alongside Date.
Please don't delete this checklist! Before submitting the PR, please make sure you do the following:
pnpm-lock.yamlunless you introduce a new test example.Tests
pnpm test:ci.Documentation
pnpm run docscommand.Changesets
feat:,fix:,perf:,docs:, orchore:.