fix(view): remove dynamic components when the parent view is dehydrated
Also adds a bunch of unit tests for affected parts. Fixes #1201
This commit is contained in:
+131
-19
@@ -1,25 +1,69 @@
|
||||
import {describe, ddescribe, it, iit, xit, xdescribe, expect, beforeEach, el} from 'angular2/test_lib';
|
||||
|
||||
import {
|
||||
AsyncTestCompleter,
|
||||
beforeEach,
|
||||
ddescribe,
|
||||
xdescribe,
|
||||
describe,
|
||||
el,
|
||||
dispatchEvent,
|
||||
expect,
|
||||
iit,
|
||||
inject,
|
||||
beforeEachBindings,
|
||||
it,
|
||||
xit,
|
||||
SpyObject, proxy
|
||||
} from 'angular2/test_lib';
|
||||
import {IMPLEMENTS, isBlank} from 'angular2/src/facade/lang';
|
||||
import {ListWrapper} from 'angular2/src/facade/collection';
|
||||
import {ViewFactory} from 'angular2/src/render/dom/view/view_factory';
|
||||
import {RenderProtoView} from 'angular2/src/render/dom/view/proto_view';
|
||||
import {RenderView} from 'angular2/src/render/dom/view/view';
|
||||
import {ElementBinder} from 'angular2/src/render/dom/view/element_binder';
|
||||
import {ShadowDomStrategy} from 'angular2/src/render/dom/shadow_dom/shadow_dom_strategy';
|
||||
import {LightDom} from 'angular2/src/render/dom/shadow_dom/light_dom'
|
||||
import {EventManager} from 'angular2/src/render/dom/events/event_manager';
|
||||
|
||||
export function main() {
|
||||
function createViewFactory({capacity}):ViewFactory {
|
||||
return new ViewFactory(capacity, null, null);
|
||||
}
|
||||
|
||||
function createPv() {
|
||||
return new RenderProtoView({
|
||||
element: el('<div></div>'),
|
||||
isRootView: false,
|
||||
elementBinders: []
|
||||
});
|
||||
}
|
||||
|
||||
describe('RenderViewFactory', () => {
|
||||
var eventManager;
|
||||
var shadowDomStrategy;
|
||||
|
||||
function createViewFactory({capacity}):ViewFactory {
|
||||
return new ViewFactory(capacity, eventManager, shadowDomStrategy);
|
||||
}
|
||||
|
||||
function createProtoView(rootEl=null, binders=null) {
|
||||
if (isBlank(rootEl)) {
|
||||
rootEl = el('<div></div>');
|
||||
}
|
||||
if (isBlank(binders)) {
|
||||
binders = [];
|
||||
}
|
||||
return new RenderProtoView({
|
||||
element: rootEl,
|
||||
isRootView: false,
|
||||
elementBinders: binders
|
||||
});
|
||||
}
|
||||
|
||||
function createComponentElBinder(componentId, nestedProtoView = null) {
|
||||
var binder = new ElementBinder({
|
||||
componentId: componentId,
|
||||
textNodeIndices: []
|
||||
});
|
||||
binder.nestedProtoView = nestedProtoView;
|
||||
return binder;
|
||||
}
|
||||
|
||||
|
||||
beforeEach( () => {
|
||||
eventManager = new SpyEventManager();
|
||||
shadowDomStrategy = new SpyShadowDomStrategy();
|
||||
});
|
||||
|
||||
it('should create views', () => {
|
||||
var pv = createPv();
|
||||
var pv = createProtoView();
|
||||
var vf = createViewFactory({
|
||||
capacity: 1
|
||||
});
|
||||
@@ -29,8 +73,8 @@ export function main() {
|
||||
describe('caching', () => {
|
||||
|
||||
it('should support multiple RenderProtoViews', () => {
|
||||
var pv1 = createPv();
|
||||
var pv2 = createPv();
|
||||
var pv1 = createProtoView();
|
||||
var pv2 = createProtoView();
|
||||
var vf = createViewFactory({ capacity: 2 });
|
||||
var view1 = vf.getView(pv1);
|
||||
var view2 = vf.getView(pv2);
|
||||
@@ -42,7 +86,7 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should reuse the newest view that has been returned', () => {
|
||||
var pv = createPv();
|
||||
var pv = createProtoView();
|
||||
var vf = createViewFactory({ capacity: 2 });
|
||||
var view1 = vf.getView(pv);
|
||||
var view2 = vf.getView(pv);
|
||||
@@ -53,7 +97,7 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should not add views when the capacity has been reached', () => {
|
||||
var pv = createPv();
|
||||
var pv = createProtoView();
|
||||
var vf = createViewFactory({ capacity: 2 });
|
||||
var view1 = vf.getView(pv);
|
||||
var view2 = vf.getView(pv);
|
||||
@@ -68,5 +112,73 @@ export function main() {
|
||||
|
||||
});
|
||||
|
||||
describe('child components', () => {
|
||||
|
||||
var vf, log;
|
||||
|
||||
beforeEach(() => {
|
||||
vf = createViewFactory({capacity: 1});
|
||||
log = [];
|
||||
shadowDomStrategy.spy('attachTemplate').andCallFake( (el, view) => {
|
||||
ListWrapper.push(log, ['attachTemplate', el, view]);
|
||||
});
|
||||
shadowDomStrategy.spy('constructLightDom').andCallFake( (lightDomView, shadowDomView, el) => {
|
||||
ListWrapper.push(log, ['constructLightDom', lightDomView, shadowDomView, el]);
|
||||
return new SpyLightDom();
|
||||
});
|
||||
});
|
||||
|
||||
it('should create static child component views', () => {
|
||||
var hostPv = createProtoView(el('<div><div class="ng-binding"></div></div>'), [
|
||||
createComponentElBinder(
|
||||
'someComponent',
|
||||
createProtoView()
|
||||
)
|
||||
]);
|
||||
var hostView = vf.getView(hostPv);
|
||||
var shadowView = hostView.componentChildViews[0];
|
||||
expect(shadowView).toBeTruthy();
|
||||
expect(hostView.lightDoms[0]).toBeTruthy();
|
||||
expect(log[0]).toEqual(['constructLightDom', hostView, shadowView, hostView.boundElements[0]]);
|
||||
expect(log[1]).toEqual(['attachTemplate', hostView.boundElements[0], shadowView]);
|
||||
});
|
||||
|
||||
it('should not create dynamic child component views', () => {
|
||||
var hostPv = createProtoView(el('<div><div class="ng-binding"></div></div>'), [
|
||||
createComponentElBinder(
|
||||
'someComponent',
|
||||
null
|
||||
)
|
||||
]);
|
||||
var hostView = vf.getView(hostPv);
|
||||
var shadowView = hostView.componentChildViews[0];
|
||||
expect(shadowView).toBeFalsy();
|
||||
expect(hostView.lightDoms[0]).toBeFalsy();
|
||||
expect(log).toEqual([]);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@proxy
|
||||
@IMPLEMENTS(EventManager)
|
||||
class SpyEventManager extends SpyObject {
|
||||
constructor(){super(EventManager);}
|
||||
noSuchMethod(m){return super.noSuchMethod(m)}
|
||||
}
|
||||
|
||||
@proxy
|
||||
@IMPLEMENTS(ShadowDomStrategy)
|
||||
class SpyShadowDomStrategy extends SpyObject {
|
||||
constructor(){super(ShadowDomStrategy);}
|
||||
noSuchMethod(m){return super.noSuchMethod(m)}
|
||||
}
|
||||
|
||||
@proxy
|
||||
@IMPLEMENTS(LightDom)
|
||||
class SpyLightDom extends SpyObject {
|
||||
constructor(){super(LightDom);}
|
||||
noSuchMethod(m){return super.noSuchMethod(m)}
|
||||
}
|
||||
|
||||
+164
-49
@@ -1,53 +1,161 @@
|
||||
import {describe, ddescribe, it, iit, xit, xdescribe, expect, beforeEach, el} from 'angular2/test_lib';
|
||||
|
||||
import {ListWrapper} from 'angular2/src/facade/collection';
|
||||
import {
|
||||
AsyncTestCompleter,
|
||||
beforeEach,
|
||||
ddescribe,
|
||||
xdescribe,
|
||||
describe,
|
||||
el,
|
||||
dispatchEvent,
|
||||
expect,
|
||||
iit,
|
||||
inject,
|
||||
beforeEachBindings,
|
||||
it,
|
||||
xit,
|
||||
SpyObject, proxy
|
||||
} from 'angular2/test_lib';
|
||||
import {IMPLEMENTS, isBlank} from 'angular2/src/facade/lang';
|
||||
|
||||
import {RenderProtoView} from 'angular2/src/render/dom/view/proto_view';
|
||||
import {ElementBinder} from 'angular2/src/render/dom/view/element_binder';
|
||||
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';
|
||||
import {EventManager} from 'angular2/src/render/dom/events/event_manager';
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
|
||||
export function main() {
|
||||
|
||||
function createView() {
|
||||
var proto = new RenderProtoView({element: el('<div></div>'), isRootView: false, elementBinders: []});
|
||||
var rootNodes = [el('<div></div>')];
|
||||
var boundTextNodes = [];
|
||||
var boundElements = [el('<div></div>')];
|
||||
var viewContainers = [];
|
||||
var contentTags = [];
|
||||
var eventManager = null;
|
||||
return new RenderView(proto, rootNodes,
|
||||
boundTextNodes, boundElements, viewContainers, contentTags, eventManager);
|
||||
}
|
||||
|
||||
function createShadowDomStrategy(log) {
|
||||
return new FakeShadowDomStrategy(log);
|
||||
}
|
||||
|
||||
describe('RenderView', () => {
|
||||
var log, strategy;
|
||||
var shadowDomStrategy;
|
||||
var eventManager;
|
||||
|
||||
function createProtoView({rootEl, binders}={}) {
|
||||
if (isBlank(rootEl)) {
|
||||
rootEl = el('<div></div>');
|
||||
}
|
||||
if (isBlank(binders)) {
|
||||
binders = [];
|
||||
}
|
||||
return new RenderProtoView({
|
||||
element: rootEl,
|
||||
isRootView: false,
|
||||
elementBinders: binders
|
||||
});
|
||||
}
|
||||
|
||||
function createComponentElBinder(componentId, nestedProtoView = null) {
|
||||
var binder = new ElementBinder({
|
||||
componentId: componentId,
|
||||
textNodeIndices: []
|
||||
});
|
||||
binder.nestedProtoView = nestedProtoView;
|
||||
return binder;
|
||||
}
|
||||
|
||||
function createHostProtoView(nestedProtoView) {
|
||||
return createProtoView({
|
||||
binders: [
|
||||
createComponentElBinder(
|
||||
'someComponent',
|
||||
nestedProtoView
|
||||
)
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
function createEmptyView() {
|
||||
var root = el('<div><div></div></div>');
|
||||
return new RenderView(createProtoView(), [DOM.childNodes(root)[0]],
|
||||
[], [], [], [], eventManager);
|
||||
}
|
||||
|
||||
function createHostView(pv, shadowDomView) {
|
||||
var view = new RenderView(pv, [el('<div></div>')],
|
||||
[], [el('<div></div>')], [], [], eventManager);
|
||||
view.setComponentView(shadowDomStrategy, 0, shadowDomView);
|
||||
return view;
|
||||
}
|
||||
|
||||
beforeEach( () => {
|
||||
log = [];
|
||||
strategy = createShadowDomStrategy(log);
|
||||
eventManager = new SpyEventManager();
|
||||
shadowDomStrategy = new SpyShadowDomStrategy();
|
||||
shadowDomStrategy.spy('constructLightDom').andCallFake( (lightDomView, shadowDomView, el) => {
|
||||
return new SpyLightDom();
|
||||
});
|
||||
});
|
||||
|
||||
describe('setComponentView', () => {
|
||||
|
||||
it('should redistribute when a component is added to a hydrated view', () => {
|
||||
var hostView = createView();
|
||||
var childView = createView();
|
||||
var shadowView = new SpyRenderView();
|
||||
var hostPv = createHostProtoView(createProtoView());
|
||||
var hostView = createHostView(hostPv, shadowView);
|
||||
hostView.hydrate(null);
|
||||
hostView.setComponentView(strategy, 0, childView);
|
||||
expect(log[0]).toEqual(['redistribute']);
|
||||
hostView.setComponentView(shadowDomStrategy, 0, shadowView);
|
||||
var lightDomSpy:SpyLightDom = hostView.lightDoms[0];
|
||||
expect(lightDomSpy.spy('redistribute')).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
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([]);
|
||||
var shadowView = new SpyRenderView();
|
||||
var hostPv = createHostProtoView(createProtoView());
|
||||
var hostView = createHostView(hostPv, shadowView);
|
||||
hostView.setComponentView(shadowDomStrategy, 0, shadowView);
|
||||
var lightDomSpy:SpyLightDom = hostView.lightDoms[0];
|
||||
expect(lightDomSpy.spy('redistribute')).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('hydrate', () => {
|
||||
|
||||
it('should hydrate existing child components', () => {
|
||||
var hostPv = createHostProtoView(createProtoView());
|
||||
var shadowView = new SpyRenderView();
|
||||
var hostView = createHostView(hostPv, shadowView);
|
||||
|
||||
hostView.hydrate(null);
|
||||
|
||||
expect(shadowView.spy('hydrate')).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('dehydrate', () => {
|
||||
var hostView;
|
||||
|
||||
function createAndHydrate(nestedProtoView, shadowView) {
|
||||
var hostPv = createHostProtoView(nestedProtoView);
|
||||
hostView = createHostView(hostPv, shadowView);
|
||||
|
||||
hostView.hydrate(null);
|
||||
}
|
||||
|
||||
it('should dehydrate child components', () => {
|
||||
var shadowView = new SpyRenderView();
|
||||
createAndHydrate(createProtoView(), shadowView);
|
||||
hostView.dehydrate();
|
||||
|
||||
expect(shadowView.spy('dehydrate')).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not clear static child components', () => {
|
||||
var shadowView = createEmptyView();
|
||||
createAndHydrate(createProtoView(), shadowView);
|
||||
hostView.dehydrate();
|
||||
|
||||
expect(hostView.componentChildViews[0]).toBe(shadowView);
|
||||
expect(shadowView.rootNodes[0].parentNode).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should clear dynamic child components', () => {
|
||||
var shadowView = createEmptyView();
|
||||
createAndHydrate(null, shadowView);
|
||||
hostView.dehydrate();
|
||||
|
||||
expect(hostView.componentChildViews[0]).toBe(null);
|
||||
expect(shadowView.rootNodes[0].parentNode).toBe(null);
|
||||
});
|
||||
|
||||
});
|
||||
@@ -55,24 +163,31 @@ export function main() {
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
@proxy
|
||||
@IMPLEMENTS(EventManager)
|
||||
class SpyEventManager extends SpyObject {
|
||||
constructor(){super(EventManager);}
|
||||
noSuchMethod(m){return super.noSuchMethod(m)}
|
||||
}
|
||||
|
||||
@proxy
|
||||
@IMPLEMENTS(ShadowDomStrategy)
|
||||
class SpyShadowDomStrategy extends SpyObject {
|
||||
constructor(){super(ShadowDomStrategy);}
|
||||
noSuchMethod(m){return super.noSuchMethod(m)}
|
||||
}
|
||||
|
||||
@proxy
|
||||
@IMPLEMENTS(LightDom)
|
||||
class SpyLightDom extends SpyObject {
|
||||
constructor(){super(LightDom);}
|
||||
noSuchMethod(m){return super.noSuchMethod(m)}
|
||||
}
|
||||
|
||||
@proxy
|
||||
@IMPLEMENTS(RenderView)
|
||||
class SpyRenderView extends SpyObject {
|
||||
constructor(){super(RenderView);}
|
||||
noSuchMethod(m){return super.noSuchMethod(m)}
|
||||
}
|
||||
|
||||
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']);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user