fix(shadow_dom): redistribute light dom when a dynamic component is attached.

Fixes #1077
Closes #1315
This commit is contained in:
Tobias Bosch 2015-04-10 16:57:26 -07:00
parent daf0f472b3
commit 8499cf84c3
5 changed files with 138 additions and 23 deletions

View File

@ -69,6 +69,9 @@ export class RenderView {
this.componentChildViews[elementIndex] = childView;
if (this._hydrated) {
childView.hydrate(lightDom);
if (isPresent(lightDom)) {
lightDom.redistribute();
}
}
}

View File

@ -21,7 +21,7 @@ import {IntegrationTestbed, LoggingEventDispatcher, FakeEvent} from './integrati
export function main() {
describe('DirectDomRenderer integration', () => {
var testbed, renderer, eventPlugin, compile, rootEl;
var testbed, renderer, eventPlugin, compileRoot, rootEl;
beforeEach(() => {
rootEl = el('<div></div>');
@ -36,7 +36,7 @@ export function main() {
});
renderer = testbed.renderer;
eventPlugin = testbed.eventPlugin;
compile = (rootEl, componentId) => testbed.compile(rootEl, componentId);
compileRoot = (rootEl, componentId) => testbed.compileRoot(rootEl, componentId);
}
it('should create root views while using the given elements in place', inject([AsyncTestCompleter], (async) => {
@ -93,7 +93,7 @@ export function main() {
directives: []
})]
});
compile(rootEl, 'someComponent').then( (rootProtoView) => {
compileRoot(rootEl, 'someComponent').then( (rootProtoView) => {
var viewRefs = renderer.createView(rootProtoView.render);
renderer.setText(viewRefs[1], 0, 'hello');
expect(rootEl).toHaveText('hello');
@ -109,7 +109,7 @@ export function main() {
directives: []
})]
});
compile(rootEl, 'someComponent').then( (rootProtoView) => {
compileRoot(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');
@ -125,7 +125,7 @@ export function main() {
directives: []
})]
});
compile(rootEl, 'someComponent').then( (rootProtoView) => {
compileRoot(rootEl, 'someComponent').then( (rootProtoView) => {
var viewRef = renderer.createView(rootProtoView.render)[1];
var vcProtoViewRef = rootProtoView.elementBinders[0]
.nestedProtoView.elementBinders[0].nestedProtoView.render;
@ -151,7 +151,7 @@ export function main() {
})],
viewCacheCapacity: 2
});
compile(rootEl, 'someComponent').then( (rootProtoView) => {
compileRoot(rootEl, 'someComponent').then( (rootProtoView) => {
var vcProtoViewRef = rootProtoView.elementBinders[0]
.nestedProtoView.elementBinders[0].nestedProtoView.render;
@ -176,7 +176,7 @@ export function main() {
directives: []
})]
});
compile(rootEl, 'someComponent').then( (rootProtoView) => {
compileRoot(rootEl, 'someComponent').then( (rootProtoView) => {
var viewRef = renderer.createView(rootProtoView.render)[1];
var dispatcher = new LoggingEventDispatcher();
renderer.setEventDispatcher(viewRef, dispatcher);

View File

@ -48,7 +48,7 @@ export class IntegrationTestbed {
this.renderer = new DirectDomRenderer(compiler, viewFactory, shadowDomStrategy);
}
compile(rootEl, componentId):Promise<ProtoViewDto> {
compileRoot(rootEl, componentId):Promise<ProtoViewDto> {
return this.renderer.createRootProtoView(rootEl, componentId).then( (rootProtoView) => {
return this._compileNestedProtoViews(rootProtoView, [
new DirectiveMetadata({
@ -59,9 +59,13 @@ export class IntegrationTestbed {
});
}
_compile(template):Promise<ProtoViewDto> {
return this.renderer.compile(template).then( (protoView) => {
return this._compileNestedProtoViews(protoView, template.directives);
compile(componentId):Promise<ProtoViewDto> {
var childTemplate = MapWrapper.get(this._templates, componentId);
if (isBlank(childTemplate)) {
throw new BaseException(`No template for component ${componentId}`);
}
return this.renderer.compile(childTemplate).then( (protoView) => {
return this._compileNestedProtoViews(protoView, childTemplate.directives);
});
}
@ -80,9 +84,11 @@ export class IntegrationTestbed {
if (isPresent(nestedComponentId)) {
var childTemplate = MapWrapper.get(this._templates, nestedComponentId);
if (isBlank(childTemplate)) {
throw new BaseException(`Could not find template for ${nestedComponentId}!`);
// dynamic component
ListWrapper.push(childComponentRenderPvRefs, null);
} else {
nestedCall = this.compile(nestedComponentId);
}
nestedCall = this._compile(childTemplate);
} else if (isPresent(elementBinder.nestedProtoView)) {
nestedCall = this._compileNestedProtoViews(elementBinder.nestedProtoView, directives);
}

View File

@ -52,7 +52,7 @@ export function main() {
describe(`${name} shadow dom strategy`, () => {
var testbed, renderer, rootEl, compile;
var testbed, renderer, rootEl, compile, compileRoot;
function createRenderer({templates}) {
testbed = new IntegrationTestbed({
@ -60,7 +60,8 @@ export function main() {
templates: ListWrapper.concat(templates, componentTemplates)
});
renderer = testbed.renderer;
compile = (rootEl, componentId) => testbed.compile(rootEl, componentId);
compileRoot = (rootEl, componentId) => testbed.compileRoot(rootEl, componentId);
compile = (componentId) => testbed.compile(componentId);
}
beforeEach( () => {
@ -77,7 +78,7 @@ export function main() {
directives: [simple]
})]
});
compile(rootEl, 'main').then( (pv) => {
compileRoot(rootEl, 'main').then( (pv) => {
renderer.createView(pv.render);
expect(rootEl).toHaveText('SIMPLE(A)');
@ -86,6 +87,29 @@ export function main() {
});
}));
it('should support dynamic components', inject([AsyncTestCompleter], (async) => {
createRenderer({
templates: [new ViewDefinition({
componentId: 'main',
template: '<dynamic>' +
'<div>A</div>' +
'</dynamic>',
directives: [dynamicComponent]
})]
});
compileRoot(rootEl, 'main').then( (rootPv) => {
compile('simple').then( (simplePv) => {
var views = renderer.createView(rootPv.render);
var simpleViews = renderer.createView(simplePv.render);
renderer.setDynamicComponentView(views[1], 0, simpleViews[0]);
expect(rootEl).toHaveText('SIMPLE(A)');
async.done();
});
});
}));
it('should support multiple content tags', inject([AsyncTestCompleter], (async) => {
createRenderer({
templates: [new ViewDefinition({
@ -98,7 +122,7 @@ export function main() {
directives: [multipleContentTagsComponent]
})]
});
compile(rootEl, 'main').then( (pv) => {
compileRoot(rootEl, 'main').then( (pv) => {
renderer.createView(pv.render);
expect(rootEl).toHaveText('(A, BC)');
@ -118,7 +142,7 @@ export function main() {
directives: [multipleContentTagsComponent]
})]
});
compile(rootEl, 'main').then( (pv) => {
compileRoot(rootEl, 'main').then( (pv) => {
renderer.createView(pv.render);
expect(rootEl).toHaveText('(, BAC)');
@ -138,7 +162,7 @@ export function main() {
directives: [multipleContentTagsComponent, manualViewportDirective]
})]
});
compile(rootEl, 'main').then( (pv) => {
compileRoot(rootEl, 'main').then( (pv) => {
var viewRefs = renderer.createView(pv.render);
var vcRef = new ViewContainerRef(viewRefs[1], 1);
var vcProtoViewRef = pv.elementBinders[0].nestedProtoView
@ -170,7 +194,7 @@ export function main() {
directives: [multipleContentTagsComponent, manualViewportDirective]
})]
});
compile(rootEl, 'main').then( (pv) => {
compileRoot(rootEl, 'main').then( (pv) => {
var viewRefs = renderer.createView(pv.render);
var vcRef = new ViewContainerRef(viewRefs[1], 1);
var vcProtoViewRef = pv.elementBinders[0].nestedProtoView
@ -202,7 +226,7 @@ export function main() {
directives: [outerWithIndirectNestedComponent]
})]
});
compile(rootEl, 'main').then( (pv) => {
compileRoot(rootEl, 'main').then( (pv) => {
renderer.createView(pv.render);
expect(rootEl).toHaveText('OUTER(SIMPLE(AB))');
@ -223,7 +247,7 @@ export function main() {
directives: [outerComponent, manualViewportDirective]
})]
});
compile(rootEl, 'main').then( (pv) => {
compileRoot(rootEl, 'main').then( (pv) => {
var viewRefs = renderer.createView(pv.render);
var vcRef = new ViewContainerRef(viewRefs[1], 1);
var vcProtoViewRef = pv.elementBinders[0].nestedProtoView
@ -251,7 +275,7 @@ export function main() {
directives: [conditionalContentComponent]
})]
});
compile(rootEl, 'main').then( (pv) => {
compileRoot(rootEl, 'main').then( (pv) => {
var viewRefs = renderer.createView(pv.render);
var vcRef = new ViewContainerRef(viewRefs[2], 0);
var vcProtoViewRef = pv.elementBinders[0].nestedProtoView
@ -302,6 +326,12 @@ var simple = new DirectiveMetadata({
type: DirectiveMetadata.COMPONENT_TYPE
});
var dynamicComponent = new DirectiveMetadata({
selector: 'dynamic',
id: 'dynamic',
type: DirectiveMetadata.COMPONENT_TYPE
});
var multipleContentTagsComponent = new DirectiveMetadata({
selector: 'multiple-content-tags',
id: 'multiple-content-tags',

View File

@ -0,0 +1,76 @@
import {describe, ddescribe, it, iit, xit, xdescribe, expect, beforeEach, el} from 'angular2/test_lib';
import {ListWrapper} from 'angular2/src/facade/collection';
import {RenderView} from 'angular2/src/render/dom/view/view';
import {ShadowDomStrategy} from 'angular2/src/render/dom/shadow_dom/shadow_dom_strategy';
import {LightDom} from 'angular2/src/render/dom/shadow_dom/light_dom';
export function main() {
function createView() {
var proto = null;
var rootNodes = [el('<div></div>')];
var boundTextNodes = [];
var boundElements = [el('<div></div>')];
var viewContainers = [];
var contentTags = [];
return new RenderView(proto, rootNodes,
boundTextNodes, boundElements, viewContainers, contentTags);
}
function createShadowDomStrategy(log) {
return new FakeShadowDomStrategy(log);
}
describe('RenderView', () => {
var log, strategy;
beforeEach( () => {
log = [];
strategy = createShadowDomStrategy(log);
});
describe('setComponentView', () => {
it('should redistribute when a component is added to a hydrated view', () => {
var hostView = createView();
var childView = createView();
hostView.hydrate(null);
hostView.setComponentView(strategy, 0, childView);
expect(log[0]).toEqual(['redistribute']);
});
it('should not redistribute when a component is added to a dehydrated view', () => {
var hostView = createView();
var childView = createView();
hostView.setComponentView(strategy, 0, childView);
expect(log).toEqual([]);
});
});
});
}
class FakeShadowDomStrategy extends ShadowDomStrategy {
log;
constructor(log) {
super();
this.log = log;
}
constructLightDom(lightDomView:RenderView, shadowDomView:RenderView, element): LightDom {
return new FakeLightDom(this.log, lightDomView, shadowDomView, element);
}
}
class FakeLightDom extends LightDom {
log;
constructor(log, lightDomView:RenderView, shadowDomView:RenderView, element) {
super(lightDomView, shadowDomView, element);
this.log = log;
}
redistribute() {
ListWrapper.push(this.log, ['redistribute']);
}
}