feat(testability): hook zone into whenstable api with async support
closes(#428)
This commit is contained in:
@ -3,6 +3,8 @@ import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
import {Map, MapWrapper, List, ListWrapper} from 'angular2/src/facade/collection';
|
||||
import {StringWrapper, isBlank, BaseException} from 'angular2/src/facade/lang';
|
||||
import * as getTestabilityModule from './get_testability';
|
||||
import {NgZone} from '../zone/ng_zone';
|
||||
import {PromiseWrapper} from 'angular2/src/facade/async';
|
||||
|
||||
|
||||
/**
|
||||
@ -12,40 +14,57 @@ import * as getTestabilityModule from './get_testability';
|
||||
*/
|
||||
@Injectable()
|
||||
export class Testability {
|
||||
_pendingCount: number;
|
||||
_callbacks: List<Function>;
|
||||
_pendingCount: number = 0;
|
||||
_callbacks: List<Function> = [];
|
||||
_isAngularEventPending: boolean = false;
|
||||
|
||||
constructor() {
|
||||
this._pendingCount = 0;
|
||||
this._callbacks = [];
|
||||
constructor(public _ngZone: NgZone) { this._watchAngularEvents(_ngZone); }
|
||||
|
||||
_watchAngularEvents(_ngZone: NgZone): void {
|
||||
_ngZone.overrideOnTurnStart(() => { this._isAngularEventPending = true; });
|
||||
_ngZone.overrideOnEventDone(() => {
|
||||
this._isAngularEventPending = false;
|
||||
this._runCallbacksIfReady();
|
||||
}, true);
|
||||
}
|
||||
|
||||
increaseCount(delta: number = 1): number {
|
||||
this._pendingCount += delta;
|
||||
if (this._pendingCount < 0) {
|
||||
throw new BaseException('pending async requests below zero');
|
||||
} else if (this._pendingCount == 0) {
|
||||
this._runCallbacks();
|
||||
}
|
||||
increasePendingRequestCount(): number {
|
||||
this._pendingCount += 1;
|
||||
return this._pendingCount;
|
||||
}
|
||||
|
||||
_runCallbacks() {
|
||||
while (this._callbacks.length !== 0) {
|
||||
ListWrapper.removeLast(this._callbacks)();
|
||||
decreasePendingRequestCount(): number {
|
||||
this._pendingCount -= 1;
|
||||
if (this._pendingCount < 0) {
|
||||
throw new BaseException('pending async requests below zero');
|
||||
}
|
||||
this._runCallbacksIfReady();
|
||||
return this._pendingCount;
|
||||
}
|
||||
|
||||
whenStable(callback: Function) {
|
||||
_runCallbacksIfReady(): void {
|
||||
if (this._pendingCount != 0 || this._isAngularEventPending) {
|
||||
return; // Not ready
|
||||
}
|
||||
|
||||
// Schedules the call backs in a new frame so that it is always async.
|
||||
PromiseWrapper.resolve(null).then((_) => {
|
||||
while (this._callbacks.length !== 0) {
|
||||
(this._callbacks.pop())();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
whenStable(callback: Function): void {
|
||||
this._callbacks.push(callback);
|
||||
|
||||
if (this._pendingCount === 0) {
|
||||
this._runCallbacks();
|
||||
}
|
||||
// TODO(juliemr) - hook into the zone api.
|
||||
this._runCallbacksIfReady();
|
||||
}
|
||||
|
||||
getPendingCount(): number { return this._pendingCount; }
|
||||
getPendingRequestCount(): number { return this._pendingCount; }
|
||||
|
||||
// This only accounts for ngZone, and not pending counts. Use `whenStable` to
|
||||
// check for stability.
|
||||
isAngularEventPending(): boolean { return this._isAngularEventPending; }
|
||||
|
||||
findBindings(using: any, binding: string, exactMatch: boolean): List<any> {
|
||||
// TODO(juliemr): implement.
|
||||
@ -55,13 +74,9 @@ export class Testability {
|
||||
|
||||
@Injectable()
|
||||
export class TestabilityRegistry {
|
||||
_applications: Map<any, Testability>;
|
||||
_applications: Map<any, Testability> = new Map();
|
||||
|
||||
constructor() {
|
||||
this._applications = new Map();
|
||||
|
||||
getTestabilityModule.GetTestability.addToWindow(this);
|
||||
}
|
||||
constructor() { getTestabilityModule.GetTestability.addToWindow(this); }
|
||||
|
||||
registerApplication(token: any, testability: Testability) {
|
||||
this._applications.set(token, testability);
|
||||
|
@ -6,6 +6,36 @@ import 'package:stack_trace/stack_trace.dart' show Chain;
|
||||
typedef void ZeroArgFunction();
|
||||
typedef void ErrorHandlingFn(error, stackTrace);
|
||||
|
||||
/**
|
||||
* A `Timer` wrapper that lets you specify additional functions to call when it
|
||||
* is cancelled.
|
||||
*/
|
||||
class WrappedTimer implements Timer {
|
||||
|
||||
Timer _timer;
|
||||
ZeroArgFunction _onCancelCb;
|
||||
|
||||
WrappedTimer(Timer timer) {
|
||||
_timer = timer;
|
||||
}
|
||||
|
||||
void addOnCancelCb(ZeroArgFunction onCancelCb) {
|
||||
if (this._onCancelCb != null) {
|
||||
throw "On cancel cb already registered";
|
||||
}
|
||||
this._onCancelCb = onCancelCb;
|
||||
}
|
||||
|
||||
void cancel() {
|
||||
if (this._onCancelCb != null) {
|
||||
this._onCancelCb();
|
||||
}
|
||||
_timer.cancel();
|
||||
}
|
||||
|
||||
bool get isActive => _timer.isActive;
|
||||
}
|
||||
|
||||
/**
|
||||
* A `Zone` wrapper that lets you schedule tasks after its private microtask queue is exhausted but
|
||||
* before the next "VM turn", i.e. event loop iteration.
|
||||
@ -45,6 +75,8 @@ class NgZone {
|
||||
|
||||
bool _inVmTurnDone = false;
|
||||
|
||||
List<Timer> _pendingTimers = [];
|
||||
|
||||
/**
|
||||
* Associates with this
|
||||
*
|
||||
@ -92,8 +124,16 @@ class NgZone {
|
||||
*
|
||||
* This hook is useful for validating application state (e.g. in a test).
|
||||
*/
|
||||
void overrideOnEventDone(ZeroArgFunction onEventDoneFn) {
|
||||
void overrideOnEventDone(ZeroArgFunction onEventDoneFn, [bool waitForAsync = false]) {
|
||||
_onEventDone = onEventDoneFn;
|
||||
|
||||
if (waitForAsync) {
|
||||
_onEventDone = () {
|
||||
if (_pendingTimers.length == 0) {
|
||||
onEventDoneFn();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -224,6 +264,20 @@ class NgZone {
|
||||
}
|
||||
}
|
||||
|
||||
Timer _createTimer(Zone self, ZoneDelegate parent, Zone zone, Duration duration, fn()) {
|
||||
WrappedTimer wrappedTimer;
|
||||
var cb = () {
|
||||
fn();
|
||||
_pendingTimers.remove(wrappedTimer);
|
||||
};
|
||||
Timer timer = parent.createTimer(zone, duration, cb);
|
||||
wrappedTimer = new WrappedTimer(timer);
|
||||
wrappedTimer.addOnCancelCb(() => _pendingTimers.remove(wrappedTimer));
|
||||
|
||||
_pendingTimers.add(wrappedTimer);
|
||||
return wrappedTimer;
|
||||
}
|
||||
|
||||
Zone _createInnerZone(Zone zone, {handleUncaughtError}) {
|
||||
return zone.fork(
|
||||
specification: new ZoneSpecification(
|
||||
@ -231,7 +285,8 @@ class NgZone {
|
||||
run: _run,
|
||||
runUnary: _runUnary,
|
||||
runBinary: _runBinary,
|
||||
handleUncaughtError: handleUncaughtError),
|
||||
handleUncaughtError: handleUncaughtError,
|
||||
createTimer: _createTimer),
|
||||
zoneValues: {'_innerZone': true});
|
||||
}
|
||||
}
|
||||
|
@ -40,6 +40,8 @@ export class NgZone {
|
||||
|
||||
_inVmTurnDone: boolean = false;
|
||||
|
||||
_pendingTimeouts: List<number> = [];
|
||||
|
||||
/**
|
||||
* Associates with this
|
||||
*
|
||||
@ -93,8 +95,17 @@ export class NgZone {
|
||||
*
|
||||
* This hook is useful for validating application state (e.g. in a test).
|
||||
*/
|
||||
overrideOnEventDone(onEventDoneFn: Function): void {
|
||||
this._onEventDone = normalizeBlank(onEventDoneFn);
|
||||
overrideOnEventDone(onEventDoneFn: Function, opt_waitForAsync: boolean): void {
|
||||
var normalizedOnEventDone = normalizeBlank(onEventDoneFn);
|
||||
if (opt_waitForAsync) {
|
||||
this._onEventDone = () => {
|
||||
if (!this._pendingTimeouts.length) {
|
||||
normalizedOnEventDone();
|
||||
}
|
||||
};
|
||||
} else {
|
||||
this._onEventDone = normalizedOnEventDone;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -215,6 +226,24 @@ export class NgZone {
|
||||
parentScheduleMicrotask.call(this, microtask);
|
||||
};
|
||||
},
|
||||
'$setTimeout': function(parentSetTimeout) {
|
||||
return function(fn: Function, delay: number, ...args) {
|
||||
var id;
|
||||
var cb = function() {
|
||||
fn();
|
||||
ListWrapper.remove(ngZone._pendingTimeouts, id);
|
||||
};
|
||||
id = parentSetTimeout(cb, delay, args);
|
||||
ngZone._pendingTimeouts.push(id);
|
||||
return id;
|
||||
};
|
||||
},
|
||||
'$clearTimeout': function(parentClearTimeout) {
|
||||
return function(id: number) {
|
||||
parentClearTimeout(id);
|
||||
ListWrapper.remove(ngZone._pendingTimeouts, id);
|
||||
};
|
||||
},
|
||||
_innerZone: true
|
||||
});
|
||||
}
|
||||
|
Reference in New Issue
Block a user