fix(view): remove dynamic components when the parent view is dehydrated
Also adds a bunch of unit tests for affected parts. Fixes #1201
This commit is contained in:
parent
6ecaa9aebb
commit
213dabdceb
|
@ -49,6 +49,7 @@ export class ChangeDetector {
|
||||||
addChild(cd:ChangeDetector) {}
|
addChild(cd:ChangeDetector) {}
|
||||||
addShadowDomChild(cd:ChangeDetector) {}
|
addShadowDomChild(cd:ChangeDetector) {}
|
||||||
removeChild(cd:ChangeDetector) {}
|
removeChild(cd:ChangeDetector) {}
|
||||||
|
removeShadowDomChild(cd:ChangeDetector) {}
|
||||||
remove() {}
|
remove() {}
|
||||||
hydrate(context:any, locals:Locals, directives:any) {}
|
hydrate(context:any, locals:Locals, directives:any) {}
|
||||||
dehydrate() {}
|
dehydrate() {}
|
||||||
|
|
|
@ -43,14 +43,12 @@ export class ComponentRef {
|
||||||
export class DynamicComponentLoader {
|
export class DynamicComponentLoader {
|
||||||
_compiler:Compiler;
|
_compiler:Compiler;
|
||||||
_viewFactory:ViewFactory;
|
_viewFactory:ViewFactory;
|
||||||
_renderer:Renderer;
|
|
||||||
_directiveMetadataReader:DirectiveMetadataReader;
|
_directiveMetadataReader:DirectiveMetadataReader;
|
||||||
|
|
||||||
constructor(compiler:Compiler, directiveMetadataReader:DirectiveMetadataReader,
|
constructor(compiler:Compiler, directiveMetadataReader:DirectiveMetadataReader,
|
||||||
renderer:Renderer, viewFactory:ViewFactory) {
|
renderer:Renderer, viewFactory:ViewFactory) {
|
||||||
this._compiler = compiler;
|
this._compiler = compiler;
|
||||||
this._directiveMetadataReader = directiveMetadataReader;
|
this._directiveMetadataReader = directiveMetadataReader;
|
||||||
this._renderer = renderer;
|
|
||||||
this._viewFactory = viewFactory
|
this._viewFactory = viewFactory
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,16 +65,13 @@ export class DynamicComponentLoader {
|
||||||
|
|
||||||
var hostEi = location.elementInjector;
|
var hostEi = location.elementInjector;
|
||||||
var hostView = location.hostView;
|
var hostView = location.hostView;
|
||||||
|
|
||||||
return this._compiler.compile(type).then(componentProtoView => {
|
return this._compiler.compile(type).then(componentProtoView => {
|
||||||
var component = hostEi.dynamicallyCreateComponent(type, directiveMetadata.annotation, inj);
|
var component = hostEi.dynamicallyCreateComponent(type, directiveMetadata.annotation, inj);
|
||||||
var componentView = this._instantiateAndHydrateView(componentProtoView, injector, hostEi, component);
|
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
|
//TODO(vsavkin): do not use component child views as we need to clear the dynamically created views
|
||||||
//same problem exists on the render side
|
//same problem exists on the render side
|
||||||
hostView.addComponentChildView(componentView);
|
hostView.setDynamicComponentChildView(location.boundElementIndex, componentView);
|
||||||
|
|
||||||
this._renderer.setDynamicComponentView(hostView.render, location.boundElementIndex, componentView.render);
|
|
||||||
|
|
||||||
// TODO(vsavkin): return a component ref that dehydrates the component view and removes it
|
// TODO(vsavkin): return a component ref that dehydrates the component view and removes it
|
||||||
// from the component child views
|
// from the component child views
|
||||||
|
|
|
@ -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 * as eiModule from './element_injector';
|
||||||
import {DirectiveBinding} from './element_injector';
|
import {DirectiveBinding} from './element_injector';
|
||||||
import {List, StringMap} from 'angular2/src/facade/collection';
|
import {List, StringMap} from 'angular2/src/facade/collection';
|
||||||
|
@ -32,4 +32,12 @@ export class ElementBinder {
|
||||||
// updated later, so we are able to resolve cycles
|
// updated later, so we are able to resolve cycles
|
||||||
this.nestedProtoView = null;
|
this.nestedProtoView = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hasStaticComponent() {
|
||||||
|
return isPresent(this.componentDirective) && isPresent(this.nestedProtoView);
|
||||||
|
}
|
||||||
|
|
||||||
|
hasDynamicComponent() {
|
||||||
|
return isPresent(this.componentDirective) && isBlank(this.nestedProtoView);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -143,7 +143,6 @@ export class AppView {
|
||||||
}
|
}
|
||||||
|
|
||||||
var binders = this.proto.elementBinders;
|
var binders = this.proto.elementBinders;
|
||||||
var componentChildViewIndex = 0;
|
|
||||||
for (var i = 0; i < binders.length; ++i) {
|
for (var i = 0; i < binders.length; ++i) {
|
||||||
var componentDirective = binders[i].componentDirective;
|
var componentDirective = binders[i].componentDirective;
|
||||||
var shadowDomAppInjector = null;
|
var shadowDomAppInjector = null;
|
||||||
|
@ -176,8 +175,8 @@ export class AppView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isPresent(binders[i].nestedProtoView) && isPresent(componentDirective)) {
|
if (binders[i].hasStaticComponent()) {
|
||||||
renderComponentIndex = this.componentChildViews[componentChildViewIndex].internalHydrateRecurse(
|
renderComponentIndex = this.componentChildViews[i].internalHydrateRecurse(
|
||||||
renderComponentViewRefs,
|
renderComponentViewRefs,
|
||||||
renderComponentIndex,
|
renderComponentIndex,
|
||||||
shadowDomAppInjector,
|
shadowDomAppInjector,
|
||||||
|
@ -185,7 +184,6 @@ export class AppView {
|
||||||
elementInjector.getComponent(),
|
elementInjector.getComponent(),
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
componentChildViewIndex++;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this._hydrateChangeDetector();
|
this._hydrateChangeDetector();
|
||||||
|
@ -198,7 +196,15 @@ export class AppView {
|
||||||
|
|
||||||
// componentChildViews
|
// componentChildViews
|
||||||
for (var i = 0; i < this.componentChildViews.length; i++) {
|
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
|
// elementInjectors
|
||||||
|
@ -255,9 +261,16 @@ export class AppView {
|
||||||
return elementInjector.getDirectiveAtIndex(directive.directiveIndex);
|
return elementInjector.getDirectiveAtIndex(directive.directiveIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
addComponentChildView(view:AppView) {
|
setDynamicComponentChildView(boundElementIndex, view:AppView) {
|
||||||
ListWrapper.push(this.componentChildViews, view);
|
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.changeDetector.addShadowDomChild(view.changeDetector);
|
||||||
|
this.proto.renderer.setDynamicComponentView(this.render, boundElementIndex, view.render);
|
||||||
}
|
}
|
||||||
|
|
||||||
// implementation of EventDispatcher#dispatchEvent
|
// implementation of EventDispatcher#dispatchEvent
|
||||||
|
|
|
@ -57,7 +57,7 @@ export class ViewFactory {
|
||||||
var rootElementInjectors = [];
|
var rootElementInjectors = [];
|
||||||
var preBuiltObjects = ListWrapper.createFixedSize(binders.length);
|
var preBuiltObjects = ListWrapper.createFixedSize(binders.length);
|
||||||
var viewContainers = 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++) {
|
for (var binderIdx = 0; binderIdx < binders.length; binderIdx++) {
|
||||||
var binder = binders[binderIdx];
|
var binder = binders[binderIdx];
|
||||||
|
@ -78,13 +78,13 @@ export class ViewFactory {
|
||||||
|
|
||||||
// componentChildViews
|
// componentChildViews
|
||||||
var bindingPropagationConfig = null;
|
var bindingPropagationConfig = null;
|
||||||
if (isPresent(binder.nestedProtoView) && isPresent(binder.componentDirective)) {
|
if (binder.hasStaticComponent()) {
|
||||||
var childView = this._createView(binder.nestedProtoView);
|
var childView = this._createView(binder.nestedProtoView);
|
||||||
changeDetector.addChild(childView.changeDetector);
|
changeDetector.addShadowDomChild(childView.changeDetector);
|
||||||
|
|
||||||
bindingPropagationConfig = new BindingPropagationConfig(childView.changeDetector);
|
bindingPropagationConfig = new BindingPropagationConfig(childView.changeDetector);
|
||||||
|
|
||||||
ListWrapper.push(componentChildViews, childView);
|
componentChildViews[binderIdx] = childView;
|
||||||
}
|
}
|
||||||
|
|
||||||
// viewContainers
|
// viewContainers
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import {isBlank, isPresent} from 'angular2/src/facade/lang';
|
||||||
import {AST} from 'angular2/change_detection';
|
import {AST} from 'angular2/change_detection';
|
||||||
import {SetterFn} from 'angular2/src/reflection/types';
|
import {SetterFn} from 'angular2/src/reflection/types';
|
||||||
import {List, ListWrapper} from 'angular2/src/facade/collection';
|
import {List, ListWrapper} from 'angular2/src/facade/collection';
|
||||||
|
@ -38,6 +39,14 @@ export class ElementBinder {
|
||||||
this.distanceToParent = distanceToParent;
|
this.distanceToParent = distanceToParent;
|
||||||
this.propertySetters = propertySetters;
|
this.propertySetters = propertySetters;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hasStaticComponent() {
|
||||||
|
return isPresent(this.componentId) && isPresent(this.nestedProtoView);
|
||||||
|
}
|
||||||
|
|
||||||
|
hasDynamicComponent() {
|
||||||
|
return isPresent(this.componentId) && isBlank(this.nestedProtoView);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Event {
|
export class Event {
|
||||||
|
|
|
@ -165,6 +165,11 @@ export class RenderView {
|
||||||
var cv = this.componentChildViews[i];
|
var cv = this.componentChildViews[i];
|
||||||
if (isPresent(cv)) {
|
if (isPresent(cv)) {
|
||||||
cv.dehydrate();
|
cv.dehydrate();
|
||||||
|
if (this.proto.elementBinders[i].hasDynamicComponent()) {
|
||||||
|
ViewContainer.removeViewNodes(cv);
|
||||||
|
this.lightDoms[i] = null;
|
||||||
|
this.componentChildViews[i] = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -84,7 +84,6 @@ export class ViewFactory {
|
||||||
} else {
|
} else {
|
||||||
viewRootNodes = [rootElementClone];
|
viewRootNodes = [rootElementClone];
|
||||||
}
|
}
|
||||||
|
|
||||||
var binders = protoView.elementBinders;
|
var binders = protoView.elementBinders;
|
||||||
var boundTextNodes = [];
|
var boundTextNodes = [];
|
||||||
var boundElements = ListWrapper.createFixedSize(binders.length);
|
var boundElements = ListWrapper.createFixedSize(binders.length);
|
||||||
|
@ -133,7 +132,7 @@ export class ViewFactory {
|
||||||
var element = boundElements[binderIdx];
|
var element = boundElements[binderIdx];
|
||||||
|
|
||||||
// static child components
|
// static child components
|
||||||
if (isPresent(binder.componentId) && isPresent(binder.nestedProtoView)) {
|
if (binder.hasStaticComponent()) {
|
||||||
var childView = this._createView(binder.nestedProtoView);
|
var childView = this._createView(binder.nestedProtoView);
|
||||||
view.setComponentView(this._shadowDomStrategy, binderIdx, childView);
|
view.setComponentView(this._shadowDomStrategy, binderIdx, childView);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
library test_lib.test_lib;
|
library test_lib.test_lib;
|
||||||
|
|
||||||
import 'package:guinness/guinness.dart' as gns;
|
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 'package:unittest/unittest.dart' hide expect;
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
@ -149,6 +149,13 @@ xit(name, fn) {
|
||||||
_it(gns.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) {
|
String elementText(n) {
|
||||||
hasNodes(n) {
|
hasNodes(n) {
|
||||||
var children = DOM.childNodes(n);
|
var children = DOM.childNodes(n);
|
||||||
|
|
|
@ -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 {DirectiveMetadataReader} from 'angular2/src/core/compiler/directive_metadata_reader';
|
||||||
import {DynamicComponentLoader} from 'angular2/src/core/compiler/dynamic_component_loader';
|
import {DynamicComponentLoader} from 'angular2/src/core/compiler/dynamic_component_loader';
|
||||||
import {Decorator, Viewport} from 'angular2/src/core/annotations/annotations';
|
import {Decorator, Viewport, Component} from 'angular2/src/core/annotations/annotations';
|
||||||
|
import {ElementRef, ElementInjector, ProtoElementInjector, PreBuiltObjects} from 'angular2/src/core/compiler/element_injector';
|
||||||
@Decorator({selector: 'someDecorator'})
|
import {Compiler} from 'angular2/src/core/compiler/compiler';
|
||||||
class SomeDecorator {}
|
import {AppProtoView, AppView} from 'angular2/src/core/compiler/view';
|
||||||
|
import {ViewFactory} from 'angular2/src/core/compiler/view_factory'
|
||||||
@Viewport({selector: 'someViewport'})
|
import {Renderer} from 'angular2/src/render/api';
|
||||||
class SomeViewport {}
|
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
describe("DynamicComponentLoader", () => {
|
describe("DynamicComponentLoader", () => {
|
||||||
|
var compiler;
|
||||||
|
var viewFactory;
|
||||||
|
var directiveMetadataReader;
|
||||||
|
var renderer;
|
||||||
var loader;
|
var loader;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach( () => {
|
||||||
loader = new DynamicComponentLoader(null, new DirectiveMetadataReader(), null, null);
|
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("loadIntoExistingLocation", () => {
|
||||||
describe('Load errors', () => {
|
describe('Load errors', () => {
|
||||||
it('should throw when trying to load a decorator', () => {
|
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.");
|
.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)}}
|
||||||
|
|
|
@ -567,7 +567,7 @@ export function main() {
|
||||||
async.done();
|
async.done();
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should support render global events from multiple directives', inject([TestBed, AsyncTestCompleter], (tb, async) => {
|
it('should support render global events from multiple directives', inject([TestBed, AsyncTestCompleter], (tb, async) => {
|
||||||
tb.overrideView(MyComp, new View({
|
tb.overrideView(MyComp, new View({
|
||||||
template: '<div *if="ctxBoolProp" listener listenerother></div>',
|
template: '<div *if="ctxBoolProp" listener listenerother></div>',
|
||||||
|
@ -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: '<div><dynamic-comp #dynamic template="if: ctxBoolProp"></dynamic-comp></div>',
|
||||||
|
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) => {
|
it('should support static attributes', inject([TestBed, AsyncTestCompleter], (tb, async) => {
|
||||||
|
|
|
@ -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 {ViewFactory} from 'angular2/src/core/compiler/view_factory';
|
||||||
import {AppProtoView, AppView} from 'angular2/src/core/compiler/view';
|
import {AppProtoView, AppView} from 'angular2/src/core/compiler/view';
|
||||||
import {dynamicChangeDetection} from 'angular2/change_detection';
|
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() {
|
export function main() {
|
||||||
function createViewFactory({capacity}):ViewFactory {
|
describe('AppViewFactory', () => {
|
||||||
return new ViewFactory(capacity);
|
var reader;
|
||||||
}
|
|
||||||
|
|
||||||
function createPv() {
|
beforeEach( () => {
|
||||||
return new AppProtoView(null,
|
reader = new DirectiveMetadataReader();
|
||||||
null,
|
});
|
||||||
dynamicChangeDetection.createProtoChangeDetector('dummy', null));
|
|
||||||
}
|
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', () => {
|
it('should create views', () => {
|
||||||
var pv = createPv();
|
var pv = createProtoView();
|
||||||
var vf = createViewFactory({
|
var vf = createViewFactory({
|
||||||
capacity: 1
|
capacity: 1
|
||||||
});
|
});
|
||||||
|
@ -27,8 +75,8 @@ export function main() {
|
||||||
describe('caching', () => {
|
describe('caching', () => {
|
||||||
|
|
||||||
it('should support multiple AppProtoViews', () => {
|
it('should support multiple AppProtoViews', () => {
|
||||||
var pv1 = createPv();
|
var pv1 = createProtoView();
|
||||||
var pv2 = createPv();
|
var pv2 = createProtoView();
|
||||||
var vf = createViewFactory({ capacity: 2 });
|
var vf = createViewFactory({ capacity: 2 });
|
||||||
var view1 = vf.getView(pv1);
|
var view1 = vf.getView(pv1);
|
||||||
var view2 = vf.getView(pv2);
|
var view2 = vf.getView(pv2);
|
||||||
|
@ -40,7 +88,7 @@ export function main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should reuse the newest view that has been returned', () => {
|
it('should reuse the newest view that has been returned', () => {
|
||||||
var pv = createPv();
|
var pv = createProtoView();
|
||||||
var vf = createViewFactory({ capacity: 2 });
|
var vf = createViewFactory({ capacity: 2 });
|
||||||
var view1 = vf.getView(pv);
|
var view1 = vf.getView(pv);
|
||||||
var view2 = 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', () => {
|
it('should not add views when the capacity has been reached', () => {
|
||||||
var pv = createPv();
|
var pv = createProtoView();
|
||||||
var vf = createViewFactory({ capacity: 2 });
|
var vf = createViewFactory({ capacity: 2 });
|
||||||
var view1 = vf.getView(pv);
|
var view1 = vf.getView(pv);
|
||||||
var view2 = 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)}
|
||||||
|
}
|
||||||
|
|
|
@ -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)}
|
||||||
|
}
|
|
@ -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 {ViewFactory} from 'angular2/src/render/dom/view/view_factory';
|
||||||
import {RenderProtoView} from 'angular2/src/render/dom/view/proto_view';
|
import {RenderProtoView} from 'angular2/src/render/dom/view/proto_view';
|
||||||
import {RenderView} from 'angular2/src/render/dom/view/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() {
|
export function main() {
|
||||||
function createViewFactory({capacity}):ViewFactory {
|
|
||||||
return new ViewFactory(capacity, null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
function createPv() {
|
|
||||||
return new RenderProtoView({
|
|
||||||
element: el('<div></div>'),
|
|
||||||
isRootView: false,
|
|
||||||
elementBinders: []
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('RenderViewFactory', () => {
|
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('<div></div>');
|
||||||
|
}
|
||||||
|
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', () => {
|
it('should create views', () => {
|
||||||
var pv = createPv();
|
var pv = createProtoView();
|
||||||
var vf = createViewFactory({
|
var vf = createViewFactory({
|
||||||
capacity: 1
|
capacity: 1
|
||||||
});
|
});
|
||||||
|
@ -29,8 +73,8 @@ export function main() {
|
||||||
describe('caching', () => {
|
describe('caching', () => {
|
||||||
|
|
||||||
it('should support multiple RenderProtoViews', () => {
|
it('should support multiple RenderProtoViews', () => {
|
||||||
var pv1 = createPv();
|
var pv1 = createProtoView();
|
||||||
var pv2 = createPv();
|
var pv2 = createProtoView();
|
||||||
var vf = createViewFactory({ capacity: 2 });
|
var vf = createViewFactory({ capacity: 2 });
|
||||||
var view1 = vf.getView(pv1);
|
var view1 = vf.getView(pv1);
|
||||||
var view2 = vf.getView(pv2);
|
var view2 = vf.getView(pv2);
|
||||||
|
@ -42,7 +86,7 @@ export function main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should reuse the newest view that has been returned', () => {
|
it('should reuse the newest view that has been returned', () => {
|
||||||
var pv = createPv();
|
var pv = createProtoView();
|
||||||
var vf = createViewFactory({ capacity: 2 });
|
var vf = createViewFactory({ capacity: 2 });
|
||||||
var view1 = vf.getView(pv);
|
var view1 = vf.getView(pv);
|
||||||
var view2 = 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', () => {
|
it('should not add views when the capacity has been reached', () => {
|
||||||
var pv = createPv();
|
var pv = createProtoView();
|
||||||
var vf = createViewFactory({ capacity: 2 });
|
var vf = createViewFactory({ capacity: 2 });
|
||||||
var view1 = vf.getView(pv);
|
var view1 = vf.getView(pv);
|
||||||
var view2 = 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('<div><div class="ng-binding"></div></div>'), [
|
||||||
|
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('<div><div class="ng-binding"></div></div>'), [
|
||||||
|
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)}
|
||||||
|
}
|
||||||
|
|
|
@ -1,53 +1,161 @@
|
||||||
import {describe, ddescribe, it, iit, xit, xdescribe, expect, beforeEach, el} from 'angular2/test_lib';
|
import {
|
||||||
|
AsyncTestCompleter,
|
||||||
import {ListWrapper} from 'angular2/src/facade/collection';
|
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 {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 {RenderView} from 'angular2/src/render/dom/view/view';
|
||||||
import {ShadowDomStrategy} from 'angular2/src/render/dom/shadow_dom/shadow_dom_strategy';
|
import {ShadowDomStrategy} from 'angular2/src/render/dom/shadow_dom/shadow_dom_strategy';
|
||||||
import {LightDom} from 'angular2/src/render/dom/shadow_dom/light_dom';
|
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() {
|
export function main() {
|
||||||
|
|
||||||
function createView() {
|
|
||||||
var proto = new RenderProtoView({element: el('<div></div>'), isRootView: false, elementBinders: []});
|
|
||||||
var rootNodes = [el('<div></div>')];
|
|
||||||
var boundTextNodes = [];
|
|
||||||
var boundElements = [el('<div></div>')];
|
|
||||||
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', () => {
|
describe('RenderView', () => {
|
||||||
var log, strategy;
|
var shadowDomStrategy;
|
||||||
|
var eventManager;
|
||||||
|
|
||||||
|
function createProtoView({rootEl, binders}={}) {
|
||||||
|
if (isBlank(rootEl)) {
|
||||||
|
rootEl = el('<div></div>');
|
||||||
|
}
|
||||||
|
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('<div><div></div></div>');
|
||||||
|
return new RenderView(createProtoView(), [DOM.childNodes(root)[0]],
|
||||||
|
[], [], [], [], eventManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createHostView(pv, shadowDomView) {
|
||||||
|
var view = new RenderView(pv, [el('<div></div>')],
|
||||||
|
[], [el('<div></div>')], [], [], eventManager);
|
||||||
|
view.setComponentView(shadowDomStrategy, 0, shadowDomView);
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
beforeEach( () => {
|
beforeEach( () => {
|
||||||
log = [];
|
eventManager = new SpyEventManager();
|
||||||
strategy = createShadowDomStrategy(log);
|
shadowDomStrategy = new SpyShadowDomStrategy();
|
||||||
|
shadowDomStrategy.spy('constructLightDom').andCallFake( (lightDomView, shadowDomView, el) => {
|
||||||
|
return new SpyLightDom();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('setComponentView', () => {
|
describe('setComponentView', () => {
|
||||||
|
|
||||||
it('should redistribute when a component is added to a hydrated view', () => {
|
it('should redistribute when a component is added to a hydrated view', () => {
|
||||||
var hostView = createView();
|
var shadowView = new SpyRenderView();
|
||||||
var childView = createView();
|
var hostPv = createHostProtoView(createProtoView());
|
||||||
|
var hostView = createHostView(hostPv, shadowView);
|
||||||
hostView.hydrate(null);
|
hostView.hydrate(null);
|
||||||
hostView.setComponentView(strategy, 0, childView);
|
hostView.setComponentView(shadowDomStrategy, 0, shadowView);
|
||||||
expect(log[0]).toEqual(['redistribute']);
|
var lightDomSpy:SpyLightDom = hostView.lightDoms[0];
|
||||||
|
expect(lightDomSpy.spy('redistribute')).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not redistribute when a component is added to a dehydrated view', () => {
|
it('should not redistribute when a component is added to a dehydrated view', () => {
|
||||||
var hostView = createView();
|
var shadowView = new SpyRenderView();
|
||||||
var childView = createView();
|
var hostPv = createHostProtoView(createProtoView());
|
||||||
hostView.setComponentView(strategy, 0, childView);
|
var hostView = createHostView(hostPv, shadowView);
|
||||||
expect(log).toEqual([]);
|
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 {
|
@proxy
|
||||||
log;
|
@IMPLEMENTS(EventManager)
|
||||||
constructor(log) {
|
class SpyEventManager extends SpyObject {
|
||||||
super();
|
constructor(){super(EventManager);}
|
||||||
this.log = log;
|
noSuchMethod(m){return super.noSuchMethod(m)}
|
||||||
}
|
}
|
||||||
constructLightDom(lightDomView:RenderView, shadowDomView:RenderView, element): LightDom {
|
|
||||||
return new FakeLightDom(this.log, lightDomView, shadowDomView, element);
|
@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']);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -81,7 +81,7 @@ export function main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create spys for all methods', () => {
|
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', () => {
|
it('should create a default spy that does not fail for numbers', () => {
|
||||||
|
|
Loading…
Reference in New Issue