diff --git a/render/domFor.js b/render/domFor.js
index 16b17a972..89a5dd966 100644
--- a/render/domFor.js
+++ b/render/domFor.js
@@ -2,12 +2,12 @@
var delayedRemoval = new WeakMap
-function *domFor(vnode, object = {}) {
+function *domFor(vnode) {
// To avoid unintended mangling of the internal bundler,
// parameter destructuring is not used here.
var dom = vnode.dom
var domSize = vnode.domSize
- var generation = object.generation
+ var generation = delayedRemoval.get(dom)
if (dom != null) do {
var nextSibling = dom.nextSibling
diff --git a/render/hyperscript.js b/render/hyperscript.js
index 8b34233e9..2f7edf6db 100644
--- a/render/hyperscript.js
+++ b/render/hyperscript.js
@@ -62,6 +62,9 @@ function execSelector(state, vnode) {
attrs = Object.assign({type: attrs.type}, attrs)
}
+ // This reduces the complexity of the evaluation of "is" within the render function.
+ vnode.is = attrs.is
+
vnode.attrs = attrs
return vnode
diff --git a/render/render.js b/render/render.js
index 674a8266a..52b7132a5 100644
--- a/render/render.js
+++ b/render/render.js
@@ -114,7 +114,7 @@ module.exports = function() {
function createElement(parent, vnode, hooks, ns, nextSibling) {
var tag = vnode.tag
var attrs = vnode.attrs
- var is = attrs && attrs.is
+ var is = vnode.is
ns = getNameSpace(vnode) || ns
@@ -396,7 +396,7 @@ module.exports = function() {
}
function updateNode(parent, old, vnode, hooks, nextSibling, ns) {
var oldTag = old.tag, tag = vnode.tag
- if (oldTag === tag) {
+ if (oldTag === tag && old.is === vnode.is) {
vnode.state = old.state
vnode.events = old.events
if (shouldNotUpdate(vnode, old)) return
@@ -426,7 +426,7 @@ module.exports = function() {
}
function updateHTML(parent, old, vnode, ns, nextSibling) {
if (old.children !== vnode.children) {
- removeDOM(parent, old, undefined)
+ removeDOM(parent, old)
createHTML(parent, vnode, ns, nextSibling)
}
else {
@@ -585,71 +585,38 @@ module.exports = function() {
if (vnode != null) removeNode(parent, vnode)
}
}
- function removeNode(parent, vnode) {
- var mask = 0
+ function tryBlockRemove(parent, vnode, source, counter) {
var original = vnode.state
- var stateResult, attrsResult
- if (typeof vnode.tag !== "string" && typeof vnode.state.onbeforeremove === "function") {
- var result = callHook.call(vnode.state.onbeforeremove, vnode)
- if (result != null && typeof result.then === "function") {
- mask = 1
- stateResult = result
- }
- }
- if (vnode.attrs && typeof vnode.attrs.onbeforeremove === "function") {
- var result = callHook.call(vnode.attrs.onbeforeremove, vnode)
- if (result != null && typeof result.then === "function") {
- // eslint-disable-next-line no-bitwise
- mask |= 2
- attrsResult = result
- }
- }
- checkState(vnode, original)
- var generation
- // If we can, try to fast-path it and avoid all the overhead of awaiting
- if (!mask) {
+ var result = callHook.call(source.onbeforeremove, vnode)
+ if (result == null) return
+
+ var generation = currentRender
+ for (var dom of domFor(vnode)) delayedRemoval.set(dom, generation)
+ counter.v++
+
+ Promise.resolve(result).finally(function () {
+ checkState(vnode, original)
+ tryResumeRemove(parent, vnode, counter)
+ })
+ }
+ function tryResumeRemove(parent, vnode, counter) {
+ if (--counter.v === 0) {
onremove(vnode)
- removeDOM(parent, vnode, generation)
- } else {
- generation = currentRender
- for (var dom of domFor(vnode)) delayedRemoval.set(dom, generation)
- if (stateResult != null) {
- stateResult.finally(function () {
- // eslint-disable-next-line no-bitwise
- if (mask & 1) {
- // eslint-disable-next-line no-bitwise
- mask &= 2
- if (!mask) {
- checkState(vnode, original)
- onremove(vnode)
- removeDOM(parent, vnode, generation)
- }
- }
- })
- }
- if (attrsResult != null) {
- attrsResult.finally(function () {
- // eslint-disable-next-line no-bitwise
- if (mask & 2) {
- // eslint-disable-next-line no-bitwise
- mask &= 1
- if (!mask) {
- checkState(vnode, original)
- onremove(vnode)
- removeDOM(parent, vnode, generation)
- }
- }
- })
- }
+ removeDOM(parent, vnode)
}
}
- function removeDOM(parent, vnode, generation) {
+ function removeNode(parent, vnode) {
+ var counter = {v: 1}
+ if (typeof vnode.tag !== "string" && typeof vnode.state.onbeforeremove === "function") tryBlockRemove(parent, vnode, vnode.state, counter)
+ if (vnode.attrs && typeof vnode.attrs.onbeforeremove === "function") tryBlockRemove(parent, vnode, vnode.attrs, counter)
+ tryResumeRemove(parent, vnode, counter)
+ }
+ function removeDOM(parent, vnode) {
if (vnode.dom == null) return
if (vnode.domSize == null) {
- // don't allocate for the common case
- if (delayedRemoval.get(vnode.dom) === generation) parent.removeChild(vnode.dom)
+ parent.removeChild(vnode.dom)
} else {
- for (var dom of domFor(vnode, {generation})) parent.removeChild(dom)
+ for (var dom of domFor(vnode)) parent.removeChild(dom)
}
}
@@ -676,7 +643,7 @@ module.exports = function() {
}
}
function setAttr(vnode, key, old, value, ns) {
- if (key === "key" || key === "is" || value == null || isLifecycleMethod(key) || (old === value && !isFormAttribute(vnode, key)) && typeof value !== "object") return
+ if (key === "key" || value == null || isLifecycleMethod(key) || (old === value && !isFormAttribute(vnode, key)) && typeof value !== "object") return
if (key[0] === "o" && key[1] === "n") return updateEvent(vnode, key, value)
if (key.slice(0, 6) === "xlink:") vnode.dom.setAttributeNS("http://www.w3.org/1999/xlink", key.slice(6), value)
else if (key === "style") updateStyle(vnode.dom, old, value)
@@ -709,7 +676,7 @@ module.exports = function() {
}
}
function removeAttr(vnode, key, old, ns) {
- if (key === "key" || key === "is" || old == null || isLifecycleMethod(key)) return
+ if (key === "key" || old == null || isLifecycleMethod(key)) return
if (key[0] === "o" && key[1] === "n") updateEvent(vnode, key, undefined)
else if (key === "style") updateStyle(vnode.dom, old, null)
else if (
@@ -743,22 +710,24 @@ module.exports = function() {
if ("selectedIndex" in attrs) setAttr(vnode, "selectedIndex", null, attrs.selectedIndex, undefined)
}
function updateAttrs(vnode, old, attrs, ns) {
- if (old && old === attrs) {
- console.warn("Don't reuse attrs object, use new object for every redraw, this will throw in next major")
- }
- if (attrs != null) {
- for (var key in attrs) {
- setAttr(vnode, key, old && old[key], attrs[key], ns)
- }
- }
+ // Some attributes may NOT be case-sensitive (e.g. data-***),
+ // so removal should be done first to prevent accidental removal for newly setting values.
var val
if (old != null) {
+ if (old === attrs) {
+ console.warn("Don't reuse attrs object, use new object for every redraw, this will throw in next major")
+ }
for (var key in old) {
if (((val = old[key]) != null) && (attrs == null || attrs[key] == null)) {
removeAttr(vnode, key, val, ns)
}
}
}
+ if (attrs != null) {
+ for (var key in attrs) {
+ setAttr(vnode, key, old && old[key], attrs[key], ns)
+ }
+ }
}
function isFormAttribute(vnode, attr) {
return attr === "value" || attr === "checked" || attr === "selectedIndex" || attr === "selected" && vnode.dom === activeElement(vnode.dom) || vnode.tag === "option" && vnode.dom.parentNode === activeElement(vnode.dom)
@@ -770,7 +739,7 @@ module.exports = function() {
// Filter out namespaced keys
return ns === undefined && (
// If it's a custom element, just keep it.
- vnode.tag.indexOf("-") > -1 || vnode.attrs != null && vnode.attrs.is ||
+ vnode.tag.indexOf("-") > -1 || vnode.is ||
// If it's a normal element, let's try to avoid a few browser bugs.
key !== "href" && key !== "list" && key !== "form" && key !== "width" && key !== "height"// && key !== "type"
// Defer the property check until *after* we check everything.
@@ -789,7 +758,7 @@ module.exports = function() {
element.style = style
} else if (old == null || typeof old !== "object") {
// `old` is missing or a string, `style` is an object.
- element.style.cssText = ""
+ element.style = ""
// Add new style properties
for (var key in style) {
var value = style[key]
@@ -800,6 +769,15 @@ module.exports = function() {
}
} else {
// Both old & new are (different) objects.
+ // Remove style properties that no longer exist
+ // Style properties may have two cases(dash-case and camelCase),
+ // so removal should be done first to prevent accidental removal for newly setting values.
+ for (var key in old) {
+ if (old[key] != null && style[key] == null) {
+ if (key.includes("-")) element.style.removeProperty(key)
+ else element.style[key] = ""
+ }
+ }
// Update style properties that have changed
for (var key in style) {
var value = style[key]
@@ -808,13 +786,6 @@ module.exports = function() {
else element.style[key] = value
}
}
- // Remove style properties that no longer exist
- for (var key in old) {
- if (old[key] != null && style[key] == null) {
- if (key.includes("-")) element.style.removeProperty(key)
- else element.style[key] = ""
- }
- }
}
}
diff --git a/render/tests/manual/case-handling.html b/render/tests/manual/case-handling.html
new file mode 100644
index 000000000..372f8bc60
--- /dev/null
+++ b/render/tests/manual/case-handling.html
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+ This is a test for special case-handling of attribute and style properties. (#2988).
+ Open your browser's Developer Console and follow these steps:
+
+ - Check the background color of the "foo" below.
+
+ - If it is light green, it is correct. The style has been updated properly.
+ - If it is red or yellow, the style has not been updated properly.
+
+ - Check the logs displayed in the console.
+
+ - If the attribute has been updated correctly, you should see the following message: "If you see this message, the update process is correct."
+ - If "null" is displayed, the attribute has not been updated properly.
+
+
+
+
+
+
+
+
diff --git a/render/tests/test-attributes.js b/render/tests/test-attributes.js
index 1daed5e5f..3d469c9d8 100644
--- a/render/tests/test-attributes.js
+++ b/render/tests/test-attributes.js
@@ -80,8 +80,8 @@ o.spec("attributes", function() {
o(spies[0].callCount).equals(0)
o(spies[2].callCount).equals(0)
o(spies[3].calls).deepEquals([{this: spies[3].elem, args: ["custom", "x"]}])
- o(spies[4].calls).deepEquals([{this: spies[4].elem, args: ["custom", "x"]}])
- o(spies[5].calls).deepEquals([{this: spies[5].elem, args: ["custom", "x"]}])
+ o(spies[4].calls).deepEquals([{this: spies[4].elem, args: ["is", "something-special"]}, {this: spies[4].elem, args: ["custom", "x"]}])
+ o(spies[5].calls).deepEquals([{this: spies[5].elem, args: ["is", "something-special"]}, {this: spies[5].elem, args: ["custom", "x"]}])
})
o("when vnode is customElement with property, custom setAttribute not called", function(){
@@ -124,8 +124,8 @@ o.spec("attributes", function() {
o(spies[1].callCount).equals(0)
o(spies[2].callCount).equals(0)
o(spies[3].callCount).equals(0)
- o(spies[4].callCount).equals(0)
- o(spies[5].callCount).equals(0)
+ o(spies[4].callCount).equals(1) // setAttribute("is", "something-special") is called
+ o(spies[5].callCount).equals(1) // setAttribute("is", "something-special") is called
o(getters[0].callCount).equals(0)
o(getters[1].callCount).equals(0)
o(getters[2].callCount).equals(0)
diff --git a/render/tests/test-domFor.js b/render/tests/test-domFor.js
index b0c3444fa..dae83bd13 100644
--- a/render/tests/test-domFor.js
+++ b/render/tests/test-domFor.js
@@ -1,6 +1,7 @@
"use strict"
const o = require("ospec")
+const callAsync = require("../../test-utils/callAsync")
const components = require("../../test-utils/components")
const domMock = require("../../test-utils/domMock")
const vdom = require("../render")
@@ -85,6 +86,790 @@ o.spec("domFor(vnode)", function() {
))
})
+ o("works in onbeforeremove and onremove", function (done) {
+ const onbeforeremove = o.spy(function onbeforeremove(vnode){
+ o(root.childNodes.length).equals(1)
+ o(root.childNodes[0].nodeName).equals("A")
+ const iter = domFor(vnode)
+ o(iter.next()).deepEquals({done:false, value: root.childNodes[0]})
+ o(iter.next().done).deepEquals(true)
+ o(root.childNodes.length).equals(1)
+ return {then(resolve){resolve()}}
+ })
+ const onremove = o.spy(function onremove(vnode){
+ o(root.childNodes.length).equals(1)
+ o(root.childNodes[0].nodeName).equals("A")
+ const iter = domFor(vnode)
+ o(iter.next()).deepEquals({done:false, value: root.childNodes[0]})
+ o(iter.next().done).deepEquals(true)
+ o(root.childNodes.length).equals(1)
+ })
+ render(root, [m("a", {onbeforeremove, onremove})])
+ render(root, [])
+
+ o(onbeforeremove.callCount).equals(1)
+ o(onremove.callCount).equals(0)
+ callAsync(function(){
+ o(onremove.callCount).equals(1)
+ done()
+ })
+ })
+ o("works multiple vnodes with onbeforeremove (#3007, 1/6, BCA)", function (done) {
+ let thenCBA, thenCBB, thenCBC
+ const onbeforeremoveA = o.spy(function onbeforeremove(){
+ return {then(resolve){thenCBA = resolve}}
+ })
+ const onbeforeremoveB = o.spy(function onbeforeremove(){
+ return {then(resolve){thenCBB = resolve}}
+ })
+ const onbeforeremoveC = o.spy(function onbeforeremove(){
+ return {then(resolve){thenCBC = resolve}}
+ })
+ // to avoid updating internal nodes only, vnodes have key attributes
+ const A = fragment({key: 1, onbeforeremove: onbeforeremoveA}, [m("a1"), m("a2")])
+ const B = fragment({key: 2, onbeforeremove: onbeforeremoveB}, [m("b1"), m("b2")])
+ const C = fragment({key: 3, onbeforeremove: onbeforeremoveC}, [m("c1"), m("c2")])
+
+ render(root, [A])
+ o(onbeforeremoveA.callCount).equals(0)
+ o(onbeforeremoveB.callCount).equals(0)
+ o(onbeforeremoveC.callCount).equals(0)
+
+ render(root, [B])
+ o(onbeforeremoveA.callCount).equals(1)
+ o(onbeforeremoveB.callCount).equals(0)
+ o(onbeforeremoveC.callCount).equals(0)
+
+ render(root, [C])
+ o(onbeforeremoveA.callCount).equals(1)
+ o(onbeforeremoveB.callCount).equals(1)
+ o(onbeforeremoveC.callCount).equals(0)
+
+ render(root, [])
+ o(onbeforeremoveA.callCount).equals(1)
+ o(onbeforeremoveB.callCount).equals(1)
+ o(onbeforeremoveC.callCount).equals(1)
+
+ // not resolved
+ o(root.childNodes.length).equals(6)
+ o(root.childNodes[0].nodeName).equals("A1")
+ o(root.childNodes[1].nodeName).equals("A2")
+ o(root.childNodes[2].nodeName).equals("B1")
+ o(root.childNodes[3].nodeName).equals("B2")
+ o(root.childNodes[4].nodeName).equals("C1")
+ o(root.childNodes[5].nodeName).equals("C2")
+
+ const iterA = domFor(A)
+ o(iterA.next().value.nodeName).equals("A1")
+ o(iterA.next().value.nodeName).equals("A2")
+ o(iterA.next().done).deepEquals(true)
+
+ const iterB = domFor(B)
+ o(iterB.next().value.nodeName).equals("B1")
+ o(iterB.next().value.nodeName).equals("B2")
+ o(iterB.next().done).deepEquals(true)
+
+ const iterC = domFor(C)
+ o(iterC.next().value.nodeName).equals("C1")
+ o(iterC.next().value.nodeName).equals("C2")
+ o(iterC.next().done).deepEquals(true)
+
+ callAsync(function(){
+ // not resolved yet
+ o(root.childNodes.length).equals(6)
+ o(root.childNodes[0].nodeName).equals("A1")
+ o(root.childNodes[1].nodeName).equals("A2")
+ o(root.childNodes[2].nodeName).equals("B1")
+ o(root.childNodes[3].nodeName).equals("B2")
+ o(root.childNodes[4].nodeName).equals("C1")
+ o(root.childNodes[5].nodeName).equals("C2")
+
+ const iterA = domFor(A)
+ o(iterA.next().value.nodeName).equals("A1")
+ o(iterA.next().value.nodeName).equals("A2")
+ o(iterA.next().done).deepEquals(true)
+
+ const iterB = domFor(B)
+ o(iterB.next().value.nodeName).equals("B1")
+ o(iterB.next().value.nodeName).equals("B2")
+ o(iterB.next().done).deepEquals(true)
+
+ const iterC = domFor(C)
+ o(iterC.next().value.nodeName).equals("C1")
+ o(iterC.next().value.nodeName).equals("C2")
+ o(iterC.next().done).deepEquals(true)
+
+ // resolve B
+ thenCBB()
+ callAsync(function(){
+ o(root.childNodes.length).equals(4)
+ o(root.childNodes[0].nodeName).equals("A1")
+ o(root.childNodes[1].nodeName).equals("A2")
+ o(root.childNodes[2].nodeName).equals("C1")
+ o(root.childNodes[3].nodeName).equals("C2")
+
+ const iterA = domFor(A)
+ o(iterA.next().value.nodeName).equals("A1")
+ o(iterA.next().value.nodeName).equals("A2")
+ o(iterA.next().done).deepEquals(true)
+
+ const iterC = domFor(C)
+ o(iterC.next().value.nodeName).equals("C1")
+ o(iterC.next().value.nodeName).equals("C2")
+ o(iterC.next().done).deepEquals(true)
+
+ // resolve C
+ thenCBC()
+ callAsync(function(){
+ o(root.childNodes.length).equals(2)
+ o(root.childNodes[0].nodeName).equals("A1")
+ o(root.childNodes[1].nodeName).equals("A2")
+
+ const iterA = domFor(A)
+ o(iterA.next().value.nodeName).equals("A1")
+ o(iterA.next().value.nodeName).equals("A2")
+ o(iterA.next().done).deepEquals(true)
+
+ // resolve A
+ thenCBA()
+ callAsync(function(){
+ o(root.childNodes.length).equals(0)
+ done()
+ })
+ })
+ })
+ })
+ })
+ o("works multiple vnodes with onbeforeremove (#3007, 2/6, CAB)", function (done) {
+ let thenCBA, thenCBB, thenCBC
+ const onbeforeremoveA = o.spy(function onbeforeremove(){
+ return {then(resolve){thenCBA = resolve}}
+ })
+ const onbeforeremoveB = o.spy(function onbeforeremove(){
+ return {then(resolve){thenCBB = resolve}}
+ })
+ const onbeforeremoveC = o.spy(function onbeforeremove(){
+ return {then(resolve){thenCBC = resolve}}
+ })
+ // to avoid updating internal nodes only, vnodes have key attributes
+ const A = fragment({key: 1, onbeforeremove: onbeforeremoveA}, [m("a1"), m("a2")])
+ const B = fragment({key: 2, onbeforeremove: onbeforeremoveB}, [m("b1"), m("b2")])
+ const C = fragment({key: 3, onbeforeremove: onbeforeremoveC}, [m("c1"), m("c2")])
+
+ render(root, [A])
+ o(onbeforeremoveA.callCount).equals(0)
+ o(onbeforeremoveB.callCount).equals(0)
+ o(onbeforeremoveC.callCount).equals(0)
+
+ render(root, [B])
+ o(onbeforeremoveA.callCount).equals(1)
+ o(onbeforeremoveB.callCount).equals(0)
+ o(onbeforeremoveC.callCount).equals(0)
+
+ render(root, [C])
+ o(onbeforeremoveA.callCount).equals(1)
+ o(onbeforeremoveB.callCount).equals(1)
+ o(onbeforeremoveC.callCount).equals(0)
+
+ render(root, [])
+ o(onbeforeremoveA.callCount).equals(1)
+ o(onbeforeremoveB.callCount).equals(1)
+ o(onbeforeremoveC.callCount).equals(1)
+
+ // not resolved
+ o(root.childNodes.length).equals(6)
+ o(root.childNodes[0].nodeName).equals("A1")
+ o(root.childNodes[1].nodeName).equals("A2")
+ o(root.childNodes[2].nodeName).equals("B1")
+ o(root.childNodes[3].nodeName).equals("B2")
+ o(root.childNodes[4].nodeName).equals("C1")
+ o(root.childNodes[5].nodeName).equals("C2")
+
+ const iterA = domFor(A)
+ o(iterA.next().value.nodeName).equals("A1")
+ o(iterA.next().value.nodeName).equals("A2")
+ o(iterA.next().done).deepEquals(true)
+
+ const iterB = domFor(B)
+ o(iterB.next().value.nodeName).equals("B1")
+ o(iterB.next().value.nodeName).equals("B2")
+ o(iterB.next().done).deepEquals(true)
+
+ const iterC = domFor(C)
+ o(iterC.next().value.nodeName).equals("C1")
+ o(iterC.next().value.nodeName).equals("C2")
+ o(iterC.next().done).deepEquals(true)
+
+ callAsync(function(){
+ // not resolved yet
+ o(root.childNodes.length).equals(6)
+ o(root.childNodes[0].nodeName).equals("A1")
+ o(root.childNodes[1].nodeName).equals("A2")
+ o(root.childNodes[2].nodeName).equals("B1")
+ o(root.childNodes[3].nodeName).equals("B2")
+ o(root.childNodes[4].nodeName).equals("C1")
+ o(root.childNodes[5].nodeName).equals("C2")
+
+ const iterA = domFor(A)
+ o(iterA.next().value.nodeName).equals("A1")
+ o(iterA.next().value.nodeName).equals("A2")
+ o(iterA.next().done).deepEquals(true)
+
+ const iterB = domFor(B)
+ o(iterB.next().value.nodeName).equals("B1")
+ o(iterB.next().value.nodeName).equals("B2")
+ o(iterB.next().done).deepEquals(true)
+
+ const iterC = domFor(C)
+ o(iterC.next().value.nodeName).equals("C1")
+ o(iterC.next().value.nodeName).equals("C2")
+ o(iterC.next().done).deepEquals(true)
+
+ // resolve C
+ thenCBC()
+ callAsync(function(){
+ o(root.childNodes.length).equals(4)
+ o(root.childNodes[0].nodeName).equals("A1")
+ o(root.childNodes[1].nodeName).equals("A2")
+ o(root.childNodes[2].nodeName).equals("B1")
+ o(root.childNodes[3].nodeName).equals("B2")
+
+ const iterB = domFor(B)
+ o(iterB.next().value.nodeName).equals("B1")
+ o(iterB.next().value.nodeName).equals("B2")
+ o(iterB.next().done).deepEquals(true)
+
+ const iterA = domFor(A)
+ o(iterA.next().value.nodeName).equals("A1")
+ o(iterA.next().value.nodeName).equals("A2")
+ o(iterA.next().done).deepEquals(true)
+
+ // resolve A
+ thenCBA()
+ callAsync(function(){
+ o(root.childNodes.length).equals(2)
+ o(root.childNodes[0].nodeName).equals("B1")
+ o(root.childNodes[1].nodeName).equals("B2")
+
+ const iterB = domFor(B)
+ o(iterB.next().value.nodeName).equals("B1")
+ o(iterB.next().value.nodeName).equals("B2")
+ o(iterB.next().done).deepEquals(true)
+
+ // resolve B
+ thenCBB()
+ callAsync(function(){
+ o(root.childNodes.length).equals(0)
+ done()
+ })
+ })
+ })
+ })
+ })
+ o("works multiple vnodes with onbeforeremove (#3007, 3/6, ABC)", function (done) {
+ let thenCBA, thenCBB, thenCBC
+ const onbeforeremoveA = o.spy(function onbeforeremove(){
+ return {then(resolve){thenCBA = resolve}}
+ })
+ const onbeforeremoveB = o.spy(function onbeforeremove(){
+ return {then(resolve){thenCBB = resolve}}
+ })
+ const onbeforeremoveC = o.spy(function onbeforeremove(){
+ return {then(resolve){thenCBC = resolve}}
+ })
+ // to avoid updating internal nodes only, vnodes have key attributes
+ const A = fragment({key: 1, onbeforeremove: onbeforeremoveA}, [m("a1"), m("a2")])
+ const B = fragment({key: 2, onbeforeremove: onbeforeremoveB}, [m("b1"), m("b2")])
+ const C = fragment({key: 3, onbeforeremove: onbeforeremoveC}, [m("c1"), m("c2")])
+
+ render(root, [A])
+ o(onbeforeremoveA.callCount).equals(0)
+ o(onbeforeremoveB.callCount).equals(0)
+ o(onbeforeremoveC.callCount).equals(0)
+
+ render(root, [B])
+ o(onbeforeremoveA.callCount).equals(1)
+ o(onbeforeremoveB.callCount).equals(0)
+ o(onbeforeremoveC.callCount).equals(0)
+
+ render(root, [C])
+ o(onbeforeremoveA.callCount).equals(1)
+ o(onbeforeremoveB.callCount).equals(1)
+ o(onbeforeremoveC.callCount).equals(0)
+
+ render(root, [])
+ o(onbeforeremoveA.callCount).equals(1)
+ o(onbeforeremoveB.callCount).equals(1)
+ o(onbeforeremoveC.callCount).equals(1)
+
+ // not resolved
+ o(root.childNodes.length).equals(6)
+ o(root.childNodes[0].nodeName).equals("A1")
+ o(root.childNodes[1].nodeName).equals("A2")
+ o(root.childNodes[2].nodeName).equals("B1")
+ o(root.childNodes[3].nodeName).equals("B2")
+ o(root.childNodes[4].nodeName).equals("C1")
+ o(root.childNodes[5].nodeName).equals("C2")
+
+ const iterA = domFor(A)
+ o(iterA.next().value.nodeName).equals("A1")
+ o(iterA.next().value.nodeName).equals("A2")
+ o(iterA.next().done).deepEquals(true)
+
+ const iterB = domFor(B)
+ o(iterB.next().value.nodeName).equals("B1")
+ o(iterB.next().value.nodeName).equals("B2")
+ o(iterB.next().done).deepEquals(true)
+
+ const iterC = domFor(C)
+ o(iterC.next().value.nodeName).equals("C1")
+ o(iterC.next().value.nodeName).equals("C2")
+ o(iterC.next().done).deepEquals(true)
+
+ callAsync(function(){
+ // not resolved yet
+ o(root.childNodes.length).equals(6)
+ o(root.childNodes[0].nodeName).equals("A1")
+ o(root.childNodes[1].nodeName).equals("A2")
+ o(root.childNodes[2].nodeName).equals("B1")
+ o(root.childNodes[3].nodeName).equals("B2")
+ o(root.childNodes[4].nodeName).equals("C1")
+ o(root.childNodes[5].nodeName).equals("C2")
+
+ const iterA = domFor(A)
+ o(iterA.next().value.nodeName).equals("A1")
+ o(iterA.next().value.nodeName).equals("A2")
+ o(iterA.next().done).deepEquals(true)
+
+ const iterB = domFor(B)
+ o(iterB.next().value.nodeName).equals("B1")
+ o(iterB.next().value.nodeName).equals("B2")
+ o(iterB.next().done).deepEquals(true)
+
+ const iterC = domFor(C)
+ o(iterC.next().value.nodeName).equals("C1")
+ o(iterC.next().value.nodeName).equals("C2")
+ o(iterC.next().done).deepEquals(true)
+
+ // resolve A
+ thenCBA()
+ callAsync(function(){
+ o(root.childNodes.length).equals(4)
+ o(root.childNodes[0].nodeName).equals("B1")
+ o(root.childNodes[1].nodeName).equals("B2")
+ o(root.childNodes[2].nodeName).equals("C1")
+ o(root.childNodes[3].nodeName).equals("C2")
+
+ const iterB = domFor(B)
+ o(iterB.next().value.nodeName).equals("B1")
+ o(iterB.next().value.nodeName).equals("B2")
+ o(iterB.next().done).deepEquals(true)
+
+ const iterC = domFor(C)
+ o(iterC.next().value.nodeName).equals("C1")
+ o(iterC.next().value.nodeName).equals("C2")
+ o(iterC.next().done).deepEquals(true)
+
+ // resolve B
+ thenCBB()
+ callAsync(function(){
+ o(root.childNodes.length).equals(2)
+ o(root.childNodes[0].nodeName).equals("C1")
+ o(root.childNodes[1].nodeName).equals("C2")
+
+ const iterC = domFor(C)
+ o(iterC.next().value.nodeName).equals("C1")
+ o(iterC.next().value.nodeName).equals("C2")
+ o(iterC.next().done).deepEquals(true)
+
+ // resolve C
+ thenCBC()
+ callAsync(function(){
+ o(root.childNodes.length).equals(0)
+ done()
+ })
+ })
+ })
+ })
+ })
+ o("works multiple vnodes with onbeforeremove (#3007, 4/6, ACB)", function (done) {
+ let thenCBA, thenCBB, thenCBC
+ const onbeforeremoveA = o.spy(function onbeforeremove(){
+ return {then(resolve){thenCBA = resolve}}
+ })
+ const onbeforeremoveB = o.spy(function onbeforeremove(){
+ return {then(resolve){thenCBB = resolve}}
+ })
+ const onbeforeremoveC = o.spy(function onbeforeremove(){
+ return {then(resolve){thenCBC = resolve}}
+ })
+ // to avoid updating internal nodes only, vnodes have key attributes
+ const A = fragment({key: 1, onbeforeremove: onbeforeremoveA}, [m("a1"), m("a2")])
+ const B = fragment({key: 2, onbeforeremove: onbeforeremoveB}, [m("b1"), m("b2")])
+ const C = fragment({key: 3, onbeforeremove: onbeforeremoveC}, [m("c1"), m("c2")])
+
+ render(root, [A])
+ o(onbeforeremoveA.callCount).equals(0)
+ o(onbeforeremoveB.callCount).equals(0)
+ o(onbeforeremoveC.callCount).equals(0)
+
+ render(root, [B])
+ o(onbeforeremoveA.callCount).equals(1)
+ o(onbeforeremoveB.callCount).equals(0)
+ o(onbeforeremoveC.callCount).equals(0)
+
+ render(root, [C])
+ o(onbeforeremoveA.callCount).equals(1)
+ o(onbeforeremoveB.callCount).equals(1)
+ o(onbeforeremoveC.callCount).equals(0)
+
+ render(root, [])
+ o(onbeforeremoveA.callCount).equals(1)
+ o(onbeforeremoveB.callCount).equals(1)
+ o(onbeforeremoveC.callCount).equals(1)
+
+ // not resolved
+ o(root.childNodes.length).equals(6)
+ o(root.childNodes[0].nodeName).equals("A1")
+ o(root.childNodes[1].nodeName).equals("A2")
+ o(root.childNodes[2].nodeName).equals("B1")
+ o(root.childNodes[3].nodeName).equals("B2")
+ o(root.childNodes[4].nodeName).equals("C1")
+ o(root.childNodes[5].nodeName).equals("C2")
+
+ const iterA = domFor(A)
+ o(iterA.next().value.nodeName).equals("A1")
+ o(iterA.next().value.nodeName).equals("A2")
+ o(iterA.next().done).deepEquals(true)
+
+ const iterB = domFor(B)
+ o(iterB.next().value.nodeName).equals("B1")
+ o(iterB.next().value.nodeName).equals("B2")
+ o(iterB.next().done).deepEquals(true)
+
+ const iterC = domFor(C)
+ o(iterC.next().value.nodeName).equals("C1")
+ o(iterC.next().value.nodeName).equals("C2")
+ o(iterC.next().done).deepEquals(true)
+
+ callAsync(function(){
+ // not resolved yet
+ o(root.childNodes.length).equals(6)
+ o(root.childNodes[0].nodeName).equals("A1")
+ o(root.childNodes[1].nodeName).equals("A2")
+ o(root.childNodes[2].nodeName).equals("B1")
+ o(root.childNodes[3].nodeName).equals("B2")
+ o(root.childNodes[4].nodeName).equals("C1")
+ o(root.childNodes[5].nodeName).equals("C2")
+
+ const iterA = domFor(A)
+ o(iterA.next().value.nodeName).equals("A1")
+ o(iterA.next().value.nodeName).equals("A2")
+ o(iterA.next().done).deepEquals(true)
+
+ const iterB = domFor(B)
+ o(iterB.next().value.nodeName).equals("B1")
+ o(iterB.next().value.nodeName).equals("B2")
+ o(iterB.next().done).deepEquals(true)
+
+ const iterC = domFor(C)
+ o(iterC.next().value.nodeName).equals("C1")
+ o(iterC.next().value.nodeName).equals("C2")
+ o(iterC.next().done).deepEquals(true)
+
+ // resolve A
+ thenCBA()
+ callAsync(function(){
+ o(root.childNodes.length).equals(4)
+ o(root.childNodes[0].nodeName).equals("B1")
+ o(root.childNodes[1].nodeName).equals("B2")
+ o(root.childNodes[2].nodeName).equals("C1")
+ o(root.childNodes[3].nodeName).equals("C2")
+
+ const iterB = domFor(B)
+ o(iterB.next().value.nodeName).equals("B1")
+ o(iterB.next().value.nodeName).equals("B2")
+ o(iterB.next().done).deepEquals(true)
+
+ const iterC = domFor(C)
+ o(iterC.next().value.nodeName).equals("C1")
+ o(iterC.next().value.nodeName).equals("C2")
+ o(iterC.next().done).deepEquals(true)
+
+ // resolve C
+ thenCBC()
+ callAsync(function(){
+ o(root.childNodes.length).equals(2)
+ o(root.childNodes[0].nodeName).equals("B1")
+ o(root.childNodes[1].nodeName).equals("B2")
+
+ const iterC = domFor(B)
+ o(iterC.next().value.nodeName).equals("B1")
+ o(iterC.next().value.nodeName).equals("B2")
+ o(iterC.next().done).deepEquals(true)
+
+ // resolve B
+ thenCBB()
+ callAsync(function(){
+ o(root.childNodes.length).equals(0)
+ done()
+ })
+ })
+ })
+ })
+ })
+ o("works multiple vnodes with onbeforeremove (#3007, 5/6, BAC)", function (done) {
+ let thenCBA, thenCBB, thenCBC
+ const onbeforeremoveA = o.spy(function onbeforeremove(){
+ return {then(resolve){thenCBA = resolve}}
+ })
+ const onbeforeremoveB = o.spy(function onbeforeremove(){
+ return {then(resolve){thenCBB = resolve}}
+ })
+ const onbeforeremoveC = o.spy(function onbeforeremove(){
+ return {then(resolve){thenCBC = resolve}}
+ })
+ // to avoid updating internal nodes only, vnodes have key attributes
+ const A = fragment({key: 1, onbeforeremove: onbeforeremoveA}, [m("a1"), m("a2")])
+ const B = fragment({key: 2, onbeforeremove: onbeforeremoveB}, [m("b1"), m("b2")])
+ const C = fragment({key: 3, onbeforeremove: onbeforeremoveC}, [m("c1"), m("c2")])
+
+ render(root, [A])
+ o(onbeforeremoveA.callCount).equals(0)
+ o(onbeforeremoveB.callCount).equals(0)
+ o(onbeforeremoveC.callCount).equals(0)
+
+ render(root, [B])
+ o(onbeforeremoveA.callCount).equals(1)
+ o(onbeforeremoveB.callCount).equals(0)
+ o(onbeforeremoveC.callCount).equals(0)
+
+ render(root, [C])
+ o(onbeforeremoveA.callCount).equals(1)
+ o(onbeforeremoveB.callCount).equals(1)
+ o(onbeforeremoveC.callCount).equals(0)
+
+ render(root, [])
+ o(onbeforeremoveA.callCount).equals(1)
+ o(onbeforeremoveB.callCount).equals(1)
+ o(onbeforeremoveC.callCount).equals(1)
+
+ // not resolved
+ o(root.childNodes.length).equals(6)
+ o(root.childNodes[0].nodeName).equals("A1")
+ o(root.childNodes[1].nodeName).equals("A2")
+ o(root.childNodes[2].nodeName).equals("B1")
+ o(root.childNodes[3].nodeName).equals("B2")
+ o(root.childNodes[4].nodeName).equals("C1")
+ o(root.childNodes[5].nodeName).equals("C2")
+
+ const iterA = domFor(A)
+ o(iterA.next().value.nodeName).equals("A1")
+ o(iterA.next().value.nodeName).equals("A2")
+ o(iterA.next().done).deepEquals(true)
+
+ const iterB = domFor(B)
+ o(iterB.next().value.nodeName).equals("B1")
+ o(iterB.next().value.nodeName).equals("B2")
+ o(iterB.next().done).deepEquals(true)
+
+ const iterC = domFor(C)
+ o(iterC.next().value.nodeName).equals("C1")
+ o(iterC.next().value.nodeName).equals("C2")
+ o(iterC.next().done).deepEquals(true)
+
+ callAsync(function(){
+ // not resolved yet
+ o(root.childNodes.length).equals(6)
+ o(root.childNodes[0].nodeName).equals("A1")
+ o(root.childNodes[1].nodeName).equals("A2")
+ o(root.childNodes[2].nodeName).equals("B1")
+ o(root.childNodes[3].nodeName).equals("B2")
+ o(root.childNodes[4].nodeName).equals("C1")
+ o(root.childNodes[5].nodeName).equals("C2")
+
+ const iterA = domFor(A)
+ o(iterA.next().value.nodeName).equals("A1")
+ o(iterA.next().value.nodeName).equals("A2")
+ o(iterA.next().done).deepEquals(true)
+
+ const iterB = domFor(B)
+ o(iterB.next().value.nodeName).equals("B1")
+ o(iterB.next().value.nodeName).equals("B2")
+ o(iterB.next().done).deepEquals(true)
+
+ const iterC = domFor(C)
+ o(iterC.next().value.nodeName).equals("C1")
+ o(iterC.next().value.nodeName).equals("C2")
+ o(iterC.next().done).deepEquals(true)
+
+ // resolve B
+ thenCBB()
+ callAsync(function(){
+ o(root.childNodes.length).equals(4)
+ o(root.childNodes[0].nodeName).equals("A1")
+ o(root.childNodes[1].nodeName).equals("A2")
+ o(root.childNodes[2].nodeName).equals("C1")
+ o(root.childNodes[3].nodeName).equals("C2")
+
+ const iterB = domFor(A)
+ o(iterB.next().value.nodeName).equals("A1")
+ o(iterB.next().value.nodeName).equals("A2")
+ o(iterB.next().done).deepEquals(true)
+
+ const iterC = domFor(C)
+ o(iterC.next().value.nodeName).equals("C1")
+ o(iterC.next().value.nodeName).equals("C2")
+ o(iterC.next().done).deepEquals(true)
+
+ // resolve A
+ thenCBA()
+ callAsync(function(){
+ o(root.childNodes.length).equals(2)
+ o(root.childNodes[0].nodeName).equals("C1")
+ o(root.childNodes[1].nodeName).equals("C2")
+
+ const iterC = domFor(C)
+ o(iterC.next().value.nodeName).equals("C1")
+ o(iterC.next().value.nodeName).equals("C2")
+ o(iterC.next().done).deepEquals(true)
+
+ // resolve C
+ thenCBC()
+ callAsync(function(){
+ o(root.childNodes.length).equals(0)
+ done()
+ })
+ })
+ })
+ })
+ })
+ o("works multiple vnodes with onbeforeremove (#3007, 6/6, CBA)", function (done) {
+ let thenCBA, thenCBB, thenCBC
+ const onbeforeremoveA = o.spy(function onbeforeremove(){
+ return {then(resolve){thenCBA = resolve}}
+ })
+ const onbeforeremoveB = o.spy(function onbeforeremove(){
+ return {then(resolve){thenCBB = resolve}}
+ })
+ const onbeforeremoveC = o.spy(function onbeforeremove(){
+ return {then(resolve){thenCBC = resolve}}
+ })
+ // to avoid updating internal nodes only, vnodes have key attributes
+ const A = fragment({key: 1, onbeforeremove: onbeforeremoveA}, [m("a1"), m("a2")])
+ const B = fragment({key: 2, onbeforeremove: onbeforeremoveB}, [m("b1"), m("b2")])
+ const C = fragment({key: 3, onbeforeremove: onbeforeremoveC}, [m("c1"), m("c2")])
+
+ render(root, [A])
+ o(onbeforeremoveA.callCount).equals(0)
+ o(onbeforeremoveB.callCount).equals(0)
+ o(onbeforeremoveC.callCount).equals(0)
+
+ render(root, [B])
+ o(onbeforeremoveA.callCount).equals(1)
+ o(onbeforeremoveB.callCount).equals(0)
+ o(onbeforeremoveC.callCount).equals(0)
+
+ render(root, [C])
+ o(onbeforeremoveA.callCount).equals(1)
+ o(onbeforeremoveB.callCount).equals(1)
+ o(onbeforeremoveC.callCount).equals(0)
+
+ render(root, [])
+ o(onbeforeremoveA.callCount).equals(1)
+ o(onbeforeremoveB.callCount).equals(1)
+ o(onbeforeremoveC.callCount).equals(1)
+
+ // not resolved
+ o(root.childNodes.length).equals(6)
+ o(root.childNodes[0].nodeName).equals("A1")
+ o(root.childNodes[1].nodeName).equals("A2")
+ o(root.childNodes[2].nodeName).equals("B1")
+ o(root.childNodes[3].nodeName).equals("B2")
+ o(root.childNodes[4].nodeName).equals("C1")
+ o(root.childNodes[5].nodeName).equals("C2")
+
+ const iterA = domFor(A)
+ o(iterA.next().value.nodeName).equals("A1")
+ o(iterA.next().value.nodeName).equals("A2")
+ o(iterA.next().done).deepEquals(true)
+
+ const iterB = domFor(B)
+ o(iterB.next().value.nodeName).equals("B1")
+ o(iterB.next().value.nodeName).equals("B2")
+ o(iterB.next().done).deepEquals(true)
+
+ const iterC = domFor(C)
+ o(iterC.next().value.nodeName).equals("C1")
+ o(iterC.next().value.nodeName).equals("C2")
+ o(iterC.next().done).deepEquals(true)
+
+ callAsync(function(){
+ // not resolved yet
+ o(root.childNodes.length).equals(6)
+ o(root.childNodes[0].nodeName).equals("A1")
+ o(root.childNodes[1].nodeName).equals("A2")
+ o(root.childNodes[2].nodeName).equals("B1")
+ o(root.childNodes[3].nodeName).equals("B2")
+ o(root.childNodes[4].nodeName).equals("C1")
+ o(root.childNodes[5].nodeName).equals("C2")
+
+ const iterA = domFor(A)
+ o(iterA.next().value.nodeName).equals("A1")
+ o(iterA.next().value.nodeName).equals("A2")
+ o(iterA.next().done).deepEquals(true)
+
+ const iterB = domFor(B)
+ o(iterB.next().value.nodeName).equals("B1")
+ o(iterB.next().value.nodeName).equals("B2")
+ o(iterB.next().done).deepEquals(true)
+
+ const iterC = domFor(C)
+ o(iterC.next().value.nodeName).equals("C1")
+ o(iterC.next().value.nodeName).equals("C2")
+ o(iterC.next().done).deepEquals(true)
+
+ // resolve C
+ thenCBC()
+ callAsync(function(){
+ o(root.childNodes.length).equals(4)
+ o(root.childNodes[0].nodeName).equals("A1")
+ o(root.childNodes[1].nodeName).equals("A2")
+ o(root.childNodes[2].nodeName).equals("B1")
+ o(root.childNodes[3].nodeName).equals("B2")
+
+ const iterB = domFor(A)
+ o(iterB.next().value.nodeName).equals("A1")
+ o(iterB.next().value.nodeName).equals("A2")
+ o(iterB.next().done).deepEquals(true)
+
+ const iterC = domFor(B)
+ o(iterC.next().value.nodeName).equals("B1")
+ o(iterC.next().value.nodeName).equals("B2")
+ o(iterC.next().done).deepEquals(true)
+
+ // resolve B
+ thenCBB()
+ callAsync(function(){
+ o(root.childNodes.length).equals(2)
+ o(root.childNodes[0].nodeName).equals("A1")
+ o(root.childNodes[1].nodeName).equals("A2")
+
+ const iterC = domFor(A)
+ o(iterC.next().value.nodeName).equals("A1")
+ o(iterC.next().value.nodeName).equals("A2")
+ o(iterC.next().done).deepEquals(true)
+
+ // resolve A
+ thenCBA()
+ callAsync(function(){
+ o(root.childNodes.length).equals(0)
+ done()
+ })
+ })
+ })
+ })
+ })
components.forEach(function(cmp){
const {kind, create: createComponent} = cmp
o.spec(kind, function(){
@@ -173,6 +958,33 @@ o.spec("domFor(vnode)", function() {
o(onupdate.callCount).equals(1)
o(onbeforeremove.callCount).equals(1)
})
+ o("works in state.onbeforeremove and attrs.onbeforeremove", function () {
+ const onbeforeremove = o.spy(function onbeforeremove(vnode){
+ o(root.childNodes.length).equals(3)
+ o(root.childNodes[0].nodeName).equals("A")
+ o(root.childNodes[1].nodeName).equals("B")
+ o(root.childNodes[2].nodeName).equals("C")
+ const iter = domFor(vnode)
+ o(iter.next()).deepEquals({done:false, value: root.childNodes[0]})
+ o(iter.next()).deepEquals({done:false, value: root.childNodes[1]})
+ o(iter.next()).deepEquals({done:false, value: root.childNodes[2]})
+ o(iter.next().done).deepEquals(true)
+ o(root.childNodes.length).equals(3)
+ return {then(){}, finally(){}}
+ })
+ const C = createComponent({
+ view({children}){return children},
+ onbeforeremove
+ })
+ render(root, m(C, {onbeforeremove}, [
+ m("a"),
+ m("b"),
+ m("c")
+ ]))
+ render(root, [])
+
+ o(onbeforeremove.callCount).equals(2)
+ })
})
})
})
\ No newline at end of file
diff --git a/render/tests/test-onbeforeremove.js b/render/tests/test-onbeforeremove.js
index b5621e421..a9a55b6c7 100644
--- a/render/tests/test-onbeforeremove.js
+++ b/render/tests/test-onbeforeremove.js
@@ -122,6 +122,23 @@ o.spec("onbeforeremove", function() {
done()
})
})
+ o("handles thenable objecs (#2592)", function(done) {
+ var remove = function() {return {then: function(resolve) {resolve()}}}
+ var vnodes = m("div", {key: 1, onbeforeremove: remove}, "a")
+ var updated = []
+
+ render(root, vnodes)
+ render(root, updated)
+
+ o(root.childNodes.length).equals(1)
+ o(root.firstChild.firstChild.nodeValue).equals("a")
+
+ callAsync(function() {
+ o(root.childNodes.length).equals(0)
+
+ done()
+ })
+ })
components.forEach(function(cmp){
o.spec(cmp.kind, function(){
var createComponent = cmp.create
diff --git a/render/tests/test-onremove.js b/render/tests/test-onremove.js
index bf4800ffd..23ae334a5 100644
--- a/render/tests/test-onremove.js
+++ b/render/tests/test-onremove.js
@@ -6,6 +6,7 @@ var domMock = require("../../test-utils/domMock")
var vdom = require("../../render/render")
var m = require("../../render/hyperscript")
var fragment = require("../../render/fragment")
+var callAsync = require("../../test-utils/callAsync")
o.spec("onremove", function() {
var $window, root, render
@@ -194,7 +195,7 @@ o.spec("onremove", function() {
o(onremove.callCount).equals(1)
})
// Warning: this test is complicated because it's replicating a race condition.
- o("removes correct nodes in fragment when child delays removal, parent removes, then child resolves", function () {
+ o("removes correct nodes in fragment when child delays removal, parent removes, then child resolves", function (done) {
// Custom assertion - we need to test the entire tree for consistency.
const template = (tpl) => (root) => {
@@ -233,12 +234,12 @@ o.spec("onremove", function() {
${actual}`
}
}
- var finallyCB1
- var finallyCB2
+ var thenCB1
+ var thenCB2
var C = createComponent({
view({children}){return children},
onbeforeremove(){
- return {then(){}, finally: function (fcb) { finallyCB1 = fcb }}
+ return {then(resolve){thenCB1=resolve}}
}
})
function update(id, showParent, showChild) {
@@ -253,7 +254,7 @@ ${actual}`
m("a", {onremove: removeSyncChild}, "sync child"),
showChild && m(C, {
onbeforeremove: function () {
- return {then(){}, finally: function (fcb) { finallyCB2 = fcb }}
+ return {then(resolve){thenCB2=resolve}}
},
onremove: removeAsyncChild
}, m("div", id))
@@ -268,8 +269,8 @@ ${actual}`
["A", "sync child"],
["DIV", "1"],
]))
- o(finallyCB1).equals(undefined)
- o(finallyCB2).equals(undefined)
+ o(thenCB1).equals(undefined)
+ o(thenCB2).equals(undefined)
const hooks2 = update("2", true, false)
@@ -277,102 +278,114 @@ ${actual}`
["A", "sync child"],
["DIV", "1"],
]))
+ o(thenCB1).equals(undefined)
+ o(thenCB2).equals(undefined)
- o(typeof finallyCB1).equals("function")
- o(typeof finallyCB2).equals("function")
+ // Promises (micro-tasks) are processed before the callAsync callback.
+ callAsync(() => {
+ o(typeof thenCB1).equals("function")
+ o(typeof thenCB2).equals("function")
- var original1 = finallyCB1
- var original2 = finallyCB2
+ var original1 = thenCB1
+ var original2 = thenCB2
- const hooks3 = update("3", true, true)
+ const hooks3 = update("3", true, true)
- o(root).satisfies(template([
- ["A", "sync child"],
- ["DIV", "1"],
- ["DIV", "3"],
- ]))
+ o(root).satisfies(template([
+ ["A", "sync child"],
+ ["DIV", "1"],
+ ["DIV", "3"],
+ ]))
- o(hooks3.removeParent.callCount).equals(0)
- o(hooks3.removeSyncChild.callCount).equals(0)
- o(hooks3.removeAsyncChild.callCount).equals(0)
- o(finallyCB1).equals(original1)
- o(finallyCB2).equals(original2)
+ o(hooks3.removeParent.callCount).equals(0)
+ o(hooks3.removeSyncChild.callCount).equals(0)
+ o(hooks3.removeAsyncChild.callCount).equals(0)
+ o(thenCB1).equals(original1)
+ o(thenCB2).equals(original2)
- const hooks4 = update("4", false, true)
+ const hooks4 = update("4", false, true)
- o(root).satisfies(template([
- ["DIV", "1"],
- ]))
+ o(root).satisfies(template([
+ ["DIV", "1"],
+ ]))
- o(hooks3.removeParent.callCount).equals(1)
- o(hooks3.removeSyncChild.callCount).equals(1)
- o(hooks3.removeAsyncChild.callCount).equals(1)
- o(hooks3.removeParent.args[0].tag).equals("[")
- o(finallyCB1).equals(original1)
- o(finallyCB2).equals(original2)
+ o(hooks3.removeParent.callCount).equals(1)
+ o(hooks3.removeSyncChild.callCount).equals(1)
+ o(hooks3.removeAsyncChild.callCount).equals(1)
+ o(hooks3.removeParent.args[0].tag).equals("[")
+ o(thenCB1).equals(original1)
+ o(thenCB2).equals(original2)
- const hooks5 = update("5", true, true)
+ const hooks5 = update("5", true, true)
- o(root).satisfies(template([
- ["DIV", "1"],
- ["A", "sync child"],
- ["DIV", "5"],
- ]))
- o(finallyCB1).equals(original1)
- o(finallyCB2).equals(original2)
+ o(root).satisfies(template([
+ ["DIV", "1"],
+ ["A", "sync child"],
+ ["DIV", "5"],
+ ]))
+ o(thenCB1).equals(original1)
+ o(thenCB2).equals(original2)
- o(hooks1.removeAsyncChild.callCount).equals(0)
+ o(hooks1.removeAsyncChild.callCount).equals(0)
- finallyCB1()
+ thenCB1()
- o(hooks1.removeAsyncChild.callCount).equals(0)
+ o(hooks1.removeAsyncChild.callCount).equals(0)
+ callAsync(() => {
+ o(hooks1.removeAsyncChild.callCount).equals(0)
- finallyCB2()
+ thenCB2()
- o(hooks1.removeAsyncChild.callCount).equals(1)
+ o(hooks1.removeAsyncChild.callCount).equals(0)
+ callAsync(() => {
+ o(hooks1.removeAsyncChild.callCount).equals(1)
- o(root).satisfies(template([
- ["A", "sync child"],
- ["DIV", "5"],
- ]))
- o(finallyCB1).equals(original1)
- o(finallyCB2).equals(original2)
+ o(root).satisfies(template([
+ ["A", "sync child"],
+ ["DIV", "5"],
+ ]))
+ o(thenCB1).equals(original1)
+ o(thenCB2).equals(original2)
- const hooks6 = update("6", true, true)
+ const hooks6 = update("6", true, true)
- o(root).satisfies(template([
- ["A", "sync child"],
- ["DIV", "6"],
- ]))
- o(finallyCB1).equals(original1)
- o(finallyCB2).equals(original2)
+ o(root).satisfies(template([
+ ["A", "sync child"],
+ ["DIV", "6"],
+ ]))
+ o(thenCB1).equals(original1)
+ o(thenCB2).equals(original2)
- // final tally
- o(hooks1.removeParent.callCount).equals(0)
- o(hooks1.removeSyncChild.callCount).equals(0)
- o(hooks1.removeAsyncChild.callCount).equals(1)
+ // final tally
+ o(hooks1.removeParent.callCount).equals(0)
+ o(hooks1.removeSyncChild.callCount).equals(0)
+ o(hooks1.removeAsyncChild.callCount).equals(1)
- o(hooks2.removeParent.callCount).equals(0)
- o(hooks2.removeSyncChild.callCount).equals(0)
- o(hooks2.removeAsyncChild.callCount).equals(0)
+ o(hooks2.removeParent.callCount).equals(0)
+ o(hooks2.removeSyncChild.callCount).equals(0)
+ o(hooks2.removeAsyncChild.callCount).equals(0)
- o(hooks3.removeParent.callCount).equals(1)
- o(hooks3.removeSyncChild.callCount).equals(1)
- o(hooks3.removeAsyncChild.callCount).equals(1)
+ o(hooks3.removeParent.callCount).equals(1)
+ o(hooks3.removeSyncChild.callCount).equals(1)
+ o(hooks3.removeAsyncChild.callCount).equals(1)
- o(hooks4.removeParent.callCount).equals(0)
- o(hooks4.removeSyncChild.callCount).equals(0)
- o(hooks4.removeAsyncChild.callCount).equals(0)
+ o(hooks4.removeParent.callCount).equals(0)
+ o(hooks4.removeSyncChild.callCount).equals(0)
+ o(hooks4.removeAsyncChild.callCount).equals(0)
- o(hooks5.removeParent.callCount).equals(0)
- o(hooks5.removeSyncChild.callCount).equals(0)
- o(hooks5.removeAsyncChild.callCount).equals(0)
+ o(hooks5.removeParent.callCount).equals(0)
+ o(hooks5.removeSyncChild.callCount).equals(0)
+ o(hooks5.removeAsyncChild.callCount).equals(0)
- o(hooks6.removeParent.callCount).equals(0)
- o(hooks6.removeSyncChild.callCount).equals(0)
- o(hooks6.removeAsyncChild.callCount).equals(0)
+ o(hooks6.removeParent.callCount).equals(0)
+ o(hooks6.removeSyncChild.callCount).equals(0)
+ o(hooks6.removeAsyncChild.callCount).equals(0)
+ done()
+ })
+ })
+ })
})
})
})
diff --git a/render/tests/test-updateElement.js b/render/tests/test-updateElement.js
index 771b4a405..2cc689321 100644
--- a/render/tests/test-updateElement.js
+++ b/render/tests/test-updateElement.js
@@ -388,4 +388,178 @@ o.spec("updateElement", function() {
o(root.childNodes.length).equals(3)
o(x).notEquals(y) // this used to be a recycling pool test
})
+ o.spec("element node with `is` attribute", function() {
+ o("recreate element node with `is` attribute (set `is`)", function() {
+ var vnode = m("a")
+ var updated = m("a", {is: "bar"})
+
+ render(root, vnode)
+ render(root, updated)
+
+ o(vnode.dom).notEquals(root.firstChild)
+ o(updated.dom).equals(root.firstChild)
+ o(updated.dom.nodeName).equals("A")
+ o(updated.dom.getAttribute("is")).equals("bar")
+ })
+ o("recreate element node without `is` attribute (remove `is`)", function() {
+ var vnode = m("a", {is: "foo"})
+ var updated = m("a")
+
+ render(root, vnode)
+ render(root, updated)
+
+ o(vnode.dom).notEquals(root.firstChild)
+ o(updated.dom).equals(root.firstChild)
+ o(updated.dom.nodeName).equals("A")
+ o(updated.dom.getAttribute("is")).equals(null)
+ })
+ o("recreate element node with `is` attribute (same tag, different `is`)", function() {
+ var vnode = m("a", {is: "foo"})
+ var updated = m("a", {is: "bar"})
+
+ render(root, vnode)
+ render(root, updated)
+
+ o(vnode.dom).notEquals(root.firstChild)
+ o(updated.dom).equals(root.firstChild)
+ o(updated.dom.nodeName).equals("A")
+ o(updated.dom.getAttribute("is")).equals("bar")
+ })
+ o("recreate element node with `is` attribute (different tag, same `is`)", function() {
+ var vnode = m("a", {is: "foo"})
+ var updated = m("b", {is: "foo"})
+
+ render(root, vnode)
+ render(root, updated)
+
+ o(vnode.dom).notEquals(root.firstChild)
+ o(updated.dom).equals(root.firstChild)
+ o(updated.dom.nodeName).equals("B")
+ o(updated.dom.getAttribute("is")).equals("foo")
+ })
+ o("recreate element node with `is` attribute (different tag, different `is`)", function() {
+ var vnode = m("a", {is: "foo"})
+ var updated = m("b", {is: "bar"})
+
+ render(root, vnode)
+ render(root, updated)
+
+ o(vnode.dom).notEquals(root.firstChild)
+ o(updated.dom).equals(root.firstChild)
+ o(updated.dom.nodeName).equals("B")
+ o(updated.dom.getAttribute("is")).equals("bar")
+ })
+ o("keep element node with `is` attribute (same tag, same `is`)", function() {
+ var vnode = m("a", {is: "foo"})
+ var updated = m("a", {is: "foo"}, "x")
+
+ render(root, vnode)
+ render(root, updated)
+
+ o(vnode.dom).equals(root.firstChild)
+ o(updated.dom).equals(root.firstChild)
+ o(updated.dom.nodeName).equals("A")
+ o(updated.dom.getAttribute("is")).equals("foo")
+ o(updated.dom.firstChild.nodeValue).equals("x")
+ })
+ o("recreate element node with `is` attribute (set `is`, CSS selector)", function() {
+ var vnode = m("a")
+ var updated = m("a[is=bar]")
+
+ render(root, vnode)
+ render(root, updated)
+
+ o(vnode.dom).notEquals(root.firstChild)
+ o(updated.dom).equals(root.firstChild)
+ o(updated.dom.nodeName).equals("A")
+ o(updated.dom.getAttribute("is")).equals("bar")
+ })
+ o("recreate element node without `is` attribute (remove `is`, CSS selector)", function() {
+ var vnode = m("a[is=foo]")
+ var updated = m("a")
+
+ render(root, vnode)
+ render(root, updated)
+
+ o(vnode.dom).notEquals(root.firstChild)
+ o(updated.dom).equals(root.firstChild)
+ o(updated.dom.nodeName).equals("A")
+ o(updated.dom.getAttribute("is")).equals(null)
+ })
+ o("recreate element node with `is` attribute (same tag, different `is`, CSS selector)", function() {
+ var vnode = m("a[is=foo]")
+ var updated = m("a[is=bar]")
+
+ render(root, vnode)
+ render(root, updated)
+
+ o(vnode.dom).notEquals(root.firstChild)
+ o(updated.dom).equals(root.firstChild)
+ o(updated.dom.nodeName).equals("A")
+ o(updated.dom.getAttribute("is")).equals("bar")
+ })
+ o("recreate element node with `is` attribute (different tag, same `is`, CSS selector)", function() {
+ var vnode = m("a[is=foo]")
+ var updated = m("b[is=foo]")
+
+ render(root, vnode)
+ render(root, updated)
+
+ o(vnode.dom).notEquals(root.firstChild)
+ o(updated.dom).equals(root.firstChild)
+ o(updated.dom.nodeName).equals("B")
+ o(updated.dom.getAttribute("is")).equals("foo")
+ })
+ o("recreate element node with `is` attribute (different tag, different `is`, CSS selector)", function() {
+ var vnode = m("a[is=foo]")
+ var updated = m("b[is=bar]")
+
+ render(root, vnode)
+ render(root, updated)
+
+ o(vnode.dom).notEquals(root.firstChild)
+ o(updated.dom).equals(root.firstChild)
+ o(updated.dom.nodeName).equals("B")
+ o(updated.dom.getAttribute("is")).equals("bar")
+ })
+ o("keep element node with `is` attribute (same tag, same `is`, CSS selector)", function() {
+ var vnode = m("a[is=foo]")
+ var updated = m("a[is=foo]", "x")
+
+ render(root, vnode)
+ render(root, updated)
+
+ o(vnode.dom).equals(root.firstChild)
+ o(updated.dom).equals(root.firstChild)
+ o(updated.dom.nodeName).equals("A")
+ o(updated.dom.getAttribute("is")).equals("foo")
+ o(updated.dom.firstChild.nodeValue).equals("x")
+ })
+ o("keep element node with `is` attribute (same tag, same `is`, from attrs to CSS selector)", function() {
+ var vnode = m("a", {is: "foo"})
+ var updated = m("a[is=foo]", "x")
+
+ render(root, vnode)
+ render(root, updated)
+
+ o(vnode.dom).equals(root.firstChild)
+ o(updated.dom).equals(root.firstChild)
+ o(updated.dom.nodeName).equals("A")
+ o(updated.dom.getAttribute("is")).equals("foo")
+ o(updated.dom.firstChild.nodeValue).equals("x")
+ })
+ o("keep element node with `is` attribute (same tag, same `is`, from CSS selector to attrs)", function() {
+ var vnode = m("a[is=foo]")
+ var updated = m("a", {is: "foo"}, "x")
+
+ render(root, vnode)
+ render(root, updated)
+
+ o(vnode.dom).equals(root.firstChild)
+ o(updated.dom).equals(root.firstChild)
+ o(updated.dom.nodeName).equals("A")
+ o(updated.dom.getAttribute("is")).equals("foo")
+ o(updated.dom.firstChild.nodeValue).equals("x")
+ })
+ })
})
diff --git a/render/vnode.js b/render/vnode.js
index ec19b174f..215980f21 100644
--- a/render/vnode.js
+++ b/render/vnode.js
@@ -1,7 +1,7 @@
"use strict"
function Vnode(tag, key, attrs, children, text, dom) {
- return {tag: tag, key: key, attrs: attrs, children: children, text: text, dom: dom, domSize: undefined, state: undefined, events: undefined, instance: undefined}
+ return {tag: tag, key: key, attrs: attrs, children: children, text: text, dom: dom, is: undefined, domSize: undefined, state: undefined, events: undefined, instance: undefined}
}
Vnode.normalize = function(node) {
if (Array.isArray(node)) return Vnode("[", undefined, undefined, Vnode.normalizeChildren(node), undefined, undefined)