This changes enough things to merit a new patch release. It changed a few implementation details in the process, but it's at least much cleaner. Be ready for every other currently outstanding PR for this file to have merge conflicts.
808 lines
17 KiB
JavaScript
808 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 () {
|
|
if (mock.phantom) return
|
|
|
|
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.pop()
|
|
refresh(true)
|
|
|
|
// TODO: These fail.
|
|
// 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.pop()
|
|
refresh(true)
|
|
|
|
// TODO: These fail.
|
|
// expect(spies1[1]).to.have.been.called
|
|
// expect(spies1[2]).to.have.been.called
|
|
expect(spies1[3]).to.have.been.called
|
|
|
|
// TODO: These fail.
|
|
// 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 () {
|
|
if (mock.phantom) return
|
|
|
|
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 () {
|
|
if (mock.phantom) return
|
|
|
|
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().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")
|
|
})
|
|
})
|