Merge branch 'next'
This commit is contained in:
commit
2635070734
79 changed files with 7993 additions and 3507 deletions
|
|
@ -21,6 +21,7 @@
|
|||
<script src="test-fragment.js"></script>
|
||||
<script src="test-normalize.js"></script>
|
||||
<script src="test-normalizeChildren.js"></script>
|
||||
<script src="test-normalizeComponentChildren.js"></script>
|
||||
<script src="test-createText.js"></script>
|
||||
<script src="test-createHTML.js"></script>
|
||||
<script src="test-createFragment.js"></script>
|
||||
|
|
|
|||
|
|
@ -44,47 +44,92 @@ o.spec("attributes", function() {
|
|||
o(b.dom.hasAttribute("id")).equals(true)
|
||||
o(b.dom.getAttribute("id")).equals("test")
|
||||
|
||||
// #1804
|
||||
render(root, [c]);
|
||||
|
||||
// #1804
|
||||
// TODO: uncomment
|
||||
// o(c.dom.hasAttribute("id")).equals(false)
|
||||
o(c.dom.hasAttribute("id")).equals(false)
|
||||
})
|
||||
})
|
||||
o.spec("customElements", function(){
|
||||
|
||||
o("when vnode is customElement, custom setAttribute called", function(){
|
||||
|
||||
var normal = [
|
||||
{tag: "input", attrs: {value: "hello"}},
|
||||
{tag: "input", attrs: {value: "hello"}},
|
||||
{tag: "input", attrs: {value: "hello"}}
|
||||
]
|
||||
|
||||
var custom = [
|
||||
{tag: "custom-element", attrs: {custom: "x"}},
|
||||
{tag: "input", attrs: {is: "something-special", custom: "x"}},
|
||||
{tag: "custom-element", attrs: {is: "something-special", custom: "x"}}
|
||||
]
|
||||
|
||||
var view = normal.concat(custom)
|
||||
|
||||
o("when vnode is customElement without property, custom setAttribute called", function(){
|
||||
var f = $window.document.createElement
|
||||
var spy
|
||||
var spies = []
|
||||
|
||||
$window.document.createElement = function(tag, is){
|
||||
var el = f(tag, is)
|
||||
if(!spy){
|
||||
spy = o.spy(el.setAttribute)
|
||||
}
|
||||
var spy = o.spy(el.setAttribute)
|
||||
el.setAttribute = spy
|
||||
|
||||
spies.push(spy)
|
||||
spy.elem = el
|
||||
return el
|
||||
}
|
||||
|
||||
render(root, view)
|
||||
render(root, [
|
||||
{tag: "input", attrs: {value: "hello"}},
|
||||
{tag: "input", attrs: {value: "hello"}},
|
||||
{tag: "input", attrs: {value: "hello"}},
|
||||
{tag: "custom-element", attrs: {custom: "x"}},
|
||||
{tag: "input", attrs: {is: "something-special", custom: "x"}},
|
||||
{tag: "custom-element", attrs: {is: "something-special", custom: "x"}}
|
||||
])
|
||||
|
||||
o(spy.callCount).equals(custom.length)
|
||||
o(spies[0].callCount).equals(0)
|
||||
o(spies[1].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("when vnode is customElement with property, custom setAttribute not called", function(){
|
||||
var f = $window.document.createElement
|
||||
var spies = []
|
||||
var getters = []
|
||||
var setters = []
|
||||
|
||||
$window.document.createElement = function(tag, is){
|
||||
var el = f(tag, is)
|
||||
var spy = o.spy(el.setAttribute)
|
||||
el.setAttribute = spy
|
||||
spies.push(spy)
|
||||
spy.elem = el
|
||||
if (tag === "custom-element" || is && is.is === "something-special") {
|
||||
var custom = "foo"
|
||||
var getter, setter
|
||||
Object.defineProperty(el, "custom", {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
get: getter = o.spy(function () { return custom }),
|
||||
set: setter = o.spy(function (value) { custom = value })
|
||||
})
|
||||
getters.push(getter)
|
||||
setters.push(setter)
|
||||
}
|
||||
return el
|
||||
}
|
||||
|
||||
render(root, [
|
||||
{tag: "input", attrs: {value: "hello"}},
|
||||
{tag: "input", attrs: {value: "hello"}},
|
||||
{tag: "input", attrs: {value: "hello"}},
|
||||
{tag: "custom-element", attrs: {custom: "x"}},
|
||||
{tag: "input", attrs: {is: "something-special", custom: "x"}},
|
||||
{tag: "custom-element", attrs: {is: "something-special", custom: "x"}}
|
||||
])
|
||||
|
||||
o(spies[0].callCount).equals(0)
|
||||
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(getters[0].callCount).equals(0)
|
||||
o(getters[1].callCount).equals(0)
|
||||
o(getters[2].callCount).equals(0)
|
||||
o(setters[0].calls).deepEquals([{this: spies[3].elem, args: ["x"]}])
|
||||
o(setters[1].calls).deepEquals([{this: spies[4].elem, args: ["x"]}])
|
||||
o(setters[2].calls).deepEquals([{this: spies[5].elem, args: ["x"]}])
|
||||
})
|
||||
|
||||
})
|
||||
|
|
@ -147,7 +192,7 @@ o.spec("attributes", function() {
|
|||
o("a lack of attribute removes `value`", function() {
|
||||
var a = {tag: "input", attrs: {}}
|
||||
var b = {tag: "input", attrs: {value: "test"}}
|
||||
// var c = {tag: "input", attrs: {}}
|
||||
var c = {tag: "input", attrs: {}}
|
||||
|
||||
render(root, [a])
|
||||
|
||||
|
|
@ -158,10 +203,9 @@ o.spec("attributes", function() {
|
|||
o(a.dom.value).equals("test")
|
||||
|
||||
// https://github.com/MithrilJS/mithril.js/issues/1804#issuecomment-304521235
|
||||
// TODO: Uncomment
|
||||
// render(root, [c])
|
||||
render(root, [c])
|
||||
|
||||
// o(a.dom.value).equals("")
|
||||
o(a.dom.value).equals("")
|
||||
})
|
||||
o("can be set as number", function() {
|
||||
var a = {tag: "input", attrs: {value: 1}}
|
||||
|
|
@ -276,17 +320,16 @@ o.spec("attributes", function() {
|
|||
o.spec("textarea.value", function() {
|
||||
o("can be removed by not passing a value", function() {
|
||||
var a = {tag: "textarea", attrs: {value:"x"}}
|
||||
// var b = {tag: "textarea", attrs: {}}
|
||||
var b = {tag: "textarea", attrs: {}}
|
||||
|
||||
render(root, [a])
|
||||
|
||||
o(a.dom.value).equals("x")
|
||||
|
||||
// https://github.com/MithrilJS/mithril.js/issues/1804#issuecomment-304521235
|
||||
// TODO: Uncomment
|
||||
// render(root, [b])
|
||||
render(root, [b])
|
||||
|
||||
// o(b.dom.value).equals("")
|
||||
o(b.dom.value).equals("")
|
||||
})
|
||||
o("isn't set when equivalent to the previous value and focused", function() {
|
||||
var $window = domMock({spy: o.spy})
|
||||
|
|
@ -352,7 +395,7 @@ o.spec("attributes", function() {
|
|||
o(canvas.dom.width).equals(100)
|
||||
})
|
||||
})
|
||||
o.spec("svg class", function() {
|
||||
o.spec("svg", function() {
|
||||
o("when className is specified then it should be added as a class", function() {
|
||||
var a = {tag: "svg", attrs: {className: "test"}}
|
||||
|
||||
|
|
@ -360,6 +403,26 @@ o.spec("attributes", function() {
|
|||
|
||||
o(a.dom.attributes["class"].value).equals("test")
|
||||
})
|
||||
/* eslint-disable no-script-url */
|
||||
o("handles xlink:href", function() {
|
||||
var vnode = {tag: "svg", ns: "http://www.w3.org/2000/svg", children: [
|
||||
{tag: "a", ns: "http://www.w3.org/2000/svg", attrs: {"xlink:href": "javascript:;"}}
|
||||
]}
|
||||
render(root, [vnode])
|
||||
|
||||
o(vnode.dom.nodeName).equals("svg")
|
||||
o(vnode.dom.firstChild.attributes["href"].value).equals("javascript:;")
|
||||
o(vnode.dom.firstChild.attributes["href"].namespaceURI).equals("http://www.w3.org/1999/xlink")
|
||||
|
||||
vnode = {tag: "svg", ns: "http://www.w3.org/2000/svg", children: [
|
||||
{tag: "a", ns: "http://www.w3.org/2000/svg", attrs: {}}
|
||||
]}
|
||||
render(root, [vnode])
|
||||
|
||||
o(vnode.dom.nodeName).equals("svg")
|
||||
o("href" in vnode.dom.firstChild.attributes).equals(false)
|
||||
})
|
||||
/* eslint-enable no-script-url */
|
||||
})
|
||||
o.spec("option.value", function() {
|
||||
o("can be set as text", function() {
|
||||
|
|
@ -376,7 +439,7 @@ o.spec("attributes", function() {
|
|||
|
||||
o(a.dom.value).equals("1")
|
||||
})
|
||||
o("null becomes the empty string", function() {
|
||||
o("null removes the attribute", function() {
|
||||
var a = {tag: "option", attrs: {value: null}}
|
||||
var b = {tag: "option", attrs: {value: "test"}}
|
||||
var c = {tag: "option", attrs: {value: null}}
|
||||
|
|
@ -384,7 +447,7 @@ o.spec("attributes", function() {
|
|||
render(root, [a]);
|
||||
|
||||
o(a.dom.value).equals("")
|
||||
o(a.dom.getAttribute("value")).equals("")
|
||||
o(a.dom.hasAttribute("value")).equals(false)
|
||||
|
||||
render(root, [b]);
|
||||
|
||||
|
|
@ -394,7 +457,7 @@ o.spec("attributes", function() {
|
|||
render(root, [c]);
|
||||
|
||||
o(c.dom.value).equals("")
|
||||
o(c.dom.getAttribute("value")).equals("")
|
||||
o(c.dom.hasAttribute("value")).equals(false)
|
||||
})
|
||||
o("'' and 0 are different values", function() {
|
||||
var a = {tag: "option", attrs: {value: 0}, children:[{tag:"#", children:""}]}
|
||||
|
|
@ -462,6 +525,19 @@ o.spec("attributes", function() {
|
|||
{tag:"option", attrs: {value: ""}}
|
||||
]}
|
||||
}
|
||||
/* FIXME
|
||||
This incomplete test is meant for testing #1916.
|
||||
However it cannot be completed until #1978 is addressed
|
||||
which is a lack a working select.selected / option.selected
|
||||
attribute. Ask isiahmeadows.
|
||||
|
||||
o("render select options", function() {
|
||||
var select = {tag: "select", selectedIndex: 0, children: [
|
||||
{tag:"option", attrs: {value: "1", selected: ""}}
|
||||
]}
|
||||
render(root, select)
|
||||
})
|
||||
*/
|
||||
o("can be set as text", function() {
|
||||
var a = makeSelect()
|
||||
var b = makeSelect("2")
|
||||
|
|
|
|||
|
|
@ -764,97 +764,6 @@ o.spec("component", function() {
|
|||
o(attrs[hook].callCount).equals(methods[hook].callCount)(hook)
|
||||
})
|
||||
})
|
||||
o("lifecycle timing megatest (for a single component with the state overwritten)", function() {
|
||||
var methods = {
|
||||
view: o.spy(function(vnode) {
|
||||
o(vnode.state).equals(1)
|
||||
return ""
|
||||
})
|
||||
}
|
||||
var attrs = {}
|
||||
var hooks = [
|
||||
"oninit", "oncreate", "onbeforeupdate",
|
||||
"onupdate", "onbeforeremove", "onremove"
|
||||
]
|
||||
hooks.forEach(function(hook) {
|
||||
// the `attrs` hooks are called before the component ones
|
||||
attrs[hook] = o.spy(function(vnode) {
|
||||
o(vnode.state).equals(1)
|
||||
o(attrs[hook].callCount).equals(methods[hook].callCount + 1)
|
||||
})
|
||||
methods[hook] = o.spy(function(vnode) {
|
||||
o(vnode.state).equals(1)
|
||||
o(attrs[hook].callCount).equals(methods[hook].callCount)
|
||||
})
|
||||
})
|
||||
|
||||
var attrsOninit = attrs.oninit
|
||||
var methodsOninit = methods.oninit
|
||||
attrs.oninit = o.spy(function(vnode){
|
||||
vnode.state = 1
|
||||
return attrsOninit.call(this, vnode)
|
||||
})
|
||||
methods.oninit = o.spy(function(vnode){
|
||||
vnode.state = 1
|
||||
return methodsOninit.call(this, vnode)
|
||||
})
|
||||
|
||||
var component = createComponent(methods)
|
||||
|
||||
o(methods.view.callCount).equals(0)
|
||||
o(methods.oninit.callCount).equals(0)
|
||||
o(methods.oncreate.callCount).equals(0)
|
||||
o(methods.onbeforeupdate.callCount).equals(0)
|
||||
o(methods.onupdate.callCount).equals(0)
|
||||
o(methods.onbeforeremove.callCount).equals(0)
|
||||
o(methods.onremove.callCount).equals(0)
|
||||
|
||||
hooks.forEach(function(hook) {
|
||||
o(attrs[hook].callCount).equals(methods[hook].callCount)(hook)
|
||||
})
|
||||
|
||||
render(root, [{tag: component, attrs: attrs}])
|
||||
|
||||
o(methods.view.callCount).equals(1)
|
||||
o(methods.oninit.callCount).equals(1)
|
||||
o(methods.oncreate.callCount).equals(1)
|
||||
o(methods.onbeforeupdate.callCount).equals(0)
|
||||
o(methods.onupdate.callCount).equals(0)
|
||||
o(methods.onbeforeremove.callCount).equals(0)
|
||||
o(methods.onremove.callCount).equals(0)
|
||||
|
||||
hooks.forEach(function(hook) {
|
||||
o(attrs[hook].callCount).equals(methods[hook].callCount)(hook)
|
||||
})
|
||||
|
||||
render(root, [{tag: component, attrs: attrs}])
|
||||
|
||||
o(methods.view.callCount).equals(2)
|
||||
o(methods.oninit.callCount).equals(1)
|
||||
o(methods.oncreate.callCount).equals(1)
|
||||
o(methods.onbeforeupdate.callCount).equals(1)
|
||||
o(methods.onupdate.callCount).equals(1)
|
||||
o(methods.onbeforeremove.callCount).equals(0)
|
||||
o(methods.onremove.callCount).equals(0)
|
||||
|
||||
hooks.forEach(function(hook) {
|
||||
o(attrs[hook].callCount).equals(methods[hook].callCount)(hook)
|
||||
})
|
||||
|
||||
render(root, [])
|
||||
|
||||
o(methods.view.callCount).equals(2)
|
||||
o(methods.oninit.callCount).equals(1)
|
||||
o(methods.oncreate.callCount).equals(1)
|
||||
o(methods.onbeforeupdate.callCount).equals(1)
|
||||
o(methods.onupdate.callCount).equals(1)
|
||||
o(methods.onbeforeremove.callCount).equals(1)
|
||||
o(methods.onremove.callCount).equals(1)
|
||||
|
||||
hooks.forEach(function(hook) {
|
||||
o(attrs[hook].callCount).equals(methods[hook].callCount)(hook)
|
||||
})
|
||||
})
|
||||
o("hook state and arguments validation", function(){
|
||||
var methods = {
|
||||
view: o.spy(function(vnode) {
|
||||
|
|
@ -899,7 +808,7 @@ o.spec("component", function() {
|
|||
o(methods[hook].args.length).equals(attrs[hook].args.length)(hook)
|
||||
})
|
||||
})
|
||||
o("recycled components get a fresh state", function() {
|
||||
o("no recycling occurs (was: recycled components get a fresh state)", function() {
|
||||
var step = 0
|
||||
var firstState
|
||||
var view = o.spy(function(vnode) {
|
||||
|
|
@ -918,7 +827,7 @@ o.spec("component", function() {
|
|||
step = 1
|
||||
render(root, [{tag: "div", children: [{tag: component, key: 1}]}])
|
||||
|
||||
o(child).equals(root.firstChild.firstChild)
|
||||
o(child).notEquals(root.firstChild.firstChild) // this used to be a recycling pool test
|
||||
o(view.callCount).equals(2)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
/* eslint-disable no-script-url */
|
||||
"use strict"
|
||||
|
||||
var o = require("../../ospec/ospec")
|
||||
|
|
@ -54,6 +53,7 @@ o.spec("createElement", function() {
|
|||
o(vnode.dom.childNodes[0].nodeName).equals("A")
|
||||
o(vnode.dom.childNodes[1].nodeName).equals("B")
|
||||
})
|
||||
/* eslint-disable no-script-url */
|
||||
o("creates svg", function() {
|
||||
var vnode = {tag: "svg", ns: "http://www.w3.org/2000/svg", children: [
|
||||
{tag: "a", ns: "http://www.w3.org/2000/svg", attrs: {"xlink:href": "javascript:;"}},
|
||||
|
|
@ -71,6 +71,7 @@ o.spec("createElement", function() {
|
|||
o(vnode.dom.childNodes[1].firstChild.nodeName).equals("body")
|
||||
o(vnode.dom.childNodes[1].firstChild.namespaceURI).equals("http://www.w3.org/1999/xhtml")
|
||||
})
|
||||
/* eslint-enable no-script-url */
|
||||
o("sets attributes correctly for svg", function() {
|
||||
var vnode = {tag: "svg", ns: "http://www.w3.org/2000/svg", attrs: {viewBox: "0 0 100 100"}}
|
||||
render(root, [vnode])
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ o.spec("createHTML", function() {
|
|||
o(vnode.dom).equals(null)
|
||||
o(vnode.domSize).equals(0)
|
||||
})
|
||||
o("handles multiple children", function() {
|
||||
o("handles multiple children in HTML", function() {
|
||||
var vnode = {tag: "<", children: "<a></a><b></b>"}
|
||||
render(root, [vnode])
|
||||
|
||||
|
|
@ -51,4 +51,34 @@ o.spec("createHTML", function() {
|
|||
o(vnode.dom.nodeName).equals(tag.toUpperCase())
|
||||
})
|
||||
})
|
||||
o("creates SVG", function() {
|
||||
var vnode = {tag: "<", children: "<g></g>"}
|
||||
render(root, [{tag:"svg", children: [vnode]}])
|
||||
|
||||
o(vnode.dom.nodeName).equals("g")
|
||||
o(vnode.dom.namespaceURI).equals("http://www.w3.org/2000/svg")
|
||||
})
|
||||
o("creates text SVG", function() {
|
||||
var vnode = {tag: "<", children: "a"}
|
||||
render(root, [{tag:"svg", children: [vnode]}])
|
||||
|
||||
o(vnode.dom.nodeValue).equals("a")
|
||||
})
|
||||
o("handles empty SVG", function() {
|
||||
var vnode = {tag: "<", children: ""}
|
||||
render(root, [{tag:"svg", children: [vnode]}])
|
||||
|
||||
o(vnode.dom).equals(null)
|
||||
o(vnode.domSize).equals(0)
|
||||
})
|
||||
o("handles multiple children in SVG", function() {
|
||||
var vnode = {tag: "<", children: "<g></g><text></text>"}
|
||||
render(root, [{tag:"svg", children: [vnode]}])
|
||||
|
||||
o(vnode.domSize).equals(2)
|
||||
o(vnode.dom.nodeName).equals("g")
|
||||
o(vnode.dom.namespaceURI).equals("http://www.w3.org/2000/svg")
|
||||
o(vnode.dom.nextSibling.nodeName).equals("text")
|
||||
o(vnode.dom.nextSibling.namespaceURI).equals("http://www.w3.org/2000/svg")
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -32,8 +32,75 @@ o.spec("event", function() {
|
|||
o(onevent.this).equals(div.dom)
|
||||
o(onevent.args[0].type).equals("click")
|
||||
o(onevent.args[0].target).equals(div.dom)
|
||||
o(e.$defaultPrevented).equals(false)
|
||||
o(e.$propagationStopped).equals(false)
|
||||
})
|
||||
|
||||
|
||||
o("handles onclick returning false", function() {
|
||||
var spy = o.spy(function () { return false })
|
||||
var div = {tag: "div", attrs: {onclick: spy}}
|
||||
var e = $window.document.createEvent("MouseEvents")
|
||||
e.initEvent("click", true, true)
|
||||
|
||||
render(root, [div])
|
||||
div.dom.dispatchEvent(e)
|
||||
|
||||
o(spy.callCount).equals(1)
|
||||
o(spy.this).equals(div.dom)
|
||||
o(spy.args[0].type).equals("click")
|
||||
o(spy.args[0].target).equals(div.dom)
|
||||
o(onevent.callCount).equals(1)
|
||||
o(onevent.this).equals(div.dom)
|
||||
o(onevent.args[0].type).equals("click")
|
||||
o(onevent.args[0].target).equals(div.dom)
|
||||
o(e.$defaultPrevented).equals(true)
|
||||
o(e.$propagationStopped).equals(true)
|
||||
})
|
||||
|
||||
o("handles click EventListener object", function() {
|
||||
var spy = o.spy()
|
||||
var listener = {handleEvent: spy}
|
||||
var div = {tag: "div", attrs: {onclick: listener}}
|
||||
var e = $window.document.createEvent("MouseEvents")
|
||||
e.initEvent("click", true, true)
|
||||
|
||||
render(root, [div])
|
||||
div.dom.dispatchEvent(e)
|
||||
|
||||
o(spy.callCount).equals(1)
|
||||
o(spy.this).equals(listener)
|
||||
o(spy.args[0].type).equals("click")
|
||||
o(spy.args[0].target).equals(div.dom)
|
||||
o(onevent.callCount).equals(1)
|
||||
o(onevent.this).equals(div.dom)
|
||||
o(onevent.args[0].type).equals("click")
|
||||
o(onevent.args[0].target).equals(div.dom)
|
||||
o(e.$defaultPrevented).equals(false)
|
||||
o(e.$propagationStopped).equals(false)
|
||||
})
|
||||
|
||||
o("handles click EventListener object returning false", function() {
|
||||
var spy = o.spy(function () { return false })
|
||||
var listener = {handleEvent: spy}
|
||||
var div = {tag: "div", attrs: {onclick: listener}}
|
||||
var e = $window.document.createEvent("MouseEvents")
|
||||
e.initEvent("click", true, true)
|
||||
|
||||
render(root, [div])
|
||||
div.dom.dispatchEvent(e)
|
||||
|
||||
o(spy.callCount).equals(1)
|
||||
o(spy.this).equals(listener)
|
||||
o(spy.args[0].type).equals("click")
|
||||
o(spy.args[0].target).equals(div.dom)
|
||||
o(onevent.callCount).equals(1)
|
||||
o(onevent.this).equals(div.dom)
|
||||
o(onevent.args[0].type).equals("click")
|
||||
o(onevent.args[0].target).equals(div.dom)
|
||||
o(e.$defaultPrevented).equals(false)
|
||||
o(e.$propagationStopped).equals(false)
|
||||
})
|
||||
|
||||
o("removes event", function() {
|
||||
var spy = o.spy()
|
||||
var vnode = {tag: "a", attrs: {onclick: spy}}
|
||||
|
|
@ -45,7 +112,130 @@ o.spec("event", function() {
|
|||
var e = $window.document.createEvent("MouseEvents")
|
||||
e.initEvent("click", true, true)
|
||||
vnode.dom.dispatchEvent(e)
|
||||
|
||||
|
||||
o(spy.callCount).equals(0)
|
||||
})
|
||||
|
||||
o("removes event when null", function() {
|
||||
var spy = o.spy()
|
||||
var vnode = {tag: "a", attrs: {onclick: spy}}
|
||||
var updated = {tag: "a", attrs: {onclick: null}}
|
||||
|
||||
render(root, [vnode])
|
||||
render(root, [updated])
|
||||
|
||||
var e = $window.document.createEvent("MouseEvents")
|
||||
e.initEvent("click", true, true)
|
||||
vnode.dom.dispatchEvent(e)
|
||||
|
||||
o(spy.callCount).equals(0)
|
||||
})
|
||||
|
||||
o("removes event when undefined", function() {
|
||||
var spy = o.spy()
|
||||
var vnode = {tag: "a", attrs: {onclick: spy}}
|
||||
var updated = {tag: "a", attrs: {onclick: undefined}}
|
||||
|
||||
render(root, [vnode])
|
||||
render(root, [updated])
|
||||
|
||||
var e = $window.document.createEvent("MouseEvents")
|
||||
e.initEvent("click", true, true)
|
||||
vnode.dom.dispatchEvent(e)
|
||||
|
||||
o(spy.callCount).equals(0)
|
||||
})
|
||||
|
||||
o("removes event added via addEventListener when null", function() {
|
||||
var spy = o.spy()
|
||||
var vnode = {tag: "a", attrs: {ontouchstart: spy}}
|
||||
var updated = {tag: "a", attrs: {ontouchstart: null}}
|
||||
|
||||
render(root, [vnode])
|
||||
render(root, [updated])
|
||||
|
||||
var e = $window.document.createEvent("TouchEvents")
|
||||
e.initEvent("touchstart", true, true)
|
||||
vnode.dom.dispatchEvent(e)
|
||||
|
||||
o(spy.callCount).equals(0)
|
||||
})
|
||||
|
||||
o("removes event added via addEventListener", function() {
|
||||
var spy = o.spy()
|
||||
var vnode = {tag: "a", attrs: {ontouchstart: spy}}
|
||||
var updated = {tag: "a", attrs: {}}
|
||||
|
||||
render(root, [vnode])
|
||||
render(root, [updated])
|
||||
|
||||
var e = $window.document.createEvent("TouchEvents")
|
||||
e.initEvent("touchstart", true, true)
|
||||
vnode.dom.dispatchEvent(e)
|
||||
|
||||
o(spy.callCount).equals(0)
|
||||
})
|
||||
|
||||
o("removes event added via addEventListener when undefined", function() {
|
||||
var spy = o.spy()
|
||||
var vnode = {tag: "a", attrs: {ontouchstart: spy}}
|
||||
var updated = {tag: "a", attrs: {ontouchstart: undefined}}
|
||||
|
||||
render(root, [vnode])
|
||||
render(root, [updated])
|
||||
|
||||
var e = $window.document.createEvent("TouchEvents")
|
||||
e.initEvent("touchstart", true, true)
|
||||
vnode.dom.dispatchEvent(e)
|
||||
|
||||
o(spy.callCount).equals(0)
|
||||
})
|
||||
|
||||
o("removes EventListener object", function() {
|
||||
var spy = o.spy()
|
||||
var listener = {handleEvent: spy}
|
||||
var vnode = {tag: "a", attrs: {onclick: listener}}
|
||||
var updated = {tag: "a", attrs: {}}
|
||||
|
||||
render(root, [vnode])
|
||||
render(root, [updated])
|
||||
|
||||
var e = $window.document.createEvent("MouseEvents")
|
||||
e.initEvent("click", true, true)
|
||||
vnode.dom.dispatchEvent(e)
|
||||
|
||||
o(spy.callCount).equals(0)
|
||||
})
|
||||
|
||||
o("removes EventListener object when null", function() {
|
||||
var spy = o.spy()
|
||||
var listener = {handleEvent: spy}
|
||||
var vnode = {tag: "a", attrs: {onclick: listener}}
|
||||
var updated = {tag: "a", attrs: {onclick: null}}
|
||||
|
||||
render(root, [vnode])
|
||||
render(root, [updated])
|
||||
|
||||
var e = $window.document.createEvent("MouseEvents")
|
||||
e.initEvent("click", true, true)
|
||||
vnode.dom.dispatchEvent(e)
|
||||
|
||||
o(spy.callCount).equals(0)
|
||||
})
|
||||
|
||||
o("removes EventListener object when undefined", function() {
|
||||
var spy = o.spy()
|
||||
var listener = {handleEvent: spy}
|
||||
var vnode = {tag: "a", attrs: {onclick: listener}}
|
||||
var updated = {tag: "a", attrs: {onclick: undefined}}
|
||||
|
||||
render(root, [vnode])
|
||||
render(root, [updated])
|
||||
|
||||
var e = $window.document.createEvent("MouseEvents")
|
||||
e.initEvent("click", true, true)
|
||||
vnode.dom.dispatchEvent(e)
|
||||
|
||||
o(spy.callCount).equals(0)
|
||||
})
|
||||
|
||||
|
|
@ -72,6 +262,30 @@ o.spec("event", function() {
|
|||
o(div.dom.attributes["id"].value).equals("b")
|
||||
})
|
||||
|
||||
o("fires click EventListener object only once after redraw", function() {
|
||||
var spy = o.spy()
|
||||
var listener = {handleEvent: spy}
|
||||
var div = {tag: "div", attrs: {id: "a", onclick: listener}}
|
||||
var updated = {tag: "div", attrs: {id: "b", onclick: listener}}
|
||||
var e = $window.document.createEvent("MouseEvents")
|
||||
e.initEvent("click", true, true)
|
||||
|
||||
render(root, [div])
|
||||
render(root, [updated])
|
||||
div.dom.dispatchEvent(e)
|
||||
|
||||
o(spy.callCount).equals(1)
|
||||
o(spy.this).equals(listener)
|
||||
o(spy.args[0].type).equals("click")
|
||||
o(spy.args[0].target).equals(div.dom)
|
||||
o(onevent.callCount).equals(1)
|
||||
o(onevent.this).equals(div.dom)
|
||||
o(onevent.args[0].type).equals("click")
|
||||
o(onevent.args[0].target).equals(div.dom)
|
||||
o(div.dom).equals(updated.dom)
|
||||
o(div.dom.attributes["id"].value).equals("b")
|
||||
})
|
||||
|
||||
o("handles ontransitionend", function() {
|
||||
var spy = o.spy()
|
||||
var div = {tag: "div", attrs: {ontransitionend: spy}}
|
||||
|
|
@ -90,4 +304,24 @@ o.spec("event", function() {
|
|||
o(onevent.args[0].type).equals("transitionend")
|
||||
o(onevent.args[0].target).equals(div.dom)
|
||||
})
|
||||
|
||||
o("handles transitionend EventListener object", function() {
|
||||
var spy = o.spy()
|
||||
var listener = {handleEvent: spy}
|
||||
var div = {tag: "div", attrs: {ontransitionend: listener}}
|
||||
var e = $window.document.createEvent("HTMLEvents")
|
||||
e.initEvent("transitionend", true, true)
|
||||
|
||||
render(root, [div])
|
||||
div.dom.dispatchEvent(e)
|
||||
|
||||
o(spy.callCount).equals(1)
|
||||
o(spy.this).equals(listener)
|
||||
o(spy.args[0].type).equals("transitionend")
|
||||
o(spy.args[0].target).equals(div.dom)
|
||||
o(onevent.callCount).equals(1)
|
||||
o(onevent.this).equals(div.dom)
|
||||
o(onevent.args[0].type).equals("transitionend")
|
||||
o(onevent.args[0].target).equals(div.dom)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -16,52 +16,51 @@ o.spec("hyperscript", function() {
|
|||
|
||||
o(vnode.tag).equals("a")
|
||||
})
|
||||
o("v1.0.1 bug-for-bug regression suite", function(){
|
||||
o("class and className normalization", function(){
|
||||
o(m("a", {
|
||||
class: null
|
||||
}).attrs).deepEquals({
|
||||
class: undefined,
|
||||
className: null
|
||||
class: null
|
||||
})
|
||||
o(m("a", {
|
||||
class: undefined
|
||||
}).attrs).deepEquals({
|
||||
class: undefined,
|
||||
class: null
|
||||
})
|
||||
o(m("a", {
|
||||
class: false
|
||||
}).attrs).deepEquals({
|
||||
class: undefined,
|
||||
class: null,
|
||||
className: false
|
||||
})
|
||||
o(m("a", {
|
||||
class: true
|
||||
}).attrs).deepEquals({
|
||||
class: undefined,
|
||||
class: null,
|
||||
className: true
|
||||
})
|
||||
o(m("a.x", {
|
||||
class: null
|
||||
}).attrs).deepEquals({
|
||||
class: undefined,
|
||||
className: "x null"
|
||||
class: null,
|
||||
className: "x"
|
||||
})
|
||||
o(m("a.x", {
|
||||
class: undefined
|
||||
}).attrs).deepEquals({
|
||||
class: undefined,
|
||||
class: null,
|
||||
className: "x"
|
||||
})
|
||||
o(m("a.x", {
|
||||
class: false
|
||||
}).attrs).deepEquals({
|
||||
class: undefined,
|
||||
class: null,
|
||||
className: "x false"
|
||||
})
|
||||
o(m("a.x", {
|
||||
class: true
|
||||
}).attrs).deepEquals({
|
||||
class: undefined,
|
||||
class: null,
|
||||
className: "x true"
|
||||
})
|
||||
o(m("a", {
|
||||
|
|
@ -97,7 +96,7 @@ o.spec("hyperscript", function() {
|
|||
o(m("a.x", {
|
||||
className: false
|
||||
}).attrs).deepEquals({
|
||||
className: "x"
|
||||
className: "x false"
|
||||
})
|
||||
o(m("a.x", {
|
||||
className: true
|
||||
|
|
@ -272,7 +271,7 @@ o.spec("hyperscript", function() {
|
|||
var vnode = m("div", {key:"a"})
|
||||
|
||||
o(vnode.tag).equals("div")
|
||||
o(vnode.attrs).equals(undefined)
|
||||
o(vnode.attrs).equals(null)
|
||||
o(vnode.key).equals("a")
|
||||
})
|
||||
o("handles many attrs", function() {
|
||||
|
|
@ -303,6 +302,63 @@ o.spec("hyperscript", function() {
|
|||
o(vnode.attrs.className).equals("a b")
|
||||
})
|
||||
})
|
||||
o.spec("custom element attrs", function() {
|
||||
o("handles string attr", function() {
|
||||
var vnode = m("custom-element", {a: "b"})
|
||||
|
||||
o(vnode.tag).equals("custom-element")
|
||||
o(vnode.attrs.a).equals("b")
|
||||
})
|
||||
o("handles falsy string attr", function() {
|
||||
var vnode = m("custom-element", {a: ""})
|
||||
|
||||
o(vnode.tag).equals("custom-element")
|
||||
o(vnode.attrs.a).equals("")
|
||||
})
|
||||
o("handles number attr", function() {
|
||||
var vnode = m("custom-element", {a: 1})
|
||||
|
||||
o(vnode.tag).equals("custom-element")
|
||||
o(vnode.attrs.a).equals(1)
|
||||
})
|
||||
o("handles falsy number attr", function() {
|
||||
var vnode = m("custom-element", {a: 0})
|
||||
|
||||
o(vnode.tag).equals("custom-element")
|
||||
o(vnode.attrs.a).equals(0)
|
||||
})
|
||||
o("handles boolean attr", function() {
|
||||
var vnode = m("custom-element", {a: true})
|
||||
|
||||
o(vnode.tag).equals("custom-element")
|
||||
o(vnode.attrs.a).equals(true)
|
||||
})
|
||||
o("handles falsy boolean attr", function() {
|
||||
var vnode = m("custom-element", {a: false})
|
||||
|
||||
o(vnode.tag).equals("custom-element")
|
||||
o(vnode.attrs.a).equals(false)
|
||||
})
|
||||
o("handles only key in attrs", function() {
|
||||
var vnode = m("custom-element", {key:"a"})
|
||||
|
||||
o(vnode.tag).equals("custom-element")
|
||||
o(vnode.attrs).equals(null)
|
||||
o(vnode.key).equals("a")
|
||||
})
|
||||
o("handles many attrs", function() {
|
||||
var vnode = m("custom-element", {a: "b", c: "d"})
|
||||
|
||||
o(vnode.tag).equals("custom-element")
|
||||
o(vnode.attrs.a).equals("b")
|
||||
o(vnode.attrs.c).equals("d")
|
||||
})
|
||||
o("handles className attrs property", function() {
|
||||
var vnode = m("custom-element", {className: "a"})
|
||||
|
||||
o(vnode.attrs.className).equals("a")
|
||||
})
|
||||
})
|
||||
o.spec("children", function() {
|
||||
o("handles string single child", function() {
|
||||
var vnode = m("div", {}, ["a"])
|
||||
|
|
@ -490,20 +546,20 @@ o.spec("hyperscript", function() {
|
|||
o("handles children without attr", function() {
|
||||
var vnode = m("div", [m("i"), m("s")])
|
||||
|
||||
o(vnode.attrs).equals(undefined)
|
||||
o(vnode.attrs).equals(null)
|
||||
o(vnode.children[0].tag).equals("i")
|
||||
o(vnode.children[1].tag).equals("s")
|
||||
})
|
||||
o("handles child without attr unwrapped", function() {
|
||||
var vnode = m("div", m("i"))
|
||||
|
||||
o(vnode.attrs).equals(undefined)
|
||||
o(vnode.attrs).equals(null)
|
||||
o(vnode.children[0].tag).equals("i")
|
||||
})
|
||||
o("handles children without attr unwrapped", function() {
|
||||
var vnode = m("div", m("i"), m("s"))
|
||||
|
||||
o(vnode.attrs).equals(undefined)
|
||||
o(vnode.attrs).equals(null)
|
||||
o(vnode.children[0].tag).equals("i")
|
||||
o(vnode.children[1].tag).equals("s")
|
||||
})
|
||||
|
|
@ -524,6 +580,15 @@ o.spec("hyperscript", function() {
|
|||
m(".a", attrs)
|
||||
o(attrs).deepEquals({a: "b"})
|
||||
})
|
||||
o("non-nullish attr takes precedence over selector", function() {
|
||||
o(m("[a=b]", {a: "c"}).attrs).deepEquals({a: "c"})
|
||||
})
|
||||
o("null attr takes precedence over selector", function() {
|
||||
o(m("[a=b]", {a: null}).attrs).deepEquals({a: null})
|
||||
})
|
||||
o("undefined attr takes precedence over selector", function() {
|
||||
o(m("[a=b]", {a: undefined}).attrs).deepEquals({a: undefined})
|
||||
})
|
||||
o("handles fragment children without attr unwrapped", function() {
|
||||
var vnode = m("div", [m("i")], [m("s")])
|
||||
|
||||
|
|
@ -551,19 +616,29 @@ o.spec("hyperscript", function() {
|
|||
o.spec("components", function() {
|
||||
o("works with POJOs", function() {
|
||||
var component = {
|
||||
view: function() {
|
||||
return m("div")
|
||||
}
|
||||
view: function() {}
|
||||
}
|
||||
var vnode = m(component, {id: "a"}, "b")
|
||||
|
||||
o(vnode.tag).equals(component)
|
||||
o(vnode.attrs.id).equals("a")
|
||||
o(vnode.children.length).equals(1)
|
||||
o(vnode.children[0].tag).equals("#")
|
||||
o(vnode.children[0].children).equals("b")
|
||||
o(vnode.children[0]).equals("b")
|
||||
})
|
||||
o("works with functions", function() {
|
||||
o("works with constructibles", function() {
|
||||
var component = o.spy()
|
||||
component.prototype.view = function() {}
|
||||
|
||||
var vnode = m(component, {id: "a"}, "b")
|
||||
|
||||
o(component.callCount).equals(0)
|
||||
|
||||
o(vnode.tag).equals(component)
|
||||
o(vnode.attrs.id).equals("a")
|
||||
o(vnode.children.length).equals(1)
|
||||
o(vnode.children[0]).equals("b")
|
||||
})
|
||||
o("works with closures", function () {
|
||||
var component = o.spy()
|
||||
|
||||
var vnode = m(component, {id: "a"}, "b")
|
||||
|
|
@ -573,8 +648,7 @@ o.spec("hyperscript", function() {
|
|||
o(vnode.tag).equals(component)
|
||||
o(vnode.attrs.id).equals("a")
|
||||
o(vnode.children.length).equals(1)
|
||||
o(vnode.children[0].tag).equals("#")
|
||||
o(vnode.children[0].children).equals("b")
|
||||
o(vnode.children[0]).equals("b")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -59,6 +59,16 @@ o.spec("form inputs", function() {
|
|||
o(updated.dom.value).equals("aaa")
|
||||
})
|
||||
|
||||
o("clear element value if vdom value is set to undefined (aka removed)", function() {
|
||||
var input = {tag: "input", attrs: {value: "aaa", oninput: function() {}}}
|
||||
var updated = {tag: "input", attrs: {value: undefined, oninput: function() {}}}
|
||||
|
||||
render(root, [input])
|
||||
render(root, [updated])
|
||||
|
||||
o(updated.dom.value).equals("")
|
||||
})
|
||||
|
||||
o("syncs input checked attribute if DOM value differs from vdom value", function() {
|
||||
var input = {tag: "input", attrs: {type: "checkbox", checked: true, onclick: function() {}}}
|
||||
var updated = {tag: "input", attrs: {type: "checkbox", checked: true, onclick: function() {}}}
|
||||
|
|
|
|||
34
render/tests/test-normalizeComponentChildren.js
Normal file
34
render/tests/test-normalizeComponentChildren.js
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
"use strict"
|
||||
|
||||
var o = require("../../ospec/ospec")
|
||||
var m = require("../../render/hyperscript")
|
||||
var domMock = require("../../test-utils/domMock")
|
||||
var vdom = require("../../render/render")
|
||||
|
||||
o.spec("component children", function () {
|
||||
var $window = domMock()
|
||||
var root = $window.document.createElement("div")
|
||||
var render = vdom($window).render
|
||||
|
||||
o.spec("component children", function () {
|
||||
var component = {
|
||||
view: function (vnode) {
|
||||
return vnode.children
|
||||
}
|
||||
}
|
||||
|
||||
var vnode = m(component, "a")
|
||||
|
||||
render(root, vnode)
|
||||
|
||||
o("are not normalized on ingestion", function () {
|
||||
o(vnode.children[0]).equals("a")
|
||||
})
|
||||
|
||||
o("are normalized upon view interpolation", function () {
|
||||
o(vnode.instance.children.length).equals(1)
|
||||
o(vnode.instance.children[0].tag).equals("#")
|
||||
o(vnode.instance.children[0].children).equals("a")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -36,10 +36,7 @@ o.spec("onbeforeremove", function() {
|
|||
o(update.callCount).equals(0)
|
||||
})
|
||||
o("calls onbeforeremove when removing element", function(done) {
|
||||
var vnode = {tag: "div", attrs: {
|
||||
oninit: function() {vnode.state = {}},
|
||||
onbeforeremove: remove
|
||||
}}
|
||||
var vnode = {tag: "div", attrs: {onbeforeremove: remove}}
|
||||
|
||||
render(root, [vnode])
|
||||
render(root, [])
|
||||
|
|
@ -47,6 +44,7 @@ o.spec("onbeforeremove", function() {
|
|||
function remove(node) {
|
||||
o(node).equals(vnode)
|
||||
o(this).equals(vnode.state)
|
||||
o(this != null && typeof this === "object").equals(true)
|
||||
o(root.childNodes.length).equals(1)
|
||||
o(root.firstChild).equals(vnode.dom)
|
||||
|
||||
|
|
|
|||
|
|
@ -129,7 +129,7 @@ o.spec("onbeforeupdate", function() {
|
|||
render(root, temp)
|
||||
render(root, updated)
|
||||
|
||||
o(vnodes[0].dom).equals(updated[0].dom)
|
||||
o(vnodes[0].dom).notEquals(updated[0].dom) // this used to be a recycling pool test
|
||||
o(updated[0].dom.nodeName).equals("DIV")
|
||||
o(onbeforeupdate.callCount).equals(0)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -199,4 +199,27 @@ o.spec("oninit", function() {
|
|||
o(vnode.dom.oninit).equals(undefined)
|
||||
o(vnode.dom.attributes["oninit"]).equals(undefined)
|
||||
})
|
||||
|
||||
o("No spurious oninit calls in mapped keyed diff when the pool is involved (#1992)", function () {
|
||||
var oninit1 = o.spy()
|
||||
var oninit2 = o.spy()
|
||||
var oninit3 = o.spy()
|
||||
|
||||
render(root, [
|
||||
{tag: "p", key: 1, attrs: {oninit: oninit1}},
|
||||
{tag: "p", key: 2, attrs: {oninit: oninit2}},
|
||||
{tag: "p", key: 3, attrs: {oninit: oninit3}},
|
||||
])
|
||||
render(root, [
|
||||
{tag: "p", key: 1, attrs: {oninit: oninit1}},
|
||||
{tag: "p", key: 3, attrs: {oninit: oninit3}},
|
||||
])
|
||||
render(root, [
|
||||
{tag: "p", key: 3, attrs: {oninit: oninit3}},
|
||||
])
|
||||
|
||||
o(oninit1.callCount).equals(1)
|
||||
o(oninit2.callCount).equals(1)
|
||||
o(oninit3.callCount).equals(1)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ o.spec("onremove", function() {
|
|||
o(vnode.dom.attributes["onremove"]).equals(undefined)
|
||||
o(vnode.events).equals(undefined)
|
||||
})
|
||||
o("calls onremove on recycle", function() {
|
||||
o("calls onremove on keyed nodes", function() {
|
||||
var remove = o.spy()
|
||||
var vnodes = [{tag: "div", key: 1}]
|
||||
var temp = [{tag: "div", key: 2, attrs: {onremove: remove}}]
|
||||
|
|
@ -101,6 +101,7 @@ o.spec("onremove", function() {
|
|||
render(root, temp)
|
||||
render(root, updated)
|
||||
|
||||
o(vnodes[0].dom).notEquals(updated[0].dom) // this used to be a recycling pool test
|
||||
o(remove.callCount).equals(1)
|
||||
})
|
||||
o("does not recycle when there's an onremove", function() {
|
||||
|
|
@ -132,7 +133,7 @@ o.spec("onremove", function() {
|
|||
})
|
||||
render(root, {tag: comp})
|
||||
render(root, null)
|
||||
|
||||
|
||||
o(spy.callCount).equals(1)
|
||||
})
|
||||
o("calls onremove on nested component child", function() {
|
||||
|
|
@ -148,7 +149,7 @@ o.spec("onremove", function() {
|
|||
})
|
||||
render(root, {tag: comp})
|
||||
render(root, null)
|
||||
|
||||
|
||||
o(spy.callCount).equals(1)
|
||||
})
|
||||
o("doesn't call onremove on children when the corresponding view returns null (after removing the parent)", function() {
|
||||
|
|
@ -191,6 +192,28 @@ o.spec("onremove", function() {
|
|||
o(spy.callCount).equals(0)
|
||||
o(threw).equals(false)
|
||||
})
|
||||
o("onremove doesn't fire on nodes that go from pool to pool (#1990)", function() {
|
||||
var onremove = o.spy();
|
||||
|
||||
render(root, [m("div", m("div")), m("div", m("div", {onremove: onremove}))]);
|
||||
render(root, [m("div", m("div"))]);
|
||||
render(root, []);
|
||||
|
||||
o(onremove.callCount).equals(1)
|
||||
})
|
||||
o("doesn't fire when removing the children of a node that's brought back from the pool (#1991 part 2)", function() {
|
||||
var onremove = o.spy()
|
||||
var vnode = {tag: "div", key: 1, children: [{tag: "div", attrs: {onremove: onremove}}]}
|
||||
var temp = {tag: "div", key: 2}
|
||||
var updated = {tag: "div", key: 1, children: [{tag: "p"}]}
|
||||
|
||||
render(root, [vnode])
|
||||
render(root, [temp])
|
||||
render(root, [updated])
|
||||
|
||||
o(vnode.dom).notEquals(updated.dom) // this used to be a recycling pool test
|
||||
o(onremove.callCount).equals(1)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
614
render/tests/test-render-hyperscript-integration.js
Normal file
614
render/tests/test-render-hyperscript-integration.js
Normal file
|
|
@ -0,0 +1,614 @@
|
|||
"use strict"
|
||||
|
||||
var o = require("../../ospec/ospec")
|
||||
var m = require("../../render/hyperscript")
|
||||
var domMock = require("../../test-utils/domMock")
|
||||
var vdom = require("../../render/render")
|
||||
|
||||
o.spec("render/hyperscript integration", function() {
|
||||
var $window, root, render
|
||||
o.beforeEach(function() {
|
||||
$window = domMock()
|
||||
root = $window.document.createElement("div")
|
||||
render = vdom($window).render
|
||||
})
|
||||
o.spec("setting class", function() {
|
||||
o("selector only", function() {
|
||||
render(root, m(".foo"))
|
||||
|
||||
o(root.firstChild.className).equals("foo")
|
||||
})
|
||||
o("class only", function() {
|
||||
render(root, m("div", {class: "foo"}))
|
||||
|
||||
o(root.firstChild.className).equals("foo")
|
||||
})
|
||||
o("className only", function() {
|
||||
render(root, m("div", {className: "foo"}))
|
||||
|
||||
o(root.firstChild.className).equals("foo")
|
||||
})
|
||||
o("selector and class", function() {
|
||||
render(root, m(".bar", {class: "foo"}))
|
||||
|
||||
o(root.firstChild.className.split(" ").sort()).deepEquals(["bar", "foo"])
|
||||
})
|
||||
o("selector and className", function() {
|
||||
render(root, m(".bar", {className: "foo"}))
|
||||
|
||||
o(root.firstChild.className.split(" ").sort()).deepEquals(["bar", "foo"])
|
||||
})
|
||||
o("selector and a null class", function() {
|
||||
render(root, m(".foo", {class: null}))
|
||||
|
||||
o(root.firstChild.className).equals("foo")
|
||||
})
|
||||
o("selector and a null className", function() {
|
||||
render(root, m(".foo", {className: null}))
|
||||
|
||||
o(root.firstChild.className).equals("foo")
|
||||
})
|
||||
o("selector and an undefined class", function() {
|
||||
render(root, m(".foo", {class: undefined}))
|
||||
|
||||
o(root.firstChild.className).equals("foo")
|
||||
})
|
||||
o("selector and an undefined className", function() {
|
||||
render(root, m(".foo", {className: undefined}))
|
||||
|
||||
o(root.firstChild.className).equals("foo")
|
||||
})
|
||||
})
|
||||
o.spec("updating class", function() {
|
||||
o.spec("from selector only", function() {
|
||||
o("to selector only", function() {
|
||||
render(root, m(".foo1"))
|
||||
render(root, m(".foo2"))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to class only", function() {
|
||||
render(root, m(".foo1"))
|
||||
render(root, m("div", {class: "foo2"}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to className only", function() {
|
||||
render(root, m(".foo1"))
|
||||
render(root, m("div", {className: "foo2"}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and class", function() {
|
||||
render(root, m(".foo1"))
|
||||
render(root, m(".bar2", {class: "foo2"}))
|
||||
|
||||
o(root.firstChild.className.split(" ").sort()).deepEquals(["bar2", "foo2"])
|
||||
})
|
||||
o("to selector and className", function() {
|
||||
render(root, m(".foo1"))
|
||||
render(root, m(".bar2", {className: "foo2"}))
|
||||
|
||||
o(root.firstChild.className.split(" ").sort()).deepEquals(["bar2", "foo2"])
|
||||
})
|
||||
o("to selector and a null class", function() {
|
||||
render(root, m(".foo1"))
|
||||
render(root, m(".foo2", {class: null}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and a null className", function() {
|
||||
render(root, m(".foo1"))
|
||||
render(root, m(".foo2", {className: null}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and an undefined class", function() {
|
||||
render(root, m(".foo1"))
|
||||
render(root, m(".foo2", {class: undefined}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and an undefined className", function() {
|
||||
render(root, m(".foo1"))
|
||||
render(root, m(".foo2", {className: undefined}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
})
|
||||
o.spec("from class only", function() {
|
||||
o("to selector only", function() {
|
||||
render(root, m("div", {class: "foo2"}))
|
||||
render(root, m(".foo2"))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to class only", function() {
|
||||
render(root, m("div", {class: "foo2"}))
|
||||
render(root, m("div", {class: "foo2"}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to className only", function() {
|
||||
render(root, m("div", {class: "foo2"}))
|
||||
render(root, m("div", {className: "foo2"}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and class", function() {
|
||||
render(root, m("div", {class: "foo2"}))
|
||||
render(root, m(".bar2", {class: "foo2"}))
|
||||
|
||||
o(root.firstChild.className.split(" ").sort()).deepEquals(["bar2", "foo2"])
|
||||
})
|
||||
o("to selector and className", function() {
|
||||
render(root, m(".bar2", {className: "foo2"}))
|
||||
|
||||
o(root.firstChild.className.split(" ").sort()).deepEquals(["bar2", "foo2"])
|
||||
})
|
||||
o("to selector and a null class", function() {
|
||||
render(root, m("div", {class: "foo2"}))
|
||||
render(root, m(".foo2", {class: null}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and a null className", function() {
|
||||
render(root, m("div", {class: "foo2"}))
|
||||
render(root, m(".foo2", {className: null}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and an undefined class", function() {
|
||||
render(root, m("div", {class: "foo2"}))
|
||||
render(root, m(".foo2", {class: undefined}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and an undefined className", function() {
|
||||
render(root, m("div", {class: "foo2"}))
|
||||
render(root, m(".foo2", {className: undefined}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
})
|
||||
o.spec("from ", function() {
|
||||
o("to selector only", function() {
|
||||
render(root, m(".foo2"))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to class only", function() {
|
||||
render(root, m("div", {class: "foo2"}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to className only", function() {
|
||||
render(root, m("div", {className: "foo2"}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and class", function() {
|
||||
render(root, m(".bar2", {class: "foo2"}))
|
||||
|
||||
o(root.firstChild.className.split(" ").sort()).deepEquals(["bar2", "foo2"])
|
||||
})
|
||||
o("to selector and className", function() {
|
||||
render(root, m(".bar2", {className: "foo2"}))
|
||||
|
||||
o(root.firstChild.className.split(" ").sort()).deepEquals(["bar2", "foo2"])
|
||||
})
|
||||
o("to selector and a null class", function() {
|
||||
render(root, m(".foo2", {class: null}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and a null className", function() {
|
||||
render(root, m(".foo2", {className: null}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and an undefined class", function() {
|
||||
render(root, m(".foo2", {class: undefined}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and an undefined className", function() {
|
||||
render(root, m(".foo2", {className: undefined}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
})
|
||||
o.spec("from className only", function() {
|
||||
o("to selector only", function() {
|
||||
render(root, m("div", {className: "foo1"}))
|
||||
render(root, m(".foo2"))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to class only", function() {
|
||||
render(root, m("div", {className: "foo1"}))
|
||||
render(root, m("div", {class: "foo2"}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to className only", function() {
|
||||
render(root, m("div", {className: "foo1"}))
|
||||
render(root, m("div", {className: "foo2"}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and class", function() {
|
||||
render(root, m("div", {className: "foo1"}))
|
||||
render(root, m(".bar2", {class: "foo2"}))
|
||||
|
||||
o(root.firstChild.className.split(" ").sort()).deepEquals(["bar2", "foo2"])
|
||||
})
|
||||
o("to selector and className", function() {
|
||||
render(root, m("div", {className: "foo1"}))
|
||||
render(root, m(".bar2", {className: "foo2"}))
|
||||
|
||||
o(root.firstChild.className.split(" ").sort()).deepEquals(["bar2", "foo2"])
|
||||
})
|
||||
o("to selector and a null class", function() {
|
||||
render(root, m("div", {className: "foo1"}))
|
||||
render(root, m(".foo2", {class: null}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and a null className", function() {
|
||||
render(root, m("div", {className: "foo1"}))
|
||||
render(root, m(".foo2", {className: null}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and an undefined class", function() {
|
||||
render(root, m("div", {className: "foo1"}))
|
||||
render(root, m(".foo2", {class: undefined}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and an undefined className", function() {
|
||||
render(root, m("div", {className: "foo1"}))
|
||||
render(root, m(".foo2", {className: undefined}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
})
|
||||
o.spec("from selector and class", function() {
|
||||
o("to selector only", function() {
|
||||
render(root, m(".bar1", {class: "foo1"}))
|
||||
render(root, m(".foo2"))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to class only", function() {
|
||||
render(root, m(".bar1", {class: "foo1"}))
|
||||
render(root, m("div", {class: "foo2"}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to className only", function() {
|
||||
render(root, m(".bar1", {class: "foo1"}))
|
||||
render(root, m("div", {className: "foo2"}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and class", function() {
|
||||
render(root, m(".bar1", {class: "foo1"}))
|
||||
render(root, m(".bar2", {class: "foo2"}))
|
||||
|
||||
o(root.firstChild.className.split(" ").sort()).deepEquals(["bar2", "foo2"])
|
||||
})
|
||||
o("to selector and className", function() {
|
||||
render(root, m(".bar1", {class: "foo1"}))
|
||||
render(root, m(".bar2", {className: "foo2"}))
|
||||
|
||||
o(root.firstChild.className.split(" ").sort()).deepEquals(["bar2", "foo2"])
|
||||
})
|
||||
o("to selector and a null class", function() {
|
||||
render(root, m(".bar1", {class: "foo1"}))
|
||||
render(root, m(".foo2", {class: null}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and a null className", function() {
|
||||
render(root, m(".bar1", {class: "foo1"}))
|
||||
render(root, m(".foo2", {className: null}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and an undefined class", function() {
|
||||
render(root, m(".bar1", {class: "foo1"}))
|
||||
render(root, m(".foo2", {class: undefined}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and an undefined className", function() {
|
||||
render(root, m(".bar1", {class: "foo1"}))
|
||||
render(root, m(".foo2", {className: undefined}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
})
|
||||
o.spec("from selector and className", function() {
|
||||
o("to selector only", function() {
|
||||
render(root, m(".bar1", {className: "foo1"}))
|
||||
render(root, m(".foo2"))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to class only", function() {
|
||||
render(root, m(".bar1", {className: "foo1"}))
|
||||
render(root, m("div", {class: "foo2"}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to className only", function() {
|
||||
render(root, m(".bar1", {className: "foo1"}))
|
||||
render(root, m("div", {className: "foo2"}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and class", function() {
|
||||
render(root, m(".bar1", {className: "foo1"}))
|
||||
render(root, m(".bar2", {class: "foo2"}))
|
||||
|
||||
o(root.firstChild.className.split(" ").sort()).deepEquals(["bar2", "foo2"])
|
||||
})
|
||||
o("to selector and className", function() {
|
||||
render(root, m(".bar1", {className: "foo1"}))
|
||||
render(root, m(".bar2", {className: "foo2"}))
|
||||
|
||||
o(root.firstChild.className.split(" ").sort()).deepEquals(["bar2", "foo2"])
|
||||
})
|
||||
o("to selector and a null class", function() {
|
||||
render(root, m(".bar1", {className: "foo1"}))
|
||||
render(root, m(".foo2", {class: null}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and a null className", function() {
|
||||
render(root, m(".bar1", {className: "foo1"}))
|
||||
render(root, m(".foo2", {className: null}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and an undefined class", function() {
|
||||
render(root, m(".bar1", {className: "foo1"}))
|
||||
render(root, m(".foo2", {class: undefined}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and an undefined className", function() {
|
||||
render(root, m(".bar1", {className: "foo1"}))
|
||||
render(root, m(".foo2", {className: undefined}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
})
|
||||
o.spec("from and a null class", function() {
|
||||
o("to selector only", function() {
|
||||
render(root, m(".foo1", {class: null}))
|
||||
render(root, m(".foo2"))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to class only", function() {
|
||||
render(root, m(".foo1", {class: null}))
|
||||
render(root, m("div", {class: "foo2"}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to className only", function() {
|
||||
render(root, m(".foo1", {class: null}))
|
||||
render(root, m("div", {className: "foo2"}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and class", function() {
|
||||
render(root, m(".foo1", {class: null}))
|
||||
render(root, m(".bar2", {class: "foo2"}))
|
||||
|
||||
o(root.firstChild.className.split(" ").sort()).deepEquals(["bar2", "foo2"])
|
||||
})
|
||||
o("to selector and className", function() {
|
||||
render(root, m(".foo1", {class: null}))
|
||||
render(root, m(".bar2", {className: "foo2"}))
|
||||
|
||||
o(root.firstChild.className.split(" ").sort()).deepEquals(["bar2", "foo2"])
|
||||
})
|
||||
o("to selector and a null class", function() {
|
||||
render(root, m(".foo1", {class: null}))
|
||||
render(root, m(".foo2", {class: null}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and a null className", function() {
|
||||
render(root, m(".foo1", {class: null}))
|
||||
render(root, m(".foo2", {className: null}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and an undefined class", function() {
|
||||
render(root, m(".foo1", {class: null}))
|
||||
render(root, m(".foo2", {class: undefined}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and an undefined className", function() {
|
||||
render(root, m(".foo1", {class: null}))
|
||||
render(root, m(".foo2", {className: undefined}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
})
|
||||
o.spec("from selector and a null className", function() {
|
||||
o("to selector only", function() {
|
||||
render(root, m(".foo1", {className: null}))
|
||||
render(root, m(".foo2"))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to class only", function() {
|
||||
render(root, m(".foo1", {className: null}))
|
||||
render(root, m("div", {class: "foo2"}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to className only", function() {
|
||||
render(root, m(".foo1", {className: null}))
|
||||
render(root, m("div", {className: "foo2"}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and class", function() {
|
||||
render(root, m(".foo1", {className: null}))
|
||||
render(root, m(".bar2", {class: "foo2"}))
|
||||
|
||||
o(root.firstChild.className.split(" ").sort()).deepEquals(["bar2", "foo2"])
|
||||
})
|
||||
o("to selector and className", function() {
|
||||
render(root, m(".foo1", {className: null}))
|
||||
render(root, m(".bar2", {className: "foo2"}))
|
||||
|
||||
o(root.firstChild.className.split(" ").sort()).deepEquals(["bar2", "foo2"])
|
||||
})
|
||||
o("to selector and a null class", function() {
|
||||
render(root, m(".foo1", {className: null}))
|
||||
render(root, m(".foo2", {class: null}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and a null className", function() {
|
||||
render(root, m(".foo1", {className: null}))
|
||||
render(root, m(".foo2", {className: null}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and an undefined class", function() {
|
||||
render(root, m(".foo1", {className: null}))
|
||||
render(root, m(".foo2", {class: undefined}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and an undefined className", function() {
|
||||
render(root, m(".foo1", {className: null}))
|
||||
render(root, m(".foo2", {className: undefined}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
})
|
||||
o.spec("from selector and an undefined class", function() {
|
||||
o("to selector only", function() {
|
||||
render(root, m(".foo1", {class: undefined}))
|
||||
render(root, m(".foo2"))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to class only", function() {
|
||||
render(root, m(".foo1", {class: undefined}))
|
||||
render(root, m("div", {class: "foo2"}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to className only", function() {
|
||||
render(root, m(".foo1", {class: undefined}))
|
||||
render(root, m("div", {className: "foo2"}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and class", function() {
|
||||
render(root, m(".foo1", {class: undefined}))
|
||||
render(root, m(".bar2", {class: "foo2"}))
|
||||
|
||||
o(root.firstChild.className.split(" ").sort()).deepEquals(["bar2", "foo2"])
|
||||
})
|
||||
o("to selector and className", function() {
|
||||
render(root, m(".foo1", {class: undefined}))
|
||||
render(root, m(".bar2", {className: "foo2"}))
|
||||
|
||||
o(root.firstChild.className.split(" ").sort()).deepEquals(["bar2", "foo2"])
|
||||
})
|
||||
o("to selector and a null class", function() {
|
||||
render(root, m(".foo1", {class: undefined}))
|
||||
render(root, m(".foo2", {class: null}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and a null className", function() {
|
||||
render(root, m(".foo1", {class: undefined}))
|
||||
render(root, m(".foo2", {className: null}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and an undefined class", function() {
|
||||
render(root, m(".foo1", {class: undefined}))
|
||||
render(root, m(".foo2", {class: undefined}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and an undefined className", function() {
|
||||
render(root, m(".foo1", {class: undefined}))
|
||||
render(root, m(".foo2", {className: undefined}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
})
|
||||
o.spec("from selector and an undefined className", function() {
|
||||
o("to selector only", function() {
|
||||
render(root, m(".foo1", {className: undefined}))
|
||||
render(root, m(".foo2"))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to class only", function() {
|
||||
render(root, m(".foo1", {className: undefined}))
|
||||
render(root, m("div", {class: "foo2"}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to className only", function() {
|
||||
render(root, m(".foo1", {className: undefined}))
|
||||
render(root, m("div", {className: "foo2"}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and class", function() {
|
||||
render(root, m(".foo1", {className: undefined}))
|
||||
render(root, m(".bar2", {class: "foo2"}))
|
||||
|
||||
o(root.firstChild.className.split(" ").sort()).deepEquals(["bar2", "foo2"])
|
||||
})
|
||||
o("to selector and className", function() {
|
||||
render(root, m(".foo1", {className: undefined}))
|
||||
render(root, m(".bar2", {className: "foo2"}))
|
||||
|
||||
o(root.firstChild.className.split(" ").sort()).deepEquals(["bar2", "foo2"])
|
||||
})
|
||||
o("to selector and a null class", function() {
|
||||
render(root, m(".foo1", {className: undefined}))
|
||||
render(root, m(".foo2", {class: null}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and a null className", function() {
|
||||
render(root, m(".foo1", {className: undefined}))
|
||||
render(root, m(".foo2", {className: null}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and an undefined class", function() {
|
||||
render(root, m(".foo1", {className: undefined}))
|
||||
render(root, m(".foo2", {class: undefined}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
o("to selector and an undefined className", function() {
|
||||
render(root, m(".foo1", {className: undefined}))
|
||||
render(root, m(".foo2", {className: undefined}))
|
||||
|
||||
o(root.firstChild.className).equals("foo2")
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -192,6 +192,85 @@ o.spec("updateElement", function() {
|
|||
o(updated.dom.style.backgroundColor).equals("")
|
||||
o(updated.dom.style.color).equals("gold")
|
||||
})
|
||||
o("does not re-render element styles for equivalent style objects", function() {
|
||||
var style = {color: "gold"}
|
||||
var vnode = {tag: "a", attrs: {style: style}}
|
||||
|
||||
render(root, [vnode])
|
||||
|
||||
root.firstChild.style.color = "red"
|
||||
style = {color: "gold"}
|
||||
var updated = {tag: "a", attrs: {style: style}}
|
||||
render(root, [updated])
|
||||
|
||||
o(updated.dom.style.color).equals("red")
|
||||
})
|
||||
o("setting style to `null` removes all styles", function() {
|
||||
var vnode = {"tag": "p", attrs: {style: "background-color: red"}}
|
||||
var updated = {"tag": "p", attrs: {style: null}}
|
||||
|
||||
render(root, [vnode])
|
||||
|
||||
o("style" in vnode.dom.attributes).equals(true)
|
||||
o(vnode.dom.attributes.style.value).equals("background-color: red;")
|
||||
|
||||
render(root, [updated])
|
||||
|
||||
//browsers disagree here
|
||||
try {
|
||||
|
||||
o(updated.dom.attributes.style.value).equals("")
|
||||
|
||||
} catch (e) {
|
||||
|
||||
o("style" in updated.dom.attributes).equals(false)
|
||||
|
||||
}
|
||||
})
|
||||
o("setting style to `undefined` removes all styles", function() {
|
||||
var vnode = {"tag": "p", attrs: {style: "background-color: red"}}
|
||||
var updated = {"tag": "p", attrs: {style: undefined}}
|
||||
|
||||
render(root, [vnode])
|
||||
|
||||
o("style" in vnode.dom.attributes).equals(true)
|
||||
o(vnode.dom.attributes.style.value).equals("background-color: red;")
|
||||
|
||||
render(root, [updated])
|
||||
|
||||
//browsers disagree here
|
||||
try {
|
||||
|
||||
o(updated.dom.attributes.style.value).equals("")
|
||||
|
||||
} catch (e) {
|
||||
|
||||
o("style" in updated.dom.attributes).equals(false)
|
||||
|
||||
}
|
||||
})
|
||||
o("not setting style removes all styles", function() {
|
||||
var vnode = {"tag": "p", attrs: {style: "background-color: red"}}
|
||||
var updated = {"tag": "p", attrs: {}}
|
||||
|
||||
render(root, [vnode])
|
||||
|
||||
o("style" in vnode.dom.attributes).equals(true)
|
||||
o(vnode.dom.attributes.style.value).equals("background-color: red;")
|
||||
|
||||
render(root, [updated])
|
||||
|
||||
//browsers disagree here
|
||||
try {
|
||||
|
||||
o(updated.dom.attributes.style.value).equals("")
|
||||
|
||||
} catch (e) {
|
||||
|
||||
o("style" in updated.dom.attributes).equals(false)
|
||||
|
||||
}
|
||||
})
|
||||
o("replaces el", function() {
|
||||
var vnode = {tag: "a"}
|
||||
var updated = {tag: "b"}
|
||||
|
|
@ -224,7 +303,7 @@ o.spec("updateElement", function() {
|
|||
|
||||
o(updated.dom.firstChild.namespaceURI).equals("http://www.w3.org/2000/svg")
|
||||
})
|
||||
o("restores correctly when recycling", function() {
|
||||
o("doesn't restore since we're not recycling", function() {
|
||||
var vnode = {tag: "div", key: 1}
|
||||
var updated = {tag: "div", key: 2}
|
||||
|
||||
|
|
@ -237,9 +316,9 @@ o.spec("updateElement", function() {
|
|||
var c = vnode.dom
|
||||
|
||||
o(root.childNodes.length).equals(1)
|
||||
o(a).equals(c)
|
||||
o(a).notEquals(c) // this used to be a recycling pool test
|
||||
})
|
||||
o("restores correctly when recycling via map", function() {
|
||||
o("doesn't restore since we're not recycling (via map)", function() {
|
||||
var a = {tag: "div", key: 1}
|
||||
var b = {tag: "div", key: 2}
|
||||
var c = {tag: "div", key: 3}
|
||||
|
|
@ -256,6 +335,6 @@ o.spec("updateElement", function() {
|
|||
var y = root.childNodes[1]
|
||||
|
||||
o(root.childNodes.length).equals(3)
|
||||
o(x).equals(y)
|
||||
o(x).notEquals(y) // this used to be a recycling pool test
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -134,17 +134,17 @@ o.spec("updateNodes", function() {
|
|||
o("reverses els w/ odd count", function() {
|
||||
var vnodes = [{tag: "a", key: 1}, {tag: "b", key: 2}, {tag: "i", key: 3}]
|
||||
var updated = [{tag: "i", key: 3}, {tag: "b", key: 2}, {tag: "a", key: 1}]
|
||||
|
||||
var expectedTags = updated.map(function(vn) {return vn.tag})
|
||||
render(root, vnodes)
|
||||
render(root, updated)
|
||||
|
||||
var tagNames = [].map.call(root.childNodes, function(n) {return n.nodeName.toLowerCase()})
|
||||
|
||||
o(root.childNodes.length).equals(3)
|
||||
o(updated[0].dom.nodeName).equals("I")
|
||||
o(updated[0].dom).equals(root.childNodes[0])
|
||||
o(updated[1].dom.nodeName).equals("B")
|
||||
o(updated[1].dom).equals(root.childNodes[1])
|
||||
o(updated[2].dom.nodeName).equals("A")
|
||||
o(updated[2].dom).equals(root.childNodes[2])
|
||||
o(tagNames).deepEquals(expectedTags)
|
||||
})
|
||||
o("creates el at start", function() {
|
||||
var vnodes = [{tag: "a", key: 1}]
|
||||
|
|
@ -264,6 +264,21 @@ o.spec("updateNodes", function() {
|
|||
o(updated[2].dom.nodeName).equals("S")
|
||||
o(updated[2].dom).equals(root.childNodes[2])
|
||||
})
|
||||
o("creates, deletes, reverses els at same time with '__proto__' key", function() {
|
||||
var vnodes = [{tag: "a", key: "__proto__"}, {tag: "i", key: 3}, {tag: "b", key: 2}]
|
||||
var updated = [{tag: "b", key: 2}, {tag: "a", key: "__proto__"}, {tag: "s", key: 4}]
|
||||
|
||||
render(root, vnodes)
|
||||
render(root, updated)
|
||||
|
||||
o(root.childNodes.length).equals(3)
|
||||
o(updated[0].dom.nodeName).equals("B")
|
||||
o(updated[0].dom).equals(root.childNodes[0])
|
||||
o(updated[1].dom.nodeName).equals("A")
|
||||
o(updated[1].dom).equals(root.childNodes[1])
|
||||
o(updated[2].dom.nodeName).equals("S")
|
||||
o(updated[2].dom).equals(root.childNodes[2])
|
||||
})
|
||||
o("adds to empty array followed by el", function() {
|
||||
var vnodes = [{tag: "[", key: 1, children: []}, {tag: "b", key: 2}]
|
||||
var updated = [{tag: "[", key: 1, children: [{tag: "a"}]}, {tag: "b", key: 2}]
|
||||
|
|
@ -614,6 +629,38 @@ o.spec("updateNodes", function() {
|
|||
o(updated[0].dom.nodeName).equals("I")
|
||||
o(updated[0].dom).equals(root.childNodes[0])
|
||||
})
|
||||
o("cached keyed nodes move when the list is reversed", function(){
|
||||
var a = {tag: "a", key: "a"}
|
||||
var b = {tag: "b", key: "b"}
|
||||
var c = {tag: "c", key: "c"}
|
||||
var d = {tag: "d", key: "d"}
|
||||
|
||||
render(root, [a, b, c, d])
|
||||
render(root, [d, c, b, a])
|
||||
|
||||
o(root.childNodes.length).equals(4)
|
||||
o(root.childNodes[0].nodeName).equals("D")
|
||||
o(root.childNodes[1].nodeName).equals("C")
|
||||
o(root.childNodes[2].nodeName).equals("B")
|
||||
o(root.childNodes[3].nodeName).equals("A")
|
||||
})
|
||||
o("cached keyed nodes move when diffed via the map", function() {
|
||||
var onupdate = o.spy()
|
||||
var a = {tag: "a", key: "a", attrs: {onupdate: onupdate}}
|
||||
var b = {tag: "b", key: "b", attrs: {onupdate: onupdate}}
|
||||
var c = {tag: "c", key: "c", attrs: {onupdate: onupdate}}
|
||||
var d = {tag: "d", key: "d", attrs: {onupdate: onupdate}}
|
||||
|
||||
render(root, [a, b, c, d])
|
||||
render(root, [b, d, a, c])
|
||||
|
||||
o(root.childNodes.length).equals(4)
|
||||
o(root.childNodes[0].nodeName).equals("B")
|
||||
o(root.childNodes[1].nodeName).equals("D")
|
||||
o(root.childNodes[2].nodeName).equals("A")
|
||||
o(root.childNodes[3].nodeName).equals("C")
|
||||
o(onupdate.callCount).equals(0)
|
||||
})
|
||||
o("removes then create different bigger", function() {
|
||||
var vnodes = [{tag: "a", key: 1}, {tag: "b", key: 2}]
|
||||
var temp = []
|
||||
|
|
@ -776,7 +823,7 @@ o.spec("updateNodes", function() {
|
|||
o(root.childNodes[0].childNodes[1].childNodes.length).equals(1)
|
||||
o(root.childNodes[1].childNodes.length).equals(0)
|
||||
})
|
||||
o("recycles", function() {
|
||||
o("doesn't recycle", function() {
|
||||
var vnodes = [{tag: "div", key: 1}]
|
||||
var temp = []
|
||||
var updated = [{tag: "div", key: 1}]
|
||||
|
|
@ -785,10 +832,10 @@ o.spec("updateNodes", function() {
|
|||
render(root, temp)
|
||||
render(root, updated)
|
||||
|
||||
o(vnodes[0].dom).equals(updated[0].dom)
|
||||
o(vnodes[0].dom).notEquals(updated[0].dom) // this used to be a recycling pool test
|
||||
o(updated[0].dom.nodeName).equals("DIV")
|
||||
})
|
||||
o("recycles when not keyed", function() {
|
||||
o("doesn't recycle when not keyed", function() {
|
||||
var vnodes = [{tag: "div"}]
|
||||
var temp = []
|
||||
var updated = [{tag: "div"}]
|
||||
|
|
@ -798,19 +845,22 @@ o.spec("updateNodes", function() {
|
|||
render(root, updated)
|
||||
|
||||
o(root.childNodes.length).equals(1)
|
||||
o(vnodes[0].dom).equals(updated[0].dom)
|
||||
o(vnodes[0].dom).notEquals(updated[0].dom) // this used to be a recycling pool test
|
||||
o(updated[0].dom.nodeName).equals("DIV")
|
||||
})
|
||||
o("recycles deep", function() {
|
||||
o("doesn't recycle deep", function() {
|
||||
var vnodes = [{tag: "div", children: [{tag: "a", key: 1}]}]
|
||||
var temp = [{tag: "div"}]
|
||||
var updated = [{tag: "div", children: [{tag: "a", key: 1}]}]
|
||||
|
||||
render(root, vnodes)
|
||||
|
||||
var oldChild = vnodes[0].dom.firstChild
|
||||
|
||||
render(root, temp)
|
||||
render(root, updated)
|
||||
|
||||
o(vnodes[0].dom.firstChild).equals(updated[0].dom.firstChild)
|
||||
o(oldChild).notEquals(updated[0].dom.firstChild) // this used to be a recycling pool test
|
||||
o(updated[0].dom.firstChild.nodeName).equals("A")
|
||||
})
|
||||
o("mixed unkeyed tags are not broken by recycle", function() {
|
||||
|
|
@ -839,6 +889,19 @@ o.spec("updateNodes", function() {
|
|||
o(root.childNodes[0].nodeName).equals("A")
|
||||
o(root.childNodes[1].nodeName).equals("B")
|
||||
})
|
||||
o("onremove doesn't fire from nodes in the pool (#1990)", function () {
|
||||
var onremove = o.spy()
|
||||
render(root, [
|
||||
{tag: "div", children: [{tag: "div", attrs: {onremove: onremove}}]},
|
||||
{tag: "div", children: [{tag: "div", attrs: {onremove: onremove}}]}
|
||||
])
|
||||
render(root, [
|
||||
{tag: "div", children: [{tag: "div", attrs: {onremove: onremove}}]}
|
||||
])
|
||||
render(root,[])
|
||||
|
||||
o(onremove.callCount).equals(2)
|
||||
})
|
||||
o("cached, non-keyed nodes skip diff", function () {
|
||||
var onupdate = o.spy();
|
||||
var cached = {tag:"a", attrs:{onupdate: onupdate}}
|
||||
|
|
@ -857,6 +920,72 @@ o.spec("updateNodes", function() {
|
|||
|
||||
o(onupdate.callCount).equals(0)
|
||||
})
|
||||
o("keyed cached elements are re-initialized when brought back from the pool (#2003)", function () {
|
||||
var onupdate = o.spy()
|
||||
var oncreate = o.spy()
|
||||
var cached = {
|
||||
tag: "B", key: 1, children: [
|
||||
{tag: "A", attrs: {oncreate: oncreate, onupdate: onupdate}, text: "A"}
|
||||
]
|
||||
}
|
||||
render(root, [{tag: "div", children: [cached]}])
|
||||
render(root, [])
|
||||
render(root, [{tag: "div", children: [cached]}])
|
||||
|
||||
o(oncreate.callCount).equals(2)
|
||||
o(onupdate.callCount).equals(0)
|
||||
})
|
||||
|
||||
o("unkeyed cached elements are re-initialized when brought back from the pool (#2003)", function () {
|
||||
var onupdate = o.spy()
|
||||
var oncreate = o.spy()
|
||||
var cached = {
|
||||
tag: "B", children: [
|
||||
{tag: "A", attrs: {oncreate: oncreate, onupdate: onupdate}, text: "A"}
|
||||
]
|
||||
}
|
||||
render(root, [{tag: "div", children: [cached]}])
|
||||
render(root, [])
|
||||
render(root, [{tag: "div", children: [cached]}])
|
||||
|
||||
o(oncreate.callCount).equals(2)
|
||||
o(onupdate.callCount).equals(0)
|
||||
})
|
||||
|
||||
o("keyed cached elements are re-initialized when brought back from nested pools (#2003)", function () {
|
||||
var onupdate = o.spy()
|
||||
var oncreate = o.spy()
|
||||
var cached = {
|
||||
tag: "B", key: 1, children: [
|
||||
{tag: "A", attrs: {oncreate: oncreate, onupdate: onupdate}, text: "A"}
|
||||
]
|
||||
}
|
||||
render(root, [{tag: "div", children: [cached]}])
|
||||
render(root, [{tag: "div", children: []}])
|
||||
render(root, [])
|
||||
render(root, [{tag: "div", children: [cached]}])
|
||||
|
||||
o(oncreate.callCount).equals(2)
|
||||
o(onupdate.callCount).equals(0)
|
||||
})
|
||||
|
||||
o("unkeyed cached elements are re-initialized when brought back from nested pools (#2003)", function () {
|
||||
var onupdate = o.spy()
|
||||
var oncreate = o.spy()
|
||||
var cached = {
|
||||
tag: "B", children: [
|
||||
{tag: "A", attrs: {oncreate: oncreate, onupdate: onupdate}, text: "A"}
|
||||
]
|
||||
}
|
||||
render(root, [{tag: "div", children: [cached]}])
|
||||
render(root, [{tag: "div", children: []}])
|
||||
render(root, [])
|
||||
render(root, [{tag: "div", children: [cached]}])
|
||||
|
||||
o(oncreate.callCount).equals(2)
|
||||
o(onupdate.callCount).equals(0)
|
||||
})
|
||||
|
||||
o("null stays in place", function() {
|
||||
var create = o.spy()
|
||||
var update = o.spy()
|
||||
|
|
@ -904,6 +1033,231 @@ o.spec("updateNodes", function() {
|
|||
|
||||
o(vnode.dom).notEquals(updated.dom)
|
||||
})
|
||||
o("don't add back elements from fragments that are restored from the pool #1991", function() {
|
||||
render(root, [
|
||||
{tag: "[", children: []},
|
||||
{tag: "[", children: []}
|
||||
])
|
||||
render(root, [
|
||||
{tag: "[", children: []},
|
||||
{tag: "[", children: [{tag: "div"}]}
|
||||
])
|
||||
render(root, [
|
||||
{tag: "[", children: [null]}
|
||||
])
|
||||
render(root, [
|
||||
{tag: "[", children: []},
|
||||
{tag: "[", children: []}
|
||||
])
|
||||
|
||||
o(root.childNodes.length).equals(0)
|
||||
})
|
||||
o("don't add back elements from fragments that are being removed #1991", function() {
|
||||
render(root, [
|
||||
{tag: "[", children: []},
|
||||
{tag: "p"},
|
||||
])
|
||||
render(root, [
|
||||
{tag: "[", children: [{tag: "div", text: 5}]}
|
||||
])
|
||||
render(root, [
|
||||
{tag: "[", children: []},
|
||||
{tag: "[", children: []}
|
||||
])
|
||||
|
||||
o(root.childNodes.length).equals(0)
|
||||
})
|
||||
o("handles null values in unkeyed lists of different length (#2003)", function() {
|
||||
var oncreate = o.spy();
|
||||
var onremove = o.spy();
|
||||
var onupdate = o.spy();
|
||||
function attrs() {
|
||||
return {oncreate: oncreate, onremove: onremove, onupdate: onupdate}
|
||||
}
|
||||
|
||||
render(root, [{tag: "div", attrs: attrs()}, null]);
|
||||
render(root, [null, {tag: "div", attrs: attrs()}, null]);
|
||||
|
||||
o(oncreate.callCount).equals(2)
|
||||
o(onremove.callCount).equals(1)
|
||||
o(onupdate.callCount).equals(0)
|
||||
})
|
||||
o("supports changing the element of a keyed element in a list when traversed bottom-up", function() {
|
||||
try {
|
||||
render(root, [{tag: "a", key: 2}])
|
||||
render(root, [{tag: "b", key: 1}, {tag: "b", key: 2}])
|
||||
|
||||
o(root.childNodes.length).equals(2)
|
||||
o(root.childNodes[0].nodeName).equals("B")
|
||||
o(root.childNodes[1].nodeName).equals("B")
|
||||
} catch (e) {
|
||||
o(e).equals(null)
|
||||
}
|
||||
})
|
||||
o("supports changing the element of a keyed element in a list when looking up nodes using the map", function() {
|
||||
try {
|
||||
render(root, [{tag: "x", key: 1}, {tag: "y", key: 2}, {tag: "z", key: 3}])
|
||||
render(root, [{tag: "b", key: 2}, {tag: "c", key: 1}, {tag: "d", key: 4}, {tag: "e", key: 3}])
|
||||
|
||||
o(root.childNodes.length).equals(4)
|
||||
o(root.childNodes[0].nodeName).equals("B")
|
||||
o(root.childNodes[1].nodeName).equals("C")
|
||||
o(root.childNodes[2].nodeName).equals("D")
|
||||
o(root.childNodes[3].nodeName).equals("E")
|
||||
} catch (e) {
|
||||
o(e).equals(null)
|
||||
}
|
||||
})
|
||||
o("don't fetch the nextSibling from the pool", function() {
|
||||
render(root, [{tag: "[", children: [{tag: "div", key: 1}, {tag: "div", key: 2}]}, {tag: "p"}])
|
||||
render(root, [{tag: "[", children: []}, {tag: "p"}])
|
||||
render(root, [{tag: "[", children: [{tag: "div", key: 2}, {tag: "div", key: 1}]}, {tag: "p"}])
|
||||
|
||||
o([].map.call(root.childNodes, function(el) {return el.nodeName})).deepEquals(["DIV", "DIV", "P"])
|
||||
})
|
||||
o("minimizes DOM operations when scrambling a keyed lists", function() {
|
||||
var vnodes = [{tag: "a", key: "a"}, {tag: "b", key: "b"}, {tag: "c", key: "c"}, {tag: "d", key: "d"}]
|
||||
var updated = [{tag: "b", key: "b"}, {tag: "a", key: "a"}, {tag: "d", key: "d"}, {tag: "c", key: "c"}]
|
||||
var expectedTagNames = updated.map(function(vn) {return vn.tag})
|
||||
|
||||
render(root, vnodes)
|
||||
|
||||
root.appendChild = o.spy(root.appendChild)
|
||||
root.insertBefore = o.spy(root.insertBefore)
|
||||
|
||||
render(root, updated)
|
||||
|
||||
var tagNames = [].map.call(root.childNodes, function(n) {return n.nodeName.toLowerCase()})
|
||||
|
||||
o(root.appendChild.callCount + root.insertBefore.callCount).equals(2)
|
||||
o(tagNames).deepEquals(expectedTagNames)
|
||||
})
|
||||
o("minimizes DOM operations when reversing a keyed lists with an odd number of items", function() {
|
||||
var vnodes = [{tag: "a", key: "a"}, {tag: "b", key: "b"}, {tag: "c", key: "c"}, {tag: "d", key: "d"}]
|
||||
var updated = [{tag: "d", key: "d"}, {tag: "c", key: "c"}, {tag: "b", key: "b"}, {tag: "a", key: "a"}]
|
||||
var expectedTagNames = updated.map(function(vn) {return vn.tag})
|
||||
|
||||
render(root, vnodes)
|
||||
|
||||
root.appendChild = o.spy(root.appendChild)
|
||||
root.insertBefore = o.spy(root.insertBefore)
|
||||
|
||||
render(root, updated)
|
||||
|
||||
var tagNames = [].map.call(root.childNodes, function(n) {return n.nodeName.toLowerCase()})
|
||||
|
||||
o(root.appendChild.callCount + root.insertBefore.callCount).equals(3)
|
||||
o(tagNames).deepEquals(expectedTagNames)
|
||||
})
|
||||
o("minimizes DOM operations when reversing a keyed lists with an even number of items", function() {
|
||||
var vnodes = [{tag: "a", key: "a"}, {tag: "b", key: "b"}, {tag: "c", key: "c"}]
|
||||
var updated = [{tag: "c", key: "c"}, {tag: "b", key: "b"}, {tag: "a", key: "a"}]
|
||||
var expectedTagNames = updated.map(function(vn) {return vn.tag})
|
||||
|
||||
render(root, vnodes)
|
||||
|
||||
root.appendChild = o.spy(root.appendChild)
|
||||
root.insertBefore = o.spy(root.insertBefore)
|
||||
|
||||
render(root, updated)
|
||||
|
||||
var tagNames = [].map.call(root.childNodes, function(n) {return n.nodeName.toLowerCase()})
|
||||
|
||||
o(root.appendChild.callCount + root.insertBefore.callCount).equals(2)
|
||||
o(tagNames).deepEquals(expectedTagNames)
|
||||
})
|
||||
o("minimizes DOM operations when scrambling a keyed lists with prefixes and suffixes", function() {
|
||||
var vnodes = [{tag: "i", key: "i"}, {tag: "a", key: "a"}, {tag: "b", key: "b"}, {tag: "c", key: "c"}, {tag: "d", key: "d"}, {tag: "j", key: "j"}]
|
||||
var updated = [{tag: "i", key: "i"}, {tag: "b", key: "b"}, {tag: "a", key: "a"}, {tag: "d", key: "d"}, {tag: "c", key: "c"}, {tag: "j", key: "j"}]
|
||||
var expectedTagNames = updated.map(function(vn) {return vn.tag})
|
||||
|
||||
render(root, vnodes)
|
||||
|
||||
root.appendChild = o.spy(root.appendChild)
|
||||
root.insertBefore = o.spy(root.insertBefore)
|
||||
|
||||
render(root, updated)
|
||||
|
||||
var tagNames = [].map.call(root.childNodes, function(n) {return n.nodeName.toLowerCase()})
|
||||
|
||||
o(root.appendChild.callCount + root.insertBefore.callCount).equals(2)
|
||||
o(tagNames).deepEquals(expectedTagNames)
|
||||
})
|
||||
o("minimizes DOM operations when reversing a keyed lists with an odd number of items with prefixes and suffixes", function() {
|
||||
var vnodes = [{tag: "i", key: "i"}, {tag: "a", key: "a"}, {tag: "b", key: "b"}, {tag: "c", key: "c"}, {tag: "d", key: "d"}, {tag: "j", key: "j"}]
|
||||
var updated = [{tag: "i", key: "i"}, {tag: "d", key: "d"}, {tag: "c", key: "c"}, {tag: "b", key: "b"}, {tag: "a", key: "a"}, {tag: "j", key: "j"}]
|
||||
var expectedTagNames = updated.map(function(vn) {return vn.tag})
|
||||
|
||||
render(root, vnodes)
|
||||
|
||||
root.appendChild = o.spy(root.appendChild)
|
||||
root.insertBefore = o.spy(root.insertBefore)
|
||||
|
||||
render(root, updated)
|
||||
|
||||
var tagNames = [].map.call(root.childNodes, function(n) {return n.nodeName.toLowerCase()})
|
||||
|
||||
o(root.appendChild.callCount + root.insertBefore.callCount).equals(3)
|
||||
o(tagNames).deepEquals(expectedTagNames)
|
||||
})
|
||||
o("minimizes DOM operations when reversing a keyed lists with an even number of items with prefixes and suffixes", function() {
|
||||
var vnodes = [{tag: "i", key: "i"}, {tag: "a", key: "a"}, {tag: "b", key: "b"}, {tag: "c", key: "c"}, {tag: "j", key: "j"}]
|
||||
var updated = [{tag: "i", key: "i"}, {tag: "c", key: "c"}, {tag: "b", key: "b"}, {tag: "a", key: "a"}, {tag: "j", key: "j"}]
|
||||
var expectedTagNames = updated.map(function(vn) {return vn.tag})
|
||||
|
||||
render(root, vnodes)
|
||||
|
||||
root.appendChild = o.spy(root.appendChild)
|
||||
root.insertBefore = o.spy(root.insertBefore)
|
||||
|
||||
render(root, updated)
|
||||
|
||||
var tagNames = [].map.call(root.childNodes, function(n) {return n.nodeName.toLowerCase()})
|
||||
|
||||
o(root.appendChild.callCount + root.insertBefore.callCount).equals(2)
|
||||
o(tagNames).deepEquals(expectedTagNames)
|
||||
})
|
||||
o("scrambling sample 1", function() {
|
||||
function vnodify(str) {
|
||||
return str.split(",").map(function(k) {return {tag: k, key: k}})
|
||||
}
|
||||
var vnodes = vnodify("k0,k1,k2,k3,k4,k5,k6,k7,k8,k9")
|
||||
var updated = vnodify("k4,k1,k2,k9,k0,k3,k6,k5,k8,k7")
|
||||
var expectedTagNames = updated.map(function(vn) {return vn.tag})
|
||||
|
||||
render(root, vnodes)
|
||||
|
||||
root.appendChild = o.spy(root.appendChild)
|
||||
root.insertBefore = o.spy(root.insertBefore)
|
||||
|
||||
render(root, updated)
|
||||
|
||||
var tagNames = [].map.call(root.childNodes, function(n) {return n.nodeName.toLowerCase()})
|
||||
|
||||
o(root.appendChild.callCount + root.insertBefore.callCount).equals(5)
|
||||
o(tagNames).deepEquals(expectedTagNames)
|
||||
})
|
||||
o("scrambling sample 2", function() {
|
||||
function vnodify(str) {
|
||||
return str.split(",").map(function(k) {return {tag: k, key: k}})
|
||||
}
|
||||
var vnodes = vnodify("k0,k1,k2,k3,k4,k5,k6,k7,k8,k9")
|
||||
var updated = vnodify("b,d,k1,k0,k2,k3,k4,a,c,k5,k6,k7,k8,k9")
|
||||
var expectedTagNames = updated.map(function(vn) {return vn.tag})
|
||||
|
||||
render(root, vnodes)
|
||||
|
||||
root.appendChild = o.spy(root.appendChild)
|
||||
root.insertBefore = o.spy(root.insertBefore)
|
||||
|
||||
render(root, updated)
|
||||
|
||||
var tagNames = [].map.call(root.childNodes, function(n) {return n.nodeName.toLowerCase()})
|
||||
|
||||
o(root.appendChild.callCount + root.insertBefore.callCount).equals(5)
|
||||
o(tagNames).deepEquals(expectedTagNames)
|
||||
})
|
||||
|
||||
components.forEach(function(cmp){
|
||||
o.spec(cmp.kind, function(){
|
||||
var createComponent = cmp.create
|
||||
|
|
@ -940,6 +1294,19 @@ o.spec("updateNodes", function() {
|
|||
o(root.childNodes[0].nodeName).equals("A")
|
||||
o(root.childNodes[1].nodeName).equals("S")
|
||||
})
|
||||
o("removing a component that returns a fragment doesn't throw (regression test for incidental bug introduced while debugging some Flems)", function() {
|
||||
var component = createComponent({
|
||||
view: function() {return {tag: "[", children:[{tag: "a"}, {tag: "b"}]}}
|
||||
})
|
||||
try {
|
||||
render(root, [{tag: component}])
|
||||
render(root, [])
|
||||
|
||||
o(root.childNodes.length).equals(0)
|
||||
} catch (e) {
|
||||
o(e).equals(null)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
157
render/tests/test-updateNodesFuzzer.js
Normal file
157
render/tests/test-updateNodesFuzzer.js
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
"use strict"
|
||||
|
||||
var o = require("../../ospec/ospec")
|
||||
var domMock = require("../../test-utils/domMock")
|
||||
var vdom = require("../../render/render")
|
||||
|
||||
// pilfered and adapted from https://github.com/domvm/domvm/blob/7aaec609e4c625b9acf9a22d035d6252a5ca654f/test/src/flat-list-keyed-fuzz.js
|
||||
o.spec("updateNodes keyed list Fuzzer", function() {
|
||||
var i = 0, $window, root, render
|
||||
o.beforeEach(function() {
|
||||
$window = domMock()
|
||||
root = $window.document.createElement("div")
|
||||
render = vdom($window).render
|
||||
})
|
||||
|
||||
|
||||
void [
|
||||
{delMax: 0, movMax: 50, insMax: 9},
|
||||
{delMax: 3, movMax: 5, insMax: 5},
|
||||
{delMax: 7, movMax: 15, insMax: 0},
|
||||
{delMax: 5, movMax: 100, insMax: 3},
|
||||
{delMax: 5, movMax: 0, insMax: 3},
|
||||
].forEach(function(c) {
|
||||
var tests = 250
|
||||
|
||||
while (tests--) {
|
||||
var test = fuzzTest(c.delMax, c.movMax, c.insMax)
|
||||
o(i++ + ": " + test.list.join() + " -> " + test.updated.join(), function() {
|
||||
render(root, test.list.map(function(x){return {tag: x, key: x}}))
|
||||
addSpies(root)
|
||||
render(root, test.updated.map(function(x){return {tag: x, key: x}}))
|
||||
|
||||
if (root.appendChild.callCount + root.insertBefore.callCount !== test.expected.creations + test.expected.moves) console.log(test, {aC: root.appendChild.callCount, iB: root.insertBefore.callCount}, [].map.call(root.childNodes, function(n){return n.nodeName.toLowerCase()}))
|
||||
|
||||
o(root.appendChild.callCount + root.insertBefore.callCount).equals(test.expected.creations + test.expected.moves)("moves")
|
||||
o(root.removeChild.callCount).equals(test.expected.deletions)("deletions")
|
||||
o([].map.call(root.childNodes, function(n){return n.nodeName.toLowerCase()})).deepEquals(test.updated)
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// https://en.wikipedia.org/wiki/Longest_increasing_subsequence
|
||||
// impl borrowed from https://github.com/ivijs/ivi
|
||||
function longestIncreasingSubsequence(a) {
|
||||
var p = a.slice()
|
||||
var result = []
|
||||
result.push(0)
|
||||
var u
|
||||
var v
|
||||
|
||||
for (var i = 0, il = a.length; i < il; ++i) {
|
||||
var j = result[result.length - 1]
|
||||
if (a[j] < a[i]) {
|
||||
p[i] = j
|
||||
result.push(i)
|
||||
continue
|
||||
}
|
||||
|
||||
u = 0
|
||||
v = result.length - 1
|
||||
|
||||
while (u < v) {
|
||||
var c = ((u + v) / 2) | 0 // eslint-disable-line no-bitwise
|
||||
if (a[result[c]] < a[i]) {
|
||||
u = c + 1
|
||||
} else {
|
||||
v = c
|
||||
}
|
||||
}
|
||||
|
||||
if (a[i] < a[result[u]]) {
|
||||
if (u > 0) {
|
||||
p[i] = result[u - 1]
|
||||
}
|
||||
result[u] = i
|
||||
}
|
||||
}
|
||||
|
||||
u = result.length
|
||||
v = result[u - 1]
|
||||
|
||||
while (u-- > 0) {
|
||||
result[u] = v
|
||||
v = p[v]
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
function rand(min, max) {
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min
|
||||
}
|
||||
|
||||
function ins(arr, qty) {
|
||||
var p = ["a","b","c","d","e","f","g","h","i"]
|
||||
|
||||
while (qty-- > 0)
|
||||
arr.splice(rand(0, arr.length - 1), 0, p.shift())
|
||||
}
|
||||
|
||||
function del(arr, qty) {
|
||||
while (qty-- > 0)
|
||||
arr.splice(rand(0, arr.length - 1), 1)
|
||||
}
|
||||
|
||||
function mov(arr, qty) {
|
||||
while (qty-- > 0) {
|
||||
var from = rand(0, arr.length - 1)
|
||||
var to = rand(0, arr.length - 1)
|
||||
|
||||
arr.splice(to, 0, arr.splice(from, 1)[0])
|
||||
}
|
||||
}
|
||||
|
||||
function fuzzTest(delMax, movMax, insMax) {
|
||||
var list = ["k0","k1","k2","k3","k4","k5","k6","k7","k8","k9"]
|
||||
var copy = list.slice()
|
||||
|
||||
var delCount = rand(0, delMax),
|
||||
movCount = rand(0, movMax),
|
||||
insCount = rand(0, insMax)
|
||||
|
||||
del(copy, delCount)
|
||||
mov(copy, movCount)
|
||||
|
||||
var expected = {
|
||||
creations: insCount,
|
||||
deletions: delCount,
|
||||
moves: 0
|
||||
}
|
||||
|
||||
if (movCount > 0) {
|
||||
var newPos = copy.map(function(v) {
|
||||
return list.indexOf(v)
|
||||
}).filter(function(i) {
|
||||
return i != -1
|
||||
})
|
||||
var lis = longestIncreasingSubsequence(newPos)
|
||||
expected.moves = copy.length - lis.length
|
||||
}
|
||||
|
||||
ins(copy, insCount)
|
||||
|
||||
return {
|
||||
expected: expected,
|
||||
list: list,
|
||||
updated: copy
|
||||
}
|
||||
}
|
||||
|
||||
function addSpies(node) {
|
||||
node.appendChild = o.spy(node.appendChild)
|
||||
node.insertBefore = o.spy(node.insertBefore)
|
||||
node.removeChild = o.spy(node.removeChild)
|
||||
}
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue