legacyFakeTimers.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615
  1. 'use strict';
  2. Object.defineProperty(exports, '__esModule', {
  3. value: true
  4. });
  5. exports.default = void 0;
  6. function _util() {
  7. const data = _interopRequireDefault(require('util'));
  8. _util = function () {
  9. return data;
  10. };
  11. return data;
  12. }
  13. function _jestMessageUtil() {
  14. const data = require('jest-message-util');
  15. _jestMessageUtil = function () {
  16. return data;
  17. };
  18. return data;
  19. }
  20. function _jestUtil() {
  21. const data = require('jest-util');
  22. _jestUtil = function () {
  23. return data;
  24. };
  25. return data;
  26. }
  27. function _interopRequireDefault(obj) {
  28. return obj && obj.__esModule ? obj : {default: obj};
  29. }
  30. function _defineProperty(obj, key, value) {
  31. if (key in obj) {
  32. Object.defineProperty(obj, key, {
  33. value: value,
  34. enumerable: true,
  35. configurable: true,
  36. writable: true
  37. });
  38. } else {
  39. obj[key] = value;
  40. }
  41. return obj;
  42. }
  43. const MS_IN_A_YEAR = 31536000000;
  44. class FakeTimers {
  45. constructor({global, moduleMocker, timerConfig, config, maxLoops}) {
  46. _defineProperty(this, '_cancelledTicks', void 0);
  47. _defineProperty(this, '_config', void 0);
  48. _defineProperty(this, '_disposed', void 0);
  49. _defineProperty(this, '_fakeTimerAPIs', void 0);
  50. _defineProperty(this, '_global', void 0);
  51. _defineProperty(this, '_immediates', void 0);
  52. _defineProperty(this, '_maxLoops', void 0);
  53. _defineProperty(this, '_moduleMocker', void 0);
  54. _defineProperty(this, '_now', void 0);
  55. _defineProperty(this, '_ticks', void 0);
  56. _defineProperty(this, '_timerAPIs', void 0);
  57. _defineProperty(this, '_timers', void 0);
  58. _defineProperty(this, '_uuidCounter', void 0);
  59. _defineProperty(this, '_timerConfig', void 0);
  60. this._global = global;
  61. this._timerConfig = timerConfig;
  62. this._config = config;
  63. this._maxLoops = maxLoops || 100000;
  64. this._uuidCounter = 1;
  65. this._moduleMocker = moduleMocker; // Store original timer APIs for future reference
  66. this._timerAPIs = {
  67. clearImmediate: global.clearImmediate,
  68. clearInterval: global.clearInterval,
  69. clearTimeout: global.clearTimeout,
  70. nextTick: global.process && global.process.nextTick,
  71. setImmediate: global.setImmediate,
  72. setInterval: global.setInterval,
  73. setTimeout: global.setTimeout
  74. };
  75. this.reset();
  76. }
  77. clearAllTimers() {
  78. this._immediates = [];
  79. this._timers.clear();
  80. }
  81. dispose() {
  82. this._disposed = true;
  83. this.clearAllTimers();
  84. }
  85. reset() {
  86. this._cancelledTicks = {};
  87. this._now = 0;
  88. this._ticks = [];
  89. this._immediates = [];
  90. this._timers = new Map();
  91. }
  92. runAllTicks() {
  93. this._checkFakeTimers(); // Only run a generous number of ticks and then bail.
  94. // This is just to help avoid recursive loops
  95. let i;
  96. for (i = 0; i < this._maxLoops; i++) {
  97. const tick = this._ticks.shift();
  98. if (tick === undefined) {
  99. break;
  100. }
  101. if (!this._cancelledTicks.hasOwnProperty(tick.uuid)) {
  102. // Callback may throw, so update the map prior calling.
  103. this._cancelledTicks[tick.uuid] = true;
  104. tick.callback();
  105. }
  106. }
  107. if (i === this._maxLoops) {
  108. throw new Error(
  109. 'Ran ' +
  110. this._maxLoops +
  111. ' ticks, and there are still more! ' +
  112. "Assuming we've hit an infinite recursion and bailing out..."
  113. );
  114. }
  115. }
  116. runAllImmediates() {
  117. this._checkFakeTimers(); // Only run a generous number of immediates and then bail.
  118. let i;
  119. for (i = 0; i < this._maxLoops; i++) {
  120. const immediate = this._immediates.shift();
  121. if (immediate === undefined) {
  122. break;
  123. }
  124. this._runImmediate(immediate);
  125. }
  126. if (i === this._maxLoops) {
  127. throw new Error(
  128. 'Ran ' +
  129. this._maxLoops +
  130. ' immediates, and there are still more! Assuming ' +
  131. "we've hit an infinite recursion and bailing out..."
  132. );
  133. }
  134. }
  135. _runImmediate(immediate) {
  136. try {
  137. immediate.callback();
  138. } finally {
  139. this._fakeClearImmediate(immediate.uuid);
  140. }
  141. }
  142. runAllTimers() {
  143. this._checkFakeTimers();
  144. this.runAllTicks();
  145. this.runAllImmediates(); // Only run a generous number of timers and then bail.
  146. // This is just to help avoid recursive loops
  147. let i;
  148. for (i = 0; i < this._maxLoops; i++) {
  149. const nextTimerHandle = this._getNextTimerHandle(); // If there are no more timer handles, stop!
  150. if (nextTimerHandle === null) {
  151. break;
  152. }
  153. this._runTimerHandle(nextTimerHandle); // Some of the immediate calls could be enqueued
  154. // during the previous handling of the timers, we should
  155. // run them as well.
  156. if (this._immediates.length) {
  157. this.runAllImmediates();
  158. }
  159. if (this._ticks.length) {
  160. this.runAllTicks();
  161. }
  162. }
  163. if (i === this._maxLoops) {
  164. throw new Error(
  165. 'Ran ' +
  166. this._maxLoops +
  167. ' timers, and there are still more! ' +
  168. "Assuming we've hit an infinite recursion and bailing out..."
  169. );
  170. }
  171. }
  172. runOnlyPendingTimers() {
  173. // We need to hold the current shape of `this._timers` because existing
  174. // timers can add new ones to the map and hence would run more than necessary.
  175. // See https://github.com/facebook/jest/pull/4608 for details
  176. const timerEntries = Array.from(this._timers.entries());
  177. this._checkFakeTimers();
  178. this._immediates.forEach(this._runImmediate, this);
  179. timerEntries
  180. .sort(([, left], [, right]) => left.expiry - right.expiry)
  181. .forEach(([timerHandle]) => this._runTimerHandle(timerHandle));
  182. }
  183. advanceTimersToNextTimer(steps = 1) {
  184. if (steps < 1) {
  185. return;
  186. }
  187. const nextExpiry = Array.from(this._timers.values()).reduce(
  188. (minExpiry, timer) => {
  189. if (minExpiry === null || timer.expiry < minExpiry) return timer.expiry;
  190. return minExpiry;
  191. },
  192. null
  193. );
  194. if (nextExpiry !== null) {
  195. this.advanceTimersByTime(nextExpiry - this._now);
  196. this.advanceTimersToNextTimer(steps - 1);
  197. }
  198. }
  199. advanceTimersByTime(msToRun) {
  200. this._checkFakeTimers(); // Only run a generous number of timers and then bail.
  201. // This is just to help avoid recursive loops
  202. let i;
  203. for (i = 0; i < this._maxLoops; i++) {
  204. const timerHandle = this._getNextTimerHandle(); // If there are no more timer handles, stop!
  205. if (timerHandle === null) {
  206. break;
  207. }
  208. const timerValue = this._timers.get(timerHandle);
  209. if (timerValue === undefined) {
  210. break;
  211. }
  212. const nextTimerExpiry = timerValue.expiry;
  213. if (this._now + msToRun < nextTimerExpiry) {
  214. // There are no timers between now and the target we're running to, so
  215. // adjust our time cursor and quit
  216. this._now += msToRun;
  217. break;
  218. } else {
  219. msToRun -= nextTimerExpiry - this._now;
  220. this._now = nextTimerExpiry;
  221. this._runTimerHandle(timerHandle);
  222. }
  223. }
  224. if (i === this._maxLoops) {
  225. throw new Error(
  226. 'Ran ' +
  227. this._maxLoops +
  228. ' timers, and there are still more! ' +
  229. "Assuming we've hit an infinite recursion and bailing out..."
  230. );
  231. }
  232. }
  233. runWithRealTimers(cb) {
  234. const prevClearImmediate = this._global.clearImmediate;
  235. const prevClearInterval = this._global.clearInterval;
  236. const prevClearTimeout = this._global.clearTimeout;
  237. const prevNextTick = this._global.process.nextTick;
  238. const prevSetImmediate = this._global.setImmediate;
  239. const prevSetInterval = this._global.setInterval;
  240. const prevSetTimeout = this._global.setTimeout;
  241. this.useRealTimers();
  242. let cbErr = null;
  243. let errThrown = false;
  244. try {
  245. cb();
  246. } catch (e) {
  247. errThrown = true;
  248. cbErr = e;
  249. }
  250. this._global.clearImmediate = prevClearImmediate;
  251. this._global.clearInterval = prevClearInterval;
  252. this._global.clearTimeout = prevClearTimeout;
  253. this._global.process.nextTick = prevNextTick;
  254. this._global.setImmediate = prevSetImmediate;
  255. this._global.setInterval = prevSetInterval;
  256. this._global.setTimeout = prevSetTimeout;
  257. if (errThrown) {
  258. throw cbErr;
  259. }
  260. }
  261. useRealTimers() {
  262. const global = this._global;
  263. (0, _jestUtil().setGlobal)(
  264. global,
  265. 'clearImmediate',
  266. this._timerAPIs.clearImmediate
  267. );
  268. (0, _jestUtil().setGlobal)(
  269. global,
  270. 'clearInterval',
  271. this._timerAPIs.clearInterval
  272. );
  273. (0, _jestUtil().setGlobal)(
  274. global,
  275. 'clearTimeout',
  276. this._timerAPIs.clearTimeout
  277. );
  278. (0, _jestUtil().setGlobal)(
  279. global,
  280. 'setImmediate',
  281. this._timerAPIs.setImmediate
  282. );
  283. (0, _jestUtil().setGlobal)(
  284. global,
  285. 'setInterval',
  286. this._timerAPIs.setInterval
  287. );
  288. (0, _jestUtil().setGlobal)(
  289. global,
  290. 'setTimeout',
  291. this._timerAPIs.setTimeout
  292. );
  293. global.process.nextTick = this._timerAPIs.nextTick;
  294. }
  295. useFakeTimers() {
  296. this._createMocks();
  297. const global = this._global;
  298. (0, _jestUtil().setGlobal)(
  299. global,
  300. 'clearImmediate',
  301. this._fakeTimerAPIs.clearImmediate
  302. );
  303. (0, _jestUtil().setGlobal)(
  304. global,
  305. 'clearInterval',
  306. this._fakeTimerAPIs.clearInterval
  307. );
  308. (0, _jestUtil().setGlobal)(
  309. global,
  310. 'clearTimeout',
  311. this._fakeTimerAPIs.clearTimeout
  312. );
  313. (0, _jestUtil().setGlobal)(
  314. global,
  315. 'setImmediate',
  316. this._fakeTimerAPIs.setImmediate
  317. );
  318. (0, _jestUtil().setGlobal)(
  319. global,
  320. 'setInterval',
  321. this._fakeTimerAPIs.setInterval
  322. );
  323. (0, _jestUtil().setGlobal)(
  324. global,
  325. 'setTimeout',
  326. this._fakeTimerAPIs.setTimeout
  327. );
  328. global.process.nextTick = this._fakeTimerAPIs.nextTick;
  329. }
  330. getTimerCount() {
  331. this._checkFakeTimers();
  332. return this._timers.size + this._immediates.length + this._ticks.length;
  333. }
  334. _checkFakeTimers() {
  335. var _this$_fakeTimerAPIs;
  336. if (
  337. this._global.setTimeout !==
  338. ((_this$_fakeTimerAPIs = this._fakeTimerAPIs) === null ||
  339. _this$_fakeTimerAPIs === void 0
  340. ? void 0
  341. : _this$_fakeTimerAPIs.setTimeout)
  342. ) {
  343. this._global.console.warn(
  344. `A function to advance timers was called but the timers API is not ` +
  345. `mocked with fake timers. Call \`jest.useFakeTimers()\` in this ` +
  346. `test or enable fake timers globally by setting ` +
  347. `\`"timers": "fake"\` in ` +
  348. `the configuration file. This warning is likely a result of a ` +
  349. `default configuration change in Jest 15.\n\n` +
  350. `Release Blog Post: https://jestjs.io/blog/2016/09/01/jest-15.html\n` +
  351. `Stack Trace:\n` +
  352. (0, _jestMessageUtil().formatStackTrace)(
  353. new Error().stack,
  354. this._config,
  355. {
  356. noStackTrace: false
  357. }
  358. )
  359. );
  360. }
  361. }
  362. _createMocks() {
  363. const fn = (
  364. impl // @ts-expect-error TODO: figure out better typings here
  365. ) => this._moduleMocker.fn().mockImplementation(impl);
  366. const promisifiableFakeSetTimeout = fn(this._fakeSetTimeout.bind(this)); // @ts-expect-error TODO: figure out better typings here
  367. promisifiableFakeSetTimeout[_util().default.promisify.custom] = (
  368. delay,
  369. arg
  370. ) =>
  371. new Promise(resolve => promisifiableFakeSetTimeout(resolve, delay, arg)); // TODO: add better typings; these are mocks, but typed as regular timers
  372. this._fakeTimerAPIs = {
  373. clearImmediate: fn(this._fakeClearImmediate.bind(this)),
  374. clearInterval: fn(this._fakeClearTimer.bind(this)),
  375. clearTimeout: fn(this._fakeClearTimer.bind(this)),
  376. nextTick: fn(this._fakeNextTick.bind(this)),
  377. // @ts-expect-error TODO: figure out better typings here
  378. setImmediate: fn(this._fakeSetImmediate.bind(this)),
  379. // @ts-expect-error TODO: figure out better typings here
  380. setInterval: fn(this._fakeSetInterval.bind(this)),
  381. // @ts-expect-error TODO: figure out better typings here
  382. setTimeout: promisifiableFakeSetTimeout
  383. };
  384. }
  385. _fakeClearTimer(timerRef) {
  386. const uuid = this._timerConfig.refToId(timerRef);
  387. if (uuid) {
  388. this._timers.delete(String(uuid));
  389. }
  390. }
  391. _fakeClearImmediate(uuid) {
  392. this._immediates = this._immediates.filter(
  393. immediate => immediate.uuid !== uuid
  394. );
  395. }
  396. _fakeNextTick(callback, ...args) {
  397. if (this._disposed) {
  398. return;
  399. }
  400. const uuid = String(this._uuidCounter++);
  401. this._ticks.push({
  402. callback: () => callback.apply(null, args),
  403. uuid
  404. });
  405. const cancelledTicks = this._cancelledTicks;
  406. this._timerAPIs.nextTick(() => {
  407. if (!cancelledTicks.hasOwnProperty(uuid)) {
  408. // Callback may throw, so update the map prior calling.
  409. cancelledTicks[uuid] = true;
  410. callback.apply(null, args);
  411. }
  412. });
  413. }
  414. _fakeSetImmediate(callback, ...args) {
  415. if (this._disposed) {
  416. return null;
  417. }
  418. const uuid = String(this._uuidCounter++);
  419. this._immediates.push({
  420. callback: () => callback.apply(null, args),
  421. uuid
  422. });
  423. this._timerAPIs.setImmediate(() => {
  424. if (this._immediates.find(x => x.uuid === uuid)) {
  425. try {
  426. callback.apply(null, args);
  427. } finally {
  428. this._fakeClearImmediate(uuid);
  429. }
  430. }
  431. });
  432. return uuid;
  433. }
  434. _fakeSetInterval(callback, intervalDelay, ...args) {
  435. if (this._disposed) {
  436. return null;
  437. }
  438. if (intervalDelay == null) {
  439. intervalDelay = 0;
  440. }
  441. const uuid = this._uuidCounter++;
  442. this._timers.set(String(uuid), {
  443. callback: () => callback.apply(null, args),
  444. expiry: this._now + intervalDelay,
  445. interval: intervalDelay,
  446. type: 'interval'
  447. });
  448. return this._timerConfig.idToRef(uuid);
  449. }
  450. _fakeSetTimeout(callback, delay, ...args) {
  451. if (this._disposed) {
  452. return null;
  453. } // eslint-disable-next-line no-bitwise
  454. delay = Number(delay) | 0;
  455. const uuid = this._uuidCounter++;
  456. this._timers.set(String(uuid), {
  457. callback: () => callback.apply(null, args),
  458. expiry: this._now + delay,
  459. interval: undefined,
  460. type: 'timeout'
  461. });
  462. return this._timerConfig.idToRef(uuid);
  463. }
  464. _getNextTimerHandle() {
  465. let nextTimerHandle = null;
  466. let soonestTime = MS_IN_A_YEAR;
  467. this._timers.forEach((timer, uuid) => {
  468. if (timer.expiry < soonestTime) {
  469. soonestTime = timer.expiry;
  470. nextTimerHandle = uuid;
  471. }
  472. });
  473. return nextTimerHandle;
  474. }
  475. _runTimerHandle(timerHandle) {
  476. const timer = this._timers.get(timerHandle);
  477. if (!timer) {
  478. return;
  479. }
  480. switch (timer.type) {
  481. case 'timeout':
  482. const callback = timer.callback;
  483. this._timers.delete(timerHandle);
  484. callback();
  485. break;
  486. case 'interval':
  487. timer.expiry = this._now + (timer.interval || 0);
  488. timer.callback();
  489. break;
  490. default:
  491. throw new Error('Unexpected timer type: ' + timer.type);
  492. }
  493. }
  494. }
  495. exports.default = FakeTimers;