async-test.ts 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  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. const _global: any =
  9. typeof window !== 'undefined' && window || typeof self !== 'undefined' && self || global;
  10. class AsyncTestZoneSpec implements ZoneSpec {
  11. static symbolParentUnresolved = Zone.__symbol__('parentUnresolved');
  12. _pendingMicroTasks: boolean = false;
  13. _pendingMacroTasks: boolean = false;
  14. _alreadyErrored: boolean = false;
  15. _isSync: boolean = false;
  16. runZone = Zone.current;
  17. unresolvedChainedPromiseCount = 0;
  18. supportWaitUnresolvedChainedPromise = false;
  19. constructor(
  20. private finishCallback: Function, private failCallback: Function, namePrefix: string) {
  21. this.name = 'asyncTestZone for ' + namePrefix;
  22. this.properties = {'AsyncTestZoneSpec': this};
  23. this.supportWaitUnresolvedChainedPromise =
  24. _global[Zone.__symbol__('supportWaitUnResolvedChainedPromise')] === true;
  25. }
  26. isUnresolvedChainedPromisePending() {
  27. return this.unresolvedChainedPromiseCount > 0;
  28. }
  29. _finishCallbackIfDone() {
  30. if (!(this._pendingMicroTasks || this._pendingMacroTasks ||
  31. (this.supportWaitUnresolvedChainedPromise && this.isUnresolvedChainedPromisePending()))) {
  32. // We do this because we would like to catch unhandled rejected promises.
  33. this.runZone.run(() => {
  34. setTimeout(() => {
  35. if (!this._alreadyErrored && !(this._pendingMicroTasks || this._pendingMacroTasks)) {
  36. this.finishCallback();
  37. }
  38. }, 0);
  39. });
  40. }
  41. }
  42. patchPromiseForTest() {
  43. if (!this.supportWaitUnresolvedChainedPromise) {
  44. return;
  45. }
  46. const patchPromiseForTest = (Promise as any)[Zone.__symbol__('patchPromiseForTest')];
  47. if (patchPromiseForTest) {
  48. patchPromiseForTest();
  49. }
  50. }
  51. unPatchPromiseForTest() {
  52. if (!this.supportWaitUnresolvedChainedPromise) {
  53. return;
  54. }
  55. const unPatchPromiseForTest = (Promise as any)[Zone.__symbol__('unPatchPromiseForTest')];
  56. if (unPatchPromiseForTest) {
  57. unPatchPromiseForTest();
  58. }
  59. }
  60. // ZoneSpec implementation below.
  61. name: string;
  62. properties: {[key: string]: any};
  63. onScheduleTask(delegate: ZoneDelegate, current: Zone, target: Zone, task: Task): Task {
  64. if (task.type !== 'eventTask') {
  65. this._isSync = false;
  66. }
  67. if (task.type === 'microTask' && task.data && task.data instanceof Promise) {
  68. // check whether the promise is a chained promise
  69. if ((task.data as any)[AsyncTestZoneSpec.symbolParentUnresolved] === true) {
  70. // chained promise is being scheduled
  71. this.unresolvedChainedPromiseCount--;
  72. }
  73. }
  74. return delegate.scheduleTask(target, task);
  75. }
  76. onInvokeTask(
  77. delegate: ZoneDelegate, current: Zone, target: Zone, task: Task, applyThis: any,
  78. applyArgs: any) {
  79. if (task.type !== 'eventTask') {
  80. this._isSync = false;
  81. }
  82. return delegate.invokeTask(target, task, applyThis, applyArgs);
  83. }
  84. onCancelTask(delegate: ZoneDelegate, current: Zone, target: Zone, task: Task) {
  85. if (task.type !== 'eventTask') {
  86. this._isSync = false;
  87. }
  88. return delegate.cancelTask(target, task);
  89. }
  90. // Note - we need to use onInvoke at the moment to call finish when a test is
  91. // fully synchronous. TODO(juliemr): remove this when the logic for
  92. // onHasTask changes and it calls whenever the task queues are dirty.
  93. // updated by(JiaLiPassion), only call finish callback when no task
  94. // was scheduled/invoked/canceled.
  95. onInvoke(
  96. parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, delegate: Function,
  97. applyThis: any, applyArgs?: any[], source?: string): any {
  98. let previousTaskCounts: any = null;
  99. try {
  100. this._isSync = true;
  101. return parentZoneDelegate.invoke(targetZone, delegate, applyThis, applyArgs, source);
  102. } finally {
  103. const afterTaskCounts: any = (parentZoneDelegate as any)._taskCounts;
  104. if (this._isSync) {
  105. this._finishCallbackIfDone();
  106. }
  107. }
  108. }
  109. onHandleError(parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, error: any):
  110. boolean {
  111. // Let the parent try to handle the error.
  112. const result = parentZoneDelegate.handleError(targetZone, error);
  113. if (result) {
  114. this.failCallback(error);
  115. this._alreadyErrored = true;
  116. }
  117. return false;
  118. }
  119. onHasTask(delegate: ZoneDelegate, current: Zone, target: Zone, hasTaskState: HasTaskState) {
  120. delegate.hasTask(target, hasTaskState);
  121. if (hasTaskState.change == 'microTask') {
  122. this._pendingMicroTasks = hasTaskState.microTask;
  123. this._finishCallbackIfDone();
  124. } else if (hasTaskState.change == 'macroTask') {
  125. this._pendingMacroTasks = hasTaskState.macroTask;
  126. this._finishCallbackIfDone();
  127. }
  128. }
  129. }
  130. // Export the class so that new instances can be created with proper
  131. // constructor params.
  132. (Zone as any)['AsyncTestZoneSpec'] = AsyncTestZoneSpec;