Fix for edge case bug when rerendering a child component that is a parent's root

This commit is contained in:
Patrick Steele-Idem 2017-03-01 19:13:15 -07:00
parent e8c3c390d0
commit 7d32145f5b
8 changed files with 112 additions and 13 deletions

View File

@ -4,7 +4,6 @@
var domInsert = require('../runtime/dom-insert');
var marko = require('../');
var componentsUtil = require('./util');
var getComponentForEl = componentsUtil.$__getComponentForEl;
var componentLookup = componentsUtil.$__componentLookup;
var emitLifecycleEvent = componentsUtil.$__emitLifecycleEvent;
var destroyComponentForEl = componentsUtil.$__destroyComponentForEl;
@ -35,10 +34,23 @@ function removeListener(removeEventListenerHandle) {
removeEventListenerHandle();
}
function hasCompatibleComponent(componentsContext, existingComponent) {
var id = existingComponent.id;
var newComponentDef = componentsContext.$__componentsById[id];
return newComponentDef && existingComponent.$__type == newComponentDef.$__component.$__type;
function checkCompatibleComponent(componentsContext, el) {
var component = el._w;
while(component) {
var id = component.id;
var newComponentDef = componentsContext.$__componentsById[id];
if (newComponentDef && component.$__type == newComponentDef.$__component.$__type) {
break;
}
var rootFor = component.$__rootFor;
if (rootFor) {
component = rootFor;
} else {
component.$__destroyShallow();
break;
}
}
}
function handleCustomEventWithMethodListener(component, targetMethodName, args, extraArgs) {
@ -488,7 +500,6 @@ Component.prototype = componentProto = {
function onBeforeElUpdated(fromEl, toEl) {
var id = fromEl.id;
var existingComponent;
if (componentsContext && id) {
var preserved = componentsContext.$__preserved[id];
@ -499,13 +510,10 @@ Component.prototype = componentProto = {
// the morphing will take place when the reused component updates.
return MORPHDOM_SKIP;
} else {
existingComponent = getComponentForEl(fromEl);
if (existingComponent && !hasCompatibleComponent(componentsContext, existingComponent)) {
// We found a component in an old DOM node that does not have
// a compatible component that was rendered so we need to
// destroy the old component
existingComponent.$__destroyShallow();
}
// We may need to destroy a Component associated with the current element
// if a new UI component was rendered to the same element and the types
// do not match
checkCompatibleComponent(componentsContext, fromEl);
}
}
}

View File

@ -0,0 +1,11 @@
class {
constructor() {
this.state = {
count: 0
};
}
}
<div.foo>
count:${state.count}
</div>

View File

@ -0,0 +1,7 @@
class {
}
<foo key="foo"/>
<div.root>
</div>

View File

@ -0,0 +1,17 @@
var expect = require('chai').expect;
module.exports = function(helpers) {
var component = helpers.mount(require('./index.marko'), {});
var foo = component.getComponent('foo');
expect(foo.el.innerHTML).to.contain('count:0');
foo.state.count++;
foo.update();
expect(component.isDestroyed()).to.equal(false);
expect(foo.isDestroyed()).to.equal(false);
expect(foo.el.innerHTML).to.contain('count:1');
};

View File

@ -0,0 +1,8 @@
class {
constructor() {
this.isBar = true;
}
}
<div.bar>
</div>

View File

@ -0,0 +1,8 @@
class {
constructor() {
this.isFoo = true;
}
}
<div.foo>
</div>

View File

@ -0,0 +1,16 @@
class {
onCreate() {
this.state = {
target: 'foo'
};
}
}
<div.root>
<if(state.target === 'foo')>
<foo key="child"/>
</if>
<else>
<bar key="child"/>
</else>
</div>

View File

@ -0,0 +1,24 @@
var expect = require('chai').expect;
module.exports = function(helpers) {
var component = helpers.mount(require('./index.marko'), {});
expect(component.el.innerHTML).to.contain('foo');
expect(component.el.innerHTML).to.not.contain('bar');
var foo = component.getComponent('child');
expect(foo.isDestroyed()).to.equal(false);
component.state.target = 'bar';
component.update();
expect(foo.isDestroyed()).to.equal(true);
var bar = component.getComponent('child');
expect(bar.isDestroyed()).to.equal(false);
expect(component.el.innerHTML).to.not.contain('foo');
expect(component.el.innerHTML).to.contain('bar');
};