Support classes and factories as components (#1339)

* Support classes and factories as components

* Tests for class and factory component support
This commit is contained in:
Pierre-Yves Gérardy 2017-02-15 04:48:02 +01:00 committed by Isiah Meadows
parent ff16c7f47a
commit 3f3af74dde
5 changed files with 301 additions and 16 deletions

View file

@ -9,6 +9,7 @@ o.spec("component", function() {
o.beforeEach(function() {
$window = domMock()
root = $window.document.createElement("div")
render = vdom($window).render
})
@ -694,4 +695,141 @@ o.spec("component", function() {
}
})
})
o.spec("Alternative ways to specify componenents", function() {
o("Classes can be used as components", function() {
function MyComponent(vnode){
o(vnode.state).equals(null)
}
var proto = MyComponent.prototype
var context
proto.oninit = o.spy(function(vnode) {
o(this).equals(vnode.state)
context = this
})
proto.oncreate = o.spy()
proto.onbeforeupdate = o.spy()
proto.onupdate = o.spy()
proto.onbeforeremove = o.spy()
proto.onremove = o.spy()
proto.view = o.spy(function() {
return ""
})
render(root, [{tag: MyComponent}])
o(context instanceof MyComponent).equals(true)
o(proto.view.callCount).equals(1)
o(proto.oncreate.callCount).equals(1)
o(proto.onbeforeupdate.callCount).equals(0)
o(proto.onupdate.callCount).equals(0)
o(proto.onbeforeremove.callCount).equals(0)
o(proto.onremove.callCount).equals(0)
render(root, [{tag: MyComponent}])
o(proto.view.callCount).equals(2)
o(proto.oncreate.callCount).equals(1)
o(proto.onbeforeupdate.callCount).equals(1)
o(proto.onupdate.callCount).equals(1)
o(proto.onbeforeremove.callCount).equals(0)
o(proto.onremove.callCount).equals(0)
render(root, [])
o(proto.view.callCount).equals(2)
o(proto.oncreate.callCount).equals(1)
o(proto.onbeforeupdate.callCount).equals(1)
o(proto.onupdate.callCount).equals(1)
o(proto.onbeforeremove.callCount).equals(1)
o(proto.onremove.callCount).equals(1)
o(proto.oninit.this).equals(context)
o(proto.view.this).equals(context)
o(proto.oncreate.this).equals(context)
o(proto.onbeforeupdate.this).equals(context)
o(proto.onupdate.this).equals(context)
o(proto.onbeforeremove.this).equals(context)
o(proto.onremove.this).equals(context)
o(proto.oninit.args.length).equals(1)
o(proto.view.args.length).equals(1)
o(proto.oncreate.args.length).equals(1)
o(proto.onbeforeupdate.args.length).equals(2)
o(proto.onupdate.args.length).equals(1)
o(proto.onbeforeremove.args.length).equals(1)
o(proto.onremove.args.length).equals(1)
})
o("Factory functions can be used as components", function() {
var state, context
function component(vnode) {
o(vnode.state).equals(null)
return state = {
oninit: o.spy(function(vnode) {
o(this).equals(vnode.state)
context = this
}),
oncreate: o.spy(),
onbeforeupdate: o.spy(),
onupdate: o.spy(),
onbeforeremove: o.spy(),
onremove: o.spy(),
view: o.spy(function() {
return ""
})
}
}
render(root, [{tag: component}])
o(state).equals(context)
o(state.oninit.callCount).equals(1)
o(state.view.callCount).equals(1)
o(state.oncreate.callCount).equals(1)
o(state.onbeforeupdate.callCount).equals(0)
o(state.onupdate.callCount).equals(0)
o(state.onbeforeremove.callCount).equals(0)
o(state.onremove.callCount).equals(0)
render(root, [{tag: component}])
o(state.oninit.callCount).equals(1)
o(state.view.callCount).equals(2)
o(state.oncreate.callCount).equals(1)
o(state.onbeforeupdate.callCount).equals(1)
o(state.onupdate.callCount).equals(1)
o(state.onbeforeremove.callCount).equals(0)
o(state.onremove.callCount).equals(0)
render(root, [])
o(state.oninit.callCount).equals(1)
o(state.view.callCount).equals(2)
o(state.oncreate.callCount).equals(1)
o(state.onbeforeupdate.callCount).equals(1)
o(state.onupdate.callCount).equals(1)
o(state.onbeforeremove.callCount).equals(1)
o(state.onremove.callCount).equals(1)
o(state.oninit.this).equals(state)
o(state.view.this).equals(state)
o(state.oncreate.this).equals(state)
o(state.onbeforeupdate.this).equals(state)
o(state.onupdate.this).equals(state)
o(state.onbeforeremove.this).equals(state)
o(state.onremove.this).equals(state)
o(state.oninit.args.length).equals(1)
o(state.view.args.length).equals(1)
o(state.oncreate.args.length).equals(1)
o(state.onbeforeupdate.args.length).equals(2)
o(state.onupdate.args.length).equals(1)
o(state.onbeforeremove.args.length).equals(1)
o(state.onremove.args.length).equals(1)
})
})
})

