fake_timers.js 14 KB

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