"use strict"; const globalObject = require("@sinonjs/commons").global; /** * @typedef {object} IdleDeadline * @property {boolean} didTimeout - whether or not the callback was called before reaching the optional timeout * @property {function():number} timeRemaining - a floating-point value providing an estimate of the number of milliseconds remaining in the current idle period */ /** * Queues a function to be called during a browser's idle periods * * @callback RequestIdleCallback * @param {function(IdleDeadline)} callback * @param {{timeout: number}} options - an options object * @returns {number} the id */ /** * @callback NextTick * @param {VoidVarArgsFunc} callback - the callback to run * @param {...*} arguments - optional arguments to call the callback with * @returns {void} */ /** * @callback SetImmediate * @param {VoidVarArgsFunc} callback - the callback to run * @param {...*} arguments - optional arguments to call the callback with * @returns {NodeImmediate} */ /** * @callback VoidVarArgsFunc * @param {...*} callback - the callback to run * @returns {void} */ /** * @typedef RequestAnimationFrame * @property {function(number):void} requestAnimationFrame * @returns {number} - the id */ /** * @typedef Performance * @property {function(): number} now */ /* eslint-disable jsdoc/require-property-description */ /** * @typedef {object} Clock * @property {number} now - the current time * @property {Date} Date - the Date constructor * @property {number} loopLimit - the maximum number of timers before assuming an infinite loop * @property {RequestIdleCallback} requestIdleCallback * @property {function(number):void} cancelIdleCallback * @property {setTimeout} setTimeout * @property {clearTimeout} clearTimeout * @property {NextTick} nextTick * @property {queueMicrotask} queueMicrotask * @property {setInterval} setInterval * @property {clearInterval} clearInterval * @property {SetImmediate} setImmediate * @property {function(NodeImmediate):void} clearImmediate * @property {function():number} countTimers * @property {RequestAnimationFrame} requestAnimationFrame * @property {function(number):void} cancelAnimationFrame * @property {function():void} runMicrotasks * @property {function(string | number): number} tick * @property {function(string | number): Promise} tickAsync * @property {function(): number} next * @property {function(): Promise} nextAsync * @property {function(): number} runAll * @property {function(): number} runToFrame * @property {function(): Promise} runAllAsync * @property {function(): number} runToLast * @property {function(): Promise} runToLastAsync * @property {function(): void} reset * @property {function(number | Date): void} setSystemTime * @property {Performance} performance * @property {function(number[]): number[]} hrtime - process.hrtime (legacy) * @property {function(): void} uninstall Uninstall the clock. * @property {Function[]} methods - the methods that are faked * @property {boolean} [shouldClearNativeTimers] inherited from config */ /* eslint-enable jsdoc/require-property-description */ /** * Configuration object for the `install` method. * * @typedef {object} Config * @property {number|Date} [now] a number (in milliseconds) or a Date object (default epoch) * @property {string[]} [toFake] names of the methods that should be faked. * @property {number} [loopLimit] the maximum number of timers that will be run when calling runAll() * @property {boolean} [shouldAdvanceTime] tells FakeTimers to increment mocked time automatically (default false) * @property {number} [advanceTimeDelta] increment mocked time every <> ms (default: 20ms) * @property {boolean} [shouldClearNativeTimers] forwards clear timer calls to native functions if they are not fakes (default: false) */ /* eslint-disable jsdoc/require-property-description */ /** * The internal structure to describe a scheduled fake timer * * @typedef {object} Timer * @property {Function} func * @property {*[]} args * @property {number} delay * @property {number} callAt * @property {number} createdAt * @property {boolean} immediate * @property {number} id * @property {Error} [error] */ /** * A Node timer * * @typedef {object} NodeImmediate * @property {function(): boolean} hasRef * @property {function(): NodeImmediate} ref * @property {function(): NodeImmediate} unref */ /* eslint-enable jsdoc/require-property-description */ /* eslint-disable complexity */ /** * Mocks available features in the specified global namespace. * * @param {*} _global Namespace to mock (e.g. `window`) * @returns {FakeTimers} */ function withGlobal(_global) { const userAgent = _global.navigator && _global.navigator.userAgent; const isRunningInIE = userAgent && userAgent.indexOf("MSIE ") > -1; 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 timeoutResult = _global.setTimeout(NOOP, 0); const addTimerReturnsObject = typeof timeoutResult === "object"; const hrtimePresent = _global.process && typeof _global.process.hrtime === "function"; const hrtimeBigintPresent = hrtimePresent && typeof _global.process.hrtime.bigint === "function"; const nextTickPresent = _global.process && typeof _global.process.nextTick === "function"; const utilPromisify = _global.process && require("util").promisify; const performancePresent = _global.performance && typeof _global.performance.now === "function"; const hasPerformancePrototype = _global.Performance && (typeof _global.Performance).match(/^(function|object)$/); const queueMicrotaskPresent = _global.hasOwnProperty("queueMicrotask"); const requestAnimationFramePresent = _global.requestAnimationFrame && typeof _global.requestAnimationFrame === "function"; const cancelAnimationFramePresent = _global.cancelAnimationFrame && typeof _global.cancelAnimationFrame === "function"; const requestIdleCallbackPresent = _global.requestIdleCallback && typeof _global.requestIdleCallback === "function"; const cancelIdleCallbackPresent = _global.cancelIdleCallback && typeof _global.cancelIdleCallback === "function"; const setImmediatePresent = _global.setImmediate && typeof _global.setImmediate === "function"; // Make properties writable in IE, as per // https://www.adequatelygood.com/Replacing-setTimeout-Globally.html /* eslint-disable no-self-assign */ if (isRunningInIE) { _global.setTimeout = _global.setTimeout; _global.clearTimeout = _global.clearTimeout; _global.setInterval = _global.setInterval; _global.clearInterval = _global.clearInterval; _global.Date = _global.Date; } // setImmediate is not a standard function // avoid adding the prop to the window object if not present if (setImmediatePresent) { _global.setImmediate = _global.setImmediate; _global.clearImmediate = _global.clearImmediate; } /* eslint-enable no-self-assign */ _global.clearTimeout(timeoutResult); const NativeDate = _global.Date; let uniqueTimerId = idCounterStart; /** * @param {number} num * @returns {boolean} */ function isNumberFinite(num) { if (Number.isFinite) { return Number.isFinite(num); } return isFinite(num); } let isNearInfiniteLimit = false; /** * @param {Clock} clock * @param {number} i */ function checkIsNearInfiniteLimit(clock, i) { if (clock.loopLimit && i === clock.loopLimit - 1) { isNearInfiniteLimit = true; } } /** * */ function resetIsNearInfiniteLimit() { 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} epoch the system time * @returns {number} */ function getEpoch(epoch) { if (!epoch) { return 0; } if (typeof epoch.getTime === "function") { return epoch.getTime(); } if (typeof epoch === "number") { return epoch; } 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 */ 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 (e) { // noop } return infiniteLoopError; } /** * @param {Date} target * @param {Date} source * @returns {Date} the target after modifications */ function mirrorDateProperties(target, source) { let prop; for (prop in source) { if (source.hasOwnProperty(prop)) { target[prop] = source[prop]; } } // set special now implementation if (source.now) { target.now = function now() { return target.clock.now; }; } else { delete target.now; } // set special toSource implementation if (source.toSource) { target.toSource = function toSource() { return source.toSource(); }; } else { delete target.toSource; } // set special toString implementation target.toString = function toString() { return source.toString(); }; target.prototype = source.prototype; target.parse = source.parse; target.UTC = source.UTC; target.prototype.toUTCString = source.prototype.toUTCString; return target; } //eslint-disable-next-line jsdoc/require-jsdoc function createDate() { /** * @param {number} year * @param {number} month * @param {number} date * @param {number} hour * @param {number} minute * @param {number} second * @param {number} ms * * @returns {Date} */ function ClockDate(year, month, date, hour, minute, second, ms) { // 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)) { return new NativeDate(ClockDate.clock.now).toString(); } // if Date is called as a constructor with 'new' keyword // Defensive and verbose to avoid potential harm in passing // explicit undefined when user does not pass argument switch (arguments.length) { case 0: return new NativeDate(ClockDate.clock.now); case 1: return new NativeDate(year); case 2: return new NativeDate(year, month); case 3: return new NativeDate(year, month, date); case 4: return new NativeDate(year, month, date, hour); case 5: return new NativeDate(year, month, date, hour, minute); case 6: return new NativeDate( year, month, date, hour, minute, second ); default: return new NativeDate( year, month, date, hour, minute, second, ms ); } } return mirrorDateProperties(ClockDate, NativeDate); } //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; } 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); } } resetIsNearInfiniteLimit(); clock.jobs = []; } /** * @param {Clock} clock * @param {Timer} timer * @returns {number} id of the created timer */ function addTimer(clock, timer) { if (timer.func === undefined) { throw new Error("Callback must be provided to timer calls"); } if (addTimerReturnsObject) { // Node.js environment 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 (isNearInfiniteLimit) { timer.error = new Error(); } timer.type = timer.immediate ? "Immediate" : "Timeout"; if (timer.hasOwnProperty("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 (timer.hasOwnProperty("interval")) { timer.type = "Interval"; timer.interval = timer.interval > maxTimeout ? 1 : timer.interval; } if (timer.hasOwnProperty("animation")) { timer.type = "AnimationFrame"; timer.animation = true; } if (timer.hasOwnProperty("idleCallback")) { timer.type = "IdleCallback"; timer.idleCallback = true; } if (!clock.timers) { clock.timers = {}; } timer.id = uniqueTimerId++; timer.createdAt = clock.now; timer.callAt = clock.now + (parseInt(timer.delay) || (clock.duringTick ? 1 : 0)); clock.timers[timer.id] = timer; if (addTimerReturnsObject) { const res = { ref: function () { return res; }, unref: function () { return res; }, refresh: function () { clearTimeout(timer.id); const args = [timer.func, timer.delay].concat(timer.args); return setTimeout.apply(null, args); }, [Symbol.toPrimitive]: function () { return timer.id; }, }; return res; } return timer.id; } /* eslint consistent-return: "off" */ /** * Timer comparitor * * @param {Timer} a * @param {Timer} b * @returns {number} */ function compareTimers(a, b) { // 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; } // 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 } /** * @param {Clock} clock * @param {number} from * @param {number} to * * @returns {Timer} */ function firstTimerInRange(clock, from, to) { const timers = clock.timers; let timer = null; let id, isInRange; for (id in timers) { if (timers.hasOwnProperty(id)) { isInRange = inRange(from, to, timers[id]); if ( isInRange && (!timer || compareTimers(timer, timers[id]) === 1) ) { timer = timers[id]; } } } return timer; } /** * @param {Clock} clock * @returns {Timer} */ function firstTimer(clock) { const timers = clock.timers; let timer = null; let id; for (id in timers) { if (timers.hasOwnProperty(id)) { if (!timer || compareTimers(timer, timers[id]) === 1) { timer = timers[id]; } } } return timer; } /** * @param {Clock} clock * @returns {Timer} */ function lastTimer(clock) { const timers = clock.timers; let timer = null; let id; for (id in timers) { if (timers.hasOwnProperty(id)) { if (!timer || compareTimers(timer, timers[id]) === -1) { timer = timers[id]; } } } return timer; } /** * @param {Clock} clock * @param {Timer} timer */ function callTimer(clock, timer) { if (typeof timer.interval === "number") { clock.timers[timer.id].callAt += timer.interval; } else { delete clock.timers[timer.id]; } if (typeof timer.func === "function") { timer.func.apply(null, timer.args); } else { /* eslint no-eval: "off" */ const eval2 = eval; (function () { eval2(timer.func); })(); } } /** * Gets clear handler name for a given timer type * @param {string} ttype */ 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 */ function getScheduleHandler(ttype) { if (ttype === "IdleCallback" || ttype === "AnimationFrame") { return `request${ttype}`; } return `set${ttype}`; } /** * Creates an anonymous function to warn only once */ function createWarnOnce() { let calls = 0; return function (msg) { // eslint-disable-next-line !calls++ && console.warn(msg); }; } const warnOnce = createWarnOnce(); /** * @param {Clock} clock * @param {number} timerId * @param {string} ttype */ 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; } if (!clock.timers) { clock.timers = {}; } // 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; } 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`." ); } if (clock.timers.hasOwnProperty(id)) { // check that the ID matches a timer of the correct type const timer = clock.timers[id]; if ( timer.type === ttype || (timer.type === "Timeout" && ttype === "Interval") || (timer.type === "Interval" && ttype === "Timeout") ) { delete clock.timers[id]; } 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 {Clock} clock * @param {Config} config * @returns {Timer[]} */ function uninstall(clock, config) { let method, i, l; const installedHrTime = "_hrtime"; const installedNextTick = "_nextTick"; 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 (_global[method] && _global[method].hadOwnProperty) { _global[method] = clock[`_${method}`]; } else { try { delete _global[method]; } catch (ignore) { /* eslint no-empty: "off" */ } } } } if (config.shouldAdvanceTime === true) { _global.clearInterval(clock.attachedInterval); } // Prevent multiple executions which will completely remove these props clock.methods = []; // return pending timers, to enable checking what timers remained on uninstall if (!clock.timers) { return []; } return Object.keys(clock.timers).map(function mapper(key) { return clock.timers[key]; }); } /** * @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].hadOwnProperty = Object.prototype.hasOwnProperty.call( target, method ); clock[`_${method}`] = target[method]; if (method === "Date") { const date = mirrorDateProperties(clock[method], target[method]); target[method] = date; } 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); } /** * @typedef {object} Timers * @property {setTimeout} setTimeout * @property {clearTimeout} clearTimeout * @property {setInterval} setInterval * @property {clearInterval} clearInterval * @property {Date} Date * @property {SetImmediate=} setImmediate * @property {function(NodeImmediate): void=} clearImmediate * @property {function(number[]):number[]=} hrtime * @property {NextTick=} nextTick * @property {Performance=} performance * @property {RequestAnimationFrame=} requestAnimationFrame * @property {boolean=} queueMicrotask * @property {function(number): void=} cancelAnimationFrame * @property {RequestIdleCallback=} requestIdleCallback * @property {function(number): void=} cancelIdleCallback */ /** @type {Timers} */ const timers = { setTimeout: _global.setTimeout, clearTimeout: _global.clearTimeout, setInterval: _global.setInterval, clearInterval: _global.clearInterval, Date: _global.Date, }; if (setImmediatePresent) { timers.setImmediate = _global.setImmediate; timers.clearImmediate = _global.clearImmediate; } if (hrtimePresent) { timers.hrtime = _global.process.hrtime; } if (nextTickPresent) { timers.nextTick = _global.process.nextTick; } if (performancePresent) { timers.performance = _global.performance; } if (requestAnimationFramePresent) { timers.requestAnimationFrame = _global.requestAnimationFrame; } if (queueMicrotaskPresent) { timers.queueMicrotask = true; } if (cancelAnimationFramePresent) { timers.cancelAnimationFrame = _global.cancelAnimationFrame; } if (requestIdleCallbackPresent) { timers.requestIdleCallback = _global.requestIdleCallback; } if (cancelIdleCallbackPresent) { timers.cancelIdleCallback = _global.cancelIdleCallback; } const originalSetTimeout = _global.setImmediate || _global.setTimeout; /** * @param {Date|number} [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) { // eslint-disable-next-line no-param-reassign start = Math.floor(getEpoch(start)); // eslint-disable-next-line no-param-reassign loopLimit = loopLimit || 1000; let nanos = 0; const adjustedSystemTime = [0, 0]; // [millis, nanoremainder] 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)" ); } const clock = { now: start, Date: createDate(), loopLimit: loopLimit, }; clock.Date.clock = clock; //eslint-disable-next-line jsdoc/require-jsdoc function getTimeToNextFrame() { return 16 - ((clock.now - start) % 16); } //eslint-disable-next-line jsdoc/require-jsdoc function hrtime(prev) { const millisSinceStart = clock.now - adjustedSystemTime[0] - start; 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]; } if (hrtimeBigintPresent) { hrtime.bigint = function () { const parts = hrtime(); return BigInt(parts[0]) * BigInt(1e9) + BigInt(parts[1]); // eslint-disable-line }; } clock.requestIdleCallback = function requestIdleCallback( func, timeout ) { let timeToNextIdlePeriod = 0; if (clock.countTimers() > 0) { timeToNextIdlePeriod = 50; // const for now } const result = addTimer(clock, { func: func, args: Array.prototype.slice.call(arguments, 2), delay: typeof timeout === "undefined" ? timeToNextIdlePeriod : Math.min(timeout, timeToNextIdlePeriod), idleCallback: 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: 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(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 (setImmediatePresent) { clock.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 ( Object.keys(clock.timers || {}).length + (clock.jobs || []).length ); }; clock.requestAnimationFrame = function requestAnimationFrame(func) { const result = addTimer(clock, { func: func, delay: getTimeToNextFrame(), args: [clock.now + getTimeToNextFrame()], animation: true, }); return Number(result); }; clock.cancelAnimationFrame = function cancelAnimationFrame(timerId) { return clearTimer(clock, timerId, "AnimationFrame"); }; clock.runMicrotasks = function runMicrotasks() { runJobs(clock); }; /** * @param {number|string} tickValue milliseconds or a string parseable by parseTime * @param {boolean} isAsync * @param {Function} resolve * @param {Function} reject * @returns {number|undefined} will return the new `now` value or nothing for async */ function doTick(tickValue, isAsync, resolve, reject) { const msFloat = typeof tickValue === "number" ? tickValue : parseTime(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; } nanos = nanosTotal; let tickFrom = clock.now; let previous = clock.now; // ESLint fails to detect this correctly /* eslint-disable prefer-const */ let timer, firstException, oldNow, nextPromiseTick, compensationCheck, postTimerCall; /* eslint-enable prefer-const */ clock.duringTick = true; // perform microtasks oldNow = clock.now; runJobs(clock); if (oldNow !== clock.now) { // compensate for any setSystemTime() call during microtask callback tickFrom += clock.now - oldNow; tickTo += clock.now - oldNow; } //eslint-disable-next-line jsdoc/require-jsdoc function doTickInner() { // perform each timer in the requested range timer = firstTimerInRange(clock, tickFrom, tickTo); // eslint-disable-next-line no-unmodified-loop-condition while (timer && tickFrom <= tickTo) { if (clock.timers[timer.id]) { tickFrom = timer.callAt; clock.now = timer.callAt; oldNow = clock.now; try { runJobs(clock); callTimer(clock, timer); } catch (e) { firstException = 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; } compensationCheck(); } postTimerCall(); } // perform process.nextTick()s again oldNow = clock.now; runJobs(clock); if (oldNow !== clock.now) { // compensate for any setSystemTime() call during process.nextTick() callback tickFrom += clock.now - oldNow; tickTo += clock.now - oldNow; } clock.duringTick = false; // corner case: during runJobs new timers were scheduled which could be in the range [clock.now, tickTo] timer = firstTimerInRange(clock, tickFrom, tickTo); if (timer) { try { clock.tick(tickTo - clock.now); // do it all again - for the remainder of the requested range } catch (e) { firstException = firstException || e; } } else { // no timers remaining in the requested range: move the clock all the way to the end clock.now = tickTo; // update nanos nanos = nanosTotal; } if (firstException) { throw firstException; } if (isAsync) { resolve(clock.now); } else { return clock.now; } } nextPromiseTick = isAsync && function () { try { compensationCheck(); postTimerCall(); doTickInner(); } catch (e) { reject(e); } }; compensationCheck = function () { // compensate for any setSystemTime() call during timer callback if (oldNow !== clock.now) { tickFrom += clock.now - oldNow; tickTo += clock.now - oldNow; previous += clock.now - oldNow; } }; postTimerCall = function () { timer = firstTimerInRange(clock, previous, tickTo); previous = tickFrom; }; return doTickInner(); } /** * @param {string|number} tickValue number of milliseconds or a human-readable value like "01:11:15" * @returns {number} will return the new `now` value */ clock.tick = function tick(tickValue) { return doTick(tickValue, false); }; if (typeof _global.Promise !== "undefined") { /** * @param {string|number} tickValue number of milliseconds or a human-readable value like "01:11:15" * @returns {Promise} */ clock.tickAsync = function tickAsync(tickValue) { return new _global.Promise(function (resolve, reject) { originalSetTimeout(function () { try { doTick(tickValue, true, resolve, reject); } catch (e) { reject(e); } }); }); }; } 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; } }; if (typeof _global.Promise !== "undefined") { clock.nextAsync = function nextAsync() { return new _global.Promise(function (resolve, reject) { originalSetTimeout(function () { try { 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); } }); } catch (e) { reject(e); } }); }); }; } clock.runAll = function runAll() { let numTimers, i; runJobs(clock); for (i = 0; i < clock.loopLimit; i++) { if (!clock.timers) { resetIsNearInfiniteLimit(); return clock.now; } numTimers = Object.keys(clock.timers).length; if (numTimers === 0) { resetIsNearInfiniteLimit(); return clock.now; } clock.next(); checkIsNearInfiniteLimit(clock, i); } const excessJob = firstTimer(clock); throw getInfiniteLoopError(clock, excessJob); }; clock.runToFrame = function runToFrame() { return clock.tick(getTimeToNextFrame()); }; if (typeof _global.Promise !== "undefined") { clock.runAllAsync = function runAllAsync() { return new _global.Promise(function (resolve, reject) { let i = 0; /** * */ function doRun() { originalSetTimeout(function () { try { let numTimers; if (i < clock.loopLimit) { if (!clock.timers) { resetIsNearInfiniteLimit(); resolve(clock.now); return; } numTimers = Object.keys(clock.timers) .length; if (numTimers === 0) { resetIsNearInfiniteLimit(); resolve(clock.now); return; } clock.next(); i++; doRun(); checkIsNearInfiniteLimit(clock, i); return; } const excessJob = firstTimer(clock); reject(getInfiniteLoopError(clock, excessJob)); } catch (e) { reject(e); } }); } doRun(); }); }; } 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") { clock.runToLastAsync = function runToLastAsync() { return new _global.Promise(function (resolve, reject) { originalSetTimeout(function () { try { const timer = lastTimer(clock); if (!timer) { resolve(clock.now); } resolve(clock.tickAsync(timer.callAt)); } catch (e) { reject(e); } }); }); }; } clock.reset = function reset() { nanos = 0; clock.timers = {}; clock.jobs = []; clock.now = start; }; clock.setSystemTime = function setSystemTime(systemTime) { // determine time difference const newNow = getEpoch(systemTime); const difference = newNow - clock.now; let id, timer; 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 for (id in clock.timers) { if (clock.timers.hasOwnProperty(id)) { timer = clock.timers[id]; timer.createdAt += difference; timer.callAt += difference; } } }; if (performancePresent) { clock.performance = Object.create(null); if (hasPerformancePrototype) { const proto = _global.Performance.prototype; Object.getOwnPropertyNames(proto).forEach(function (name) { if (name.indexOf("getEntries") === 0) { // match expected return type for getEntries functions clock.performance[name] = NOOP_ARRAY; } else { clock.performance[name] = NOOP; } }); } clock.performance.now = function FakeTimersNow() { const hrt = hrtime(); const millis = hrt[0] * 1000 + hrt[1] / 1e6; return millis; }; } if (hrtimePresent) { clock.hrtime = hrtime; } return clock; } /* 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` ); } // 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; if (config.target) { throw new TypeError( "config.target is no longer supported. Use `withGlobal(target)` instead." ); } let i, l; const clock = createClock(config.now, config.loopLimit); clock.shouldClearNativeTimers = config.shouldClearNativeTimers; clock.uninstall = function () { return uninstall(clock, config); }; clock.methods = config.toFake || []; if (clock.methods.length === 0) { // do not fake nextTick by default - GitHub#126 clock.methods = Object.keys(timers).filter(function (key) { return key !== "nextTick" && key !== "queueMicrotask"; }); } if (config.shouldAdvanceTime === true) { const intervalTick = doIntervalTick.bind( null, clock, config.advanceTimeDelta ); const intervalId = _global.setInterval( intervalTick, config.advanceTimeDelta ); clock.attachedInterval = intervalId; } for (i = 0, l = clock.methods.length; i < l; i++) { const nameOfMethodToReplace = clock.methods[i]; 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); } } return clock; } /* eslint-enable complexity */ return { timers: timers, createClock: createClock, install: install, withGlobal: withGlobal, }; } /** * @typedef {object} FakeTimers * @property {Timers} timers * @property {createClock} createClock * @property {Function} install * @property {withGlobal} withGlobal */ /* eslint-enable complexity */ /** @type {FakeTimers} */ const defaultImplementation = withGlobal(globalObject); exports.timers = defaultImplementation.timers; exports.createClock = defaultImplementation.createClock; exports.install = defaultImplementation.install; exports.withGlobal = withGlobal;