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 {Map, MapWrapper, List, ListWrapper} from 'angular2/src/facade/collection';
|
||||||
import {StringWrapper, isBlank, BaseException} from 'angular2/src/facade/lang';
|
import {StringWrapper, isBlank, BaseException} from 'angular2/src/facade/lang';
|
||||||
import * as getTestabilityModule from './get_testability';
|
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()
|
@Injectable()
|
||||||
export class Testability {
|
export class Testability {
|
||||||
_pendingCount: number;
|
_pendingCount: number = 0;
|
||||||
_callbacks: List<Function>;
|
_callbacks: List<Function> = [];
|
||||||
|
_isAngularEventPending: boolean = false;
|
||||||
|
|
||||||
constructor() {
|
constructor(public _ngZone: NgZone) { this._watchAngularEvents(_ngZone); }
|
||||||
this._pendingCount = 0;
|
|
||||||
this._callbacks = [];
|
_watchAngularEvents(_ngZone: NgZone): void {
|
||||||
|
_ngZone.overrideOnTurnStart(() => { this._isAngularEventPending = true; });
|
||||||
|
_ngZone.overrideOnEventDone(() => {
|
||||||
|
this._isAngularEventPending = false;
|
||||||
|
this._runCallbacksIfReady();
|
||||||
|
}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
increaseCount(delta: number = 1): number {
|
increasePendingRequestCount(): number {
|
||||||
this._pendingCount += delta;
|
this._pendingCount += 1;
|
||||||
if (this._pendingCount < 0) {
|
|
||||||
throw new BaseException('pending async requests below zero');
|
|
||||||
} else if (this._pendingCount == 0) {
|
|
||||||
this._runCallbacks();
|
|
||||||
}
|
|
||||||
return this._pendingCount;
|
return this._pendingCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
_runCallbacks() {
|
decreasePendingRequestCount(): number {
|
||||||
while (this._callbacks.length !== 0) {
|
this._pendingCount -= 1;
|
||||||
ListWrapper.removeLast(this._callbacks)();
|
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);
|
this._callbacks.push(callback);
|
||||||
|
this._runCallbacksIfReady();
|
||||||
if (this._pendingCount === 0) {
|
|
||||||
this._runCallbacks();
|
|
||||||
}
|
|
||||||
// TODO(juliemr) - hook into the zone api.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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> {
|
findBindings(using: any, binding: string, exactMatch: boolean): List<any> {
|
||||||
// TODO(juliemr): implement.
|
// TODO(juliemr): implement.
|
||||||
@ -55,13 +74,9 @@ export class Testability {
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class TestabilityRegistry {
|
export class TestabilityRegistry {
|
||||||
_applications: Map<any, Testability>;
|
_applications: Map<any, Testability> = new Map();
|
||||||
|
|
||||||
constructor() {
|
constructor() { getTestabilityModule.GetTestability.addToWindow(this); }
|
||||||
this._applications = new Map();
|
|
||||||
|
|
||||||
getTestabilityModule.GetTestability.addToWindow(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
registerApplication(token: any, testability: Testability) {
|
registerApplication(token: any, testability: Testability) {
|
||||||
this._applications.set(token, testability);
|
this._applications.set(token, testability);
|
||||||
|
@ -6,6 +6,36 @@ import 'package:stack_trace/stack_trace.dart' show Chain;
|
|||||||
typedef void ZeroArgFunction();
|
typedef void ZeroArgFunction();
|
||||||
typedef void ErrorHandlingFn(error, stackTrace);
|
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
|
* 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.
|
* before the next "VM turn", i.e. event loop iteration.
|
||||||
@ -45,6 +75,8 @@ class NgZone {
|
|||||||
|
|
||||||
bool _inVmTurnDone = false;
|
bool _inVmTurnDone = false;
|
||||||
|
|
||||||
|
List<Timer> _pendingTimers = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Associates with this
|
* Associates with this
|
||||||
*
|
*
|
||||||
@ -92,8 +124,16 @@ class NgZone {
|
|||||||
*
|
*
|
||||||
* This hook is useful for validating application state (e.g. in a test).
|
* 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;
|
_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}) {
|
Zone _createInnerZone(Zone zone, {handleUncaughtError}) {
|
||||||
return zone.fork(
|
return zone.fork(
|
||||||
specification: new ZoneSpecification(
|
specification: new ZoneSpecification(
|
||||||
@ -231,7 +285,8 @@ class NgZone {
|
|||||||
run: _run,
|
run: _run,
|
||||||
runUnary: _runUnary,
|
runUnary: _runUnary,
|
||||||
runBinary: _runBinary,
|
runBinary: _runBinary,
|
||||||
handleUncaughtError: handleUncaughtError),
|
handleUncaughtError: handleUncaughtError,
|
||||||
|
createTimer: _createTimer),
|
||||||
zoneValues: {'_innerZone': true});
|
zoneValues: {'_innerZone': true});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,6 +40,8 @@ export class NgZone {
|
|||||||
|
|
||||||
_inVmTurnDone: boolean = false;
|
_inVmTurnDone: boolean = false;
|
||||||
|
|
||||||
|
_pendingTimeouts: List<number> = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Associates with this
|
* Associates with this
|
||||||
*
|
*
|
||||||
@ -93,8 +95,17 @@ export class NgZone {
|
|||||||
*
|
*
|
||||||
* This hook is useful for validating application state (e.g. in a test).
|
* This hook is useful for validating application state (e.g. in a test).
|
||||||
*/
|
*/
|
||||||
overrideOnEventDone(onEventDoneFn: Function): void {
|
overrideOnEventDone(onEventDoneFn: Function, opt_waitForAsync: boolean): void {
|
||||||
this._onEventDone = normalizeBlank(onEventDoneFn);
|
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);
|
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
|
_innerZone: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,41 +1,212 @@
|
|||||||
import {describe, ddescribe, it, iit, xit, xdescribe, expect, beforeEach} from 'angular2/test_lib';
|
import {
|
||||||
|
AsyncTestCompleter,
|
||||||
|
inject,
|
||||||
|
describe,
|
||||||
|
ddescribe,
|
||||||
|
it,
|
||||||
|
iit,
|
||||||
|
xit,
|
||||||
|
xdescribe,
|
||||||
|
expect,
|
||||||
|
beforeEach,
|
||||||
|
SpyObject
|
||||||
|
} from 'angular2/test_lib';
|
||||||
import {Testability} from 'angular2/src/core/testability/testability';
|
import {Testability} from 'angular2/src/core/testability/testability';
|
||||||
|
import {NgZone} from 'angular2/src/core/zone/ng_zone';
|
||||||
|
import {normalizeBlank} from 'angular2/src/facade/lang';
|
||||||
|
import {PromiseWrapper} from 'angular2/src/facade/async';
|
||||||
|
|
||||||
|
// Schedules a microtasks (using a resolved promise .then())
|
||||||
|
function microTask(fn: Function): void {
|
||||||
|
PromiseWrapper.resolve(null).then((_) => { fn(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
class MockNgZone extends NgZone {
|
||||||
|
_onTurnStart: () => void;
|
||||||
|
_onEventDone: () => void;
|
||||||
|
|
||||||
|
constructor() { super({enableLongStackTrace: false}); }
|
||||||
|
|
||||||
|
start(): void { this._onTurnStart(); }
|
||||||
|
|
||||||
|
finish(): void { this._onEventDone(); }
|
||||||
|
|
||||||
|
overrideOnTurnStart(onTurnStartFn: Function): void {
|
||||||
|
this._onTurnStart = normalizeBlank(onTurnStartFn);
|
||||||
|
}
|
||||||
|
|
||||||
|
overrideOnEventDone(onEventDoneFn: Function, waitForAsync: boolean = false): void {
|
||||||
|
this._onEventDone = normalizeBlank(onEventDoneFn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
describe('Testability', () => {
|
describe('Testability', () => {
|
||||||
var testability, executed;
|
var testability, execute, ngZone;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
testability = new Testability();
|
ngZone = new MockNgZone();
|
||||||
executed = false;
|
testability = new Testability(ngZone);
|
||||||
|
execute = new SpyObject().spy('execute');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should start with a pending count of 0',
|
describe('Pending count logic', () => {
|
||||||
() => { expect(testability.getPendingCount()).toEqual(0); });
|
it('should start with a pending count of 0',
|
||||||
|
() => { expect(testability.getPendingRequestCount()).toEqual(0); });
|
||||||
|
|
||||||
it('should fire whenstable callbacks if pending count is 0', () => {
|
it('should fire whenstable callbacks if pending count is 0',
|
||||||
testability.whenStable(() => executed = true);
|
inject([AsyncTestCompleter], (async) => {
|
||||||
expect(executed).toBe(true);
|
testability.whenStable(execute);
|
||||||
|
microTask(() => {
|
||||||
|
expect(execute).toHaveBeenCalled();
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should not fire whenstable callbacks synchronously if pending count is 0', () => {
|
||||||
|
testability.whenStable(execute);
|
||||||
|
expect(execute).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not call whenstable callbacks when there are pending counts',
|
||||||
|
inject([AsyncTestCompleter], (async) => {
|
||||||
|
testability.increasePendingRequestCount();
|
||||||
|
testability.increasePendingRequestCount();
|
||||||
|
testability.whenStable(execute);
|
||||||
|
|
||||||
|
microTask(() => {
|
||||||
|
expect(execute).not.toHaveBeenCalled();
|
||||||
|
testability.decreasePendingRequestCount();
|
||||||
|
|
||||||
|
microTask(() => {
|
||||||
|
expect(execute).not.toHaveBeenCalled();
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should fire whenstable callbacks when pending drops to 0',
|
||||||
|
inject([AsyncTestCompleter], (async) => {
|
||||||
|
testability.increasePendingRequestCount();
|
||||||
|
testability.whenStable(execute);
|
||||||
|
|
||||||
|
microTask(() => {
|
||||||
|
expect(execute).not.toHaveBeenCalled();
|
||||||
|
testability.decreasePendingRequestCount();
|
||||||
|
|
||||||
|
microTask(() => {
|
||||||
|
expect(execute).toHaveBeenCalled();
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should not fire whenstable callbacks synchronously when pending drops to 0', () => {
|
||||||
|
testability.increasePendingRequestCount();
|
||||||
|
testability.whenStable(execute);
|
||||||
|
testability.decreasePendingRequestCount();
|
||||||
|
|
||||||
|
expect(execute).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not call whenstable callbacks when there are pending counts', () => {
|
describe('NgZone callback logic', () => {
|
||||||
testability.increaseCount(2);
|
it('should start being ready',
|
||||||
testability.whenStable(() => executed = true);
|
() => { expect(testability.isAngularEventPending()).toEqual(false); });
|
||||||
|
|
||||||
expect(executed).toBe(false);
|
it('should fire whenstable callback if event is already finished',
|
||||||
testability.increaseCount(-1);
|
inject([AsyncTestCompleter], (async) => {
|
||||||
expect(executed).toBe(false);
|
ngZone.start();
|
||||||
});
|
ngZone.finish();
|
||||||
|
testability.whenStable(execute);
|
||||||
|
|
||||||
it('should fire whenstable callbacks when pending drops to 0', () => {
|
microTask(() => {
|
||||||
testability.increaseCount(2);
|
expect(execute).toHaveBeenCalled();
|
||||||
testability.whenStable(() => executed = true);
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
expect(executed).toBe(false);
|
it('should not fire whenstable callbacks synchronously if event is already finished', () => {
|
||||||
|
ngZone.start();
|
||||||
|
ngZone.finish();
|
||||||
|
testability.whenStable(execute);
|
||||||
|
|
||||||
testability.increaseCount(-2);
|
expect(execute).not.toHaveBeenCalled();
|
||||||
expect(executed).toBe(true);
|
});
|
||||||
|
|
||||||
|
it('should fire whenstable callback when event finishes',
|
||||||
|
inject([AsyncTestCompleter], (async) => {
|
||||||
|
ngZone.start();
|
||||||
|
testability.whenStable(execute);
|
||||||
|
|
||||||
|
microTask(() => {
|
||||||
|
expect(execute).not.toHaveBeenCalled();
|
||||||
|
ngZone.finish();
|
||||||
|
|
||||||
|
microTask(() => {
|
||||||
|
expect(execute).toHaveBeenCalled();
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should not fire whenstable callbacks synchronously when event finishes', () => {
|
||||||
|
ngZone.start();
|
||||||
|
testability.whenStable(execute);
|
||||||
|
ngZone.finish();
|
||||||
|
|
||||||
|
expect(execute).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not fire whenstable callback when event did not finish',
|
||||||
|
inject([AsyncTestCompleter], (async) => {
|
||||||
|
ngZone.start();
|
||||||
|
testability.increasePendingRequestCount();
|
||||||
|
testability.whenStable(execute);
|
||||||
|
|
||||||
|
microTask(() => {
|
||||||
|
expect(execute).not.toHaveBeenCalled();
|
||||||
|
testability.decreasePendingRequestCount();
|
||||||
|
|
||||||
|
microTask(() => {
|
||||||
|
expect(execute).not.toHaveBeenCalled();
|
||||||
|
ngZone.finish();
|
||||||
|
|
||||||
|
microTask(() => {
|
||||||
|
expect(execute).toHaveBeenCalled();
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should not fire whenstable callback when there are pending counts',
|
||||||
|
inject([AsyncTestCompleter], (async) => {
|
||||||
|
ngZone.start();
|
||||||
|
testability.increasePendingRequestCount();
|
||||||
|
testability.increasePendingRequestCount();
|
||||||
|
testability.whenStable(execute);
|
||||||
|
|
||||||
|
microTask(() => {
|
||||||
|
expect(execute).not.toHaveBeenCalled();
|
||||||
|
ngZone.finish();
|
||||||
|
|
||||||
|
microTask(() => {
|
||||||
|
expect(execute).not.toHaveBeenCalled();
|
||||||
|
testability.decreasePendingRequestCount();
|
||||||
|
|
||||||
|
microTask(() => {
|
||||||
|
expect(execute).not.toHaveBeenCalled();
|
||||||
|
testability.decreasePendingRequestCount();
|
||||||
|
|
||||||
|
microTask(() => {
|
||||||
|
expect(execute).toHaveBeenCalled();
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
5
modules/examples/e2e_test/async/async_spec.dart
Normal file
5
modules/examples/e2e_test/async/async_spec.dart
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
library examples.e2e_test.async_spec;
|
||||||
|
|
||||||
|
main() {
|
||||||
|
|
||||||
|
}
|
71
modules/examples/e2e_test/async/async_spec.ts
Normal file
71
modules/examples/e2e_test/async/async_spec.ts
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import {verifyNoBrowserErrors} from 'angular2/src/test_lib/e2e_util';
|
||||||
|
|
||||||
|
function whenStable(rootSelector) {
|
||||||
|
// TODO(hankduan): remove this call once Protractor implements it
|
||||||
|
return browser.executeAsyncScript('var el = document.querySelector("' + rootSelector + '");' +
|
||||||
|
'window.getAngularTestability(el).whenStable(arguments[0]);');
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('async', () => {
|
||||||
|
var URL = 'examples/src/async/index.html';
|
||||||
|
|
||||||
|
beforeEach(() => browser.get(URL));
|
||||||
|
|
||||||
|
it('should work with synchronous actions', () => {
|
||||||
|
var increment = $('#increment');
|
||||||
|
increment.$('.action').click();
|
||||||
|
|
||||||
|
expect(increment.$('.val').getText()).toEqual('1');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should wait for asynchronous actions', () => {
|
||||||
|
var timeout = $('#delayedIncrement');
|
||||||
|
timeout.$('.action').click();
|
||||||
|
|
||||||
|
// At this point, the async action is still pending, so the count should
|
||||||
|
// still be 0.
|
||||||
|
expect(timeout.$('.val').getText()).toEqual('0');
|
||||||
|
|
||||||
|
whenStable('async-app')
|
||||||
|
.then(() => {
|
||||||
|
// whenStable should only be called when the async action finished,
|
||||||
|
// so the count should be 1 at this point.
|
||||||
|
expect(timeout.$('.val').getText()).toEqual('1');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should notice when asynchronous actions are cancelled', () => {
|
||||||
|
var timeout = $('#delayedIncrement');
|
||||||
|
timeout.$('.action').click();
|
||||||
|
|
||||||
|
// At this point, the async action is still pending, so the count should
|
||||||
|
// still be 0.
|
||||||
|
expect(timeout.$('.val').getText()).toEqual('0');
|
||||||
|
|
||||||
|
timeout.$('.cancel').click();
|
||||||
|
whenStable('async-app')
|
||||||
|
.then(() => {
|
||||||
|
// whenStable should be called since the async action is cancelled. The
|
||||||
|
// count should still be 0;
|
||||||
|
expect(timeout.$('.val').getText()).toEqual('0');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should wait for a series of asynchronous actions', () => {
|
||||||
|
var timeout = $('#multiDelayedIncrements');
|
||||||
|
timeout.$('.action').click();
|
||||||
|
|
||||||
|
// At this point, the async action is still pending, so the count should
|
||||||
|
// still be 0.
|
||||||
|
expect(timeout.$('.val').getText()).toEqual('0');
|
||||||
|
|
||||||
|
whenStable('async-app')
|
||||||
|
.then(() => {
|
||||||
|
// whenStable should only be called when all the async actions
|
||||||
|
// finished, so the count should be 10 at this point.
|
||||||
|
expect(timeout.$('.val').getText()).toEqual('10');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(verifyNoBrowserErrors);
|
||||||
|
});
|
14
modules/examples/src/async/index.html
Normal file
14
modules/examples/src/async/index.html
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Async</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<async-app>
|
||||||
|
Loading...
|
||||||
|
</async-app>
|
||||||
|
|
||||||
|
$SCRIPTS$
|
||||||
|
</body>
|
||||||
|
</html>
|
96
modules/examples/src/async/index.ts
Normal file
96
modules/examples/src/async/index.ts
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
import {NgIf, bootstrap, Component, View} from 'angular2/bootstrap';
|
||||||
|
import {TimerWrapper} from 'angular2/src/facade/async';
|
||||||
|
|
||||||
|
|
||||||
|
@Component({selector: 'async-app'})
|
||||||
|
@View({
|
||||||
|
template: `
|
||||||
|
<div id='increment'>
|
||||||
|
<span class='val'>{{val1}}</span>
|
||||||
|
<button class='action' (click)="increment()">Increment</button>
|
||||||
|
</div>
|
||||||
|
<div id='delayedIncrement'>
|
||||||
|
<span class='val'>{{val2}}</span>
|
||||||
|
<button class='action' (click)="delayedIncrement()">Delayed Increment</button>
|
||||||
|
<button class='cancel' *ng-if="timeoutId != null" (click)="cancelDelayedIncrement()">Cancel</button>
|
||||||
|
</div>
|
||||||
|
<div id='multiDelayedIncrements'>
|
||||||
|
<span class='val'>{{val3}}</span>
|
||||||
|
<button class='action' (click)="multiDelayedIncrements(10)">10 Delayed Increments</button>
|
||||||
|
<button class='cancel' *ng-if="multiTimeoutId != null" (click)="cancelMultiDelayedIncrements()">Cancel</button>
|
||||||
|
</div>
|
||||||
|
<div id='periodicIncrement'>
|
||||||
|
<span class='val'>{{val4}}</span>
|
||||||
|
<button class='action' (click)="periodicIncrement()">Periodic Increment</button>
|
||||||
|
<button class='cancel' *ng-if="intervalId != null" (click)="cancelPeriodicIncrement()">Cancel</button>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
directives: [NgIf]
|
||||||
|
})
|
||||||
|
class AsyncApplication {
|
||||||
|
val1: number = 0;
|
||||||
|
val2: number = 0;
|
||||||
|
val3: number = 0;
|
||||||
|
val4: number = 0;
|
||||||
|
timeoutId = null;
|
||||||
|
multiTimeoutId = null;
|
||||||
|
intervalId = null;
|
||||||
|
|
||||||
|
increment(): void { this.val1++; };
|
||||||
|
|
||||||
|
delayedIncrement(): void {
|
||||||
|
this.cancelDelayedIncrement();
|
||||||
|
this.timeoutId = TimerWrapper.setTimeout(() => {
|
||||||
|
this.val2++;
|
||||||
|
this.timeoutId = null;
|
||||||
|
}, 2000);
|
||||||
|
};
|
||||||
|
|
||||||
|
multiDelayedIncrements(i: number): void {
|
||||||
|
this.cancelMultiDelayedIncrements();
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
function helper(_i) {
|
||||||
|
if (_i <= 0) {
|
||||||
|
self.multiTimeoutId = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.multiTimeoutId = TimerWrapper.setTimeout(() => {
|
||||||
|
self.val3++;
|
||||||
|
helper(_i - 1);
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
helper(i);
|
||||||
|
};
|
||||||
|
|
||||||
|
periodicIncrement(): void {
|
||||||
|
this.cancelPeriodicIncrement();
|
||||||
|
this.intervalId = TimerWrapper.setInterval(() => { this.val4++; }, 2000)
|
||||||
|
};
|
||||||
|
|
||||||
|
cancelDelayedIncrement(): void {
|
||||||
|
if (this.timeoutId != null) {
|
||||||
|
TimerWrapper.clearTimeout(this.timeoutId);
|
||||||
|
this.timeoutId = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
cancelMultiDelayedIncrements(): void {
|
||||||
|
if (this.multiTimeoutId != null) {
|
||||||
|
TimerWrapper.clearTimeout(this.multiTimeoutId);
|
||||||
|
this.multiTimeoutId = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
cancelPeriodicIncrement(): void {
|
||||||
|
if (this.intervalId != null) {
|
||||||
|
TimerWrapper.clearInterval(this.intervalId);
|
||||||
|
this.intervalId = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function main() {
|
||||||
|
bootstrap(AsyncApplication);
|
||||||
|
}
|
@ -53,6 +53,7 @@ const kServedPaths = [
|
|||||||
'examples/src/sourcemap',
|
'examples/src/sourcemap',
|
||||||
'examples/src/todo',
|
'examples/src/todo',
|
||||||
'examples/src/zippy_component',
|
'examples/src/zippy_component',
|
||||||
|
'examples/src/async',
|
||||||
'examples/src/material/button',
|
'examples/src/material/button',
|
||||||
'examples/src/material/checkbox',
|
'examples/src/material/checkbox',
|
||||||
'examples/src/material/dialog',
|
'examples/src/material/dialog',
|
||||||
|
Reference in New Issue
Block a user