| /** |
| * @license |
| * Copyright Google Inc. All Rights Reserved. |
| * |
| * Use of this source code is governed by an MIT-style license that can be |
| * found in the LICENSE file at https://angular.io/license |
| */ |
| const _global: any = |
| typeof window !== 'undefined' && window || typeof self !== 'undefined' && self || global; |
| class AsyncTestZoneSpec implements ZoneSpec { |
| static symbolParentUnresolved = Zone.__symbol__('parentUnresolved'); |
| |
| _pendingMicroTasks: boolean = false; |
| _pendingMacroTasks: boolean = false; |
| _alreadyErrored: boolean = false; |
| _isSync: boolean = false; |
| runZone = Zone.current; |
| unresolvedChainedPromiseCount = 0; |
| |
| supportWaitUnresolvedChainedPromise = false; |
| |
| constructor( |
| private finishCallback: Function, private failCallback: Function, namePrefix: string) { |
| this.name = 'asyncTestZone for ' + namePrefix; |
| this.properties = {'AsyncTestZoneSpec': this}; |
| this.supportWaitUnresolvedChainedPromise = |
| _global[Zone.__symbol__('supportWaitUnResolvedChainedPromise')] === true; |
| } |
| |
| isUnresolvedChainedPromisePending() { |
| return this.unresolvedChainedPromiseCount > 0; |
| } |
| |
| _finishCallbackIfDone() { |
| if (!(this._pendingMicroTasks || this._pendingMacroTasks || |
| (this.supportWaitUnresolvedChainedPromise && this.isUnresolvedChainedPromisePending()))) { |
| // We do this because we would like to catch unhandled rejected promises. |
| this.runZone.run(() => { |
| setTimeout(() => { |
| if (!this._alreadyErrored && !(this._pendingMicroTasks || this._pendingMacroTasks)) { |
| this.finishCallback(); |
| } |
| }, 0); |
| }); |
| } |
| } |
| |
| patchPromiseForTest() { |
| if (!this.supportWaitUnresolvedChainedPromise) { |
| return; |
| } |
| const patchPromiseForTest = (Promise as any)[Zone.__symbol__('patchPromiseForTest')]; |
| if (patchPromiseForTest) { |
| patchPromiseForTest(); |
| } |
| } |
| |
| unPatchPromiseForTest() { |
| if (!this.supportWaitUnresolvedChainedPromise) { |
| return; |
| } |
| const unPatchPromiseForTest = (Promise as any)[Zone.__symbol__('unPatchPromiseForTest')]; |
| if (unPatchPromiseForTest) { |
| unPatchPromiseForTest(); |
| } |
| } |
| |
| // ZoneSpec implementation below. |
| |
| name: string; |
| |
| properties: {[key: string]: any}; |
| |
| onScheduleTask(delegate: ZoneDelegate, current: Zone, target: Zone, task: Task): Task { |
| if (task.type !== 'eventTask') { |
| this._isSync = false; |
| } |
| if (task.type === 'microTask' && task.data && task.data instanceof Promise) { |
| // check whether the promise is a chained promise |
| if ((task.data as any)[AsyncTestZoneSpec.symbolParentUnresolved] === true) { |
| // chained promise is being scheduled |
| this.unresolvedChainedPromiseCount--; |
| } |
| } |
| return delegate.scheduleTask(target, task); |
| } |
| |
| onInvokeTask( |
| delegate: ZoneDelegate, current: Zone, target: Zone, task: Task, applyThis: any, |
| applyArgs: any) { |
| if (task.type !== 'eventTask') { |
| this._isSync = false; |
| } |
| return delegate.invokeTask(target, task, applyThis, applyArgs); |
| } |
| |
| onCancelTask(delegate: ZoneDelegate, current: Zone, target: Zone, task: Task) { |
| if (task.type !== 'eventTask') { |
| this._isSync = false; |
| } |
| return delegate.cancelTask(target, task); |
| } |
| |
| // Note - we need to use onInvoke at the moment to call finish when a test is |
| // fully synchronous. TODO(juliemr): remove this when the logic for |
| // onHasTask changes and it calls whenever the task queues are dirty. |
| // updated by(JiaLiPassion), only call finish callback when no task |
| // was scheduled/invoked/canceled. |
| onInvoke( |
| parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, delegate: Function, |
| applyThis: any, applyArgs?: any[], source?: string): any { |
| let previousTaskCounts: any = null; |
| try { |
| this._isSync = true; |
| return parentZoneDelegate.invoke(targetZone, delegate, applyThis, applyArgs, source); |
| } finally { |
| const afterTaskCounts: any = (parentZoneDelegate as any)._taskCounts; |
| if (this._isSync) { |
| this._finishCallbackIfDone(); |
| } |
| } |
| } |
| |
| onHandleError(parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, error: any): |
| boolean { |
| // Let the parent try to handle the error. |
| const result = parentZoneDelegate.handleError(targetZone, error); |
| if (result) { |
| this.failCallback(error); |
| this._alreadyErrored = true; |
| } |
| return false; |
| } |
| |
| onHasTask(delegate: ZoneDelegate, current: Zone, target: Zone, hasTaskState: HasTaskState) { |
| delegate.hasTask(target, hasTaskState); |
| if (hasTaskState.change == 'microTask') { |
| this._pendingMicroTasks = hasTaskState.microTask; |
| this._finishCallbackIfDone(); |
| } else if (hasTaskState.change == 'macroTask') { |
| this._pendingMacroTasks = hasTaskState.macroTask; |
| this._finishCallbackIfDone(); |
| } |
| } |
| } |
| |
| // Export the class so that new instances can be created with proper |
| // constructor params. |
| (Zone as any)['AsyncTestZoneSpec'] = AsyncTestZoneSpec; |