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
@@ -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];
+46 -78
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;
}
}
@@ -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>)',