diff --git a/api/tests/index.html b/api/tests/index.html
index fd2557d5..37d313f4 100644
--- a/api/tests/index.html
+++ b/api/tests/index.html
@@ -13,6 +13,7 @@
+
diff --git a/api/tests/test-mount.js b/api/tests/test-mount.js
index 038f470a..4ba466c7 100644
--- a/api/tests/test-mount.js
+++ b/api/tests/test-mount.js
@@ -1,6 +1,7 @@
"use strict"
var o = require("../../ospec/ospec")
+var components = require("../../test-utils/components")
var domMock = require("../../test-utils/domMock")
var m = require("../../render/hyperscript")
@@ -22,16 +23,6 @@ o.spec("mount", function() {
render = coreRenderer($window).render
})
- o("throws on invalid `root` DOM node", function() {
- var threw = false
- try {
- mount(null, {view: function() {}})
- } catch (e) {
- threw = true
- }
- o(threw).equals(true)
- })
-
o("throws on invalid component", function() {
var threw = false
try {
@@ -42,227 +33,223 @@ o.spec("mount", function() {
o(threw).equals(true)
})
- o("renders into `root` (POJO component)", function() {
- mount(root, {
- view : function() {
- return m("div")
- }
- })
+ components.forEach(function(cmp){
+ o.spec(cmp.kind, function(){
+ var createComponent = cmp.create
- o(root.firstChild.nodeName).equals("DIV")
- })
-
- o("renders into `root` (class component)", function() {
- function Cmp(){}
- Cmp.prototype.view = function(){return m("div")}
- mount(root, Cmp)
-
- o(root.firstChild.nodeName).equals("DIV")
- })
-
- o("renders into `root` factory (factory component)", function() {
- mount(root, function(){
- return {
- view : function() {
- return m("div")
+ o("throws on invalid `root` DOM node", function() {
+ var threw = false
+ try {
+ mount(null, createComponent({view: function() {}}))
+ } catch (e) {
+ threw = true
}
- }
- })
+ o(threw).equals(true)
+ })
- o(root.firstChild.nodeName).equals("DIV")
- })
-
- o("mounting null unmounts", function() {
- mount(root, {
- view : function() {
- return m("div")
- }
- })
-
- mount(root, null)
-
- o(root.childNodes.length).equals(0)
- })
-
- o("redraws on events", function(done) {
- var onupdate = o.spy()
- var oninit = o.spy()
- var onclick = o.spy()
- var e = $window.document.createEvent("MouseEvents")
-
- e.initEvent("click", true, true)
-
- mount(root, {
- view : function() {
- return m("div", {
- oninit : oninit,
- onupdate : onupdate,
- onclick : onclick,
- })
- }
- })
-
- root.firstChild.dispatchEvent(e)
-
- o(oninit.callCount).equals(1)
- o(onupdate.callCount).equals(0)
-
- o(onclick.callCount).equals(1)
- o(onclick.this).equals(root.firstChild)
- o(onclick.args[0].type).equals("click")
- o(onclick.args[0].target).equals(root.firstChild)
-
- // Wrapped to give time for the rate-limited redraw to fire
- setTimeout(function() {
- o(onupdate.callCount).equals(1)
-
- done()
- }, FRAME_BUDGET)
- })
-
- o("redraws several mount points on events", function(done, timeout) {
- timeout(60)
-
- var onupdate0 = o.spy()
- var oninit0 = o.spy()
- var onclick0 = o.spy()
- var onupdate1 = o.spy()
- var oninit1 = o.spy()
- var onclick1 = o.spy()
-
- var e = $window.document.createEvent("MouseEvents")
-
- e.initEvent("click", true, true)
-
- render(root, [
- m("#child0"),
- m("#child1")
- ])
-
- mount(root.childNodes[0], {
- view : function() {
- return m("div", {
- oninit : oninit0,
- onupdate : onupdate0,
- onclick : onclick0,
- })
- }
- })
-
- o(oninit0.callCount).equals(1)
- o(onupdate0.callCount).equals(0)
-
- mount(root.childNodes[1], {
- view : function() {
- return m("div", {
- oninit : oninit1,
- onupdate : onupdate1,
- onclick : onclick1,
- })
- }
- })
-
- o(oninit1.callCount).equals(1)
- o(onupdate1.callCount).equals(0)
-
- root.childNodes[0].firstChild.dispatchEvent(e)
- o(onclick0.callCount).equals(1)
- o(onclick0.this).equals(root.childNodes[0].firstChild)
-
- setTimeout(function() {
- o(onupdate0.callCount).equals(1)
- o(onupdate1.callCount).equals(1)
-
- root.childNodes[1].firstChild.dispatchEvent(e)
- o(onclick1.callCount).equals(1)
- o(onclick1.this).equals(root.childNodes[1].firstChild)
-
- setTimeout(function() {
- o(onupdate0.callCount).equals(2)
- o(onupdate1.callCount).equals(2)
-
- done()
- }, FRAME_BUDGET)
- }, FRAME_BUDGET)
-
- })
-
- o("event handlers can skip redraw", function(done) {
- var onupdate = o.spy()
- var oninit = o.spy()
- var e = $window.document.createEvent("MouseEvents")
-
- e.initEvent("click", true, true)
-
- mount(root, {
- view: function() {
- return m("div", {
- oninit: oninit,
- onupdate: onupdate,
- onclick: function(e) {
- e.redraw = false
+ o("renders into `root`", function() {
+ mount(root, createComponent({
+ view : function() {
+ return m("div")
}
- })
- }
+ }))
+
+ o(root.firstChild.nodeName).equals("DIV")
+ })
+
+ o("mounting null unmounts", function() {
+ mount(root, createComponent({
+ view : function() {
+ return m("div")
+ }
+ }))
+
+ mount(root, null)
+
+ o(root.childNodes.length).equals(0)
+ })
+
+ o("redraws on events", function(done) {
+ var onupdate = o.spy()
+ var oninit = o.spy()
+ var onclick = o.spy()
+ var e = $window.document.createEvent("MouseEvents")
+
+ e.initEvent("click", true, true)
+
+ mount(root, createComponent({
+ view : function() {
+ return m("div", {
+ oninit : oninit,
+ onupdate : onupdate,
+ onclick : onclick,
+ })
+ }
+ }))
+
+ root.firstChild.dispatchEvent(e)
+
+ o(oninit.callCount).equals(1)
+ o(onupdate.callCount).equals(0)
+
+ o(onclick.callCount).equals(1)
+ o(onclick.this).equals(root.firstChild)
+ o(onclick.args[0].type).equals("click")
+ o(onclick.args[0].target).equals(root.firstChild)
+
+ // Wrapped to give time for the rate-limited redraw to fire
+ setTimeout(function() {
+ o(onupdate.callCount).equals(1)
+
+ done()
+ }, FRAME_BUDGET)
+ })
+
+ o("redraws several mount points on events", function(done, timeout) {
+ timeout(60)
+
+ var onupdate0 = o.spy()
+ var oninit0 = o.spy()
+ var onclick0 = o.spy()
+ var onupdate1 = o.spy()
+ var oninit1 = o.spy()
+ var onclick1 = o.spy()
+
+ var e = $window.document.createEvent("MouseEvents")
+
+ e.initEvent("click", true, true)
+
+ render(root, [
+ m("#child0"),
+ m("#child1")
+ ])
+
+ mount(root.childNodes[0], createComponent({
+ view : function() {
+ return m("div", {
+ oninit : oninit0,
+ onupdate : onupdate0,
+ onclick : onclick0,
+ })
+ }
+ }))
+
+ o(oninit0.callCount).equals(1)
+ o(onupdate0.callCount).equals(0)
+
+ mount(root.childNodes[1], createComponent({
+ view : function() {
+ return m("div", {
+ oninit : oninit1,
+ onupdate : onupdate1,
+ onclick : onclick1,
+ })
+ }
+ }))
+
+ o(oninit1.callCount).equals(1)
+ o(onupdate1.callCount).equals(0)
+
+ root.childNodes[0].firstChild.dispatchEvent(e)
+ o(onclick0.callCount).equals(1)
+ o(onclick0.this).equals(root.childNodes[0].firstChild)
+
+ setTimeout(function() {
+ o(onupdate0.callCount).equals(1)
+ o(onupdate1.callCount).equals(1)
+
+ root.childNodes[1].firstChild.dispatchEvent(e)
+ o(onclick1.callCount).equals(1)
+ o(onclick1.this).equals(root.childNodes[1].firstChild)
+
+ setTimeout(function() {
+ o(onupdate0.callCount).equals(2)
+ o(onupdate1.callCount).equals(2)
+
+ done()
+ }, FRAME_BUDGET)
+ }, FRAME_BUDGET)
+
+ })
+
+ o("event handlers can skip redraw", function(done) {
+ var onupdate = o.spy()
+ var oninit = o.spy()
+ var e = $window.document.createEvent("MouseEvents")
+
+ e.initEvent("click", true, true)
+
+ mount(root, createComponent({
+ view: function() {
+ return m("div", {
+ oninit: oninit,
+ onupdate: onupdate,
+ onclick: function(e) {
+ e.redraw = false
+ }
+ })
+ }
+ }))
+
+ root.firstChild.dispatchEvent(e)
+
+ o(oninit.callCount).equals(1)
+
+ // Wrapped to ensure no redraw fired
+ setTimeout(function() {
+ o(onupdate.callCount).equals(0)
+
+ done()
+ }, FRAME_BUDGET)
+ })
+
+ o("redraws when the render function is run", function(done) {
+ var onupdate = o.spy()
+ var oninit = o.spy()
+
+ mount(root, createComponent({
+ view : function() {
+ return m("div", {
+ oninit: oninit,
+ onupdate: onupdate
+ })
+ }
+ }))
+
+ o(oninit.callCount).equals(1)
+ o(onupdate.callCount).equals(0)
+
+ redrawService.redraw()
+
+ // Wrapped to give time for the rate-limited redraw to fire
+ setTimeout(function() {
+ o(onupdate.callCount).equals(1)
+
+ done()
+ }, FRAME_BUDGET)
+ })
+
+ o("throttles", function(done, timeout) {
+ timeout(200)
+
+ var i = 0
+ mount(root, createComponent({view: function() {i++}}))
+ var before = i
+
+ redrawService.redraw()
+ redrawService.redraw()
+ redrawService.redraw()
+ redrawService.redraw()
+
+ var after = i
+
+ setTimeout(function(){
+ o(before).equals(1) // mounts synchronously
+ o(after).equals(1) // throttles rest
+ o(i).equals(2)
+ done()
+ },40)
+ })
})
-
- root.firstChild.dispatchEvent(e)
-
- o(oninit.callCount).equals(1)
-
- // Wrapped to ensure no redraw fired
- setTimeout(function() {
- o(onupdate.callCount).equals(0)
-
- done()
- }, FRAME_BUDGET)
})
-
- o("redraws when the render function is run", function(done) {
- var onupdate = o.spy()
- var oninit = o.spy()
-
- mount(root, {
- view : function() {
- return m("div", {
- oninit: oninit,
- onupdate: onupdate
- })
- }
- })
-
- o(oninit.callCount).equals(1)
- o(onupdate.callCount).equals(0)
-
- redrawService.redraw()
-
- // Wrapped to give time for the rate-limited redraw to fire
- setTimeout(function() {
- o(onupdate.callCount).equals(1)
-
- done()
- }, FRAME_BUDGET)
- })
-
- o("throttles", function(done, timeout) {
- timeout(200)
-
- var i = 0
- mount(root, {view: function() {i++}})
- var before = i
-
- redrawService.redraw()
- redrawService.redraw()
- redrawService.redraw()
- redrawService.redraw()
-
- var after = i
-
- setTimeout(function(){
- o(before).equals(1) // mounts synchronously
- o(after).equals(1) // throttles rest
- o(i).equals(2)
- done()
- },40)
- })
-})
+})
\ No newline at end of file
diff --git a/api/tests/test-router.js b/api/tests/test-router.js
index c624789b..f43605e1 100644
--- a/api/tests/test-router.js
+++ b/api/tests/test-router.js
@@ -82,7 +82,7 @@ o.spec("route", function() {
})
- o("routed mount points can redraw synchronously (factory component)", function() {
+ o("routed mount points can redraw synchronously (closure component)", function() {
var view = o.spy()
function Cmp() {return {view: view}}
diff --git a/render/tests/index.html b/render/tests/index.html
index 480b8b7c..b978ae6f 100644
--- a/render/tests/index.html
+++ b/render/tests/index.html
@@ -8,6 +8,7 @@
+
diff --git a/render/tests/test-component.js b/render/tests/test-component.js
index ed70fe7d..64cd6fcb 100644
--- a/render/tests/test-component.js
+++ b/render/tests/test-component.js
@@ -1,6 +1,7 @@
"use strict"
var o = require("../../ospec/ospec")
+var components = require("../../test-utils/components")
var domMock = require("../../test-utils/domMock")
var vdom = require("../../render/render")
@@ -13,689 +14,715 @@ o.spec("component", function() {
render = vdom($window).render
})
- o.spec("basics", function() {
- o("works", function() {
- var component = {
- view: function() {
- return {tag: "div", attrs: {id: "a"}, text: "b"}
- }
- }
- var node = {tag: component}
+ components.forEach(function(cmp){
+ o.spec(cmp.kind, function(){
+ var createComponent = cmp.create
- render(root, [node])
+ o.spec("basics", function() {
+ o("works", function() {
+ var component = createComponent({
+ view: function() {
+ return {tag: "div", attrs: {id: "a"}, text: "b"}
+ }
+ })
+ var node = {tag: component}
- o(root.firstChild.nodeName).equals("DIV")
- o(root.firstChild.attributes["id"].nodeValue).equals("a")
- o(root.firstChild.firstChild.nodeValue).equals("b")
- })
- o("receives arguments", function() {
- var component = {
- view: function(vnode) {
- return {tag: "div", attrs: vnode.attrs, text: vnode.text}
- }
- }
- var node = {tag: component, attrs: {id: "a"}, text: "b"}
+ render(root, [node])
- render(root, [node])
+ o(root.firstChild.nodeName).equals("DIV")
+ o(root.firstChild.attributes["id"].nodeValue).equals("a")
+ o(root.firstChild.firstChild.nodeValue).equals("b")
+ })
+ o("receives arguments", function() {
+ var component = createComponent({
+ view: function(vnode) {
+ return {tag: "div", attrs: vnode.attrs, text: vnode.text}
+ }
+ })
+ var node = {tag: component, attrs: {id: "a"}, text: "b"}
- o(root.firstChild.nodeName).equals("DIV")
- o(root.firstChild.attributes["id"].nodeValue).equals("a")
- o(root.firstChild.firstChild.nodeValue).equals("b")
- })
- o("updates", function() {
- var component = {
- view: function(vnode) {
- return {tag: "div", attrs: vnode.attrs, text: vnode.text}
- }
- }
- render(root, [{tag: component, attrs: {id: "a"}, text: "b"}])
- render(root, [{tag: component, attrs: {id: "c"}, text: "d"}])
+ render(root, [node])
- o(root.firstChild.nodeName).equals("DIV")
- o(root.firstChild.attributes["id"].nodeValue).equals("c")
- o(root.firstChild.firstChild.nodeValue).equals("d")
- })
- o("updates root from null", function() {
- var visible = false
- var component = {
- view: function(vnode) {
- return visible ? {tag: "div"} : null
- }
- }
- render(root, [{tag: component}])
- visible = true
- render(root, [{tag: component}])
+ o(root.firstChild.nodeName).equals("DIV")
+ o(root.firstChild.attributes["id"].nodeValue).equals("a")
+ o(root.firstChild.firstChild.nodeValue).equals("b")
+ })
+ o("updates", function() {
+ var component = createComponent({
+ view: function(vnode) {
+ return {tag: "div", attrs: vnode.attrs, text: vnode.text}
+ }
+ })
+ render(root, [{tag: component, attrs: {id: "a"}, text: "b"}])
+ render(root, [{tag: component, attrs: {id: "c"}, text: "d"}])
- o(root.firstChild.nodeName).equals("DIV")
- })
- o("updates root from primitive", function() {
- var visible = false
- var component = {
- view: function(vnode) {
- return visible ? {tag: "div"} : false
- }
- }
- render(root, [{tag: component}])
- visible = true
- render(root, [{tag: component}])
+ o(root.firstChild.nodeName).equals("DIV")
+ o(root.firstChild.attributes["id"].nodeValue).equals("c")
+ o(root.firstChild.firstChild.nodeValue).equals("d")
+ })
+ o("updates root from null", function() {
+ var visible = false
+ var component = createComponent({
+ view: function(vnode) {
+ return visible ? {tag: "div"} : null
+ }
+ })
+ render(root, [{tag: component}])
+ visible = true
+ render(root, [{tag: component}])
- o(root.firstChild.nodeName).equals("DIV")
- })
- o("updates root to null", function() {
- var visible = true
- var component = {
- view: function(vnode) {
- return visible ? {tag: "div"} : null
- }
- }
- render(root, [{tag: component}])
- visible = false
- render(root, [{tag: component}])
+ o(root.firstChild.nodeName).equals("DIV")
+ })
+ o("updates root from primitive", function() {
+ var visible = false
+ var component = createComponent({
+ view: function(vnode) {
+ return visible ? {tag: "div"} : false
+ }
+ })
+ render(root, [{tag: component}])
+ visible = true
+ render(root, [{tag: component}])
- o(root.childNodes.length).equals(0)
- })
- o("updates root to primitive", function() {
- var visible = true
- var component = {
- view: function(vnode) {
- return visible ? {tag: "div"} : false
- }
- }
- render(root, [{tag: component}])
- visible = false
- render(root, [{tag: component}])
+ o(root.firstChild.nodeName).equals("DIV")
+ })
+ o("updates root to null", function() {
+ var visible = true
+ var component = createComponent({
+ view: function(vnode) {
+ return visible ? {tag: "div"} : null
+ }
+ })
+ render(root, [{tag: component}])
+ visible = false
+ render(root, [{tag: component}])
- o(root.firstChild.nodeValue).equals("")
- })
- o("updates root from null to null", function() {
- var component = {
- view: function(vnode) {
- return null
- }
- }
- render(root, [{tag: component}])
- render(root, [{tag: component}])
+ o(root.childNodes.length).equals(0)
+ })
+ o("updates root to primitive", function() {
+ var visible = true
+ var component = createComponent({
+ view: function(vnode) {
+ return visible ? {tag: "div"} : false
+ }
+ })
+ render(root, [{tag: component}])
+ visible = false
+ render(root, [{tag: component}])
- o(root.childNodes.length).equals(0)
- })
- o("removes", function() {
- var component = {
- view: function(vnode) {
- return {tag: "div"}
- }
- }
- var div = {tag: "div", key: 2}
- render(root, [{tag: component, key: 1}, div])
- render(root, [{tag: "div", key: 2}])
+ o(root.firstChild.nodeValue).equals("")
+ })
+ o("updates root from null to null", function() {
+ var component = createComponent({
+ view: function(vnode) {
+ return null
+ }
+ })
+ render(root, [{tag: component}])
+ render(root, [{tag: component}])
- o(root.childNodes.length).equals(1)
- o(root.firstChild).equals(div.dom)
- })
- o("svg works when creating across component boundary", function() {
- var component = {
- view: function(vnode) {
- return {tag: "g"}
- }
- }
- render(root, [{tag: "svg", children: [{tag: component}]}])
+ o(root.childNodes.length).equals(0)
+ })
+ o("removes", function() {
+ var component = createComponent({
+ view: function(vnode) {
+ return {tag: "div"}
+ }
+ })
+ var div = {tag: "div", key: 2}
+ render(root, [{tag: component, key: 1}, div])
+ render(root, [{tag: "div", key: 2}])
- o(root.firstChild.firstChild.namespaceURI).equals("http://www.w3.org/2000/svg")
- })
- o("svg works when updating across component boundary", function() {
- var component = {
- view: function(vnode) {
- return {tag: "g"}
- }
- }
- render(root, [{tag: "svg", children: [{tag: component}]}])
- render(root, [{tag: "svg", children: [{tag: component}]}])
+ o(root.childNodes.length).equals(1)
+ o(root.firstChild).equals(div.dom)
+ })
+ o("svg works when creating across component boundary", function() {
+ var component = createComponent({
+ view: function(vnode) {
+ return {tag: "g"}
+ }
+ })
+ render(root, [{tag: "svg", children: [{tag: component}]}])
- o(root.firstChild.firstChild.namespaceURI).equals("http://www.w3.org/2000/svg")
+ o(root.firstChild.firstChild.namespaceURI).equals("http://www.w3.org/2000/svg")
+ })
+ o("svg works when updating across component boundary", function() {
+ var component = createComponent({
+ view: function(vnode) {
+ return {tag: "g"}
+ }
+ })
+ render(root, [{tag: "svg", children: [{tag: component}]}])
+ render(root, [{tag: "svg", children: [{tag: component}]}])
+
+ o(root.firstChild.firstChild.namespaceURI).equals("http://www.w3.org/2000/svg")
+ })
+ })
+ o.spec("return value", function() {
+ o("can return fragments", function() {
+ var component = createComponent({
+ view: function(vnode) {
+ return [
+ {tag: "label"},
+ {tag: "input"},
+ ]
+ }
+ })
+ render(root, [{tag: component}])
+
+ o(root.childNodes.length).equals(2)
+ o(root.childNodes[0].nodeName).equals("LABEL")
+ o(root.childNodes[1].nodeName).equals("INPUT")
+ })
+ o("can return string", function() {
+ var component = createComponent({
+ view: function(vnode) {
+ return "a"
+ }
+ })
+ render(root, [{tag: component}])
+
+ o(root.firstChild.nodeType).equals(3)
+ o(root.firstChild.nodeValue).equals("a")
+ })
+ o("can return falsy string", function() {
+ var component = createComponent({
+ view: function(vnode) {
+ return ""
+ }
+ })
+ render(root, [{tag: component}])
+
+ o(root.firstChild.nodeType).equals(3)
+ o(root.firstChild.nodeValue).equals("")
+ })
+ o("can return number", function() {
+ var component = createComponent({
+ view: function(vnode) {
+ return 1
+ }
+ })
+ render(root, [{tag: component}])
+
+ o(root.firstChild.nodeType).equals(3)
+ o(root.firstChild.nodeValue).equals("1")
+ })
+ o("can return falsy number", function() {
+ var component = createComponent({
+ view: function(vnode) {
+ return 0
+ }
+ })
+ render(root, [{tag: component}])
+
+ o(root.firstChild.nodeType).equals(3)
+ o(root.firstChild.nodeValue).equals("0")
+ })
+ o("can return boolean", function() {
+ var component = createComponent({
+ view: function(vnode) {
+ return true
+ }
+ })
+ render(root, [{tag: component}])
+
+ o(root.firstChild.nodeType).equals(3)
+ o(root.firstChild.nodeValue).equals("true")
+ })
+ o("can return falsy boolean", function() {
+ var component = createComponent({
+ view: function(vnode) {
+ return false
+ }
+ })
+ render(root, [{tag: component}])
+
+ o(root.firstChild.nodeType).equals(3)
+ o(root.firstChild.nodeValue).equals("")
+ })
+ o("can return null", function() {
+ var component = createComponent({
+ view: function(vnode) {
+ return null
+ }
+ })
+ render(root, [{tag: component}])
+
+ o(root.childNodes.length).equals(0)
+ })
+ o("can return undefined", function() {
+ var component = createComponent({
+ view: function(vnode) {
+ return undefined
+ }
+ })
+ render(root, [{tag: component}])
+
+ o(root.childNodes.length).equals(0)
+ })
+ o("throws a custom error if it returns itself", function() {
+ // A view that returns its vnode would otherwise trigger an infinite loop
+ var component = createComponent({
+ view: function(vnode) {
+ return vnode
+ }
+ })
+ try {
+ render(root, [{tag: component}])
+ }
+ catch (e) {
+ o(e instanceof Error).equals(true)
+ // Call stack exception is a RangeError
+ o(e instanceof RangeError).equals(false)
+ }
+ })
+ o("can update when returning fragments", function() {
+ var component = createComponent({
+ view: function(vnode) {
+ return [
+ {tag: "label"},
+ {tag: "input"},
+ ]
+ }
+ })
+ render(root, [{tag: component}])
+ render(root, [{tag: component}])
+
+ o(root.childNodes.length).equals(2)
+ o(root.childNodes[0].nodeName).equals("LABEL")
+ o(root.childNodes[1].nodeName).equals("INPUT")
+ })
+ o("can update when returning primitive", function() {
+ var component = createComponent({
+ view: function(vnode) {
+ return "a"
+ }
+ })
+ render(root, [{tag: component}])
+ render(root, [{tag: component}])
+
+ o(root.firstChild.nodeType).equals(3)
+ o(root.firstChild.nodeValue).equals("a")
+ })
+ o("can update when returning null", function() {
+ var component = createComponent({
+ view: function(vnode) {
+ return null
+ }
+ })
+ render(root, [{tag: component}])
+ render(root, [{tag: component}])
+
+ o(root.childNodes.length).equals(0)
+ })
+ o("can remove when returning fragments", function() {
+ var component = createComponent({
+ view: function(vnode) {
+ return [
+ {tag: "label"},
+ {tag: "input"},
+ ]
+ }
+ })
+ var div = {tag: "div", key: 2}
+ render(root, [{tag: component, key: 1}, div])
+
+ render(root, [{tag: "div", key: 2}])
+
+ o(root.childNodes.length).equals(1)
+ o(root.firstChild).equals(div.dom)
+ })
+ o("can remove when returning primitive", function() {
+ var component = createComponent({
+ view: function(vnode) {
+ return "a"
+ }
+ })
+ var div = {tag: "div", key: 2}
+ render(root, [{tag: component, key: 1}, div])
+
+ render(root, [{tag: "div", key: 2}])
+
+ o(root.childNodes.length).equals(1)
+ o(root.firstChild).equals(div.dom)
+ })
+ })
+ o.spec("lifecycle", function() {
+ o("calls oninit", function() {
+ var called = 0
+ var component = createComponent({
+ oninit: function(vnode) {
+ called++
+
+ o(vnode.tag).equals(component)
+ o(vnode.dom).equals(undefined)
+ o(root.childNodes.length).equals(0)
+ },
+ view: function() {
+ return {tag: "div", attrs: {id: "a"}, text: "b"}
+ }
+ })
+ var node = {tag: component}
+
+ render(root, [node])
+
+ o(called).equals(1)
+ o(root.firstChild.nodeName).equals("DIV")
+ o(root.firstChild.attributes["id"].nodeValue).equals("a")
+ o(root.firstChild.firstChild.nodeValue).equals("b")
+ })
+ o("calls oninit when returning fragment", function() {
+ var called = 0
+ var component = createComponent({
+ oninit: function(vnode) {
+ called++
+
+ o(vnode.tag).equals(component)
+ o(vnode.dom).equals(undefined)
+ o(root.childNodes.length).equals(0)
+ },
+ view: function() {
+ return [{tag: "div", attrs: {id: "a"}, text: "b"}]
+ }
+ })
+ var node = {tag: component}
+
+ render(root, [node])
+
+ o(called).equals(1)
+ o(root.firstChild.nodeName).equals("DIV")
+ o(root.firstChild.attributes["id"].nodeValue).equals("a")
+ o(root.firstChild.firstChild.nodeValue).equals("b")
+ })
+ o("calls oninit before view", function() {
+ var viewCalled = false
+
+ render(root, createComponent({
+ tag: {
+ view: function() {
+ viewCalled = true
+ return [{tag: "div", attrs: {id: "a"}, text: "b"}]
+ },
+ oninit: function(vnode) {
+ o(viewCalled).equals(false)
+ },
+ }
+ }))
+ })
+ o("does not calls oninit on redraw", function() {
+ var init = o.spy()
+ var component = createComponent({
+ view: function() {
+ return {tag: "div", attrs: {id: "a"}, text: "b"}
+ },
+ oninit: init,
+ })
+
+ function view() {
+ return {tag: component}
+ }
+
+ render(root, view())
+ render(root, view())
+
+ o(init.callCount).equals(1)
+ })
+ o("calls oncreate", function() {
+ var called = 0
+ var component = createComponent({
+ oncreate: function(vnode) {
+ called++
+
+ o(vnode.dom).notEquals(undefined)
+ o(vnode.dom).equals(root.firstChild)
+ o(root.childNodes.length).equals(1)
+ },
+ view: function() {
+ return {tag: "div", attrs: {id: "a"}, text: "b"}
+ }
+ })
+ var node = {tag: component}
+
+ render(root, [node])
+
+ o(called).equals(1)
+ o(root.firstChild.nodeName).equals("DIV")
+ o(root.firstChild.attributes["id"].nodeValue).equals("a")
+ o(root.firstChild.firstChild.nodeValue).equals("b")
+ })
+ o("does not calls oncreate on redraw", function() {
+ var create = o.spy()
+ var component = createComponent({
+ view: function() {
+ return {tag: "div", attrs: {id: "a"}, text: "b"}
+ },
+ oncreate: create,
+ })
+
+ function view() {
+ return {tag: component}
+ }
+
+ render(root, view())
+ render(root, view())
+
+ o(create.callCount).equals(1)
+ })
+ o("calls oncreate when returning fragment", function() {
+ var called = 0
+ var component = createComponent({
+ oncreate: function(vnode) {
+ called++
+
+ o(vnode.dom).notEquals(undefined)
+ o(vnode.dom).equals(root.firstChild)
+ o(root.childNodes.length).equals(1)
+ },
+ view: function() {
+ return [{tag: "div", attrs: {id: "a"}, text: "b"}]
+ }
+ })
+ var node = {tag: component}
+
+ render(root, [node])
+
+ o(called).equals(1)
+ o(root.firstChild.nodeName).equals("DIV")
+ o(root.firstChild.attributes["id"].nodeValue).equals("a")
+ o(root.firstChild.firstChild.nodeValue).equals("b")
+ })
+ o("calls onupdate", function() {
+ var called = 0
+ var component = createComponent({
+ onupdate: function(vnode) {
+ called++
+
+ o(vnode.dom).notEquals(undefined)
+ o(vnode.dom).equals(root.firstChild)
+ o(root.childNodes.length).equals(1)
+ },
+ view: function() {
+ return {tag: "div", attrs: {id: "a"}, text: "b"}
+ }
+ })
+
+ render(root, [{tag: component}])
+
+ o(called).equals(0)
+
+ render(root, [{tag: component}])
+
+ o(called).equals(1)
+ o(root.firstChild.nodeName).equals("DIV")
+ o(root.firstChild.attributes["id"].nodeValue).equals("a")
+ o(root.firstChild.firstChild.nodeValue).equals("b")
+ })
+ o("calls onupdate when returning fragment", function() {
+ var called = 0
+ var component = createComponent({
+ onupdate: function(vnode) {
+ called++
+
+ o(vnode.dom).notEquals(undefined)
+ o(vnode.dom).equals(root.firstChild)
+ o(root.childNodes.length).equals(1)
+ },
+ view: function() {
+ return [{tag: "div", attrs: {id: "a"}, text: "b"}]
+ }
+ })
+
+ render(root, [{tag: component}])
+
+ o(called).equals(0)
+
+ render(root, [{tag: component}])
+
+ o(called).equals(1)
+ o(root.firstChild.nodeName).equals("DIV")
+ o(root.firstChild.attributes["id"].nodeValue).equals("a")
+ o(root.firstChild.firstChild.nodeValue).equals("b")
+ })
+ o("calls onremove", function() {
+ var called = 0
+ var component = createComponent({
+ onremove: function(vnode) {
+ called++
+
+ o(vnode.dom).notEquals(undefined)
+ o(vnode.dom).equals(root.firstChild)
+ o(root.childNodes.length).equals(1)
+ },
+ view: function() {
+ return {tag: "div", attrs: {id: "a"}, text: "b"}
+ }
+ })
+
+ render(root, [{tag: component}])
+
+ o(called).equals(0)
+
+ render(root, [])
+
+ o(called).equals(1)
+ o(root.childNodes.length).equals(0)
+ })
+ o("calls onremove when returning fragment", function() {
+ var called = 0
+ var component = createComponent({
+ onremove: function(vnode) {
+ called++
+
+ o(vnode.dom).notEquals(undefined)
+ o(vnode.dom).equals(root.firstChild)
+ o(root.childNodes.length).equals(1)
+ },
+ view: function() {
+ return [{tag: "div", attrs: {id: "a"}, text: "b"}]
+ }
+ })
+
+ render(root, [{tag: component}])
+
+ o(called).equals(0)
+
+ render(root, [])
+
+ o(called).equals(1)
+ o(root.childNodes.length).equals(0)
+ })
+ o("calls onbeforeremove", function() {
+ var called = 0
+ var component = createComponent({
+ onbeforeremove: function(vnode) {
+ called++
+
+ o(vnode.dom).notEquals(undefined)
+ o(vnode.dom).equals(root.firstChild)
+ o(root.childNodes.length).equals(1)
+ },
+ view: function() {
+ return {tag: "div", attrs: {id: "a"}, text: "b"}
+ }
+ })
+
+ render(root, [{tag: component}])
+
+ o(called).equals(0)
+
+ render(root, [])
+
+ o(called).equals(1)
+ o(root.childNodes.length).equals(0)
+ })
+ o("calls onbeforeremove when returning fragment", function() {
+ var called = 0
+ var component = createComponent({
+ onbeforeremove: function(vnode) {
+ called++
+
+ o(vnode.dom).notEquals(undefined)
+ o(vnode.dom).equals(root.firstChild)
+ o(root.childNodes.length).equals(1)
+ },
+ view: function() {
+ return [{tag: "div", attrs: {id: "a"}, text: "b"}]
+ }
+ })
+
+ render(root, [{tag: component}])
+
+ o(called).equals(0)
+
+ render(root, [])
+
+ o(called).equals(1)
+ o(root.childNodes.length).equals(0)
+ })
+ o("does not recycle when there's an onupdate", function() {
+ var component = createComponent({
+ onupdate: function() {},
+ view: function() {
+ return {tag: "div"}
+ }
+ })
+ var update = o.spy()
+ var vnode = {tag: component, key: 1}
+ var updated = {tag: component, key: 1}
+
+ render(root, [vnode])
+ render(root, [])
+ render(root, [updated])
+
+ o(vnode.dom).notEquals(updated.dom)
+ })
+ })
+ o.spec("state", function() {
+ o("initializes state", function() {
+ var called = 0
+ var data = {a: 1}
+ var component = createComponent(createComponent({
+ data: data,
+ oninit: init,
+ view: function() {
+ return ""
+ }
+ }))
+
+ render(root, [{tag: component}])
+
+ function init(vnode) {
+ o(vnode.state.data).equals(data)
+ }
+ })
+ o('state "copy" is shallow', function() {
+ var called = 0
+ var body = {a: 1}
+ var data = [body]
+ var component = createComponent(createComponent({
+ data: data,
+ oninit: init,
+ view: function() {
+ return ""
+ }
+ }))
+
+ render(root, [{tag: component}])
+
+ function init(vnode) {
+ o(vnode.state.data).equals(data)
+ o(vnode.state.data[0]).equals(body)
+ }
+ })
+ })
})
})
- o.spec("return value", function() {
- o("can return fragments", function() {
- var component = {
- view: function(vnode) {
- return [
- {tag: "label"},
- {tag: "input"},
- ]
- }
- }
- render(root, [{tag: component}])
+ o.spec("Tests specific to certain component kinds", function() {
- o(root.childNodes.length).equals(2)
- o(root.childNodes[0].nodeName).equals("LABEL")
- o(root.childNodes[1].nodeName).equals("INPUT")
- })
- o("can return string", function() {
- var component = {
- view: function(vnode) {
- return "a"
- }
- }
- render(root, [{tag: component}])
-
- o(root.firstChild.nodeType).equals(3)
- o(root.firstChild.nodeValue).equals("a")
- })
- o("can return falsy string", function() {
- var component = {
- view: function(vnode) {
- return ""
- }
- }
- render(root, [{tag: component}])
-
- o(root.firstChild.nodeType).equals(3)
- o(root.firstChild.nodeValue).equals("")
- })
- o("can return number", function() {
- var component = {
- view: function(vnode) {
- return 1
- }
- }
- render(root, [{tag: component}])
-
- o(root.firstChild.nodeType).equals(3)
- o(root.firstChild.nodeValue).equals("1")
- })
- o("can return falsy number", function() {
- var component = {
- view: function(vnode) {
- return 0
- }
- }
- render(root, [{tag: component}])
-
- o(root.firstChild.nodeType).equals(3)
- o(root.firstChild.nodeValue).equals("0")
- })
- o("can return boolean", function() {
- var component = {
- view: function(vnode) {
- return true
- }
- }
- render(root, [{tag: component}])
-
- o(root.firstChild.nodeType).equals(3)
- o(root.firstChild.nodeValue).equals("true")
- })
- o("can return falsy boolean", function() {
- var component = {
- view: function(vnode) {
- return false
- }
- }
- render(root, [{tag: component}])
-
- o(root.firstChild.nodeType).equals(3)
- o(root.firstChild.nodeValue).equals("")
- })
- o("can return null", function() {
- var component = {
- view: function(vnode) {
- return null
- }
- }
- render(root, [{tag: component}])
-
- o(root.childNodes.length).equals(0)
- })
- o("can return undefined", function() {
- var component = {
- view: function(vnode) {
- return undefined
- }
- }
- render(root, [{tag: component}])
-
- o(root.childNodes.length).equals(0)
- })
- o("throws a custom error if it returns itself", function() {
- // A view that returns its vnode would otherwise trigger an infinite loop
- var component = {
- view: function(vnode) {
- return vnode
- }
- }
- try {
- render(root, [{tag: component}])
- }
- catch (e) {
- o(e instanceof Error).equals(true)
- // Call stack exception is a RangeError
- o(e instanceof RangeError).equals(false)
- }
- })
- o("can update when returning fragments", function() {
- var component = {
- view: function(vnode) {
- return [
- {tag: "label"},
- {tag: "input"},
- ]
- }
- }
- render(root, [{tag: component}])
- render(root, [{tag: component}])
-
- o(root.childNodes.length).equals(2)
- o(root.childNodes[0].nodeName).equals("LABEL")
- o(root.childNodes[1].nodeName).equals("INPUT")
- })
- o("can update when returning primitive", function() {
- var component = {
- view: function(vnode) {
- return "a"
- }
- }
- render(root, [{tag: component}])
- render(root, [{tag: component}])
-
- o(root.firstChild.nodeType).equals(3)
- o(root.firstChild.nodeValue).equals("a")
- })
- o("can update when returning null", function() {
- var component = {
- view: function(vnode) {
- return null
- }
- }
- render(root, [{tag: component}])
- render(root, [{tag: component}])
-
- o(root.childNodes.length).equals(0)
- })
- o("can remove when returning fragments", function() {
- var component = {
- view: function(vnode) {
- return [
- {tag: "label"},
- {tag: "input"},
- ]
- }
- }
- var div = {tag: "div", key: 2}
- render(root, [{tag: component, key: 1}, div])
-
- render(root, [{tag: "div", key: 2}])
-
- o(root.childNodes.length).equals(1)
- o(root.firstChild).equals(div.dom)
- })
- o("can remove when returning primitive", function() {
- var component = {
- view: function(vnode) {
- return "a"
- }
- }
- var div = {tag: "div", key: 2}
- render(root, [{tag: component, key: 1}, div])
-
- render(root, [{tag: "div", key: 2}])
-
- o(root.childNodes.length).equals(1)
- o(root.firstChild).equals(div.dom)
- })
- })
- o.spec("lifecycle", function() {
- o("calls oninit", function() {
- var called = 0
- var component = {
- oninit: function(vnode) {
- called++
-
- o(vnode.tag).equals(component)
- o(vnode.dom).equals(undefined)
- o(root.childNodes.length).equals(0)
- },
- view: function() {
- return {tag: "div", attrs: {id: "a"}, text: "b"}
- }
- }
- var node = {tag: component}
-
- render(root, [node])
-
- o(called).equals(1)
- o(root.firstChild.nodeName).equals("DIV")
- o(root.firstChild.attributes["id"].nodeValue).equals("a")
- o(root.firstChild.firstChild.nodeValue).equals("b")
- })
- o("calls oninit when returning fragment", function() {
- var called = 0
- var component = {
- oninit: function(vnode) {
- called++
-
- o(vnode.tag).equals(component)
- o(vnode.dom).equals(undefined)
- o(root.childNodes.length).equals(0)
- },
- view: function() {
- return [{tag: "div", attrs: {id: "a"}, text: "b"}]
- }
- }
- var node = {tag: component}
-
- render(root, [node])
-
- o(called).equals(1)
- o(root.firstChild.nodeName).equals("DIV")
- o(root.firstChild.attributes["id"].nodeValue).equals("a")
- o(root.firstChild.firstChild.nodeValue).equals("b")
- })
- o("calls oninit before view", function() {
- var viewCalled = false
-
- render(root, {
- tag: {
+ o.spec("POJO state", function() {
+ o("copies state", function() {
+ var called = 0
+ var data = {a: 1}
+ var component = {
+ data: data,
+ oninit: init,
view: function() {
- viewCalled = true
- return [{tag: "div", attrs: {id: "a"}, text: "b"}]
- },
- oninit: function(vnode) {
- o(viewCalled).equals(false)
- },
+ return ""
+ }
+ }
+
+ render(root, [{tag: component}])
+
+ function init(vnode) {
+ o(vnode.state.data).equals(data)
+
+ //inherits state via prototype
+ component.x = 1
+ o(vnode.state.x).equals(1)
}
})
})
- o("does not calls oninit on redraw", function() {
- var init = o.spy()
- var component = {
- view: function() {
- return {tag: "div", attrs: {id: "a"}, text: "b"}
- },
- oninit: init,
- }
- function view() {
- return {tag: component}
- }
-
- render(root, view())
- render(root, view())
-
- o(init.callCount).equals(1)
- })
- o("calls oncreate", function() {
- var called = 0
- var component = {
- oncreate: function(vnode) {
- called++
-
- o(vnode.dom).notEquals(undefined)
- o(vnode.dom).equals(root.firstChild)
- o(root.childNodes.length).equals(1)
- },
- view: function() {
- return {tag: "div", attrs: {id: "a"}, text: "b"}
- }
- }
- var node = {tag: component}
-
- render(root, [node])
-
- o(called).equals(1)
- o(root.firstChild.nodeName).equals("DIV")
- o(root.firstChild.attributes["id"].nodeValue).equals("a")
- o(root.firstChild.firstChild.nodeValue).equals("b")
- })
- o("does not calls oncreate on redraw", function() {
- var create = o.spy()
- var component = {
- view: function() {
- return {tag: "div", attrs: {id: "a"}, text: "b"}
- },
- oncreate: create,
- }
-
- function view() {
- return {tag: component}
- }
-
- render(root, view())
- render(root, view())
-
- o(create.callCount).equals(1)
- })
- o("calls oncreate when returning fragment", function() {
- var called = 0
- var component = {
- oncreate: function(vnode) {
- called++
-
- o(vnode.dom).notEquals(undefined)
- o(vnode.dom).equals(root.firstChild)
- o(root.childNodes.length).equals(1)
- },
- view: function() {
- return [{tag: "div", attrs: {id: "a"}, text: "b"}]
- }
- }
- var node = {tag: component}
-
- render(root, [node])
-
- o(called).equals(1)
- o(root.firstChild.nodeName).equals("DIV")
- o(root.firstChild.attributes["id"].nodeValue).equals("a")
- o(root.firstChild.firstChild.nodeValue).equals("b")
- })
- o("calls onupdate", function() {
- var called = 0
- var component = {
- onupdate: function(vnode) {
- called++
-
- o(vnode.dom).notEquals(undefined)
- o(vnode.dom).equals(root.firstChild)
- o(root.childNodes.length).equals(1)
- },
- view: function() {
- return {tag: "div", attrs: {id: "a"}, text: "b"}
- }
- }
-
- render(root, [{tag: component}])
-
- o(called).equals(0)
-
- render(root, [{tag: component}])
-
- o(called).equals(1)
- o(root.firstChild.nodeName).equals("DIV")
- o(root.firstChild.attributes["id"].nodeValue).equals("a")
- o(root.firstChild.firstChild.nodeValue).equals("b")
- })
- o("calls onupdate when returning fragment", function() {
- var called = 0
- var component = {
- onupdate: function(vnode) {
- called++
-
- o(vnode.dom).notEquals(undefined)
- o(vnode.dom).equals(root.firstChild)
- o(root.childNodes.length).equals(1)
- },
- view: function() {
- return [{tag: "div", attrs: {id: "a"}, text: "b"}]
- }
- }
-
- render(root, [{tag: component}])
-
- o(called).equals(0)
-
- render(root, [{tag: component}])
-
- o(called).equals(1)
- o(root.firstChild.nodeName).equals("DIV")
- o(root.firstChild.attributes["id"].nodeValue).equals("a")
- o(root.firstChild.firstChild.nodeValue).equals("b")
- })
- o("calls onremove", function() {
- var called = 0
- var component = {
- onremove: function(vnode) {
- called++
-
- o(vnode.dom).notEquals(undefined)
- o(vnode.dom).equals(root.firstChild)
- o(root.childNodes.length).equals(1)
- },
- view: function() {
- return {tag: "div", attrs: {id: "a"}, text: "b"}
- }
- }
-
- render(root, [{tag: component}])
-
- o(called).equals(0)
-
- render(root, [])
-
- o(called).equals(1)
- o(root.childNodes.length).equals(0)
- })
- o("calls onremove when returning fragment", function() {
- var called = 0
- var component = {
- onremove: function(vnode) {
- called++
-
- o(vnode.dom).notEquals(undefined)
- o(vnode.dom).equals(root.firstChild)
- o(root.childNodes.length).equals(1)
- },
- view: function() {
- return [{tag: "div", attrs: {id: "a"}, text: "b"}]
- }
- }
-
- render(root, [{tag: component}])
-
- o(called).equals(0)
-
- render(root, [])
-
- o(called).equals(1)
- o(root.childNodes.length).equals(0)
- })
- o("calls onbeforeremove", function() {
- var called = 0
- var component = {
- onbeforeremove: function(vnode) {
- called++
-
- o(vnode.dom).notEquals(undefined)
- o(vnode.dom).equals(root.firstChild)
- o(root.childNodes.length).equals(1)
- },
- view: function() {
- return {tag: "div", attrs: {id: "a"}, text: "b"}
- }
- }
-
- render(root, [{tag: component}])
-
- o(called).equals(0)
-
- render(root, [])
-
- o(called).equals(1)
- o(root.childNodes.length).equals(0)
- })
- o("calls onbeforeremove when returning fragment", function() {
- var called = 0
- var component = {
- onbeforeremove: function(vnode) {
- called++
-
- o(vnode.dom).notEquals(undefined)
- o(vnode.dom).equals(root.firstChild)
- o(root.childNodes.length).equals(1)
- },
- view: function() {
- return [{tag: "div", attrs: {id: "a"}, text: "b"}]
- }
- }
-
- render(root, [{tag: component}])
-
- o(called).equals(0)
-
- render(root, [])
-
- o(called).equals(1)
- o(root.childNodes.length).equals(0)
- })
- o("does not recycle when there's an onupdate", function() {
- var component = {
- onupdate: function() {},
- view: function() {
- return {tag: "div"}
- }
- }
- var update = o.spy()
- var vnode = {tag: component, key: 1}
- var updated = {tag: component, key: 1}
-
- render(root, [vnode])
- render(root, [])
- render(root, [updated])
-
- o(vnode.dom).notEquals(updated.dom)
- })
- })
- o.spec("state", function() {
- o("copies state", function() {
- var called = 0
- var data = {a: 1}
- var component = {
- data: data,
- oninit: init,
- view: function() {
- return ""
- }
- }
-
- render(root, [{tag: component}])
-
- function init(vnode) {
- o(vnode.state.data).deepEquals(data)
- o(vnode.state.data).equals(data)
-
- //inherits state via prototype
- component.x = 1
- o(vnode.state.x).equals(1)
- }
- })
- o("state copy is shallow", function() {
- var called = 0
- var body = {a: 1}
- var data = [body]
- var component = {
- data: data,
- oninit: init,
- view: function() {
- return ""
- }
- }
-
- render(root, [{tag: component}])
-
- function init(vnode) {
- o(vnode.state.data).equals(data)
- o(vnode.state.data[0]).equals(body)
- }
- })
- })
- o.spec("Alternative ways to specify componenents", function() {
o("Classes can be used as components", function() {
function MyComponent(vnode){
o(vnode.state).equals(null)
@@ -762,7 +789,7 @@ o.spec("component", function() {
o(proto.onbeforeremove.args.length).equals(1)
o(proto.onremove.args.length).equals(1)
})
- o("Factory functions can be used as components", function() {
+ o("Closure functions can be used as components", function() {
var state, context
function component(vnode) {
o(vnode.state).equals(null)
diff --git a/render/tests/test-onbeforeremove.js b/render/tests/test-onbeforeremove.js
index c9af4894..4395f90a 100644
--- a/render/tests/test-onbeforeremove.js
+++ b/render/tests/test-onbeforeremove.js
@@ -2,6 +2,7 @@
var o = require("../../ospec/ospec")
var callAsync = require("../../test-utils/callAsync")
+var components = require("../../test-utils/components")
var domMock = require("../../test-utils/domMock")
var vdom = require("../../render/render")
var Promise = require("../../promise/promise")
@@ -169,39 +170,44 @@ o.spec("onbeforeremove", function() {
done()
})
})
- o("finalizes the remove phase asynchronously when promise is returned synchronously from both attrs- and tag.onbeforeremove", function(done) {
- var onremove = o.spy()
- var onbeforeremove = function(){return Promise.resolve()}
- var component = {
- onbeforeremove: onbeforeremove,
- onremove: onremove,
- view: function() {},
- }
- render(root, [{tag: component, attrs: {onbeforeremove: onbeforeremove, onremove: onremove}}])
- render(root, [])
- callAsync(function() {
- o(onremove.callCount).equals(2) // once for `tag`, once for `attrs`
- done()
- })
- })
- o("awaits promise resolution before removing the node", function(done) {
- var view = o.spy()
- var onremove = o.spy()
- var onbeforeremove = function(){return new Promise(function(resolve){callAsync(resolve)})}
- var component = {
- onbeforeremove: onbeforeremove,
- onremove: onremove,
- view: view,
- }
- render(root, [{tag: component}])
- render(root, [])
+ components.forEach(function(cmp){
+ o.spec(cmp.kind, function(){
+ var createComponent = cmp.create
+ o("finalizes the remove phase asynchronously when promise is returned synchronously from both attrs- and tag.onbeforeremove", function(done) {
+ var onremove = o.spy()
+ var onbeforeremove = function(){return Promise.resolve()}
+ var component = createComponent({
+ onbeforeremove: onbeforeremove,
+ onremove: onremove,
+ view: function() {},
+ })
+ render(root, [{tag: component, attrs: {onbeforeremove: onbeforeremove, onremove: onremove}}])
+ render(root, [])
+ callAsync(function() {
+ o(onremove.callCount).equals(2) // once for `tag`, once for `attrs`
+ done()
+ })
+ })
+ o("awaits promise resolution before removing the node", function(done) {
+ var view = o.spy()
+ var onremove = o.spy()
+ var onbeforeremove = function(){return new Promise(function(resolve){callAsync(resolve)})}
+ var component = createComponent({
+ onbeforeremove: onbeforeremove,
+ onremove: onremove,
+ view: view,
+ })
+ render(root, [{tag: component}])
+ render(root, [])
- callAsync(function(){
- o(onremove.callCount).equals(0)
+ callAsync(function(){
+ o(onremove.callCount).equals(0)
- callAsync(function() {
- o(onremove.callCount).equals(1)
- done()
+ callAsync(function() {
+ o(onremove.callCount).equals(1)
+ done()
+ })
+ })
})
})
})
diff --git a/render/tests/test-onbeforeupdate.js b/render/tests/test-onbeforeupdate.js
index 8b326b8c..cf16dd1c 100644
--- a/render/tests/test-onbeforeupdate.js
+++ b/render/tests/test-onbeforeupdate.js
@@ -1,6 +1,7 @@
"use strict"
var o = require("../../ospec/ospec")
+var components = require("../../test-utils/components")
var domMock = require("../../test-utils/domMock")
var vdom = require("../../render/render")
@@ -56,86 +57,6 @@ o.spec("onbeforeupdate", function() {
o(root.firstChild.nodeValue).equals("a")
})
- o("prevents update in component", function() {
- var component = {
- onbeforeupdate: function() {return false},
- view: function(vnode) {
- return {tag: "div", children: vnode.children}
- },
- }
- var vnode = {tag: component, children: [{tag: "#", children: "a"}]}
- var updated = {tag: component, children: [{tag: "#", children: "b"}]}
-
- render(root, [vnode])
- render(root, [updated])
-
- o(root.firstChild.firstChild.nodeValue).equals("a")
- })
-
- o("prevents update if returning false in component and false in vnode", function() {
- var component = {
- onbeforeupdate: function() {return false},
- view: function(vnode) {
- return {tag: "div", attrs: {id: vnode.attrs.id}}
- },
- }
- var vnode = {tag: component, attrs: {id: "a", onbeforeupdate: function() {return false}}}
- var updated = {tag: component, attrs: {id: "b", onbeforeupdate: function() {return false}}}
-
- render(root, [vnode])
- render(root, [updated])
-
- o(root.firstChild.attributes["id"].nodeValue).equals("a")
- })
-
- o("does not prevent update if returning true in component and true in vnode", function() {
- var component = {
- onbeforeupdate: function() {return true},
- view: function(vnode) {
- return {tag: "div", attrs: {id: vnode.attrs.id}}
- },
- }
- var vnode = {tag: component, attrs: {id: "a", onbeforeupdate: function() {return true}}}
- var updated = {tag: component, attrs: {id: "b", onbeforeupdate: function() {return true}}}
-
- render(root, [vnode])
- render(root, [updated])
-
- o(root.firstChild.attributes["id"].nodeValue).equals("b")
- })
-
- o("does not prevent update if returning false in component but true in vnode", function() {
- var component = {
- onbeforeupdate: function() {return false},
- view: function(vnode) {
- return {tag: "div", attrs: {id: vnode.attrs.id}}
- },
- }
- var vnode = {tag: component, attrs: {id: "a", onbeforeupdate: function() {return true}}}
- var updated = {tag: component, attrs: {id: "b", onbeforeupdate: function() {return true}}}
-
- render(root, [vnode])
- render(root, [updated])
-
- o(root.firstChild.attributes["id"].nodeValue).equals("b")
- })
-
- o("does not prevent update if returning true in component but false in vnode", function() {
- var component = {
- onbeforeupdate: function() {return true},
- view: function(vnode) {
- return {tag: "div", attrs: {id: vnode.attrs.id}}
- },
- }
- var vnode = {tag: component, attrs: {id: "a", onbeforeupdate: function() {return false}}}
- var updated = {tag: component, attrs: {id: "b", onbeforeupdate: function() {return false}}}
-
- render(root, [vnode])
- render(root, [updated])
-
- o(root.firstChild.attributes["id"].nodeValue).equals("b")
- })
-
o("does not prevent update if returning true", function() {
var onbeforeupdate = function() {return true}
var vnode = {tag: "div", attrs: {id: "a", onbeforeupdate: onbeforeupdate}}
@@ -147,22 +68,6 @@ o.spec("onbeforeupdate", function() {
o(root.firstChild.attributes["id"].nodeValue).equals("b")
})
- o("does not prevent update if returning true from component", function() {
- var component = {
- onbeforeupdate: function() {return true},
- view: function(vnode) {
- return {tag: "div", attrs: vnode.attrs}
- },
- }
- var vnode = {tag: component, attrs: {id: "a"}}
- var updated = {tag: component, attrs: {id: "b"}}
-
- render(root, [vnode])
- render(root, [updated])
-
- o(root.firstChild.attributes["id"].nodeValue).equals("b")
- })
-
o("accepts arguments for comparison", function() {
var count = 0
var vnode = {tag: "div", attrs: {id: "a", onbeforeupdate: onbeforeupdate}}
@@ -184,33 +89,6 @@ o.spec("onbeforeupdate", function() {
o(root.firstChild.attributes["id"].nodeValue).equals("b")
})
- o("accepts arguments for comparison in component", function() {
- var component = {
- onbeforeupdate: onbeforeupdate,
- view: function(vnode) {
- return {tag: "div", attrs: vnode.attrs}
- },
- }
- var count = 0
- var vnode = {tag: component, attrs: {id: "a"}}
- var updated = {tag: component, attrs: {id: "b"}}
-
- render(root, [vnode])
- render(root, [updated])
-
- function onbeforeupdate(vnode, old) {
- count++
-
- o(old.attrs.id).equals("a")
- o(vnode.attrs.id).equals("b")
-
- return old.attrs.id !== vnode.attrs.id
- }
-
- o(count).equals(1)
- o(root.firstChild.attributes["id"].nodeValue).equals("b")
- })
-
o("is not called on creation", function() {
var count = 0
var vnode = {tag: "div", attrs: {id: "a", onbeforeupdate: onbeforeupdate}}
@@ -226,28 +104,6 @@ o.spec("onbeforeupdate", function() {
o(count).equals(0)
})
- o("is not called on component creation", function() {
- var component = {
- onbeforeupdate: onbeforeupdate,
- view: function(vnode) {
- return {tag: "div", attrs: vnode.attrs}
- },
- }
-
- var count = 0
- var vnode = {tag: "div", attrs: {id: "a"}}
- var updated = {tag: "div", attrs: {id: "b"}}
-
- render(root, [vnode])
-
- function onbeforeupdate(vnode, old) {
- count++
- return true
- }
-
- o(count).equals(0)
- })
-
o("is called only once on update", function() {
var count = 0
var vnode = {tag: "div", attrs: {id: "a", onbeforeupdate: onbeforeupdate}}
@@ -264,26 +120,177 @@ o.spec("onbeforeupdate", function() {
o(count).equals(1)
})
- o("is called only once on component update", function() {
- var component = {
- onbeforeupdate: onbeforeupdate,
- view: function(vnode) {
- return {tag: "div", attrs: vnode.attrs}
- },
- }
+ components.forEach(function(cmp){
+ o.spec(cmp.kind, function(){
+ var createComponent = cmp.create
- var count = 0
- var vnode = {tag: component, attrs: {id: "a"}}
- var updated = {tag: component, attrs: {id: "b"}}
+ o("prevents update in component", function() {
+ var component = createComponent({
+ onbeforeupdate: function() {return false},
+ view: function(vnode) {
+ return {tag: "div", children: vnode.children}
+ },
+ })
+ var vnode = {tag: component, children: [{tag: "#", children: "a"}]}
+ var updated = {tag: component, children: [{tag: "#", children: "b"}]}
- render(root, [vnode])
- render(root, [updated])
+ render(root, [vnode])
+ render(root, [updated])
- function onbeforeupdate(vnode, old) {
- count++
- return true
- }
+ o(root.firstChild.firstChild.nodeValue).equals("a")
+ })
- o(count).equals(1)
+ o("prevents update if returning false in component and false in vnode", function() {
+ var component = createComponent({
+ onbeforeupdate: function() {return false},
+ view: function(vnode) {
+ return {tag: "div", attrs: {id: vnode.attrs.id}}
+ },
+ })
+ var vnode = {tag: component, attrs: {id: "a", onbeforeupdate: function() {return false}}}
+ var updated = {tag: component, attrs: {id: "b", onbeforeupdate: function() {return false}}}
+
+ render(root, [vnode])
+ render(root, [updated])
+
+ o(root.firstChild.attributes["id"].nodeValue).equals("a")
+ })
+
+ o("does not prevent update if returning true in component and true in vnode", function() {
+ var component = createComponent({
+ onbeforeupdate: function() {return true},
+ view: function(vnode) {
+ return {tag: "div", attrs: {id: vnode.attrs.id}}
+ },
+ })
+ var vnode = {tag: component, attrs: {id: "a", onbeforeupdate: function() {return true}}}
+ var updated = {tag: component, attrs: {id: "b", onbeforeupdate: function() {return true}}}
+
+ render(root, [vnode])
+ render(root, [updated])
+
+ o(root.firstChild.attributes["id"].nodeValue).equals("b")
+ })
+
+ o("does not prevent update if returning false in component but true in vnode", function() {
+ var component = createComponent({
+ onbeforeupdate: function() {return false},
+ view: function(vnode) {
+ return {tag: "div", attrs: {id: vnode.attrs.id}}
+ },
+ })
+ var vnode = {tag: component, attrs: {id: "a", onbeforeupdate: function() {return true}}}
+ var updated = {tag: component, attrs: {id: "b", onbeforeupdate: function() {return true}}}
+
+ render(root, [vnode])
+ render(root, [updated])
+
+ o(root.firstChild.attributes["id"].nodeValue).equals("b")
+ })
+
+ o("does not prevent update if returning true in component but false in vnode", function() {
+ var component = createComponent({
+ onbeforeupdate: function() {return true},
+ view: function(vnode) {
+ return {tag: "div", attrs: {id: vnode.attrs.id}}
+ },
+ })
+ var vnode = {tag: component, attrs: {id: "a", onbeforeupdate: function() {return false}}}
+ var updated = {tag: component, attrs: {id: "b", onbeforeupdate: function() {return false}}}
+
+ render(root, [vnode])
+ render(root, [updated])
+
+ o(root.firstChild.attributes["id"].nodeValue).equals("b")
+ })
+
+ o("does not prevent update if returning true from component", function() {
+ var component = createComponent({
+ onbeforeupdate: function() {return true},
+ view: function(vnode) {
+ return {tag: "div", attrs: vnode.attrs}
+ },
+ })
+ var vnode = {tag: component, attrs: {id: "a"}}
+ var updated = {tag: component, attrs: {id: "b"}}
+
+ render(root, [vnode])
+ render(root, [updated])
+
+ o(root.firstChild.attributes["id"].nodeValue).equals("b")
+ })
+
+ o("accepts arguments for comparison in component", function() {
+ var component = createComponent({
+ onbeforeupdate: onbeforeupdate,
+ view: function(vnode) {
+ return {tag: "div", attrs: vnode.attrs}
+ },
+ })
+ var count = 0
+ var vnode = {tag: component, attrs: {id: "a"}}
+ var updated = {tag: component, attrs: {id: "b"}}
+
+ render(root, [vnode])
+ render(root, [updated])
+
+ function onbeforeupdate(vnode, old) {
+ count++
+
+ o(old.attrs.id).equals("a")
+ o(vnode.attrs.id).equals("b")
+
+ return old.attrs.id !== vnode.attrs.id
+ }
+
+ o(count).equals(1)
+ o(root.firstChild.attributes["id"].nodeValue).equals("b")
+ })
+
+ o("is not called on component creation", function() {
+ var component = createComponent({
+ onbeforeupdate: onbeforeupdate,
+ view: function(vnode) {
+ return {tag: "div", attrs: vnode.attrs}
+ },
+ })
+
+ var count = 0
+ var vnode = {tag: "div", attrs: {id: "a"}}
+ var updated = {tag: "div", attrs: {id: "b"}}
+
+ render(root, [vnode])
+
+ function onbeforeupdate(vnode, old) {
+ count++
+ return true
+ }
+
+ o(count).equals(0)
+ })
+
+ o("is called only once on component update", function() {
+ var component = createComponent({
+ onbeforeupdate: onbeforeupdate,
+ view: function(vnode) {
+ return {tag: "div", attrs: vnode.attrs}
+ },
+ })
+
+ var count = 0
+ var vnode = {tag: component, attrs: {id: "a"}}
+ var updated = {tag: component, attrs: {id: "b"}}
+
+ render(root, [vnode])
+ render(root, [updated])
+
+ function onbeforeupdate(vnode, old) {
+ count++
+ return true
+ }
+
+ o(count).equals(1)
+ })
+ })
})
-})
+})
\ No newline at end of file
diff --git a/render/tests/test-onremove.js b/render/tests/test-onremove.js
index d3f423fc..a7f88a6b 100644
--- a/render/tests/test-onremove.js
+++ b/render/tests/test-onremove.js
@@ -1,6 +1,7 @@
"use strict"
var o = require("../../ospec/ospec")
+var components = require("../../test-utils/components")
var domMock = require("../../test-utils/domMock")
var vdom = require("../../render/render")
var m = require("../../render/hyperscript")
@@ -80,39 +81,6 @@ o.spec("onremove", function() {
o(remove.this).equals(vnode.state)
o(remove.args[0]).equals(vnode)
})
- o("calls onremove on nested component", function() {
- var spy = o.spy()
- var comp = {
- view: function() {return m(outer)}
- }
- var outer = {
- view: function() {return m(inner)}
- }
- var inner = {
- onremove: spy,
- view: function() {return m("div")}
- }
- render(root, {tag: comp})
- render(root, null)
-
- o(spy.callCount).equals(1)
- })
- o("calls onremove on nested component child", function() {
- var spy = o.spy()
- var comp = {
- view: function() {return m(outer)}
- }
- var outer = {
- view: function() {return m(inner, m("a", {onremove: spy}))}
- }
- var inner = {
- view: function(vnode) {return m("div", vnode.children)}
- }
- render(root, {tag: comp})
- render(root, null)
-
- o(spy.callCount).equals(1)
- })
o("does not set onremove as an event handler", function() {
var remove = o.spy()
var vnode = {tag: "div", attrs: {onremove: remove}, children: []}
@@ -145,4 +113,43 @@ o.spec("onremove", function() {
o(vnode.dom).notEquals(updated.dom)
})
-})
+ components.forEach(function(cmp){
+ o.spec(cmp.kind, function(){
+ var createComponent = cmp.create
+
+ o("calls onremove on nested component", function() {
+ var spy = o.spy()
+ var comp = createComponent({
+ view: function() {return m(outer)}
+ })
+ var outer = createComponent({
+ view: function() {return m(inner)}
+ })
+ var inner = createComponent({
+ onremove: spy,
+ view: function() {return m("div")}
+ })
+ render(root, {tag: comp})
+ render(root, null)
+
+ o(spy.callCount).equals(1)
+ })
+ o("calls onremove on nested component child", function() {
+ var spy = o.spy()
+ var comp = createComponent({
+ view: function() {return m(outer)}
+ })
+ var outer = createComponent({
+ view: function() {return m(inner, m("a", {onremove: spy}))}
+ })
+ var inner = createComponent({
+ view: function(vnode) {return m("div", vnode.children)}
+ })
+ render(root, {tag: comp})
+ render(root, null)
+
+ o(spy.callCount).equals(1)
+ })
+ })
+ })
+})
\ No newline at end of file
diff --git a/render/tests/test-render.js b/render/tests/test-render.js
index 2f3ebb9a..82e5ddba 100644
--- a/render/tests/test-render.js
+++ b/render/tests/test-render.js
@@ -1,6 +1,7 @@
"use strict"
var o = require("../../ospec/ospec")
+var components = require("../../test-utils/components")
var domMock = require("../../test-utils/domMock")
var vdom = require("../../render/render")
@@ -118,7 +119,7 @@ o.spec("render", function() {
o(oninit.callCount).equals(0)
o(onbeforeupdate.callCount).equals(0)
})
- o("does not try to re-initialize a factory component whose view has thrown", function() {
+ o("does not try to re-initialize a closure component whose view has thrown", function() {
var oninit = o.spy()
var onbeforeupdate = o.spy()
function A() {
@@ -141,7 +142,7 @@ o.spec("render", function() {
o(oninit.callCount).equals(1)
o(onbeforeupdate.callCount).equals(0)
})
- o("does not try to re-initialize a factory component whose oninit has thrown", function() {
+ o("does not try to re-initialize a closure component whose oninit has thrown", function() {
var oninit = o.spy(function(vnode) {throw new Error("error")})
var onbeforeupdate = o.spy()
function A() {
@@ -164,7 +165,7 @@ o.spec("render", function() {
o(oninit.callCount).equals(1)
o(onbeforeupdate.callCount).equals(0)
})
- o("does not try to re-initialize a factory component whose factory has thrown", function() {
+ o("does not try to re-initialize a closure component whose closure has thrown", function() {
function A() {
throw new Error("error")
}
diff --git a/render/tests/test-updateNodes.js b/render/tests/test-updateNodes.js
index b0e8c337..c58a29b4 100644
--- a/render/tests/test-updateNodes.js
+++ b/render/tests/test-updateNodes.js
@@ -1,6 +1,7 @@
"use strict"
var o = require("../../ospec/ospec")
+var components = require("../../test-utils/components")
var domMock = require("../../test-utils/domMock")
var vdom = require("../../render/render")
@@ -838,38 +839,6 @@ o.spec("updateNodes", function() {
o(root.childNodes[0].nodeName).equals("A")
o(root.childNodes[1].nodeName).equals("B")
})
- o("fragment child toggles from null when followed by null component then tag", function() {
- var component = {view: function() {return null}}
- var vnodes = [{tag: "[", children: [{tag: "a"}, {tag: component}, {tag: "b"}]}]
- var temp = [{tag: "[", children: [null, {tag: component}, {tag: "b"}]}]
- var updated = [{tag: "[", children: [{tag: "a"}, {tag: component}, {tag: "b"}]}]
-
- render(root, vnodes)
- render(root, temp)
- render(root, updated)
-
- o(root.childNodes.length).equals(2)
- o(root.childNodes[0].nodeName).equals("A")
- o(root.childNodes[1].nodeName).equals("B")
- })
- o("fragment child toggles from null in component when followed by null component then tag", function() {
- var flag = true
- var a = {view: function() {return flag ? {tag: "a"} : null}}
- var b = {view: function() {return null}}
- var vnodes = [{tag: "[", children: [{tag: a}, {tag: b}, {tag: "s"}]}]
- var temp = [{tag: "[", children: [{tag: a}, {tag: b}, {tag: "s"}]}]
- var updated = [{tag: "[", children: [{tag: a}, {tag: b}, {tag: "s"}]}]
-
- render(root, vnodes)
- flag = false
- render(root, temp)
- flag = true
- render(root, updated)
-
- o(root.childNodes.length).equals(2)
- o(root.childNodes[0].nodeName).equals("A")
- o(root.childNodes[1].nodeName).equals("S")
- })
o("cached, non-keyed nodes skip diff", function () {
var onupdate = o.spy();
var cached = {tag:"a", attrs:{onupdate: onupdate}}
@@ -926,7 +895,7 @@ o.spec("updateNodes", function() {
o(update.callCount).equals(2)
o(remove.callCount).equals(0)
})
- o("component is recreated if key changes to undefined", function () {
+ o("node is recreated if key changes to undefined", function () {
var vnode = {tag: "b", key: 1}
var updated = {tag: "b"}
@@ -936,4 +905,42 @@ o.spec("updateNodes", function() {
o(vnode.dom).notEquals(updated.dom)
})
-})
+ components.forEach(function(cmp){
+ o.spec(cmp.kind, function(){
+ var createComponent = cmp.create
+
+ o("fragment child toggles from null when followed by null component then tag", function() {
+ var component = createComponent({view: function() {return null}})
+ var vnodes = [{tag: "[", children: [{tag: "a"}, {tag: component}, {tag: "b"}]}]
+ var temp = [{tag: "[", children: [null, {tag: component}, {tag: "b"}]}]
+ var updated = [{tag: "[", children: [{tag: "a"}, {tag: component}, {tag: "b"}]}]
+
+ render(root, vnodes)
+ render(root, temp)
+ render(root, updated)
+
+ o(root.childNodes.length).equals(2)
+ o(root.childNodes[0].nodeName).equals("A")
+ o(root.childNodes[1].nodeName).equals("B")
+ })
+ o("fragment child toggles from null in component when followed by null component then tag", function() {
+ var flag = true
+ var a = createComponent({view: function() {return flag ? {tag: "a"} : null}})
+ var b = createComponent({view: function() {return null}})
+ var vnodes = [{tag: "[", children: [{tag: a}, {tag: b}, {tag: "s"}]}]
+ var temp = [{tag: "[", children: [{tag: a}, {tag: b}, {tag: "s"}]}]
+ var updated = [{tag: "[", children: [{tag: a}, {tag: b}, {tag: "s"}]}]
+
+ render(root, vnodes)
+ flag = false
+ render(root, temp)
+ flag = true
+ render(root, updated)
+
+ o(root.childNodes.length).equals(2)
+ o(root.childNodes[0].nodeName).equals("A")
+ o(root.childNodes[1].nodeName).equals("S")
+ })
+ })
+ })
+})
\ No newline at end of file
diff --git a/test-utils/components.js b/test-utils/components.js
new file mode 100644
index 00000000..c25ad5da
--- /dev/null
+++ b/test-utils/components.js
@@ -0,0 +1,27 @@
+module.exports = [
+ {
+ kind: 'POJO',
+ create: function(methods) {
+ var res = {view: function() {return {tag:'div'}}}
+ Object.keys(methods || {}).forEach(function(m){res[m] = methods[m]})
+ return res
+ }
+ }, {
+ kind: 'constructible',
+ create: function(methods) {
+ function res(){}
+ res.prototype.view = function() {return {tag:'div'}}
+ Object.keys(methods || {}).forEach(function(m){res.prototype[m] = methods[m]})
+ return res
+ }
+ }, {
+ kind: 'closure',
+ create: function(methods) {
+ return function() {
+ var res = {view: function() {return {tag:'div'}}}
+ Object.keys(methods || {}).forEach(function(m){res[m] = methods[m]})
+ return res
+ }
+ }
+ }
+]
diff --git a/test-utils/tests/index.html b/test-utils/tests/index.html
index e24fa2f8..51b04d73 100644
--- a/test-utils/tests/index.html
+++ b/test-utils/tests/index.html
@@ -14,12 +14,14 @@
+
+