parent
95248f46a1
commit
69ae3634c7
|
@ -30,6 +30,7 @@ interface BrowserNodeGlobal {
|
||||||
zone: Zone;
|
zone: Zone;
|
||||||
getAngularTestability: Function;
|
getAngularTestability: Function;
|
||||||
getAllAngularTestabilities: Function;
|
getAllAngularTestabilities: Function;
|
||||||
|
frameworkStabilizers: Array<Function>;
|
||||||
setTimeout: Function;
|
setTimeout: Function;
|
||||||
clearTimeout: Function;
|
clearTimeout: Function;
|
||||||
setInterval: Function;
|
setInterval: Function;
|
||||||
|
|
|
@ -15,6 +15,13 @@ import {PromiseWrapper, ObservableWrapper} from 'angular2/src/facade/async';
|
||||||
export class Testability {
|
export class Testability {
|
||||||
/** @internal */
|
/** @internal */
|
||||||
_pendingCount: number = 0;
|
_pendingCount: number = 0;
|
||||||
|
/**
|
||||||
|
* Whether any work was done since the last 'whenStable' callback. This is
|
||||||
|
* useful to detect if this could have potentially destabilized another
|
||||||
|
* component while it is stabilizing.
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
_didWork: boolean = false;
|
||||||
/** @internal */
|
/** @internal */
|
||||||
_callbacks: Function[] = [];
|
_callbacks: Function[] = [];
|
||||||
/** @internal */
|
/** @internal */
|
||||||
|
@ -23,8 +30,10 @@ export class Testability {
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
_watchAngularEvents(_ngZone: NgZone): void {
|
_watchAngularEvents(_ngZone: NgZone): void {
|
||||||
ObservableWrapper.subscribe(_ngZone.onTurnStart,
|
ObservableWrapper.subscribe(_ngZone.onTurnStart, (_) => {
|
||||||
(_) => { this._isAngularEventPending = true; });
|
this._didWork = true;
|
||||||
|
this._isAngularEventPending = true;
|
||||||
|
});
|
||||||
|
|
||||||
_ngZone.runOutsideAngular(() => {
|
_ngZone.runOutsideAngular(() => {
|
||||||
ObservableWrapper.subscribe(_ngZone.onEventDone, (_) => {
|
ObservableWrapper.subscribe(_ngZone.onEventDone, (_) => {
|
||||||
|
@ -38,6 +47,7 @@ export class Testability {
|
||||||
|
|
||||||
increasePendingRequestCount(): number {
|
increasePendingRequestCount(): number {
|
||||||
this._pendingCount += 1;
|
this._pendingCount += 1;
|
||||||
|
this._didWork = true;
|
||||||
return this._pendingCount;
|
return this._pendingCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,14 +65,16 @@ export class Testability {
|
||||||
/** @internal */
|
/** @internal */
|
||||||
_runCallbacksIfReady(): void {
|
_runCallbacksIfReady(): void {
|
||||||
if (!this.isStable()) {
|
if (!this.isStable()) {
|
||||||
|
this._didWork = true;
|
||||||
return; // Not ready
|
return; // Not ready
|
||||||
}
|
}
|
||||||
|
|
||||||
// Schedules the call backs in a new frame so that it is always async.
|
// Schedules the call backs in a new frame so that it is always async.
|
||||||
PromiseWrapper.resolve(null).then((_) => {
|
PromiseWrapper.resolve(null).then((_) => {
|
||||||
while (this._callbacks.length !== 0) {
|
while (this._callbacks.length !== 0) {
|
||||||
(this._callbacks.pop())();
|
(this._callbacks.pop())(this._didWork);
|
||||||
}
|
}
|
||||||
|
this._didWork = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -90,7 +90,7 @@ class PublicTestability implements _JsObjectProxyable {
|
||||||
'findBindings': (bindingString, [exactMatch, allowNonElementNodes]) =>
|
'findBindings': (bindingString, [exactMatch, allowNonElementNodes]) =>
|
||||||
findBindings(bindingString, exactMatch, allowNonElementNodes),
|
findBindings(bindingString, exactMatch, allowNonElementNodes),
|
||||||
'isStable': () => isStable(),
|
'isStable': () => isStable(),
|
||||||
'whenStable': (callback) => whenStable(() => callback.apply([]))
|
'whenStable': (callback) => whenStable((didWork) => callback.apply([didWork]))
|
||||||
})..['_dart_'] = this;
|
})..['_dart_'] = this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -116,16 +116,38 @@ class BrowserGetTestability implements GetTestability {
|
||||||
}
|
}
|
||||||
throw 'Could not find testability for element.';
|
throw 'Could not find testability for element.';
|
||||||
});
|
});
|
||||||
js.context['getAllAngularTestabilities'] = _jsify(() {
|
var getAllAngularTestabilities = () {
|
||||||
var registry = js.context['ngTestabilityRegistries'];
|
var registry = js.context['ngTestabilityRegistries'];
|
||||||
var result = [];
|
var result = [];
|
||||||
for (int i = 0; i < registry.length; i++) {
|
for (int i = 0; i < registry.length; i++) {
|
||||||
var testabilities =
|
var testabilities =
|
||||||
registry[i].callMethod('getAllAngularTestabilities');
|
registry[i].callMethod('getAllAngularTestabilities');
|
||||||
if (testabilities != null) result.addAll(testabilities);
|
if (testabilities != null) result.addAll(testabilities);
|
||||||
}
|
}
|
||||||
return _jsify(result);
|
return _jsify(result);
|
||||||
|
};
|
||||||
|
js.context['getAllAngularTestabilities'] =
|
||||||
|
_jsify(getAllAngularTestabilities);
|
||||||
|
|
||||||
|
var whenAllStable = _jsify((callback) {
|
||||||
|
var testabilities = getAllAngularTestabilities();
|
||||||
|
var count = testabilities.length;
|
||||||
|
var didWork = false;
|
||||||
|
var decrement = _jsify((bool didWork_) {
|
||||||
|
didWork = didWork || didWork_;
|
||||||
|
count--;
|
||||||
|
if (count == 0) {
|
||||||
|
callback.apply([didWork]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
testabilities.forEach((testability) {
|
||||||
|
testability.callMethod('whenStable', [decrement]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
if (js.context['frameworkStabilizers'] == null) {
|
||||||
|
js.context['frameworkStabilizers'] = new js.JsArray();
|
||||||
|
}
|
||||||
|
js.context['frameworkStabilizers'].add(whenAllStable);
|
||||||
}
|
}
|
||||||
jsRegistry.add(this._createRegistry(registry));
|
jsRegistry.add(this._createRegistry(registry));
|
||||||
}
|
}
|
||||||
|
@ -163,4 +185,4 @@ class BrowserGetTestability implements GetTestability {
|
||||||
});
|
});
|
||||||
return object;
|
return object;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,6 +48,25 @@ export class BrowserGetTestability implements GetTestability {
|
||||||
var testabilities = registry.getAllTestabilities();
|
var testabilities = registry.getAllTestabilities();
|
||||||
return testabilities.map((testability) => { return new PublicTestability(testability); });
|
return testabilities.map((testability) => { return new PublicTestability(testability); });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var whenAllStable = (callback) => {
|
||||||
|
var testabilities = global.getAllAngularTestabilities();
|
||||||
|
var count = testabilities.length;
|
||||||
|
var didWork = false;
|
||||||
|
var decrement = function(didWork_) {
|
||||||
|
didWork = didWork || didWork_;
|
||||||
|
count--;
|
||||||
|
if (count == 0) {
|
||||||
|
callback(didWork);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
testabilities.forEach(function(testability) { testability.whenStable(decrement); });
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!global.frameworkStabilizers) {
|
||||||
|
global.frameworkStabilizers = ListWrapper.createGrowableSize(0);
|
||||||
|
}
|
||||||
|
global.frameworkStabilizers.push(whenAllStable);
|
||||||
}
|
}
|
||||||
|
|
||||||
findTestabilityInTree(registry: TestabilityRegistry, elem: any,
|
findTestabilityInTree(registry: TestabilityRegistry, elem: any,
|
||||||
|
|
|
@ -43,12 +43,13 @@ class MockNgZone extends NgZone {
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
describe('Testability', () => {
|
describe('Testability', () => {
|
||||||
var testability, execute, ngZone;
|
var testability, execute, execute2, ngZone;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ngZone = new MockNgZone();
|
ngZone = new MockNgZone();
|
||||||
testability = new Testability(ngZone);
|
testability = new Testability(ngZone);
|
||||||
execute = new SpyObject().spy('execute');
|
execute = new SpyObject().spy('execute');
|
||||||
|
execute2 = new SpyObject().spy('execute');
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Pending count logic', () => {
|
describe('Pending count logic', () => {
|
||||||
|
@ -109,6 +110,35 @@ export function main() {
|
||||||
|
|
||||||
expect(execute).not.toHaveBeenCalled();
|
expect(execute).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should fire whenstable callbacks with didWork if pending count is 0',
|
||||||
|
inject([AsyncTestCompleter], (async) => {
|
||||||
|
testability.whenStable(execute);
|
||||||
|
microTask(() => {
|
||||||
|
expect(execute).toHaveBeenCalledWith(false);
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should fire whenstable callbacks with didWork when pending drops to 0',
|
||||||
|
inject([AsyncTestCompleter], (async) => {
|
||||||
|
testability.increasePendingRequestCount();
|
||||||
|
testability.whenStable(execute);
|
||||||
|
|
||||||
|
microTask(() => {
|
||||||
|
testability.decreasePendingRequestCount();
|
||||||
|
|
||||||
|
microTask(() => {
|
||||||
|
expect(execute).toHaveBeenCalledWith(true);
|
||||||
|
testability.whenStable(execute2);
|
||||||
|
|
||||||
|
microTask(() => {
|
||||||
|
expect(execute2).toHaveBeenCalledWith(false);
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('NgZone callback logic', () => {
|
describe('NgZone callback logic', () => {
|
||||||
|
@ -208,6 +238,43 @@ export function main() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
it('should fire whenstable callback with didWork if event is already finished',
|
||||||
|
inject([AsyncTestCompleter], (async) => {
|
||||||
|
ngZone.start();
|
||||||
|
ngZone.finish();
|
||||||
|
testability.whenStable(execute);
|
||||||
|
|
||||||
|
microTask(() => {
|
||||||
|
expect(execute).toHaveBeenCalledWith(true);
|
||||||
|
testability.whenStable(execute2);
|
||||||
|
|
||||||
|
microTask(() => {
|
||||||
|
expect(execute2).toHaveBeenCalledWith(false);
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should fire whenstable callback with didwork when event finishes',
|
||||||
|
inject([AsyncTestCompleter], (async) => {
|
||||||
|
ngZone.start();
|
||||||
|
testability.whenStable(execute);
|
||||||
|
|
||||||
|
microTask(() => {
|
||||||
|
ngZone.finish();
|
||||||
|
|
||||||
|
microTask(() => {
|
||||||
|
expect(execute).toHaveBeenCalledWith(true);
|
||||||
|
testability.whenStable(execute2);
|
||||||
|
|
||||||
|
microTask(() => {
|
||||||
|
expect(execute2).toHaveBeenCalledWith(false);
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,5 +58,35 @@ describe('async', () => {
|
||||||
expect(timeout.$('.val').getText()).toEqual('10');
|
expect(timeout.$('.val').getText()).toEqual('10');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should wait via frameworkStabilizer', () => {
|
||||||
|
var whenAllStable = function() {
|
||||||
|
return browser.executeAsyncScript('window.frameworkStabilizers[0](arguments[0]);');
|
||||||
|
};
|
||||||
|
|
||||||
|
// This disables protractor's wait mechanism
|
||||||
|
browser.ignoreSynchronization = true;
|
||||||
|
|
||||||
|
var timeout = $('#multiDelayedIncrements');
|
||||||
|
|
||||||
|
// At this point, the async action is still pending, so the count should
|
||||||
|
// still be 0.
|
||||||
|
expect(timeout.$('.val').getText()).toEqual('0');
|
||||||
|
|
||||||
|
timeout.$('.action').click();
|
||||||
|
|
||||||
|
whenAllStable().then((didWork) => {
|
||||||
|
// whenAllStable 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');
|
||||||
|
expect(didWork).toBeTruthy(); // Work was done.
|
||||||
|
});
|
||||||
|
|
||||||
|
whenAllStable().then((didWork) => {
|
||||||
|
// whenAllStable should be called immediately since nothing is pending.
|
||||||
|
expect(didWork).toBeFalsy(); // No work was done.
|
||||||
|
browser.ignoreSynchronization = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
afterEach(verifyNoBrowserErrors);
|
afterEach(verifyNoBrowserErrors);
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue