- the previous test only remove last item on array, but expect all subcomponent's onunload to be called. This PR change it by clear that array. - uncomment previous test case that marked fail.
798 lines
17 KiB
JavaScript
798 lines
17 KiB
JavaScript
describe("m.mount()", function () {
|
|
"use strict"
|
|
|
|
// This is a frequent idiom
|
|
function refresh(force) {
|
|
m.redraw(!!force)
|
|
mock.requestAnimationFrame.$resolve()
|
|
}
|
|
|
|
function clear(root) {
|
|
m.mount(root, null)
|
|
mock.requestAnimationFrame.$resolve()
|
|
}
|
|
|
|
function mount(root, mod) {
|
|
var res = m.mount(root, mod)
|
|
mock.requestAnimationFrame.$resolve()
|
|
return res
|
|
}
|
|
|
|
// This is extremely frequent in the tests
|
|
function pure(view) {
|
|
return {
|
|
controller: function () {},
|
|
view: view
|
|
}
|
|
}
|
|
|
|
it("exists", function () {
|
|
expect(m.mount).to.be.a("function")
|
|
})
|
|
|
|
it("mounts onto the root", function () {
|
|
var root = mock.document.createElement("div")
|
|
var whatever = 1
|
|
|
|
var app = pure(function () {
|
|
return [
|
|
whatever % 2 ? m("span", "% 2") : undefined,
|
|
m("div", "bugs"),
|
|
m("a")
|
|
]
|
|
})
|
|
|
|
mount(root, app)
|
|
|
|
whatever++
|
|
refresh()
|
|
|
|
whatever++
|
|
refresh()
|
|
|
|
expect(root.childNodes).to.have.length.above(0)
|
|
})
|
|
|
|
it("reloads components correctly", function () {
|
|
mock.requestAnimationFrame.$resolve()
|
|
|
|
var root1 = mock.document.createElement("div")
|
|
var controller1 = sinon.spy(function () { this.value = "test1" }) // eslint-disable-line
|
|
var view1 = sinon.stub().returns("test1")
|
|
|
|
var mod1 = m.mount(root1, {
|
|
controller: controller1,
|
|
view: view1
|
|
})
|
|
|
|
var controller2 = sinon.spy(function () { this.value = "test2" }) // eslint-disable-line
|
|
var view2 = sinon.stub().returns("test2")
|
|
var root2 = mock.document.createElement("div")
|
|
|
|
var mod2 = mount(root2, {
|
|
controller: controller2,
|
|
view: view2
|
|
})
|
|
|
|
expect(controller1).to.have.been.called
|
|
expect(view1).to.have.been.called
|
|
expect(controller2).to.have.been.called
|
|
expect(view2).to.have.been.called
|
|
|
|
expect(root1.childNodes[0].nodeValue).to.equal("test1")
|
|
expect(root2.childNodes[0].nodeValue).to.equal("test2")
|
|
expect(mod1).to.have.property("value", "test1")
|
|
expect(mod2).to.have.property("value", "test2")
|
|
})
|
|
|
|
it("triggers an unload when the element is removed", function () {
|
|
mock.requestAnimationFrame.$resolve()
|
|
|
|
var root = mock.document.createElement("div")
|
|
var spy = sinon.spy()
|
|
|
|
mount(root, {
|
|
controller: function () {
|
|
this.onunload = spy
|
|
},
|
|
view: function () {}
|
|
})
|
|
|
|
clear(root)
|
|
|
|
expect(spy).to.have.been.called
|
|
})
|
|
|
|
it("passes the args to both component & view", function () {
|
|
mock.requestAnimationFrame.$resolve()
|
|
|
|
var root = mock.document.createElement("div")
|
|
var ctrlSpy = sinon.spy()
|
|
var viewSpy = sinon.stub().returns(m("div"))
|
|
|
|
var component = {
|
|
controller: ctrlSpy,
|
|
view: viewSpy
|
|
}
|
|
|
|
var arg = {}
|
|
|
|
mount(root, m.component(component, arg))
|
|
|
|
expect(ctrlSpy).to.have.been.calledWith(arg)
|
|
expect(viewSpy.firstCall.args[1]).to.equal(arg)
|
|
})
|
|
|
|
it("mounts a component without a controller", function () {
|
|
mock.requestAnimationFrame.$resolve()
|
|
|
|
var root = mock.document.createElement("div")
|
|
var arg = {}
|
|
var spy = sinon.spy()
|
|
|
|
var component = pure(spy)
|
|
|
|
mount(root, m.component(component, arg))
|
|
|
|
expect(spy.firstCall.args[1]).to.equal(arg)
|
|
})
|
|
|
|
it("only runs a component controller once", function () {
|
|
mock.requestAnimationFrame.$resolve()
|
|
|
|
var root = mock.document.createElement("div")
|
|
var ctrlSpy = sinon.spy()
|
|
var viewSpy = sinon.stub().returns(m("div"))
|
|
|
|
var sub = {
|
|
controller: ctrlSpy,
|
|
view: viewSpy
|
|
}
|
|
|
|
mount(root, pure(function () { return sub }))
|
|
|
|
refresh(true)
|
|
|
|
expect(ctrlSpy).to.have.been.calledOnce
|
|
expect(viewSpy).to.have.been.calledTwice
|
|
})
|
|
|
|
it("only runs a subcomponent controller once", function () {
|
|
mock.requestAnimationFrame.$resolve()
|
|
|
|
var root = mock.document.createElement("div")
|
|
var ctrl1 = sinon.spy()
|
|
var view1 = sinon.stub().returns(m("div"))
|
|
|
|
var subsub = {
|
|
controller: ctrl1,
|
|
view: view1
|
|
}
|
|
|
|
var ctrl2 = sinon.spy()
|
|
var view2 = sinon.stub().returns(subsub)
|
|
|
|
var sub = {
|
|
controller: ctrl2,
|
|
view: view2
|
|
}
|
|
|
|
mount(root, pure(function () { return sub }))
|
|
|
|
refresh(true)
|
|
|
|
expect(ctrl1).to.have.been.calledOnce
|
|
expect(ctrl2).to.have.been.calledOnce
|
|
expect(view1).to.have.been.calledTwice
|
|
expect(view2).to.have.been.calledTwice
|
|
})
|
|
|
|
it("addresses keys in components", function () {
|
|
mock.requestAnimationFrame.$resolve()
|
|
|
|
var root = mock.document.createElement("div")
|
|
var list = [1, 2, 3]
|
|
|
|
var sub = pure(function () { return m("div") })
|
|
|
|
m.mount(root, pure(function () {
|
|
return list.map(function (i) {
|
|
return m.component(sub, {key: i})
|
|
})
|
|
}))
|
|
|
|
var firstBefore = root.childNodes[0]
|
|
|
|
mock.requestAnimationFrame.$resolve()
|
|
|
|
list.reverse()
|
|
refresh(true)
|
|
|
|
expect(root.childNodes[2]).to.equal(firstBefore)
|
|
})
|
|
|
|
it("addresses keys in subcomponents correctly", function () {
|
|
mock.requestAnimationFrame.$resolve()
|
|
|
|
var root = mock.document.createElement("div")
|
|
var list = [1, 2, 3]
|
|
|
|
var subsub = pure(function () { return m("div") })
|
|
|
|
var sub = pure(function () { return subsub })
|
|
|
|
m.mount(root, pure(function () {
|
|
return list.map(function (i) {
|
|
return m.component(sub, {key: i})
|
|
})
|
|
}))
|
|
|
|
var firstBefore = root.childNodes[0]
|
|
|
|
mock.requestAnimationFrame.$resolve()
|
|
|
|
list.reverse()
|
|
refresh(true)
|
|
|
|
expect(root.childNodes[2]).to.equal(firstBefore)
|
|
})
|
|
|
|
it("is error resistant with keys in components", function () {
|
|
mock.requestAnimationFrame.$resolve()
|
|
|
|
var root = mock.document.createElement("div")
|
|
var list = [1, 2, 3]
|
|
|
|
var sub = pure(function () { return m("div", {key: 1}) })
|
|
|
|
m.mount(root, pure(function () {
|
|
return list.map(function (i) {
|
|
return m.component(sub, {key: i})
|
|
})
|
|
}))
|
|
|
|
var firstBefore = root.childNodes[0]
|
|
|
|
mock.requestAnimationFrame.$resolve()
|
|
|
|
list.reverse()
|
|
refresh(true)
|
|
|
|
expect(root.childNodes[2]).to.equal(firstBefore)
|
|
})
|
|
|
|
it("is error resistant with keys in subcomponents", function () {
|
|
mock.requestAnimationFrame.$resolve()
|
|
|
|
var root = mock.document.createElement("div")
|
|
var list = [1, 2, 3]
|
|
|
|
var subsub = pure(function () { return m("div", {key: 1}) })
|
|
|
|
var sub = pure(function () { return subsub })
|
|
|
|
m.mount(root, pure(function () {
|
|
return list.map(function (i) {
|
|
return m.component(sub, {key: i})
|
|
})
|
|
}))
|
|
|
|
var firstBefore = root.childNodes[0]
|
|
|
|
mock.requestAnimationFrame.$resolve()
|
|
|
|
list.reverse()
|
|
refresh(true)
|
|
|
|
expect(root.childNodes[2]).to.equal(firstBefore)
|
|
})
|
|
|
|
it("retains subcomponent identity if child of keyed element", function () {
|
|
mock.requestAnimationFrame.$resolve()
|
|
|
|
var root = mock.document.createElement("div")
|
|
var list = [1, 2, 3]
|
|
|
|
var sub = pure(function () { return m("div") })
|
|
|
|
m.mount(root, pure(function () {
|
|
return list.map(function (i) {
|
|
return m("div", {key: i}, sub)
|
|
})
|
|
}))
|
|
|
|
var firstBefore = root.childNodes[0].childNodes[0]
|
|
|
|
mock.requestAnimationFrame.$resolve()
|
|
|
|
list.reverse()
|
|
refresh(true)
|
|
|
|
expect(root.childNodes[2].childNodes[0]).to.equal(firstBefore)
|
|
})
|
|
|
|
it("calls component onunload when removed from template", function () {
|
|
mock.requestAnimationFrame.$resolve()
|
|
|
|
var root = mock.document.createElement("div")
|
|
var list = [1, 2, 3]
|
|
var spies = []
|
|
|
|
var sub = {
|
|
controller: function (opts) {
|
|
this.onunload = spies[opts.key] = sinon.spy()
|
|
},
|
|
view: function () {
|
|
return m("div")
|
|
}
|
|
}
|
|
|
|
mount(root, pure(function () {
|
|
return list.map(function (i) {
|
|
return m.component(sub, {key: i})
|
|
})
|
|
}))
|
|
|
|
list = []
|
|
refresh(true)
|
|
|
|
expect(spies[1]).to.have.been.called
|
|
expect(spies[2]).to.have.been.called
|
|
expect(spies[3]).to.have.been.called
|
|
})
|
|
|
|
it("calls subcomponent onunload when removed from template", function () {
|
|
mock.requestAnimationFrame.$resolve()
|
|
|
|
var root = mock.document.createElement("div")
|
|
var list = [1, 2, 3]
|
|
var spies1 = []
|
|
var spies2 = []
|
|
|
|
var subsub = {
|
|
controller: function (opts) {
|
|
this.onunload = spies1[opts.key] = sinon.spy()
|
|
},
|
|
view: function () {
|
|
return m("div")
|
|
}
|
|
}
|
|
|
|
var sub = {
|
|
controller: function (opts) {
|
|
this.onunload = spies2[opts.key] = sinon.spy()
|
|
},
|
|
view: function (ctrl, opts) {
|
|
return m.component(subsub, {key: opts.key})
|
|
}
|
|
}
|
|
|
|
mount(root, pure(function () {
|
|
return list.map(function (i) {
|
|
return m.component(sub, {key: i})
|
|
})
|
|
}))
|
|
|
|
list = []
|
|
refresh(true)
|
|
|
|
expect(spies1[1]).to.have.been.called
|
|
expect(spies1[2]).to.have.been.called
|
|
expect(spies1[3]).to.have.been.called
|
|
|
|
expect(spies2[1]).to.have.been.called
|
|
expect(spies2[2]).to.have.been.called
|
|
expect(spies2[3]).to.have.been.called
|
|
})
|
|
|
|
it("doesn't redraw if m.render() is called by controller constructor", function () { // eslint-disable-line
|
|
mock.requestAnimationFrame.$resolve()
|
|
|
|
var root = mock.document.createElement("div")
|
|
var spy = sinon.stub().returns(m("div"))
|
|
|
|
var sub = {
|
|
controller: function () {
|
|
m.redraw()
|
|
},
|
|
view: spy
|
|
}
|
|
|
|
mount(root, pure(function () { return sub }))
|
|
|
|
expect(spy).to.have.been.called
|
|
})
|
|
|
|
it("doesn't redraw if m.render() is called by subcomponent controller constructor", function () { // eslint-disable-line
|
|
mock.requestAnimationFrame.$resolve()
|
|
|
|
var root = mock.document.createElement("div")
|
|
var spy = sinon.stub().returns(m("div"))
|
|
|
|
var subsub = {
|
|
controller: function () {
|
|
m.redraw()
|
|
},
|
|
view: spy
|
|
}
|
|
|
|
var sub = pure(function () { return subsub })
|
|
|
|
mount(root, pure(function () { return sub }))
|
|
|
|
expect(spy).to.have.been.called
|
|
})
|
|
|
|
it("renders nested components under keyed components", function () {
|
|
mock.requestAnimationFrame.$resolve()
|
|
|
|
var root = mock.document.createElement("div")
|
|
var spy = sinon.stub().returns(m(".reply"))
|
|
|
|
var Reply = pure(spy)
|
|
|
|
var CommentList = pure(function (ctrl, props) {
|
|
return m(".list", props.list.map(function (i) {
|
|
return m(".comment", [
|
|
m.component(Reply, {key: i})
|
|
])
|
|
}))
|
|
})
|
|
|
|
mount(root, pure(function () {
|
|
return m(".outer", [
|
|
m(".inner", m.component(CommentList, {list: [1, 2, 3]}))
|
|
])
|
|
}))
|
|
|
|
expect(spy).to.have.been.calledThrice
|
|
})
|
|
|
|
it("calls unload when the component is replaced with another component", function () { // eslint-disable-line
|
|
var root = mock.document.createElement("div")
|
|
var spy = sinon.spy()
|
|
|
|
m.mount(root, {
|
|
controller: function () {
|
|
this.onunload = spy
|
|
},
|
|
view: function () {}
|
|
})
|
|
|
|
m.mount(root, pure(function () {}))
|
|
|
|
expect(spy).to.have.been.called
|
|
})
|
|
|
|
it("calls config with truthy init only once", function () {
|
|
mock.requestAnimationFrame.$resolve()
|
|
|
|
var root = mock.document.createElement("div")
|
|
var count = 0
|
|
|
|
mount(root, pure(function () {
|
|
return m("div", {
|
|
config: function (el, init) {
|
|
if (init) count += 1
|
|
}
|
|
})
|
|
}))
|
|
|
|
refresh()
|
|
|
|
expect(count).to.equal(1)
|
|
})
|
|
|
|
it("doesn't recreate node that modifies DOM in config, but stays same between redraws", function () { // eslint-disable-line
|
|
var root = mock.document.createElement("div")
|
|
var child = mock.document.createElement("div")
|
|
|
|
var show = true
|
|
|
|
function test(el, init) {
|
|
if (!init) {
|
|
root.appendChild(child)
|
|
}
|
|
}
|
|
|
|
mount(root, pure(function () {
|
|
return [
|
|
m(".foo", {
|
|
key: 1,
|
|
config: test,
|
|
onclick: function () { show = !show }
|
|
}),
|
|
show ? m(".bar", {key: 2}) : null
|
|
]
|
|
}))
|
|
|
|
show = false
|
|
refresh()
|
|
|
|
show = true
|
|
refresh()
|
|
|
|
expect(root.childNodes).to.have.length(3)
|
|
})
|
|
|
|
it("correctly replaces nodes", function () {
|
|
var root = mock.document.createElement("div")
|
|
var show = true
|
|
|
|
var sub = pure(function () { return m("div", "component") })
|
|
|
|
mount(root, pure(function () {
|
|
return show ? [
|
|
m("h1", "1"),
|
|
sub
|
|
] : [
|
|
m("h1", "2")
|
|
]
|
|
}))
|
|
|
|
show = false
|
|
refresh()
|
|
|
|
show = true
|
|
refresh()
|
|
|
|
expect(root.childNodes).to.have.length(2)
|
|
})
|
|
|
|
// https://github.com/lhorie/mithril.js/issues/551
|
|
it("only redraws a component when clicked", function () {
|
|
var root = mock.document.createElement("div")
|
|
var a = false
|
|
var found = {}
|
|
|
|
var onunload = sinon.spy()
|
|
var view = sinon.spy(function () {
|
|
return m("div", {config: Comp.config}, [ // eslint-disable-line
|
|
m("div", {
|
|
onclick: function () {
|
|
a = !a
|
|
m.redraw(true)
|
|
found = root.childNodes[0].childNodes[1]
|
|
}
|
|
}, "asd"),
|
|
a ? m("#a", "aaa") : null,
|
|
"test"
|
|
])
|
|
})
|
|
|
|
var Comp = {
|
|
view: view,
|
|
|
|
config: function (el, init, ctx) {
|
|
if (!init) ctx.onunload = onunload
|
|
}
|
|
}
|
|
|
|
m.mount(root, pure(function () { return Comp }))
|
|
|
|
var target = root.childNodes[0].childNodes[0]
|
|
|
|
target.onclick({currentTarget: target})
|
|
mock.requestAnimationFrame.$resolve()
|
|
|
|
expect(onunload).to.not.be.called
|
|
expect(found).to.have.property("id", "a")
|
|
expect(view).to.have.been.calledThrice
|
|
})
|
|
|
|
// https://github.com/lhorie/mithril.js/issues/551
|
|
it("only redraws a component when clicked if the strategy is `none`", function () { // eslint-disable-line
|
|
var root = mock.document.createElement("div")
|
|
var a = false
|
|
var found = {}
|
|
|
|
var onunload = sinon.spy()
|
|
var view = sinon.spy(function () {
|
|
return m("div", {config: Comp.config}, [ // eslint-disable-line
|
|
m("div", {
|
|
onclick: function () {
|
|
a = !a
|
|
m.redraw(true)
|
|
found = root.childNodes[0].childNodes[1]
|
|
m.redraw.strategy("none")
|
|
}
|
|
}, "asd"),
|
|
a ? m("#a", "aaa") : null,
|
|
"test"
|
|
])
|
|
})
|
|
|
|
var Comp = {
|
|
view: view,
|
|
|
|
config: function (el, init, ctx) {
|
|
if (!init) ctx.onunload = onunload
|
|
}
|
|
}
|
|
|
|
m.mount(root, pure(function () { return Comp }))
|
|
|
|
var target = root.childNodes[0].childNodes[0]
|
|
|
|
target.onclick({currentTarget: target})
|
|
mock.requestAnimationFrame.$resolve()
|
|
|
|
expect(onunload).to.not.be.called
|
|
expect(found).to.have.property("id", "a")
|
|
expect(view).to.have.been.calledTwice
|
|
})
|
|
|
|
it("redraws when clicked and click handler forces redraw", function () {
|
|
var root = mock.document.createElement("div")
|
|
var view = sinon.stub().returns(m("div", {
|
|
onclick: function () { m.redraw(true) }
|
|
}))
|
|
|
|
m.mount(root, pure(view))
|
|
|
|
var target = root.childNodes[0]
|
|
|
|
target.onclick({currentTarget: target})
|
|
mock.requestAnimationFrame.$resolve()
|
|
|
|
expect(view).to.be.calledThrice
|
|
})
|
|
|
|
function resolveXhr() {
|
|
mock.XMLHttpRequest.$instances.pop().$resolve().onreadystatechange()
|
|
mock.requestAnimationFrame.$resolve()
|
|
}
|
|
|
|
it("doesn't redraw on a single synchronous request", function () {
|
|
var root = mock.document.createElement("div")
|
|
|
|
var data
|
|
var view = sinon.spy(function (ctrl) {
|
|
data = ctrl.foo()
|
|
return m("div")
|
|
})
|
|
|
|
var Comp = {
|
|
controller: function () {
|
|
this.foo = m.request({method: "GET", url: "/foo"})
|
|
},
|
|
|
|
view: view
|
|
}
|
|
|
|
mount(root, pure(function () { return Comp }))
|
|
|
|
resolveXhr()
|
|
|
|
clear(root)
|
|
|
|
expect(view).to.be.calledOnce
|
|
expect(data).to.have.property("url", "/foo")
|
|
})
|
|
|
|
it("doesn't redraw on multiple synchronous requests", function () {
|
|
mock.requestAnimationFrame.$resolve()
|
|
mock.location.search = "?"
|
|
|
|
var root = mock.document.createElement("div")
|
|
var view1 = sinon.stub().returns(m("div"))
|
|
var view2 = sinon.stub().returns(m("div"))
|
|
|
|
var Comp1 = {
|
|
controller: function () {
|
|
this.foo = m.request({method: "GET", url: "/foo"})
|
|
},
|
|
view: view1
|
|
}
|
|
|
|
var Comp2 = {
|
|
controller: function () {
|
|
this.bar = m.request({method: "GET", url: "/bar"})
|
|
},
|
|
view: view2
|
|
}
|
|
|
|
mount(root, pure(function () {
|
|
return m("div", [
|
|
Comp1,
|
|
Comp2
|
|
])
|
|
}))
|
|
|
|
resolveXhr()
|
|
resolveXhr()
|
|
|
|
clear(root)
|
|
|
|
expect(view1).to.be.calledOnce
|
|
expect(view2).to.be.calledOnce
|
|
})
|
|
|
|
it("instantiates different controllers for components without controller constructors", function () { // eslint-disable-line
|
|
var root = mock.document.createElement("div")
|
|
|
|
var cond = true
|
|
var controller1, controller2
|
|
|
|
var Comp1 = pure(function (ctrl) {
|
|
controller1 = ctrl
|
|
return m("div")
|
|
})
|
|
|
|
var Comp2 = pure(function (ctrl) {
|
|
controller2 = ctrl
|
|
return m("div")
|
|
})
|
|
|
|
mount(root, pure(function () { return cond ? Comp1 : Comp2 }))
|
|
|
|
cond = false
|
|
refresh(true)
|
|
|
|
expect(controller1).to.not.equal(controller2)
|
|
})
|
|
|
|
it("unloads removed components", function () {
|
|
var root = mock.document.createElement("div")
|
|
|
|
var onunload = sinon.spy()
|
|
var cond = true
|
|
|
|
var Comp1 = pure(function () {
|
|
return m("div", {
|
|
config: function (el, init, ctx) {
|
|
ctx.onunload = onunload
|
|
}
|
|
})
|
|
})
|
|
|
|
var Comp2 = pure(function () { return m("div") })
|
|
|
|
mount(root, pure(function () { return cond ? Comp1 : Comp2 }))
|
|
|
|
cond = false
|
|
refresh(true)
|
|
|
|
expect(onunload).to.be.called
|
|
})
|
|
|
|
it("calls config with its second argument false first", function () {
|
|
var root = mock.document.createElement("div")
|
|
|
|
var cond = true
|
|
var config = sinon.spy()
|
|
|
|
var Comp1 = pure(function () { return m("div") })
|
|
var Comp2 = pure(function () { return m("div", {config: config}) })
|
|
|
|
mount(root, pure(function () { return cond ? Comp1 : Comp2 }))
|
|
|
|
cond = false
|
|
refresh(true)
|
|
|
|
expect(config.firstCall.args[1]).to.be.false
|
|
})
|
|
|
|
it("refreshes the component when it's redrawn in a handler", function () {
|
|
var root = mock.document.createElement("div")
|
|
var sub = pure(function () { return m("#bar", "test") })
|
|
var el
|
|
|
|
m.mount(root, pure(function (ctrl) {
|
|
return m("div", [
|
|
m("button", {
|
|
onclick: function () {
|
|
ctrl.bar = true
|
|
m.redraw(true)
|
|
el = root.childNodes[0].childNodes[1]
|
|
}
|
|
}, "click me"),
|
|
ctrl.bar ? m.component(sub) : ""
|
|
])
|
|
}))
|
|
|
|
root.childNodes[0].childNodes[0].onclick({})
|
|
|
|
expect(el).to.have.property("id", "bar")
|
|
})
|
|
})
|