feat(test): add element probe
Usage: bootstrap the app with the special binding `ELEMENT_PROBE_CONFIG` from `angular2/debug`. This will provide a global method `ngProbe(element)` that will expose a `DebugElement` with directive instances, ... on it. During tests that use Angular's test injector, the probe is enabled by default. The `DebugElement ` can be retrieved via the function `inspectDomElement` of `angular2/debug`. Note that the `TestComponentBuilder` already returns `DebugElement `s. Closes #1992
This commit is contained in:
parent
24bc4b66d0
commit
f9908cd436
|
@ -0,0 +1,2 @@
|
||||||
|
export * from './src/debug/debug_element';
|
||||||
|
export {inspectDomElement, ELEMENT_PROBE_CONFIG} from './src/debug/debug_element_view_listener';
|
|
@ -0,0 +1,203 @@
|
||||||
|
import {Type, isPresent, BaseException, isBlank} from 'angular2/src/facade/lang';
|
||||||
|
import {List, ListWrapper, MapWrapper} from 'angular2/src/facade/collection';
|
||||||
|
|
||||||
|
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||||
|
|
||||||
|
import {ElementInjector} from 'angular2/src/core/compiler/element_injector';
|
||||||
|
import {AppView} from 'angular2/src/core/compiler/view';
|
||||||
|
import {internalView} from 'angular2/src/core/compiler/view_ref';
|
||||||
|
import {ElementRef} from 'angular2/src/core/compiler/element_ref';
|
||||||
|
|
||||||
|
import {resolveInternalDomView} from 'angular2/src/render/dom/view/view';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @exportedAs angular2/test
|
||||||
|
*
|
||||||
|
* An DebugElement contains information from the Angular compiler about an
|
||||||
|
* element and provides access to the corresponding ElementInjector and
|
||||||
|
* underlying dom Element, as well as a way to query for children.
|
||||||
|
*/
|
||||||
|
export class DebugElement {
|
||||||
|
_elementInjector: ElementInjector;
|
||||||
|
|
||||||
|
constructor(private _parentView: AppView, private _boundElementIndex: number) {
|
||||||
|
this._elementInjector = this._parentView.elementInjectors[this._boundElementIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
static create(elementRef: ElementRef): DebugElement {
|
||||||
|
return new DebugElement(internalView(elementRef.parentView), elementRef.boundElementIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
get componentInstance(): any {
|
||||||
|
if (!isPresent(this._elementInjector)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return this._elementInjector.getComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
get dynamicallyCreatedComponentInstance(): any {
|
||||||
|
if (!isPresent(this._elementInjector)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return this._elementInjector.getDynamicallyLoadedComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
get domElement(): any {
|
||||||
|
return resolveInternalDomView(this._parentView.render).boundElements[this._boundElementIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
getDirectiveInstance(directiveIndex: number): any {
|
||||||
|
return this._elementInjector.getDirectiveAtIndex(directiveIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get child DebugElements from within the Light DOM.
|
||||||
|
*
|
||||||
|
* @return {List<DebugElement>}
|
||||||
|
*/
|
||||||
|
get children(): List<DebugElement> {
|
||||||
|
var thisElementBinder = this._parentView.proto.elementBinders[this._boundElementIndex];
|
||||||
|
|
||||||
|
return this._getChildElements(this._parentView, thisElementBinder.index);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the root DebugElement children of a component. Returns an empty
|
||||||
|
* list if the current DebugElement is not a component root.
|
||||||
|
*
|
||||||
|
* @return {List<DebugElement>}
|
||||||
|
*/
|
||||||
|
get componentViewChildren(): List<DebugElement> {
|
||||||
|
var shadowView = this._parentView.componentChildViews[this._boundElementIndex];
|
||||||
|
|
||||||
|
if (!isPresent(shadowView)) {
|
||||||
|
// The current element is not a component.
|
||||||
|
return ListWrapper.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._getChildElements(shadowView, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
triggerEventHandler(eventName, eventObj): void {
|
||||||
|
this._parentView.triggerEventHandlers(eventName, eventObj, this._boundElementIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
hasDirective(type: Type): boolean {
|
||||||
|
if (!isPresent(this._elementInjector)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return this._elementInjector.hasDirective(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
inject(type: Type): any {
|
||||||
|
if (!isPresent(this._elementInjector)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return this._elementInjector.get(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the first descendant TestElememt matching the given predicate
|
||||||
|
* and scope.
|
||||||
|
*
|
||||||
|
* @param {Function: boolean} predicate
|
||||||
|
* @param {Scope} scope
|
||||||
|
*
|
||||||
|
* @return {DebugElement}
|
||||||
|
*/
|
||||||
|
query(predicate: Function, scope = Scope.all): DebugElement {
|
||||||
|
var results = this.queryAll(predicate, scope);
|
||||||
|
return results.length > 0 ? results[0] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return descendant TestElememts matching the given predicate
|
||||||
|
* and scope.
|
||||||
|
*
|
||||||
|
* @param {Function: boolean} predicate
|
||||||
|
* @param {Scope} scope
|
||||||
|
*
|
||||||
|
* @return {List<DebugElement>}
|
||||||
|
*/
|
||||||
|
queryAll(predicate: Function, scope = Scope.all): List<DebugElement> {
|
||||||
|
var elementsInScope = scope(this);
|
||||||
|
|
||||||
|
return ListWrapper.filter(elementsInScope, predicate);
|
||||||
|
}
|
||||||
|
|
||||||
|
_getChildElements(view: AppView, parentBoundElementIndex: number): List<DebugElement> {
|
||||||
|
var els = ListWrapper.create();
|
||||||
|
var parentElementBinder = null;
|
||||||
|
if (isPresent(parentBoundElementIndex)) {
|
||||||
|
parentElementBinder = view.proto.elementBinders[parentBoundElementIndex];
|
||||||
|
}
|
||||||
|
for (var i = 0; i < view.proto.elementBinders.length; ++i) {
|
||||||
|
var binder = view.proto.elementBinders[i];
|
||||||
|
if (binder.parent == parentElementBinder) {
|
||||||
|
ListWrapper.push(els, new DebugElement(view, i));
|
||||||
|
|
||||||
|
var views = view.viewContainers[i];
|
||||||
|
if (isPresent(views)) {
|
||||||
|
ListWrapper.forEach(views.views, (nextView) => {
|
||||||
|
els = ListWrapper.concat(els, this._getChildElements(nextView, null));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return els;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function inspectElement(elementRef: ElementRef): DebugElement {
|
||||||
|
return DebugElement.create(elementRef);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @exportedAs angular2/test
|
||||||
|
*/
|
||||||
|
export class Scope {
|
||||||
|
static all(debugElement): List<DebugElement> {
|
||||||
|
var scope = ListWrapper.create();
|
||||||
|
ListWrapper.push(scope, debugElement);
|
||||||
|
|
||||||
|
ListWrapper.forEach(debugElement.children,
|
||||||
|
(child) => { scope = ListWrapper.concat(scope, Scope.all(child)); });
|
||||||
|
|
||||||
|
ListWrapper.forEach(debugElement.componentViewChildren,
|
||||||
|
(child) => { scope = ListWrapper.concat(scope, Scope.all(child)); });
|
||||||
|
|
||||||
|
return scope;
|
||||||
|
}
|
||||||
|
static light(debugElement): List<DebugElement> {
|
||||||
|
var scope = ListWrapper.create();
|
||||||
|
ListWrapper.forEach(debugElement.children, (child) => {
|
||||||
|
ListWrapper.push(scope, child);
|
||||||
|
scope = ListWrapper.concat(scope, Scope.light(child));
|
||||||
|
});
|
||||||
|
return scope;
|
||||||
|
}
|
||||||
|
|
||||||
|
static view(debugElement): List<DebugElement> {
|
||||||
|
var scope = ListWrapper.create();
|
||||||
|
|
||||||
|
ListWrapper.forEach(debugElement.componentViewChildren, (child) => {
|
||||||
|
ListWrapper.push(scope, child);
|
||||||
|
scope = ListWrapper.concat(scope, Scope.light(child));
|
||||||
|
});
|
||||||
|
return scope;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @exportedAs angular2/test
|
||||||
|
*/
|
||||||
|
export class By {
|
||||||
|
static all(): Function { return (debugElement) => true; }
|
||||||
|
|
||||||
|
static css(selector: string): Function {
|
||||||
|
return (debugElement) => { return DOM.elementMatches(debugElement.domElement, selector); };
|
||||||
|
}
|
||||||
|
static directive(type: Type): Function {
|
||||||
|
return (debugElement) => { return debugElement.hasDirective(type); };
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
import {
|
||||||
|
CONST_EXPR,
|
||||||
|
isPresent,
|
||||||
|
NumberWrapper,
|
||||||
|
StringWrapper,
|
||||||
|
RegExpWrapper
|
||||||
|
} from 'angular2/src/facade/lang';
|
||||||
|
import {MapWrapper, Map, ListWrapper, List} from 'angular2/src/facade/collection';
|
||||||
|
import {Injectable, bind, Binding} from 'angular2/di';
|
||||||
|
import {AppViewListener} from 'angular2/src/core/compiler/view_listener';
|
||||||
|
import {AppView} from 'angular2/src/core/compiler/view';
|
||||||
|
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||||
|
import {resolveInternalDomView} from 'angular2/src/render/dom/view/view';
|
||||||
|
import {DebugElement} from './debug_element';
|
||||||
|
|
||||||
|
const NG_ID_PROPERTY = 'ngid';
|
||||||
|
const INSPECT_GLOBAL_NAME = 'ngProbe';
|
||||||
|
|
||||||
|
var NG_ID_SEPARATOR_RE = RegExpWrapper.create('#');
|
||||||
|
var NG_ID_SEPARATOR = '#';
|
||||||
|
|
||||||
|
// Need to keep the views in a global Map so that multiple angular apps are supported
|
||||||
|
var _allIdsByView: Map<AppView, number> = CONST_EXPR(MapWrapper.create());
|
||||||
|
var _allViewsById: Map<number, AppView> = CONST_EXPR(MapWrapper.create());
|
||||||
|
var _nextId = 0;
|
||||||
|
|
||||||
|
function _setElementId(element, indices: List<number>) {
|
||||||
|
if (isPresent(element)) {
|
||||||
|
DOM.setData(element, NG_ID_PROPERTY, ListWrapper.join(indices, NG_ID_SEPARATOR));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _getElementId(element): List<number> {
|
||||||
|
var elId = DOM.getData(element, NG_ID_PROPERTY);
|
||||||
|
if (isPresent(elId)) {
|
||||||
|
return ListWrapper.map(StringWrapper.split(elId, NG_ID_SEPARATOR_RE),
|
||||||
|
(partStr) => NumberWrapper.parseInt(partStr, 10));
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function inspectDomElement(element): DebugElement {
|
||||||
|
var elId = _getElementId(element);
|
||||||
|
if (isPresent(elId)) {
|
||||||
|
var view = MapWrapper.get(_allViewsById, elId[0]);
|
||||||
|
if (isPresent(view)) {
|
||||||
|
return new DebugElement(view, elId[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class DebugElementViewListener implements AppViewListener {
|
||||||
|
constructor() { DOM.setGlobalVar(INSPECT_GLOBAL_NAME, inspectDomElement); }
|
||||||
|
|
||||||
|
viewCreated(view: AppView) {
|
||||||
|
var viewId = _nextId++;
|
||||||
|
MapWrapper.set(_allViewsById, viewId, view);
|
||||||
|
MapWrapper.set(_allIdsByView, view, viewId);
|
||||||
|
var renderView = resolveInternalDomView(view.render);
|
||||||
|
for (var i = 0; i < renderView.boundElements.length; i++) {
|
||||||
|
_setElementId(renderView.boundElements[i], [viewId, i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
viewDestroyed(view: AppView) {
|
||||||
|
var viewId = MapWrapper.get(_allIdsByView, view);
|
||||||
|
MapWrapper.delete(_allIdsByView, view);
|
||||||
|
MapWrapper.delete(_allViewsById, viewId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export var ELEMENT_PROBE_CONFIG = [
|
||||||
|
DebugElementViewListener,
|
||||||
|
bind(AppViewListener).toAlias(DebugElementViewListener),
|
||||||
|
];
|
|
@ -258,9 +258,7 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter {
|
||||||
setData(element, name: string, value: string) { element.dataset[name] = value; }
|
setData(element, name: string, value: string) { element.dataset[name] = value; }
|
||||||
getData(element, name: string): string { return element.dataset[name]; }
|
getData(element, name: string): string { return element.dataset[name]; }
|
||||||
// TODO(tbosch): move this into a separate environment class once we have it
|
// TODO(tbosch): move this into a separate environment class once we have it
|
||||||
setGlobalVar(name: string, value: any) {
|
setGlobalVar(name: string, value: any) { global[name] = value; }
|
||||||
global[name] = value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// based on urlUtils.js in AngularJS 1
|
// based on urlUtils.js in AngularJS 1
|
||||||
|
|
|
@ -6,7 +6,6 @@ import {List, ListWrapper, MapWrapper} from 'angular2/src/facade/collection';
|
||||||
|
|
||||||
import {View} from 'angular2/src/core/annotations_impl/view';
|
import {View} from 'angular2/src/core/annotations_impl/view';
|
||||||
|
|
||||||
import {ElementInjector} from 'angular2/src/core/compiler/element_injector';
|
|
||||||
import {TemplateResolver} from 'angular2/src/core/compiler/template_resolver';
|
import {TemplateResolver} from 'angular2/src/core/compiler/template_resolver';
|
||||||
import {AppView} from 'angular2/src/core/compiler/view';
|
import {AppView} from 'angular2/src/core/compiler/view';
|
||||||
import {internalView} from 'angular2/src/core/compiler/view_ref';
|
import {internalView} from 'angular2/src/core/compiler/view_ref';
|
||||||
|
@ -14,161 +13,18 @@ import {
|
||||||
DynamicComponentLoader,
|
DynamicComponentLoader,
|
||||||
ComponentRef
|
ComponentRef
|
||||||
} from 'angular2/src/core/compiler/dynamic_component_loader';
|
} from 'angular2/src/core/compiler/dynamic_component_loader';
|
||||||
import {ElementRef} from 'angular2/src/core/compiler/element_ref';
|
|
||||||
|
|
||||||
import {el} from './utils';
|
import {el} from './utils';
|
||||||
|
|
||||||
import {DOCUMENT_TOKEN} from 'angular2/src/render/dom/dom_renderer';
|
import {DOCUMENT_TOKEN} from 'angular2/src/render/dom/dom_renderer';
|
||||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||||
|
|
||||||
import {resolveInternalDomView} from 'angular2/src/render/dom/view/view';
|
import {DebugElement} from 'angular2/src/debug/debug_element';
|
||||||
|
|
||||||
/**
|
|
||||||
* @exportedAs angular2/test
|
|
||||||
*
|
|
||||||
* A TestElement contains information from the Angular compiler about an
|
|
||||||
* element and provides access to the corresponding ElementInjector and
|
|
||||||
* underlying dom Element, as well as a way to query for children.
|
|
||||||
*/
|
|
||||||
export class TestElement {
|
|
||||||
_elementInjector: ElementInjector;
|
|
||||||
|
|
||||||
constructor(private _parentView: AppView, private _boundElementIndex: number) {
|
|
||||||
this._elementInjector = this._parentView.elementInjectors[this._boundElementIndex];
|
|
||||||
}
|
|
||||||
|
|
||||||
static create(elementRef: ElementRef): TestElement {
|
|
||||||
return new TestElement(internalView(elementRef.parentView), elementRef.boundElementIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
get componentInstance(): any {
|
|
||||||
if (!isPresent(this._elementInjector)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return this._elementInjector.getComponent();
|
|
||||||
}
|
|
||||||
|
|
||||||
get dynamicallyCreatedComponentInstance(): any {
|
|
||||||
if (!isPresent(this._elementInjector)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return this._elementInjector.getDynamicallyLoadedComponent();
|
|
||||||
}
|
|
||||||
|
|
||||||
get domElement(): any {
|
|
||||||
return resolveInternalDomView(this._parentView.render).boundElements[this._boundElementIndex];
|
|
||||||
}
|
|
||||||
|
|
||||||
getDirectiveInstance(directiveIndex: number): any {
|
|
||||||
return this._elementInjector.getDirectiveAtIndex(directiveIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get child TestElements from within the Light DOM.
|
|
||||||
*
|
|
||||||
* @return {List<TestElement>}
|
|
||||||
*/
|
|
||||||
get children(): List<TestElement> {
|
|
||||||
var thisElementBinder = this._parentView.proto.elementBinders[this._boundElementIndex];
|
|
||||||
|
|
||||||
return this._getChildElements(this._parentView, thisElementBinder.index);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the root TestElement children of a component. Returns an empty
|
|
||||||
* list if the current TestElement is not a component root.
|
|
||||||
*
|
|
||||||
* @return {List<TestElement>}
|
|
||||||
*/
|
|
||||||
get componentViewChildren(): List<TestElement> {
|
|
||||||
var shadowView = this._parentView.componentChildViews[this._boundElementIndex];
|
|
||||||
|
|
||||||
if (!isPresent(shadowView)) {
|
|
||||||
// The current test element is not a component.
|
|
||||||
return ListWrapper.create();
|
|
||||||
}
|
|
||||||
|
|
||||||
return this._getChildElements(shadowView, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
triggerEventHandler(eventName, eventObj): void {
|
|
||||||
this._parentView.triggerEventHandlers(eventName, eventObj, this._boundElementIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
hasDirective(type: Type): boolean {
|
|
||||||
if (!isPresent(this._elementInjector)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return this._elementInjector.hasDirective(type);
|
|
||||||
}
|
|
||||||
|
|
||||||
inject(type: Type): any {
|
|
||||||
if (!isPresent(this._elementInjector)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return this._elementInjector.get(type);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the first descendant TestElememt matching the given predicate
|
|
||||||
* and scope.
|
|
||||||
*
|
|
||||||
* @param {Function: boolean} predicate
|
|
||||||
* @param {Scope} scope
|
|
||||||
*
|
|
||||||
* @return {TestElement}
|
|
||||||
*/
|
|
||||||
query(predicate: Function, scope = Scope.all): TestElement {
|
|
||||||
var results = this.queryAll(predicate, scope);
|
|
||||||
return results.length > 0 ? results[0] : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return descendant TestElememts matching the given predicate
|
|
||||||
* and scope.
|
|
||||||
*
|
|
||||||
* @param {Function: boolean} predicate
|
|
||||||
* @param {Scope} scope
|
|
||||||
*
|
|
||||||
* @return {List<TestElement>}
|
|
||||||
*/
|
|
||||||
queryAll(predicate: Function, scope = Scope.all): List<TestElement> {
|
|
||||||
var elementsInScope = scope(this);
|
|
||||||
|
|
||||||
return ListWrapper.filter(elementsInScope, predicate);
|
|
||||||
}
|
|
||||||
|
|
||||||
_getChildElements(view: AppView, parentBoundElementIndex: number): List<TestElement> {
|
|
||||||
var els = ListWrapper.create();
|
|
||||||
var parentElementBinder = null;
|
|
||||||
if (isPresent(parentBoundElementIndex)) {
|
|
||||||
parentElementBinder = view.proto.elementBinders[parentBoundElementIndex];
|
|
||||||
}
|
|
||||||
for (var i = 0; i < view.proto.elementBinders.length; ++i) {
|
|
||||||
var binder = view.proto.elementBinders[i];
|
|
||||||
if (binder.parent == parentElementBinder) {
|
|
||||||
ListWrapper.push(els, new TestElement(view, i));
|
|
||||||
|
|
||||||
var views = view.viewContainers[i];
|
|
||||||
if (isPresent(views)) {
|
|
||||||
ListWrapper.forEach(views.views, (nextView) => {
|
|
||||||
els = ListWrapper.concat(els, this._getChildElements(nextView, null));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return els;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function inspectElement(elementRef: ElementRef): TestElement {
|
|
||||||
return TestElement.create(elementRef);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @exportedAs angular2/test
|
* @exportedAs angular2/test
|
||||||
*/
|
*/
|
||||||
export class RootTestComponent extends TestElement {
|
export class RootTestComponent extends DebugElement {
|
||||||
_componentRef: ComponentRef;
|
_componentRef: ComponentRef;
|
||||||
_componentParentView: AppView;
|
_componentParentView: AppView;
|
||||||
|
|
||||||
|
@ -187,55 +43,7 @@ export class RootTestComponent extends TestElement {
|
||||||
destroy(): void { this._componentRef.dispose(); }
|
destroy(): void { this._componentRef.dispose(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
var _nextRootElementId = 0;
|
||||||
* @exportedAs angular2/test
|
|
||||||
*/
|
|
||||||
export class Scope {
|
|
||||||
static all(testElement): List<TestElement> {
|
|
||||||
var scope = ListWrapper.create();
|
|
||||||
ListWrapper.push(scope, testElement);
|
|
||||||
|
|
||||||
ListWrapper.forEach(testElement.children,
|
|
||||||
(child) => { scope = ListWrapper.concat(scope, Scope.all(child)); });
|
|
||||||
|
|
||||||
ListWrapper.forEach(testElement.componentViewChildren,
|
|
||||||
(child) => { scope = ListWrapper.concat(scope, Scope.all(child)); });
|
|
||||||
|
|
||||||
return scope;
|
|
||||||
}
|
|
||||||
static light(testElement): List<TestElement> {
|
|
||||||
var scope = ListWrapper.create();
|
|
||||||
ListWrapper.forEach(testElement.children, (child) => {
|
|
||||||
ListWrapper.push(scope, child);
|
|
||||||
scope = ListWrapper.concat(scope, Scope.light(child));
|
|
||||||
});
|
|
||||||
return scope;
|
|
||||||
}
|
|
||||||
|
|
||||||
static view(testElement): List<TestElement> {
|
|
||||||
var scope = ListWrapper.create();
|
|
||||||
|
|
||||||
ListWrapper.forEach(testElement.componentViewChildren, (child) => {
|
|
||||||
ListWrapper.push(scope, child);
|
|
||||||
scope = ListWrapper.concat(scope, Scope.light(child));
|
|
||||||
});
|
|
||||||
return scope;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @exportedAs angular2/test
|
|
||||||
*/
|
|
||||||
export class By {
|
|
||||||
static all(): Function { return (testElement) => true; }
|
|
||||||
|
|
||||||
static css(selector: string): Function {
|
|
||||||
return (testElement) => { return DOM.elementMatches(testElement.domElement, selector); };
|
|
||||||
}
|
|
||||||
static directive(type: Type): Function {
|
|
||||||
return (testElement) => { return testElement.hasDirective(type); };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @exportedAs angular2/test
|
* @exportedAs angular2/test
|
||||||
|
@ -331,13 +139,14 @@ export class TestComponentBuilder {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
var rootEl = el('<div id="root"></div>');
|
var rootElId = `root${_nextRootElementId++}`;
|
||||||
|
var rootEl = el(`<div id="${rootElId}"></div>`);
|
||||||
var doc = this._injector.get(DOCUMENT_TOKEN);
|
var doc = this._injector.get(DOCUMENT_TOKEN);
|
||||||
|
|
||||||
// TODO(juliemr): can/should this be optional?
|
// TODO(juliemr): can/should this be optional?
|
||||||
DOM.appendChild(doc.body, rootEl);
|
DOM.appendChild(doc.body, rootEl);
|
||||||
return this._injector.get(DynamicComponentLoader)
|
return this._injector.get(DynamicComponentLoader)
|
||||||
.loadAsRoot(rootComponentType, '#root', this._injector)
|
.loadAsRoot(rootComponentType, `#${rootElId}`, this._injector)
|
||||||
.then((componentRef) => { return new RootTestComponent(componentRef); });
|
.then((componentRef) => { return new RootTestComponent(componentRef); });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,6 +45,7 @@ import {FunctionWrapper, Type} from 'angular2/src/facade/lang';
|
||||||
import {AppViewPool, APP_VIEW_POOL_CAPACITY} from 'angular2/src/core/compiler/view_pool';
|
import {AppViewPool, APP_VIEW_POOL_CAPACITY} from 'angular2/src/core/compiler/view_pool';
|
||||||
import {AppViewManager} from 'angular2/src/core/compiler/view_manager';
|
import {AppViewManager} from 'angular2/src/core/compiler/view_manager';
|
||||||
import {AppViewManagerUtils} from 'angular2/src/core/compiler/view_manager_utils';
|
import {AppViewManagerUtils} from 'angular2/src/core/compiler/view_manager_utils';
|
||||||
|
import {ELEMENT_PROBE_CONFIG} from 'angular2/debug';
|
||||||
import {ProtoViewFactory} from 'angular2/src/core/compiler/proto_view_factory';
|
import {ProtoViewFactory} from 'angular2/src/core/compiler/proto_view_factory';
|
||||||
import {RenderCompiler, Renderer} from 'angular2/src/render/api';
|
import {RenderCompiler, Renderer} from 'angular2/src/render/api';
|
||||||
import {DomRenderer, DOCUMENT_TOKEN} from 'angular2/src/render/dom/dom_renderer';
|
import {DomRenderer, DOCUMENT_TOKEN} from 'angular2/src/render/dom/dom_renderer';
|
||||||
|
@ -80,7 +81,6 @@ function _getAppBindings() {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
appDoc = null;
|
appDoc = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
bind(DOCUMENT_TOKEN)
|
bind(DOCUMENT_TOKEN)
|
||||||
.toValue(appDoc),
|
.toValue(appDoc),
|
||||||
|
@ -96,6 +96,7 @@ function _getAppBindings() {
|
||||||
AppViewPool,
|
AppViewPool,
|
||||||
AppViewManager,
|
AppViewManager,
|
||||||
AppViewManagerUtils,
|
AppViewManagerUtils,
|
||||||
|
ELEMENT_PROBE_CONFIG,
|
||||||
bind(APP_VIEW_POOL_CAPACITY).toValue(500),
|
bind(APP_VIEW_POOL_CAPACITY).toValue(500),
|
||||||
Compiler,
|
Compiler,
|
||||||
CompilerCache,
|
CompilerCache,
|
||||||
|
|
|
@ -725,9 +725,7 @@ export function main() {
|
||||||
var updateHost = injector.get(DirectiveUpdatingHostActions);
|
var updateHost = injector.get(DirectiveUpdatingHostActions);
|
||||||
|
|
||||||
ObservableWrapper.subscribe(updateHost.setAttr, (_) => {
|
ObservableWrapper.subscribe(updateHost.setAttr, (_) => {
|
||||||
expect(stringifyElement(domElement))
|
expect(DOM.hasAttribute(domElement, 'update-host-actions')).toBe(true);
|
||||||
.toEqual(
|
|
||||||
'<div class="ng-binding" key="value" update-host-actions=""></div>');
|
|
||||||
async.done();
|
async.done();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,273 @@
|
||||||
|
import {
|
||||||
|
AsyncTestCompleter,
|
||||||
|
beforeEach,
|
||||||
|
ddescribe,
|
||||||
|
xdescribe,
|
||||||
|
describe,
|
||||||
|
dispatchEvent,
|
||||||
|
expect,
|
||||||
|
iit,
|
||||||
|
inject,
|
||||||
|
IS_DARTIUM,
|
||||||
|
beforeEachBindings,
|
||||||
|
it,
|
||||||
|
xit,
|
||||||
|
TestComponentBuilder,
|
||||||
|
By,
|
||||||
|
Scope
|
||||||
|
} from 'angular2/test_lib';
|
||||||
|
|
||||||
|
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||||
|
|
||||||
|
import {List, ListWrapper} from 'angular2/src/facade/collection';
|
||||||
|
import {PromiseWrapper, EventEmitter, ObservableWrapper} from 'angular2/src/facade/async';
|
||||||
|
|
||||||
|
import {Injectable} from 'angular2/di';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Directive,
|
||||||
|
Component,
|
||||||
|
View,
|
||||||
|
} from 'angular2/annotations';
|
||||||
|
|
||||||
|
import {NgFor} from 'angular2/src/directives/ng_for';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
class Logger {
|
||||||
|
log: List<string>;
|
||||||
|
|
||||||
|
constructor() { this.log = ListWrapper.create(); }
|
||||||
|
|
||||||
|
add(thing: string) { ListWrapper.push(this.log, thing); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Directive({selector: '[message]', properties: ['message']})
|
||||||
|
@Injectable()
|
||||||
|
class MessageDir {
|
||||||
|
logger: Logger;
|
||||||
|
|
||||||
|
constructor(logger: Logger) { this.logger = logger; }
|
||||||
|
|
||||||
|
set message(newMessage) { this.logger.add(newMessage); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({selector: 'child-comp'})
|
||||||
|
@View({
|
||||||
|
template: `<div class="child" message="child">
|
||||||
|
<span class="childnested" message="nestedchild">Child</span>
|
||||||
|
</div>
|
||||||
|
<span class="child">{{childBinding}}</span>`,
|
||||||
|
directives: [MessageDir]
|
||||||
|
})
|
||||||
|
@Injectable()
|
||||||
|
class ChildComp {
|
||||||
|
childBinding: string;
|
||||||
|
|
||||||
|
constructor() { this.childBinding = 'Original'; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({selector: 'parent-comp', appInjector: [Logger]})
|
||||||
|
@View({
|
||||||
|
template: `<div class="parent" message="parent">
|
||||||
|
<span class="parentnested" message="nestedparent">Parent</span>
|
||||||
|
</div>
|
||||||
|
<span class="parent">{{parentBinding}}</span>
|
||||||
|
<child-comp class="child-comp-class"></child-comp>`,
|
||||||
|
directives: [ChildComp, MessageDir]
|
||||||
|
})
|
||||||
|
@Injectable()
|
||||||
|
class ParentComp {
|
||||||
|
parentBinding: string;
|
||||||
|
constructor() { this.parentBinding = 'OriginalParent'; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Directive({selector: 'custom-emitter', events: ['myevent']})
|
||||||
|
@Injectable()
|
||||||
|
class CustomEmitter {
|
||||||
|
myevent: EventEmitter;
|
||||||
|
|
||||||
|
constructor() { this.myevent = new EventEmitter(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({selector: 'events-comp'})
|
||||||
|
@View({
|
||||||
|
template: `<button (click)="handleClick()"></button>
|
||||||
|
<custom-emitter (myevent)="handleCustom()"></custom-emitter>`,
|
||||||
|
directives: [CustomEmitter]
|
||||||
|
})
|
||||||
|
@Injectable()
|
||||||
|
class EventsComp {
|
||||||
|
clicked: boolean;
|
||||||
|
customed: boolean;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.clicked = false;
|
||||||
|
this.customed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleClick() { this.clicked = true; }
|
||||||
|
|
||||||
|
handleCustom() { this.customed = true; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({selector: 'using-for', appInjector: [Logger]})
|
||||||
|
@View({
|
||||||
|
template: `<span *ng-for="#thing of stuff">{{thing}}</span>
|
||||||
|
<ul message="list">
|
||||||
|
<li *ng-for="#item of stuff">{{item}}</li>
|
||||||
|
</ul>`,
|
||||||
|
directives: [NgFor, MessageDir]
|
||||||
|
})
|
||||||
|
@Injectable()
|
||||||
|
class UsingFor {
|
||||||
|
stuff: List<string>;
|
||||||
|
|
||||||
|
constructor() { this.stuff = ['one', 'two', 'three']; }
|
||||||
|
}
|
||||||
|
|
||||||
|
export function main() {
|
||||||
|
describe('debug element', function() {
|
||||||
|
|
||||||
|
it('should list component child elements',
|
||||||
|
inject([TestComponentBuilder, AsyncTestCompleter], (tcb, async) => {
|
||||||
|
|
||||||
|
tcb.createAsync(ParentComp)
|
||||||
|
.then((rootTestComponent) => {
|
||||||
|
rootTestComponent.detectChanges();
|
||||||
|
|
||||||
|
var childEls = rootTestComponent.children;
|
||||||
|
// The root is a lone component, and has no children in the light dom.
|
||||||
|
expect(childEls.length).toEqual(0);
|
||||||
|
|
||||||
|
var rootCompChildren = rootTestComponent.componentViewChildren;
|
||||||
|
// The root component has 3 elements in its shadow view.
|
||||||
|
expect(rootCompChildren.length).toEqual(3);
|
||||||
|
expect(DOM.hasClass(rootCompChildren[0].domElement, 'parent')).toBe(true);
|
||||||
|
expect(DOM.hasClass(rootCompChildren[1].domElement, 'parent')).toBe(true);
|
||||||
|
expect(DOM.hasClass(rootCompChildren[2].domElement, 'child-comp-class')).toBe(true);
|
||||||
|
|
||||||
|
var nested = rootCompChildren[0].children;
|
||||||
|
expect(nested.length).toEqual(1);
|
||||||
|
expect(DOM.hasClass(nested[0].domElement, 'parentnested')).toBe(true);
|
||||||
|
|
||||||
|
var childComponent = rootCompChildren[2];
|
||||||
|
expect(childComponent.children.length).toEqual(0);
|
||||||
|
|
||||||
|
var childCompChildren = childComponent.componentViewChildren;
|
||||||
|
expect(childCompChildren.length).toEqual(2);
|
||||||
|
expect(DOM.hasClass(childCompChildren[0].domElement, 'child')).toBe(true);
|
||||||
|
expect(DOM.hasClass(childCompChildren[1].domElement, 'child')).toBe(true);
|
||||||
|
|
||||||
|
var childNested = childCompChildren[0].children;
|
||||||
|
expect(childNested.length).toEqual(1);
|
||||||
|
expect(DOM.hasClass(childNested[0].domElement, 'childnested')).toBe(true);
|
||||||
|
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should list child elements within viewports',
|
||||||
|
inject([TestComponentBuilder, AsyncTestCompleter], (tcb, async) => {
|
||||||
|
|
||||||
|
tcb.createAsync(UsingFor).then((rootTestComponent) => {
|
||||||
|
rootTestComponent.detectChanges();
|
||||||
|
|
||||||
|
var childEls = rootTestComponent.componentViewChildren;
|
||||||
|
// TODO should this count include the <template> element?
|
||||||
|
expect(childEls.length).toEqual(5);
|
||||||
|
|
||||||
|
var list = childEls[4];
|
||||||
|
expect(list.children.length).toEqual(4);
|
||||||
|
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should query child elements',
|
||||||
|
inject([TestComponentBuilder, AsyncTestCompleter], (tcb, async) => {
|
||||||
|
|
||||||
|
tcb.createAsync(ParentComp)
|
||||||
|
.then((rootTestComponent) => {
|
||||||
|
rootTestComponent.detectChanges();
|
||||||
|
|
||||||
|
var childTestEls = rootTestComponent.queryAll(By.directive(MessageDir));
|
||||||
|
|
||||||
|
expect(childTestEls.length).toBe(4);
|
||||||
|
expect(DOM.hasClass(childTestEls[0].domElement, 'parent')).toBe(true);
|
||||||
|
expect(DOM.hasClass(childTestEls[1].domElement, 'parentnested')).toBe(true);
|
||||||
|
expect(DOM.hasClass(childTestEls[2].domElement, 'child')).toBe(true);
|
||||||
|
expect(DOM.hasClass(childTestEls[3].domElement, 'childnested')).toBe(true);
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should query child elements in the light DOM',
|
||||||
|
inject([TestComponentBuilder, AsyncTestCompleter], (tcb, async) => {
|
||||||
|
|
||||||
|
tcb.createAsync(ParentComp)
|
||||||
|
.then((rootTestComponent) => {
|
||||||
|
rootTestComponent.detectChanges();
|
||||||
|
|
||||||
|
var parentEl = rootTestComponent.componentViewChildren[0];
|
||||||
|
|
||||||
|
var childTestEls = parentEl.queryAll(By.directive(MessageDir), Scope.light);
|
||||||
|
|
||||||
|
expect(childTestEls.length).toBe(1);
|
||||||
|
expect(DOM.hasClass(childTestEls[0].domElement, 'parentnested')).toBe(true);
|
||||||
|
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should query child elements in the current component view DOM',
|
||||||
|
inject([TestComponentBuilder, AsyncTestCompleter], (tcb, async) => {
|
||||||
|
|
||||||
|
tcb.createAsync(ParentComp)
|
||||||
|
.then((rootTestComponent) => {
|
||||||
|
rootTestComponent.detectChanges();
|
||||||
|
|
||||||
|
var childTestEls = rootTestComponent.queryAll(By.directive(MessageDir), Scope.view);
|
||||||
|
|
||||||
|
expect(childTestEls.length).toBe(2);
|
||||||
|
expect(DOM.hasClass(childTestEls[0].domElement, 'parent')).toBe(true);
|
||||||
|
expect(DOM.hasClass(childTestEls[1].domElement, 'parentnested')).toBe(true);
|
||||||
|
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should allow injecting from the element injector',
|
||||||
|
inject([TestComponentBuilder, AsyncTestCompleter], (tcb, async) => {
|
||||||
|
|
||||||
|
tcb.createAsync(ParentComp)
|
||||||
|
.then((rootTestComponent) => {
|
||||||
|
rootTestComponent.detectChanges();
|
||||||
|
|
||||||
|
expect(rootTestComponent.componentViewChildren[0].inject(Logger).log)
|
||||||
|
.toEqual(['parent', 'nestedparent', 'child', 'nestedchild']);
|
||||||
|
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should trigger event handlers',
|
||||||
|
inject([TestComponentBuilder, AsyncTestCompleter], (tcb, async) => {
|
||||||
|
|
||||||
|
tcb.createAsync(EventsComp)
|
||||||
|
.then((rootTestComponent) => {
|
||||||
|
rootTestComponent.detectChanges();
|
||||||
|
|
||||||
|
expect(rootTestComponent.componentInstance.clicked).toBe(false);
|
||||||
|
expect(rootTestComponent.componentInstance.customed).toBe(false);
|
||||||
|
|
||||||
|
rootTestComponent.componentViewChildren[0].triggerEventHandler('click', {});
|
||||||
|
expect(rootTestComponent.componentInstance.clicked).toBe(true);
|
||||||
|
|
||||||
|
rootTestComponent.componentViewChildren[1].triggerEventHandler('myevent', {});
|
||||||
|
expect(rootTestComponent.componentInstance.customed).toBe(true);
|
||||||
|
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
import {
|
||||||
|
AsyncTestCompleter,
|
||||||
|
beforeEach,
|
||||||
|
ddescribe,
|
||||||
|
xdescribe,
|
||||||
|
describe,
|
||||||
|
dispatchEvent,
|
||||||
|
expect,
|
||||||
|
iit,
|
||||||
|
inject,
|
||||||
|
IS_DARTIUM,
|
||||||
|
beforeEachBindings,
|
||||||
|
it,
|
||||||
|
xit,
|
||||||
|
TestComponentBuilder,
|
||||||
|
By,
|
||||||
|
Scope,
|
||||||
|
inspectDomElement
|
||||||
|
} from 'angular2/test_lib';
|
||||||
|
|
||||||
|
import {global} from 'angular2/src/facade/lang';
|
||||||
|
import {APP_VIEW_POOL_CAPACITY} from 'angular2/src/core/compiler/view_pool';
|
||||||
|
|
||||||
|
import {Injectable, bind} from 'angular2/di';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Directive,
|
||||||
|
Component,
|
||||||
|
View,
|
||||||
|
} from 'angular2/annotations';
|
||||||
|
|
||||||
|
@Component({selector: 'my-comp'})
|
||||||
|
@View({directives: []})
|
||||||
|
@Injectable()
|
||||||
|
class MyComp {
|
||||||
|
ctxProp: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function main() {
|
||||||
|
describe('element probe', function() {
|
||||||
|
beforeEachBindings(() => [bind(APP_VIEW_POOL_CAPACITY).toValue(0)]);
|
||||||
|
|
||||||
|
it('should return a TestElement from a dom element',
|
||||||
|
inject([TestComponentBuilder, AsyncTestCompleter], (tcb, async) => {
|
||||||
|
tcb.overrideTemplate(MyComp, '<div some-dir></div>')
|
||||||
|
.createAsync(MyComp)
|
||||||
|
.then((rootTestComponent) => {
|
||||||
|
expect(inspectDomElement(rootTestComponent.domElement).componentInstance)
|
||||||
|
.toBeAnInstanceOf(MyComp);
|
||||||
|
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should clean up whent the view is destroyed',
|
||||||
|
inject([TestComponentBuilder, AsyncTestCompleter], (tcb, async) => {
|
||||||
|
tcb.overrideTemplate(MyComp, '')
|
||||||
|
.createAsync(MyComp)
|
||||||
|
.then((rootTestComponent) => {
|
||||||
|
rootTestComponent.destroy();
|
||||||
|
expect(inspectDomElement(rootTestComponent.domElement)).toBe(null);
|
||||||
|
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (!IS_DARTIUM) {
|
||||||
|
it('should provide a global function to inspect elements',
|
||||||
|
inject([TestComponentBuilder, AsyncTestCompleter], (tcb, async) => {
|
||||||
|
tcb.overrideTemplate(MyComp, '')
|
||||||
|
.createAsync(MyComp)
|
||||||
|
.then((rootTestComponent) => {
|
||||||
|
expect(global['ngProbe'](rootTestComponent.domElement).componentInstance)
|
||||||
|
.toBeAnInstanceOf(MyComp);
|
||||||
|
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
|
@ -11,16 +11,10 @@ import {
|
||||||
IS_DARTIUM,
|
IS_DARTIUM,
|
||||||
beforeEachBindings,
|
beforeEachBindings,
|
||||||
it,
|
it,
|
||||||
xit
|
xit,
|
||||||
|
TestComponentBuilder
|
||||||
} from 'angular2/test_lib';
|
} from 'angular2/test_lib';
|
||||||
|
|
||||||
import {TestComponentBuilder, By, Scope} from 'angular2/src/test_lib/test_component_builder';
|
|
||||||
|
|
||||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
|
||||||
|
|
||||||
import {List, ListWrapper} from 'angular2/src/facade/collection';
|
|
||||||
import {PromiseWrapper, EventEmitter, ObservableWrapper} from 'angular2/src/facade/async';
|
|
||||||
|
|
||||||
import {Injectable} from 'angular2/di';
|
import {Injectable} from 'angular2/di';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -32,40 +26,13 @@ import {
|
||||||
import * as viewAnn from 'angular2/src/core/annotations_impl/view';
|
import * as viewAnn from 'angular2/src/core/annotations_impl/view';
|
||||||
|
|
||||||
import {NgIf} from 'angular2/src/directives/ng_if';
|
import {NgIf} from 'angular2/src/directives/ng_if';
|
||||||
import {NgFor} from 'angular2/src/directives/ng_for';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
class Logger {
|
|
||||||
log: List<string>;
|
|
||||||
|
|
||||||
constructor() { this.log = ListWrapper.create(); }
|
|
||||||
|
|
||||||
add(thing: string) { ListWrapper.push(this.log, thing); }
|
|
||||||
}
|
|
||||||
|
|
||||||
@Directive({selector: '[message]', properties: ['message']})
|
|
||||||
@Injectable()
|
|
||||||
class MessageDir {
|
|
||||||
logger: Logger;
|
|
||||||
|
|
||||||
constructor(logger: Logger) { this.logger = logger; }
|
|
||||||
|
|
||||||
set message(newMessage) { this.logger.add(newMessage); }
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({selector: 'child-comp'})
|
@Component({selector: 'child-comp'})
|
||||||
@View({
|
@View({template: `<snap>Original {{childBinding}}</span>`, directives: []})
|
||||||
template: `<div class="child" message="child">
|
|
||||||
<span class="childnested" message="nestedchild">Child</span>
|
|
||||||
</div>
|
|
||||||
<span class="child">{{childBinding}}</span>`,
|
|
||||||
directives: [MessageDir]
|
|
||||||
})
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
class ChildComp {
|
class ChildComp {
|
||||||
childBinding: string;
|
childBinding: string;
|
||||||
|
constructor() { this.childBinding = 'Child'; }
|
||||||
constructor() { this.childBinding = 'Original'; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({selector: 'child-comp'})
|
@Component({selector: 'child-comp'})
|
||||||
|
@ -74,82 +41,30 @@ class ChildComp {
|
||||||
class MockChildComp {
|
class MockChildComp {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({selector: 'parent-comp', appInjector: [Logger]})
|
@Component({selector: 'parent-comp'})
|
||||||
@View({
|
@View({template: `Parent(<child-comp></child-comp>)`, directives: [ChildComp]})
|
||||||
template: `<div class="parent" message="parent">
|
|
||||||
<span class="parentnested" message="nestedparent">Parent</span>
|
|
||||||
</div>
|
|
||||||
<span class="parent">{{parentBinding}}</span>
|
|
||||||
<child-comp class="child-comp-class"></child-comp>`,
|
|
||||||
directives: [ChildComp, MessageDir]
|
|
||||||
})
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
class ParentComp {
|
class ParentComp {
|
||||||
parentBinding: string;
|
|
||||||
constructor() { this.parentBinding = 'OriginalParent'; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({selector: 'my-if-comp'})
|
@Component({selector: 'my-if-comp'})
|
||||||
@View({template: `<span *ng-if="showMore">More</span>`, directives: [NgIf]})
|
@View({template: `MyIf(<span *ng-if="showMore">More</span>)`, directives: [NgIf]})
|
||||||
@Injectable()
|
@Injectable()
|
||||||
class MyIfComp {
|
class MyIfComp {
|
||||||
showMore: boolean = false;
|
showMore: boolean = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Directive({selector: 'custom-emitter', events: ['myevent']})
|
|
||||||
@Injectable()
|
|
||||||
class CustomEmitter {
|
|
||||||
myevent: EventEmitter;
|
|
||||||
|
|
||||||
constructor() { this.myevent = new EventEmitter(); }
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({selector: 'events-comp'})
|
|
||||||
@View({
|
|
||||||
template: `<button (click)="handleClick()"></button>
|
|
||||||
<custom-emitter (myevent)="handleCustom()"></custom-emitter>`,
|
|
||||||
directives: [CustomEmitter]
|
|
||||||
})
|
|
||||||
@Injectable()
|
|
||||||
class EventsComp {
|
|
||||||
clicked: boolean;
|
|
||||||
customed: boolean;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.clicked = false;
|
|
||||||
this.customed = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
handleClick() { this.clicked = true; }
|
|
||||||
|
|
||||||
handleCustom() { this.customed = true; }
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({selector: 'using-for', appInjector: [Logger]})
|
|
||||||
@View({
|
|
||||||
template: `<span *ng-for="#thing of stuff">{{thing}}</span>
|
|
||||||
<ul message="list">
|
|
||||||
<li *ng-for="#item of stuff">{{item}}</li>
|
|
||||||
</ul>`,
|
|
||||||
directives: [NgFor, MessageDir]
|
|
||||||
})
|
|
||||||
@Injectable()
|
|
||||||
class UsingFor {
|
|
||||||
stuff: List<string>;
|
|
||||||
|
|
||||||
constructor() { this.stuff = ['one', 'two', 'three']; }
|
|
||||||
}
|
|
||||||
export function main() {
|
export function main() {
|
||||||
describe('test component builder', function() {
|
describe('test component builder', function() {
|
||||||
it('should instantiate a component with valid DOM',
|
it('should instantiate a component with valid DOM',
|
||||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb, async) => {
|
inject([TestComponentBuilder, AsyncTestCompleter], (tcb, async) => {
|
||||||
|
|
||||||
tcb.createAsync(MockChildComp)
|
tcb.createAsync(ChildComp).then((rootTestComponent) => {
|
||||||
.then((rootTestComponent) => {
|
rootTestComponent.detectChanges();
|
||||||
var childSpans = DOM.querySelectorAll(rootTestComponent.domElement, 'span');
|
|
||||||
expect(DOM.getInnerHTML(childSpans[0])).toEqual('Mock');
|
expect(rootTestComponent.domElement).toHaveText('Original Child');
|
||||||
async.done();
|
async.done();
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should allow changing members of the component',
|
it('should allow changing members of the component',
|
||||||
|
@ -157,150 +72,24 @@ export function main() {
|
||||||
|
|
||||||
tcb.createAsync(MyIfComp).then((rootTestComponent) => {
|
tcb.createAsync(MyIfComp).then((rootTestComponent) => {
|
||||||
rootTestComponent.detectChanges();
|
rootTestComponent.detectChanges();
|
||||||
var childSpans = DOM.querySelectorAll(rootTestComponent.domElement, 'span');
|
expect(rootTestComponent.domElement).toHaveText('MyIf()');
|
||||||
expect(childSpans.length).toEqual(0);
|
|
||||||
|
|
||||||
rootTestComponent.componentInstance.showMore = true;
|
rootTestComponent.componentInstance.showMore = true;
|
||||||
rootTestComponent.detectChanges();
|
rootTestComponent.detectChanges();
|
||||||
childSpans = DOM.querySelectorAll(rootTestComponent.domElement, 'span');
|
expect(rootTestComponent.domElement).toHaveText('MyIf(More)');
|
||||||
expect(childSpans.length).toEqual(1);
|
|
||||||
|
|
||||||
async.done();
|
async.done();
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should list component child elements',
|
|
||||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb, async) => {
|
|
||||||
|
|
||||||
tcb.createAsync(ParentComp)
|
|
||||||
.then((rootTestComponent) => {
|
|
||||||
rootTestComponent.detectChanges();
|
|
||||||
|
|
||||||
var childEls = rootTestComponent.children;
|
|
||||||
// The root is a lone component, and has no children in the light dom.
|
|
||||||
expect(childEls.length).toEqual(0);
|
|
||||||
|
|
||||||
var rootCompChildren = rootTestComponent.componentViewChildren;
|
|
||||||
// The root component has 3 elements in its shadow view.
|
|
||||||
expect(rootCompChildren.length).toEqual(3);
|
|
||||||
expect(DOM.hasClass(rootCompChildren[0].domElement, 'parent')).toBe(true);
|
|
||||||
expect(DOM.hasClass(rootCompChildren[1].domElement, 'parent')).toBe(true);
|
|
||||||
expect(DOM.hasClass(rootCompChildren[2].domElement, 'child-comp-class')).toBe(true);
|
|
||||||
|
|
||||||
var nested = rootCompChildren[0].children;
|
|
||||||
expect(nested.length).toEqual(1);
|
|
||||||
expect(DOM.hasClass(nested[0].domElement, 'parentnested')).toBe(true);
|
|
||||||
|
|
||||||
var childComponent = rootCompChildren[2];
|
|
||||||
expect(childComponent.children.length).toEqual(0);
|
|
||||||
|
|
||||||
var childCompChildren = childComponent.componentViewChildren;
|
|
||||||
expect(childCompChildren.length).toEqual(2);
|
|
||||||
expect(DOM.hasClass(childCompChildren[0].domElement, 'child')).toBe(true);
|
|
||||||
expect(DOM.hasClass(childCompChildren[1].domElement, 'child')).toBe(true);
|
|
||||||
|
|
||||||
var childNested = childCompChildren[0].children;
|
|
||||||
expect(childNested.length).toEqual(1);
|
|
||||||
expect(DOM.hasClass(childNested[0].domElement, 'childnested')).toBe(true);
|
|
||||||
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should list child elements within viewports',
|
|
||||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb, async) => {
|
|
||||||
|
|
||||||
tcb.createAsync(UsingFor).then((rootTestComponent) => {
|
|
||||||
rootTestComponent.detectChanges();
|
|
||||||
|
|
||||||
var childEls = rootTestComponent.componentViewChildren;
|
|
||||||
// TODO should this count include the <template> element?
|
|
||||||
expect(childEls.length).toEqual(5);
|
|
||||||
|
|
||||||
var list = childEls[4];
|
|
||||||
expect(list.children.length).toEqual(4);
|
|
||||||
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should query child elements',
|
|
||||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb, async) => {
|
|
||||||
|
|
||||||
tcb.createAsync(ParentComp)
|
|
||||||
.then((rootTestComponent) => {
|
|
||||||
rootTestComponent.detectChanges();
|
|
||||||
|
|
||||||
var childTestEls = rootTestComponent.queryAll(By.directive(MessageDir));
|
|
||||||
|
|
||||||
expect(childTestEls.length).toBe(4);
|
|
||||||
expect(DOM.hasClass(childTestEls[0].domElement, 'parent')).toBe(true);
|
|
||||||
expect(DOM.hasClass(childTestEls[1].domElement, 'parentnested')).toBe(true);
|
|
||||||
expect(DOM.hasClass(childTestEls[2].domElement, 'child')).toBe(true);
|
|
||||||
expect(DOM.hasClass(childTestEls[3].domElement, 'childnested')).toBe(true);
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should query child elements in the light DOM',
|
|
||||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb, async) => {
|
|
||||||
|
|
||||||
tcb.createAsync(ParentComp)
|
|
||||||
.then((rootTestComponent) => {
|
|
||||||
rootTestComponent.detectChanges();
|
|
||||||
|
|
||||||
var parentEl = rootTestComponent.componentViewChildren[0];
|
|
||||||
|
|
||||||
var childTestEls = parentEl.queryAll(By.directive(MessageDir), Scope.light);
|
|
||||||
|
|
||||||
expect(childTestEls.length).toBe(1);
|
|
||||||
expect(DOM.hasClass(childTestEls[0].domElement, 'parentnested')).toBe(true);
|
|
||||||
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should query child elements in the current component view DOM',
|
|
||||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb, async) => {
|
|
||||||
|
|
||||||
tcb.createAsync(ParentComp)
|
|
||||||
.then((rootTestComponent) => {
|
|
||||||
rootTestComponent.detectChanges();
|
|
||||||
|
|
||||||
var childTestEls = rootTestComponent.queryAll(By.directive(MessageDir), Scope.view);
|
|
||||||
|
|
||||||
expect(childTestEls.length).toBe(2);
|
|
||||||
expect(DOM.hasClass(childTestEls[0].domElement, 'parent')).toBe(true);
|
|
||||||
expect(DOM.hasClass(childTestEls[1].domElement, 'parentnested')).toBe(true);
|
|
||||||
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should allow injecting from the element injector',
|
|
||||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb, async) => {
|
|
||||||
|
|
||||||
tcb.createAsync(ParentComp)
|
|
||||||
.then((rootTestComponent) => {
|
|
||||||
rootTestComponent.detectChanges();
|
|
||||||
|
|
||||||
expect(rootTestComponent.componentViewChildren[0].inject(Logger).log)
|
|
||||||
.toEqual(['parent', 'nestedparent', 'child', 'nestedchild']);
|
|
||||||
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should override a template',
|
it('should override a template',
|
||||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb, async) => {
|
inject([TestComponentBuilder, AsyncTestCompleter], (tcb, async) => {
|
||||||
|
|
||||||
tcb.overrideTemplate(MockChildComp, '<span>Modified</span>')
|
tcb.overrideTemplate(MockChildComp, '<span>Mock</span>')
|
||||||
.createAsync(MockChildComp)
|
.createAsync(MockChildComp)
|
||||||
.then((rootTestComponent) => {
|
.then((rootTestComponent) => {
|
||||||
rootTestComponent.detectChanges();
|
rootTestComponent.detectChanges();
|
||||||
var childSpans = DOM.querySelectorAll(rootTestComponent.domElement, 'span');
|
expect(rootTestComponent.domElement).toHaveText('Mock');
|
||||||
expect(childSpans.length).toEqual(1);
|
|
||||||
expect(DOM.getInnerHTML(childSpans[0])).toEqual('Modified');
|
|
||||||
|
|
||||||
async.done();
|
async.done();
|
||||||
});
|
});
|
||||||
|
@ -314,9 +103,7 @@ export function main() {
|
||||||
.createAsync(ChildComp)
|
.createAsync(ChildComp)
|
||||||
.then((rootTestComponent) => {
|
.then((rootTestComponent) => {
|
||||||
rootTestComponent.detectChanges();
|
rootTestComponent.detectChanges();
|
||||||
var childSpans = DOM.querySelectorAll(rootTestComponent.domElement, 'span');
|
expect(rootTestComponent.domElement).toHaveText('Modified Child');
|
||||||
expect(childSpans.length).toEqual(1);
|
|
||||||
expect(DOM.getInnerHTML(childSpans[0])).toEqual('Modified Original');
|
|
||||||
|
|
||||||
async.done();
|
async.done();
|
||||||
});
|
});
|
||||||
|
@ -329,29 +116,7 @@ export function main() {
|
||||||
.createAsync(ParentComp)
|
.createAsync(ParentComp)
|
||||||
.then((rootTestComponent) => {
|
.then((rootTestComponent) => {
|
||||||
rootTestComponent.detectChanges();
|
rootTestComponent.detectChanges();
|
||||||
var childSpans = DOM.querySelectorAll(rootTestComponent.domElement, 'span');
|
expect(rootTestComponent.domElement).toHaveText('Parent(Mock)');
|
||||||
expect(childSpans.length).toEqual(3);
|
|
||||||
expect(DOM.getInnerHTML(childSpans[2])).toEqual('Mock');
|
|
||||||
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should trigger event handlers',
|
|
||||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb, async) => {
|
|
||||||
|
|
||||||
tcb.createAsync(EventsComp)
|
|
||||||
.then((rootTestComponent) => {
|
|
||||||
rootTestComponent.detectChanges();
|
|
||||||
|
|
||||||
expect(rootTestComponent.componentInstance.clicked).toBe(false);
|
|
||||||
expect(rootTestComponent.componentInstance.customed).toBe(false);
|
|
||||||
|
|
||||||
rootTestComponent.componentViewChildren[0].triggerEventHandler('click', {});
|
|
||||||
expect(rootTestComponent.componentInstance.clicked).toBe(true);
|
|
||||||
|
|
||||||
rootTestComponent.componentViewChildren[1].triggerEventHandler('myevent', {});
|
|
||||||
expect(rootTestComponent.componentInstance.customed).toBe(true);
|
|
||||||
|
|
||||||
async.done();
|
async.done();
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
export * from './src/test_lib/test_lib';
|
export * from './src/test_lib/test_lib';
|
||||||
export * from './src/test_lib/utils';
|
export * from './src/test_lib/utils';
|
||||||
export * from './src/test_lib/fake_async';
|
export * from './src/test_lib/fake_async';
|
||||||
|
export * from './src/test_lib/test_component_builder';
|
||||||
|
export * from './debug';
|
||||||
|
|
Loading…
Reference in New Issue