refactor(render): create and store render ProtoViewRef in every app ProtoView

Needed to change Renderer.mergeChildComponentProtoViews to not create
new ProtoViews to be able to deal with cyclic references.

This commit is part of using the new render layer in Angular.
This commit is contained in:
Tobias Bosch 2015-04-07 17:24:09 -07:00
parent d6003ee0ab
commit ca958464c4
16 changed files with 509 additions and 436 deletions

View File

@ -3,7 +3,6 @@ import {Type, isBlank, isPresent, BaseException, assertionsEnabled, print, strin
import {BrowserDomAdapter} from 'angular2/src/dom/browser_adapter';
import {DOM} from 'angular2/src/dom/dom_adapter';
import {Compiler, CompilerCache} from './compiler/compiler';
import {ProtoView} from './compiler/view';
import {Reflector, reflector} from 'angular2/src/reflection/reflection';
import {Parser, Lexer, ChangeDetection, dynamicChangeDetection, jitChangeDetection} from 'angular2/change_detection';
import {ExceptionHandler} from './exception_handler';
@ -72,12 +71,11 @@ function _injectorBindings(appComponentType): List<Binding> {
throw new BaseException(`Only Components can be bootstrapped; ` +
`Directive of ${stringify(type)} is not a Component`);
}
return compiler.compile(appComponentAnnotatedType.type).then(
(protoView) => {
var appProtoView = ProtoView.createRootProtoView(protoView, appElement,
DirectiveBinding.createFromType(appComponentAnnotatedType.type, appComponentAnnotatedType.annotation),
changeDetection.createProtoChangeDetector('root'),
strategy);
return compiler.compileRoot(
appElement,
DirectiveBinding.createFromType(appComponentAnnotatedType.type, appComponentAnnotatedType.annotation)
).then(
(appProtoView) => {
// The light Dom of the app element is not considered part of
// the angular application. Thus the context and lightDomInjector are
// empty.

View File

@ -85,6 +85,17 @@ export class NewCompiler {
return DirectiveBinding.createFromType(meta.type, meta.annotation);
}
// Create a rootView as if the compiler encountered <rootcmp></rootcmp>.
// Used for bootstrapping.
compileRoot(elementOrSelector, componentBinding:DirectiveBinding):Promise<ProtoView> {
return this._renderer.createRootProtoView(elementOrSelector, 'root').then( (rootRenderPv) => {
return this._compileNestedProtoViews(null, rootRenderPv, [componentBinding], true)
}).then( (rootProtoView) => {
rootProtoView.instantiateInPlace = true;
return rootProtoView;
});
}
compile(component: Type):Promise<ProtoView> {
var protoView = this._compile(this._bindDirective(component));
return PromiseWrapper.isPromise(protoView) ? protoView : PromiseWrapper.resolve(protoView);
@ -96,7 +107,8 @@ export class NewCompiler {
var protoView = this._compilerCache.get(component);
if (isPresent(protoView)) {
// The component has already been compiled into a ProtoView,
// returns a resolved Promise.
// returns a plain ProtoView, not wrapped inside of a Promise.
// Needed for recursive components.
return protoView;
}
@ -113,31 +125,72 @@ export class NewCompiler {
this._flattenDirectives(template),
(directive) => this._bindDirective(directive)
);
pvPromise = this._compileNoRecurse(componentBinding, template, directives).then( (protoView) => {
// Populate the cache before compiling the nested components,
// so that components can reference themselves in their template.
this._compilerCache.set(component, protoView);
MapWrapper.delete(this._compiling, component);
// Compile all the components from the template
var nestedPVPromises = this._compileNestedComponents(protoView);
if (nestedPVPromises.length > 0) {
// Returns ProtoView Promise when there are any asynchronous nested ProtoViews.
// The promise will resolved after nested ProtoViews are compiled.
return PromiseWrapper.then(PromiseWrapper.all(nestedPVPromises),
(_) => protoView,
(e) => { throw new BaseException(`${e} -> Failed to compile ${stringify(component)}`); }
);
}
return protoView;
var renderTemplate = this._buildRenderTemplate(component, template, directives);
pvPromise = this._renderer.compile(renderTemplate).then( (renderPv) => {
return this._compileNestedProtoViews(componentBinding, renderPv, directives, true);
});
MapWrapper.set(this._compiling, component, pvPromise);
return pvPromise;
}
_compileNoRecurse(componentBinding, template, directives):Promise<ProtoView> {
var component = componentBinding.key.token;
// TODO(tbosch): union type return ProtoView or Promise<ProtoView>
_compileNestedProtoViews(componentBinding, renderPv, directives, isComponentRootView) {
var nestedPVPromises = [];
var protoView = this._protoViewFactory.createProtoView(componentBinding, renderPv, directives);
if (isComponentRootView && isPresent(componentBinding)) {
// Populate the cache before compiling the nested components,
// so that components can reference themselves in their template.
var component = componentBinding.key.token;
this._compilerCache.set(component, protoView);
MapWrapper.delete(this._compiling, component);
}
var binderIndex = 0;
ListWrapper.forEach(protoView.elementBinders, (elementBinder) => {
var nestedComponent = elementBinder.componentDirective;
var nestedRenderProtoView = renderPv.elementBinders[binderIndex].nestedProtoView;
var elementBinderDone = (nestedPv) => {
elementBinder.nestedProtoView = nestedPv;
// Can't set the parentProtoView for components,
// as their ProtoView might be used in multiple other components.
nestedPv.parentProtoView = isPresent(nestedComponent) ? null : protoView;
};
var nestedCall = null;
if (isPresent(nestedComponent)) {
if (!(nestedComponent.annotation instanceof DynamicComponent)) {
nestedCall = this._compile(nestedComponent);
}
} else if (isPresent(nestedRenderProtoView)) {
nestedCall = this._compileNestedProtoViews(componentBinding, nestedRenderProtoView, directives, false);
}
if (PromiseWrapper.isPromise(nestedCall)) {
ListWrapper.push(nestedPVPromises, nestedCall.then(elementBinderDone));
} else if (isPresent(nestedCall)) {
elementBinderDone(nestedCall);
}
binderIndex++;
});
var protoViewDone = (_) => {
var childComponentRenderPvRefs = [];
ListWrapper.forEach(protoView.elementBinders, (eb) => {
if (isPresent(eb.componentDirective)) {
var componentPv = eb.nestedProtoView;
ListWrapper.push(childComponentRenderPvRefs, isPresent(componentPv) ? componentPv.render : null);
}
});
this._renderer.mergeChildComponentProtoViews(protoView.render, childComponentRenderPvRefs);
return protoView;
};
if (nestedPVPromises.length > 0) {
return PromiseWrapper.all(nestedPVPromises).then(protoViewDone);
} else {
return protoViewDone(null);
}
}
_buildRenderTemplate(component, template, directives) {
var componentUrl = this._urlResolver.resolve(
this._appUrl, this._componentUrlMapper.getUrl(component)
);
@ -150,37 +203,12 @@ export class NewCompiler {
// is able to resolve urls in stylesheets.
templateAbsUrl = componentUrl;
}
var renderTemplate = new renderApi.Template({
return new renderApi.Template({
componentId: stringify(component),
absUrl: templateAbsUrl,
inline: template.inline,
directives: ListWrapper.map(directives, this._buildRenderDirective)
});
return this._renderer.compile(renderTemplate).then( (renderPv) => {
return this._protoViewFactory.createProtoView(componentBinding.annotation, renderPv, directives);
});
}
_compileNestedComponents(protoView, nestedPVPromises = null):List<Promise> {
if (isBlank(nestedPVPromises)) {
nestedPVPromises = [];
}
ListWrapper.map(protoView.elementBinders, (elementBinder) => {
var nestedComponent = elementBinder.componentDirective;
if (isPresent(nestedComponent) && !(nestedComponent.annotation instanceof DynamicComponent)) {
var nestedCall = this._compile(nestedComponent);
if (PromiseWrapper.isPromise(nestedCall)) {
ListWrapper.push(nestedPVPromises, nestedCall.then( (nestedPv) => {
elementBinder.nestedProtoView = nestedPv;
}));
} else {
elementBinder.nestedProtoView = nestedCall;
}
} else if (isPresent(elementBinder.nestedProtoView)) {
this._compileNestedComponents(elementBinder.nestedProtoView, nestedPVPromises);
}
});
return nestedPVPromises;
}
_buildRenderDirective(directiveBinding) {
@ -269,7 +297,7 @@ export class Compiler extends NewCompiler {
new DefaultStepFactory(parser, shadowDomStrategy.render),
templateLoader
),
null, null
null, shadowDomStrategy.render
),
new ProtoViewFactory(changeDetection, shadowDomStrategy)
);

View File

@ -20,16 +20,19 @@ export class ProtoViewFactory {
this._shadowDomStrategy = shadowDomStrategy;
}
createProtoView(componentAnnotation:Component, renderProtoView: renderApi.ProtoView, directives:List<DirectiveBinding>):ProtoView {
return this._createProtoView(null, componentAnnotation, renderProtoView, directives);
}
_createProtoView(parent:ProtoView, componentAnnotation:Component, renderProtoView: renderApi.ProtoView, directives:List<DirectiveBinding>):ProtoView {
var protoChangeDetector = this._changeDetection.createProtoChangeDetector('dummy',
componentAnnotation.changeDetection);
createProtoView(componentBinding:DirectiveBinding, renderProtoView: renderApi.ProtoView, directives:List<DirectiveBinding>):ProtoView {
var protoChangeDetector;
if (isBlank(componentBinding)) {
protoChangeDetector = this._changeDetection.createProtoChangeDetector('root', null);
} else {
var componentAnnotation:Component = componentBinding.annotation;
protoChangeDetector = this._changeDetection.createProtoChangeDetector(
'dummy', componentAnnotation.changeDetection
);
}
var domProtoView = this._getDomProtoView(renderProtoView.render);
var protoView = new ProtoView(domProtoView.element, protoChangeDetector,
this._shadowDomStrategy, parent);
var protoView = new ProtoView(renderProtoView.render, domProtoView.element, protoChangeDetector,
this._shadowDomStrategy, null);
for (var i=0; i<renderProtoView.elementBinders.length; i++) {
var renderElementBinder = renderProtoView.elementBinders[i];
@ -42,13 +45,10 @@ export class ProtoViewFactory {
i, parentPeiWithDistance,
sortedDirectives, renderElementBinder
);
var elementBinder = this._createElementBinder(
this._createElementBinder(
protoView, renderElementBinder, domElementBinder, protoElementInjector, sortedDirectives
);
this._createDirectiveBinders(protoView, sortedDirectives);
if (isPresent(renderElementBinder.nestedProtoView)) {
elementBinder.nestedProtoView = this._createProtoView(protoView, componentAnnotation, renderElementBinder.nestedProtoView, directives);
}
}
MapWrapper.forEach(renderProtoView.variableBindings, (mappedName, varName) => {
protoView.bindVariable(varName, mappedName);

View File

@ -16,6 +16,7 @@ import {Content} from './shadow_dom_emulation/content_tag';
import {ShadowDomStrategy} from './shadow_dom_strategy';
import {ViewPool} from './view_pool';
import {EventManager} from 'angular2/src/render/dom/events/event_manager';
import * as renderApi from 'angular2/src/render/api';
const NG_BINDING_CLASS = 'ng-binding';
const NG_BINDING_CLASS_SELECTOR = '.ng-binding';
@ -283,11 +284,14 @@ export class ProtoView {
_directiveMementosMap:Map;
_directiveMementos:List;
render:renderApi.ProtoViewRef;
constructor(
render:renderApi.ProtoViewRef,
template,
protoChangeDetector:ProtoChangeDetector,
shadowDomStrategy:ShadowDomStrategy, parentProtoView:ProtoView = null) {
this.render = render;
this.element = template;
this.elementBinders = [];
this.variableBindings = MapWrapper.create();
@ -642,28 +646,6 @@ export class ProtoView {
return MapWrapper.get(this._directiveMementosMap, id);
}
// Create a rootView as if the compiler encountered <rootcmp></rootcmp>,
// and the component template is already compiled into protoView.
// Used for bootstrapping.
static createRootProtoView(protoView: ProtoView,
insertionElement,
rootComponentBinding: DirectiveBinding,
protoChangeDetector:ProtoChangeDetector,
shadowDomStrategy: ShadowDomStrategy
): ProtoView {
DOM.addClass(insertionElement, NG_BINDING_CLASS);
var cmpType = rootComponentBinding.key.token;
var rootProtoView = new ProtoView(insertionElement, protoChangeDetector, shadowDomStrategy);
rootProtoView.instantiateInPlace = true;
var binder = rootProtoView.bindElement(null, 0,
new ProtoElementInjector(null, 0, [cmpType], true));
binder.componentDirective = rootComponentBinding;
binder.nestedProtoView = protoView;
shadowDomStrategy.shimAppElement(cmpType, insertionElement);
return rootProtoView;
}
}
/**

View File

@ -141,20 +141,20 @@ export class Renderer {
compile(template:Template):Promise<ProtoView> { return null; }
/**
* Creates a new ProtoView with preset nested components,
* Sets the preset nested components,
* which will be instantiated when this protoView is instantiated.
* Note: We can't create new ProtoViewRefs here as we need to support cycles / recursive components.
* @param {List<ProtoViewRef>} protoViewRefs
* ProtoView for every element with a component in this protoView or in a view container's protoView
* @return {List<ProtoViewRef>}
* new ProtoViewRef for the given protoView and all of its view container's protoViews
*/
mergeChildComponentProtoViews(protoViewRef:ProtoViewRef, protoViewRefs:List<ProtoViewRef>):List<ProtoViewRef> { return null; }
mergeChildComponentProtoViews(protoViewRef:ProtoViewRef, componentProtoViewRefs:List<ProtoViewRef>) { return null; }
/**
* Creats a ProtoView that will create a root view for the given element,
* i.e. it will not clone the element but only attach other proto views to it.
* Contains a single nested component with the given componentId.
*/
createRootProtoView(selectorOrElement):ProtoViewRef { return null; }
createRootProtoView(selectorOrElement, componentId):Promise<ProtoView> { return null; }
/**
* Creates a view and all of its nested child components.

View File

@ -1,4 +1,4 @@
import {Promise} from 'angular2/src/facade/async';
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
import {List, ListWrapper} from 'angular2/src/facade/collection';
import {isBlank, isPresent} from 'angular2/src/facade/lang';
@ -26,10 +26,6 @@ function _wrapView(view:View) {
return new _DirectDomViewRef(view);
}
function _wrapProtoView(protoView:ProtoView) {
return new DirectDomProtoViewRef(protoView);
}
function _collectComponentChildViewRefs(view, target = null) {
if (isBlank(target)) {
target = [];
@ -83,22 +79,22 @@ export class DirectDomRenderer extends api.Renderer {
return this._compiler.compile(template);
}
mergeChildComponentProtoViews(protoViewRef:api.ProtoViewRef, protoViewRefs:List<api.ProtoViewRef>):List<api.ProtoViewRef> {
var protoViews = [];
mergeChildComponentProtoViews(protoViewRef:api.ProtoViewRef, protoViewRefs:List<api.ProtoViewRef>) {
_resolveProtoView(protoViewRef).mergeChildComponentProtoViews(
ListWrapper.map(protoViewRefs, _resolveProtoView),
protoViews
ListWrapper.map(protoViewRefs, _resolveProtoView)
);
return ListWrapper.map(protoViews, _wrapProtoView);
}
createRootProtoView(selectorOrElement):api.ProtoViewRef {
createRootProtoView(selectorOrElement, componentId):Promise<api.ProtoView> {
var element = selectorOrElement; // TODO: select the element if it is not a real element...
var rootProtoViewBuilder = new ProtoViewBuilder(element);
rootProtoViewBuilder.setIsRootView(true);
rootProtoViewBuilder.bindElement(element, 'root element').setComponentId('root');
this._shadowDomStrategy.processElement(null, 'root', element);
return rootProtoViewBuilder.build().render;
var elBinder = rootProtoViewBuilder.bindElement(element, 'root element');
elBinder.setComponentId(componentId);
elBinder.bindDirective(0);
this._shadowDomStrategy.processElement(null, componentId, element);
return PromiseWrapper.resolve(rootProtoViewBuilder.build());
}
createView(protoViewRef:api.ProtoViewRef):List<api.ViewRef> {

View File

@ -1,12 +1,8 @@
import {AST} from 'angular2/change_detection';
import {SetterFn} from 'angular2/src/reflection/types';
import {isPresent, isBlank, BaseException} from 'angular2/src/facade/lang';
import {List, ListWrapper} from 'angular2/src/facade/collection';
import * as protoViewModule from './proto_view';
/**
* Note: Code that uses this class assumes that is immutable!
*/
export class ElementBinder {
contentTagSelector: string;
textNodeIndices: List<number>;
@ -39,24 +35,4 @@ export class ElementBinder {
this.distanceToParent = distanceToParent;
this.propertySetters = propertySetters;
}
mergeChildComponentProtoViews(protoViews:List<protoViewModule.ProtoView>, target:List<protoViewModule.ProtoView>):ElementBinder {
var nestedProtoView;
if (isPresent(this.componentId)) {
nestedProtoView = ListWrapper.removeAt(protoViews, 0);
} else if (isPresent(this.nestedProtoView)) {
nestedProtoView = this.nestedProtoView.mergeChildComponentProtoViews(protoViews, target);
}
return new ElementBinder({
parentIndex: this.parentIndex,
textNodeIndices: this.textNodeIndices,
contentTagSelector: this.contentTagSelector,
nestedProtoView: nestedProtoView,
componentId: this.componentId,
eventLocals: this.eventLocals,
eventNames: this.eventNames,
distanceToParent: this.distanceToParent,
propertySetters: this.propertySetters
});
}
}

View File

@ -6,9 +6,6 @@ import {List, Map, ListWrapper, MapWrapper} from 'angular2/src/facade/collection
import {ElementBinder} from './element_binder';
import {NG_BINDING_CLASS} from '../util';
/**
* Note: Code that uses this class assumes that is immutable!
*/
export class ProtoView {
element;
elementBinders:List<ElementBinder>;
@ -28,22 +25,14 @@ export class ProtoView {
this.rootBindingOffset = (isPresent(this.element) && DOM.hasClass(this.element, NG_BINDING_CLASS)) ? 1 : 0;
}
mergeChildComponentProtoViews(protoViews:List<ProtoView>, target:List<ProtoView>):ProtoView {
var elementBinders = ListWrapper.createFixedSize(this.elementBinders.length);
mergeChildComponentProtoViews(componentProtoViews:List<ProtoView>) {
var componentProtoViewIndex = 0;
for (var i=0; i<this.elementBinders.length; i++) {
var eb = this.elementBinders[i];
if (isPresent(eb.componentId) || isPresent(eb.nestedProtoView)) {
elementBinders[i] = eb.mergeChildComponentProtoViews(protoViews, target);
} else {
elementBinders[i] = eb;
if (isPresent(eb.componentId)) {
eb.nestedProtoView = componentProtoViews[componentProtoViewIndex];
componentProtoViewIndex++;
}
}
var result = new ProtoView({
elementBinders: elementBinders,
element: this.element,
isRootView: this.isRootView
});
ListWrapper.insert(target, 0, result);
return result
}
}

View File

@ -214,14 +214,12 @@ export function main() {
});
}));
it('should pass the component annotation', inject([AsyncTestCompleter], (async) => {
it('should pass the component binding', inject([AsyncTestCompleter], (async) => {
tplResolver.setTemplate(MainComponent, new Template({inline: '<div></div>'}));
var compiler = createCompiler([createRenderProtoView()], [createProtoView()]);
compiler.compile(MainComponent).then( (protoView) => {
var request = protoViewFactory.requests[0];
expect(request[0]).toEqual(new Component({
selector: 'main-comp'
}));
expect(request[0].key.token).toBe(MainComponent);
async.done();
});
}));
@ -255,7 +253,7 @@ export function main() {
});
it('should load nested components in root ProtoView', inject([AsyncTestCompleter], (async) => {
it('should load nested components', inject([AsyncTestCompleter], (async) => {
tplResolver.setTemplate(MainComponent, new Template({inline: '<div></div>'}));
tplResolver.setTemplate(NestedComponent, new Template({inline: '<div></div>'}));
var mainProtoView = createProtoView([
@ -263,34 +261,54 @@ export function main() {
]);
var nestedProtoView = createProtoView();
var compiler = createCompiler(
[createRenderProtoView(), createRenderProtoView()],
[
createRenderProtoView([createRenderComponentElementBinder(0)]),
createRenderProtoView()
],
[mainProtoView, nestedProtoView]
);
compiler.compile(MainComponent).then( (protoView) => {
expect(protoView).toBe(mainProtoView);
expect(mainProtoView.elementBinders[0].nestedProtoView).toBe(nestedProtoView);
// parentProtoView of nested components has to be null as components can
// be used by multiple other components.
expect(nestedProtoView.parentProtoView).toBe(null);
async.done();
});
}));
it('should load nested components in viewport ProtoView', inject([AsyncTestCompleter], (async) => {
it('should load nested components in viewport', inject([AsyncTestCompleter], (async) => {
tplResolver.setTemplate(MainComponent, new Template({inline: '<div></div>'}));
tplResolver.setTemplate(NestedComponent, new Template({inline: '<div></div>'}));
var mainProtoView = createProtoView([
createViewportElementBinder(createProtoView([
createComponentElementBinder(reader, NestedComponent)
]))
createViewportElementBinder(null)
]);
var viewportProtoView = createProtoView([
createComponentElementBinder(reader, NestedComponent)
]);
var nestedProtoView = createProtoView();
var compiler = createCompiler(
[createRenderProtoView(), createRenderProtoView()],
[mainProtoView, nestedProtoView]
[
createRenderProtoView([
createRenderViewportElementBinder(
createRenderProtoView([
createRenderComponentElementBinder(0)
])
)
]),
createRenderProtoView()
],
[mainProtoView, viewportProtoView, nestedProtoView]
);
compiler.compile(MainComponent).then( (protoView) => {
expect(protoView).toBe(mainProtoView);
expect(
mainProtoView.elementBinders[0].nestedProtoView.elementBinders[0].nestedProtoView
).toBe(nestedProtoView);
expect(mainProtoView.elementBinders[0].nestedProtoView).toBe(viewportProtoView);
expect(viewportProtoView.parentProtoView).toBe(mainProtoView);
expect(viewportProtoView.elementBinders[0].nestedProtoView).toBe(nestedProtoView);
// parentProtoView of nested components has to be null as components can
// be used by multiple other components.
expect(nestedProtoView.parentProtoView).toBe(null);
async.done();
});
}));
@ -331,7 +349,9 @@ export function main() {
createComponentElementBinder(reader, MainComponent)
]);
var compiler = createCompiler(
[createRenderProtoView()],
[createRenderProtoView([
createRenderComponentElementBinder(0)
])],
[mainProtoView]
);
compiler.compile(MainComponent).then( (protoView) => {
@ -340,20 +360,44 @@ export function main() {
async.done();
});
}));
it('should create root proto views', inject([AsyncTestCompleter], (async) => {
tplResolver.setTemplate(MainComponent, new Template({inline: '<div></div>'}));
var rootProtoView = createProtoView([
createComponentElementBinder(reader, MainComponent)
]);
var mainProtoView = createProtoView();
var compiler = createCompiler(
[
createRenderProtoView()
],
[rootProtoView, mainProtoView]
);
compiler.compileRoot(null, createDirectiveBinding(reader, MainComponent)).then( (protoView) => {
expect(protoView).toBe(rootProtoView);
expect(rootProtoView.elementBinders[0].nestedProtoView).toBe(mainProtoView);
async.done();
});
}));
});
}
function createDirectiveBinding(reader, type) {
var meta = reader.read(type);
return DirectiveBinding.createFromType(meta.type, meta.annotation);
}
function createProtoView(elementBinders = null) {
var pv = new ProtoView(null, null, null, null);
if (isPresent(elementBinders)) {
pv.elementBinders = elementBinders;
var pv = new ProtoView(null, null, null, null, null);
if (isBlank(elementBinders)) {
elementBinders = [];
}
pv.elementBinders = elementBinders;
return pv;
}
function createComponentElementBinder(reader, type) {
var meta = reader.read(type);
var binding = DirectiveBinding.createFromType(meta.type, meta.annotation);
var binding = createDirectiveBinding(reader, type);
return new ElementBinder(
0, null, 0,
null, binding,
@ -371,8 +415,27 @@ function createViewportElementBinder(nestedProtoView) {
return elBinder;
}
function createRenderProtoView() {
return new renderApi.ProtoView();
function createRenderProtoView(elementBinders = null) {
if (isBlank(elementBinders)) {
elementBinders = [];
}
return new renderApi.ProtoView({
elementBinders: elementBinders
});
}
function createRenderComponentElementBinder(directiveIndex) {
return new renderApi.ElementBinder({
directives: [new renderApi.DirectiveBinder({
directiveIndex: directiveIndex
})]
});
}
function createRenderViewportElementBinder(nestedProtoView) {
return new renderApi.ElementBinder({
nestedProtoView: nestedProtoView
});
}
@Component({
@ -433,6 +496,12 @@ class FakeRenderer extends renderApi.Renderer {
ListWrapper.push(this.requests, template);
return PromiseWrapper.resolve(ListWrapper.removeAt(this._results, 0));
}
createRootProtoView(elementOrSelector, componentId):Promise<renderApi.ProtoView> {
return PromiseWrapper.resolve(
createRenderProtoView([createRenderComponentElementBinder(0)])
);
}
}
class FakeUrlResolver extends UrlResolver {
@ -481,8 +550,8 @@ class FakeProtoViewFactory extends ProtoViewFactory {
this._results = results;
}
createProtoView(componentAnnotation:Component, renderProtoView: renderApi.ProtoView, directives:List<DirectiveBinding>):ProtoView {
ListWrapper.push(this.requests, [componentAnnotation, renderProtoView, directives]);
createProtoView(componentBinding:DirectiveBinding, renderProtoView: renderApi.ProtoView, directives:List<DirectiveBinding>):ProtoView {
ListWrapper.push(this.requests, [componentBinding, renderProtoView, directives]);
return ListWrapper.removeAt(this._results, 0);
}
}

View File

@ -577,7 +577,7 @@ export function main() {
function createpreBuildObject(eventName, eventHandler) {
var handlers = StringMapWrapper.create();
StringMapWrapper.set(handlers, eventName, eventHandler);
var pv = new ProtoView(null, null, null);
var pv = new ProtoView(null, null, null, null);
pv.eventHandlers = [handlers];
var view = new View(pv, null, MapWrapper.create());
return new PreBuiltObjects(view, null, null, null);

View File

@ -43,7 +43,7 @@ export function main() {
it('should attach the view nodes to the shadow root', () => {
var host = el('<div></div>');
var nodes = el('<div>view</div>');
var pv = new ProtoView(nodes, new DynamicProtoChangeDetector(null, null), null);
var pv = new ProtoView(null, nodes, new DynamicProtoChangeDetector(null, null), null);
var view = pv.instantiate(null, null);
strategy.attachTemplate(host, view);
@ -68,7 +68,7 @@ export function main() {
it('should attach the view nodes as child of the host element', () => {
var host = el('<div><span>original content</span></div>');
var nodes = el('<div>view</div>');
var pv = new ProtoView(nodes, new DynamicProtoChangeDetector(null, null), null);
var pv = new ProtoView(null, nodes, new DynamicProtoChangeDetector(null, null), null);
var view = pv.instantiate(null, null);
strategy.attachTemplate(host, view);
@ -92,7 +92,7 @@ export function main() {
it('should attach the view nodes as child of the host element', () => {
var host = el('<div><span>original content</span></div>');
var nodes = el('<div>view</div>');
var pv = new ProtoView(nodes, new DynamicProtoChangeDetector(null, null), null);
var pv = new ProtoView(null, nodes, new DynamicProtoChangeDetector(null, null), null);
var view = pv.instantiate(null, null);
strategy.attachTemplate(host, view);

View File

@ -71,7 +71,7 @@ export function main() {
dom = el(`<div><stuff></stuff><div insert-after-me></div><stuff></stuff></div>`);
var insertionElement = dom.childNodes[1];
parentView = createView([dom.childNodes[0]]);
protoView = new ProtoView(el('<div>hi</div>'), new DynamicProtoChangeDetector(null, null),
protoView = new ProtoView(null, el('<div>hi</div>'), new DynamicProtoChangeDetector(null, null),
new NativeShadowDomStrategy(null));
elementInjector = new ElementInjector(null, null, null);
viewContainer = new ViewContainer(parentView, insertionElement, protoView, elementInjector,
@ -216,7 +216,7 @@ export function main() {
var parser = new Parser(new Lexer());
viewContainer.hydrate(new Injector([]), null, null);
var pv = new ProtoView(el('<div class="ng-binding">{{}}</div>'),
var pv = new ProtoView(null, el('<div class="ng-binding">{{}}</div>'),
new DynamicProtoChangeDetector(null, null), new NativeShadowDomStrategy(null));
pv.bindElement(null, 0, new ProtoElementInjector(null, 1, [SomeDirective]));
pv.bindTextNode(0, parser.parseBinding('foo', null));

View File

@ -63,7 +63,7 @@ export function main() {
describe('instantiated from protoView', () => {
var view;
beforeEach(() => {
var pv = new ProtoView(el('<div id="1"></div>'), new DynamicProtoChangeDetector(null, null), null);
var pv = new ProtoView(null, el('<div id="1"></div>'), new DynamicProtoChangeDetector(null, null), null);
view = pv.instantiate(null, null);
});
@ -90,7 +90,7 @@ export function main() {
});
it('should use the view pool to reuse views', () => {
var pv = new ProtoView(el('<div id="1"></div>'), new DynamicProtoChangeDetector(null, null), null);
var pv = new ProtoView(null, el('<div id="1"></div>'), new DynamicProtoChangeDetector(null, null), null);
var fakeView = new FakeView();
pv.returnToPool(fakeView);
@ -101,7 +101,7 @@ export function main() {
describe('with locals', function() {
var view;
beforeEach(() => {
var pv = new ProtoView(el('<div id="1"></div>'), new DynamicProtoChangeDetector(null, null), null);
var pv = new ProtoView(null, el('<div id="1"></div>'), new DynamicProtoChangeDetector(null, null), null);
pv.bindVariable('context-foo', 'template-foo');
view = createView(pv);
});
@ -137,7 +137,7 @@ export function main() {
}
it('should collect the root node in the ProtoView element', () => {
var pv = new ProtoView(templateAwareCreateElement('<div id="1"></div>'),
var pv = new ProtoView(null, templateAwareCreateElement('<div id="1"></div>'),
new DynamicProtoChangeDetector(null, null), null);
var view = pv.instantiate(null, null);
view.hydrate(null, null, null, null, null);
@ -148,7 +148,7 @@ export function main() {
describe('collect elements with property bindings', () => {
it('should collect property bindings on the root element if it has the ng-binding class', () => {
var pv = new ProtoView(templateAwareCreateElement('<div [prop]="a" class="ng-binding"></div>'),
var pv = new ProtoView(null, templateAwareCreateElement('<div [prop]="a" class="ng-binding"></div>'),
new DynamicProtoChangeDetector(null, null), null);
pv.bindElement(null, 0, null);
pv.bindElementProperty(parser.parseBinding('a', null), 'prop', reflector.setter('prop'));
@ -160,7 +160,7 @@ export function main() {
});
it('should collect property bindings on child elements with ng-binding class', () => {
var pv = new ProtoView(templateAwareCreateElement('<div><span></span><span class="ng-binding"></span></div>'),
var pv = new ProtoView(null, templateAwareCreateElement('<div><span></span><span class="ng-binding"></span></div>'),
new DynamicProtoChangeDetector(null, null), null);
pv.bindElement(null, 0, null);
pv.bindElementProperty(parser.parseBinding('b', null), 'a', reflector.setter('a'));
@ -176,7 +176,7 @@ export function main() {
describe('collect text nodes with bindings', () => {
it('should collect text nodes under the root element', () => {
var pv = new ProtoView(templateAwareCreateElement('<div class="ng-binding">{{}}<span></span>{{}}</div>'),
var pv = new ProtoView(null, templateAwareCreateElement('<div class="ng-binding">{{}}<span></span>{{}}</div>'),
new DynamicProtoChangeDetector(null, null), null);
pv.bindElement(null, 0, null);
pv.bindTextNode(0, parser.parseBinding('a', null));
@ -190,7 +190,7 @@ export function main() {
});
it('should collect text nodes with bindings on child elements with ng-binding class', () => {
var pv = new ProtoView(templateAwareCreateElement('<div><span> </span><span class="ng-binding">{{}}</span></div>'),
var pv = new ProtoView(null, templateAwareCreateElement('<div><span> </span><span class="ng-binding">{{}}</span></div>'),
new DynamicProtoChangeDetector(null, null), null);
pv.bindElement(null, 0, null);
pv.bindTextNode(0, parser.parseBinding('b', null));
@ -207,7 +207,7 @@ export function main() {
describe('inplace instantiation', () => {
it('should be supported.', () => {
var template = el('<div></div>');
var pv = new ProtoView(template, new DynamicProtoChangeDetector(null, null),
var pv = new ProtoView(null, template, new DynamicProtoChangeDetector(null, null),
new NativeShadowDomStrategy(null));
pv.instantiateInPlace = true;
var view = pv.instantiate(null, null);
@ -217,7 +217,7 @@ export function main() {
it('should be off by default.', () => {
var template = el('<div></div>')
var pv = new ProtoView(template, new DynamicProtoChangeDetector(null, null),
var pv = new ProtoView(null, template, new DynamicProtoChangeDetector(null, null),
new NativeShadowDomStrategy(null))
var view = pv.instantiate(null, null);
view.hydrate(null, null, null, null, null);
@ -235,7 +235,7 @@ export function main() {
describe('create ElementInjectors', () => {
it('should use the directives of the ProtoElementInjector', () => {
var pv = new ProtoView(el('<div class="ng-binding"></div>'),
var pv = new ProtoView(null, el('<div class="ng-binding"></div>'),
new DynamicProtoChangeDetector(null, null), null);
pv.bindElement(null, 0, new ProtoElementInjector(null, 1, [SomeDirective]));
@ -246,7 +246,7 @@ export function main() {
});
it('should use the correct parent', () => {
var pv = new ProtoView(el('<div class="ng-binding"><span class="ng-binding"></span></div>'),
var pv = new ProtoView(null, el('<div class="ng-binding"><span class="ng-binding"></span></div>'),
new DynamicProtoChangeDetector(null, null), null);
var protoParent = new ProtoElementInjector(null, 0, [SomeDirective]);
pv.bindElement(null, 0, protoParent);
@ -260,7 +260,7 @@ export function main() {
});
it('should not pass the host injector when a parent injector exists', () => {
var pv = new ProtoView(el('<div class="ng-binding"><span class="ng-binding"></span></div>'),
var pv = new ProtoView(null, el('<div class="ng-binding"><span class="ng-binding"></span></div>'),
new DynamicProtoChangeDetector(null, null), null);
var protoParent = new ProtoElementInjector(null, 0, [SomeDirective]);
pv.bindElement(null, 0, protoParent);
@ -276,7 +276,7 @@ export function main() {
});
it('should pass the host injector when there is no parent injector', () => {
var pv = new ProtoView(el('<div class="ng-binding"><span class="ng-binding"></span></div>'),
var pv = new ProtoView(null, el('<div class="ng-binding"><span class="ng-binding"></span></div>'),
new DynamicProtoChangeDetector(null, null), null);
pv.bindElement(null, 0, new ProtoElementInjector(null, 0, [SomeDirective]));
var testProtoElementInjector = new TestProtoElementInjector(null, 1, [AnotherDirective]);
@ -293,7 +293,7 @@ export function main() {
describe('collect root element injectors', () => {
it('should collect a single root element injector', () => {
var pv = new ProtoView(el('<div class="ng-binding"><span class="ng-binding"></span></div>'),
var pv = new ProtoView(null, el('<div class="ng-binding"><span class="ng-binding"></span></div>'),
new DynamicProtoChangeDetector(null, null), null);
var protoParent = new ProtoElementInjector(null, 0, [SomeDirective]);
pv.bindElement(null, 0, protoParent);
@ -306,7 +306,7 @@ export function main() {
});
it('should collect multiple root element injectors', () => {
var pv = new ProtoView(el('<div><span class="ng-binding"></span><span class="ng-binding"></span></div>'),
var pv = new ProtoView(null, el('<div><span class="ng-binding"></span><span class="ng-binding"></span></div>'),
new DynamicProtoChangeDetector(null, null), null);
pv.bindElement(null, 0, new ProtoElementInjector(null, 1, [SomeDirective]));
pv.bindElement(null, 0, new ProtoElementInjector(null, 2, [AnotherDirective]));
@ -324,7 +324,7 @@ export function main() {
var ctx;
function createComponentWithSubPV(subProtoView) {
var pv = new ProtoView(el('<cmp class="ng-binding"></cmp>'),
var pv = new ProtoView(null, el('<cmp class="ng-binding"></cmp>'),
new DynamicProtoChangeDetector(null, null), new NativeShadowDomStrategy(null));
var binder = pv.bindElement(null, 0, new ProtoElementInjector(null, 0, [SomeComponent], true));
binder.componentDirective = someComponentDirective;
@ -340,7 +340,7 @@ export function main() {
}
it('should expose component services to the component', () => {
var subpv = new ProtoView(el('<span></span>'), new DynamicProtoChangeDetector(null, null), null);
var subpv = new ProtoView(null, el('<span></span>'), new DynamicProtoChangeDetector(null, null), null);
var pv = createComponentWithSubPV(subpv);
var view = createNestedView(pv);
@ -351,7 +351,7 @@ export function main() {
it('should expose component services and component instance to directives in the shadow Dom',
() => {
var subpv = new ProtoView(
var subpv = new ProtoView(null,
el('<div dec class="ng-binding">hello shadow dom</div>'),
new DynamicProtoChangeDetector(null, null),
null);
@ -376,7 +376,7 @@ export function main() {
}
it('dehydration should dehydrate child component views too', () => {
var subpv = new ProtoView(
var subpv = new ProtoView(null,
el('<div dec class="ng-binding">hello shadow dom</div>'),
new DynamicProtoChangeDetector(null, null),
null);
@ -394,7 +394,7 @@ export function main() {
});
it('should create shadow dom (Native Strategy)', () => {
var subpv = new ProtoView(el('<span>hello shadow dom</span>'),
var subpv = new ProtoView(null, el('<span>hello shadow dom</span>'),
new DynamicProtoChangeDetector(null, null),
null);
var pv = createComponentWithSubPV(subpv);
@ -405,10 +405,10 @@ export function main() {
});
it('should emulate shadow dom (Emulated Strategy)', () => {
var subpv = new ProtoView(el('<span>hello shadow dom</span>'),
var subpv = new ProtoView(null, el('<span>hello shadow dom</span>'),
new DynamicProtoChangeDetector(null, null), null);
var pv = new ProtoView(el('<cmp class="ng-binding"></cmp>'),
var pv = new ProtoView(null, el('<cmp class="ng-binding"></cmp>'),
new DynamicProtoChangeDetector(null, null), new EmulatedScopedShadowDomStrategy(null, null, null));
var binder = pv.bindElement(null, 0, new ProtoElementInjector(null, 0, [SomeComponent], true));
binder.componentDirective = readDirectiveBinding(SomeComponent);
@ -422,9 +422,9 @@ export function main() {
describe('with template views', () => {
function createViewWithViewport() {
var templateProtoView = new ProtoView(
var templateProtoView = new ProtoView(null,
el('<div id="1"></div>'), new DynamicProtoChangeDetector(null, null), null);
var pv = new ProtoView(el('<someTmpl class="ng-binding"></someTmpl>'),
var pv = new ProtoView(null, el('<someTmpl class="ng-binding"></someTmpl>'),
new DynamicProtoChangeDetector(null, null), new NativeShadowDomStrategy(null));
var binder = pv.bindElement(null, 0, new ProtoElementInjector(null, 0, [SomeViewport]));
binder.viewportDirective = someViewportDirective;
@ -470,7 +470,7 @@ export function main() {
}
function createProtoView() {
var pv = new ProtoView(el('<div class="ng-binding"><div></div></div>'),
var pv = new ProtoView(null, el('<div class="ng-binding"><div></div></div>'),
new DynamicProtoChangeDetector(null, null), null);
pv.bindElement(null, 0, new TestProtoElementInjector(null, 0, []));
pv.bindEvent('click', parser.parseBinding('callMe($event)', null));
@ -505,7 +505,7 @@ export function main() {
});
it('should support custom event emitters', () => {
var pv = new ProtoView(el('<div class="ng-binding"><div></div></div>'),
var pv = new ProtoView(null, el('<div class="ng-binding"><div></div></div>'),
new DynamicProtoChangeDetector(null, null), null);
pv.bindElement(null, 0, new TestProtoElementInjector(null, 0, [EventEmitterDirective]));
pv.bindEvent('click', parser.parseBinding('callMe($event)', null));
@ -526,7 +526,7 @@ export function main() {
});
it('should bind to directive events', () => {
var pv = new ProtoView(el('<div class="ng-binding"></div>'),
var pv = new ProtoView(null, el('<div class="ng-binding"></div>'),
new DynamicProtoChangeDetector(null, null), null);
pv.bindElement(null, 0, new ProtoElementInjector(null, 0, [SomeDirectiveWithEventHandler]));
pv.bindEvent('click', parser.parseAction('onEvent($event)', null), 0);
@ -551,7 +551,7 @@ export function main() {
}
it('should consume text node changes', () => {
var pv = new ProtoView(el('<div class="ng-binding">{{}}</div>'),
var pv = new ProtoView(null, el('<div class="ng-binding">{{}}</div>'),
new DynamicProtoChangeDetector(null, null), null);
pv.bindElement(null, 0, null);
pv.bindTextNode(0, parser.parseBinding('foo', null));
@ -563,7 +563,7 @@ export function main() {
});
it('should consume element binding changes', () => {
var pv = new ProtoView(el('<div class="ng-binding"></div>'),
var pv = new ProtoView(null, el('<div class="ng-binding"></div>'),
new DynamicProtoChangeDetector(null, null), null);
pv.bindElement(null, 0, null);
pv.bindElementProperty(parser.parseBinding('foo', null), 'id', reflector.setter('id'));
@ -575,7 +575,7 @@ export function main() {
});
it('should consume directive watch expression change', () => {
var pv = new ProtoView(el('<div class="ng-binding"></div>'),
var pv = new ProtoView(null, el('<div class="ng-binding"></div>'),
new DynamicProtoChangeDetector(null, null), null);
pv.bindElement(null, 0, new ProtoElementInjector(null, 0, [SomeDirective]));
pv.bindDirectiveProperty(0, parser.parseBinding('foo', null), 'prop', reflector.setter('prop'));
@ -587,7 +587,7 @@ export function main() {
});
it('should notify a directive about changes after all its properties have been set', () => {
var pv = new ProtoView(el('<div class="ng-binding"></div>'),
var pv = new ProtoView(null, el('<div class="ng-binding"></div>'),
new DynamicProtoChangeDetector(null, null), null);
pv.bindElement(null, 0, new ProtoElementInjector(null, 0, [
@ -607,7 +607,7 @@ export function main() {
});
it('should provide a map of updated properties using onChange callback', () => {
var pv = new ProtoView(el('<div class="ng-binding"></div>'),
var pv = new ProtoView(null, el('<div class="ng-binding"></div>'),
new DynamicProtoChangeDetector(null, null), null);
pv.bindElement(null, 0, new ProtoElementInjector(null, 0, [
@ -634,7 +634,7 @@ export function main() {
});
it('should invoke the onAllChangesDone callback', () => {
var pv = new ProtoView(el('<div class="ng-binding"></div>'),
var pv = new ProtoView(null, el('<div class="ng-binding"></div>'),
new DynamicProtoChangeDetector(null, null), null);
pv.bindElement(null, 0, new ProtoElementInjector(null, 0, [
@ -651,32 +651,6 @@ export function main() {
});
});
describe('protoView createRootProtoView', () => {
var element, pv;
beforeEach(() => {
element = DOM.createElement('div');
pv = new ProtoView(el('<div>hi</div>'), new DynamicProtoChangeDetector(null, null),
new NativeShadowDomStrategy(null));
});
it('should create the root component when instantiated', () => {
var rootProtoView = ProtoView.createRootProtoView(pv, element,
someComponentDirective, new DynamicProtoChangeDetector(null, null),
new NativeShadowDomStrategy(null));
var view = rootProtoView.instantiate(null, null);
view.hydrate(new Injector([]), null, null, null, null);
expect(view.rootElementInjectors[0].get(SomeComponent)).not.toBe(null);
});
it('should inject the protoView into the shadowDom', () => {
var rootProtoView = ProtoView.createRootProtoView(pv, element,
someComponentDirective, new DynamicProtoChangeDetector(null, null),
new NativeShadowDomStrategy(null));
var view = rootProtoView.instantiate(null, null);
view.hydrate(new Injector([]), null, null, null, null);
expect(element.shadowRoot.childNodes[0].childNodes[0].nodeValue).toEqual('hi');
});
});
});
}

View File

@ -21,7 +21,11 @@ import {IntegrationTestbed, LoggingEventDispatcher, FakeEvent} from './integrati
export function main() {
describe('DirectDomRenderer integration', () => {
var testbed, renderer, rootEl, rootProtoViewRef, eventPlugin, compile;
var testbed, renderer, eventPlugin, compile, rootEl;
beforeEach(() => {
rootEl = el('<div></div>');
});
function createRenderer({urlData, viewCacheCapacity, shadowDomStrategy, templates}={}) {
testbed = new IntegrationTestbed({
@ -31,54 +35,66 @@ export function main() {
templates: templates
});
renderer = testbed.renderer;
rootEl = testbed.rootEl;
rootProtoViewRef = testbed.rootProtoViewRef;
eventPlugin = testbed.eventPlugin;
compile = (template, directives) => testbed.compile(template, directives);
compile = (rootEl, componentId) => testbed.compile(rootEl, componentId);
}
it('should create root views while using the given elements in place', () => {
it('should create root views while using the given elements in place', inject([AsyncTestCompleter], (async) => {
createRenderer();
var viewRefs = renderer.createView(rootProtoViewRef);
expect(viewRefs.length).toBe(1);
expect(viewRefs[0].delegate.rootNodes[0]).toEqual(rootEl);
});
renderer.createRootProtoView(rootEl, 'someComponentId').then( (rootProtoView) => {
expect(rootProtoView.elementBinders[0].directives[0].directiveIndex).toBe(0);
var viewRefs = renderer.createView(rootProtoView.render);
expect(viewRefs.length).toBe(1);
expect(viewRefs[0].delegate.rootNodes[0]).toEqual(rootEl);
async.done();
});
}));
it('should add a static component', inject([AsyncTestCompleter], (async) => {
createRenderer();
var template = new Template({
componentId: 'someComponent',
inline: 'hello',
directives: []
});
renderer.compile(template).then( (pv) => {
var mergedProtoViewRefs = renderer.mergeChildComponentProtoViews(rootProtoViewRef, [pv.render]);
renderer.createView(mergedProtoViewRefs[0]);
expect(rootEl).toHaveText('hello');
async.done();
renderer.createRootProtoView(rootEl, 'someComponentId').then( (rootProtoView) => {
var template = new Template({
componentId: 'someComponent',
inline: 'hello',
directives: []
});
renderer.compile(template).then( (pv) => {
renderer.mergeChildComponentProtoViews(rootProtoView.render, [pv.render]);
renderer.createView(rootProtoView.render);
expect(rootEl).toHaveText('hello');
async.done();
});
});
}));
it('should add a a dynamic component', inject([AsyncTestCompleter], (async) => {
createRenderer();
var template = new Template({
componentId: 'someComponent',
inline: 'hello',
directives: []
});
renderer.compile(template).then( (pv) => {
var rootViewRef = renderer.createView(rootProtoViewRef)[0];
var childComponentViewRef = renderer.createView(pv.render)[0];
renderer.setDynamicComponentView(rootViewRef, 0, childComponentViewRef);
expect(rootEl).toHaveText('hello');
async.done();
renderer.createRootProtoView(rootEl, 'someComponentId').then( (rootProtoView) => {
var template = new Template({
componentId: 'someComponent',
inline: 'hello',
directives: []
});
renderer.compile(template).then( (pv) => {
var rootViewRef = renderer.createView(rootProtoView.render)[0];
var childComponentViewRef = renderer.createView(pv.render)[0];
renderer.setDynamicComponentView(rootViewRef, 0, childComponentViewRef);
expect(rootEl).toHaveText('hello');
async.done();
});
});
}));
it('should update text nodes', inject([AsyncTestCompleter], (async) => {
createRenderer();
compile('{{a}}', [someComponent]).then( (pvRefs) => {
var viewRefs = renderer.createView(pvRefs[0]);
createRenderer({
templates: [new Template({
componentId: 'someComponent',
inline: '{{a}}',
directives: []
})]
});
compile(rootEl, 'someComponent').then( (rootProtoView) => {
var viewRefs = renderer.createView(rootProtoView.render);
renderer.setText(viewRefs[1], 0, 'hello');
expect(rootEl).toHaveText('hello');
async.done();
@ -86,9 +102,15 @@ export function main() {
}));
it('should update element properties', inject([AsyncTestCompleter], (async) => {
createRenderer();
compile('<input [value]="someProp">', []).then( (pvRefs) => {
var viewRefs = renderer.createView(pvRefs[0]);
createRenderer({
templates: [new Template({
componentId: 'someComponent',
inline: '<input [value]="someProp">',
directives: []
})]
});
compile(rootEl, 'someComponent').then( (rootProtoView) => {
var viewRefs = renderer.createView(rootProtoView.render);
renderer.setElementProperty(viewRefs[1], 0, 'value', 'hello');
expect(DOM.childNodes(rootEl)[0].value).toEqual('hello');
async.done();
@ -96,10 +118,17 @@ export function main() {
}));
it('should add and remove views to and from containers', inject([AsyncTestCompleter], (async) => {
createRenderer();
compile('<template>hello</template>', []).then( (pvRefs) => {
var viewRef = renderer.createView(pvRefs[0])[1];
var vcProtoViewRef = pvRefs[2];
createRenderer({
templates: [new Template({
componentId: 'someComponent',
inline: '<template>hello</template>',
directives: []
})]
});
compile(rootEl, 'someComponent').then( (rootProtoView) => {
var viewRef = renderer.createView(rootProtoView.render)[1];
var vcProtoViewRef = rootProtoView.elementBinders[0]
.nestedProtoView.elementBinders[0].nestedProtoView.render;
var vcRef = new ViewContainerRef(viewRef, 0);
var childViewRef = renderer.createView(vcProtoViewRef)[0];
@ -115,10 +144,17 @@ export function main() {
it('should cache views', inject([AsyncTestCompleter], (async) => {
createRenderer({
templates: [new Template({
componentId: 'someComponent',
inline: '<template>hello</template>',
directives: []
})],
viewCacheCapacity: 2
});
compile('<template>hello</template>', []).then( (pvRefs) => {
var vcProtoViewRef = pvRefs[2];
compile(rootEl, 'someComponent').then( (rootProtoView) => {
var vcProtoViewRef = rootProtoView.elementBinders[0]
.nestedProtoView.elementBinders[0].nestedProtoView.render;
var viewRef1 = renderer.createView(vcProtoViewRef)[0];
renderer.destroyView(viewRef1);
var viewRef2 = renderer.createView(vcProtoViewRef)[0];
@ -133,9 +169,15 @@ export function main() {
// TODO(tbosch): This is not working yet as we commented out
// the event expression processing...
xit('should handle events', inject([AsyncTestCompleter], (async) => {
createRenderer();
compile('<input (change)="$event.target.value">', []).then( (pvRefs) => {
var viewRef = renderer.createView(pvRefs[0])[1];
createRenderer({
templates: [new Template({
componentId: 'someComponent',
inline: '<input (change)="$event.target.value">',
directives: []
})]
});
compile(rootEl, 'someComponent').then( (rootProtoView) => {
var viewRef = renderer.createView(rootProtoView.render)[1];
var dispatcher = new LoggingEventDispatcher();
renderer.setEventDispatcher(viewRef, dispatcher);
var inputEl = DOM.childNodes(rootEl)[0];

View File

@ -1,7 +1,3 @@
import {
el
} from 'angular2/test_lib';
import {isBlank, isPresent, BaseException} from 'angular2/src/facade/lang';
import {MapWrapper, ListWrapper, List} from 'angular2/src/facade/collection';
import {PromiseWrapper, Promise} from 'angular2/src/facade/async';
@ -23,11 +19,8 @@ import {ViewFactory} from 'angular2/src/render/dom/view/view_factory';
export class IntegrationTestbed {
renderer;
parser;
rootEl;
rootProtoViewRef;
eventPlugin;
_templates:Map<string, Template>;
_compileCache:Map<string, Promise<List>>;
constructor({urlData, viewCacheCapacity, shadowDomStrategy, templates}) {
this._templates = MapWrapper.create();
@ -36,7 +29,6 @@ export class IntegrationTestbed {
MapWrapper.set(this._templates, template.componentId, template);
});
}
this._compileCache = MapWrapper.create();
var parser = new Parser(new Lexer());
var urlResolver = new UrlResolver();
if (isBlank(shadowDomStrategy)) {
@ -54,90 +46,66 @@ export class IntegrationTestbed {
var eventManager = new EventManager([this.eventPlugin], new FakeVmTurnZone());
var viewFactory = new ViewFactory(viewCacheCapacity, eventManager, shadowDomStrategy);
this.renderer = new DirectDomRenderer(compiler, viewFactory, shadowDomStrategy);
this.rootEl = el('<div></div>');
this.rootProtoViewRef = this.renderer.createRootProtoView(this.rootEl);
}
compile(templateHtml, directives):Promise<List<ProtoViewRef>> {
return this._compileRecurse(new Template({
componentId: 'root',
inline: templateHtml,
directives: directives
})).then( (protoViewRefs) => {
return this._flattenList([
this.renderer.mergeChildComponentProtoViews(this.rootProtoViewRef, [protoViewRefs[0]]),
protoViewRefs
compile(rootEl, componentId):Promise<ProtoView> {
return this.renderer.createRootProtoView(rootEl, componentId).then( (rootProtoView) => {
return this._compileNestedProtoViews(rootProtoView, [
new DirectiveMetadata({
type: DirectiveMetadata.COMPONENT_TYPE,
id: componentId
})
]);
});
}
_compileRecurse(template):Promise<List<ProtoViewRef>> {
var result = MapWrapper.get(this._compileCache, template.componentId);
if (isPresent(result)) {
return result;
}
result = this.renderer.compile(template).then( (pv) => {
var childComponentPromises = ListWrapper.map(
this._findNestedComponentIds(template, pv),
(componentId) => {
var childTemplate = MapWrapper.get(this._templates, componentId);
if (isBlank(childTemplate)) {
throw new BaseException(`Could not find template for ${componentId}!`);
}
return this._compileRecurse(childTemplate);
}
);
return PromiseWrapper.all(childComponentPromises).then(
(protoViewRefsWithChildren) => {
var protoViewRefs =
ListWrapper.map(protoViewRefsWithChildren, (arr) => arr[0]);
return this._flattenList([
this.renderer.mergeChildComponentProtoViews(pv.render, protoViewRefs),
protoViewRefsWithChildren
]);
}
);
_compile(template):Promise<ProtoView> {
return this.renderer.compile(template).then( (protoView) => {
return this._compileNestedProtoViews(protoView, template.directives);
});
MapWrapper.set(this._compileCache, template.componentId, result);
return result;
}
_findNestedComponentIds(template, pv, target = null):List<string> {
if (isBlank(target)) {
target = [];
}
for (var binderIdx=0; binderIdx<pv.elementBinders.length; binderIdx++) {
var eb = pv.elementBinders[binderIdx];
var componentDirective;
ListWrapper.forEach(eb.directives, (db) => {
var meta = template.directives[db.directiveIndex];
if (meta.type === DirectiveMetadata.COMPONENT_TYPE) {
componentDirective = meta;
_compileNestedProtoViews(protoView, directives):Promise<ProtoView> {
var childComponentRenderPvRefs = [];
var nestedPVPromises = [];
ListWrapper.forEach(protoView.elementBinders, (elementBinder) => {
var nestedComponentId = null;
ListWrapper.forEach(elementBinder.directives, (db) => {
var directiveMeta = directives[db.directiveIndex];
if (directiveMeta.type === DirectiveMetadata.COMPONENT_TYPE) {
nestedComponentId = directiveMeta.id;
}
});
if (isPresent(componentDirective)) {
ListWrapper.push(target, componentDirective.id);
} else if (isPresent(eb.nestedProtoView)) {
this._findNestedComponentIds(template, eb.nestedProtoView, target);
var nestedCall;
if (isPresent(nestedComponentId)) {
var childTemplate = MapWrapper.get(this._templates, nestedComponentId);
if (isBlank(childTemplate)) {
throw new BaseException(`Could not find template for ${nestedComponentId}!`);
}
nestedCall = this._compile(childTemplate);
} else if (isPresent(elementBinder.nestedProtoView)) {
nestedCall = this._compileNestedProtoViews(elementBinder.nestedProtoView, directives);
}
}
return target;
}
_flattenList(tree:List, out:List = null):List {
if (isBlank(out)) {
out = [];
}
for (var i = 0; i < tree.length; i++) {
var item = tree[i];
if (ListWrapper.isList(item)) {
this._flattenList(item, out);
} else {
ListWrapper.push(out, item);
if (isPresent(nestedCall)) {
ListWrapper.push(
nestedPVPromises,
nestedCall.then( (nestedPv) => {
elementBinder.nestedProtoView = nestedPv;
if (isPresent(nestedComponentId)) {
ListWrapper.push(childComponentRenderPvRefs, nestedPv.render);
}
})
);
}
});
if (nestedPVPromises.length > 0) {
return PromiseWrapper.all(nestedPVPromises).then((_) => {
this.renderer.mergeChildComponentProtoViews(protoView.render, childComponentRenderPvRefs);
return protoView;
});
} else {
return PromiseWrapper.resolve(protoView);
}
return out;
}
}

View File

@ -40,34 +40,45 @@ export function main() {
StringMapWrapper.set(strategies, "native", () => new NativeShadowDomStrategy(styleUrlResolver));
}
beforeEach( () => {
urlResolver = new UrlResolver();
styleUrlResolver = new StyleUrlResolver(urlResolver);
styleInliner = new StyleInliner(null, styleUrlResolver, urlResolver);
});
StringMapWrapper.forEach(strategies,
(strategyFactory, name) => {
describe(`${name} shadow dom strategy`, () => {
var testbed, renderer, rootEl, compile, strategy;
var testbed, renderer, rootEl, compile;
beforeEach( () => {
urlResolver = new UrlResolver();
styleUrlResolver = new StyleUrlResolver(urlResolver);
styleInliner = new StyleInliner(null, styleUrlResolver, urlResolver);
strategy = strategyFactory();
function createRenderer({templates}) {
testbed = new IntegrationTestbed({
shadowDomStrategy: strategy,
templates: templates
shadowDomStrategy: strategyFactory(),
templates: ListWrapper.concat(templates, componentTemplates)
});
renderer = testbed.renderer;
rootEl = testbed.rootEl;
compile = (template, directives) => testbed.compile(template, directives);
compile = (rootEl, componentId) => testbed.compile(rootEl, componentId);
}
beforeEach( () => {
rootEl = el('<div></div>');
});
it('should support simple components', inject([AsyncTestCompleter], (async) => {
var temp = '<simple>' +
'<div>A</div>' +
'</simple>';
compile(temp, [simple]).then( (pvRefs) => {
renderer.createView(pvRefs[0]);
createRenderer({
templates: [new Template({
componentId: 'main',
inline: '<simple>' +
'<div>A</div>' +
'</simple>',
directives: [simple]
})]
});
compile(rootEl, 'main').then( (pv) => {
renderer.createView(pv.render);
expect(rootEl).toHaveText('SIMPLE(A)');
@ -76,14 +87,19 @@ export function main() {
}));
it('should support multiple content tags', inject([AsyncTestCompleter], (async) => {
var temp = '<multiple-content-tags>' +
'<div>B</div>' +
'<div>C</div>' +
'<div class="left">A</div>' +
'</multiple-content-tags>';
compile(temp, [multipleContentTagsComponent]).then( (pvRefs) => {
renderer.createView(pvRefs[0]);
createRenderer({
templates: [new Template({
componentId: 'main',
inline: '<multiple-content-tags>' +
'<div>B</div>' +
'<div>C</div>' +
'<div class="left">A</div>' +
'</multiple-content-tags>',
directives: [multipleContentTagsComponent]
})]
});
compile(rootEl, 'main').then( (pv) => {
renderer.createView(pv.render);
expect(rootEl).toHaveText('(A, BC)');
@ -92,13 +108,18 @@ export function main() {
}));
it('should redistribute only direct children', inject([AsyncTestCompleter], (async) => {
var temp = '<multiple-content-tags>' +
'<div>B<div class="left">A</div></div>' +
'<div>C</div>' +
'</multiple-content-tags>';
compile(temp, [multipleContentTagsComponent]).then( (pvRefs) => {
renderer.createView(pvRefs[0]);
createRenderer({
templates: [new Template({
componentId: 'main',
inline: '<multiple-content-tags>' +
'<div>B<div class="left">A</div></div>' +
'<div>C</div>' +
'</multiple-content-tags>',
directives: [multipleContentTagsComponent]
})]
});
compile(rootEl, 'main').then( (pv) => {
renderer.createView(pv.render);
expect(rootEl).toHaveText('(, BAC)');
@ -107,15 +128,21 @@ export function main() {
}));
it("should redistribute direct child viewcontainers when the light dom changes", inject([AsyncTestCompleter], (async) => {
var temp = '<multiple-content-tags>' +
'<div><div template="manual" class="left">A</div></div>' +
'<div>B</div>' +
'</multiple-content-tags>';
compile(temp, [multipleContentTagsComponent, manualViewportDirective]).then( (pvRefs) => {
var viewRefs = renderer.createView(pvRefs[0]);
createRenderer({
templates: [new Template({
componentId: 'main',
inline: '<multiple-content-tags>' +
'<div><div template="manual" class="left">A</div></div>' +
'<div>B</div>' +
'</multiple-content-tags>',
directives: [multipleContentTagsComponent, manualViewportDirective]
})]
});
compile(rootEl, 'main').then( (pv) => {
var viewRefs = renderer.createView(pv.render);
var vcRef = new ViewContainerRef(viewRefs[1], 1);
var vcProtoViewRef = pvRefs[2];
var vcProtoViewRef = pv.elementBinders[0].nestedProtoView
.elementBinders[1].nestedProtoView.render;
var childViewRef = renderer.createView(vcProtoViewRef)[0];
expect(rootEl).toHaveText('(, B)');
@ -133,15 +160,21 @@ export function main() {
}));
it("should redistribute when the light dom changes", inject([AsyncTestCompleter], (async) => {
var temp = '<multiple-content-tags>' +
'<div template="manual" class="left">A</div>' +
'<div>B</div>' +
'</multiple-content-tags>';
compile(temp, [multipleContentTagsComponent, manualViewportDirective]).then( (pvRefs) => {
var viewRefs = renderer.createView(pvRefs[0]);
createRenderer({
templates: [new Template({
componentId: 'main',
inline: '<multiple-content-tags>' +
'<div template="manual" class="left">A</div>' +
'<div>B</div>' +
'</multiple-content-tags>',
directives: [multipleContentTagsComponent, manualViewportDirective]
})]
});
compile(rootEl, 'main').then( (pv) => {
var viewRefs = renderer.createView(pv.render);
var vcRef = new ViewContainerRef(viewRefs[1], 1);
var vcProtoViewRef = pvRefs[2];
var vcProtoViewRef = pv.elementBinders[0].nestedProtoView
.elementBinders[1].nestedProtoView.render;
var childViewRef = renderer.createView(vcProtoViewRef)[0];
expect(rootEl).toHaveText('(, B)');
@ -159,13 +192,18 @@ export function main() {
}));
it("should support nested components", inject([AsyncTestCompleter], (async) => {
var temp = '<outer-with-indirect-nested>' +
'<div>A</div>' +
'<div>B</div>' +
'</outer-with-indirect-nested>';
compile(temp, [outerWithIndirectNestedComponent]).then( (pvRefs) => {
renderer.createView(pvRefs[0]);
createRenderer({
templates: [new Template({
componentId: 'main',
inline: '<outer-with-indirect-nested>' +
'<div>A</div>' +
'<div>B</div>' +
'</outer-with-indirect-nested>',
directives: [outerWithIndirectNestedComponent]
})]
});
compile(rootEl, 'main').then( (pv) => {
renderer.createView(pv.render);
expect(rootEl).toHaveText('OUTER(SIMPLE(AB))');
@ -174,16 +212,22 @@ export function main() {
}));
it("should support nesting with content being direct child of a nested component", inject([AsyncTestCompleter], (async) => {
var temp = '<outer>' +
'<div template="manual" class="left">A</div>' +
'<div>B</div>' +
'<div>C</div>' +
'</outer>';
compile(temp, [outerComponent, manualViewportDirective]).then( (pvRefs) => {
var viewRefs = renderer.createView(pvRefs[0]);
createRenderer({
templates: [new Template({
componentId: 'main',
inline: '<outer>' +
'<div template="manual" class="left">A</div>' +
'<div>B</div>' +
'<div>C</div>' +
'</outer>',
directives: [outerComponent, manualViewportDirective]
})]
});
compile(rootEl, 'main').then( (pv) => {
var viewRefs = renderer.createView(pv.render);
var vcRef = new ViewContainerRef(viewRefs[1], 1);
var vcProtoViewRef = pvRefs[2];
var vcProtoViewRef = pv.elementBinders[0].nestedProtoView
.elementBinders[1].nestedProtoView.render;
var childViewRef = renderer.createView(vcProtoViewRef)[0];
expect(rootEl).toHaveText('OUTER(INNER(INNERINNER(,BC)))');
@ -196,16 +240,23 @@ export function main() {
}));
it('should redistribute when the shadow dom changes', inject([AsyncTestCompleter], (async) => {
var temp = '<conditional-content>' +
'<div class="left">A</div>' +
'<div>B</div>' +
'<div>C</div>' +
'</conditional-content>';
compile(temp, [conditionalContentComponent, autoViewportDirective]).then( (pvRefs) => {
var viewRefs = renderer.createView(pvRefs[0]);
createRenderer({
templates: [new Template({
componentId: 'main',
inline: '<conditional-content>' +
'<div class="left">A</div>' +
'<div>B</div>' +
'<div>C</div>' +
'</conditional-content>',
directives: [conditionalContentComponent]
})]
});
compile(rootEl, 'main').then( (pv) => {
var viewRefs = renderer.createView(pv.render);
var vcRef = new ViewContainerRef(viewRefs[2], 0);
var vcProtoViewRef = pvRefs[3];
var vcProtoViewRef = pv.elementBinders[0].nestedProtoView
.elementBinders[0].nestedProtoView
.elementBinders[0].nestedProtoView.render;
var childViewRef = renderer.createView(vcProtoViewRef)[0];
expect(rootEl).toHaveText('(, ABC)');
@ -299,7 +350,7 @@ var autoViewportDirective = new DirectiveMetadata({
type: DirectiveMetadata.VIEWPORT_TYPE
});
var templates = [
var componentTemplates = [
new Template({
componentId: 'simple',
inline: 'SIMPLE(<content></content>)',