implement m.domFor() and use it internally to move and remove nodes. Fix #2780
This commit is contained in:
parent
27fb1ea617
commit
3fd82e6359
8 changed files with 345 additions and 267 deletions
176
render/tests/test-dom-for.js
Normal file
176
render/tests/test-dom-for.js
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
var o = require("ospec")
|
||||
var components = require("../../test-utils/components")
|
||||
var domMock = require("../../test-utils/domMock")
|
||||
var vdom = require("../../render/render")
|
||||
var m = require("../../render/hyperscript")
|
||||
var fragment = require("../../render/fragment")
|
||||
var domFor = require('../../render/dom-for').domFor
|
||||
|
||||
o.spec("domFor(vnode)", function() {
|
||||
var $window, root, render
|
||||
o.beforeEach(function() {
|
||||
$window = domMock()
|
||||
root = $window.document.createElement("div")
|
||||
render = vdom($window)
|
||||
})
|
||||
o('works for simple vnodes', function() {
|
||||
render(root, m('div', {oncreate(vnode){
|
||||
let n = 0
|
||||
for (const dom of domFor(vnode)) {
|
||||
o(dom).equals(root.firstChild)
|
||||
o(++n).equals(1)
|
||||
}
|
||||
}}))
|
||||
})
|
||||
o('works for fragments', function () {
|
||||
render(root, fragment({
|
||||
oncreate(vnode){
|
||||
let n = 0
|
||||
for (const dom of domFor(vnode)) {
|
||||
o(dom).equals(root.childNodes[n])
|
||||
n++
|
||||
}
|
||||
o(n).equals(2)
|
||||
}
|
||||
}, [
|
||||
m('a'),
|
||||
m('b')
|
||||
]))
|
||||
})
|
||||
o('works in fragments with children that have delayed removal', function() {
|
||||
function oncreate(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)
|
||||
}
|
||||
function onupdate(vnode) {
|
||||
// the b node is still present in the DOM
|
||||
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[2]})
|
||||
o(iter.next().done).deepEquals(true)
|
||||
o(root.childNodes.length).equals(3)
|
||||
}
|
||||
|
||||
render(root, fragment(
|
||||
{oncreate, onupdate},
|
||||
[
|
||||
m('a'),
|
||||
m('b', {onbeforeremove(){return {then(){}, finally(){}}}}),
|
||||
m('c')
|
||||
]
|
||||
))
|
||||
render(root, fragment(
|
||||
{oncreate, onupdate},
|
||||
[
|
||||
m('a'),
|
||||
null,
|
||||
m('c'),
|
||||
]
|
||||
))
|
||||
|
||||
})
|
||||
components.forEach(function(cmp){
|
||||
o.spec(cmp.kind, function(){
|
||||
var createComponent = cmp.create
|
||||
o('works for components that return one element', function() {
|
||||
const C = createComponent({
|
||||
view(){return m('div')},
|
||||
oncreate(vnode){
|
||||
let n = 0
|
||||
for (const dom of domFor(vnode)) {
|
||||
o(dom).equals(root.firstChild)
|
||||
o(++n).equals(1)
|
||||
}
|
||||
}
|
||||
})
|
||||
render(root, m(C))
|
||||
})
|
||||
o('works for components that return fragments', function () {
|
||||
const oncreate = o.spy(function oncreate(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)
|
||||
})
|
||||
const C = createComponent({
|
||||
view({children}){return children},
|
||||
oncreate
|
||||
})
|
||||
render(root, m(C, [
|
||||
m('a'),
|
||||
m('b'),
|
||||
m('c')
|
||||
]))
|
||||
o(oncreate.callCount).equals(1)
|
||||
})
|
||||
o('works for components that return fragments with delayed removal', function () {
|
||||
const onbeforeremove = o.spy(function onbeforeremove(){return {then(){}, finally(){}}})
|
||||
const oncreate = o.spy(function oncreate(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)
|
||||
})
|
||||
const onupdate = o.spy(function onupdate(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[2]})
|
||||
o(iter.next().done).deepEquals(true)
|
||||
o(root.childNodes.length).equals(3)
|
||||
})
|
||||
const C = createComponent({
|
||||
view({children}){return children},
|
||||
oncreate,
|
||||
onupdate
|
||||
})
|
||||
render(root, m(C, [
|
||||
m('a'),
|
||||
m('b', {onbeforeremove}),
|
||||
m('c')
|
||||
]))
|
||||
render(root, m(C, [
|
||||
m('a'),
|
||||
null,
|
||||
m('c')
|
||||
]))
|
||||
o(oncreate.callCount).equals(1)
|
||||
o(onupdate.callCount).equals(1)
|
||||
o(onbeforeremove.callCount).equals(1)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -195,13 +195,13 @@ o.spec("onremove", function() {
|
|||
})
|
||||
// Warning: this test is complicated because it's replicating a race condition.
|
||||
o("removes correct nodes when child delays removal, parent removes, then child resolves", function () {
|
||||
// Sugar over the complexity - I need to test the entire tree for consistency.
|
||||
function expect(expectedPairs) {
|
||||
// Custom assertion - we need to test the entire tree for consistency.
|
||||
function template(tpl) {return function (root) {
|
||||
var expected = []
|
||||
|
||||
for (var i = 0; i < expectedPairs.length; i++) {
|
||||
var name = expectedPairs[i][0]
|
||||
var text = expectedPairs[i][1]
|
||||
for (var i = 0; i < tpl.length; i++) {
|
||||
var name = tpl[i][0]
|
||||
var text = tpl[i][1]
|
||||
expected.push({
|
||||
name: name,
|
||||
firstType: name === "#text" ? null : "#text",
|
||||
|
|
@ -222,23 +222,30 @@ o.spec("onremove", function() {
|
|||
text: textNode.nodeValue,
|
||||
})
|
||||
}
|
||||
actual = JSON.stringify(actual, null, ' ')
|
||||
expected = JSON.stringify(expected, null, ' ')
|
||||
return {
|
||||
pass: actual === expected,
|
||||
message:
|
||||
`${expected}
|
||||
expected, got
|
||||
${actual}`
|
||||
}
|
||||
}}
|
||||
|
||||
o(actual).deepEquals(expected)
|
||||
}
|
||||
|
||||
var resolve
|
||||
|
||||
var finallyCB
|
||||
function update(id, showParent, showChild) {
|
||||
render(root,
|
||||
m("div",
|
||||
showParent && fragment(
|
||||
"", // Required
|
||||
showChild && fragment({
|
||||
onbeforeremove: function () {
|
||||
return {then: function (r) { resolve = r }}
|
||||
showChild && fragment(
|
||||
{
|
||||
onbeforeremove: function () {
|
||||
return {then(){}, finally: function (fcb) { finallyCB = fcb }}
|
||||
},
|
||||
},
|
||||
},
|
||||
m("div", id)
|
||||
m("div", id)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
|
@ -246,170 +253,61 @@ o.spec("onremove", function() {
|
|||
}
|
||||
|
||||
update("1", true, true)
|
||||
expect([
|
||||
o(root).satisfies(template([
|
||||
["#text", ""],
|
||||
["DIV", "1"],
|
||||
])
|
||||
o(resolve).equals(undefined)
|
||||
]))
|
||||
o(finallyCB).equals(undefined)
|
||||
|
||||
update("2", true, false)
|
||||
expect([
|
||||
|
||||
o(root).satisfies(template([
|
||||
["#text", ""],
|
||||
["DIV", "1"],
|
||||
])
|
||||
o(typeof resolve).equals("function")
|
||||
var original = resolve
|
||||
]))
|
||||
o(typeof finallyCB).equals("function")
|
||||
|
||||
var original = finallyCB
|
||||
|
||||
update("3", true, true)
|
||||
expect([
|
||||
|
||||
o(root).satisfies(template([
|
||||
["#text", ""],
|
||||
["DIV", "1"],
|
||||
["DIV", "3"],
|
||||
])
|
||||
o(resolve).equals(original)
|
||||
]))
|
||||
o(finallyCB).equals(original)
|
||||
|
||||
update("4", false, true)
|
||||
expect([
|
||||
|
||||
o(root).satisfies(template([
|
||||
["DIV", "1"],
|
||||
])
|
||||
o(resolve).equals(original)
|
||||
]))
|
||||
o(finallyCB).equals(original)
|
||||
|
||||
update("5", true, true)
|
||||
expect([
|
||||
|
||||
o(root).satisfies(template([
|
||||
["DIV", "1"],
|
||||
["#text", ""],
|
||||
["DIV", "5"],
|
||||
])
|
||||
o(resolve).equals(original)
|
||||
]))
|
||||
o(finallyCB).equals(original)
|
||||
|
||||
resolve()
|
||||
expect([
|
||||
finallyCB()
|
||||
|
||||
o(root).satisfies(template([
|
||||
["#text", ""],
|
||||
["DIV", "5"],
|
||||
])
|
||||
o(resolve).equals(original)
|
||||
]))
|
||||
o(finallyCB).equals(original)
|
||||
|
||||
update("6", true, true)
|
||||
expect([
|
||||
o(root).satisfies(template([
|
||||
["#text", ""],
|
||||
["DIV", "6"],
|
||||
])
|
||||
o(resolve).equals(original)
|
||||
})
|
||||
// Warning: this test is complicated because it's replicating a race condition.
|
||||
o("removes correct nodes when child delays removal, parent removes, then child resolves + rejects both", function () {
|
||||
// Sugar over the complexity - I need to test the entire tree for consistency.
|
||||
function expect(expectedPairs) {
|
||||
var expected = []
|
||||
|
||||
for (var i = 0; i < expectedPairs.length; i++) {
|
||||
var name = expectedPairs[i][0]
|
||||
var text = expectedPairs[i][1]
|
||||
expected.push({
|
||||
name: name,
|
||||
firstType: name === "#text" ? null : "#text",
|
||||
text: text,
|
||||
})
|
||||
}
|
||||
|
||||
var actual = []
|
||||
var list = root.firstChild.childNodes
|
||||
for (var i = 0; i < list.length; i++) {
|
||||
var current = list[i]
|
||||
var textNode = current.childNodes.length === 1
|
||||
? current.firstChild
|
||||
: current
|
||||
actual.push({
|
||||
name: current.nodeName,
|
||||
firstType: textNode === current ? null : textNode.nodeName,
|
||||
text: textNode.nodeValue,
|
||||
})
|
||||
}
|
||||
|
||||
o(actual).deepEquals(expected)
|
||||
}
|
||||
|
||||
var resolve, reject
|
||||
|
||||
function update(id, showParent, showChild) {
|
||||
render(root,
|
||||
m("div",
|
||||
showParent && fragment(
|
||||
"", // Required
|
||||
showChild && fragment({
|
||||
onbeforeremove: function () {
|
||||
return {then: function (res, rej) {
|
||||
resolve = res
|
||||
reject = rej
|
||||
}}
|
||||
},
|
||||
},
|
||||
m("div", id)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
update("1", true, true)
|
||||
expect([
|
||||
["#text", ""],
|
||||
["DIV", "1"],
|
||||
])
|
||||
o(resolve).equals(undefined)
|
||||
|
||||
update("2", true, false)
|
||||
expect([
|
||||
["#text", ""],
|
||||
["DIV", "1"],
|
||||
])
|
||||
o(typeof resolve).equals("function")
|
||||
var originalResolve = resolve
|
||||
var originalReject = reject
|
||||
|
||||
update("3", true, true)
|
||||
expect([
|
||||
["#text", ""],
|
||||
["DIV", "1"],
|
||||
["DIV", "3"],
|
||||
])
|
||||
o(resolve).equals(originalResolve)
|
||||
o(reject).equals(originalReject)
|
||||
|
||||
update("4", false, true)
|
||||
expect([
|
||||
["DIV", "1"],
|
||||
])
|
||||
o(resolve).equals(originalResolve)
|
||||
o(reject).equals(originalReject)
|
||||
|
||||
update("5", true, true)
|
||||
expect([
|
||||
["DIV", "1"],
|
||||
["#text", ""],
|
||||
["DIV", "5"],
|
||||
])
|
||||
o(resolve).equals(originalResolve)
|
||||
o(reject).equals(originalReject)
|
||||
|
||||
resolve()
|
||||
reject()
|
||||
reject()
|
||||
resolve()
|
||||
expect([
|
||||
["#text", ""],
|
||||
["DIV", "5"],
|
||||
])
|
||||
o(resolve).equals(originalResolve)
|
||||
o(reject).equals(originalReject)
|
||||
|
||||
update("6", true, true)
|
||||
expect([
|
||||
["#text", ""],
|
||||
["DIV", "6"],
|
||||
])
|
||||
o(resolve).equals(originalResolve)
|
||||
o(reject).equals(originalReject)
|
||||
]))
|
||||
o(finallyCB).equals(original)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue