fake-async.ts 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. /**
  2. * @license
  3. * Copyright Google Inc. All Rights Reserved.
  4. *
  5. * Use of this source code is governed by an MIT-style license that can be
  6. * found in the LICENSE file at https://angular.io/license
  7. */
  8. import '../zone-spec/fake-async-test';
  9. Zone.__load_patch('fakeasync', (global: any, Zone: ZoneType, api: _ZonePrivate) => {
  10. const FakeAsyncTestZoneSpec = Zone && (Zone as any)['FakeAsyncTestZoneSpec'];
  11. type ProxyZoneSpec = {
  12. setDelegate(delegateSpec: ZoneSpec): void; getDelegate(): ZoneSpec; resetDelegate(): void;
  13. };
  14. const ProxyZoneSpec: {get(): ProxyZoneSpec; assertPresent: () => ProxyZoneSpec} =
  15. Zone && (Zone as any)['ProxyZoneSpec'];
  16. let _fakeAsyncTestZoneSpec: any = null;
  17. /**
  18. * Clears out the shared fake async zone for a test.
  19. * To be called in a global `beforeEach`.
  20. *
  21. * @experimental
  22. */
  23. function resetFakeAsyncZone() {
  24. if (_fakeAsyncTestZoneSpec) {
  25. _fakeAsyncTestZoneSpec.unlockDatePatch();
  26. }
  27. _fakeAsyncTestZoneSpec = null;
  28. // in node.js testing we may not have ProxyZoneSpec in which case there is nothing to reset.
  29. ProxyZoneSpec && ProxyZoneSpec.assertPresent().resetDelegate();
  30. }
  31. /**
  32. * Wraps a function to be executed in the fakeAsync zone:
  33. * - microtasks are manually executed by calling `flushMicrotasks()`,
  34. * - timers are synchronous, `tick()` simulates the asynchronous passage of time.
  35. *
  36. * If there are any pending timers at the end of the function, an exception will be thrown.
  37. *
  38. * Can be used to wrap inject() calls.
  39. *
  40. * ## Example
  41. *
  42. * {@example core/testing/ts/fake_async.ts region='basic'}
  43. *
  44. * @param fn
  45. * @returns The function wrapped to be executed in the fakeAsync zone
  46. *
  47. * @experimental
  48. */
  49. function fakeAsync(fn: Function): (...args: any[]) => any {
  50. // Not using an arrow function to preserve context passed from call site
  51. return function(...args: any[]) {
  52. const proxyZoneSpec = ProxyZoneSpec.assertPresent();
  53. if (Zone.current.get('FakeAsyncTestZoneSpec')) {
  54. throw new Error('fakeAsync() calls can not be nested');
  55. }
  56. try {
  57. // in case jasmine.clock init a fakeAsyncTestZoneSpec
  58. if (!_fakeAsyncTestZoneSpec) {
  59. if (proxyZoneSpec.getDelegate() instanceof FakeAsyncTestZoneSpec) {
  60. throw new Error('fakeAsync() calls can not be nested');
  61. }
  62. _fakeAsyncTestZoneSpec = new FakeAsyncTestZoneSpec();
  63. }
  64. let res: any;
  65. const lastProxyZoneSpec = proxyZoneSpec.getDelegate();
  66. proxyZoneSpec.setDelegate(_fakeAsyncTestZoneSpec);
  67. _fakeAsyncTestZoneSpec.lockDatePatch();
  68. try {
  69. res = fn.apply(this, args);
  70. flushMicrotasks();
  71. } finally {
  72. proxyZoneSpec.setDelegate(lastProxyZoneSpec);
  73. }
  74. if (_fakeAsyncTestZoneSpec.pendingPeriodicTimers.length > 0) {
  75. throw new Error(
  76. `${_fakeAsyncTestZoneSpec.pendingPeriodicTimers.length} ` +
  77. `periodic timer(s) still in the queue.`);
  78. }
  79. if (_fakeAsyncTestZoneSpec.pendingTimers.length > 0) {
  80. throw new Error(
  81. `${_fakeAsyncTestZoneSpec.pendingTimers.length} timer(s) still in the queue.`);
  82. }
  83. return res;
  84. } finally {
  85. resetFakeAsyncZone();
  86. }
  87. };
  88. }
  89. function _getFakeAsyncZoneSpec(): any {
  90. if (_fakeAsyncTestZoneSpec == null) {
  91. _fakeAsyncTestZoneSpec = Zone.current.get('FakeAsyncTestZoneSpec');
  92. if (_fakeAsyncTestZoneSpec == null) {
  93. throw new Error('The code should be running in the fakeAsync zone to call this function');
  94. }
  95. }
  96. return _fakeAsyncTestZoneSpec;
  97. }
  98. /**
  99. * Simulates the asynchronous passage of time for the timers in the fakeAsync zone.
  100. *
  101. * The microtasks queue is drained at the very start of this function and after any timer callback
  102. * has been executed.
  103. *
  104. * ## Example
  105. *
  106. * {@example core/testing/ts/fake_async.ts region='basic'}
  107. *
  108. * @experimental
  109. */
  110. function tick(millis: number = 0): void {
  111. _getFakeAsyncZoneSpec().tick(millis);
  112. }
  113. /**
  114. * Simulates the asynchronous passage of time for the timers in the fakeAsync zone by
  115. * draining the macrotask queue until it is empty. The returned value is the milliseconds
  116. * of time that would have been elapsed.
  117. *
  118. * @param maxTurns
  119. * @returns The simulated time elapsed, in millis.
  120. *
  121. * @experimental
  122. */
  123. function flush(maxTurns?: number): number {
  124. return _getFakeAsyncZoneSpec().flush(maxTurns);
  125. }
  126. /**
  127. * Discard all remaining periodic tasks.
  128. *
  129. * @experimental
  130. */
  131. function discardPeriodicTasks(): void {
  132. const zoneSpec = _getFakeAsyncZoneSpec();
  133. const pendingTimers = zoneSpec.pendingPeriodicTimers;
  134. zoneSpec.pendingPeriodicTimers.length = 0;
  135. }
  136. /**
  137. * Flush any pending microtasks.
  138. *
  139. * @experimental
  140. */
  141. function flushMicrotasks(): void {
  142. _getFakeAsyncZoneSpec().flushMicrotasks();
  143. }
  144. (Zone as any)[api.symbol('fakeAsyncTest')] =
  145. {resetFakeAsyncZone, flushMicrotasks, discardPeriodicTasks, tick, flush, fakeAsync};
  146. });