feat(testability): add an initial scaffold for the testability api

Make each application component register itself onto the testability
API and exports the API onto the window object.
This commit is contained in:
Julie Ralph
2015-03-23 16:46:18 -07:00
parent f68cdf3878
commit e81e5fb2b9
15 changed files with 369 additions and 4 deletions

View File

@ -0,0 +1,96 @@
library testability.get_testability;
import './testability.dart';
import 'dart:html';
import 'dart:js' as js;
// Work around http://dartbug.com/17752, copied from
// https://github.com/angular/angular.dart/blob/master/lib/introspection.dart
// Proxies a Dart function that accepts up to 10 parameters.
js.JsFunction _jsFunction(Function fn) {
const Object X = __varargSentinel;
return new js.JsFunction.withThis(
(thisArg, [o1=X, o2=X, o3=X, o4=X, o5=X, o6=X, o7=X, o8=X, o9=X, o10=X]) {
return __invokeFn(fn, o1, o2, o3, o4, o5, o6, o7, o8, o9, o10);
});
}
const Object __varargSentinel = const Object();
__invokeFn(fn, o1, o2, o3, o4, o5, o6, o7, o8, o9, o10) {
var args = [o1, o2, o3, o4, o5, o6, o7, o8, o9, o10];
while (args.length > 0 && identical(args.last, __varargSentinel)) {
args.removeLast();
}
return _jsify(Function.apply(fn, args));
}
// Helper function to JSify a Dart object. While this is *required* to JSify
// the result of a scope.eval(), other uses are not required and are used to
// work around http://dartbug.com/17752 in a convenient way (that bug affects
// dart2js in checked mode.)
_jsify(var obj) {
if (obj == null || obj is js.JsObject) {
return obj;
}
if (obj is _JsObjectProxyable) {
return obj._toJsObject();
}
if (obj is Function) {
return _jsFunction(obj);
}
if ((obj is Map) || (obj is Iterable)) {
var mappedObj = (obj is Map) ?
new Map.fromIterables(obj.keys, obj.values.map(_jsify)) : obj.map(_jsify);
if (obj is List) {
return new js.JsArray.from(mappedObj);
} else {
return new js.JsObject.jsify(mappedObj);
}
}
return obj;
}
abstract class _JsObjectProxyable {
js.JsObject _toJsObject();
}
class PublicTestability implements _JsObjectProxyable {
Testability _testability;
PublicTestability(Testability testability) {
this._testability = testability;
}
whenStable(Function callback) {
return this._testability.whenStable(callback);
}
findBindings(Element elem, String binding, bool exactMatch) {
return this._testability.findBindings(elem, binding, exactMatch);
}
js.JsObject _toJsObject() {
return _jsify({
'findBindings': (bindingString, [exactMatch, allowNonElementNodes]) =>
findBindings(bindingString, exactMatch, allowNonElementNodes),
'whenStable': (callback) =>
whenStable(() => callback.apply([])),
})..['_dart_'] = this;
}
}
class GetTestability {
static addToWindow(TestabilityRegistry registry) {
js.context['angular2'] = _jsify({
'getTestability': (Element elem) {
Testability testability = registry.findTestabilityInTree(elem);
return _jsify(new PublicTestability(testability));
},
'resumeBootstrap': ([arg]) {},
});
}
}

View File

@ -0,0 +1,37 @@
import {TestabilityRegistry, Testability} from 'angular2/src/core/testability/testability';
class PublicTestability {
_testabililty: Testability;
constructor(testability: Testability) {
this._testability = testability;
}
whenStable(callback: Function) {
this._testability.whenStable(callback);
}
findBindings(using, binding: string, exactMatch: boolean) {
return this._testability.findBindings(using, binding, exactMatch);
}
}
export class GetTestability {
static addToWindow(registry: TestabilityRegistry) {
if (!window.angular2) {
window.angular2 = {};
}
window.angular2.getTestability = function(elem): PublicTestability {
var testability = registry.findTestabilityInTree(elem);
if (testability == null) {
throw new Error('Could not find testability for element.');
}
return new PublicTestability(testability);
};
window.angular2.resumeBootstrap = function() {
// Intentionally left blank. This will allow Protractor to run
// against angular2 without turning off Angular synchronization.
};
}
}

View File

@ -0,0 +1,81 @@
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 'angular2/src/core/testability/get_testability';
/**
* The Testability service provides testing hooks that can be accessed from
* the browser and by services such as Protractor. Each bootstrapped Angular
* application on the page will have an instance of Testability.
*/
export class Testability {
_pendingCount: number;
_callbacks: List;
constructor() {
this._pendingCount = 0;
this._callbacks = ListWrapper.create();
}
increaseCount(delta: number = 1) {
this._pendingCount += delta;
if (this._pendingCount < 0) {
throw new BaseException('pending async requests below zero');
} else if (this._pendingCount == 0) {
this._runCallbacks();
}
return this._pendingCount;
}
_runCallbacks() {
while (this._callbacks.length !== 0) {
ListWrapper.removeLast(this._callbacks)();
}
}
whenStable(callback: Function) {
ListWrapper.push(this._callbacks, callback);
if (this._pendingCount === 0) {
this._runCallbacks();
}
// TODO(juliemr) - hook into the zone api.
}
getPendingCount(): number {
return this._pendingCount;
}
findBindings(using, binding: string, exactMatch: boolean): List {
// TODO(juliemr): implement.
return [];
}
}
export class TestabilityRegistry {
_applications: Map;
constructor() {
this._applications = MapWrapper.create();
getTestabilityModule.GetTestability.addToWindow(this);
}
registerApplication(token, testability: Testability) {
MapWrapper.set(this._applications, token, testability);
}
findTestabilityInTree(elem) : Testability {
if (elem == null) {
return null;
}
if (MapWrapper.contains(this._applications, elem)) {
return MapWrapper.get(this._applications, elem);
}
if (DOM.isShadowRoot(elem)) {
return this.findTestabilityInTree(DOM.getHost(elem));
}
return this.findTestabilityInTree(DOM.parentElement(elem));
}
}