fake-async-test.js 32 KB

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