diff --git a/packages/vitest/src/integrations/mock/timers.ts b/packages/vitest/src/integrations/mock/timers.ts index 77f6a35f2c22..2c12d1c9aba5 100644 --- a/packages/vitest/src/integrations/mock/timers.ts +++ b/packages/vitest/src/integrations/mock/timers.ts @@ -17,6 +17,7 @@ import { isChildProcess } from '../../utils/base' import { RealDate, mockDate, resetDate } from './date' export class FakeTimers { + private _global: typeof globalThis private _clock!: InstalledClock private _fakingTime: boolean private _fakingDate: boolean @@ -37,6 +38,7 @@ export class FakeTimers { this._fakingTime = false this._fakeTimers = withGlobal(global) + this._global = global } clearAllTimers(): void { @@ -138,13 +140,17 @@ export class FakeTimers { if (this._userConfig?.toFake?.includes('nextTick') && isChildProcess()) throw new Error('process.nextTick cannot be mocked inside child_process') + // setImmediate/clearImmediate is not possible to mock when it's not globally avaiable and it throws an internal error. + // this might be @sinonjs/fake-timers's bug and inconsistent behavior, but for now, we silently filter out these two beforehand for browser testing. + // https://github.com/sinonjs/fake-timers/issues/277 + // https://github.com/sinonjs/sinon/issues/2085 const existingFakedMethods = (this._userConfig?.toFake || toFake).filter((method) => { switch (method) { - case 'hrtime': - case 'nextTick': - return typeof process !== 'undefined' && method in process && process[method] + case 'setImmediate': + case 'clearImmediate': + return method in this._global && this._global[method] default: - return method in globalThis && globalThis[method] + return true } }) diff --git a/test/core/test/fixtures/timers.suite.ts b/test/core/test/fixtures/timers.suite.ts index da3c64f8ed94..3afc1e894cf7 100644 --- a/test/core/test/fixtures/timers.suite.ts +++ b/test/core/test/fixtures/timers.suite.ts @@ -119,6 +119,21 @@ describe('FakeTimers', () => { timers.useFakeTimers() expect(global.clearImmediate).not.toBe(origClearImmediate) }) + + it('mocks requestIdleCallback even if not on global', () => { + const global = { Date: FakeDate, clearTimeout, setTimeout }; + const timers = new FakeTimers({ global, config: { toFake: ["requestIdleCallback"] }}) + timers.useFakeTimers() + expect(global.requestIdleCallback).toBeDefined(); + }) + + it('cannot mock setImmediate and clearImmediate if not on global', () => { + const global = { Date: FakeDate, clearTimeout, setTimeout }; + const timers = new FakeTimers({ global, config: { toFake: ["setImmediate", "clearImmediate"] }}) + timers.useFakeTimers() + expect(global.setImmediate).toBeUndefined(); + expect(global.clearImmediate).toBeUndefined(); + }) }) describe('runAllTicks', () => {