fake-async-test.js 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731
  1. 'use strict';
  2. /**
  3. * @license Angular v<unknown>
  4. * (c) 2010-2022 Google LLC. https://angular.io/
  5. * License: MIT
  6. */
  7. (function (global) {
  8. const OriginalDate = global.Date;
  9. // Since when we compile this file to `es2015`, and if we define
  10. // this `FakeDate` as `class FakeDate`, and then set `FakeDate.prototype`
  11. // there will be an error which is `Cannot assign to read only property 'prototype'`
  12. // so we need to use function implementation here.
  13. function FakeDate() {
  14. if (arguments.length === 0) {
  15. const d = new OriginalDate();
  16. d.setTime(FakeDate.now());
  17. return d;
  18. }
  19. else {
  20. const args = Array.prototype.slice.call(arguments);
  21. return new OriginalDate(...args);
  22. }
  23. }
  24. FakeDate.now = function () {
  25. const fakeAsyncTestZoneSpec = Zone.current.get('FakeAsyncTestZoneSpec');
  26. if (fakeAsyncTestZoneSpec) {
  27. return fakeAsyncTestZoneSpec.getFakeSystemTime();
  28. }
  29. return OriginalDate.now.apply(this, arguments);
  30. };
  31. FakeDate.UTC = OriginalDate.UTC;
  32. FakeDate.parse = OriginalDate.parse;
  33. // keep a reference for zone patched timer function
  34. const timers = {
  35. setTimeout: global.setTimeout,
  36. setInterval: global.setInterval,
  37. clearTimeout: global.clearTimeout,
  38. clearInterval: global.clearInterval
  39. };
  40. class Scheduler {
  41. // Next scheduler id.
  42. static { this.nextId = 1; }
  43. constructor() {
  44. // Scheduler queue with the tuple of end time and callback function - sorted by end time.
  45. this._schedulerQueue = [];
  46. // Current simulated time in millis.
  47. this._currentTickTime = 0;
  48. // Current fake system base time in millis.
  49. this._currentFakeBaseSystemTime = OriginalDate.now();
  50. // track requeuePeriodicTimer
  51. this._currentTickRequeuePeriodicEntries = [];
  52. }
  53. getCurrentTickTime() {
  54. return this._currentTickTime;
  55. }
  56. getFakeSystemTime() {
  57. return this._currentFakeBaseSystemTime + this._currentTickTime;
  58. }
  59. setFakeBaseSystemTime(fakeBaseSystemTime) {
  60. this._currentFakeBaseSystemTime = fakeBaseSystemTime;
  61. }
  62. getRealSystemTime() {
  63. return OriginalDate.now();
  64. }
  65. scheduleFunction(cb, delay, options) {
  66. options = {
  67. ...{
  68. args: [],
  69. isPeriodic: false,
  70. isRequestAnimationFrame: false,
  71. id: -1,
  72. isRequeuePeriodic: false
  73. },
  74. ...options
  75. };
  76. let currentId = options.id < 0 ? Scheduler.nextId++ : options.id;
  77. let endTime = this._currentTickTime + delay;
  78. // Insert so that scheduler queue remains sorted by end time.
  79. let newEntry = {
  80. endTime: endTime,
  81. id: currentId,
  82. func: cb,
  83. args: options.args,
  84. delay: delay,
  85. isPeriodic: options.isPeriodic,
  86. isRequestAnimationFrame: options.isRequestAnimationFrame
  87. };
  88. if (options.isRequeuePeriodic) {
  89. this._currentTickRequeuePeriodicEntries.push(newEntry);
  90. }
  91. let i = 0;
  92. for (; i < this._schedulerQueue.length; i++) {
  93. let currentEntry = this._schedulerQueue[i];
  94. if (newEntry.endTime < currentEntry.endTime) {
  95. break;
  96. }
  97. }
  98. this._schedulerQueue.splice(i, 0, newEntry);
  99. return currentId;
  100. }
  101. removeScheduledFunctionWithId(id) {
  102. for (let i = 0; i < this._schedulerQueue.length; i++) {
  103. if (this._schedulerQueue[i].id == id) {
  104. this._schedulerQueue.splice(i, 1);
  105. break;
  106. }
  107. }
  108. }
  109. removeAll() {
  110. this._schedulerQueue = [];
  111. }
  112. getTimerCount() {
  113. return this._schedulerQueue.length;
  114. }
  115. tickToNext(step = 1, doTick, tickOptions) {
  116. if (this._schedulerQueue.length < step) {
  117. return;
  118. }
  119. // Find the last task currently queued in the scheduler queue and tick
  120. // till that time.
  121. const startTime = this._currentTickTime;
  122. const targetTask = this._schedulerQueue[step - 1];
  123. this.tick(targetTask.endTime - startTime, doTick, tickOptions);
  124. }
  125. tick(millis = 0, doTick, tickOptions) {
  126. let finalTime = this._currentTickTime + millis;
  127. let lastCurrentTime = 0;
  128. tickOptions = Object.assign({ processNewMacroTasksSynchronously: true }, tickOptions);
  129. // we need to copy the schedulerQueue so nested timeout
  130. // will not be wrongly called in the current tick
  131. // https://github.com/angular/angular/issues/33799
  132. const schedulerQueue = tickOptions.processNewMacroTasksSynchronously ?
  133. this._schedulerQueue :
  134. this._schedulerQueue.slice();
  135. if (schedulerQueue.length === 0 && doTick) {
  136. doTick(millis);
  137. return;
  138. }
  139. while (schedulerQueue.length > 0) {
  140. // clear requeueEntries before each loop
  141. this._currentTickRequeuePeriodicEntries = [];
  142. let current = schedulerQueue[0];
  143. if (finalTime < current.endTime) {
  144. // Done processing the queue since it's sorted by endTime.
  145. break;
  146. }
  147. else {
  148. // Time to run scheduled function. Remove it from the head of queue.
  149. let current = schedulerQueue.shift();
  150. if (!tickOptions.processNewMacroTasksSynchronously) {
  151. const idx = this._schedulerQueue.indexOf(current);
  152. if (idx >= 0) {
  153. this._schedulerQueue.splice(idx, 1);
  154. }
  155. }
  156. lastCurrentTime = this._currentTickTime;
  157. this._currentTickTime = current.endTime;
  158. if (doTick) {
  159. doTick(this._currentTickTime - lastCurrentTime);
  160. }
  161. let retval = current.func.apply(global, current.isRequestAnimationFrame ? [this._currentTickTime] : current.args);
  162. if (!retval) {
  163. // Uncaught exception in the current scheduled function. Stop processing the queue.
  164. break;
  165. }
  166. // check is there any requeue periodic entry is added in
  167. // current loop, if there is, we need to add to current loop
  168. if (!tickOptions.processNewMacroTasksSynchronously) {
  169. this._currentTickRequeuePeriodicEntries.forEach(newEntry => {
  170. let i = 0;
  171. for (; i < schedulerQueue.length; i++) {
  172. const currentEntry = schedulerQueue[i];
  173. if (newEntry.endTime < currentEntry.endTime) {
  174. break;
  175. }
  176. }
  177. schedulerQueue.splice(i, 0, newEntry);
  178. });
  179. }
  180. }
  181. }
  182. lastCurrentTime = this._currentTickTime;
  183. this._currentTickTime = finalTime;
  184. if (doTick) {
  185. doTick(this._currentTickTime - lastCurrentTime);
  186. }
  187. }
  188. flushOnlyPendingTimers(doTick) {
  189. if (this._schedulerQueue.length === 0) {
  190. return 0;
  191. }
  192. // Find the last task currently queued in the scheduler queue and tick
  193. // till that time.
  194. const startTime = this._currentTickTime;
  195. const lastTask = this._schedulerQueue[this._schedulerQueue.length - 1];
  196. this.tick(lastTask.endTime - startTime, doTick, { processNewMacroTasksSynchronously: false });
  197. return this._currentTickTime - startTime;
  198. }
  199. flush(limit = 20, flushPeriodic = false, doTick) {
  200. if (flushPeriodic) {
  201. return this.flushPeriodic(doTick);
  202. }
  203. else {
  204. return this.flushNonPeriodic(limit, doTick);
  205. }
  206. }
  207. flushPeriodic(doTick) {
  208. if (this._schedulerQueue.length === 0) {
  209. return 0;
  210. }
  211. // Find the last task currently queued in the scheduler queue and tick
  212. // till that time.
  213. const startTime = this._currentTickTime;
  214. const lastTask = this._schedulerQueue[this._schedulerQueue.length - 1];
  215. this.tick(lastTask.endTime - startTime, doTick);
  216. return this._currentTickTime - startTime;
  217. }
  218. flushNonPeriodic(limit, doTick) {
  219. const startTime = this._currentTickTime;
  220. let lastCurrentTime = 0;
  221. let count = 0;
  222. while (this._schedulerQueue.length > 0) {
  223. count++;
  224. if (count > limit) {
  225. throw new Error('flush failed after reaching the limit of ' + limit +
  226. ' tasks. Does your code use a polling timeout?');
  227. }
  228. // flush only non-periodic timers.
  229. // If the only remaining tasks are periodic(or requestAnimationFrame), finish flushing.
  230. if (this._schedulerQueue.filter(task => !task.isPeriodic && !task.isRequestAnimationFrame)
  231. .length === 0) {
  232. break;
  233. }
  234. const current = this._schedulerQueue.shift();
  235. lastCurrentTime = this._currentTickTime;
  236. this._currentTickTime = current.endTime;
  237. if (doTick) {
  238. // Update any secondary schedulers like Jasmine mock Date.
  239. doTick(this._currentTickTime - lastCurrentTime);
  240. }
  241. const retval = current.func.apply(global, current.args);
  242. if (!retval) {
  243. // Uncaught exception in the current scheduled function. Stop processing the queue.
  244. break;
  245. }
  246. }
  247. return this._currentTickTime - startTime;
  248. }
  249. }
  250. class FakeAsyncTestZoneSpec {
  251. static assertInZone() {
  252. if (Zone.current.get('FakeAsyncTestZoneSpec') == null) {
  253. throw new Error('The code should be running in the fakeAsync zone to call this function');
  254. }
  255. }
  256. constructor(namePrefix, trackPendingRequestAnimationFrame = false, macroTaskOptions) {
  257. this.trackPendingRequestAnimationFrame = trackPendingRequestAnimationFrame;
  258. this.macroTaskOptions = macroTaskOptions;
  259. this._scheduler = new Scheduler();
  260. this._microtasks = [];
  261. this._lastError = null;
  262. this._uncaughtPromiseErrors = Promise[Zone.__symbol__('uncaughtPromiseErrors')];
  263. this.pendingPeriodicTimers = [];
  264. this.pendingTimers = [];
  265. this.patchDateLocked = false;
  266. this.properties = { 'FakeAsyncTestZoneSpec': this };
  267. this.name = 'fakeAsyncTestZone for ' + namePrefix;
  268. // in case user can't access the construction of FakeAsyncTestSpec
  269. // user can also define macroTaskOptions by define a global variable.
  270. if (!this.macroTaskOptions) {
  271. this.macroTaskOptions = global[Zone.__symbol__('FakeAsyncTestMacroTask')];
  272. }
  273. }
  274. _fnAndFlush(fn, completers) {
  275. return (...args) => {
  276. fn.apply(global, args);
  277. if (this._lastError === null) { // Success
  278. if (completers.onSuccess != null) {
  279. completers.onSuccess.apply(global);
  280. }
  281. // Flush microtasks only on success.
  282. this.flushMicrotasks();
  283. }
  284. else { // Failure
  285. if (completers.onError != null) {
  286. completers.onError.apply(global);
  287. }
  288. }
  289. // Return true if there were no errors, false otherwise.
  290. return this._lastError === null;
  291. };
  292. }
  293. static _removeTimer(timers, id) {
  294. let index = timers.indexOf(id);
  295. if (index > -1) {
  296. timers.splice(index, 1);
  297. }
  298. }
  299. _dequeueTimer(id) {
  300. return () => {
  301. FakeAsyncTestZoneSpec._removeTimer(this.pendingTimers, id);
  302. };
  303. }
  304. _requeuePeriodicTimer(fn, interval, args, id) {
  305. return () => {
  306. // Requeue the timer callback if it's not been canceled.
  307. if (this.pendingPeriodicTimers.indexOf(id) !== -1) {
  308. this._scheduler.scheduleFunction(fn, interval, { args, isPeriodic: true, id, isRequeuePeriodic: true });
  309. }
  310. };
  311. }
  312. _dequeuePeriodicTimer(id) {
  313. return () => {
  314. FakeAsyncTestZoneSpec._removeTimer(this.pendingPeriodicTimers, id);
  315. };
  316. }
  317. _setTimeout(fn, delay, args, isTimer = true) {
  318. let removeTimerFn = this._dequeueTimer(Scheduler.nextId);
  319. // Queue the callback and dequeue the timer on success and error.
  320. let cb = this._fnAndFlush(fn, { onSuccess: removeTimerFn, onError: removeTimerFn });
  321. let id = this._scheduler.scheduleFunction(cb, delay, { args, isRequestAnimationFrame: !isTimer });
  322. if (isTimer) {
  323. this.pendingTimers.push(id);
  324. }
  325. return id;
  326. }
  327. _clearTimeout(id) {
  328. FakeAsyncTestZoneSpec._removeTimer(this.pendingTimers, id);
  329. this._scheduler.removeScheduledFunctionWithId(id);
  330. }
  331. _setInterval(fn, interval, args) {
  332. let id = Scheduler.nextId;
  333. let completers = { onSuccess: null, onError: this._dequeuePeriodicTimer(id) };
  334. let cb = this._fnAndFlush(fn, completers);
  335. // Use the callback created above to requeue on success.
  336. completers.onSuccess = this._requeuePeriodicTimer(cb, interval, args, id);
  337. // Queue the callback and dequeue the periodic timer only on error.
  338. this._scheduler.scheduleFunction(cb, interval, { args, isPeriodic: true });
  339. this.pendingPeriodicTimers.push(id);
  340. return id;
  341. }
  342. _clearInterval(id) {
  343. FakeAsyncTestZoneSpec._removeTimer(this.pendingPeriodicTimers, id);
  344. this._scheduler.removeScheduledFunctionWithId(id);
  345. }
  346. _resetLastErrorAndThrow() {
  347. let error = this._lastError || this._uncaughtPromiseErrors[0];
  348. this._uncaughtPromiseErrors.length = 0;
  349. this._lastError = null;
  350. throw error;
  351. }
  352. getCurrentTickTime() {
  353. return this._scheduler.getCurrentTickTime();
  354. }
  355. getFakeSystemTime() {
  356. return this._scheduler.getFakeSystemTime();
  357. }
  358. setFakeBaseSystemTime(realTime) {
  359. this._scheduler.setFakeBaseSystemTime(realTime);
  360. }
  361. getRealSystemTime() {
  362. return this._scheduler.getRealSystemTime();
  363. }
  364. static patchDate() {
  365. if (!!global[Zone.__symbol__('disableDatePatching')]) {
  366. // we don't want to patch global Date
  367. // because in some case, global Date
  368. // is already being patched, we need to provide
  369. // an option to let user still use their
  370. // own version of Date.
  371. return;
  372. }
  373. if (global['Date'] === FakeDate) {
  374. // already patched
  375. return;
  376. }
  377. global['Date'] = FakeDate;
  378. FakeDate.prototype = OriginalDate.prototype;
  379. // try check and reset timers
  380. // because jasmine.clock().install() may
  381. // have replaced the global timer
  382. FakeAsyncTestZoneSpec.checkTimerPatch();
  383. }
  384. static resetDate() {
  385. if (global['Date'] === FakeDate) {
  386. global['Date'] = OriginalDate;
  387. }
  388. }
  389. static checkTimerPatch() {
  390. if (global.setTimeout !== timers.setTimeout) {
  391. global.setTimeout = timers.setTimeout;
  392. global.clearTimeout = timers.clearTimeout;
  393. }
  394. if (global.setInterval !== timers.setInterval) {
  395. global.setInterval = timers.setInterval;
  396. global.clearInterval = timers.clearInterval;
  397. }
  398. }
  399. lockDatePatch() {
  400. this.patchDateLocked = true;
  401. FakeAsyncTestZoneSpec.patchDate();
  402. }
  403. unlockDatePatch() {
  404. this.patchDateLocked = false;
  405. FakeAsyncTestZoneSpec.resetDate();
  406. }
  407. tickToNext(steps = 1, doTick, tickOptions = { processNewMacroTasksSynchronously: true }) {
  408. if (steps <= 0) {
  409. return;
  410. }
  411. FakeAsyncTestZoneSpec.assertInZone();
  412. this.flushMicrotasks();
  413. this._scheduler.tickToNext(steps, doTick, tickOptions);
  414. if (this._lastError !== null) {
  415. this._resetLastErrorAndThrow();
  416. }
  417. }
  418. tick(millis = 0, doTick, tickOptions = { processNewMacroTasksSynchronously: true }) {
  419. FakeAsyncTestZoneSpec.assertInZone();
  420. this.flushMicrotasks();
  421. this._scheduler.tick(millis, doTick, tickOptions);
  422. if (this._lastError !== null) {
  423. this._resetLastErrorAndThrow();
  424. }
  425. }
  426. flushMicrotasks() {
  427. FakeAsyncTestZoneSpec.assertInZone();
  428. const flushErrors = () => {
  429. if (this._lastError !== null || this._uncaughtPromiseErrors.length) {
  430. // If there is an error stop processing the microtask queue and rethrow the error.
  431. this._resetLastErrorAndThrow();
  432. }
  433. };
  434. while (this._microtasks.length > 0) {
  435. let microtask = this._microtasks.shift();
  436. microtask.func.apply(microtask.target, microtask.args);
  437. }
  438. flushErrors();
  439. }
  440. flush(limit, flushPeriodic, doTick) {
  441. FakeAsyncTestZoneSpec.assertInZone();
  442. this.flushMicrotasks();
  443. const elapsed = this._scheduler.flush(limit, flushPeriodic, doTick);
  444. if (this._lastError !== null) {
  445. this._resetLastErrorAndThrow();
  446. }
  447. return elapsed;
  448. }
  449. flushOnlyPendingTimers(doTick) {
  450. FakeAsyncTestZoneSpec.assertInZone();
  451. this.flushMicrotasks();
  452. const elapsed = this._scheduler.flushOnlyPendingTimers(doTick);
  453. if (this._lastError !== null) {
  454. this._resetLastErrorAndThrow();
  455. }
  456. return elapsed;
  457. }
  458. removeAllTimers() {
  459. FakeAsyncTestZoneSpec.assertInZone();
  460. this._scheduler.removeAll();
  461. this.pendingPeriodicTimers = [];
  462. this.pendingTimers = [];
  463. }
  464. getTimerCount() {
  465. return this._scheduler.getTimerCount() + this._microtasks.length;
  466. }
  467. onScheduleTask(delegate, current, target, task) {
  468. switch (task.type) {
  469. case 'microTask':
  470. let args = task.data && task.data.args;
  471. // should pass additional arguments to callback if have any
  472. // currently we know process.nextTick will have such additional
  473. // arguments
  474. let additionalArgs;
  475. if (args) {
  476. let callbackIndex = task.data.cbIdx;
  477. if (typeof args.length === 'number' && args.length > callbackIndex + 1) {
  478. additionalArgs = Array.prototype.slice.call(args, callbackIndex + 1);
  479. }
  480. }
  481. this._microtasks.push({
  482. func: task.invoke,
  483. args: additionalArgs,
  484. target: task.data && task.data.target
  485. });
  486. break;
  487. case 'macroTask':
  488. switch (task.source) {
  489. case 'setTimeout':
  490. task.data['handleId'] = this._setTimeout(task.invoke, task.data['delay'], Array.prototype.slice.call(task.data['args'], 2));
  491. break;
  492. case 'setImmediate':
  493. task.data['handleId'] = this._setTimeout(task.invoke, 0, Array.prototype.slice.call(task.data['args'], 1));
  494. break;
  495. case 'setInterval':
  496. task.data['handleId'] = this._setInterval(task.invoke, task.data['delay'], Array.prototype.slice.call(task.data['args'], 2));
  497. break;
  498. case 'XMLHttpRequest.send':
  499. throw new Error('Cannot make XHRs from within a fake async test. Request URL: ' +
  500. task.data['url']);
  501. case 'requestAnimationFrame':
  502. case 'webkitRequestAnimationFrame':
  503. case 'mozRequestAnimationFrame':
  504. // Simulate a requestAnimationFrame by using a setTimeout with 16 ms.
  505. // (60 frames per second)
  506. task.data['handleId'] = this._setTimeout(task.invoke, 16, task.data['args'], this.trackPendingRequestAnimationFrame);
  507. break;
  508. default:
  509. // user can define which macroTask they want to support by passing
  510. // macroTaskOptions
  511. const macroTaskOption = this.findMacroTaskOption(task);
  512. if (macroTaskOption) {
  513. const args = task.data && task.data['args'];
  514. const delay = args && args.length > 1 ? args[1] : 0;
  515. let callbackArgs = macroTaskOption.callbackArgs ? macroTaskOption.callbackArgs : args;
  516. if (!!macroTaskOption.isPeriodic) {
  517. // periodic macroTask, use setInterval to simulate
  518. task.data['handleId'] = this._setInterval(task.invoke, delay, callbackArgs);
  519. task.data.isPeriodic = true;
  520. }
  521. else {
  522. // not periodic, use setTimeout to simulate
  523. task.data['handleId'] = this._setTimeout(task.invoke, delay, callbackArgs);
  524. }
  525. break;
  526. }
  527. throw new Error('Unknown macroTask scheduled in fake async test: ' + task.source);
  528. }
  529. break;
  530. case 'eventTask':
  531. task = delegate.scheduleTask(target, task);
  532. break;
  533. }
  534. return task;
  535. }
  536. onCancelTask(delegate, current, target, task) {
  537. switch (task.source) {
  538. case 'setTimeout':
  539. case 'requestAnimationFrame':
  540. case 'webkitRequestAnimationFrame':
  541. case 'mozRequestAnimationFrame':
  542. return this._clearTimeout(task.data['handleId']);
  543. case 'setInterval':
  544. return this._clearInterval(task.data['handleId']);
  545. default:
  546. // user can define which macroTask they want to support by passing
  547. // macroTaskOptions
  548. const macroTaskOption = this.findMacroTaskOption(task);
  549. if (macroTaskOption) {
  550. const handleId = task.data['handleId'];
  551. return macroTaskOption.isPeriodic ? this._clearInterval(handleId) :
  552. this._clearTimeout(handleId);
  553. }
  554. return delegate.cancelTask(target, task);
  555. }
  556. }
  557. onInvoke(delegate, current, target, callback, applyThis, applyArgs, source) {
  558. try {
  559. FakeAsyncTestZoneSpec.patchDate();
  560. return delegate.invoke(target, callback, applyThis, applyArgs, source);
  561. }
  562. finally {
  563. if (!this.patchDateLocked) {
  564. FakeAsyncTestZoneSpec.resetDate();
  565. }
  566. }
  567. }
  568. findMacroTaskOption(task) {
  569. if (!this.macroTaskOptions) {
  570. return null;
  571. }
  572. for (let i = 0; i < this.macroTaskOptions.length; i++) {
  573. const macroTaskOption = this.macroTaskOptions[i];
  574. if (macroTaskOption.source === task.source) {
  575. return macroTaskOption;
  576. }
  577. }
  578. return null;
  579. }
  580. onHandleError(parentZoneDelegate, currentZone, targetZone, error) {
  581. this._lastError = error;
  582. return false; // Don't propagate error to parent zone.
  583. }
  584. }
  585. // Export the class so that new instances can be created with proper
  586. // constructor params.
  587. Zone['FakeAsyncTestZoneSpec'] = FakeAsyncTestZoneSpec;
  588. })(typeof window === 'object' && window || typeof self === 'object' && self || global);
  589. Zone.__load_patch('fakeasync', (global, Zone, api) => {
  590. const FakeAsyncTestZoneSpec = Zone && Zone['FakeAsyncTestZoneSpec'];
  591. function getProxyZoneSpec() {
  592. return Zone && Zone['ProxyZoneSpec'];
  593. }
  594. let _fakeAsyncTestZoneSpec = null;
  595. /**
  596. * Clears out the shared fake async zone for a test.
  597. * To be called in a global `beforeEach`.
  598. *
  599. * @experimental
  600. */
  601. function resetFakeAsyncZone() {
  602. if (_fakeAsyncTestZoneSpec) {
  603. _fakeAsyncTestZoneSpec.unlockDatePatch();
  604. }
  605. _fakeAsyncTestZoneSpec = null;
  606. // in node.js testing we may not have ProxyZoneSpec in which case there is nothing to reset.
  607. getProxyZoneSpec() && getProxyZoneSpec().assertPresent().resetDelegate();
  608. }
  609. /**
  610. * Wraps a function to be executed in the fakeAsync zone:
  611. * - microtasks are manually executed by calling `flushMicrotasks()`,
  612. * - timers are synchronous, `tick()` simulates the asynchronous passage of time.
  613. *
  614. * If there are any pending timers at the end of the function, an exception will be thrown.
  615. *
  616. * Can be used to wrap inject() calls.
  617. *
  618. * ## Example
  619. *
  620. * {@example core/testing/ts/fake_async.ts region='basic'}
  621. *
  622. * @param fn
  623. * @returns The function wrapped to be executed in the fakeAsync zone
  624. *
  625. * @experimental
  626. */
  627. function fakeAsync(fn) {
  628. // Not using an arrow function to preserve context passed from call site
  629. const fakeAsyncFn = function (...args) {
  630. const ProxyZoneSpec = getProxyZoneSpec();
  631. if (!ProxyZoneSpec) {
  632. throw new Error('ProxyZoneSpec is needed for the async() test helper but could not be found. ' +
  633. 'Please make sure that your environment includes zone.js/plugins/proxy');
  634. }
  635. const proxyZoneSpec = ProxyZoneSpec.assertPresent();
  636. if (Zone.current.get('FakeAsyncTestZoneSpec')) {
  637. throw new Error('fakeAsync() calls can not be nested');
  638. }
  639. try {
  640. // in case jasmine.clock init a fakeAsyncTestZoneSpec
  641. if (!_fakeAsyncTestZoneSpec) {
  642. if (proxyZoneSpec.getDelegate() instanceof FakeAsyncTestZoneSpec) {
  643. throw new Error('fakeAsync() calls can not be nested');
  644. }
  645. _fakeAsyncTestZoneSpec = new FakeAsyncTestZoneSpec();
  646. }
  647. let res;
  648. const lastProxyZoneSpec = proxyZoneSpec.getDelegate();
  649. proxyZoneSpec.setDelegate(_fakeAsyncTestZoneSpec);
  650. _fakeAsyncTestZoneSpec.lockDatePatch();
  651. try {
  652. res = fn.apply(this, args);
  653. flushMicrotasks();
  654. }
  655. finally {
  656. proxyZoneSpec.setDelegate(lastProxyZoneSpec);
  657. }
  658. if (_fakeAsyncTestZoneSpec.pendingPeriodicTimers.length > 0) {
  659. throw new Error(`${_fakeAsyncTestZoneSpec.pendingPeriodicTimers.length} ` +
  660. `periodic timer(s) still in the queue.`);
  661. }
  662. if (_fakeAsyncTestZoneSpec.pendingTimers.length > 0) {
  663. throw new Error(`${_fakeAsyncTestZoneSpec.pendingTimers.length} timer(s) still in the queue.`);
  664. }
  665. return res;
  666. }
  667. finally {
  668. resetFakeAsyncZone();
  669. }
  670. };
  671. fakeAsyncFn.isFakeAsync = true;
  672. return fakeAsyncFn;
  673. }
  674. function _getFakeAsyncZoneSpec() {
  675. if (_fakeAsyncTestZoneSpec == null) {
  676. _fakeAsyncTestZoneSpec = Zone.current.get('FakeAsyncTestZoneSpec');
  677. if (_fakeAsyncTestZoneSpec == null) {
  678. throw new Error('The code should be running in the fakeAsync zone to call this function');
  679. }
  680. }
  681. return _fakeAsyncTestZoneSpec;
  682. }
  683. /**
  684. * Simulates the asynchronous passage of time for the timers in the fakeAsync zone.
  685. *
  686. * The microtasks queue is drained at the very start of this function and after any timer callback
  687. * has been executed.
  688. *
  689. * ## Example
  690. *
  691. * {@example core/testing/ts/fake_async.ts region='basic'}
  692. *
  693. * @experimental
  694. */
  695. function tick(millis = 0, ignoreNestedTimeout = false) {
  696. _getFakeAsyncZoneSpec().tick(millis, null, ignoreNestedTimeout);
  697. }
  698. /**
  699. * Simulates the asynchronous passage of time for the timers in the fakeAsync zone by
  700. * draining the macrotask queue until it is empty. The returned value is the milliseconds
  701. * of time that would have been elapsed.
  702. *
  703. * @param maxTurns
  704. * @returns The simulated time elapsed, in millis.
  705. *
  706. * @experimental
  707. */
  708. function flush(maxTurns) {
  709. return _getFakeAsyncZoneSpec().flush(maxTurns);
  710. }
  711. /**
  712. * Discard all remaining periodic tasks.
  713. *
  714. * @experimental
  715. */
  716. function discardPeriodicTasks() {
  717. const zoneSpec = _getFakeAsyncZoneSpec();
  718. zoneSpec.pendingPeriodicTimers;
  719. zoneSpec.pendingPeriodicTimers.length = 0;
  720. }
  721. /**
  722. * Flush any pending microtasks.
  723. *
  724. * @experimental
  725. */
  726. function flushMicrotasks() {
  727. _getFakeAsyncZoneSpec().flushMicrotasks();
  728. }
  729. Zone[api.symbol('fakeAsyncTest')] =
  730. { resetFakeAsyncZone, flushMicrotasks, discardPeriodicTasks, tick, flush, fakeAsync };
  731. }, true);