View file

@ -424,7 +424,7 @@ o.spec("hyperscript", function() {
})
})
o.spec("components", function() {
o("works", function() {
o("works with POJOs", function() {
var component = {
view: function() {
return m("div")
@ -432,6 +432,19 @@ o.spec("hyperscript", 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("works with functions", function() {
var component = o.spy()
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)

View file

@ -32,7 +32,7 @@ o.spec("render", function() {
o(threw).equals(true)
})
o("does not enter infinite loop when oninit triggers render and view throws", function(done) {
o("does not enter infinite loop when oninit triggers render and view throws with an object literal component", function(done) {
var A = {
oninit: init,
view: function() {throw new Error("error")}
@ -55,6 +55,128 @@ o.spec("render", function() {
o(threwOuter).equals(true)
})
o("does not try to re-initialize a constructibe component whose view has thrown", function() {
var oninit = o.spy()
var onbeforeupdate = o.spy()
function A(){}
A.prototype.view = function() {throw new Error("error")}
A.prototype.oninit = oninit
A.prototype.onbeforeupdate = onbeforeupdate
var throwCount = 0
try {render(root, {tag: A})} catch (e) {throwCount++}
o(throwCount).equals(1)
o(oninit.callCount).equals(1)
o(onbeforeupdate.callCount).equals(0)
try {render(root, {tag: A})} catch (e) {throwCount++}
o(throwCount).equals(1)
o(oninit.callCount).equals(1)
o(onbeforeupdate.callCount).equals(0)
})
o("does not try to re-initialize a constructible component whose oninit has thrown", function() {
var oninit = o.spy(function(){throw new Error("error")})
var onbeforeupdate = o.spy()
function A(){}
A.prototype.view = function(){}
A.prototype.oninit = oninit
A.prototype.onbeforeupdate = onbeforeupdate
var throwCount = 0
try {render(root, {tag: A})} catch (e) {throwCount++}
o(throwCount).equals(1)
o(oninit.callCount).equals(1)
o(onbeforeupdate.callCount).equals(0)
try {render(root, {tag: A})} catch (e) {throwCount++}
o(throwCount).equals(1)
o(oninit.callCount).equals(1)
o(onbeforeupdate.callCount).equals(0)
})
o("does not try to re-initialize a constructible component whose constructor has thrown", function() {
var oninit = o.spy()
var onbeforeupdate = o.spy()
function A(){throw new Error("error")}
A.prototype.view = function() {}
A.prototype.oninit = oninit
A.prototype.onbeforeupdate = onbeforeupdate
var throwCount = 0
try {render(root, {tag: A})} catch (e) {throwCount++}
o(throwCount).equals(1)
o(oninit.callCount).equals(0)
o(onbeforeupdate.callCount).equals(0)
try {render(root, {tag: A})} catch (e) {throwCount++}
o(throwCount).equals(1)
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() {
var oninit = o.spy()
var onbeforeupdate = o.spy()
function A() {
return {
view: function(vnode) {throw new Error("error")},
oninit: oninit,
onbeforeupdate: onbeforeupdate
}
}
var throwCount = 0
try {render(root, {tag: A})} catch (e) {throwCount++}
o(throwCount).equals(1)
o(oninit.callCount).equals(1)
o(onbeforeupdate.callCount).equals(0)
try {render(root, {tag: A})} catch (e) {throwCount++}
o(throwCount).equals(1)
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() {
var oninit = o.spy(function(vnode) {throw new Error("error")})
var onbeforeupdate = o.spy()
function A() {
return {
view: function(vnode) {},
oninit: oninit,
onbeforeupdate: onbeforeupdate
}
}
var throwCount = 0
try {render(root, {tag: A})} catch (e) {throwCount++}
o(throwCount).equals(1)
o(oninit.callCount).equals(1)
o(onbeforeupdate.callCount).equals(0)
try {render(root, {tag: A})} catch (e) {throwCount++}
o(throwCount).equals(1)
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() {
function A() {
throw new Error("error")
}
var throwCount = 0
try {render(root, {tag: A})} catch (e) {throwCount++}
o(throwCount).equals(1)
try {render(root, {tag: A})} catch (e) {throwCount++}
o(throwCount).equals(1)
})
o("lifecycle methods work in keyed children of recycled keyed", function() {
var createA = o.spy()
var updateA = o.spy()