feat(compiler): attach components and project light dom during compilation.

Closes #2529

BREAKING CHANGES:
- shadow dom emulation no longer
  supports the `<content>` tag. Use the new `<ng-content>` instead
  (works with all shadow dom strategies).
- removed `DomRenderer.setViewRootNodes` and `AppViewManager.getComponentView`
  -> use `DomRenderer.getNativeElementSync(elementRef)` and change shadow dom directly
- the `Renderer` interface has changed:
  * `createView` now also has to support sub views
  * the notion of a container has been removed. Instead, the renderer has
    to implement methods to attach views next to elements or other views.
  * a RenderView now contains multiple RenderFragments. Fragments
    are used to move DOM nodes around.

Internal changes / design changes:
- Introduce notion of view fragments on render side
- DomProtoViews and DomViews on render side are merged,
  AppProtoViews are not merged, AppViews are partially merged
  (they share arrays with the other merged AppViews but we keep
  individual AppView instances for now).
- DomProtoViews always have a `<template>` element as root
  * needed for storing subviews
  * we have less chunks of DOM to clone now
- remove fake ElementBinder / Bound element for root text bindings
  and model them explicitly. This removes a lot of special cases we had!
- AppView shares data with nested component views
- some methods in AppViewManager (create, hydrate, dehydrate) are iterative now
  * now possible as we have all child AppViews / ElementRefs already in an array!
This commit is contained in:
Tobias Bosch
2015-06-24 13:46:39 -07:00
parent d449ea5ca4
commit b1df54501a
82 changed files with 3418 additions and 2806 deletions

View File

