* De-servicify router (mostly) Still uses the redraw service, but it no longer has an intermediate service of its own. Also, did a *lot* of test deduplication in this. About 30-40% of the router service tests were already tested on the main router API instance itself. Bundle size decreased from 9560 to 9548 bytes min+gzip. * Merge `m.mount` + `m.redraw`, update router Simplifies the router and redraw mechanism, and makes it much easier to keep predictable. Bundle size down to 9433 bytes min+gzip, docs updated accordingly. * Make `mithril/render` just return the `m.render` function directly. * Deservicify `m.render`, revise `m.route` - You now have to use `mithril/render/render` directly if you want an implicit redraw function. (This will likely be going away in v3.) - Revise `m.route` to only `key` components * Add `redraw` to `m.render`, deservicify requests * Test error logging * Update docs + changelog [skip ci]
424 lines
9.1 KiB
JavaScript
424 lines
9.1 KiB
JavaScript
"use strict"
|
|
|
|
// Low-priority TODO: remove the dependency on the renderer here.
|
|
var o = require("../../ospec/ospec")
|
|
var components = require("../../test-utils/components")
|
|
var domMock = require("../../test-utils/domMock")
|
|
var throttleMocker = require("../../test-utils/throttleMock")
|
|
var mountRedraw = require("../../api/mount-redraw")
|
|
var coreRenderer = require("../../render/render")
|
|
var h = require("../../render/hyperscript")
|
|
|
|
o.spec("mount/redraw", function() {
|
|
var root, m, throttleMock, consoleMock, $document, errors
|
|
o.beforeEach(function() {
|
|
var $window = domMock()
|
|
consoleMock = {error: o.spy()}
|
|
throttleMock = throttleMocker()
|
|
root = $window.document.body
|
|
m = mountRedraw(coreRenderer($window), throttleMock.schedule, consoleMock)
|
|
$document = $window.document
|
|
errors = []
|
|
})
|
|
|
|
o.afterEach(function() {
|
|
o(consoleMock.error.calls.map(function(c) {
|
|
return c.args[0]
|
|
})).deepEquals(errors)
|
|
o(throttleMock.queueLength()).equals(0)
|
|
})
|
|
|
|
o("shouldn't error if there are no renderers", function() {
|
|
m.redraw()
|
|
throttleMock.fire()
|
|
})
|
|
|
|
o("schedules correctly", function() {
|
|
var spy = o.spy()
|
|
|
|
m.mount(root, {view: spy})
|
|
o(spy.callCount).equals(1)
|
|
m.redraw()
|
|
o(spy.callCount).equals(1)
|
|
throttleMock.fire()
|
|
o(spy.callCount).equals(2)
|
|
})
|
|
|
|
o("should run a single renderer entry", function() {
|
|
var spy = o.spy()
|
|
|
|
m.mount(root, {view: spy})
|
|
|
|
o(spy.callCount).equals(1)
|
|
|
|
m.redraw()
|
|
m.redraw()
|
|
m.redraw()
|
|
|
|
o(spy.callCount).equals(1)
|
|
throttleMock.fire()
|
|
o(spy.callCount).equals(2)
|
|
})
|
|
|
|
o("should run all renderer entries", function() {
|
|
var el1 = $document.createElement("div")
|
|
var el2 = $document.createElement("div")
|
|
var el3 = $document.createElement("div")
|
|
var spy1 = o.spy()
|
|
var spy2 = o.spy()
|
|
var spy3 = o.spy()
|
|
|
|
m.mount(el1, {view: spy1})
|
|
m.mount(el2, {view: spy2})
|
|
m.mount(el3, {view: spy3})
|
|
|
|
m.redraw()
|
|
|
|
o(spy1.callCount).equals(1)
|
|
o(spy2.callCount).equals(1)
|
|
o(spy3.callCount).equals(1)
|
|
|
|
m.redraw()
|
|
|
|
o(spy1.callCount).equals(1)
|
|
o(spy2.callCount).equals(1)
|
|
o(spy3.callCount).equals(1)
|
|
|
|
throttleMock.fire()
|
|
|
|
o(spy1.callCount).equals(2)
|
|
o(spy2.callCount).equals(2)
|
|
o(spy3.callCount).equals(2)
|
|
})
|
|
|
|
o("should stop running after mount null", function() {
|
|
var spy = o.spy()
|
|
|
|
m.mount(root, {view: spy})
|
|
o(spy.callCount).equals(1)
|
|
m.mount(root, null)
|
|
|
|
m.redraw()
|
|
|
|
o(spy.callCount).equals(1)
|
|
throttleMock.fire()
|
|
o(spy.callCount).equals(1)
|
|
})
|
|
|
|
o("should stop running after mount undefined", function() {
|
|
var spy = o.spy()
|
|
|
|
m.mount(root, {view: spy})
|
|
o(spy.callCount).equals(1)
|
|
m.mount(root, undefined)
|
|
|
|
m.redraw()
|
|
|
|
o(spy.callCount).equals(1)
|
|
throttleMock.fire()
|
|
o(spy.callCount).equals(1)
|
|
})
|
|
|
|
o("should stop running after mount no arg", function() {
|
|
var spy = o.spy()
|
|
|
|
m.mount(root, {view: spy})
|
|
o(spy.callCount).equals(1)
|
|
m.mount(root)
|
|
|
|
m.redraw()
|
|
|
|
o(spy.callCount).equals(1)
|
|
throttleMock.fire()
|
|
o(spy.callCount).equals(1)
|
|
})
|
|
|
|
o("should invoke remove callback on unmount", function() {
|
|
var spy = o.spy()
|
|
var onremove = o.spy()
|
|
|
|
m.mount(root, {view: spy, onremove: onremove})
|
|
o(spy.callCount).equals(1)
|
|
m.mount(root)
|
|
|
|
o(spy.callCount).equals(1)
|
|
o(onremove.callCount).equals(1)
|
|
})
|
|
|
|
o("should stop running after unsubscribe, even if it occurs after redraw is requested", function() {
|
|
var spy = o.spy()
|
|
|
|
m.mount(root, {view: spy})
|
|
o(spy.callCount).equals(1)
|
|
m.redraw()
|
|
m.mount(root)
|
|
|
|
o(spy.callCount).equals(1)
|
|
throttleMock.fire()
|
|
o(spy.callCount).equals(1)
|
|
})
|
|
|
|
o("does nothing on invalid unmount", function() {
|
|
var spy = o.spy()
|
|
|
|
m.mount(root, {view: spy})
|
|
o(spy.callCount).equals(1)
|
|
|
|
m.mount(null)
|
|
m.redraw()
|
|
throttleMock.fire()
|
|
o(spy.callCount).equals(2)
|
|
})
|
|
|
|
o("redraw.sync() redraws all roots synchronously", function() {
|
|
var el1 = $document.createElement("div")
|
|
var el2 = $document.createElement("div")
|
|
var el3 = $document.createElement("div")
|
|
var spy1 = o.spy()
|
|
var spy2 = o.spy()
|
|
var spy3 = o.spy()
|
|
|
|
m.mount(el1, {view: spy1})
|
|
m.mount(el2, {view: spy2})
|
|
m.mount(el3, {view: spy3})
|
|
|
|
o(spy1.callCount).equals(1)
|
|
o(spy2.callCount).equals(1)
|
|
o(spy3.callCount).equals(1)
|
|
|
|
m.redraw.sync()
|
|
|
|
o(spy1.callCount).equals(2)
|
|
o(spy2.callCount).equals(2)
|
|
o(spy3.callCount).equals(2)
|
|
|
|
m.redraw.sync()
|
|
|
|
o(spy1.callCount).equals(3)
|
|
o(spy2.callCount).equals(3)
|
|
o(spy3.callCount).equals(3)
|
|
})
|
|
|
|
|
|
o("throws on invalid component", function() {
|
|
o(function() { m.mount(root, {}) }).throws(TypeError)
|
|
})
|
|
|
|
components.forEach(function(cmp){
|
|
o.spec(cmp.kind, function(){
|
|
var createComponent = cmp.create
|
|
|
|
o("throws on invalid `root` DOM node", function() {
|
|
o(function() {
|
|
m.mount(null, createComponent({view: function() {}}))
|
|
}).throws(TypeError)
|
|
})
|
|
|
|
o("renders into `root` synchronously", function() {
|
|
m.mount(root, createComponent({
|
|
view: function() {
|
|
return h("div")
|
|
}
|
|
}))
|
|
|
|
o(root.firstChild.nodeName).equals("DIV")
|
|
})
|
|
|
|
o("mounting null unmounts", function() {
|
|
m.mount(root, createComponent({
|
|
view: function() {
|
|
return h("div")
|
|
}
|
|
}))
|
|
|
|
m.mount(root, null)
|
|
|
|
o(root.childNodes.length).equals(0)
|
|
})
|
|
|
|
o("Mounting a second root doesn't cause the first one to redraw", function() {
|
|
var root1 = $document.createElement("div")
|
|
var root2 = $document.createElement("div")
|
|
var view = o.spy()
|
|
|
|
m.mount(root1, createComponent({view: view}))
|
|
o(view.callCount).equals(1)
|
|
|
|
m.mount(root2, createComponent({view: function() {}}))
|
|
|
|
o(view.callCount).equals(1)
|
|
|
|
throttleMock.fire()
|
|
o(view.callCount).equals(1)
|
|
})
|
|
|
|
o("redraws on events", function() {
|
|
var onupdate = o.spy()
|
|
var oninit = o.spy()
|
|
var onclick = o.spy()
|
|
var e = $document.createEvent("MouseEvents")
|
|
|
|
e.initEvent("click", true, true)
|
|
|
|
m.mount(root, createComponent({
|
|
view: function() {
|
|
return h("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)
|
|
|
|
throttleMock.fire()
|
|
|
|
o(onupdate.callCount).equals(1)
|
|
})
|
|
|
|
o("redraws several mount points on events", function() {
|
|
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 root1 = $document.createElement("div")
|
|
var root2 = $document.createElement("div")
|
|
var e = $document.createEvent("MouseEvents")
|
|
|
|
e.initEvent("click", true, true)
|
|
|
|
m.mount(root1, createComponent({
|
|
view: function() {
|
|
return h("div", {
|
|
oninit: oninit0,
|
|
onupdate: onupdate0,
|
|
onclick: onclick0,
|
|
})
|
|
}
|
|
}))
|
|
|
|
o(oninit0.callCount).equals(1)
|
|
o(onupdate0.callCount).equals(0)
|
|
|
|
m.mount(root2, createComponent({
|
|
view: function() {
|
|
return h("div", {
|
|
oninit: oninit1,
|
|
onupdate: onupdate1,
|
|
onclick: onclick1,
|
|
})
|
|
}
|
|
}))
|
|
|
|
o(oninit1.callCount).equals(1)
|
|
o(onupdate1.callCount).equals(0)
|
|
|
|
root1.firstChild.dispatchEvent(e)
|
|
o(onclick0.callCount).equals(1)
|
|
o(onclick0.this).equals(root1.firstChild)
|
|
|
|
throttleMock.fire()
|
|
|
|
o(onupdate0.callCount).equals(1)
|
|
o(onupdate1.callCount).equals(1)
|
|
|
|
root2.firstChild.dispatchEvent(e)
|
|
|
|
o(onclick1.callCount).equals(1)
|
|
o(onclick1.this).equals(root2.firstChild)
|
|
|
|
throttleMock.fire()
|
|
|
|
o(onupdate0.callCount).equals(2)
|
|
o(onupdate1.callCount).equals(2)
|
|
})
|
|
|
|
o("event handlers can skip redraw", function() {
|
|
var onupdate = o.spy(function(){
|
|
throw new Error("This shouldn't have been called")
|
|
})
|
|
var oninit = o.spy()
|
|
var e = $document.createEvent("MouseEvents")
|
|
|
|
e.initEvent("click", true, true)
|
|
|
|
m.mount(root, createComponent({
|
|
view: function() {
|
|
return h("div", {
|
|
oninit: oninit,
|
|
onupdate: onupdate,
|
|
onclick: function(e) {
|
|
e.redraw = false
|
|
}
|
|
})
|
|
}
|
|
}))
|
|
|
|
root.firstChild.dispatchEvent(e)
|
|
|
|
o(oninit.callCount).equals(1)
|
|
o(e.redraw).equals(false)
|
|
|
|
throttleMock.fire()
|
|
|
|
o(onupdate.callCount).equals(0)
|
|
o(e.redraw).equals(false)
|
|
})
|
|
|
|
o("redraws when the render function is run", function() {
|
|
var onupdate = o.spy()
|
|
var oninit = o.spy()
|
|
|
|
m.mount(root, createComponent({
|
|
view: function() {
|
|
return h("div", {
|
|
oninit: oninit,
|
|
onupdate: onupdate
|
|
})
|
|
}
|
|
}))
|
|
|
|
o(oninit.callCount).equals(1)
|
|
o(onupdate.callCount).equals(0)
|
|
|
|
m.redraw()
|
|
|
|
throttleMock.fire()
|
|
|
|
o(onupdate.callCount).equals(1)
|
|
})
|
|
|
|
o("emits errors correctly", function() {
|
|
errors = ["foo", "bar", "baz"]
|
|
var counter = -1
|
|
|
|
m.mount(root, createComponent({
|
|
view: function() {
|
|
var value = errors[counter++]
|
|
if (value != null) throw value
|
|
return null
|
|
}
|
|
}))
|
|
|
|
m.redraw()
|
|
throttleMock.fire()
|
|
m.redraw()
|
|
throttleMock.fire()
|
|
m.redraw()
|
|
throttleMock.fire()
|
|
})
|
|
})
|
|
})
|
|
})
|