fix(shadow_dom): redistribute light dom when a dynamic component is attached.
Fixes #1077 Closes #1315
This commit is contained in:
parent
daf0f472b3
commit
8499cf84c3
3
modules/angular2/src/render/dom/view/view.js
vendored
3
modules/angular2/src/render/dom/view/view.js
vendored
@ -69,6 +69,9 @@ export class RenderView {
|
|||||||
this.componentChildViews[elementIndex] = childView;
|
this.componentChildViews[elementIndex] = childView;
|
||||||
if (this._hydrated) {
|
if (this._hydrated) {
|
||||||
childView.hydrate(lightDom);
|
childView.hydrate(lightDom);
|
||||||
|
if (isPresent(lightDom)) {
|
||||||
|
lightDom.redistribute();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ import {IntegrationTestbed, LoggingEventDispatcher, FakeEvent} from './integrati
|
|||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
describe('DirectDomRenderer integration', () => {
|
describe('DirectDomRenderer integration', () => {
|
||||||
var testbed, renderer, eventPlugin, compile, rootEl;
|
var testbed, renderer, eventPlugin, compileRoot, rootEl;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
rootEl = el('<div></div>');
|
rootEl = el('<div></div>');
|
||||||
@ -36,7 +36,7 @@ export function main() {
|
|||||||
});
|
});
|
||||||
renderer = testbed.renderer;
|
renderer = testbed.renderer;
|
||||||
eventPlugin = testbed.eventPlugin;
|
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) => {
|
it('should create root views while using the given elements in place', inject([AsyncTestCompleter], (async) => {
|
||||||
@ -93,7 +93,7 @@ export function main() {
|
|||||||
directives: []
|
directives: []
|
||||||
})]
|
})]
|
||||||
});
|
});
|
||||||
compile(rootEl, 'someComponent').then( (rootProtoView) => {
|
compileRoot(rootEl, 'someComponent').then( (rootProtoView) => {
|
||||||
var viewRefs = renderer.createView(rootProtoView.render);
|
var viewRefs = renderer.createView(rootProtoView.render);
|
||||||
renderer.setText(viewRefs[1], 0, 'hello');
|
renderer.setText(viewRefs[1], 0, 'hello');
|
||||||
expect(rootEl).toHaveText('hello');
|
expect(rootEl).toHaveText('hello');
|
||||||
@ -109,7 +109,7 @@ export function main() {
|
|||||||
directives: []
|
directives: []
|
||||||
})]
|
})]
|
||||||
});
|
});
|
||||||
compile(rootEl, 'someComponent').then( (rootProtoView) => {
|
compileRoot(rootEl, 'someComponent').then( (rootProtoView) => {
|
||||||
var viewRefs = renderer.createView(rootProtoView.render);
|
var viewRefs = renderer.createView(rootProtoView.render);
|
||||||
renderer.setElementProperty(viewRefs[1], 0, 'value', 'hello');
|
renderer.setElementProperty(viewRefs[1], 0, 'value', 'hello');
|
||||||
expect(DOM.childNodes(rootEl)[0].value).toEqual('hello');
|
expect(DOM.childNodes(rootEl)[0].value).toEqual('hello');
|
||||||
@ -125,7 +125,7 @@ export function main() {
|
|||||||
directives: []
|
directives: []
|
||||||
})]
|
})]
|
||||||
});
|
});
|
||||||
compile(rootEl, 'someComponent').then( (rootProtoView) => {
|
compileRoot(rootEl, 'someComponent').then( (rootProtoView) => {
|
||||||
var viewRef = renderer.createView(rootProtoView.render)[1];
|
var viewRef = renderer.createView(rootProtoView.render)[1];
|
||||||
var vcProtoViewRef = rootProtoView.elementBinders[0]
|
var vcProtoViewRef = rootProtoView.elementBinders[0]
|
||||||
.nestedProtoView.elementBinders[0].nestedProtoView.render;
|
.nestedProtoView.elementBinders[0].nestedProtoView.render;
|
||||||
@ -151,7 +151,7 @@ export function main() {
|
|||||||
})],
|
})],
|
||||||
viewCacheCapacity: 2
|
viewCacheCapacity: 2
|
||||||
});
|
});
|
||||||
compile(rootEl, 'someComponent').then( (rootProtoView) => {
|
compileRoot(rootEl, 'someComponent').then( (rootProtoView) => {
|
||||||
var vcProtoViewRef = rootProtoView.elementBinders[0]
|
var vcProtoViewRef = rootProtoView.elementBinders[0]
|
||||||
.nestedProtoView.elementBinders[0].nestedProtoView.render;
|
.nestedProtoView.elementBinders[0].nestedProtoView.render;
|
||||||
|
|
||||||
@ -176,7 +176,7 @@ export function main() {
|
|||||||
directives: []
|
directives: []
|
||||||
})]
|
})]
|
||||||
});
|
});
|
||||||
compile(rootEl, 'someComponent').then( (rootProtoView) => {
|
compileRoot(rootEl, 'someComponent').then( (rootProtoView) => {
|
||||||
var viewRef = renderer.createView(rootProtoView.render)[1];
|
var viewRef = renderer.createView(rootProtoView.render)[1];
|
||||||
var dispatcher = new LoggingEventDispatcher();
|
var dispatcher = new LoggingEventDispatcher();
|
||||||
renderer.setEventDispatcher(viewRef, dispatcher);
|
renderer.setEventDispatcher(viewRef, dispatcher);
|
||||||
|
@ -48,7 +48,7 @@ export class IntegrationTestbed {
|
|||||||
this.renderer = new DirectDomRenderer(compiler, viewFactory, shadowDomStrategy);
|
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.renderer.createRootProtoView(rootEl, componentId).then( (rootProtoView) => {
|
||||||
return this._compileNestedProtoViews(rootProtoView, [
|
return this._compileNestedProtoViews(rootProtoView, [
|
||||||
new DirectiveMetadata({
|
new DirectiveMetadata({
|
||||||
@ -59,9 +59,13 @@ export class IntegrationTestbed {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_compile(template):Promise<ProtoViewDto> {
|
compile(componentId):Promise<ProtoViewDto> {
|
||||||
return this.renderer.compile(template).then( (protoView) => {
|
var childTemplate = MapWrapper.get(this._templates, componentId);
|
||||||
return this._compileNestedProtoViews(protoView, template.directives);
|
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)) {
|
if (isPresent(nestedComponentId)) {
|
||||||
var childTemplate = MapWrapper.get(this._templates, nestedComponentId);
|
var childTemplate = MapWrapper.get(this._templates, nestedComponentId);
|
||||||
if (isBlank(childTemplate)) {
|
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)) {
|
} else if (isPresent(elementBinder.nestedProtoView)) {
|
||||||
nestedCall = this._compileNestedProtoViews(elementBinder.nestedProtoView, directives);
|
nestedCall = this._compileNestedProtoViews(elementBinder.nestedProtoView, directives);
|
||||||
}
|
}
|
||||||
|
@ -52,7 +52,7 @@ export function main() {
|
|||||||
|
|
||||||
describe(`${name} shadow dom strategy`, () => {
|
describe(`${name} shadow dom strategy`, () => {
|
||||||
|
|
||||||
var testbed, renderer, rootEl, compile;
|
var testbed, renderer, rootEl, compile, compileRoot;
|
||||||
|
|
||||||
function createRenderer({templates}) {
|
function createRenderer({templates}) {
|
||||||
testbed = new IntegrationTestbed({
|
testbed = new IntegrationTestbed({
|
||||||
@ -60,7 +60,8 @@ export function main() {
|
|||||||
templates: ListWrapper.concat(templates, componentTemplates)
|
templates: ListWrapper.concat(templates, componentTemplates)
|
||||||
});
|
});
|
||||||
renderer = testbed.renderer;
|
renderer = testbed.renderer;
|
||||||
compile = (rootEl, componentId) => testbed.compile(rootEl, componentId);
|
compileRoot = (rootEl, componentId) => testbed.compileRoot(rootEl, componentId);
|
||||||
|
compile = (componentId) => testbed.compile(componentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeEach( () => {
|
beforeEach( () => {
|
||||||
@ -77,7 +78,7 @@ export function main() {
|
|||||||
directives: [simple]
|
directives: [simple]
|
||||||
})]
|
})]
|
||||||
});
|
});
|
||||||
compile(rootEl, 'main').then( (pv) => {
|
compileRoot(rootEl, 'main').then( (pv) => {
|
||||||
renderer.createView(pv.render);
|
renderer.createView(pv.render);
|
||||||
|
|
||||||
expect(rootEl).toHaveText('SIMPLE(A)');
|
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) => {
|
it('should support multiple content tags', inject([AsyncTestCompleter], (async) => {
|
||||||
createRenderer({
|
createRenderer({
|
||||||
templates: [new ViewDefinition({
|
templates: [new ViewDefinition({
|
||||||
@ -98,7 +122,7 @@ export function main() {
|
|||||||
directives: [multipleContentTagsComponent]
|
directives: [multipleContentTagsComponent]
|
||||||
})]
|
})]
|
||||||
});
|
});
|
||||||
compile(rootEl, 'main').then( (pv) => {
|
compileRoot(rootEl, 'main').then( (pv) => {
|
||||||
renderer.createView(pv.render);
|
renderer.createView(pv.render);
|
||||||
|
|
||||||
expect(rootEl).toHaveText('(A, BC)');
|
expect(rootEl).toHaveText('(A, BC)');
|
||||||
@ -118,7 +142,7 @@ export function main() {
|
|||||||
directives: [multipleContentTagsComponent]
|
directives: [multipleContentTagsComponent]
|
||||||
})]
|
})]
|
||||||
});
|
});
|
||||||
compile(rootEl, 'main').then( (pv) => {
|
compileRoot(rootEl, 'main').then( (pv) => {
|
||||||
renderer.createView(pv.render);
|
renderer.createView(pv.render);
|
||||||
|
|
||||||
expect(rootEl).toHaveText('(, BAC)');
|
expect(rootEl).toHaveText('(, BAC)');
|
||||||
@ -138,7 +162,7 @@ export function main() {
|
|||||||
directives: [multipleContentTagsComponent, manualViewportDirective]
|
directives: [multipleContentTagsComponent, manualViewportDirective]
|
||||||
})]
|
})]
|
||||||
});
|
});
|
||||||
compile(rootEl, 'main').then( (pv) => {
|
compileRoot(rootEl, 'main').then( (pv) => {
|
||||||
var viewRefs = renderer.createView(pv.render);
|
var viewRefs = renderer.createView(pv.render);
|
||||||
var vcRef = new ViewContainerRef(viewRefs[1], 1);
|
var vcRef = new ViewContainerRef(viewRefs[1], 1);
|
||||||
var vcProtoViewRef = pv.elementBinders[0].nestedProtoView
|
var vcProtoViewRef = pv.elementBinders[0].nestedProtoView
|
||||||
@ -170,7 +194,7 @@ export function main() {
|
|||||||
directives: [multipleContentTagsComponent, manualViewportDirective]
|
directives: [multipleContentTagsComponent, manualViewportDirective]
|
||||||
})]
|
})]
|
||||||
});
|
});
|
||||||
compile(rootEl, 'main').then( (pv) => {
|
compileRoot(rootEl, 'main').then( (pv) => {
|
||||||
var viewRefs = renderer.createView(pv.render);
|
var viewRefs = renderer.createView(pv.render);
|
||||||
var vcRef = new ViewContainerRef(viewRefs[1], 1);
|
var vcRef = new ViewContainerRef(viewRefs[1], 1);
|
||||||
var vcProtoViewRef = pv.elementBinders[0].nestedProtoView
|
var vcProtoViewRef = pv.elementBinders[0].nestedProtoView
|
||||||
@ -202,7 +226,7 @@ export function main() {
|
|||||||
directives: [outerWithIndirectNestedComponent]
|
directives: [outerWithIndirectNestedComponent]
|
||||||
})]
|
})]
|
||||||
});
|
});
|
||||||
compile(rootEl, 'main').then( (pv) => {
|
compileRoot(rootEl, 'main').then( (pv) => {
|
||||||
renderer.createView(pv.render);
|
renderer.createView(pv.render);
|
||||||
|
|
||||||
expect(rootEl).toHaveText('OUTER(SIMPLE(AB))');
|
expect(rootEl).toHaveText('OUTER(SIMPLE(AB))');
|
||||||
@ -223,7 +247,7 @@ export function main() {
|
|||||||
directives: [outerComponent, manualViewportDirective]
|
directives: [outerComponent, manualViewportDirective]
|
||||||
})]
|
})]
|
||||||
});
|
});
|
||||||
compile(rootEl, 'main').then( (pv) => {
|
compileRoot(rootEl, 'main').then( (pv) => {
|
||||||
var viewRefs = renderer.createView(pv.render);
|
var viewRefs = renderer.createView(pv.render);
|
||||||
var vcRef = new ViewContainerRef(viewRefs[1], 1);
|
var vcRef = new ViewContainerRef(viewRefs[1], 1);
|
||||||
var vcProtoViewRef = pv.elementBinders[0].nestedProtoView
|
var vcProtoViewRef = pv.elementBinders[0].nestedProtoView
|
||||||
@ -251,7 +275,7 @@ export function main() {
|
|||||||
directives: [conditionalContentComponent]
|
directives: [conditionalContentComponent]
|
||||||
})]
|
})]
|
||||||
});
|
});
|
||||||
compile(rootEl, 'main').then( (pv) => {
|
compileRoot(rootEl, 'main').then( (pv) => {
|
||||||
var viewRefs = renderer.createView(pv.render);
|
var viewRefs = renderer.createView(pv.render);
|
||||||
var vcRef = new ViewContainerRef(viewRefs[2], 0);
|
var vcRef = new ViewContainerRef(viewRefs[2], 0);
|
||||||
var vcProtoViewRef = pv.elementBinders[0].nestedProtoView
|
var vcProtoViewRef = pv.elementBinders[0].nestedProtoView
|
||||||
@ -302,6 +326,12 @@ var simple = new DirectiveMetadata({
|
|||||||
type: DirectiveMetadata.COMPONENT_TYPE
|
type: DirectiveMetadata.COMPONENT_TYPE
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var dynamicComponent = new DirectiveMetadata({
|
||||||
|
selector: 'dynamic',
|
||||||
|
id: 'dynamic',
|
||||||
|
type: DirectiveMetadata.COMPONENT_TYPE
|
||||||
|
});
|
||||||
|
|
||||||
var multipleContentTagsComponent = new DirectiveMetadata({
|
var multipleContentTagsComponent = new DirectiveMetadata({
|
||||||
selector: 'multiple-content-tags',
|
selector: 'multiple-content-tags',
|
||||||
id: 'multiple-content-tags',
|
id: 'multiple-content-tags',
|
||||||
|
76
modules/angular2/test/render/dom/view/view_spec.js
vendored
Normal file
76
modules/angular2/test/render/dom/view/view_spec.js
vendored
Normal 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']);
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user