diff --git a/modules/angular2/src/change_detection/interfaces.js b/modules/angular2/src/change_detection/interfaces.js
index 41938652db..0231ca764e 100644
--- a/modules/angular2/src/change_detection/interfaces.js
+++ b/modules/angular2/src/change_detection/interfaces.js
@@ -49,6 +49,7 @@ export class ChangeDetector {
addChild(cd:ChangeDetector) {}
addShadowDomChild(cd:ChangeDetector) {}
removeChild(cd:ChangeDetector) {}
+ removeShadowDomChild(cd:ChangeDetector) {}
remove() {}
hydrate(context:any, locals:Locals, directives:any) {}
dehydrate() {}
diff --git a/modules/angular2/src/core/compiler/dynamic_component_loader.js b/modules/angular2/src/core/compiler/dynamic_component_loader.js
index 9a719e0537..ef113fe0b6 100644
--- a/modules/angular2/src/core/compiler/dynamic_component_loader.js
+++ b/modules/angular2/src/core/compiler/dynamic_component_loader.js
@@ -43,14 +43,12 @@ export class ComponentRef {
export class DynamicComponentLoader {
_compiler:Compiler;
_viewFactory:ViewFactory;
- _renderer:Renderer;
_directiveMetadataReader:DirectiveMetadataReader;
constructor(compiler:Compiler, directiveMetadataReader:DirectiveMetadataReader,
renderer:Renderer, viewFactory:ViewFactory) {
this._compiler = compiler;
this._directiveMetadataReader = directiveMetadataReader;
- this._renderer = renderer;
this._viewFactory = viewFactory
}
@@ -67,16 +65,13 @@ export class DynamicComponentLoader {
var hostEi = location.elementInjector;
var hostView = location.hostView;
-
return this._compiler.compile(type).then(componentProtoView => {
var component = hostEi.dynamicallyCreateComponent(type, directiveMetadata.annotation, inj);
var componentView = this._instantiateAndHydrateView(componentProtoView, injector, hostEi, component);
//TODO(vsavkin): do not use component child views as we need to clear the dynamically created views
//same problem exists on the render side
- hostView.addComponentChildView(componentView);
-
- this._renderer.setDynamicComponentView(hostView.render, location.boundElementIndex, componentView.render);
+ hostView.setDynamicComponentChildView(location.boundElementIndex, componentView);
// TODO(vsavkin): return a component ref that dehydrates the component view and removes it
// from the component child views
diff --git a/modules/angular2/src/core/compiler/element_binder.js b/modules/angular2/src/core/compiler/element_binder.js
index 92230a494f..d644124355 100644
--- a/modules/angular2/src/core/compiler/element_binder.js
+++ b/modules/angular2/src/core/compiler/element_binder.js
@@ -1,4 +1,4 @@
-import {int, isBlank, BaseException} from 'angular2/src/facade/lang';
+import {int, isBlank, isPresent, BaseException} from 'angular2/src/facade/lang';
import * as eiModule from './element_injector';
import {DirectiveBinding} from './element_injector';
import {List, StringMap} from 'angular2/src/facade/collection';
@@ -32,4 +32,12 @@ export class ElementBinder {
// updated later, so we are able to resolve cycles
this.nestedProtoView = null;
}
+
+ hasStaticComponent() {
+ return isPresent(this.componentDirective) && isPresent(this.nestedProtoView);
+ }
+
+ hasDynamicComponent() {
+ return isPresent(this.componentDirective) && isBlank(this.nestedProtoView);
+ }
}
diff --git a/modules/angular2/src/core/compiler/view.js b/modules/angular2/src/core/compiler/view.js
index 16ba1f3d47..ce2e40158c 100644
--- a/modules/angular2/src/core/compiler/view.js
+++ b/modules/angular2/src/core/compiler/view.js
@@ -143,7 +143,6 @@ export class AppView {
}
var binders = this.proto.elementBinders;
- var componentChildViewIndex = 0;
for (var i = 0; i < binders.length; ++i) {
var componentDirective = binders[i].componentDirective;
var shadowDomAppInjector = null;
@@ -176,8 +175,8 @@ export class AppView {
}
}
- if (isPresent(binders[i].nestedProtoView) && isPresent(componentDirective)) {
- renderComponentIndex = this.componentChildViews[componentChildViewIndex].internalHydrateRecurse(
+ if (binders[i].hasStaticComponent()) {
+ renderComponentIndex = this.componentChildViews[i].internalHydrateRecurse(
renderComponentViewRefs,
renderComponentIndex,
shadowDomAppInjector,
@@ -185,7 +184,6 @@ export class AppView {
elementInjector.getComponent(),
null
);
- componentChildViewIndex++;
}
}
this._hydrateChangeDetector();
@@ -198,7 +196,15 @@ export class AppView {
// componentChildViews
for (var i = 0; i < this.componentChildViews.length; i++) {
- this.componentChildViews[i].internalDehydrateRecurse();
+ var componentView = this.componentChildViews[i];
+ if (isPresent(componentView)) {
+ componentView.internalDehydrateRecurse();
+ var binder = this.proto.elementBinders[i];
+ if (binder.hasDynamicComponent()) {
+ this.componentChildViews[i] = null;
+ this.changeDetector.removeShadowDomChild(componentView.changeDetector);
+ }
+ }
}
// elementInjectors
@@ -255,9 +261,16 @@ export class AppView {
return elementInjector.getDirectiveAtIndex(directive.directiveIndex);
}
- addComponentChildView(view:AppView) {
- ListWrapper.push(this.componentChildViews, view);
+ setDynamicComponentChildView(boundElementIndex, view:AppView) {
+ if (!this.proto.elementBinders[boundElementIndex].hasDynamicComponent()) {
+ throw new BaseException(`There is no dynamic component directive at element ${boundElementIndex}`);
+ }
+ if (isPresent(this.componentChildViews[boundElementIndex])) {
+ throw new BaseException(`There already is a bound component at element ${boundElementIndex}`);
+ }
+ this.componentChildViews[boundElementIndex] = view;
this.changeDetector.addShadowDomChild(view.changeDetector);
+ this.proto.renderer.setDynamicComponentView(this.render, boundElementIndex, view.render);
}
// implementation of EventDispatcher#dispatchEvent
diff --git a/modules/angular2/src/core/compiler/view_factory.js b/modules/angular2/src/core/compiler/view_factory.js
index cb01d6a984..1996111a9e 100644
--- a/modules/angular2/src/core/compiler/view_factory.js
+++ b/modules/angular2/src/core/compiler/view_factory.js
@@ -57,7 +57,7 @@ export class ViewFactory {
var rootElementInjectors = [];
var preBuiltObjects = ListWrapper.createFixedSize(binders.length);
var viewContainers = ListWrapper.createFixedSize(binders.length);
- var componentChildViews = [];
+ var componentChildViews = ListWrapper.createFixedSize(binders.length);
for (var binderIdx = 0; binderIdx < binders.length; binderIdx++) {
var binder = binders[binderIdx];
@@ -78,13 +78,13 @@ export class ViewFactory {
// componentChildViews
var bindingPropagationConfig = null;
- if (isPresent(binder.nestedProtoView) && isPresent(binder.componentDirective)) {
+ if (binder.hasStaticComponent()) {
var childView = this._createView(binder.nestedProtoView);
- changeDetector.addChild(childView.changeDetector);
+ changeDetector.addShadowDomChild(childView.changeDetector);
bindingPropagationConfig = new BindingPropagationConfig(childView.changeDetector);
- ListWrapper.push(componentChildViews, childView);
+ componentChildViews[binderIdx] = childView;
}
// viewContainers
diff --git a/modules/angular2/src/render/dom/view/element_binder.js b/modules/angular2/src/render/dom/view/element_binder.js
index 9c978f8f15..b14e945e87 100644
--- a/modules/angular2/src/render/dom/view/element_binder.js
+++ b/modules/angular2/src/render/dom/view/element_binder.js
@@ -1,3 +1,4 @@
+import {isBlank, isPresent} from 'angular2/src/facade/lang';
import {AST} from 'angular2/change_detection';
import {SetterFn} from 'angular2/src/reflection/types';
import {List, ListWrapper} from 'angular2/src/facade/collection';
@@ -38,6 +39,14 @@ export class ElementBinder {
this.distanceToParent = distanceToParent;
this.propertySetters = propertySetters;
}
+
+ hasStaticComponent() {
+ return isPresent(this.componentId) && isPresent(this.nestedProtoView);
+ }
+
+ hasDynamicComponent() {
+ return isPresent(this.componentId) && isBlank(this.nestedProtoView);
+ }
}
export class Event {
diff --git a/modules/angular2/src/render/dom/view/view.js b/modules/angular2/src/render/dom/view/view.js
index a366e2d58d..b30c423083 100644
--- a/modules/angular2/src/render/dom/view/view.js
+++ b/modules/angular2/src/render/dom/view/view.js
@@ -165,6 +165,11 @@ export class RenderView {
var cv = this.componentChildViews[i];
if (isPresent(cv)) {
cv.dehydrate();
+ if (this.proto.elementBinders[i].hasDynamicComponent()) {
+ ViewContainer.removeViewNodes(cv);
+ this.lightDoms[i] = null;
+ this.componentChildViews[i] = null;
+ }
}
}
diff --git a/modules/angular2/src/render/dom/view/view_factory.js b/modules/angular2/src/render/dom/view/view_factory.js
index e50129b1f6..77910dfb21 100644
--- a/modules/angular2/src/render/dom/view/view_factory.js
+++ b/modules/angular2/src/render/dom/view/view_factory.js
@@ -84,7 +84,6 @@ export class ViewFactory {
} else {
viewRootNodes = [rootElementClone];
}
-
var binders = protoView.elementBinders;
var boundTextNodes = [];
var boundElements = ListWrapper.createFixedSize(binders.length);
@@ -133,7 +132,7 @@ export class ViewFactory {
var element = boundElements[binderIdx];
// static child components
- if (isPresent(binder.componentId) && isPresent(binder.nestedProtoView)) {
+ if (binder.hasStaticComponent()) {
var childView = this._createView(binder.nestedProtoView);
view.setComponentView(this._shadowDomStrategy, binderIdx, childView);
}
diff --git a/modules/angular2/src/test_lib/test_lib.dart b/modules/angular2/src/test_lib/test_lib.dart
index be005c3a9c..c8eeba7c83 100644
--- a/modules/angular2/src/test_lib/test_lib.dart
+++ b/modules/angular2/src/test_lib/test_lib.dart
@@ -1,7 +1,7 @@
library test_lib.test_lib;
import 'package:guinness/guinness.dart' as gns;
-export 'package:guinness/guinness.dart' hide Expect, expect, NotExpect, beforeEach, it, iit, xit;
+export 'package:guinness/guinness.dart' hide Expect, expect, NotExpect, beforeEach, it, iit, xit, SpyObject;
import 'package:unittest/unittest.dart' hide expect;
import 'dart:async';
@@ -149,6 +149,13 @@ xit(name, fn) {
_it(gns.xit, name, fn);
}
+class SpyObject extends gns.SpyObject {
+ // Need to take an optional type as this is required by
+ // the JS SpyObject.
+ SpyObject([type = null]) {
+ }
+}
+
String elementText(n) {
hasNodes(n) {
var children = DOM.childNodes(n);
diff --git a/modules/angular2/test/core/compiler/dynamic_component_loader_spec.js b/modules/angular2/test/core/compiler/dynamic_component_loader_spec.js
index 95ca21ce1d..ea6583f4b3 100644
--- a/modules/angular2/test/core/compiler/dynamic_component_loader_spec.js
+++ b/modules/angular2/test/core/compiler/dynamic_component_loader_spec.js
@@ -1,22 +1,59 @@
-import {ddescribe, describe, it, iit, expect, beforeEach} from 'angular2/test_lib';
+import {
+ AsyncTestCompleter,
+ beforeEach,
+ ddescribe,
+ xdescribe,
+ describe,
+ el,
+ dispatchEvent,
+ expect,
+ iit,
+ inject,
+ beforeEachBindings,
+ it,
+ xit,
+ SpyObject, proxy
+} from 'angular2/test_lib';
+import {IMPLEMENTS} from 'angular2/src/facade/lang';
+import {MapWrapper, ListWrapper} from 'angular2/src/facade/collection';
+import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
import {DirectiveMetadataReader} from 'angular2/src/core/compiler/directive_metadata_reader';
import {DynamicComponentLoader} from 'angular2/src/core/compiler/dynamic_component_loader';
-import {Decorator, Viewport} from 'angular2/src/core/annotations/annotations';
-
-@Decorator({selector: 'someDecorator'})
-class SomeDecorator {}
-
-@Viewport({selector: 'someViewport'})
-class SomeViewport {}
+import {Decorator, Viewport, Component} from 'angular2/src/core/annotations/annotations';
+import {ElementRef, ElementInjector, ProtoElementInjector, PreBuiltObjects} from 'angular2/src/core/compiler/element_injector';
+import {Compiler} from 'angular2/src/core/compiler/compiler';
+import {AppProtoView, AppView} from 'angular2/src/core/compiler/view';
+import {ViewFactory} from 'angular2/src/core/compiler/view_factory'
+import {Renderer} from 'angular2/src/render/api';
export function main() {
describe("DynamicComponentLoader", () => {
+ var compiler;
+ var viewFactory;
+ var directiveMetadataReader;
+ var renderer;
var loader;
- beforeEach(() => {
- loader = new DynamicComponentLoader(null, new DirectiveMetadataReader(), null, null);
+ beforeEach( () => {
+ compiler = new SpyCompiler();
+ viewFactory = new SpyViewFactory();
+ renderer = new SpyRenderer();
+ directiveMetadataReader = new DirectiveMetadataReader();
+ loader = new DynamicComponentLoader(compiler, directiveMetadataReader, renderer, viewFactory);;
});
+ function createProtoView() {
+ return new AppProtoView(null, null, null);
+ }
+
+ function createElementRef(view, boundElementIndex) {
+ var peli = new ProtoElementInjector(null, boundElementIndex, []);
+ var eli = new ElementInjector(peli, null);
+ var preBuiltObjects = new PreBuiltObjects(view, null, null, null);
+ eli.instantiateDirectives(null, null, null, preBuiltObjects);
+ return new ElementRef(eli);
+ }
+
describe("loadIntoExistingLocation", () => {
describe('Load errors', () => {
it('should throw when trying to load a decorator', () => {
@@ -29,7 +66,55 @@ export function main() {
.toThrowError("Could not load 'SomeViewport' because it is not a component.");
});
});
+
+ it('should add the child view into the host view', inject([AsyncTestCompleter], (async) => {
+ var log = [];
+ var hostView = new SpyAppView();
+ var childView = new SpyAppView();
+ hostView.spy('setDynamicComponentChildView').andCallFake( (boundElementIndex, childView) => {
+ ListWrapper.push(log, ['setDynamicComponentChildView', boundElementIndex, childView]);
+ });
+ childView.spy('hydrate').andCallFake( (appInjector, hostElementInjector, context, locals) => {
+ ListWrapper.push(log, 'hydrate');
+ });
+ compiler.spy('compile').andCallFake( (_) => PromiseWrapper.resolve(createProtoView()));
+ viewFactory.spy('getView').andCallFake( (_) => childView);
+
+ var elementRef = createElementRef(hostView, 23);
+ loader.loadIntoExistingLocation(SomeComponent, elementRef).then( (componentRef) => {
+ expect(log[0]).toEqual('hydrate');
+ expect(log[1]).toEqual(['setDynamicComponentChildView', 23, childView]);
+ async.done();
+ });
+ }));
+
});
});
}
+
+@Decorator({selector: 'someDecorator'})
+class SomeDecorator {}
+
+@Viewport({selector: 'someViewport'})
+class SomeViewport {}
+
+@Component({selector: 'someComponent'})
+class SomeComponent {}
+
+
+@proxy
+@IMPLEMENTS(Compiler)
+class SpyCompiler extends SpyObject {noSuchMethod(m){return super.noSuchMethod(m)}}
+
+@proxy
+@IMPLEMENTS(ViewFactory)
+class SpyViewFactory extends SpyObject {noSuchMethod(m){return super.noSuchMethod(m)}}
+
+@proxy
+@IMPLEMENTS(Renderer)
+class SpyRenderer extends SpyObject {noSuchMethod(m){return super.noSuchMethod(m)}}
+
+@proxy
+@IMPLEMENTS(AppView)
+class SpyAppView extends SpyObject {noSuchMethod(m){return super.noSuchMethod(m)}}
diff --git a/modules/angular2/test/core/compiler/integration_spec.js b/modules/angular2/test/core/compiler/integration_spec.js
index 493ac409d4..c6c0020c8e 100644
--- a/modules/angular2/test/core/compiler/integration_spec.js
+++ b/modules/angular2/test/core/compiler/integration_spec.js
@@ -567,7 +567,7 @@ export function main() {
async.done();
});
}));
-
+
it('should support render global events from multiple directives', inject([TestBed, AsyncTestCompleter], (tb, async) => {
tb.overrideView(MyComp, new View({
template: '
',
@@ -636,6 +636,40 @@ export function main() {
});
});
}));
+
+ it('should allow to destroy and create them via viewport directives',
+ inject([TestBed, AsyncTestCompleter], (tb, async) => {
+ tb.overrideView(MyComp, new View({
+ template: '
',
+ directives: [DynamicComp, If]
+ }));
+
+ tb.createView(MyComp).then((view) => {
+ view.context.ctxBoolProp = true;
+ view.detectChanges();
+ var dynamicComponent = view.rawView.viewContainers[0].get(0).locals.get("dynamic");
+ dynamicComponent.done.then((_) => {
+ view.detectChanges();
+ expect(view.rootNodes).toHaveText('hello');
+
+ view.context.ctxBoolProp = false;
+ view.detectChanges();
+
+ expect(view.rawView.viewContainers[0].length).toBe(0);
+ expect(view.rootNodes).toHaveText('');
+
+ view.context.ctxBoolProp = true;
+ view.detectChanges();
+
+ var dynamicComponent = view.rawView.viewContainers[0].get(0).locals.get("dynamic");
+ return dynamicComponent.done;
+ }).then((_) => {
+ view.detectChanges();
+ expect(view.rootNodes).toHaveText('hello');
+ async.done();
+ });
+ });
+ }));
});
it('should support static attributes', inject([TestBed, AsyncTestCompleter], (tb, async) => {
diff --git a/modules/angular2/test/core/compiler/view_factory_spec.js b/modules/angular2/test/core/compiler/view_factory_spec.js
index 8007f2472d..eef71f16c9 100644
--- a/modules/angular2/test/core/compiler/view_factory_spec.js
+++ b/modules/angular2/test/core/compiler/view_factory_spec.js
@@ -1,23 +1,71 @@
-import {describe, ddescribe, it, iit, xit, xdescribe, expect, beforeEach, el} from 'angular2/test_lib';
-
+import {
+ AsyncTestCompleter,
+ beforeEach,
+ ddescribe,
+ xdescribe,
+ describe,
+ el,
+ dispatchEvent,
+ expect,
+ iit,
+ inject,
+ beforeEachBindings,
+ it,
+ xit,
+ SpyObject, proxy
+} from 'angular2/test_lib';
+import {IMPLEMENTS, isBlank} from 'angular2/src/facade/lang';
import {ViewFactory} from 'angular2/src/core/compiler/view_factory';
import {AppProtoView, AppView} from 'angular2/src/core/compiler/view';
import {dynamicChangeDetection} from 'angular2/change_detection';
+import {DirectiveBinding, ElementInjector} from 'angular2/src/core/compiler/element_injector';
+import {DirectiveMetadataReader} from 'angular2/src/core/compiler/directive_metadata_reader';
+import {Component} from 'angular2/src/core/annotations/annotations';
+import {ElementBinder} from 'angular2/src/core/compiler/element_binder';
+import {ChangeDetector, ProtoChangeDetector} from 'angular2/change_detection';
export function main() {
- function createViewFactory({capacity}):ViewFactory {
- return new ViewFactory(capacity);
- }
+ describe('AppViewFactory', () => {
+ var reader;
- function createPv() {
- return new AppProtoView(null,
- null,
- dynamicChangeDetection.createProtoChangeDetector('dummy', null));
- }
+ beforeEach( () => {
+ reader = new DirectiveMetadataReader();
+ });
+
+ function createViewFactory({capacity}):ViewFactory {
+ return new ViewFactory(capacity);
+ }
+
+ function createProtoChangeDetector() {
+ var pcd = new SpyProtoChangeDetector();
+ pcd.spy('instantiate').andCallFake( (dispatcher, bindingRecords, variableBindings, directiveRecords) => {
+ return new SpyChangeDetector();
+ });
+ return pcd;
+ }
+
+ function createProtoView(binders=null) {
+ if (isBlank(binders)) {
+ binders = [];
+ }
+ var pv = new AppProtoView(null, null, createProtoChangeDetector());
+ pv.elementBinders = binders;
+ return pv;
+ }
+
+ function createDirectiveBinding(type) {
+ var meta = reader.read(type);
+ return DirectiveBinding.createFromType(meta.type, meta.annotation);
+ }
+
+ function createComponentElBinder(binding, nestedProtoView = null) {
+ var binder = new ElementBinder(0, null, 0, null, binding, null);
+ binder.nestedProtoView = nestedProtoView;
+ return binder;
+ }
- describe('RenderViewFactory', () => {
it('should create views', () => {
- var pv = createPv();
+ var pv = createProtoView();
var vf = createViewFactory({
capacity: 1
});
@@ -27,8 +75,8 @@ export function main() {
describe('caching', () => {
it('should support multiple AppProtoViews', () => {
- var pv1 = createPv();
- var pv2 = createPv();
+ var pv1 = createProtoView();
+ var pv2 = createProtoView();
var vf = createViewFactory({ capacity: 2 });
var view1 = vf.getView(pv1);
var view2 = vf.getView(pv2);
@@ -40,7 +88,7 @@ export function main() {
});
it('should reuse the newest view that has been returned', () => {
- var pv = createPv();
+ var pv = createProtoView();
var vf = createViewFactory({ capacity: 2 });
var view1 = vf.getView(pv);
var view2 = vf.getView(pv);
@@ -51,7 +99,7 @@ export function main() {
});
it('should not add views when the capacity has been reached', () => {
- var pv = createPv();
+ var pv = createProtoView();
var vf = createViewFactory({ capacity: 2 });
var view1 = vf.getView(pv);
var view2 = vf.getView(pv);
@@ -66,5 +114,57 @@ export function main() {
});
+ describe('child components', () => {
+
+ var vf;
+
+ beforeEach(() => {
+ vf = createViewFactory({capacity: 1});
+ });
+
+ it('should create static child component views', () => {
+ var hostPv = createProtoView([
+ createComponentElBinder(
+ createDirectiveBinding(SomeComponent),
+ createProtoView()
+ )
+ ]);
+ var hostView = vf.getView(hostPv);
+ var shadowView = hostView.componentChildViews[0];
+ expect(shadowView).toBeTruthy();
+ expect(hostView.changeDetector.spy('addShadowDomChild')).toHaveBeenCalledWith(shadowView.changeDetector);
+ });
+
+ it('should not create dynamic child component views', () => {
+ var hostPv = createProtoView([
+ createComponentElBinder(
+ createDirectiveBinding(SomeComponent),
+ null
+ )
+ ]);
+ var hostView = vf.getView(hostPv);
+ var shadowView = hostView.componentChildViews[0];
+ expect(shadowView).toBeFalsy();
+ });
+
+ });
+
});
}
+
+@Component({ selector: 'someComponent' })
+class SomeComponent {}
+
+@proxy
+@IMPLEMENTS(ChangeDetector)
+class SpyChangeDetector extends SpyObject {
+ constructor(){super(ChangeDetector);}
+ noSuchMethod(m){return super.noSuchMethod(m)}
+}
+
+@proxy
+@IMPLEMENTS(ProtoChangeDetector)
+class SpyProtoChangeDetector extends SpyObject {
+ constructor(){super(ProtoChangeDetector);}
+ noSuchMethod(m){return super.noSuchMethod(m)}
+}
diff --git a/modules/angular2/test/core/compiler/view_spec.js b/modules/angular2/test/core/compiler/view_spec.js
new file mode 100644
index 0000000000..08daf38b30
--- /dev/null
+++ b/modules/angular2/test/core/compiler/view_spec.js
@@ -0,0 +1,213 @@
+import {
+ AsyncTestCompleter,
+ beforeEach,
+ ddescribe,
+ xdescribe,
+ describe,
+ el,
+ dispatchEvent,
+ expect,
+ iit,
+ inject,
+ beforeEachBindings,
+ it,
+ xit,
+ SpyObject, proxy
+} from 'angular2/test_lib';
+import {IMPLEMENTS, isBlank} from 'angular2/src/facade/lang';
+import {MapWrapper, ListWrapper} from 'angular2/src/facade/collection';
+
+import {AppProtoView, AppView} from 'angular2/src/core/compiler/view';
+import {Renderer, ViewRef} from 'angular2/src/render/api';
+import {ChangeDetector} from 'angular2/change_detection';
+import {ElementBinder} from 'angular2/src/core/compiler/element_binder';
+import {DirectiveBinding, ElementInjector} from 'angular2/src/core/compiler/element_injector';
+import {DirectiveMetadataReader} from 'angular2/src/core/compiler/directive_metadata_reader';
+import {Component} from 'angular2/src/core/annotations/annotations';
+
+export function main() {
+ describe('AppView', () => {
+ var renderer;
+ var reader;
+
+ beforeEach( () => {
+ renderer = new SpyRenderer();
+ reader = new DirectiveMetadataReader();
+ });
+
+ function createDirectiveBinding(type) {
+ var meta = reader.read(type);
+ return DirectiveBinding.createFromType(meta.type, meta.annotation);
+ }
+
+ function createElementInjector() {
+ var res = new SpyElementInjector();
+ res.spy('isExportingComponent').andCallFake( () => false );
+ res.spy('isExportingElement').andCallFake( () => false );
+ return res;
+ }
+
+ function createEmptyElBinder() {
+ return new ElementBinder(0, null, 0, null, null, null);
+ }
+
+ function createComponentElBinder(binding, nestedProtoView = null) {
+ var binder = new ElementBinder(0, null, 0, null, binding, null);
+ binder.nestedProtoView = nestedProtoView;
+ return binder;
+ }
+
+ function createProtoView(binders = null) {
+ if (isBlank(binders)) {
+ binders = [];
+ }
+ var res = new AppProtoView(renderer, null, null);
+ res.elementBinders = binders;
+ return res;
+ }
+
+ function createHostProtoView(nestedProtoView) {
+ return createProtoView([
+ createComponentElBinder(
+ createDirectiveBinding(SomeComponent),
+ nestedProtoView
+ )
+ ]);
+ }
+
+ function createHostView(pv, shadowView, componentInstance) {
+ var view = new AppView(pv, MapWrapper.create());
+ var changeDetector = new SpyChangeDetector();
+ var eij = createElementInjector();
+ eij.spy('getComponent').andCallFake( () => componentInstance );
+ view.init(changeDetector, [eij], [eij],
+ [null], [null], [shadowView]);
+ return view;
+ }
+
+ describe('setDynamicComponentChildView', () => {
+
+ it('should not allow to use non component indices', () => {
+ var pv = createProtoView([createEmptyElBinder()]);
+ var view = createHostView(pv, null, null);
+ var shadowView = new FakeAppView();
+ expect(
+ () => view.setDynamicComponentChildView(0, shadowView)
+ ).toThrowError('There is no dynamic component directive at element 0');
+ });
+
+ it('should not allow to use static component indices', () => {
+ var pv = createHostProtoView(createProtoView());
+ var view = createHostView(pv, null, null);
+ var shadowView = new FakeAppView();
+ expect(
+ () => view.setDynamicComponentChildView(0, shadowView)
+ ).toThrowError('There is no dynamic component directive at element 0');
+ });
+
+ it('should not allow to overwrite an existing component', () => {
+ var pv = createHostProtoView(null);
+ var shadowView = new FakeAppView();
+ var view = createHostView(pv, null, null);
+ view.setDynamicComponentChildView(0, shadowView);
+ expect(
+ () => view.setDynamicComponentChildView(0, shadowView)
+ ).toThrowError('There already is a bound component at element 0');
+ });
+
+ });
+
+ describe('hydrate', () => {
+
+ it('should hydrate existing child components', () => {
+ var hostPv = createHostProtoView(createProtoView());
+ var componentInstance = {};
+ var shadowView = new FakeAppView();
+ var hostView = createHostView(hostPv, shadowView, componentInstance);
+ renderer.spy('createView').andCallFake( (_) => {
+ return [new ViewRef(), new ViewRef()];
+ });
+
+ hostView.hydrate(null, null, null, null);
+
+ expect(shadowView.spy('hydrate')).not.toHaveBeenCalled();
+ expect(shadowView.spy('internalHydrateRecurse')).toHaveBeenCalled();
+ });
+
+ });
+
+ describe('dehydrate', () => {
+ var hostView;
+ var shadowView;
+
+ function createAndHydrate(nestedProtoView) {
+ var componentInstance = {};
+ shadowView = new FakeAppView();
+ var hostPv = createHostProtoView(nestedProtoView);
+ hostView = createHostView(hostPv, shadowView, componentInstance);
+ renderer.spy('createView').andCallFake( (_) => {
+ return [new ViewRef(), new ViewRef()];
+ });
+
+ hostView.hydrate(null, null, null, null);
+ }
+
+ it('should dehydrate child components', () => {
+ createAndHydrate(createProtoView());
+ hostView.dehydrate();
+
+ expect(shadowView.spy('dehydrate')).not.toHaveBeenCalled();
+ expect(shadowView.spy('internalDehydrateRecurse')).toHaveBeenCalled();
+ });
+
+ it('should not clear static child components', () => {
+ createAndHydrate(createProtoView());
+ hostView.dehydrate();
+
+ expect(hostView.componentChildViews[0]).toBe(shadowView);
+ expect(hostView.changeDetector.spy('removeShadowDomChild')).not.toHaveBeenCalled();
+ });
+
+ it('should clear dynamic child components', () => {
+ createAndHydrate(null);
+ hostView.dehydrate();
+
+ expect(hostView.componentChildViews[0]).toBe(null);
+ expect(hostView.changeDetector.spy('removeShadowDomChild')).toHaveBeenCalledWith(shadowView.changeDetector);
+ });
+
+ });
+
+ });
+}
+
+@Component({ selector: 'someComponent' })
+class SomeComponent {}
+
+@proxy
+@IMPLEMENTS(Renderer)
+class SpyRenderer extends SpyObject {
+ constructor(){super(Renderer);}
+ noSuchMethod(m){return super.noSuchMethod(m)}
+}
+
+@proxy
+@IMPLEMENTS(ChangeDetector)
+class SpyChangeDetector extends SpyObject {
+ constructor(){super(ChangeDetector);}
+ noSuchMethod(m){return super.noSuchMethod(m)}
+}
+
+@proxy
+@IMPLEMENTS(ElementInjector)
+class SpyElementInjector extends SpyObject {
+ constructor(){super(ElementInjector);}
+ noSuchMethod(m){return super.noSuchMethod(m)}
+}
+
+@proxy
+@IMPLEMENTS(AppView)
+class FakeAppView extends SpyObject {
+ constructor(){super(AppView);}
+ noSuchMethod(m){return super.noSuchMethod(m)}
+}
diff --git a/modules/angular2/test/render/dom/view/view_factory_spec.js b/modules/angular2/test/render/dom/view/view_factory_spec.js
index e03b489fd9..3be319fed6 100644
--- a/modules/angular2/test/render/dom/view/view_factory_spec.js
+++ b/modules/angular2/test/render/dom/view/view_factory_spec.js
@@ -1,25 +1,69 @@
-import {describe, ddescribe, it, iit, xit, xdescribe, expect, beforeEach, el} from 'angular2/test_lib';
-
+import {
+ AsyncTestCompleter,
+ beforeEach,
+ ddescribe,
+ xdescribe,
+ describe,
+ el,
+ dispatchEvent,
+ expect,
+ iit,
+ inject,
+ beforeEachBindings,
+ it,
+ xit,
+ SpyObject, proxy
+} from 'angular2/test_lib';
+import {IMPLEMENTS, isBlank} from 'angular2/src/facade/lang';
+import {ListWrapper} from 'angular2/src/facade/collection';
import {ViewFactory} from 'angular2/src/render/dom/view/view_factory';
import {RenderProtoView} from 'angular2/src/render/dom/view/proto_view';
import {RenderView} from 'angular2/src/render/dom/view/view';
+import {ElementBinder} from 'angular2/src/render/dom/view/element_binder';
+import {ShadowDomStrategy} from 'angular2/src/render/dom/shadow_dom/shadow_dom_strategy';
+import {LightDom} from 'angular2/src/render/dom/shadow_dom/light_dom'
+import {EventManager} from 'angular2/src/render/dom/events/event_manager';
export function main() {
- function createViewFactory({capacity}):ViewFactory {
- return new ViewFactory(capacity, null, null);
- }
-
- function createPv() {
- return new RenderProtoView({
- element: el(''),
- isRootView: false,
- elementBinders: []
- });
- }
-
describe('RenderViewFactory', () => {
+ var eventManager;
+ var shadowDomStrategy;
+
+ function createViewFactory({capacity}):ViewFactory {
+ return new ViewFactory(capacity, eventManager, shadowDomStrategy);
+ }
+
+ function createProtoView(rootEl=null, binders=null) {
+ if (isBlank(rootEl)) {
+ rootEl = el('');
+ }
+ if (isBlank(binders)) {
+ binders = [];
+ }
+ return new RenderProtoView({
+ element: rootEl,
+ isRootView: false,
+ elementBinders: binders
+ });
+ }
+
+ function createComponentElBinder(componentId, nestedProtoView = null) {
+ var binder = new ElementBinder({
+ componentId: componentId,
+ textNodeIndices: []
+ });
+ binder.nestedProtoView = nestedProtoView;
+ return binder;
+ }
+
+
+ beforeEach( () => {
+ eventManager = new SpyEventManager();
+ shadowDomStrategy = new SpyShadowDomStrategy();
+ });
+
it('should create views', () => {
- var pv = createPv();
+ var pv = createProtoView();
var vf = createViewFactory({
capacity: 1
});
@@ -29,8 +73,8 @@ export function main() {
describe('caching', () => {
it('should support multiple RenderProtoViews', () => {
- var pv1 = createPv();
- var pv2 = createPv();
+ var pv1 = createProtoView();
+ var pv2 = createProtoView();
var vf = createViewFactory({ capacity: 2 });
var view1 = vf.getView(pv1);
var view2 = vf.getView(pv2);
@@ -42,7 +86,7 @@ export function main() {
});
it('should reuse the newest view that has been returned', () => {
- var pv = createPv();
+ var pv = createProtoView();
var vf = createViewFactory({ capacity: 2 });
var view1 = vf.getView(pv);
var view2 = vf.getView(pv);
@@ -53,7 +97,7 @@ export function main() {
});
it('should not add views when the capacity has been reached', () => {
- var pv = createPv();
+ var pv = createProtoView();
var vf = createViewFactory({ capacity: 2 });
var view1 = vf.getView(pv);
var view2 = vf.getView(pv);
@@ -68,5 +112,73 @@ export function main() {
});
+ describe('child components', () => {
+
+ var vf, log;
+
+ beforeEach(() => {
+ vf = createViewFactory({capacity: 1});
+ log = [];
+ shadowDomStrategy.spy('attachTemplate').andCallFake( (el, view) => {
+ ListWrapper.push(log, ['attachTemplate', el, view]);
+ });
+ shadowDomStrategy.spy('constructLightDom').andCallFake( (lightDomView, shadowDomView, el) => {
+ ListWrapper.push(log, ['constructLightDom', lightDomView, shadowDomView, el]);
+ return new SpyLightDom();
+ });
+ });
+
+ it('should create static child component views', () => {
+ var hostPv = createProtoView(el(''), [
+ createComponentElBinder(
+ 'someComponent',
+ createProtoView()
+ )
+ ]);
+ var hostView = vf.getView(hostPv);
+ var shadowView = hostView.componentChildViews[0];
+ expect(shadowView).toBeTruthy();
+ expect(hostView.lightDoms[0]).toBeTruthy();
+ expect(log[0]).toEqual(['constructLightDom', hostView, shadowView, hostView.boundElements[0]]);
+ expect(log[1]).toEqual(['attachTemplate', hostView.boundElements[0], shadowView]);
+ });
+
+ it('should not create dynamic child component views', () => {
+ var hostPv = createProtoView(el(''), [
+ createComponentElBinder(
+ 'someComponent',
+ null
+ )
+ ]);
+ var hostView = vf.getView(hostPv);
+ var shadowView = hostView.componentChildViews[0];
+ expect(shadowView).toBeFalsy();
+ expect(hostView.lightDoms[0]).toBeFalsy();
+ expect(log).toEqual([]);
+ });
+
+ });
+
});
}
+
+@proxy
+@IMPLEMENTS(EventManager)
+class SpyEventManager extends SpyObject {
+ constructor(){super(EventManager);}
+ noSuchMethod(m){return super.noSuchMethod(m)}
+}
+
+@proxy
+@IMPLEMENTS(ShadowDomStrategy)
+class SpyShadowDomStrategy extends SpyObject {
+ constructor(){super(ShadowDomStrategy);}
+ noSuchMethod(m){return super.noSuchMethod(m)}
+}
+
+@proxy
+@IMPLEMENTS(LightDom)
+class SpyLightDom extends SpyObject {
+ constructor(){super(LightDom);}
+ noSuchMethod(m){return super.noSuchMethod(m)}
+}
diff --git a/modules/angular2/test/render/dom/view/view_spec.js b/modules/angular2/test/render/dom/view/view_spec.js
index e7e2399cc7..191eb11d0f 100644
--- a/modules/angular2/test/render/dom/view/view_spec.js
+++ b/modules/angular2/test/render/dom/view/view_spec.js
@@ -1,53 +1,161 @@
-import {describe, ddescribe, it, iit, xit, xdescribe, expect, beforeEach, el} from 'angular2/test_lib';
-
-import {ListWrapper} from 'angular2/src/facade/collection';
+import {
+ AsyncTestCompleter,
+ beforeEach,
+ ddescribe,
+ xdescribe,
+ describe,
+ el,
+ dispatchEvent,
+ expect,
+ iit,
+ inject,
+ beforeEachBindings,
+ it,
+ xit,
+ SpyObject, proxy
+} from 'angular2/test_lib';
+import {IMPLEMENTS, isBlank} from 'angular2/src/facade/lang';
import {RenderProtoView} from 'angular2/src/render/dom/view/proto_view';
+import {ElementBinder} from 'angular2/src/render/dom/view/element_binder';
import {RenderView} from 'angular2/src/render/dom/view/view';
import {ShadowDomStrategy} from 'angular2/src/render/dom/shadow_dom/shadow_dom_strategy';
import {LightDom} from 'angular2/src/render/dom/shadow_dom/light_dom';
+import {EventManager} from 'angular2/src/render/dom/events/event_manager';
+import {DOM} from 'angular2/src/dom/dom_adapter';
export function main() {
- function createView() {
- var proto = new RenderProtoView({element: el(''), isRootView: false, elementBinders: []});
- var rootNodes = [el('')];
- var boundTextNodes = [];
- var boundElements = [el('')];
- var viewContainers = [];
- var contentTags = [];
- var eventManager = null;
- return new RenderView(proto, rootNodes,
- boundTextNodes, boundElements, viewContainers, contentTags, eventManager);
- }
-
- function createShadowDomStrategy(log) {
- return new FakeShadowDomStrategy(log);
- }
-
describe('RenderView', () => {
- var log, strategy;
+ var shadowDomStrategy;
+ var eventManager;
+
+ function createProtoView({rootEl, binders}={}) {
+ if (isBlank(rootEl)) {
+ rootEl = el('');
+ }
+ if (isBlank(binders)) {
+ binders = [];
+ }
+ return new RenderProtoView({
+ element: rootEl,
+ isRootView: false,
+ elementBinders: binders
+ });
+ }
+
+ function createComponentElBinder(componentId, nestedProtoView = null) {
+ var binder = new ElementBinder({
+ componentId: componentId,
+ textNodeIndices: []
+ });
+ binder.nestedProtoView = nestedProtoView;
+ return binder;
+ }
+
+ function createHostProtoView(nestedProtoView) {
+ return createProtoView({
+ binders: [
+ createComponentElBinder(
+ 'someComponent',
+ nestedProtoView
+ )
+ ]
+ });
+ }
+
+ function createEmptyView() {
+ var root = el('');
+ return new RenderView(createProtoView(), [DOM.childNodes(root)[0]],
+ [], [], [], [], eventManager);
+ }
+
+ function createHostView(pv, shadowDomView) {
+ var view = new RenderView(pv, [el('')],
+ [], [el('')], [], [], eventManager);
+ view.setComponentView(shadowDomStrategy, 0, shadowDomView);
+ return view;
+ }
beforeEach( () => {
- log = [];
- strategy = createShadowDomStrategy(log);
+ eventManager = new SpyEventManager();
+ shadowDomStrategy = new SpyShadowDomStrategy();
+ shadowDomStrategy.spy('constructLightDom').andCallFake( (lightDomView, shadowDomView, el) => {
+ return new SpyLightDom();
+ });
});
describe('setComponentView', () => {
it('should redistribute when a component is added to a hydrated view', () => {
- var hostView = createView();
- var childView = createView();
+ var shadowView = new SpyRenderView();
+ var hostPv = createHostProtoView(createProtoView());
+ var hostView = createHostView(hostPv, shadowView);
hostView.hydrate(null);
- hostView.setComponentView(strategy, 0, childView);
- expect(log[0]).toEqual(['redistribute']);
+ hostView.setComponentView(shadowDomStrategy, 0, shadowView);
+ var lightDomSpy:SpyLightDom = hostView.lightDoms[0];
+ expect(lightDomSpy.spy('redistribute')).toHaveBeenCalled();
});
it('should not redistribute when a component is added to a dehydrated view', () => {
- var hostView = createView();
- var childView = createView();
- hostView.setComponentView(strategy, 0, childView);
- expect(log).toEqual([]);
+ var shadowView = new SpyRenderView();
+ var hostPv = createHostProtoView(createProtoView());
+ var hostView = createHostView(hostPv, shadowView);
+ hostView.setComponentView(shadowDomStrategy, 0, shadowView);
+ var lightDomSpy:SpyLightDom = hostView.lightDoms[0];
+ expect(lightDomSpy.spy('redistribute')).not.toHaveBeenCalled();
+ });
+
+ });
+
+ describe('hydrate', () => {
+
+ it('should hydrate existing child components', () => {
+ var hostPv = createHostProtoView(createProtoView());
+ var shadowView = new SpyRenderView();
+ var hostView = createHostView(hostPv, shadowView);
+
+ hostView.hydrate(null);
+
+ expect(shadowView.spy('hydrate')).toHaveBeenCalled();
+ });
+
+ });
+
+ describe('dehydrate', () => {
+ var hostView;
+
+ function createAndHydrate(nestedProtoView, shadowView) {
+ var hostPv = createHostProtoView(nestedProtoView);
+ hostView = createHostView(hostPv, shadowView);
+
+ hostView.hydrate(null);
+ }
+
+ it('should dehydrate child components', () => {
+ var shadowView = new SpyRenderView();
+ createAndHydrate(createProtoView(), shadowView);
+ hostView.dehydrate();
+
+ expect(shadowView.spy('dehydrate')).toHaveBeenCalled();
+ });
+
+ it('should not clear static child components', () => {
+ var shadowView = createEmptyView();
+ createAndHydrate(createProtoView(), shadowView);
+ hostView.dehydrate();
+
+ expect(hostView.componentChildViews[0]).toBe(shadowView);
+ expect(shadowView.rootNodes[0].parentNode).toBeTruthy();
+ });
+
+ it('should clear dynamic child components', () => {
+ var shadowView = createEmptyView();
+ createAndHydrate(null, shadowView);
+ hostView.dehydrate();
+
+ expect(hostView.componentChildViews[0]).toBe(null);
+ expect(shadowView.rootNodes[0].parentNode).toBe(null);
});
});
@@ -55,24 +163,31 @@ export function main() {
});
}
-class FakeShadowDomStrategy extends ShadowDomStrategy {
- log;
- constructor(log) {
- super();
- this.log = log;
- }
- constructLightDom(lightDomView:RenderView, shadowDomView:RenderView, element): LightDom {
- return new FakeLightDom(this.log, lightDomView, shadowDomView, element);
- }
+@proxy
+@IMPLEMENTS(EventManager)
+class SpyEventManager extends SpyObject {
+ constructor(){super(EventManager);}
+ noSuchMethod(m){return super.noSuchMethod(m)}
+}
+
+@proxy
+@IMPLEMENTS(ShadowDomStrategy)
+class SpyShadowDomStrategy extends SpyObject {
+ constructor(){super(ShadowDomStrategy);}
+ noSuchMethod(m){return super.noSuchMethod(m)}
+}
+
+@proxy
+@IMPLEMENTS(LightDom)
+class SpyLightDom extends SpyObject {
+ constructor(){super(LightDom);}
+ noSuchMethod(m){return super.noSuchMethod(m)}
+}
+
+@proxy
+@IMPLEMENTS(RenderView)
+class SpyRenderView extends SpyObject {
+ constructor(){super(RenderView);}
+ noSuchMethod(m){return super.noSuchMethod(m)}
}
-class FakeLightDom extends LightDom {
- log;
- constructor(log, lightDomView:RenderView, shadowDomView:RenderView, element) {
- super(lightDomView, shadowDomView, element);
- this.log = log;
- }
- redistribute() {
- ListWrapper.push(this.log, ['redistribute']);
- }
-}
\ No newline at end of file
diff --git a/modules/angular2/test/test_lib/test_lib_spec.js b/modules/angular2/test/test_lib/test_lib_spec.js
index 9dfc8992d7..904a7c5d12 100644
--- a/modules/angular2/test/test_lib/test_lib_spec.js
+++ b/modules/angular2/test/test_lib/test_lib_spec.js
@@ -81,7 +81,7 @@ export function main() {
});
it('should create spys for all methods', () => {
- expect(spyObj.someFunc).toBeTruthy();
+ expect(() => spyObj.someFunc()).not.toThrow();
});
it('should create a default spy that does not fail for numbers', () => {