@ -15,7 +15,7 @@ import {
} from 'angular2/test_lib';
import {List, ListWrapper, Map, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
import {IMPLEMENTS, Type, isBlank, stringify, isPresent} from 'angular2/src/facade/lang';
import {IMPLEMENTS, Type, isBlank, stringify, isPresent, isArray} from 'angular2/src/facade/lang';
import {PromiseWrapper, Promise} from 'angular2/src/facade/async';
import {Compiler, CompilerCache} from 'angular2/src/core/compiler/compiler';
@ -45,24 +45,41 @@ export function main() {
rootProtoView;
var renderCompileRequests: any[];
beforeEach(() => {
directiveResolver = new DirectiveResolver();
tplResolver = new FakeViewResolver();
cmpUrlMapper = new RuntimeComponentUrlMapper();
renderCompiler = new SpyRenderCompiler();
renderCompiler.spy('compileHost')
.andCallFake((componentId) => {
return PromiseWrapper.resolve(createRenderProtoView(
[createRenderComponentElementBinder(0)], renderApi.ViewType.HOST));
});
rootProtoView = createRootProtoView(directiveResolver, MainComponent);
});
function mergeProtoViewsRecursively(
protoViewRefs: List<renderApi.RenderProtoViewRef | List<any>>,
target: renderApi.RenderProtoViewMergeMapping[]): renderApi.RenderProtoViewRef {
var targetIndex = target.length;
target.push(null);
var flattended = protoViewRefs.map(protoViewRefOrArray => {
var resolvedProtoViewRef;
if (isArray(protoViewRefOrArray)) {
resolvedProtoViewRef = mergeProtoViewsRecursively(
<List<renderApi.RenderProtoViewRef>>protoViewRefOrArray, target);
} else {
resolvedProtoViewRef = protoViewRefOrArray;
}
return resolvedProtoViewRef;
});
var merged = [];
flattended.forEach((entry) => {
if (entry instanceof MergedRenderProtoViewRef) {
entry.originals.forEach(ref => merged.push(ref));
} else {
merged.push(entry);
}
});
var result = new MergedRenderProtoViewRef(merged);
target[targetIndex] = new renderApi.RenderProtoViewMergeMapping(result, 1, [], [], []);
return result;
}
function createCompiler(renderCompileResults:
List<renderApi.ProtoViewDto | Promise<renderApi.ProtoViewDto>>,
protoViewFactoryResults: List<List<AppProtoView>>) {
protoViewFactoryResults: List<AppProtoView>) {
var urlResolver = new UrlResolver();
renderCompileRequests = [];
renderCompileResults = ListWrapper.clone(renderCompileResults);
renderCompiler.spy('compile').andCallFake((view) => {
renderCompileRequests.push(view);
return PromiseWrapper.resolve(ListWrapper.removeAt(renderCompileResults, 0));
@ -73,12 +90,31 @@ export function main() {
urlResolver, renderCompiler, protoViewFactory, new FakeAppRootUrl());
}
beforeEach(() => {
directiveResolver = new DirectiveResolver();
tplResolver = new FakeViewResolver();
cmpUrlMapper = new RuntimeComponentUrlMapper();
renderCompiler = new SpyRenderCompiler();
renderCompiler.spy('compileHost')
.andCallFake((componentId) => {
return PromiseWrapper.resolve(createRenderProtoView(
[createRenderComponentElementBinder(0)], renderApi.ViewType.HOST));
});
renderCompiler.spy('mergeProtoViewsRecursively')
.andCallFake((protoViewRefs: List<renderApi.RenderProtoViewRef | List<any>>) => {
var result: renderApi.RenderProtoViewMergeMapping[] = [];
mergeProtoViewsRecursively(protoViewRefs, result);
return PromiseWrapper.resolve(result);
});
rootProtoView = createRootProtoView(directiveResolver, MainComponent);
});
describe('serialize template', () => {
function captureTemplate(template: viewAnn.View): Promise<renderApi.ViewDefinition> {
tplResolver.setView(MainComponent, template);
var compiler =
createCompiler([createRenderProtoView()], [[rootProtoView], [createProtoView()]]);
createCompiler([createRenderProtoView()], [rootProtoView, createProtoView()]);
return compiler.compileInHost(MainComponent)
.then((_) => {
expect(renderCompileRequests.length).toBe(1);
@ -260,7 +296,7 @@ export function main() {
tplResolver.setView(MainComponent, new viewAnn.View({template: '<div></div>'}));
var renderProtoView = createRenderProtoView();
var expectedProtoView = createProtoView();
var compiler = createCompiler([renderProtoView], [[rootProtoView], [expectedProtoView]]);
var compiler = createCompiler([renderProtoView], [rootProtoView, expectedProtoView]);
compiler.compileInHost(MainComponent)
.then((_) => {
var request = protoViewFactory.requests[1];
@ -272,7 +308,7 @@ export function main() {
it('should pass the component binding', inject([AsyncTestCompleter], (async) => {
tplResolver.setView(MainComponent, new viewAnn.View({template: '<div></div>'}));
var compiler =
createCompiler([createRenderProtoView()], [[rootProtoView], [createProtoView()]]);
createCompiler([createRenderProtoView()], [rootProtoView, createProtoView()]);
compiler.compileInHost(MainComponent)
.then((_) => {
var request = protoViewFactory.requests[1];
@ -286,7 +322,7 @@ export function main() {
MainComponent,
new viewAnn.View({template: '<div></div>', directives: [SomeDirective]}));
var compiler =
createCompiler([createRenderProtoView()], [[rootProtoView], [createProtoView()]]);
createCompiler([createRenderProtoView()], [rootProtoView, createProtoView()]);
compiler.compileInHost(MainComponent)
.then((_) => {
var request = protoViewFactory.requests[1];
@ -300,7 +336,7 @@ export function main() {
inject([AsyncTestCompleter], (async) => {
tplResolver.setView(MainComponent, new viewAnn.View({template: '<div></div>'}));
var compiler =
createCompiler([createRenderProtoView()], [[rootProtoView], [createProtoView()]]);
createCompiler([createRenderProtoView()], [rootProtoView, createProtoView()]);
compiler.compileInHost(MainComponent)
.then((protoViewRef) => {
expect(internalProtoView(protoViewRef)).toBe(rootProtoView);
@ -316,17 +352,21 @@ export function main() {
var mainProtoView =
createProtoView([createComponentElementBinder(directiveResolver, NestedComponent)]);
var nestedProtoView = createProtoView();
var compiler = createCompiler(
[
createRenderProtoView([createRenderComponentElementBinder(0)]),
createRenderProtoView()
],
[[rootProtoView], [mainProtoView], [nestedProtoView]]);
var renderPvDtos = [
createRenderProtoView([createRenderComponentElementBinder(0)]),
createRenderProtoView()
];
var compiler =
createCompiler(renderPvDtos, [rootProtoView, mainProtoView, nestedProtoView]);
compiler.compileInHost(MainComponent)
.then((protoViewRef) => {
expect(internalProtoView(protoViewRef).elementBinders[0].nestedProtoView)
.toBe(mainProtoView);
expect(originalRenderProtoViewRefs(mainProtoView))
.toEqual([renderPvDtos[0].render, renderPvDtos[1].render]);
expect(mainProtoView.elementBinders[0].nestedProtoView).toBe(nestedProtoView);
expect(originalRenderProtoViewRefs(nestedProtoView))
.toEqual([renderPvDtos[1].render]);
async.done();
});
}));
@ -334,25 +374,40 @@ export function main() {
it('should load nested components in viewcontainers', inject([AsyncTestCompleter], (async) => {
tplResolver.setView(MainComponent, new viewAnn.View({template: '<div></div>'}));
tplResolver.setView(NestedComponent, new viewAnn.View({template: '<div></div>'}));
var mainProtoView = createProtoView([createViewportElementBinder(null)]);
var viewportProtoView =
createProtoView([createComponentElementBinder(directiveResolver, NestedComponent)]);
var mainProtoView = createProtoView([createViewportElementBinder(viewportProtoView)]);
var nestedProtoView = createProtoView();
var compiler = createCompiler(
[
createRenderProtoView([
createRenderViewportElementBinder(createRenderProtoView(
[createRenderComponentElementBinder(0)], renderApi.ViewType.EMBEDDED))
]),
createRenderProtoView()
],
[[rootProtoView], [mainProtoView, viewportProtoView], [nestedProtoView]]);
var renderPvDtos = [
createRenderProtoView([
createRenderViewportElementBinder(createRenderProtoView(
[createRenderComponentElementBinder(0)], renderApi.ViewType.EMBEDDED))
]),
createRenderProtoView()
];
var compiler =
createCompiler(renderPvDtos, [rootProtoView, mainProtoView, nestedProtoView]);
compiler.compileInHost(MainComponent)
.then((protoViewRef) => {
expect(internalProtoView(protoViewRef).elementBinders[0].nestedProtoView)
.toBe(mainProtoView);
expect(originalRenderProtoViewRefs(mainProtoView))
.toEqual([
renderPvDtos[0]
.render,
renderPvDtos[0].elementBinders[0].nestedProtoView.render,
renderPvDtos[1].render
]);
expect(viewportProtoView.elementBinders[0].nestedProtoView).toBe(nestedProtoView);
expect(originalRenderProtoViewRefs(viewportProtoView))
.toEqual([
renderPvDtos[0]
.elementBinders[0]
.nestedProtoView.render,
renderPvDtos[1].render
]);
expect(originalRenderProtoViewRefs(nestedProtoView))
.toEqual([renderPvDtos[1].render]);
async.done();
});
}));
@ -360,7 +415,7 @@ export function main() {
it('should cache compiled host components', inject([AsyncTestCompleter], (async) => {
tplResolver.setView(MainComponent, new viewAnn.View({template: '<div></div>'}));
var mainPv = createProtoView();
var compiler = createCompiler([createRenderProtoView()], [[rootProtoView], [mainPv]]);
var compiler = createCompiler([createRenderProtoView()], [rootProtoView, mainPv]);
compiler.compileInHost(MainComponent)
.then((protoViewRef) => {
expect(internalProtoView(protoViewRef).elementBinders[0].nestedProtoView)
@ -404,7 +459,7 @@ export function main() {
var nestedPv = createProtoView([]);
var compiler = createCompiler(
[createRenderProtoView(), createRenderProtoView(), createRenderProtoView()],
[[rootProtoView], [mainPv], [nestedPv], [rootProtoView2], [mainPv]]);
[rootProtoView, mainPv, nestedPv, rootProtoView2, mainPv]);
compiler.compileInHost(MainComponent)
.then((protoViewRef) => {
expect(internalProtoView(protoViewRef)
@ -429,7 +484,7 @@ export function main() {
var renderProtoViewCompleter = PromiseWrapper.completer();
var expectedProtoView = createProtoView();
var compiler = createCompiler([renderProtoViewCompleter.promise],
[[rootProtoView], [rootProtoView], [expectedProtoView]]);
[rootProtoView, rootProtoView, expectedProtoView]);
var result = PromiseWrapper.all([
compiler.compileInHost(MainComponent),
compiler.compileInHost(MainComponent),
@ -445,18 +500,55 @@ export function main() {
});
}));
it('should allow recursive components', inject([AsyncTestCompleter], (async) => {
it('should throw on unconditional recursive components',
inject([AsyncTestCompleter], (async) => {
tplResolver.setView(MainComponent, new viewAnn.View({template: '<div></div>'}));
var mainProtoView =
createProtoView([createComponentElementBinder(directiveResolver, MainComponent)]);
var compiler =
createCompiler([createRenderProtoView([createRenderComponentElementBinder(0)])],
[[rootProtoView], [mainProtoView]]);
[rootProtoView, mainProtoView]);
PromiseWrapper.catchError(compiler.compileInHost(MainComponent), (e) => {
expect(() => { throw e; })
.toThrowError(`Unconditional component cycle in ${stringify(MainComponent)}`);
async.done();
return null;
});
}));
it('should allow recursive components that are connected via an embedded ProtoView',
inject([AsyncTestCompleter], (async) => {
tplResolver.setView(MainComponent, new viewAnn.View({template: '<div></div>'}));
var viewportProtoView =
createProtoView([createComponentElementBinder(directiveResolver, MainComponent)]);
var mainProtoView = createProtoView([createViewportElementBinder(viewportProtoView)]);
var renderPvDtos = [
createRenderProtoView([
createRenderViewportElementBinder(createRenderProtoView(
[createRenderComponentElementBinder(0)], renderApi.ViewType.EMBEDDED))
]),
createRenderProtoView()
];
var compiler = createCompiler(renderPvDtos, [rootProtoView, mainProtoView]);
compiler.compileInHost(MainComponent)
.then((protoViewRef) => {
expect(internalProtoView(protoViewRef).elementBinders[0].nestedProtoView)
.toBe(mainProtoView);
expect(mainProtoView.elementBinders[0].nestedProtoView).toBe(mainProtoView);
expect(mainProtoView.elementBinders[0]
.nestedProtoView.elementBinders[0]
.nestedProtoView)
.toBe(mainProtoView);
// In case of a cycle, don't merge the embedded proto views into the component!
expect(originalRenderProtoViewRefs(mainProtoView))
.toEqual([renderPvDtos[0].render, null]);
expect(originalRenderProtoViewRefs(viewportProtoView))
.toEqual([
renderPvDtos[0]
.elementBinders[0]
.nestedProtoView.render,
renderPvDtos[1].render,
null
]);
async.done();
});
}));
@ -466,8 +558,7 @@ export function main() {
var rootProtoView =
createProtoView([createComponentElementBinder(directiveResolver, MainComponent)]);
var mainProtoView = createProtoView();
var compiler =
createCompiler([createRenderProtoView()], [[rootProtoView], [mainProtoView]]);
var compiler = createCompiler([createRenderProtoView()], [rootProtoView, mainProtoView]);
compiler.compileInHost(MainComponent)
.then((protoViewRef) => {
expect(internalProtoView(protoViewRef)).toBe(rootProtoView);
@ -491,7 +582,7 @@ function createDirectiveBinding(directiveResolver, type): DirectiveBinding {
}
function createProtoView(elementBinders = null): AppProtoView {
var pv = new AppProtoView(null, null, new Map(), null);
var pv = new AppProtoView(null, null, null, new Map(), null);
if (isBlank(elementBinders)) {
elementBinders = [];
}
@ -518,7 +609,8 @@ function createRenderProtoView(elementBinders = null, type: renderApi.ViewType =
if (isBlank(elementBinders)) {
elementBinders = [];
}
return new renderApi.ProtoViewDto({elementBinders: elementBinders, type: type});
return new renderApi.ProtoViewDto(
{elementBinders: elementBinders, type: type, render: new renderApi.RenderProtoViewRef()});
}
function createRenderComponentElementBinder(directiveIndex): renderApi.ElementBinder {
@ -611,14 +703,22 @@ class FakeViewResolver extends ViewResolver {
class FakeProtoViewFactory extends ProtoViewFactory {
requests: List<List<any>>;
constructor(public results: List<List<AppProtoView>>) {
constructor(public results: List<AppProtoView>) {
super(null);
this.requests = [];
}
createAppProtoViews(componentBinding: DirectiveBinding, renderProtoView: renderApi.ProtoViewDto,
directives: List<DirectiveBinding>): List<AppProtoView> {
directives: List<DirectiveBinding>): AppProtoView {
this.requests.push([componentBinding, renderProtoView, directives]);
return ListWrapper.removeAt(this.results, 0);
}
}
class MergedRenderProtoViewRef extends renderApi.RenderProtoViewRef {
constructor(public originals: renderApi.RenderProtoViewRef[]) { super(); }
}
function originalRenderProtoViewRefs(appProtoView: AppProtoView) {
return (<MergedRenderProtoViewRef>appProtoView.mergeMapping.renderProtoViewRef).originals;
}

View File

@ -12,7 +12,6 @@ import {
beforeEachBindings,
it,
xit,
viewRootNodes,
TestComponentBuilder,
RootTestComponent,
inspectElement,
@ -240,8 +239,7 @@ export function main() {
componentRef.dispose();
expect(rootEl).toHaveText('');
expect(rootEl.parentNode).toBe(doc.body);
expect(rootEl.parentNode).toBeFalsy();
async.done();
});

View File

@ -51,17 +51,10 @@ import {QueryList} from 'angular2/src/core/compiler/query_list';
@proxy
@IMPLEMENTS(AppView)
class DummyView extends SpyObject {
componentChildViews;
changeDetector;
elementRefs;
constructor(elementCount = 0) {
constructor() {
super(AppView);
this.componentChildViews = [];
this.changeDetector = null;
this.elementRefs = ListWrapper.createFixedSize(elementCount);
for (var i=0; i<elementCount; i++) {
this.elementRefs[i] = new DummyElementRef();
}
}
noSuchMethod(m) { return super.noSuchMethod(m); }
}
@ -69,6 +62,7 @@ class DummyView extends SpyObject {
@proxy
@IMPLEMENTS(ElementRef)
class DummyElementRef extends SpyObject {
boundElementIndex: number = 0;
constructor() { super(ElementRef); }
noSuchMethod(m) { return super.noSuchMethod(m); }
}
@ -246,14 +240,14 @@ class TestNode extends TreeNode<TestNode> {
}
export function main() {
var defaultPreBuiltObjects = new PreBuiltObjects(null, <any>new DummyView(1), null);
var defaultPreBuiltObjects = new PreBuiltObjects(null, <any>new DummyView(), <any>new DummyElementRef(), null);
// An injector with more than 10 bindings will switch to the dynamic strategy
var dynamicBindings = [];
for (var i = 0; i < 20; i++) {
dynamicBindings.push(bind(i).toValue(i));
}
}
function createPei(parent, index, bindings, distance = 1, hasShadowRoot = false, dirVariableBindings = null) {
var directiveBinding = ListWrapper.map(bindings, b => {
@ -754,9 +748,9 @@ export function main() {
});
it("should instantiate directives that depend on pre built objects", () => {
var protoView = new AppProtoView(null, null, null, null);
var protoView = new AppProtoView(null, null, null, null, null);
var bindings = ListWrapper.concat([NeedsProtoViewRef], extraBindings);
var inj = injector(bindings, null, false, new PreBuiltObjects(null, null, protoView));
var inj = injector(bindings, null, false, new PreBuiltObjects(null, null, null, protoView));
expect(inj.get(NeedsProtoViewRef).protoViewRef).toEqual(new ProtoViewRef(protoView));
});
@ -947,7 +941,7 @@ export function main() {
describe("refs", () => {
it("should inject ElementRef", () => {
var inj = injector(ListWrapper.concat([NeedsElementRef], extraBindings));
expect(inj.get(NeedsElementRef).elementRef).toBe(defaultPreBuiltObjects.view.elementRefs[0]);
expect(inj.get(NeedsElementRef).elementRef).toBe(defaultPreBuiltObjects.elementRef);
});
it("should inject ChangeDetectorRef of the component's view into the component", () => {
@ -955,10 +949,10 @@ export function main() {
var view = <any>new DummyView();
var childView = new DummyView();
childView.changeDetector = cd;
view.componentChildViews = [childView];
view.spy('getNestedView').andReturn(childView);
var binding = DirectiveBinding.createFromType(ComponentNeedsChangeDetectorRef, new dirAnn.Component());
var inj = injector(ListWrapper.concat([binding], extraBindings), null, true,
new PreBuiltObjects(null, view, null));
new PreBuiltObjects(null, view, <any>new DummyElementRef(), null));
expect(inj.get(ComponentNeedsChangeDetectorRef).changeDetectorRef).toBe(cd.ref);
});
@ -969,7 +963,7 @@ export function main() {
view.changeDetector =cd;
var binding = DirectiveBinding.createFromType(DirectiveNeedsChangeDetectorRef, new dirAnn.Directive());
var inj = injector(ListWrapper.concat([binding], extraBindings), null, false,
new PreBuiltObjects(null, view, null));
new PreBuiltObjects(null, view, <any>new DummyElementRef(), null));
expect(inj.get(DirectiveNeedsChangeDetectorRef).changeDetectorRef).toBe(cd.ref);
});
@ -980,9 +974,9 @@ export function main() {
});
it("should inject ProtoViewRef", () => {
var protoView = new AppProtoView(null, null, null, null);
var protoView = new AppProtoView(null, null, null, null, null);
var inj = injector(ListWrapper.concat([NeedsProtoViewRef], extraBindings), null, false,
new PreBuiltObjects(null, null, protoView));
new PreBuiltObjects(null, null, null, protoView));
expect(inj.get(NeedsProtoViewRef).protoViewRef).toEqual(new ProtoViewRef(protoView));
});
@ -1071,7 +1065,7 @@ export function main() {
var inj = injector(ListWrapper.concat(dirs, extraBindings), null,
false, preBuildObjects, null, dirVariableBindings);
expect(inj.get(NeedsQueryByVarBindings).query.first).toBe(defaultPreBuiltObjects.view.elementRefs[0]);
expect(inj.get(NeedsQueryByVarBindings).query.first).toBe(defaultPreBuiltObjects.elementRef);
});
it('should contain directives on the same injector when querying by variable bindings' +
@ -1198,7 +1192,7 @@ export function main() {
});
});
});
});
});
}
class ContextWithHandler {

View File

@ -15,7 +15,8 @@ import {
xit,
containsRegexp,
stringifyElement,
TestComponentBuilder
TestComponentBuilder,
fakeAsync
} from 'angular2/test_lib';
@ -1369,8 +1370,8 @@ class SimpleImperativeViewComponent {
done;
constructor(self: ElementRef, viewManager: AppViewManager, renderer: DomRenderer) {
var shadowViewRef = viewManager.getComponentView(self);
renderer.setComponentViewRootNodes(shadowViewRef.render, [el('hello imp view')]);
var hostElement = renderer.getNativeElementSync(self);
DOM.appendChild(hostElement, el('hello imp view'));
}
}
@ -1870,7 +1871,7 @@ class SomeImperativeViewport {
}
if (value) {
this.view = this.vc.create(this.protoView);
var nodes = this.renderer.getRootNodes(this.view.render);
var nodes = this.renderer.getRootNodes(this.view.renderFragment);
for (var i = 0; i < nodes.length; i++) {
DOM.appendChild(this.anchor, nodes[i]);
}

View File

@ -0,0 +1,492 @@
import {
AsyncTestCompleter,
beforeEach,
ddescribe,
xdescribe,
describe,
el,
dispatchEvent,
expect,
iit,
inject,
IS_DARTIUM,
beforeEachBindings,
it,
xit,
containsRegexp,
stringifyElement,
TestComponentBuilder,
RootTestComponent,
fakeAsync,
tick,
By
} from 'angular2/test_lib';
import {DOM} from 'angular2/src/dom/dom_adapter';
import * as viewAnn from 'angular2/src/core/annotations_impl/view';
import {
Component,
Directive,
View,
forwardRef,
ViewContainerRef,
ProtoViewRef,
ElementRef,
bind
} from 'angular2/angular2';
import {ShadowDomStrategy, NativeShadowDomStrategy} from 'angular2/render';
export function main() {
describe('projection', () => {
it('should support simple components',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
tcb.overrideView(MainComp, new viewAnn.View({
template: '<simple>' +
'<div>A</div>' +
'</simple>',
directives: [Simple]
}))
.createAsync(MainComp)
.then((main) => {
expect(main.nativeElement).toHaveText('SIMPLE(A)');
async.done();
});
}));
it('should support simple components with text interpolation as direct children',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
tcb.overrideView(MainComp, new viewAnn.View({
template: '{{\'START(\'}}<simple>' +
'{{text}}' +
'</simple>{{\')END\'}}',
directives: [Simple]
}))
.createAsync(MainComp)
.then((main) => {
main.componentInstance.text = 'A';
main.detectChanges();
expect(main.nativeElement).toHaveText('START(SIMPLE(A))END');
async.done();
});
}));
it('should not show the light dom even if there is no content tag',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
tcb.overrideView(MainComp,
new viewAnn.View({template: '<empty>A</empty>', directives: [Empty]}))
.createAsync(MainComp)
.then((main) => {
expect(main.nativeElement).toHaveText('');
async.done();
});
}));
it('should support multiple content tags',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
tcb.overrideView(MainComp, new viewAnn.View({
template: '<multiple-content-tags>' +
'<div>B</div>' +
'<div>C</div>' +
'<div class="left">A</div>' +
'</multiple-content-tags>',
directives: [MultipleContentTagsComponent]
}))
.createAsync(MainComp)
.then((main) => {
expect(main.nativeElement).toHaveText('(A, BC)');
async.done();
});
}));
it('should redistribute only direct children',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
tcb.overrideView(MainComp, new viewAnn.View({
template: '<multiple-content-tags>' +
'<div>B<div class="left">A</div></div>' +
'<div>C</div>' +
'</multiple-content-tags>',
directives: [MultipleContentTagsComponent]
}))
.createAsync(MainComp)
.then((main) => {
expect(main.nativeElement).toHaveText('(, BAC)');
async.done();
});
}));
it("should redistribute direct child viewcontainers when the light dom changes",
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
tcb.overrideView(MainComp, new viewAnn.View({
template: '<multiple-content-tags>' +
'<template manual class="left"><div>A1</div></template>' +
'<div>B</div>' +
'</multiple-content-tags>',
directives: [MultipleContentTagsComponent, ManualViewportDirective]
}))
.createAsync(MainComp)
.then((main) => {
var viewportDirectives = main.queryAll(By.directive(ManualViewportDirective))
.map(de => de.inject(ManualViewportDirective));
expect(main.nativeElement).toHaveText('(, B)');
viewportDirectives.forEach(d => d.show());
expect(main.nativeElement).toHaveText('(A1, B)');
viewportDirectives.forEach(d => d.hide());
expect(main.nativeElement).toHaveText('(, B)');
async.done();
});
}));
it("should support nested components",
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
tcb.overrideView(MainComp, new viewAnn.View({
template: '<outer-with-indirect-nested>' +
'<div>A</div>' +
'<div>B</div>' +
'</outer-with-indirect-nested>',
directives: [OuterWithIndirectNestedComponent]
}))
.createAsync(MainComp)
.then((main) => {
expect(main.nativeElement).toHaveText('OUTER(SIMPLE(AB))');
async.done();
});
}));
it("should support nesting with content being direct child of a nested component",
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
tcb.overrideView(MainComp, new viewAnn.View({
template: '<outer>' +
'<template manual class="left"><div>A</div></template>' +
'<div>B</div>' +
'<div>C</div>' +
'</outer>',
directives: [OuterComponent, ManualViewportDirective],
}))
.createAsync(MainComp)
.then((main) => {
var viewportDirective = main.query(By.directive(ManualViewportDirective))
.inject(ManualViewportDirective);
expect(main.nativeElement).toHaveText('OUTER(INNER(INNERINNER(,BC)))');
viewportDirective.show();
expect(main.nativeElement).toHaveText('OUTER(INNER(INNERINNER(A,BC)))');
async.done();
});
}));
it('should redistribute when the shadow dom changes',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
tcb.overrideView(MainComp, new viewAnn.View({
template: '<conditional-content>' +
'<div class="left">A</div>' +
'<div>B</div>' +
'<div>C</div>' +
'</conditional-content>',
directives: [ConditionalContentComponent]
}))
.createAsync(MainComp)
.then((main) => {
var viewportDirective = main.query(By.directive(ManualViewportDirective))
.inject(ManualViewportDirective);
expect(main.nativeElement).toHaveText('(, BC)');
viewportDirective.show();
expect(main.nativeElement).toHaveText('(A, BC)');
viewportDirective.hide();
expect(main.nativeElement).toHaveText('(, BC)');
async.done();
});
}));
// GH-2095 - https://github.com/angular/angular/issues/2095
// important as we are removing the ng-content element during compilation,
// which could skrew up text node indices.
it('should support text nodes after content tags',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb, async) => {
tcb.overrideView(
MainComp,
new viewAnn.View(
{template: '<simple string-prop="text"></simple>', directives: [Simple]}))
.overrideTemplate(Simple, '<ng-content></ng-content><p>P,</p>{{stringProp}}')
.createAsync(MainComp)
.then((main) => {
main.detectChanges();
expect(main.nativeElement).toHaveText('P,text');
async.done();
});
}));
// important as we are moving style tags around during compilation,
// which could skrew up text node indices.
it('should support text nodes after style tags',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb, async) => {
tcb.overrideView(
MainComp,
new viewAnn.View(
{template: '<simple string-prop="text"></simple>', directives: [Simple]}))
.overrideTemplate(Simple, '<style></style><p>P,</p>{{stringProp}}')
.createAsync(MainComp)
.then((main) => {
main.detectChanges();
expect(main.nativeElement).toHaveText('P,text');
async.done();
});
}));
it('should support moving non projected light dom around',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
tcb.overrideView(MainComp, new viewAnn.View({
template: '<empty>' +
' <template manual><div>A</div></template>' +
'</empty>' +
'START(<div project></div>)END',
directives: [Empty, ProjectDirective, ManualViewportDirective],
}))
.createAsync(MainComp)
.then((main) => {
var sourceDirective: ManualViewportDirective =
main.query(By.directive(ManualViewportDirective))
.inject(ManualViewportDirective);
var projectDirective: ProjectDirective =
main.query(By.directive(ProjectDirective)).inject(ProjectDirective);
expect(main.nativeElement).toHaveText('START()END');
projectDirective.show(sourceDirective.protoViewRef, sourceDirective.elementRef);
expect(main.nativeElement).toHaveText('START(A)END');
async.done();
});
}));
it('should support moving projected light dom around',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
tcb.overrideView(MainComp, new viewAnn.View({
template: '<simple><template manual><div>A</div></template></simple>' +
'START(<div project></div>)END',
directives: [Simple, ProjectDirective, ManualViewportDirective],
}))
.createAsync(MainComp)
.then((main) => {
var sourceDirective: ManualViewportDirective =
main.query(By.directive(ManualViewportDirective))
.inject(ManualViewportDirective);
var projectDirective: ProjectDirective =
main.query(By.directive(ProjectDirective)).inject(ProjectDirective);
expect(main.nativeElement).toHaveText('SIMPLE()START()END');
projectDirective.show(sourceDirective.protoViewRef, sourceDirective.elementRef);
expect(main.nativeElement).toHaveText('SIMPLE()START(A)END');
async.done();
});
}));
it('should support moving ng-content around',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
tcb.overrideView(MainComp, new viewAnn.View({
template: '<conditional-content>' +
'<div class="left">A</div>' +
'<div>B</div>' +
'</conditional-content>' +
'START(<div project></div>)END',
directives:
[ConditionalContentComponent, ProjectDirective, ManualViewportDirective]
}))
.createAsync(MainComp)
.then((main) => {
var sourceDirective: ManualViewportDirective =
main.query(By.directive(ManualViewportDirective))
.inject(ManualViewportDirective);
var projectDirective: ProjectDirective =
main.query(By.directive(ProjectDirective)).inject(ProjectDirective);
expect(main.nativeElement).toHaveText('(, B)START()END');
projectDirective.show(sourceDirective.protoViewRef, sourceDirective.elementRef);
expect(main.nativeElement).toHaveText('(, B)START(A)END');
// Stamping ng-content multiple times should not produce the content multiple
// times...
projectDirective.show(sourceDirective.protoViewRef, sourceDirective.elementRef);
expect(main.nativeElement).toHaveText('(, B)START(A)END');
async.done();
});
}));
// Note: This does not use a ng-content element, but
// is still important as we are merging proto views independent of
// the presence of ng-content elements!
it('should still allow to implement a recursive trees',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
tcb.overrideView(MainComp,
new viewAnn.View({template: '<tree></tree>', directives: [Tree]}))
.createAsync(MainComp)
.then((main) => {
main.detectChanges();
var manualDirective: ManualViewportDirective =
main.query(By.directive(ManualViewportDirective))
.inject(ManualViewportDirective);
expect(main.nativeElement).toHaveText('TREE(0:)');
manualDirective.show();
main.detectChanges();
expect(main.nativeElement).toHaveText('TREE(0:TREE(1:))');
async.done();
});
}));
if (DOM.supportsNativeShadowDOM()) {
describe('native shadow dom support', () => {
beforeEachBindings(
() => { return [bind(ShadowDomStrategy).toValue(new NativeShadowDomStrategy())]; });
it('should support native content projection',
inject([TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async) => {
tcb.overrideView(MainComp, new viewAnn.View({
template: '<simple-native>' +
'<div>A</div>' +
'</simple-native>',
directives: [SimpleNative]
}))
.createAsync(MainComp)
.then((main) => {
expect(main.nativeElement).toHaveText('SIMPLE(A)');
async.done();
});
}));
});
}
});
}
@Component({selector: 'main'})
@View({template: '', directives: []})
class MainComp {
text: string = '';
}
@Component({selector: 'simple', properties: ['stringProp']})
@View({template: 'SIMPLE(<ng-content></ng-content>)', directives: []})
class Simple {
stringProp: string = '';
}
@Component({selector: 'simple-native'})
@View({template: 'SIMPLE(<content></content>)', directives: []})
class SimpleNative {
}
@Component({selector: 'empty'})
@View({template: '', directives: []})
class Empty {
}
@Component({selector: 'multiple-content-tags'})
@View({
template: '(<ng-content select=".left"></ng-content>, <ng-content></ng-content>)',
directives: []
})
class MultipleContentTagsComponent {
}
@Directive({selector: '[manual]'})
class ManualViewportDirective {
constructor(public vc: ViewContainerRef, public protoViewRef: ProtoViewRef,
public elementRef: ElementRef) {}
show() { this.vc.create(this.protoViewRef, 0); }
hide() { this.vc.clear(); }
}
@Directive({selector: '[project]'})
class ProjectDirective {
constructor(public vc: ViewContainerRef) {}
show(protoViewRef: ProtoViewRef, context: ElementRef) {
this.vc.create(protoViewRef, 0, context);
}
hide() { this.vc.clear(); }
}
@Component({selector: 'outer-with-indirect-nested'})
@View({
template: 'OUTER(<simple><div><ng-content></ng-content></div></simple>)',
directives: [Simple]
})
class OuterWithIndirectNestedComponent {
}
@Component({selector: 'outer'})
@View({
template: 'OUTER(<inner><ng-content></ng-content></inner>)',
directives: [forwardRef(() => InnerComponent)]
})
class OuterComponent {
}
@Component({selector: 'inner'})
@View({
template: 'INNER(<innerinner><ng-content></ng-content></innerinner>)',
directives: [forwardRef(() => InnerInnerComponent)]
})
class InnerComponent {
}
@Component({selector: 'innerinner'})
@View({
template: 'INNERINNER(<ng-content select=".left"></ng-content>,<ng-content></ng-content>)',
directives: []
})
class InnerInnerComponent {
}
@Component({selector: 'conditional-content'})
@View({
template:
'<div>(<div *manual><ng-content select=".left"></ng-content></div>, <ng-content></ng-content>)</div>',
directives: [ManualViewportDirective]
})
class ConditionalContentComponent {
}
@Component({selector: 'tab'})
@View({
template: '<div><div *manual>TAB(<ng-content></ng-content>)</div></div>',
directives: [ManualViewportDirective]
})
class Tab {
}
@Component({selector: 'tree', properties: ['depth']})
@View({
template: 'TREE({{depth}}:<tree *manual [depth]="depth+1"></tree>)',
directives: [ManualViewportDirective, Tree]
})
class Tree {
depth = 0;
}

View File

@ -35,7 +35,7 @@ export function main() {
describe('ProtoViewFactory', () => {
var changeDetection;
var protoViewFactory;
var protoViewFactory: ProtoViewFactory;
var directiveResolver;
beforeEach(() => {
@ -63,10 +63,13 @@ export function main() {
describe('createAppProtoViews', () => {
it('should create an AppProtoView for the root render proto view', () => {
var renderPv = createRenderProtoView();
var pvs = protoViewFactory.createAppProtoViews(bindDirective(MainComponent), renderPv, []);
expect(pvs.length).toBe(1);
expect(pvs[0].render).toBe(renderPv.render);
var varBindings = new Map();
varBindings.set('a', 'b');
var renderPv = createRenderProtoView([], null, varBindings);
var appPv =
protoViewFactory.createAppProtoViews(bindDirective(MainComponent), renderPv, []);
expect(appPv.variableBindings.get('a')).toEqual('b');
expect(appPv).toBeTruthy();
});
});
@ -159,15 +162,23 @@ function directiveBinding({metadata}: {metadata?: any} = {}) {
return new DirectiveBinding(Key.get("dummy"), null, [], [], [], metadata);
}
function createRenderProtoView(elementBinders = null, type: renderApi.ViewType = null) {
function createRenderProtoView(elementBinders = null, type: renderApi.ViewType = null,
variableBindings = null) {
if (isBlank(type)) {
type = renderApi.ViewType.COMPONENT;
}
if (isBlank(elementBinders)) {
elementBinders = [];
}
return new renderApi.ProtoViewDto(
{elementBinders: elementBinders, type: type, variableBindings: new Map()});
if (isBlank(variableBindings)) {
variableBindings = new Map();
}
return new renderApi.ProtoViewDto({
elementBinders: elementBinders,
type: type,
variableBindings: variableBindings,
textBindings: []
});
}
function createRenderComponentElementBinder(directiveIndex) {

View File

@ -398,7 +398,7 @@ class NeedsQueryDesc {
}
@Component({selector: 'needs-query-by-var-binding'})
@View({directives: [], template: '<content>'})
@View({directives: [], template: '<ng-content>'})
@Injectable()
class NeedsQueryByLabel {
query: QueryList<any>;
@ -408,7 +408,7 @@ class NeedsQueryByLabel {
}
@Component({selector: 'needs-query-by-var-bindings'})
@View({directives: [], template: '<content>'})
@View({directives: [], template: '<ng-content>'})
@Injectable()
class NeedsQueryByTwoLabels {
query: QueryList<any>;

View File

@ -18,10 +18,11 @@ import {
import {IMPLEMENTS} from 'angular2/src/facade/lang';
import {AppView, AppProtoView, AppViewContainer} from 'angular2/src/core/compiler/view';
import {AppView, AppViewContainer} from 'angular2/src/core/compiler/view';
import {ViewContainerRef} from 'angular2/src/core/compiler/view_container_ref';
import {AppViewManager} from 'angular2/src/core/compiler/view_manager';
import {ElementBinder} from 'angular2/src/core/compiler/element_binder';
import {ElementRef} from 'angular2/src/core/compiler/element_ref';
import {ViewRef} from 'angular2/src/core/compiler/view_ref';
export function main() {
// TODO(tbosch): add missing tests
@ -31,34 +32,26 @@ export function main() {
var view;
var viewManager;
function createProtoView() {
var pv = new AppProtoView(null, null, null, null);
pv.elementBinders = [new ElementBinder(0, null, 0, null, null)];
return pv;
}
function createView() { return new AppView(null, createProtoView(), new Map()); }
function createViewContainer() { return new ViewContainerRef(viewManager, location); }
beforeEach(() => {
viewManager = new AppViewManagerSpy();
view = createView();
view.viewContainers = [null];
location = view.elementRefs[0];
view = new AppViewSpy();
location = new ElementRef(new ViewRef(view), 0, 0, null);
});
describe('length', () => {
it('should return a 0 length if there is no underlying ViewContainerRef', () => {
it('should return a 0 length if there is no underlying AppViewContainer', () => {
var vc = createViewContainer();
expect(vc.length).toBe(0);
});
it('should return the size of the underlying ViewContainerRef', () => {
it('should return the size of the underlying AppViewContainer', () => {
var vc = createViewContainer();
view.viewContainers = [new AppViewContainer()];
view.viewContainers[0].views = [createView()];
var appVc = new AppViewContainer();
view.viewContainers = [appVc];
appVc.views = [<any>new AppViewSpy()];
expect(vc.length).toBe(1);
});
@ -69,6 +62,14 @@ export function main() {
});
}
@proxy
@IMPLEMENTS(AppView)
class AppViewSpy extends SpyObject {
viewContainers: AppViewContainer[] = [null];
constructor() { super(AppView); }
noSuchMethod(m) { return super.noSuchMethod(m) }
}
@proxy
@IMPLEMENTS(AppViewManager)
class AppViewManagerSpy extends SpyObject {

View File

@ -16,240 +16,155 @@ import {
proxy
} from 'angular2/test_lib';
import {Injector, bind} from 'angular2/di';
import {IMPLEMENTS, isBlank, isPresent} from 'angular2/src/facade/lang';
import {MapWrapper, ListWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
import {IMPLEMENTS} from 'angular2/src/facade/lang';
import {AppProtoView, AppView, AppViewContainer} from 'angular2/src/core/compiler/view';
import {
AppProtoView,
AppView,
AppViewContainer,
AppProtoViewMergeMapping
} from 'angular2/src/core/compiler/view';
import {ProtoViewRef, ViewRef, internalView} from 'angular2/src/core/compiler/view_ref';
import {Renderer, RenderViewRef, RenderProtoViewRef} from 'angular2/src/render/api';
import {ElementBinder} from 'angular2/src/core/compiler/element_binder';
import {DirectiveBinding, ElementInjector} from 'angular2/src/core/compiler/element_injector';
import {DirectiveResolver} from 'angular2/src/core/compiler/directive_resolver';
import {Component} from 'angular2/annotations';
import {ElementRef} from 'angular2/src/core/compiler/element_ref';
import {
Renderer,
RenderViewRef,
RenderProtoViewRef,
RenderFragmentRef,
ViewType,
RenderProtoViewMergeMapping,
RenderViewWithFragments
} from 'angular2/src/render/api';
import {AppViewManager} from 'angular2/src/core/compiler/view_manager';
import {AppViewManagerUtils} from 'angular2/src/core/compiler/view_manager_utils';
import {AppViewListener} from 'angular2/src/core/compiler/view_listener';
import {AppViewPool} from 'angular2/src/core/compiler/view_pool';
import {
createHostPv,
createComponentPv,
createEmbeddedPv,
createEmptyElBinder,
createNestedElBinder,
createProtoElInjector
} from './view_manager_utils_spec';
export function main() {
// TODO(tbosch): add missing tests
describe('AppViewManager', () => {
var renderer;
var utils;
var utils: AppViewManagerUtils;
var viewListener;
var viewPool;
var manager;
var directiveResolver;
var createdViews: any[];
var createdRenderViews: any[];
var manager: AppViewManager;
var createdRenderViews: RenderViewWithFragments[];
function wrapPv(protoView: AppProtoView): ProtoViewRef { return new ProtoViewRef(protoView); }
function wrapView(view: AppView): ViewRef { return new ViewRef(view); }
function elementRef(parentView, boundElementIndex) {
return parentView.elementRefs[boundElementIndex];
}
function createDirectiveBinding(type) {
var annotation = directiveResolver.resolve(type);
return DirectiveBinding.createFromType(type, annotation);
}
function createEmptyElBinder() { return new ElementBinder(0, null, 0, null, null); }
function createComponentElBinder(nestedProtoView = null) {
var binding = createDirectiveBinding(SomeComponent);
var binder = new ElementBinder(0, null, 0, null, binding);
binder.nestedProtoView = nestedProtoView;
return binder;
}
function createProtoView(binders = null) {
if (isBlank(binders)) {
binders = [];
}
var staticChildComponentCount = 0;
for (var i = 0; i < binders.length; i++) {
if (binders[i].hasStaticComponent()) {
staticChildComponentCount++;
}
}
var res = new AppProtoView(new MockProtoViewRef(staticChildComponentCount), null, null, null);
res.elementBinders = binders;
return res;
}
function createElementInjector() {
return SpyObject.stub(new SpyElementInjector(),
{
'isExportingComponent': false,
'isExportingElement': false,
'getEventEmitterAccessors': [],
'getComponent': null
},
{});
}
function createView(pv = null, renderViewRef = null) {
if (isBlank(pv)) {
pv = createProtoView();
}
if (isBlank(renderViewRef)) {
renderViewRef = new RenderViewRef();
}
var view = new AppView(renderer, pv, new Map());
view.render = renderViewRef;
var elementInjectors = ListWrapper.createFixedSize(pv.elementBinders.length);
for (var i = 0; i < pv.elementBinders.length; i++) {
elementInjectors[i] = createElementInjector();
}
view.init(null, elementInjectors, [], ListWrapper.createFixedSize(pv.elementBinders.length),
ListWrapper.createFixedSize(pv.elementBinders.length));
return view;
function resetSpies() {
viewListener.spy('viewCreated').reset();
viewListener.spy('viewDestroyed').reset();
renderer.spy('createView').reset();
renderer.spy('destroyView').reset();
renderer.spy('createRootHostView').reset();
renderer.spy('setEventDispatcher').reset();
renderer.spy('hydrateView').reset();
renderer.spy('dehydrateView').reset();
viewPool.spy('returnView').reset();
}
beforeEach(() => {
directiveResolver = new DirectiveResolver();
renderer = new SpyRenderer();
utils = new SpyAppViewManagerUtils();
utils = new AppViewManagerUtils();
viewListener = new SpyAppViewListener();
viewPool = new SpyAppViewPool();
manager = new AppViewManager(viewPool, viewListener, utils, renderer);
createdViews = [];
createdRenderViews = [];
utils.spy('createView')
.andCallFake((proto, renderViewRef, _a, _b) => {
var view = createView(proto, renderViewRef);
createdViews.push(view);
return view;
});
utils.spy('attachComponentView')
.andCallFake((hostView, elementIndex, childView) => {
hostView.componentChildViews[elementIndex] = childView;
});
utils.spy('attachViewInContainer')
.andCallFake((parentView, elementIndex, _a, _b, atIndex, childView) => {
var viewContainer = parentView.viewContainers[elementIndex];
if (isBlank(viewContainer)) {
viewContainer = new AppViewContainer();
parentView.viewContainers[elementIndex] = viewContainer;
}
ListWrapper.insert(viewContainer.views, atIndex, childView);
});
renderer.spy('createRootHostView')
.andCallFake((_b, _c) => {
var rv = new RenderViewRef();
.andCallFake((_a, renderFragmentCount, _b) => {
var fragments = [];
for (var i = 0; i < renderFragmentCount; i++) {
fragments.push(new RenderFragmentRef());
}
var rv = new RenderViewWithFragments(new RenderViewRef(), fragments);
createdRenderViews.push(rv);
return rv;
});
renderer.spy('createView')
.andCallFake((_a) => {
var rv = new RenderViewRef();
.andCallFake((_a, renderFragmentCount) => {
var fragments = [];
for (var i = 0; i < renderFragmentCount; i++) {
fragments.push(new RenderFragmentRef());
}
var rv = new RenderViewWithFragments(new RenderViewRef(), fragments);
createdRenderViews.push(rv);
return rv;
});
viewPool.spy('returnView').andReturn(true);
});
describe('static child components', () => {
describe('recursively create when not cached', () => {
var rootProtoView, hostProtoView, componentProtoView, hostView, componentView;
beforeEach(() => {
componentProtoView = createProtoView();
hostProtoView = createProtoView([createComponentElBinder(componentProtoView)]);
rootProtoView = createProtoView([createComponentElBinder(hostProtoView)]);
manager.createRootHostView(wrapPv(rootProtoView), null, null);
hostView = createdViews[1];
componentView = createdViews[2];
});
it('should create the view', () => {
expect(hostView.proto).toBe(hostProtoView);
expect(componentView.proto).toBe(componentProtoView);
});
it('should hydrate the view', () => {
expect(utils.spy('hydrateComponentView')).toHaveBeenCalledWith(hostView, 0);
expect(renderer.spy('hydrateView')).toHaveBeenCalledWith(componentView.render);
});
it('should set the render view',
() => {expect(componentView.render).toBe(createdRenderViews[2])});
it('should set the event dispatcher', () => {
expect(renderer.spy('setEventDispatcher'))
.toHaveBeenCalledWith(componentView.render, componentView);
});
});
describe('recursively hydrate when getting from from the cache',
() => {
// TODO(tbosch): implement this
});
describe('recursively dehydrate', () => {
// TODO(tbosch): implement this
});
});
describe('createRootHostView', () => {
var hostProtoView;
beforeEach(() => { hostProtoView = createProtoView([createComponentElBinder(null)]); });
var hostProtoView: AppProtoView;
beforeEach(
() => { hostProtoView = createHostPv([createNestedElBinder(createComponentPv())]); });
it('should create the view', () => {
expect(internalView(manager.createRootHostView(wrapPv(hostProtoView), null, null)))
.toBe(createdViews[0]);
expect(createdViews[0].proto).toBe(hostProtoView);
expect(viewListener.spy('viewCreated')).toHaveBeenCalledWith(createdViews[0]);
var rootView = internalView(manager.createRootHostView(wrapPv(hostProtoView), null, null));
expect(rootView.proto).toBe(hostProtoView);
expect(viewListener.spy('viewCreated')).toHaveBeenCalledWith(rootView);
});
it('should hydrate the view', () => {
var injector = Injector.resolveAndCreate([]);
manager.createRootHostView(wrapPv(hostProtoView), null, injector);
expect(utils.spy('hydrateRootHostView')).toHaveBeenCalledWith(createdViews[0], injector);
expect(renderer.spy('hydrateView')).toHaveBeenCalledWith(createdViews[0].render);
var rootView =
internalView(manager.createRootHostView(wrapPv(hostProtoView), null, injector));
expect(rootView.hydrated()).toBe(true);
expect(renderer.spy('hydrateView')).toHaveBeenCalledWith(rootView.render);
});
it('should create and set the render view using the component selector', () => {
manager.createRootHostView(wrapPv(hostProtoView), null, null)
expect(renderer.spy('createRootHostView'))
.toHaveBeenCalledWith(hostProtoView.render, 'someComponent');
expect(createdViews[0].render).toBe(createdRenderViews[0]);
var rootView = internalView(manager.createRootHostView(wrapPv(hostProtoView), null, null));
expect(renderer.spy('createRootHostView'))
.toHaveBeenCalledWith(hostProtoView.mergeMapping.renderProtoViewRef,
hostProtoView.mergeMapping.renderFragmentCount, 'someComponent');
expect(rootView.render).toBe(createdRenderViews[0].viewRef);
expect(rootView.renderFragment).toBe(createdRenderViews[0].fragmentRefs[0]);
});
it('should allow to override the selector', () => {
var selector = 'someOtherSelector';
manager.createRootHostView(wrapPv(hostProtoView), selector, null)
expect(renderer.spy('createRootHostView'))
.toHaveBeenCalledWith(hostProtoView.render, selector);
internalView(manager.createRootHostView(wrapPv(hostProtoView), selector, null));
expect(renderer.spy('createRootHostView'))
.toHaveBeenCalledWith(hostProtoView.mergeMapping.renderProtoViewRef,
hostProtoView.mergeMapping.renderFragmentCount, selector);
});
it('should set the event dispatcher', () => {
manager.createRootHostView(wrapPv(hostProtoView), null, null);
var cmpView = createdViews[0];
expect(renderer.spy('setEventDispatcher')).toHaveBeenCalledWith(cmpView.render, cmpView);
var rootView = internalView(manager.createRootHostView(wrapPv(hostProtoView), null, null));
expect(renderer.spy('setEventDispatcher')).toHaveBeenCalledWith(rootView.render, rootView);
});
});
describe('destroyRootHostView', () => {
var hostProtoView, hostView, hostRenderViewRef;
var hostProtoView: AppProtoView;
var hostView: AppView;
var hostRenderViewRef: RenderViewRef;
beforeEach(() => {
hostProtoView = createProtoView([createComponentElBinder(null)]);
hostProtoView = createHostPv([createNestedElBinder(createComponentPv())]);
hostView = internalView(manager.createRootHostView(wrapPv(hostProtoView), null, null));
hostRenderViewRef = hostView.render;
});
it('should dehydrate', () => {
manager.destroyRootHostView(wrapView(hostView));
expect(utils.spy('dehydrateView')).toHaveBeenCalledWith(hostView);
expect(hostView.hydrated()).toBe(false);
expect(renderer.spy('dehydrateView')).toHaveBeenCalledWith(hostView.render);
});
@ -269,60 +184,120 @@ export function main() {
describe('createViewInContainer', () => {
describe('basic functionality', () => {
var parentView, childProtoView;
var hostView: AppView;
var childProtoView: AppProtoView;
var vcRef: ElementRef;
beforeEach(() => {
parentView = createView(createProtoView([createEmptyElBinder()]));
childProtoView = createProtoView();
childProtoView = createEmbeddedPv();
var hostProtoView = createHostPv(
[createNestedElBinder(createComponentPv([createNestedElBinder(childProtoView)]))]);
hostView = internalView(manager.createRootHostView(wrapPv(hostProtoView), null, null));
vcRef = hostView.elementRefs[1];
resetSpies();
});
it('should create a ViewContainerRef if not yet existing', () => {
manager.createViewInContainer(elementRef(parentView, 0), 0, wrapPv(childProtoView), null);
expect(parentView.viewContainers[0]).toBeTruthy();
describe('create the first view', () => {
it('should create an AppViewContainer if not yet existing', () => {
manager.createViewInContainer(vcRef, 0, wrapPv(childProtoView), null);
expect(hostView.viewContainers[1]).toBeTruthy();
});
it('should use an existing nested view', () => {
var childView =
internalView(manager.createViewInContainer(vcRef, 0, wrapPv(childProtoView), null));
expect(childView.proto).toBe(childProtoView);
expect(childView).toBe(hostView.views[2]);
expect(viewListener.spy('viewCreated')).not.toHaveBeenCalled();
expect(renderer.spy('createView')).not.toHaveBeenCalled();
});
it('should attach the fragment', () => {
var childView =
internalView(manager.createViewInContainer(vcRef, 0, wrapPv(childProtoView), null));
expect(childView.proto).toBe(childProtoView);
expect(hostView.viewContainers[1].views.length).toBe(1);
expect(hostView.viewContainers[1].views[0]).toBe(childView);
expect(renderer.spy('attachFragmentAfterElement'))
.toHaveBeenCalledWith(vcRef, childView.renderFragment);
});
it('should hydrate the view but not the render view', () => {
var childView =
internalView(manager.createViewInContainer(vcRef, 0, wrapPv(childProtoView), null));
expect(childView.hydrated()).toBe(true);
expect(renderer.spy('hydrateView')).not.toHaveBeenCalled();
});
it('should not set the EventDispatcher', () => {
internalView(manager.createViewInContainer(vcRef, 0, wrapPv(childProtoView), null));
expect(renderer.spy('setEventDispatcher')).not.toHaveBeenCalled();
});
});
it('should create the view', () => {
expect(internalView(manager.createViewInContainer(elementRef(parentView, 0), 0,
wrapPv(childProtoView), null)))
.toBe(createdViews[0]);
expect(createdViews[0].proto).toBe(childProtoView);
expect(viewListener.spy('viewCreated')).toHaveBeenCalledWith(createdViews[0]);
describe('create the second view', () => {
var firstChildView;
beforeEach(() => {
firstChildView =
internalView(manager.createViewInContainer(vcRef, 0, wrapPv(childProtoView), null));
resetSpies();
});
it('should create a new view', () => {
var childView =
internalView(manager.createViewInContainer(vcRef, 1, wrapPv(childProtoView), null));
expect(childView.proto).toBe(childProtoView);
expect(childView).not.toBe(firstChildView);
expect(viewListener.spy('viewCreated')).toHaveBeenCalledWith(childView);
expect(renderer.spy('createView'))
.toHaveBeenCalledWith(childProtoView.mergeMapping.renderProtoViewRef,
childProtoView.mergeMapping.renderFragmentCount);
expect(childView.render).toBe(createdRenderViews[1].viewRef);
expect(childView.renderFragment).toBe(createdRenderViews[1].fragmentRefs[0]);
});
it('should attach the fragment', () => {
var childView =
internalView(manager.createViewInContainer(vcRef, 1, wrapPv(childProtoView), null));
expect(childView.proto).toBe(childProtoView);
expect(hostView.viewContainers[1].views[1]).toBe(childView);
expect(renderer.spy('attachFragmentAfterFragment'))
.toHaveBeenCalledWith(firstChildView.renderFragment, childView.renderFragment);
});
it('should hydrate the view', () => {
var childView =
internalView(manager.createViewInContainer(vcRef, 1, wrapPv(childProtoView), null));
expect(childView.hydrated()).toBe(true);
expect(renderer.spy('hydrateView')).toHaveBeenCalledWith(childView.render);
});
it('should set the EventDispatcher', () => {
var childView =
internalView(manager.createViewInContainer(vcRef, 1, wrapPv(childProtoView), null));
expect(renderer.spy('setEventDispatcher'))
.toHaveBeenCalledWith(childView.render, childView);
});
});
it('should attach the view', () => {
var contextView =
createView(createProtoView([createEmptyElBinder(), createEmptyElBinder()]));
var elRef = elementRef(parentView, 0);
manager.createViewInContainer(elRef, 0, wrapPv(childProtoView),
elementRef(contextView, 1), null);
expect(utils.spy('attachViewInContainer'))
.toHaveBeenCalledWith(parentView, 0, contextView, 1, 0, createdViews[0]);
expect(renderer.spy('attachViewInContainer'))
.toHaveBeenCalledWith(elRef, 0, createdViews[0].render);
});
describe('create another view when the first view has been returned', () => {
beforeEach(() => {
internalView(manager.createViewInContainer(vcRef, 0, wrapPv(childProtoView), null));
manager.destroyViewInContainer(vcRef, 0);
resetSpies();
});
it('should hydrate the view', () => {
var contextView =
createView(createProtoView([createEmptyElBinder(), createEmptyElBinder()]));
manager.createViewInContainer(elementRef(parentView, 0), 0, wrapPv(childProtoView),
elementRef(contextView, 1), []);
expect(utils.spy('hydrateViewInContainer'))
.toHaveBeenCalledWith(parentView, 0, contextView, 1, 0, []);
expect(renderer.spy('hydrateView')).toHaveBeenCalledWith(createdViews[0].render);
});
it('should use an existing nested view', () => {
var childView =
internalView(manager.createViewInContainer(vcRef, 0, wrapPv(childProtoView), null));
expect(childView.proto).toBe(childProtoView);
expect(childView).toBe(hostView.views[2]);
expect(viewListener.spy('viewCreated')).not.toHaveBeenCalled();
expect(renderer.spy('createView')).not.toHaveBeenCalled();
});
it('should create and set the render view', () => {
manager.createViewInContainer(elementRef(parentView, 0), 0, wrapPv(childProtoView), null,
null);
expect(renderer.spy('createView')).toHaveBeenCalledWith(childProtoView.render);
expect(createdViews[0].render).toBe(createdRenderViews[0]);
});
it('should set the event dispatcher', () => {
manager.createViewInContainer(elementRef(parentView, 0), 0, wrapPv(childProtoView), null,
null);
var childView = createdViews[0];
expect(renderer.spy('setEventDispatcher'))
.toHaveBeenCalledWith(childView.render, childView);
});
});
@ -331,61 +306,159 @@ export function main() {
describe('destroyViewInContainer', () => {
describe('basic functionality', () => {
var parentView, childProtoView, childView;
var hostView: AppView;
var childProtoView: AppProtoView;
var vcRef: ElementRef;
var firstChildView: AppView;
beforeEach(() => {
parentView = createView(createProtoView([createEmptyElBinder()]));
childProtoView = createProtoView();
childView = internalView(manager.createViewInContainer(elementRef(parentView, 0), 0,
wrapPv(childProtoView), null));
childProtoView = createEmbeddedPv();
var hostProtoView = createHostPv(
[createNestedElBinder(createComponentPv([createNestedElBinder(childProtoView)]))]);
hostView = internalView(manager.createRootHostView(wrapPv(hostProtoView), null, null));
vcRef = hostView.elementRefs[1];
firstChildView =
internalView(manager.createViewInContainer(vcRef, 0, wrapPv(childProtoView), null));
resetSpies();
});
it('should dehydrate', () => {
manager.destroyViewInContainer(elementRef(parentView, 0), 0);
expect(utils.spy('dehydrateView'))
.toHaveBeenCalledWith(parentView.viewContainers[0].views[0]);
expect(renderer.spy('dehydrateView')).toHaveBeenCalledWith(childView.render);
describe('destroy the first view', () => {
it('should dehydrate the app view but not the render view', () => {
manager.destroyViewInContainer(vcRef, 0);
expect(firstChildView.hydrated()).toBe(false);
expect(renderer.spy('dehydrateView')).not.toHaveBeenCalled();
});
it('should detach', () => {
manager.destroyViewInContainer(vcRef, 0);
expect(hostView.viewContainers[1].views).toEqual([]);
expect(renderer.spy('detachFragment'))
.toHaveBeenCalledWith(firstChildView.renderFragment);
});
it('should not return the view to the pool', () => {
manager.destroyViewInContainer(vcRef, 0);
expect(viewPool.spy('returnView')).not.toHaveBeenCalled();
});
});
it('should detach', () => {
var elRef = elementRef(parentView, 0);
manager.destroyViewInContainer(elRef, 0);
expect(utils.spy('detachViewInContainer')).toHaveBeenCalledWith(parentView, 0, 0);
expect(renderer.spy('detachViewInContainer'))
.toHaveBeenCalledWith(elRef, 0, childView.render);
});
describe('destroy another view', () => {
var secondChildView;
beforeEach(() => {
secondChildView =
internalView(manager.createViewInContainer(vcRef, 1, wrapPv(childProtoView), null));
resetSpies();
});
it('should dehydrate', () => {
manager.destroyViewInContainer(vcRef, 1);
expect(secondChildView.hydrated()).toBe(false);
expect(renderer.spy('dehydrateView')).toHaveBeenCalledWith(secondChildView.render);
});
it('should detach', () => {
manager.destroyViewInContainer(vcRef, 1);
expect(hostView.viewContainers[1].views[0]).toBe(firstChildView);
expect(renderer.spy('detachFragment'))
.toHaveBeenCalledWith(secondChildView.renderFragment);
});
it('should return the view to the pool', () => {
manager.destroyViewInContainer(vcRef, 1);
expect(viewPool.spy('returnView')).toHaveBeenCalledWith(secondChildView);
});
it('should return the view to the pool', () => {
manager.destroyViewInContainer(elementRef(parentView, 0), 0);
expect(viewPool.spy('returnView')).toHaveBeenCalledWith(childView);
});
});
describe('recursively destroy views in ViewContainers', () => {
var parentView, childProtoView, childView;
beforeEach(() => {
parentView = createView(createProtoView([createEmptyElBinder()]));
childProtoView = createProtoView();
childView = internalView(manager.createViewInContainer(elementRef(parentView, 0), 0,
wrapPv(childProtoView), null));
describe('destroy child views when a component is destroyed', () => {
var hostView: AppView;
var childProtoView: AppProtoView;
var vcRef: ElementRef;
var firstChildView: AppView;
var secondChildView: AppView;
beforeEach(() => {
childProtoView = createEmbeddedPv();
var hostProtoView = createHostPv(
[createNestedElBinder(createComponentPv([createNestedElBinder(childProtoView)]))]);
hostView = internalView(manager.createRootHostView(wrapPv(hostProtoView), null, null));
vcRef = hostView.elementRefs[1];
firstChildView =
internalView(manager.createViewInContainer(vcRef, 0, wrapPv(childProtoView), null));
secondChildView =
internalView(manager.createViewInContainer(vcRef, 1, wrapPv(childProtoView), null));
resetSpies();
});
it('should dehydrate', () => {
manager.destroyRootHostView(wrapView(hostView));
expect(firstChildView.hydrated()).toBe(false);
expect(secondChildView.hydrated()).toBe(false);
expect(renderer.spy('dehydrateView')).toHaveBeenCalledWith(hostView.render);
expect(renderer.spy('dehydrateView')).toHaveBeenCalledWith(secondChildView.render);
});
it('should detach', () => {
manager.destroyRootHostView(wrapView(hostView));
expect(hostView.viewContainers[1].views).toEqual([]);
expect(renderer.spy('detachFragment'))
.toHaveBeenCalledWith(firstChildView.renderFragment);
expect(renderer.spy('detachFragment'))
.toHaveBeenCalledWith(secondChildView.renderFragment);
});
it('should return the view to the pool', () => {
manager.destroyRootHostView(wrapView(hostView));
expect(viewPool.spy('returnView')).not.toHaveBeenCalledWith(firstChildView);
expect(viewPool.spy('returnView')).toHaveBeenCalledWith(secondChildView);
});
});
it('should dehydrate', () => {
manager.destroyRootHostView(wrapView(parentView));
expect(utils.spy('dehydrateView'))
.toHaveBeenCalledWith(parentView.viewContainers[0].views[0]);
expect(renderer.spy('dehydrateView')).toHaveBeenCalledWith(childView.render);
});
describe('destroy child views over multiple levels', () => {
var hostView: AppView;
var childProtoView: AppProtoView;
var nestedChildProtoView: AppProtoView;
var vcRef: ElementRef;
var nestedVcRefs: ElementRef[];
var childViews: AppView[];
var nestedChildViews: AppView[];
beforeEach(() => {
nestedChildProtoView = createEmbeddedPv();
childProtoView = createEmbeddedPv([
createNestedElBinder(
createComponentPv([createNestedElBinder(nestedChildProtoView)]))
]);
var hostProtoView = createHostPv(
[createNestedElBinder(createComponentPv([createNestedElBinder(childProtoView)]))]);
hostView = internalView(manager.createRootHostView(wrapPv(hostProtoView), null, null));
vcRef = hostView.elementRefs[1];
nestedChildViews = [];
childViews = [];
nestedVcRefs = [];
for (var i = 0; i < 2; i++) {
var view = internalView(
manager.createViewInContainer(vcRef, i, wrapPv(childProtoView), null));
childViews.push(view);
var nestedVcRef = view.elementRefs[view.elementOffset];
nestedVcRefs.push(nestedVcRef);
for (var j = 0; j < 2; j++) {
var nestedView = internalView(
manager.createViewInContainer(nestedVcRef, j, wrapPv(childProtoView), null));
nestedChildViews.push(nestedView);
}
}
resetSpies();
});
it('should detach', () => {
manager.destroyRootHostView(wrapView(parentView));
expect(utils.spy('detachViewInContainer')).toHaveBeenCalledWith(parentView, 0, 0);
expect(renderer.spy('detachViewInContainer'))
.toHaveBeenCalledWith(parentView.elementRefs[0], 0, childView.render);
});
it('should dehydrate all child views', () => {
manager.destroyRootHostView(wrapView(hostView));
childViews.forEach((childView) => expect(childView.hydrated()).toBe(false));
nestedChildViews.forEach((childView) => expect(childView.hydrated()).toBe(false));
});
it('should return the view to the pool', () => {
manager.destroyRootHostView(wrapView(parentView));
expect(viewPool.spy('returnView')).toHaveBeenCalledWith(childView);
});
});
@ -402,18 +475,6 @@ export function main() {
});
}
class MockProtoViewRef extends RenderProtoViewRef {
nestedComponentCount: number;
constructor(nestedComponentCount: number) {
super();
this.nestedComponentCount = nestedComponentCount;
}
}
@Component({selector: 'someComponent'})
class SomeComponent {
}
@proxy
@IMPLEMENTS(Renderer)
class SpyRenderer extends SpyObject {
@ -428,23 +489,9 @@ class SpyAppViewPool extends SpyObject {
noSuchMethod(m) { return super.noSuchMethod(m) }
}
@proxy
@IMPLEMENTS(AppViewManagerUtils)
class SpyAppViewManagerUtils extends SpyObject {
constructor() { super(AppViewManagerUtils); }
noSuchMethod(m) { return super.noSuchMethod(m) }
}
@proxy
@IMPLEMENTS(AppViewListener)
class SpyAppViewListener extends SpyObject {
constructor() { super(AppViewListener); }
noSuchMethod(m) { return super.noSuchMethod(m) }
}
@proxy
@IMPLEMENTS(ElementInjector)
class SpyElementInjector extends SpyObject {
constructor() { super(ElementInjector); }
noSuchMethod(m) { return super.noSuchMethod(m) }
}

View File

@ -14,6 +14,7 @@ import {
xit,
SpyObject,
SpyChangeDetector,
SpyProtoChangeDetector,
proxy,
Log
} from 'angular2/test_lib';
@ -22,7 +23,7 @@ import {Injector, bind} from 'angular2/di';
import {IMPLEMENTS, isBlank, isPresent} from 'angular2/src/facade/lang';
import {MapWrapper, ListWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
import {AppProtoView, AppView} from 'angular2/src/core/compiler/view';
import {AppProtoView, AppView, AppProtoViewMergeMapping} from 'angular2/src/core/compiler/view';
import {ElementBinder} from 'angular2/src/core/compiler/element_binder';
import {
DirectiveBinding,
@ -33,89 +34,31 @@ import {
import {DirectiveResolver} from 'angular2/src/core/compiler/directive_resolver';
import {Component} from 'angular2/annotations';
import {AppViewManagerUtils} from 'angular2/src/core/compiler/view_manager_utils';
import {RenderProtoViewMergeMapping, ViewType, RenderViewWithFragments} from 'angular2/render';
export function main() {
// TODO(tbosch): add more tests here!
describe('AppViewManagerUtils', () => {
var directiveResolver;
var utils;
var utils: AppViewManagerUtils;
function createInjector() { return Injector.resolveAndCreate([]); }
beforeEach(() => { utils = new AppViewManagerUtils(); });
function createDirectiveBinding(type) {
var annotation = directiveResolver.resolve(type);
return DirectiveBinding.createFromType(type, annotation);
function createViewWithChildren(pv: AppProtoView): AppView {
var renderViewWithFragments = new RenderViewWithFragments(null, [null, null]);
return utils.createView(pv, renderViewWithFragments, null, null);
}
function createEmptyElBinder() { return new ElementBinder(0, null, 0, null, null); }
function createComponentElBinder(nestedProtoView = null) {
var binding = createDirectiveBinding(SomeComponent);
var binder = new ElementBinder(0, null, 0, null, binding);
binder.nestedProtoView = nestedProtoView;
return binder;
}
function createProtoView(binders = null) {
if (isBlank(binders)) {
binders = [];
}
var res = new AppProtoView(null, null, null, null);
res.elementBinders = binders;
return res;
}
function createElementInjector(parent = null) {
var host = new SpyElementInjector();
var elementInjector =
isPresent(parent) ? new SpyElementInjectorWithParent(parent) : new SpyElementInjector();
return SpyObject.stub(elementInjector,
{
'isExportingComponent': false,
'isExportingElement': false,
'getEventEmitterAccessors': [],
'getHostActionAccessors': [],
'getComponent': null,
'getHost': host
},
{});
}
function createView(pv = null, nestedInjectors = false) {
if (isBlank(pv)) {
pv = createProtoView();
}
var view = new AppView(null, pv, new Map());
var elementInjectors = ListWrapper.createGrowableSize(pv.elementBinders.length);
var preBuiltObjects = ListWrapper.createFixedSize(pv.elementBinders.length);
for (var i = 0; i < pv.elementBinders.length; i++) {
if (nestedInjectors && i > 0) {
elementInjectors[i] = createElementInjector(elementInjectors[i - 1]);
} else {
elementInjectors[i] = createElementInjector();
}
preBuiltObjects[i] = new SpyPreBuiltObjects();
}
view.init(<any>new SpyChangeDetector(), elementInjectors, elementInjectors, preBuiltObjects,
ListWrapper.createFixedSize(pv.elementBinders.length));
return view;
}
beforeEach(() => {
directiveResolver = new DirectiveResolver();
utils = new AppViewManagerUtils();
});
describe("hydrateComponentView", () => {
describe('shared hydrate functionality', () => {
it("should hydrate the change detector after hydrating element injectors", () => {
var log = new Log();
var componentView = createView(createProtoView([createEmptyElBinder()]));
var hostView = createView(createProtoView([createComponentElBinder(createProtoView())]));
hostView.componentChildViews = [componentView];
var componentProtoView = createComponentPv([createEmptyElBinder()]);
var hostView =
createViewWithChildren(createHostPv([createNestedElBinder(componentProtoView)]));
var componentView = hostView.views[1];
var spyEi = <any>componentView.elementInjectors[0];
spyEi.spy('hydrate').andCallFake(log.fn('hydrate'));
@ -123,20 +66,17 @@ export function main() {
var spyCd = <any>componentView.changeDetector;
spyCd.spy('hydrate').andCallFake(log.fn('hydrateCD'));
utils.hydrateComponentView(hostView, 0);
utils.hydrateRootHostView(hostView, createInjector());
expect(log.result()).toEqual('hydrate; hydrateCD');
});
});
describe('shared hydrate functionality', () => {
it("should set up event listeners", () => {
var dir = new Object();
var hostPv = createProtoView([createComponentElBinder(null), createEmptyElBinder()]);
var hostView = createView(hostPv);
var hostPv =
createHostPv([createNestedElBinder(createComponentPv()), createEmptyElBinder()]);
var hostView = createViewWithChildren(hostPv);
var spyEventAccessor1 = SpyObject.stub({"subscribe": null});
SpyObject.stub(hostView.elementInjectors[0], {
'getHostActionAccessors': [],
@ -150,9 +90,6 @@ export function main() {
'getDirectiveAtIndex': dir
});
var shadowView = createView();
utils.attachComponentView(hostView, 0, shadowView);
utils.hydrateRootHostView(hostView, createInjector());
expect(spyEventAccessor1.spy('subscribe')).toHaveBeenCalledWith(hostView, 0, dir);
@ -162,8 +99,9 @@ export function main() {
it("should set up host action listeners", () => {
var dir = new Object();
var hostPv = createProtoView([createComponentElBinder(null), createEmptyElBinder()]);
var hostView = createView(hostPv);
var hostPv =
createHostPv([createNestedElBinder(createComponentPv()), createEmptyElBinder()]);
var hostView = createViewWithChildren(hostPv);
var spyActionAccessor1 = SpyObject.stub({"subscribe": null});
SpyObject.stub(hostView.elementInjectors[0], {
'getHostActionAccessors': [[spyActionAccessor1]],
@ -177,37 +115,51 @@ export function main() {
'getDirectiveAtIndex': dir
});
var shadowView = createView();
utils.attachComponentView(hostView, 0, shadowView);
utils.hydrateRootHostView(hostView, createInjector());
expect(spyActionAccessor1.spy('subscribe')).toHaveBeenCalledWith(hostView, 0, dir);
expect(spyActionAccessor2.spy('subscribe')).toHaveBeenCalledWith(hostView, 1, dir);
});
it("should not hydrate element injectors of component views inside of embedded fragments",
() => {
var hostView = createViewWithChildren(createHostPv([
createNestedElBinder(createComponentPv([
createNestedElBinder(createEmbeddedPv(
[createNestedElBinder(createComponentPv([createEmptyElBinder()]))]))
]))
]));
utils.hydrateRootHostView(hostView, createInjector());
expect(hostView.elementInjectors.length).toBe(4);
expect((<any>hostView.elementInjectors[3]).spy('hydrate')).not.toHaveBeenCalled();
});
});
describe('attachViewInContainer', () => {
var parentView, contextView, childView;
function createViews(numInj = 1) {
var parentPv = createProtoView([createEmptyElBinder()]);
parentView = createView(parentPv);
var childPv = createEmbeddedPv([createEmptyElBinder()]);
childView = createViewWithChildren(childPv);
var parentPv = createHostPv([createEmptyElBinder()]);
parentView = createViewWithChildren(parentPv);
var binders = [];
for (var i = 0; i < numInj; i++) binders.push(createEmptyElBinder());
var contextPv = createProtoView(binders);
contextView = createView(contextPv, true);
var childPv = createProtoView([createEmptyElBinder()]);
childView = createView(childPv);
for (var i = 0; i < numInj; i++) {
binders.push(createEmptyElBinder(i > 0 ? binders[i - 1] : null))
};
var contextPv = createHostPv(binders);
contextView = createViewWithChildren(contextPv);
}
it('should link the views rootElementInjectors at the given context', () => {
createViews();
utils.attachViewInContainer(parentView, 0, contextView, 0, 0, childView);
expect(contextView.elementInjectors.length).toEqual(2);
expect(contextView.rootElementInjectors.length).toEqual(2);
});
it('should link the views rootElementInjectors after the elementInjector at the given context',
@ -223,14 +175,14 @@ export function main() {
var parentView, contextView, childView;
function createViews() {
var parentPv = createProtoView([createEmptyElBinder()]);
parentView = createView(parentPv);
var parentPv = createHostPv([createEmptyElBinder()]);
parentView = createViewWithChildren(parentPv);
var contextPv = createProtoView([createEmptyElBinder()]);
contextView = createView(contextPv);
var contextPv = createHostPv([createEmptyElBinder()]);
contextView = createViewWithChildren(contextPv);
var childPv = createProtoView([createEmptyElBinder()]);
childView = createView(childPv);
var childPv = createEmbeddedPv([createEmptyElBinder()]);
childView = createViewWithChildren(childPv);
utils.attachViewInContainer(parentView, 0, contextView, 0, 0, childView);
}
@ -249,8 +201,8 @@ export function main() {
var hostView;
function createViews() {
var hostPv = createProtoView([createComponentElBinder()]);
hostView = createView(hostPv);
var hostPv = createHostPv([createNestedElBinder(createComponentPv())]);
hostView = createViewWithChildren(hostPv);
}
it("should instantiate the elementInjectors with the given injector and an empty host element injector",
@ -268,25 +220,125 @@ export function main() {
});
}
export function createInjector() {
return Injector.resolveAndCreate([]);
}
function createElementInjector(parent = null) {
var host = new SpyElementInjector(null);
var elementInjector = new SpyElementInjector(parent);
return SpyObject.stub(elementInjector,
{
'isExportingComponent': false,
'isExportingElement': false,
'getEventEmitterAccessors': [],
'getHostActionAccessors': [],
'getComponent': new Object(),
'getHost': host
},
{});
}
export function createProtoElInjector(parent: ProtoElementInjector = null): ProtoElementInjector {
var pei = new SpyProtoElementInjector(parent);
pei.spy('instantiate').andCallFake((parentEli) => createElementInjector(parentEli));
return <any>pei;
}
export function createEmptyElBinder(parent: ElementBinder = null) {
var parentPeli = isPresent(parent) ? parent.protoElementInjector : null;
return new ElementBinder(0, null, 0, createProtoElInjector(parentPeli), null);
}
export function createNestedElBinder(nestedProtoView: AppProtoView) {
var componentBinding = null;
if (nestedProtoView.type === ViewType.COMPONENT) {
var annotation = new DirectiveResolver().resolve(SomeComponent);
componentBinding = DirectiveBinding.createFromType(SomeComponent, annotation);
}
var binder = new ElementBinder(0, null, 0, createProtoElInjector(), componentBinding);
binder.nestedProtoView = nestedProtoView;
return binder;
}
function countNestedElementBinders(pv: AppProtoView): number {
var result = pv.elementBinders.length;
pv.elementBinders.forEach(binder => {
if (isPresent(binder.nestedProtoView)) {
result += countNestedElementBinders(binder.nestedProtoView);
}
});
return result;
}
function calcHostElementIndicesByViewIndex(pv: AppProtoView, elementOffset = 0,
target: number[] = null): number[] {
if (isBlank(target)) {
target = [null];
}
for (var binderIdx = 0; binderIdx < pv.elementBinders.length; binderIdx++) {
var binder = pv.elementBinders[binderIdx];
if (isPresent(binder.nestedProtoView)) {
target.push(elementOffset + binderIdx);
calcHostElementIndicesByViewIndex(binder.nestedProtoView,
elementOffset + pv.elementBinders.length, target);
elementOffset += countNestedElementBinders(binder.nestedProtoView);
}
}
return target;
}
function _createProtoView(type: ViewType, binders: ElementBinder[] = null) {
if (isBlank(binders)) {
binders = [];
}
var protoChangeDetector = <any>new SpyProtoChangeDetector();
protoChangeDetector.spy('instantiate').andReturn(new SpyChangeDetector());
var res = new AppProtoView(type, protoChangeDetector, null, null, 0);
res.elementBinders = binders;
var mappedElementIndices = ListWrapper.createFixedSize(countNestedElementBinders(res));
for (var i = 0; i < binders.length; i++) {
var binder = binders[i];
mappedElementIndices[i] = i;
binder.protoElementInjector.index = i;
}
var hostElementIndicesByViewIndex = calcHostElementIndicesByViewIndex(res);
res.mergeMapping = new AppProtoViewMergeMapping(
new RenderProtoViewMergeMapping(null, hostElementIndicesByViewIndex.length,
mappedElementIndices, [], hostElementIndicesByViewIndex));
return res;
}
export function createHostPv(binders: ElementBinder[] = null) {
return _createProtoView(ViewType.HOST, binders);
}
export function createComponentPv(binders: ElementBinder[] = null) {
return _createProtoView(ViewType.COMPONENT, binders);
}
export function createEmbeddedPv(binders: ElementBinder[] = null) {
return _createProtoView(ViewType.EMBEDDED, binders);
}
@Component({selector: 'someComponent'})
class SomeComponent {
}
@proxy
@IMPLEMENTS(ElementInjector)
class SpyElementInjector extends SpyObject {
constructor() { super(ElementInjector); }
@IMPLEMENTS(ProtoElementInjector)
class SpyProtoElementInjector extends SpyObject {
index: number;
constructor(public parent: ProtoElementInjector) { super(ProtoElementInjector); }
noSuchMethod(m) { return super.noSuchMethod(m) }
}
@proxy
@IMPLEMENTS(ElementInjector)
class SpyElementInjectorWithParent extends SpyObject {
parent: ElementInjector;
constructor(parent) {
super(ElementInjector);
this.parent = parent;
}
class SpyElementInjector extends SpyObject {
constructor(public parent: ElementInjector) { super(ElementInjector); }
noSuchMethod(m) { return super.noSuchMethod(m) }
}

View File

@ -24,9 +24,11 @@ export function main() {
function createViewPool({capacity}): AppViewPool { return new AppViewPool(capacity); }
function createProtoView() { return new AppProtoView(null, null, null, null); }
function createProtoView() { return new AppProtoView(null, null, null, null, null); }
function createView(pv) { return new AppView(null, pv, new Map()); }
function createView(pv) {
return new AppView(null, pv, null, null, null, null, new Map(), null, null);
}
it('should support multiple AppProtoViews', () => {
var vf = createViewPool({capacity: 2});