"use strict"; const globalObject = require("@sinonjs/commons").global; let timersModule, timersPromisesModule; if (typeof require === "function" && typeof module === "object") { try { timersModule = require("timers"); } catch { // ignored } try { timersPromisesModule = require("timers/promises"); } catch { // ignored } } /** * @typedef {"nextAsync" | "manual" | "interval"} TickMode */ /** * @typedef {object} NextAsyncTickMode * @property {"nextAsync"} mode - runs timers one macrotask at a time */ /** * @typedef {object} ManualTickMode * @property {"manual"} mode - advances only when the caller explicitly ticks */ /** * @typedef {object} IntervalTickMode * @property {"interval"} mode - advances automatically on a native interval * @property {number} [delta] - interval duration in milliseconds */ /** * @typedef {IntervalTickMode | NextAsyncTickMode | ManualTickMode} TimerTickMode */ /** * @callback FakeTimersFunction * @param {...unknown[]} args * @returns {unknown} */ /** * @callback VoidVarArgsFunc * @param {...unknown[]} args - optional arguments to call the callback with * @returns {void} */ /** * @callback NextTick * @param {VoidVarArgsFunc} callback - the callback to run * @param {...unknown[]} args - optional arguments to call the callback with * @returns {void} */ /** * @callback SetImmediate * @param {VoidVarArgsFunc} callback - the callback to run * @param {...unknown[]} args - optional arguments to call the callback with * @returns {NodeImmediate} */ /** * @callback SetTimeout * @param {VoidVarArgsFunc} callback - the callback to run * @param {number} [delay] - optional delay in milliseconds * @param {...unknown[]} args - optional arguments to call the callback with * @returns {TimerId} - the timeout identifier */ /** * @callback ClearTimeout * @param {TimerId} [id] - the timeout identifier to clear * @returns {void} */ /** * @callback SetInterval * @param {VoidVarArgsFunc} callback - the callback to run * @param {number} [delay] - optional delay in milliseconds * @param {...unknown[]} args - optional arguments to call the callback with * @returns {TimerId} - the interval identifier */ /** * @callback ClearInterval * @param {TimerId} [id] - the interval identifier to clear * @returns {void} */ /** * @callback QueueMicrotask * @param {VoidVarArgsFunc} callback - the callback to run * @returns {void} */ /** * @callback TimeRemaining * @returns {number} */ /** * @typedef {object} IdleDeadline * @property {boolean} didTimeout - whether or not the callback was called before reaching the optional timeout * @property {TimeRemaining} timeRemaining - a floating-point value providing an estimate of the number of milliseconds remaining in the current idle period */ /** * @callback RequestIdleCallbackCallback * @param {IdleDeadline} deadline */ /** * Queues a function to be called during a browser's idle periods * @callback RequestIdleCallback * @param {RequestIdleCallbackCallback} callback * @param {{timeout?: number}} [options] - an options object * @returns {number} the id */ /** * @callback AnimationFrameCallback * @param {number} timestamp */ /** * @callback RequestAnimationFrame * @param {AnimationFrameCallback} callback * @returns {TimerId} - the request id */ /** * @callback CancelAnimationFrame * @param {TimerId} id - cancels a frame callback * @returns {void} */ /** * @callback CancelIdleCallback * @param {TimerId} id - cancels a scheduled idle callback * @returns {void} */ /** * @callback ClearImmediate * @param {NodeImmediate} id - faked `clearImmediate` * @returns {void} */ /** * @callback CountTimers * @returns {number} */ /** * @callback RunMicrotasks * @returns {void} */ /** * @typedef {object} TemporalDuration * @property {number} years - years component * @property {number} months - months component * @property {number} weeks - weeks component * @property {number} days - days component * @property {number} hours - hours component * @property {number} minutes - minutes component * @property {number} seconds - seconds component * @property {number} milliseconds - milliseconds component * @property {number} microseconds - microseconds component * @property {number} nanoseconds - nanoseconds component * @property {(options: {unit: string, relativeTo?: unknown}) => number} total - converts to a single unit */ /** * @typedef {object} TemporalTimelike * @property {number} epochMilliseconds - milliseconds since the Unix epoch (present on Temporal.Instant and Temporal.ZonedDateTime) */ /** * @callback Tick * @param {number|string|TemporalDuration} tickValue milliseconds, a string parseable by parseTime, or a Temporal.Duration * @returns {number} will return the new `now` value */ /** * @callback TickAsync * @param {number|string|TemporalDuration} tickValue milliseconds, a string parseable by parseTime, or a Temporal.Duration * @returns {Promise} */ /** * @callback Next * @returns {number} */ /** * @callback NextAsync * @returns {Promise} */ /** * @callback RunAll * @returns {number} */ /** * @callback RunToFrame * @returns {number} */ /** * @callback RunAllAsync * @returns {Promise} */ /** * @callback RunToLast * @returns {number} */ /** * @callback RunToLastAsync * @returns {Promise} */ /** * @callback Reset * @returns {void} */ /** * @callback SetSystemTime * @param {number|Date|TemporalTimelike} [now] initial mocked time, as milliseconds since epoch, a Date, a Temporal.Instant, or a Temporal.ZonedDateTime * @returns {void} */ /** * @callback Jump * @param {number|string|TemporalDuration} tickValue milliseconds, a human-readable value like "01:11:15", or a Temporal.Duration * @returns {number} */ /** * @callback Uninstall * @returns {void} */ /** * @callback SetTickMode * @param {SetTickModeConfig} tickModeConfig - The new configuration for how the clock should tick. * @returns {void} */ /** * @callback Hrtime * @param {Array} [prev] * @returns {Array} */ /** * @callback WithGlobal * @param {object} _global Namespace to mock (e.g. `window`) * @returns {FakeTimers} */ /** * @typedef {"setTimeout" | "clearTimeout" | "setImmediate" | "clearImmediate" | "setInterval" | "clearInterval" | "Date" | "nextTick" | "hrtime" | "requestAnimationFrame" | "cancelAnimationFrame" | "requestIdleCallback" | "cancelIdleCallback" | "performance" | "queueMicrotask" | "Intl" | "Temporal"} FakeMethod */ /** * @typedef {number | NodeImmediate | Timer} TimerId */ /* eslint-disable jsdoc/reject-any-type */ /** * @typedef {Record & { * setTimeout?: SetTimeout, * clearTimeout?: ClearTimeout, * setInterval?: SetInterval, * clearInterval?: ClearInterval, * setImmediate?: SetImmediate, * clearImmediate?: ClearImmediate, * queueMicrotask?: QueueMicrotask, * requestAnimationFrame?: RequestAnimationFrame, * cancelAnimationFrame?: CancelAnimationFrame, * requestIdleCallback?: RequestIdleCallback, * cancelIdleCallback?: CancelIdleCallback, * process?: any, * performance?: any, * Performance?: any, * Intl?: any, * Temporal?: any, * Promise?: typeof Promise, * Date: typeof Date & { isFake?: boolean, toSource?: () => string, clock?: any } * }} GlobalObject */ /** * @typedef {object} TimerHeap * @property {Timer[]} timers - the heap-ordered timers * @property {() => Timer | undefined} peek - returns the next timer without removing it * @property {(timer: Timer) => void} push - adds a timer to the heap * @property {() => Timer | undefined} pop - removes and returns the next timer * @property {(timer: Timer) => void} remove - removes a specific timer */ /** * @typedef {object} ClockTickMode * @property {TickMode} mode - active tick mode * @property {number} counter - increments whenever the mode changes * @property {number} [delta] - interval length in milliseconds */ /** * @typedef {object} SetTickModeConfig * @property {TickMode} mode - desired tick mode * @property {number} [delta] - interval length in milliseconds */ /** * @typedef {Record & { clock: Clock }} IntlWithClock */ /** * @typedef {Record & { now: () => number }} PerformanceLike */ /** * @typedef {object} Timers * @property {SetTimeout} setTimeout - native `setTimeout` * @property {ClearTimeout} clearTimeout - native `clearTimeout` * @property {SetInterval} setInterval - native `setInterval` * @property {ClearInterval} clearInterval - native `clearInterval` * @property {typeof Date} Date - native `Date` * @property {typeof Intl} [Intl] - native `Intl` * @property {any} [Temporal] - native `Temporal` * @property {SetImmediate} [setImmediate] - native `setImmediate`, if available * @property {ClearImmediate} [clearImmediate] - native `clearImmediate`, if available * @property {Hrtime} [hrtime] - native `process.hrtime`, if available * @property {NextTick} [nextTick] - native `process.nextTick`, if available * @property {PerformanceLike} [performance] - native `performance`, if available * @property {RequestAnimationFrame} [requestAnimationFrame] - native `requestAnimationFrame`, if available * @property {QueueMicrotask} [queueMicrotask] - whether `queueMicrotask` exists * @property {CancelAnimationFrame} [cancelAnimationFrame] - native `cancelAnimationFrame`, if available * @property {RequestIdleCallback} [requestIdleCallback] - native `requestIdleCallback`, if available * @property {CancelIdleCallback} [cancelIdleCallback] - native `cancelIdleCallback`, if available */ /** * @typedef {object} ClockState * @property {number} tickFrom - lower bound of the current tick range * @property {number} tickTo - upper bound of the current tick range * @property {number} [previous] - previous timer time used during ticking * @property {number | null} [oldNow] - previous value of `now` * @property {Timer} [timer] - timer currently being processed * @property {unknown} [firstException] - first exception raised while processing timers * @property {number} [nanosTotal] - accumulated nanoseconds from fractional ticks * @property {number} [msFloat] - accumulated fractional milliseconds * @property {number} [ms] - accumulated whole milliseconds */ /** * @typedef {object} TimerInitialProps * @property {VoidVarArgsFunc} func - callback or string to execute * @property {unknown[]} [args] - arguments passed to the callback * @property {'Timeout' | 'Interval' | 'Immediate' | 'AnimationFrame' | 'IdleCallback'} [type] - timer kind * @property {number} [delay] - requested delay in milliseconds * @property {number} [callAt] - scheduled execution time * @property {number} [createdAt] - time at which the timer was created * @property {boolean} [immediate] - whether this timer should run before non-immediate timers at the same time * @property {number} [id] - unique timer identifier * @property {Error} [error] - captured stack for loop diagnostics * @property {number} [interval] - interval for repeated timers * @property {boolean} [animation] - whether this is an animation frame timer * @property {boolean} [requestIdleCallback] - whether this is an idle callback timer * @property {number} [order] - execution order for timers at the same time * @property {number} [heapIndex] - index in the timer heap */ /** * @callback CreateClockCallback * @param {number|Date|TemporalTimelike} [start] initial mocked time, as milliseconds since epoch, a Date, a Temporal.Instant, or a Temporal.ZonedDateTime * @param {number} [loopLimit] maximum number of timers run before aborting with an infinite-loop error * @returns {Clock} */ /** * @callback InstallCallback * @param {Config} [config] Optional config * @returns {Clock} */ /** * @typedef {object} FakeTimers * @property {Timers} timers - the native timer APIs saved for later restoration * @property {CreateClockCallback} createClock - creates a new fake clock * @property {InstallCallback} install - installs the fake timers onto the default global object * @property {WithGlobal} withGlobal - creates a fake-timers instance for a provided global object */ /** * @typedef {object} Clock * @property {number} now - current mocked time in milliseconds * @property {typeof Date & {clock?: Clock, isFake?: boolean, toSource?: () => string}} Date - fake Date constructor bound to this clock * @property {number} loopLimit - maximum number of timers before assuming an infinite loop * @property {RequestIdleCallback} requestIdleCallback - schedules an idle callback * @property {CancelIdleCallback} cancelIdleCallback - cancels a scheduled idle callback * @property {SetTimeout} setTimeout - faked `setTimeout` * @property {ClearTimeout} clearTimeout - faked `clearTimeout` * @property {NextTick} nextTick - faked `process.nextTick` * @property {QueueMicrotask} queueMicrotask - faked `queueMicrotask` * @property {SetInterval} setInterval - faked `setInterval` * @property {ClearInterval} clearInterval - faked `clearInterval` * @property {SetImmediate} setImmediate - faked `setImmediate` * @property {ClearImmediate} clearImmediate - faked `clearImmediate` * @property {CountTimers} countTimers - counts scheduled timers * @property {RequestAnimationFrame} requestAnimationFrame - schedules a frame callback * @property {CancelAnimationFrame} cancelAnimationFrame - cancels a frame callback * @property {RunMicrotasks} runMicrotasks - drains microtasks * @property {Tick} tick - advances fake time synchronously * @property {TickAsync} tickAsync - advances fake time asynchronously * @property {Next} next - runs the next scheduled timer * @property {NextAsync} nextAsync - runs the next scheduled timer asynchronously * @property {RunAll} runAll - runs all scheduled timers * @property {RunToFrame} runToFrame - runs timers up to the next animation frame * @property {RunAllAsync} runAllAsync - runs all scheduled timers asynchronously * @property {RunToLast} runToLast - runs timers up to the last scheduled timer * @property {RunToLastAsync} runToLastAsync - runs timers up to the last scheduled timer asynchronously * @property {Reset} reset - clears all timers and resets the clock * @property {SetSystemTime} setSystemTime - sets the clock to a specific wall-clock time * @property {Jump} jump - advances time and returns the new `now` * @property {any} performance - fake performance object * @property {Hrtime} hrtime - faked `process.hrtime` * @property {Uninstall} uninstall - restores native timers * @property {string[]} methods - names of faked methods * @property {boolean} [shouldClearNativeTimers] - inherited from config * @property {{methodName:string, original:unknown}[] | undefined} timersModuleMethods - saved Node timers module methods * @property {{methodName:string, original:unknown}[] | undefined} timersPromisesModuleMethods - saved Node timers/promises methods * @property {Map} abortListenerMap - active abort listeners * @property {SetTickMode} setTickMode - switches the auto-tick mode * @property {Map} [timers] - internal timer storage * @property {TimerHeap} [timerHeap] - internal timer heap * @property {boolean} [duringTick] - internal flag * @property {boolean} isNearInfiniteLimit - internal flag indicating the loop limit is nearly reached * @property {TimerId} [attachedInterval] - internal flag * @property {ClockTickMode} [tickMode] - internal flag * @property {Timer[]} [jobs] - internal flag * @property {IntlWithClock} [Intl] - fake Intl object * @property {any} [Temporal] - fake Temporal object */ /* eslint-enable jsdoc/reject-any-type */ /** * Configuration object for the `install` method. * @typedef {object} Config * @property {number|Date|TemporalTimelike} [now] initial mocked time, as milliseconds since epoch, a Date, a Temporal.Instant, or a Temporal.ZonedDateTime * @property {FakeMethod[]} [toFake] method names that should be faked * @property {FakeMethod[]} [toNotFake] method names that should remain native * @property {number} [loopLimit] maximum number of timers run before aborting with an infinite-loop error * @property {boolean} [shouldAdvanceTime] automatically increments mocked time while the clock is installed * @property {number} [advanceTimeDelta] interval in milliseconds used when `shouldAdvanceTime` is enabled * @property {boolean} [shouldClearNativeTimers] forwards clear calls to native methods when the timer is not fake * @property {boolean} [ignoreMissingTimers] suppresses errors when a requested timer is missing from the global object * @property {GlobalObject} [target] global object to install onto */ /** * The internal structure to describe a scheduled fake timer * @typedef {TimerInitialProps} Timer * @property {unknown[]} args - arguments passed to the callback * @property {number} callAt - scheduled execution time * @property {number} createdAt - time at which the timer was created * @property {number} id - unique timer identifier * @property {'Timeout' | 'Interval' | 'Immediate' | 'AnimationFrame' | 'IdleCallback'} type - timer kind */ /** * @callback NodeImmediateHasRef * @returns {boolean} */ /** * @callback NodeImmediateRef * @returns {NodeImmediate} */ /** * @callback NodeImmediateUnref * @returns {NodeImmediate} */ /** * A Node timer * @typedef {object} NodeImmediate * @property {NodeImmediateHasRef} hasRef - reports whether the timer keeps the event loop alive * @property {NodeImmediateRef} ref - marks the timer as referenced * @property {NodeImmediateUnref} unref - marks the timer as unreferenced */ /* eslint-disable complexity */ /** * Mocks available features in the specified global namespace. * @param {GlobalObject} _global Namespace to mock (e.g. `window`) * @returns {FakeTimers} */ function withGlobal(_global) { const maxTimeout = Math.pow(2, 31) - 1; //see https://heycam.github.io/webidl/#abstract-opdef-converttoint const idCounterStart = 1e12; // arbitrarily large number to avoid collisions with native timer IDs const NOOP = function () { return undefined; }; const NOOP_ARRAY = function () { return []; }; const isPresent = {}; let timeoutResult, addTimerReturnsObject = false; if (_global.setTimeout) { isPresent.setTimeout = true; timeoutResult = _global.setTimeout(NOOP, 0); addTimerReturnsObject = typeof timeoutResult === "object"; } isPresent.clearTimeout = Boolean(_global.clearTimeout); isPresent.setInterval = Boolean(_global.setInterval); isPresent.clearInterval = Boolean(_global.clearInterval); isPresent.hrtime = _global.process && typeof _global.process.hrtime === "function"; isPresent.hrtimeBigint = isPresent.hrtime && typeof _global.process.hrtime.bigint === "function"; isPresent.nextTick = _global.process && typeof _global.process.nextTick === "function"; const utilPromisify = _global.process && require("util").promisify; isPresent.performance = _global.performance && typeof _global.performance.now === "function"; const hasPerformancePrototype = _global.Performance && (typeof _global.Performance).match(/^(function|object)$/); const hasPerformanceConstructorPrototype = _global.performance && _global.performance.constructor && _global.performance.constructor.prototype; isPresent.queueMicrotask = Object.prototype.hasOwnProperty.call( _global, "queueMicrotask", ); isPresent.requestAnimationFrame = _global.requestAnimationFrame && typeof _global.requestAnimationFrame === "function"; isPresent.cancelAnimationFrame = _global.cancelAnimationFrame && typeof _global.cancelAnimationFrame === "function"; isPresent.requestIdleCallback = _global.requestIdleCallback && typeof _global.requestIdleCallback === "function"; isPresent.cancelIdleCallback = _global.cancelIdleCallback && typeof _global.cancelIdleCallback === "function"; isPresent.setImmediate = _global.setImmediate && typeof _global.setImmediate === "function"; isPresent.clearImmediate = _global.clearImmediate && typeof _global.clearImmediate === "function"; isPresent.Intl = _global.Intl && typeof _global.Intl === "object"; isPresent.Temporal = _global.Temporal !== null && typeof _global.Temporal === "object" && typeof _global.Temporal.Now !== "undefined" && typeof _global.Temporal.Instant !== "undefined"; if (_global.clearTimeout) { _global.clearTimeout(timeoutResult); } const NativeDate = _global.Date; const NativeIntl = isPresent.Intl ? Object.defineProperties( Object.create(null), Object.getOwnPropertyDescriptors(_global.Intl), ) : undefined; const NativeTemporal = isPresent.Temporal ? _global.Temporal : undefined; let uniqueTimerId = idCounterStart; /** @type {number} */ let uniqueTimerOrder = 0; if (NativeDate === undefined) { throw new Error( "The global scope doesn't have a `Date` object" + " (see https://github.com/sinonjs/sinon/issues/1852#issuecomment-419622780)", ); } isPresent.Date = true; /** * The PerformanceEntry object encapsulates a single performance metric * that is part of the browser's performance timeline. * * This is an object returned by the `mark` and `measure` methods on the Performance prototype */ class FakePerformanceEntry { constructor(name, entryType, startTime, duration) { this.name = name; this.entryType = entryType; this.startTime = startTime; this.duration = duration; } toJSON() { return JSON.stringify({ ...this }); } } /** * @param {number} num * @returns {boolean} */ function isNumberFinite(num) { if (Number.isFinite) { return Number.isFinite(num); } return isFinite(num); } /** * @param {Clock} clock * @param {number} i */ function checkIsNearInfiniteLimit(clock, i) { if (clock.loopLimit && i === clock.loopLimit - 1) { clock.isNearInfiniteLimit = true; } } /** * @param {Clock} clock */ function resetIsNearInfiniteLimit(clock) { if (clock) { clock.isNearInfiniteLimit = false; } } /** * Parse strings like "01:10:00" (meaning 1 hour, 10 minutes, 0 seconds) into * number of milliseconds. This is used to support human-readable strings passed * to clock.tick() * @param {string} str * @returns {number} */ function parseTime(str) { if (!str) { return 0; } const strings = str.split(":"); const l = strings.length; let i = l; let ms = 0; let parsed; if (l > 3 || !/^(\d\d:){0,2}\d\d?$/.test(str)) { throw new Error( "tick only understands numbers, 'm:s' and 'h:m:s'. Each part must be two digits", ); } while (i--) { parsed = parseInt(strings[i], 10); if (parsed >= 60) { throw new Error(`Invalid time ${str}`); } ms += parsed * Math.pow(60, l - i - 1); } return ms * 1000; } /** * Get the decimal part of the millisecond value as nanoseconds * @param {number} msFloat the number of milliseconds * @returns {number} an integer number of nanoseconds in the range [0,1e6) * * Example: nanoRemainer(123.456789) -> 456789 */ function nanoRemainder(msFloat) { const modulo = 1e6; const remainder = (msFloat * 1e6) % modulo; const positiveRemainder = remainder < 0 ? remainder + modulo : remainder; return Math.floor(positiveRemainder); } /** * Used to grok the `now` parameter to createClock. * @param {Date|number|TemporalTimelike} epoch the system time * @returns {number} */ function getEpoch(epoch) { if (!epoch) { return 0; } if (typeof epoch === "number") { return epoch; } if (typeof (/** @type {Date} */ (epoch).getTime) === "function") { return /** @type {Date} */ (epoch).getTime(); } if ( typeof ( /** @type {TemporalTimelike} */ (epoch).epochMilliseconds ) === "number" ) { // Temporal.Instant and Temporal.ZonedDateTime both have epochMilliseconds return /** @type {TemporalTimelike} */ (epoch).epochMilliseconds; } throw new TypeError("now should be milliseconds since UNIX epoch"); } /** * @param {number} from * @param {number} to * @param {Timer} timer * @returns {boolean} */ function inRange(from, to, timer) { return timer && timer.callAt >= from && timer.callAt <= to; } /** * @param {Clock} clock * @param {Timer} job * @returns {Error} */ function getInfiniteLoopError(clock, job) { const infiniteLoopError = new Error( `Aborting after running ${clock.loopLimit} timers, assuming an infinite loop!`, ); if (!job.error) { return infiniteLoopError; } // pattern never matched in Node const computedTargetPattern = /target\.*[<|(|[].*?[>|\]|)]\s*/; let clockMethodPattern = new RegExp( String(Object.keys(clock).join("|")), ); if (addTimerReturnsObject) { // node.js environment clockMethodPattern = new RegExp( `\\s+at (Object\\.)?(?:${Object.keys(clock).join("|")})\\s+`, ); } let matchedLineIndex = -1; job.error.stack.split("\n").some(function (line, i) { // If we've matched a computed target line (e.g. setTimeout) then we // don't need to look any further. Return true to stop iterating. const matchedComputedTarget = line.match(computedTargetPattern); /* istanbul ignore if */ if (matchedComputedTarget) { matchedLineIndex = i; return true; } // If we've matched a clock method line, then there may still be // others further down the trace. Return false to keep iterating. const matchedClockMethod = line.match(clockMethodPattern); if (matchedClockMethod) { matchedLineIndex = i; return false; } // If we haven't matched anything on this line, but we matched // previously and set the matched line index, then we can stop. // If we haven't matched previously, then we should keep iterating. return matchedLineIndex >= 0; }); const stack = `${infiniteLoopError}\n${job.type || "Microtask"} - ${ job.func.name || "anonymous" }\n${job.error.stack .split("\n") .slice(matchedLineIndex + 1) .join("\n")}`; try { Object.defineProperty(infiniteLoopError, "stack", { value: stack, }); } catch { // noop } return infiniteLoopError; } /** * @returns {typeof Date & { clock: Clock }} */ function createDate() { class ClockDate extends NativeDate { /** @type {Clock} */ static clock; constructor(...args) { // Preserve fake time when Date is called without arguments. if (args.length === 0) { super(ClockDate.clock.now); } else { // The subclass is intentionally thin for explicit args. // @ts-expect-error Date constructor overloads are intentionally dynamic. super(...args); } // ensures identity checks using the constructor prop still works // this should have no other functional effect Object.defineProperty(this, "constructor", { value: NativeDate, enumerable: false, }); } static [Symbol.hasInstance](instance) { return instance instanceof NativeDate; } } ClockDate.isFake = true; if (NativeDate.now) { ClockDate.now = function now() { return ClockDate.clock.now; }; } const NativeDateWithToSource = /** @type {typeof Date & { toSource?: () => string }} */ ( NativeDate ); if (NativeDateWithToSource.toSource) { ClockDate.toSource = function toSource() { return NativeDateWithToSource.toSource(); }; } ClockDate.toString = function toString() { return NativeDateWithToSource.toString(); }; // noinspection UnnecessaryLocalVariableJS /** * A normal Class constructor cannot be called without `new`, but Date can, so we need * to wrap it in a Proxy in order to ensure this functionality of Date is kept intact * @type {typeof ClockDate} */ const ClockDateProxy = new Proxy(ClockDate, { // handler for [[Call]] invocations (i.e. not using `new`) apply() { // the Date constructor called as a function, ref Ecma-262 Edition 5.1, section 15.9.2. // This remains so in the 10th edition of 2019 as well. if (this instanceof ClockDate) { throw new TypeError( "A Proxy should only capture `new` calls with the `construct` handler. This is not supposed to be possible, so check the logic.", ); } return new NativeDate(ClockDate.clock.now).toString(); }, }); return /** @type {typeof Date & { clock: Clock }} */ ( /** @type {unknown} */ (ClockDateProxy) ); } /** * Mirror Intl by default on our fake implementation * * Most of the properties are the original native ones, * but we need to take control of those that have a * dependency on the current clock. * @param {Clock} clock * @returns {IntlWithClock} the partly fake Intl implementation */ function createIntl(clock) { /** @type {IntlWithClock} */ const IntlWithClock = { clock: clock }; /* * All properties of Intl are non-enumerable, so we need * to do a bit of work to get them out. */ Object.getOwnPropertyNames(NativeIntl).forEach( (property) => (IntlWithClock[property] = NativeIntl[property]), ); IntlWithClock.DateTimeFormat = function (...args) { const realFormatter = new NativeIntl.DateTimeFormat(...args); const formatter = {}; ["formatRange", "formatRangeToParts", "resolvedOptions"].forEach( (method) => { formatter[method] = realFormatter[method].bind(realFormatter); }, ); ["format", "formatToParts"].forEach((method) => { formatter[method] = function (date) { return realFormatter[method]( date || IntlWithClock.clock.now, ); }; }); return formatter; }; IntlWithClock.DateTimeFormat.prototype = Object.create( NativeIntl.DateTimeFormat.prototype, ); IntlWithClock.DateTimeFormat.supportedLocalesOf = NativeIntl.DateTimeFormat.supportedLocalesOf; return IntlWithClock; } //eslint-disable-next-line jsdoc/require-jsdoc function createTemporal(clock, getNanos) { const fakeNow = { instant() { return NativeTemporal.Instant.fromEpochNanoseconds( BigInt(clock.now) * 1_000_000n + BigInt(getNanos()), ); }, timeZoneId() { return NativeTemporal.Now.timeZoneId(); }, zonedDateTimeISO(timeZone) { const tz = timeZone ?? NativeTemporal.Now.timeZoneId(); return fakeNow.instant().toZonedDateTimeISO(tz); }, plainDateTimeISO(timeZone) { return fakeNow.zonedDateTimeISO(timeZone).toPlainDateTime(); }, plainDateISO(timeZone) { return fakeNow.zonedDateTimeISO(timeZone).toPlainDate(); }, plainTimeISO(timeZone) { return fakeNow.zonedDateTimeISO(timeZone).toPlainTime(); }, }; const TemporalWithClock = Object.create( Object.getPrototypeOf(NativeTemporal), ); [ ...Object.getOwnPropertyNames(NativeTemporal), ...Object.getOwnPropertySymbols(NativeTemporal), ].forEach((prop) => { Object.defineProperty( TemporalWithClock, prop, Object.getOwnPropertyDescriptor(NativeTemporal, prop), ); }); // Temporal.Now is writable:false in the spec so we must use defineProperty Object.defineProperty(TemporalWithClock, "Now", { value: fakeNow, writable: true, enumerable: false, configurable: true, }); return TemporalWithClock; } //eslint-disable-next-line jsdoc/require-jsdoc function enqueueJob(clock, job) { // enqueues a microtick-deferred task - ecma262/#sec-enqueuejob if (!clock.jobs) { clock.jobs = []; } clock.jobs.push(job); } //eslint-disable-next-line jsdoc/require-jsdoc function runJobs(clock) { // runs all microtick-deferred tasks - ecma262/#sec-runjobs if (!clock.jobs) { return; } const wasNearLimit = clock.isNearInfiniteLimit; for (let i = 0; i < clock.jobs.length; i++) { const job = clock.jobs[i]; job.func.apply(null, job.args); checkIsNearInfiniteLimit(clock, i); if (clock.loopLimit && i > clock.loopLimit) { throw getInfiniteLoopError(clock, job); } } if (!wasNearLimit) { resetIsNearInfiniteLimit(clock); } clock.jobs = []; } /** * A compact "soonest timer first" container. * * Think of this as a waiting room for scheduled callbacks where the next * callback to run is always kept at the front of the list. The internal * array is arranged so we can find, add, remove, and reorder timers * efficiently without sorting the whole list every time something changes. * * The important idea is not the data structure name, but the behavior: * the timer that should run next stays near the front, and when one timer * moves, the rest are shifted just enough to keep that promise true. */ class TimerHeap { constructor() { this.timers = []; } /** * Look at the next timer without removing it. * This is the timer the clock would run first if time advanced now. * @returns {Timer} */ peek() { return this.timers[0]; } /** * Add a timer to the waiting room, then move it upward until it is in * the right place relative to the timers it should run before and after. * @param {Timer} timer */ push(timer) { this.timers.push(timer); this.bubbleUp(this.timers.length - 1); } /** * Remove and return the next timer to run. * * We pull the front timer out, move the last timer into the empty spot, * and then shift that replacement down until the ordering is correct * again. That avoids rebuilding the whole list from scratch. * @returns {Timer|undefined} */ pop() { if (this.timers.length === 0) { return undefined; } const first = this.timers[0]; const last = this.timers.pop(); if (this.timers.length > 0) { this.timers[0] = last; last.heapIndex = 0; this.bubbleDown(0); } delete first.heapIndex; return first; } /** * Remove a specific timer from the waiting room. * * The heap stores timers in a shape that lets us jump directly to the * timer's current position, replace it with the last timer, and then * move that replacement up or down until the ordering is correct again. * @param {Timer} timer * @returns {boolean} */ remove(timer) { const index = timer.heapIndex; if (index === undefined || this.timers[index] !== timer) { return false; } const last = this.timers.pop(); if (timer !== last) { this.timers[index] = last; last.heapIndex = index; if (compareTimers(last, timer) < 0) { this.bubbleUp(index); } else { this.bubbleDown(index); } } delete timer.heapIndex; return true; } /** * Move a timer toward the front until it is no longer "earlier" than * the timer above it. * * Conceptually, this is what happens when something newly scheduled * turns out to belong ahead of its parent in the waiting room. We keep * swapping it upward until it is no longer out of place. * @param {number} index */ bubbleUp(index) { const timer = this.timers[index]; let currentIndex = index; while (currentIndex > 0) { const parentIndex = Math.floor((currentIndex - 1) / 2); const parent = this.timers[parentIndex]; if (compareTimers(timer, parent) < 0) { this.timers[currentIndex] = parent; parent.heapIndex = currentIndex; currentIndex = parentIndex; } else { break; } } this.timers[currentIndex] = timer; timer.heapIndex = currentIndex; } /** * Move a timer away from the front until the timer below it is no * longer supposed to run after it. * * This is the opposite of `bubbleUp`: when a timer at the front is * removed or moved, the replacement may be too far ahead, so we * repeatedly swap it downward with the best child until the waiting * room is ordered again. * @param {number} index */ bubbleDown(index) { const timer = this.timers[index]; let currentIndex = index; const halfLength = Math.floor(this.timers.length / 2); while (currentIndex < halfLength) { const leftIndex = currentIndex * 2 + 1; const rightIndex = leftIndex + 1; let bestChildIndex = leftIndex; let bestChild = this.timers[leftIndex]; if ( rightIndex < this.timers.length && compareTimers(this.timers[rightIndex], bestChild) < 0 ) { bestChildIndex = rightIndex; bestChild = this.timers[rightIndex]; } if (compareTimers(bestChild, timer) < 0) { this.timers[currentIndex] = bestChild; bestChild.heapIndex = currentIndex; currentIndex = bestChildIndex; } else { break; } } this.timers[currentIndex] = timer; timer.heapIndex = currentIndex; } } /** * Ensure timer storage and heap stay in sync even if a clear path touches * timer state before anything has been scheduled. * * Why do we need two data structures to keep tabs on timers? * 1. Fast ID Lookup (clock.timers): This is a Map from timer IDs to their respective timer objects. It allows clearTimeout(id) and * clearInterval(id) to be $O(1)$ operations. Without this map, finding a specific timer in the heap to remove it would require a linear * $O(n)$ search, which would significantly degrade performance as the number of active timers grows. * 2. Efficient Scheduling (clock.timerHeap): This is a priority queue (min-heap) that keeps timers ordered by their execution time (callAt). It * allows the library to instantly find the next timer to run (peek() in $O(1)$) and efficiently update the schedule when timers are added or * removed ($O(\log n)$). * * In short: clock.timers provides fast access by ID, while clock.timerHeap provides fast access by Time. Removing either one would make common * operations (like clearing or finding the next timer) much slower. * @param {Clock} clock */ function ensureTimerState(clock) { if (!clock.timers) { clock.timers = new Map(); clock.timerHeap = new TimerHeap(); } } /** * @param {Clock} clock * @param {number} id * @returns {boolean} */ function hasTimer(clock, id) { return clock.timers ? clock.timers.has(id) : false; } /** * @param {Clock} clock * @param {number} id * @returns {Timer} */ function getTimer(clock, id) { return clock.timers ? clock.timers.get(id) : undefined; } /** * @param {Clock} clock * @param {Timer} timer */ function setTimer(clock, timer) { ensureTimerState(clock); clock.timers.set(timer.id, timer); } /** * @param {Clock} clock * @param {number} id * @returns {boolean} */ function deleteTimer(clock, id) { return clock.timers ? clock.timers.delete(id) : false; } /** * @param {Clock} clock * @param {(timer: Timer) => void} callback */ function forEachActiveTimer(clock, callback) { if (!clock.timers) { return; } for (const timer of clock.timers.values()) { callback(timer); } } /** * @param {Clock} clock */ function rebuildTimerHeap(clock) { clock.timerHeap = new TimerHeap(); forEachActiveTimer(clock, (timer) => { clock.timerHeap.push(timer); }); } /** * @param {Clock} clock * @param {TimerInitialProps} timer * @returns {TimerId} id of the created timer */ function addTimer(clock, timer) { if (timer.func === undefined) { throw new Error("Callback must be provided to timer calls"); } if (typeof timer.func !== "function") { throw new TypeError( `[ERR_INVALID_CALLBACK]: Callback must be a function. Received ${ timer.func } of type ${typeof timer.func}`, ); } if (clock.isNearInfiniteLimit) { timer.error = new Error(); } timer.type = timer.immediate ? "Immediate" : "Timeout"; if (Object.prototype.hasOwnProperty.call(timer, "delay")) { if (typeof timer.delay !== "number") { timer.delay = parseInt(timer.delay, 10); } if (!isNumberFinite(timer.delay)) { timer.delay = 0; } timer.delay = timer.delay > maxTimeout ? 1 : timer.delay; timer.delay = Math.max(0, timer.delay); } if (Object.prototype.hasOwnProperty.call(timer, "interval")) { timer.type = "Interval"; timer.interval = timer.interval > maxTimeout ? 1 : timer.interval; } if (Object.prototype.hasOwnProperty.call(timer, "animation")) { timer.type = "AnimationFrame"; timer.animation = true; } if ( Object.prototype.hasOwnProperty.call(timer, "requestIdleCallback") ) { // mark timer as IdleCallback type if it has no delay, otherwise it'd be of type timeout // this way we are able to sort such that the timer only gets called when there's truly no pending task to run if (!timer.delay) { timer.type = "IdleCallback"; } timer.requestIdleCallback = true; } ensureTimerState(clock); while (hasTimer(clock, uniqueTimerId)) { uniqueTimerId++; if (uniqueTimerId >= Number.MAX_SAFE_INTEGER) { uniqueTimerId = idCounterStart; } } timer.id = uniqueTimerId++; if (uniqueTimerId >= Number.MAX_SAFE_INTEGER) { uniqueTimerId = idCounterStart; } timer.order = uniqueTimerOrder++; timer.createdAt = clock.now; timer.callAt = clock.now + (parseInt(String(timer.delay)) || (clock.duringTick ? 1 : 0)); setTimer(clock, timer); clock.timerHeap.push(timer); if (addTimerReturnsObject) { const res = { refed: true, ref: function () { this.refed = true; return this; }, unref: function () { this.refed = false; return this; }, hasRef: function () { return this.refed; }, refresh: function () { timer.callAt = clock.now + (parseInt(String(timer.delay)) || (clock.duringTick ? 1 : 0)); clock.timerHeap.remove(timer); timer.order = uniqueTimerOrder++; setTimer(clock, timer); clock.timerHeap.push(timer); return this; }, [Symbol.toPrimitive]: function () { return timer.id; }, }; return res; } return timer.id; } /* eslint consistent-return: "off" */ /** * Timer comparator * @param {Timer} a * @param {Timer} b * @returns {number} */ function compareTimers(a, b) { // Sort IdleCallback timers to the bottom when scheduled for the same time if (a.type === "IdleCallback" && b.type !== "IdleCallback") { return 1; } if (a.type !== "IdleCallback" && b.type === "IdleCallback") { return -1; } // Sort first by absolute timing if (a.callAt < b.callAt) { return -1; } if (a.callAt > b.callAt) { return 1; } // Sort next by immediate, immediate timers take precedence if (a.immediate && !b.immediate) { return -1; } if (!a.immediate && b.immediate) { return 1; } if (a.order < b.order) { return -1; } if (a.order > b.order) { return 1; } // Sort next by creation time, earlier-created timers take precedence if (a.createdAt < b.createdAt) { return -1; } if (a.createdAt > b.createdAt) { return 1; } // Sort next by id, lower-id timers take precedence if (a.id < b.id) { return -1; } if (a.id > b.id) { return 1; } // As timer ids are unique, no fallback `0` is necessary return 0; } /** * @param {Clock} clock * @param {number} from * @param {number} to * @returns {Timer} */ function firstTimerInRange(clock, from, to) { if (!clock.timerHeap) { return null; } const timers = clock.timerHeap.timers; if (timers.length === 1 && timers[0].requestIdleCallback) { return timers[0]; } const first = clock.timerHeap.peek(); if (first && inRange(from, to, first)) { return first; } /** * @type {?Timer} */ let timer = null; for (let i = 0; i < timers.length; i++) { if ( inRange(from, to, timers[i]) && (!timer || compareTimers(timer, timers[i]) === 1) ) { timer = timers[i]; } } return timer; } /** * @param {Clock} clock * @returns {Timer} */ function firstTimer(clock) { if (!clock.timerHeap) { return null; } return clock.timerHeap.peek() || null; } /** * @param {Clock} clock * @returns {Timer} */ function lastTimer(clock) { if (!clock.timerHeap) { return null; } const timers = clock.timerHeap.timers; let timer = null; for (let i = 0; i < timers.length; i++) { if (!timer || compareTimers(timer, timers[i]) === -1) { timer = timers[i]; } } return timer; } /** * @param {Clock} clock * @param {Timer} timer */ function callTimer(clock, timer) { if (typeof timer.interval === "number") { clock.timerHeap.remove(timer); timer.callAt += timer.interval; timer.order = uniqueTimerOrder++; if (clock.isNearInfiniteLimit) { timer.error = new Error(); } clock.timerHeap.push(timer); } else { deleteTimer(clock, timer.id); clock.timerHeap.remove(timer); } if (typeof timer.func === "function") { timer.func.apply(null, timer.args); } } /** * Gets clear handler name for a given timer type * @param {string} ttype * @returns {string} */ function getClearHandler(ttype) { if (ttype === "IdleCallback" || ttype === "AnimationFrame") { return `cancel${ttype}`; } return `clear${ttype}`; } /** * Gets schedule handler name for a given timer type * @param {string} ttype * @returns {string} */ function getScheduleHandler(ttype) { if (ttype === "IdleCallback" || ttype === "AnimationFrame") { return `request${ttype}`; } return `set${ttype}`; } /** * Creates an anonymous function to warn only once * @returns {(msg: string) => void} */ function createWarnOnce() { let calls = 0; return function (msg) { // eslint-disable-next-line !calls++ && console.warn(msg); }; } const warnOnce = createWarnOnce(); /** * @param {Clock} clock * @param {TimerId} timerId * @param {string} ttype * @returns {void} */ function clearTimer(clock, timerId, ttype) { if (!timerId) { // null appears to be allowed in most browsers, and appears to be // relied upon by some libraries, like Bootstrap carousel return; } // in Node, the ID is stored as the primitive value for `Timeout` objects // for `Immediate` objects, no ID exists, so it gets coerced to NaN const id = Number(timerId); if (Number.isNaN(id) || id < idCounterStart) { const handlerName = getClearHandler(ttype); if (clock.shouldClearNativeTimers === true) { const nativeHandler = clock[`_${handlerName}`]; return typeof nativeHandler === "function" ? nativeHandler(timerId) : undefined; } // Include the stacktrace, excluding the 'error' line const stackTrace = new Error().stack .split("\n") .slice(1) .join("\n"); warnOnce( `FakeTimers: ${handlerName} was invoked to clear a native timer instead of one created by this library.` + "\nTo automatically clean-up native timers, use `shouldClearNativeTimers`." + `\n${stackTrace}`, ); } if (hasTimer(clock, id)) { // check that the ID matches a timer of the correct type const timer = getTimer(clock, id); if ( timer.type === ttype || (timer.type === "Timeout" && ttype === "Interval") || (timer.type === "Interval" && ttype === "Timeout") ) { deleteTimer(clock, id); clock.timerHeap.remove(timer); } else { const clear = getClearHandler(ttype); const schedule = getScheduleHandler(timer.type); throw new Error( `Cannot clear timer: timer created with ${schedule}() but cleared with ${clear}()`, ); } } } /** * @param {object} target the target containing the method to replace * @param {string} method the keyname of the method on the target * @param {Clock} clock */ function hijackMethod(target, method, clock) { clock[method].hasOwnProperty = Object.prototype.hasOwnProperty.call( target, method, ); clock[`_${method}`] = target[method]; if (method === "Date") { target[method] = clock[method]; } else if (method === "Intl") { target[method] = clock[method]; } else if (method === "Temporal") { target[method] = clock[method]; } else if (method === "performance") { const originalPerfDescriptor = Object.getOwnPropertyDescriptor( target, method, ); // JSDOM has a read only performance field so we have to save/copy it differently if ( originalPerfDescriptor && originalPerfDescriptor.get && !originalPerfDescriptor.set ) { Object.defineProperty( clock, `_${method}`, originalPerfDescriptor, ); const perfDescriptor = Object.getOwnPropertyDescriptor( clock, method, ); Object.defineProperty(target, method, perfDescriptor); } else { target[method] = clock[method]; } } else { target[method] = function () { return clock[method].apply(clock, arguments); }; Object.defineProperties( target[method], Object.getOwnPropertyDescriptors(clock[method]), ); } target[method].clock = clock; } /** * @param {Clock} clock * @param {number} advanceTimeDelta */ function doIntervalTick(clock, advanceTimeDelta) { clock.tick(advanceTimeDelta); } /** @type {Timers} */ const timers = { setTimeout: _global.setTimeout, clearTimeout: _global.clearTimeout, setInterval: _global.setInterval, clearInterval: _global.clearInterval, Date: _global.Date, }; if (isPresent.setImmediate) { timers.setImmediate = _global.setImmediate; } if (isPresent.clearImmediate) { timers.clearImmediate = _global.clearImmediate; } if (isPresent.hrtime) { timers.hrtime = _global.process.hrtime; } if (isPresent.nextTick) { timers.nextTick = _global.process.nextTick; } if (isPresent.performance) { timers.performance = _global.performance; } if (isPresent.requestAnimationFrame) { timers.requestAnimationFrame = _global.requestAnimationFrame; } if (isPresent.queueMicrotask) { timers.queueMicrotask = _global.queueMicrotask; } if (isPresent.cancelAnimationFrame) { timers.cancelAnimationFrame = _global.cancelAnimationFrame; } if (isPresent.requestIdleCallback) { timers.requestIdleCallback = _global.requestIdleCallback; } if (isPresent.cancelIdleCallback) { timers.cancelIdleCallback = _global.cancelIdleCallback; } if (isPresent.Intl) { timers.Intl = NativeIntl; } if (isPresent.Temporal) { timers.Temporal = NativeTemporal; } const originalSetTimeout = _global.setImmediate || _global.setTimeout; const originalClearInterval = _global.clearInterval; const originalSetInterval = _global.setInterval; /** * @param {Date|number|TemporalTimelike} [start] the system time - non-integer values are floored * @param {number} [loopLimit] maximum number of timers that will be run when calling runAll() * @returns {Clock} */ function createClock(start, loopLimit) { /** @type {number} */ // eslint-disable-next-line no-param-reassign start = Math.floor(getEpoch(start)); const startTimestamp = start; // eslint-disable-next-line no-param-reassign loopLimit = loopLimit || 1000; /** @type {number} */ let nanos = 0; let uninstalled = false; /** @type {number[]} */ const adjustedSystemTime = [0, 0]; // [millis, nanoremainder] /** @type {Clock} */ const clock = /** @type {Clock} */ ({ now: start, Date: createDate(), loopLimit: loopLimit, isNearInfiniteLimit: false, tickMode: { mode: "manual", counter: 0, delta: undefined }, }); clock.Date.clock = clock; //eslint-disable-next-line jsdoc/require-jsdoc function getTimeToNextFrame() { return 16 - ((clock.now - startTimestamp) % 16); } //eslint-disable-next-line jsdoc/require-jsdoc function hrtime(prev) { const millisSinceStart = clock.now - adjustedSystemTime[0] - startTimestamp; const secsSinceStart = Math.floor(millisSinceStart / 1000); const remainderInNanos = (millisSinceStart - secsSinceStart * 1e3) * 1e6 + nanos - adjustedSystemTime[1]; if (Array.isArray(prev)) { if (prev[1] > 1e9) { throw new TypeError( "Number of nanoseconds can't exceed a billion", ); } const oldSecs = prev[0]; let nanoDiff = remainderInNanos - prev[1]; let secDiff = secsSinceStart - oldSecs; if (nanoDiff < 0) { nanoDiff += 1e9; secDiff -= 1; } return [secDiff, nanoDiff]; } return [secsSinceStart, remainderInNanos]; } /** * A high resolution timestamp in milliseconds. * @typedef {number} DOMHighResTimeStamp */ /** * performance.now() * @returns {DOMHighResTimeStamp} */ function fakePerformanceNow() { const hrt = hrtime(); const millis = hrt[0] * 1000 + hrt[1] / 1e6; return millis; } if (isPresent.hrtimeBigint) { hrtime.bigint = function () { const parts = hrtime(); return BigInt(parts[0]) * BigInt(1e9) + BigInt(parts[1]); }; } if (isPresent.Intl) { clock.Intl = createIntl(clock); clock.Intl.clock = clock; } if (isPresent.Temporal) { clock.Temporal = createTemporal(clock, () => nanos); } /** * @param {SetTickModeConfig} tickModeConfig - The new configuration for how the clock should tick. */ clock.setTickMode = function (tickModeConfig) { const { mode: newMode, delta: newDelta } = /** @type {SetTickModeConfig} */ (tickModeConfig); const { mode: oldMode, delta: oldDelta } = clock.tickMode; if (newMode === oldMode && newDelta === oldDelta) { return; } if (oldMode === "interval") { originalClearInterval(clock.attachedInterval); } clock.tickMode = { counter: clock.tickMode.counter + 1, mode: newMode, delta: newDelta, }; if (newMode === "nextAsync") { advanceUntilModeChanges(); } else if (newMode === "interval") { createIntervalTick(clock, newDelta || 20); } }; /** * Keeps advancing the native event loop until the tick mode changes. * @returns {Promise} */ async function advanceUntilModeChanges() { /** * Waits for one native macrotask and then one microtask turn. * @returns {Promise} */ async function newMacrotask() { // MessageChannel ensures that setTimeout is not throttled to 4ms. // https://developer.mozilla.org/en-US/docs/Web/API/setTimeout#reasons_for_delays_longer_than_specified // https://stackblitz.com/edit/stackblitz-starters-qtlpcc const channel = new MessageChannel(); await new Promise((resolve) => { channel.port1.onmessage = () => { resolve(undefined); channel.port1.close(); }; channel.port2.postMessage(undefined); }); channel.port1.close(); channel.port2.close(); // setTimeout ensures microtask queue is emptied await new Promise((resolve) => { originalSetTimeout(resolve); }); } const { counter } = clock.tickMode; while (clock.tickMode.counter === counter) { await newMacrotask(); if (clock.tickMode.counter !== counter) { return; } clock.next(); } } /** * Temporarily pauses nextAsync auto-ticking while an async operation runs. * @param {Promise} promise * @returns {Promise} */ function pauseAutoTickUntilFinished(promise) { if (clock.tickMode.mode !== "nextAsync") { return promise; } clock.setTickMode({ mode: "manual" }); return promise.finally(() => { if (!uninstalled) { clock.setTickMode({ mode: "nextAsync" }); } }); } /** * Returns the remaining time in the current idle window. * @returns {number} */ function getTimeToNextIdlePeriod() { let timeToNextIdlePeriod = 0; if (clock.countTimers() > 0) { timeToNextIdlePeriod = 50; // const for now } return timeToNextIdlePeriod; } clock.requestIdleCallback = function requestIdleCallback( func, { timeout } = /** @type {{ timeout?: number }} */ ({}), ) { /** * @type {IdleDeadline} */ const idleDeadline = { didTimeout: true, timeRemaining: getTimeToNextIdlePeriod, }; const result = addTimer(clock, { func: func, args: [idleDeadline], delay: timeout, requestIdleCallback: true, }); return Number(result); }; clock.cancelIdleCallback = function cancelIdleCallback(timerId) { return clearTimer(clock, timerId, "IdleCallback"); }; clock.setTimeout = function setTimeout(func, timeout) { return addTimer(clock, { func: func, args: Array.prototype.slice.call(arguments, 2), delay: timeout, }); }; if (typeof _global.Promise !== "undefined" && utilPromisify) { clock.setTimeout[utilPromisify.custom] = function promisifiedSetTimeout(timeout, arg) { return new _global.Promise(function setTimeoutExecutor( resolve, ) { addTimer(clock, { func: resolve, args: [arg], delay: timeout, }); }); }; } clock.clearTimeout = function clearTimeout(timerId) { return clearTimer(clock, timerId, "Timeout"); }; clock.nextTick = function nextTick(func) { return enqueueJob(clock, { func: func, args: Array.prototype.slice.call(arguments, 1), error: clock.isNearInfiniteLimit ? new Error() : null, }); }; clock.queueMicrotask = function queueMicrotask(func) { return clock.nextTick(func); // explicitly drop additional arguments }; clock.setInterval = function setInterval(func, timeout) { // eslint-disable-next-line no-param-reassign timeout = parseInt(String(timeout), 10); return addTimer(clock, { func: func, args: Array.prototype.slice.call(arguments, 2), delay: timeout, interval: timeout, }); }; clock.clearInterval = function clearInterval(timerId) { return clearTimer(clock, timerId, "Interval"); }; if (isPresent.setImmediate) { clock.setImmediate = /** @type {SetImmediate} */ ( function setImmediate(func) { return addTimer(clock, { func: func, args: Array.prototype.slice.call(arguments, 1), immediate: true, }); } ); if (typeof _global.Promise !== "undefined" && utilPromisify) { clock.setImmediate[utilPromisify.custom] = function promisifiedSetImmediate(arg) { return new _global.Promise( function setImmediateExecutor(resolve) { addTimer(clock, { func: resolve, args: [arg], immediate: true, }); }, ); }; } clock.clearImmediate = function clearImmediate(timerId) { return clearTimer(clock, timerId, "Immediate"); }; } clock.countTimers = function countTimers() { return ( (clock.timerHeap ? clock.timerHeap.timers.length : 0) + (clock.jobs || []).length ); }; clock.requestAnimationFrame = function requestAnimationFrame(func) { const result = addTimer(clock, { func: func, delay: getTimeToNextFrame(), get args() { return [fakePerformanceNow()]; }, animation: true, }); return Number(result); }; clock.cancelAnimationFrame = function cancelAnimationFrame(timerId) { return clearTimer(clock, timerId, "AnimationFrame"); }; clock.runMicrotasks = function runMicrotasks() { runJobs(clock); }; //eslint-disable-next-line jsdoc/require-jsdoc function durationToMs(duration) { // relativeTo uses the real system timezone — fake-timers fakes time, not place. // Calendar-unit durations (months, years) will resolve DST/length using the host tz. const relativeTo = NativeTemporal.Instant.fromEpochMilliseconds( clock.now, ).toZonedDateTimeISO(NativeTemporal.Now.timeZoneId()); return duration.total({ unit: "millisecond", relativeTo }); } /** * @param {number|string|TemporalDuration} tickValue * @returns {number} milliseconds as a float */ function tickValueToMs(tickValue) { if (typeof tickValue === "number") { return tickValue; } if ( isPresent.Temporal && tickValue !== null && typeof tickValue === "object" && typeof (/** @type {TemporalDuration} */ (tickValue).total) === "function" ) { return durationToMs( /** @type {TemporalDuration} */ (tickValue), ); } return parseTime(/** @type {string} */ (tickValue)); } /** * @param {number|string|TemporalDuration} tickValue milliseconds, a string parseable by parseTime, or a Temporal.Duration * @returns {ClockState} a mutable state object for the tick execution */ function createTickState(tickValue) { const msFloat = tickValueToMs(tickValue); const ms = Math.floor(msFloat); const remainder = nanoRemainder(msFloat); let nanosTotal = nanos + remainder; let tickTo = clock.now + ms; if (msFloat < 0) { throw new TypeError("Negative ticks are not supported"); } // adjust for positive overflow if (nanosTotal >= 1e6) { tickTo += 1; nanosTotal -= 1e6; } return /** @type {ClockState} */ ({ msFloat: msFloat, ms: ms, nanosTotal: nanosTotal, tickFrom: clock.now, tickTo: tickTo, previous: clock.now, timer: null, firstException: null, oldNow: null, }); } /** * @param {ClockState} state mutable tick state * @param {number} oldNow the clock.now before some action * @param {object} [options] compensation options * @param {boolean} [options.includePrevious] whether to also update state.previous */ function applyClockChangeCompensation(state, oldNow, options) { if (oldNow !== clock.now) { const difference = clock.now - oldNow; state.tickFrom += difference; state.tickTo += difference; if (options && options.includePrevious) { state.previous += difference; } } } /** * @param {ClockState} state mutable tick state */ function runInitialJobs(state) { state.oldNow = clock.now; runJobs(clock); applyClockChangeCompensation(state, state.oldNow); } /** * @param {ClockState} state mutable tick state */ function runPostLoopJobs(state) { state.oldNow = clock.now; runJobs(clock); applyClockChangeCompensation(state, state.oldNow); } /** * @param {ClockState} state mutable tick state */ function selectNextTimerInRange(state) { state.timer = firstTimerInRange( clock, state.previous, state.tickTo, ); state.previous = state.tickFrom; } /** * @param {ClockState} state mutable tick state * @param {boolean} isAsync whether this is an async tick * @param {FakeTimersFunction} nextPromiseTick callback for async promise settlement * @param {FakeTimersFunction} compensationCheck callback for clock change compensation * @returns {boolean} whether an early return was triggered (async mode) */ function runTimersInRange( state, isAsync, nextPromiseTick, compensationCheck, ) { state.timer = firstTimerInRange( clock, state.tickFrom, state.tickTo, ); while (state.timer && state.tickFrom <= state.tickTo) { if (hasTimer(clock, state.timer.id)) { state.tickFrom = state.timer.callAt; clock.now = state.timer.callAt; state.oldNow = clock.now; try { runJobs(clock); callTimer(clock, state.timer); } catch (e) { state.firstException = state.firstException || e; } if (isAsync) { // finish up after native setImmediate callback to allow // all native es6 promises to process their callbacks after // each timer fires. originalSetTimeout(nextPromiseTick); return true; } compensationCheck(); } selectNextTimerInRange(state); } return false; } /** * @param {ClockState} state mutable tick state * @param {boolean} isAsync whether this is an async tick * @param {FakeTimersFunction} resolve promise resolve function * @returns {number|undefined} the new clock.now or nothing for async */ function finalizeTick(state, isAsync, resolve) { // corner case: during runJobs new timers were scheduled which could be in the range [clock.now, tickTo] state.timer = firstTimerInRange( clock, state.tickFrom, state.tickTo, ); if (state.timer) { try { clock.tick(state.tickTo - clock.now); // do it all again - for the remainder of the requested range } catch (e) { state.firstException = state.firstException || e; } } else { // no timers remaining in the requested range: move the clock all the way to the end clock.now = state.tickTo; // update nanos nanos = state.nanosTotal; } if (state.firstException) { throw state.firstException; } if (isAsync) { resolve(clock.now); } else { return clock.now; } } /** * @param {number|string|TemporalDuration} tickValue milliseconds or a string parseable by parseTime * @param {boolean} isAsync whether this is an async tick * @param {FakeTimersFunction} [resolve] promise resolve function * @param {FakeTimersFunction} [reject] promise reject function * @returns {number|undefined} the new clock.now or nothing for async */ function doTick(tickValue, isAsync, resolve, reject) { /** @type {ClockState} */ const state = createTickState(tickValue); nanos = state.nanosTotal; clock.duringTick = true; runInitialJobs(state); const compensationCheck = function () { applyClockChangeCompensation(state, state.oldNow, { includePrevious: true, }); }; const nextPromiseTick = isAsync && function () { try { compensationCheck(); selectNextTimerInRange(state); doTickInner(); } catch (e) { reject(e); } }; //eslint-disable-next-line jsdoc/require-jsdoc function doTickInner() { if ( runTimersInRange( state, isAsync, nextPromiseTick, compensationCheck, ) ) { return; } runPostLoopJobs(state); clock.duringTick = false; return finalizeTick(state, isAsync, resolve); } return doTickInner(); } /** * @param {string|number|TemporalDuration} tickValue number of milliseconds, a human-readable value like "01:11:15", or a Temporal.Duration * @returns {number} will return the new `now` value */ clock.tick = function tick(tickValue) { return doTick(tickValue, false); }; clock.next = function next() { runJobs(clock); const timer = firstTimer(clock); if (!timer) { return clock.now; } clock.duringTick = true; try { clock.now = timer.callAt; callTimer(clock, timer); runJobs(clock); return clock.now; } finally { clock.duringTick = false; } }; /** * @param {(resolve: (value: unknown) => void, reject: (reason?: unknown) => void) => void} callback function to run inside native setTimeout * @returns {Promise} */ function runAsyncWithNativeTimeout(callback) { return pauseAutoTickUntilFinished( new _global.Promise(function (resolve, reject) { originalSetTimeout(function () { try { callback(resolve, reject); } catch (e) { reject(e); } }); }), ); } clock.runAll = function runAll() { runJobs(clock); for (let i = 0; i < clock.loopLimit; i++) { if (!clock.timers) { resetIsNearInfiniteLimit(clock); return clock.now; } const numTimers = clock.timerHeap.timers.length; if (numTimers === 0) { resetIsNearInfiniteLimit(clock); return clock.now; } checkIsNearInfiniteLimit(clock, i); clock.next(); } const excessJob = firstTimer(clock); throw getInfiniteLoopError(clock, excessJob); }; clock.runToFrame = function runToFrame() { return clock.tick(getTimeToNextFrame()); }; clock.runToLast = function runToLast() { const timer = lastTimer(clock); if (!timer) { runJobs(clock); return clock.now; } return clock.tick(timer.callAt - clock.now); }; if (typeof _global.Promise !== "undefined") { /** * @param {string|number|TemporalDuration} tickValue number of milliseconds, a human-readable value like "01:11:15", or a Temporal.Duration * @returns {Promise} */ clock.tickAsync = function tickAsync(tickValue) { return runAsyncWithNativeTimeout(function (resolve, reject) { doTick(tickValue, true, resolve, reject); }); }; clock.nextAsync = function nextAsync() { return runAsyncWithNativeTimeout(function (resolve, reject) { const timer = firstTimer(clock); if (!timer) { resolve(clock.now); return; } let err; clock.duringTick = true; clock.now = timer.callAt; try { callTimer(clock, timer); } catch (e) { err = e; } clock.duringTick = false; originalSetTimeout(function () { if (err) { reject(err); } else { resolve(clock.now); } }); }); }; clock.runAllAsync = function runAllAsync() { let i = 0; /** * @param {(value: unknown) => void} resolve promise resolve function * @param {(reason?: unknown) => void} reject promise reject function */ function doRun(resolve, reject) { try { runJobs(clock); let numTimers; if (i < clock.loopLimit) { if (!clock.timerHeap) { resetIsNearInfiniteLimit(clock); resolve(clock.now); return; } numTimers = clock.timerHeap.timers.length; if (numTimers === 0) { resetIsNearInfiniteLimit(clock); resolve(clock.now); return; } checkIsNearInfiniteLimit(clock, i); clock.next(); i++; originalSetTimeout(function () { doRun(resolve, reject); }); return; } const excessJob = firstTimer(clock); reject(getInfiniteLoopError(clock, excessJob)); } catch (e) { reject(e); } } return runAsyncWithNativeTimeout(function (resolve, reject) { doRun(resolve, reject); }); }; clock.runToLastAsync = function runToLastAsync() { return runAsyncWithNativeTimeout(function (resolve) { const timer = lastTimer(clock); if (!timer) { runJobs(clock); resolve(clock.now); return; } resolve(clock.tickAsync(timer.callAt - clock.now)); }); }; } clock.reset = function reset() { nanos = 0; clock.timers = new Map(); clock.timerHeap = new TimerHeap(); clock.jobs = []; clock.now = start; }; clock.setSystemTime = function setSystemTime(systemTime) { // determine time difference const newNow = getEpoch(systemTime); const difference = newNow - clock.now; adjustedSystemTime[0] = adjustedSystemTime[0] + difference; adjustedSystemTime[1] = adjustedSystemTime[1] + nanos; // update 'system clock' clock.now = newNow; nanos = 0; // update timers and intervals to keep them stable forEachActiveTimer(clock, (timer) => { timer.createdAt += difference; timer.callAt += difference; }); }; /** * @param {string|number|TemporalDuration} tickValue number of milliseconds, a human-readable value like "01:11:15", or a Temporal.Duration * @returns {number} the new `now` value */ clock.jump = function jump(tickValue) { const msFloat = tickValueToMs(tickValue); const ms = Math.floor(msFloat); forEachActiveTimer(clock, (timer) => { if (clock.now + ms > timer.callAt) { timer.callAt = clock.now + ms; } }); // Rebuild heap as order might have changed rebuildTimerHeap(clock); clock.tick(ms); return clock.now; }; if (isPresent.performance) { clock.performance = Object.create(null); clock.performance.now = fakePerformanceNow; } if (isPresent.hrtime) { clock.hrtime = hrtime; } /** * @returns {Timer[]} */ clock.uninstall = function () { uninstalled = true; clock.setTickMode({ mode: "manual" }); if (clock.methods) { const installedHrTime = "_hrtime"; const installedNextTick = "_nextTick"; let method, i, l; for (i = 0, l = clock.methods.length; i < l; i++) { method = clock.methods[i]; if (method === "hrtime" && _global.process) { _global.process.hrtime = clock[installedHrTime]; } else if (method === "nextTick" && _global.process) { _global.process.nextTick = clock[installedNextTick]; } else if (method === "performance") { const originalPerfDescriptor = Object.getOwnPropertyDescriptor( clock, `_${method}`, ); if ( originalPerfDescriptor && originalPerfDescriptor.get && !originalPerfDescriptor.set ) { Object.defineProperty( _global, method, originalPerfDescriptor, ); } else if (originalPerfDescriptor.configurable) { _global[method] = clock[`_${method}`]; } } else { if (clock[method] && clock[method].hasOwnProperty) { _global[method] = clock[`_${method}`]; } else { try { delete _global[method]; } catch { /* eslint no-empty: "off" */ } } } if (clock.timersModuleMethods !== undefined) { for ( let j = 0; j < clock.timersModuleMethods.length; j++ ) { const entry = clock.timersModuleMethods[j]; timersModule[entry.methodName] = entry.original; } } if (clock.timersPromisesModuleMethods !== undefined) { for ( let j = 0; j < clock.timersPromisesModuleMethods.length; j++ ) { const entry = clock.timersPromisesModuleMethods[j]; timersPromisesModule[entry.methodName] = entry.original; } } } // Prevent multiple executions which will completely remove these props clock.methods = []; } if (clock.abortListenerMap) { for (const [ listener, signal, ] of clock.abortListenerMap.entries()) { signal.removeEventListener("abort", listener); clock.abortListenerMap.delete(listener); } } // return pending timers, to enable checking what timers remained on uninstall if (!clock.timerHeap) { return []; } return clock.timerHeap.timers.slice(); }; return clock; } /** * Starts the interval used to advance the clock automatically. * @param {Clock} clock * @param {number} delta */ function createIntervalTick(clock, delta) { const intervalTick = doIntervalTick.bind(null, clock, delta); const intervalId = originalSetInterval(intervalTick, delta); clock.attachedInterval = intervalId; } /* eslint-disable complexity */ /** * @param {Config=} [config] Optional config * @returns {Clock} */ function install(config) { if ( arguments.length > 1 || config instanceof Date || Array.isArray(config) || typeof config === "number" ) { throw new TypeError( `FakeTimers.install called with ${String( config, )} install requires an object parameter`, ); } if (_global.Date.isFake === true) { // Timers are already faked; this is a problem. // Make the user reset timers before continuing. throw new TypeError( "Can't install fake timers twice on the same global object.", ); } // eslint-disable-next-line no-param-reassign config = typeof config !== "undefined" ? config : {}; config.shouldAdvanceTime = config.shouldAdvanceTime || false; config.advanceTimeDelta = config.advanceTimeDelta || 20; config.shouldClearNativeTimers = config.shouldClearNativeTimers || false; const hasToFake = Object.prototype.hasOwnProperty.call( config, "toFake", ); const hasToNotFake = Object.prototype.hasOwnProperty.call( config, "toNotFake", ); if (hasToFake && hasToNotFake) { throw new TypeError( "config.toFake and config.toNotFake cannot be used together", ); } if (config.target) { throw new TypeError( "config.target is no longer supported. Use `withGlobal(target)` instead.", ); } /** * Handles a missing timer or API name during installation. * @param {string} timer - the name of the missing timer or object */ function handleMissingTimer(timer) { if (config.ignoreMissingTimers) { return; } throw new ReferenceError( `non-existent timers and/or objects cannot be faked: '${timer}'`, ); } let i, l; const clock = createClock(config.now, config.loopLimit); clock.shouldClearNativeTimers = config.shouldClearNativeTimers; clock.abortListenerMap = new Map(); if (hasToFake) { clock.methods = /** @type {FakeMethod[]} */ (config.toFake || []); if (clock.methods.length === 0) { clock.methods = /** @type {FakeMethod[]} */ ( Object.keys(timers) ); } } else if (hasToNotFake) { const methodsToNotFake = /** @type {string[]} */ ( config.toNotFake || [] ); clock.methods = /** @type {FakeMethod[]} */ ( Object.keys(timers).filter( (method) => !methodsToNotFake.includes(method), ) ); } else { clock.methods = /** @type {FakeMethod[]} */ (Object.keys(timers)); } if (config.shouldAdvanceTime === true) { clock.setTickMode({ mode: "interval", delta: config.advanceTimeDelta, }); } if (clock.methods.includes("performance")) { const proto = (() => { if (hasPerformanceConstructorPrototype) { return _global.performance.constructor.prototype; } if (hasPerformancePrototype) { return _global.Performance.prototype; } })(); if (proto) { Object.getOwnPropertyNames(proto).forEach(function (name) { if (name !== "now") { clock.performance[name] = name.indexOf("getEntries") === 0 ? NOOP_ARRAY : NOOP; } }); // ensure `mark` returns a value that is valid clock.performance.mark = (name) => new FakePerformanceEntry(name, "mark", 0, 0); clock.performance.measure = (name) => new FakePerformanceEntry(name, "measure", 0, 100); // `timeOrigin` should return the time of when the Window session started // (or the Worker was installed) clock.performance.timeOrigin = getEpoch(config.now); } else if ((config.toFake || []).includes("performance")) { handleMissingTimer("performance"); } } if (_global === globalObject && timersModule) { clock.timersModuleMethods = []; } if (_global === globalObject && timersPromisesModule) { clock.timersPromisesModuleMethods = []; } for (i = 0, l = clock.methods.length; i < l; i++) { const nameOfMethodToReplace = clock.methods[i]; if (!isPresent[nameOfMethodToReplace]) { handleMissingTimer(nameOfMethodToReplace); // eslint-disable-next-line continue; } if (nameOfMethodToReplace === "hrtime") { if ( _global.process && typeof _global.process.hrtime === "function" ) { hijackMethod(_global.process, nameOfMethodToReplace, clock); } } else if (nameOfMethodToReplace === "nextTick") { if ( _global.process && typeof _global.process.nextTick === "function" ) { hijackMethod(_global.process, nameOfMethodToReplace, clock); } } else { hijackMethod(_global, nameOfMethodToReplace, clock); } if ( clock.timersModuleMethods !== undefined && timersModule[nameOfMethodToReplace] ) { const original = timersModule[nameOfMethodToReplace]; clock.timersModuleMethods.push({ methodName: nameOfMethodToReplace, original: original, }); timersModule[nameOfMethodToReplace] = _global[nameOfMethodToReplace]; } if (clock.timersPromisesModuleMethods !== undefined) { if (nameOfMethodToReplace === "setTimeout") { clock.timersPromisesModuleMethods.push({ methodName: "setTimeout", original: timersPromisesModule.setTimeout, }); timersPromisesModule.setTimeout = ( delay, value, options = {}, ) => new Promise((resolve, reject) => { const abort = () => { options.signal.removeEventListener( "abort", abort, ); clock.abortListenerMap.delete(abort); // This is safe, there is no code path that leads to this function // being invoked before handle has been assigned. // eslint-disable-next-line no-use-before-define clock.clearTimeout(handle); reject(options.signal.reason); }; const handle = clock.setTimeout(() => { if (options.signal) { options.signal.removeEventListener( "abort", abort, ); clock.abortListenerMap.delete(abort); } resolve(value); }, delay); if (options.signal) { if (options.signal.aborted) { abort(); } else { options.signal.addEventListener( "abort", abort, ); clock.abortListenerMap.set( abort, options.signal, ); } } }); } else if (nameOfMethodToReplace === "setImmediate") { clock.timersPromisesModuleMethods.push({ methodName: "setImmediate", original: timersPromisesModule.setImmediate, }); timersPromisesModule.setImmediate = (value, options = {}) => new Promise((resolve, reject) => { const abort = () => { options.signal.removeEventListener( "abort", abort, ); clock.abortListenerMap.delete(abort); // This is safe, there is no code path that leads to this function // being invoked before handle has been assigned. // eslint-disable-next-line no-use-before-define clock.clearImmediate(handle); reject(options.signal.reason); }; const handle = clock.setImmediate(() => { if (options.signal) { options.signal.removeEventListener( "abort", abort, ); clock.abortListenerMap.delete(abort); } resolve(value); }); if (options.signal) { if (options.signal.aborted) { abort(); } else { options.signal.addEventListener( "abort", abort, ); clock.abortListenerMap.set( abort, options.signal, ); } } }); } else if (nameOfMethodToReplace === "setInterval") { clock.timersPromisesModuleMethods.push({ methodName: "setInterval", original: timersPromisesModule.setInterval, }); timersPromisesModule.setInterval = ( delay, value, options = {}, ) => ({ [Symbol.asyncIterator]: () => { const createResolvable = () => { let resolve, reject; const promise = /** @type {Promise & { resolve: (value: unknown) => void; reject: (reason: unknown) => void }} */ ( new Promise((res, rej) => { resolve = res; reject = rej; }) ); promise.resolve = resolve; promise.reject = reject; return promise; }; let done = false; let hasThrown = false; let returnCall; let nextAvailable = 0; const nextQueue = []; const handle = clock.setInterval(() => { if (nextQueue.length > 0) { nextQueue.shift().resolve(); } else { nextAvailable++; } }, delay); const abort = () => { options.signal.removeEventListener( "abort", abort, ); clock.abortListenerMap.delete(abort); clock.clearInterval(handle); done = true; for (const resolvable of nextQueue) { resolvable.resolve(); } }; if (options.signal) { if (options.signal.aborted) { done = true; } else { options.signal.addEventListener( "abort", abort, ); clock.abortListenerMap.set( abort, options.signal, ); } } return { next: async () => { if (options.signal?.aborted && !hasThrown) { hasThrown = true; throw options.signal.reason; } if (done) { return { done: true, value: undefined }; } if (nextAvailable > 0) { nextAvailable--; return { done: false, value: value }; } const resolvable = createResolvable(); nextQueue.push(resolvable); await resolvable; if (returnCall && nextQueue.length === 0) { returnCall.resolve(); } if (options.signal?.aborted && !hasThrown) { hasThrown = true; throw options.signal.reason; } if (done) { return { done: true, value: undefined }; } return { done: false, value: value }; }, return: async () => { if (done) { return { done: true, value: undefined }; } if (nextQueue.length > 0) { returnCall = createResolvable(); await returnCall; } clock.clearInterval(handle); done = true; if (options.signal) { options.signal.removeEventListener( "abort", abort, ); clock.abortListenerMap.delete(abort); } return { done: true, value: undefined }; }, }; }, }); } } } return clock; } /* eslint-enable complexity */ return { timers: timers, createClock: createClock, install: install, withGlobal: withGlobal, }; } /** @type {FakeTimers} */ const defaultImplementation = withGlobal(globalObject); exports.timers = defaultImplementation.timers; exports.createClock = defaultImplementation.createClock; exports.install = defaultImplementation.install; /** @type {WithGlobal} */ exports.withGlobal = withGlobal;