Deservicify core (#2458)
* 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]
This commit is contained in:
parent
db277217f8
commit
1f4b2cf49a
60 changed files with 1212 additions and 1393 deletions
50
api/mount-redraw.js
Normal file
50
api/mount-redraw.js
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
"use strict"
|
||||
|
||||
var Vnode = require("../render/vnode")
|
||||
|
||||
module.exports = function(render, schedule, console) {
|
||||
var subscriptions = []
|
||||
var rendering = false
|
||||
var pending = false
|
||||
|
||||
function sync() {
|
||||
if (rendering) throw new Error("Nested m.redraw.sync() call")
|
||||
rendering = true
|
||||
for (var i = 0; i < subscriptions.length; i += 2) {
|
||||
try { render(subscriptions[i], Vnode(subscriptions[i + 1]), redraw) }
|
||||
catch (e) { console.error(e) }
|
||||
}
|
||||
rendering = false
|
||||
}
|
||||
|
||||
function redraw() {
|
||||
if (!pending) {
|
||||
pending = true
|
||||
schedule(function() {
|
||||
pending = false
|
||||
sync()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
redraw.sync = sync
|
||||
|
||||
function mount(root, component) {
|
||||
if (component != null && component.view == null && typeof component !== "function") {
|
||||
throw new TypeError("m.mount(element, component) expects a component, not a vnode")
|
||||
}
|
||||
|
||||
var index = subscriptions.indexOf(root)
|
||||
if (index >= 0) {
|
||||
subscriptions.splice(index, 2)
|
||||
render(root, [], redraw)
|
||||
}
|
||||
|
||||
if (component != null) {
|
||||
subscriptions.push(root, component)
|
||||
render(root, Vnode(component), redraw)
|
||||
}
|
||||
}
|
||||
|
||||
return {mount: mount, redraw: redraw}
|
||||
}
|
||||
15
api/mount.js
15
api/mount.js
|
|
@ -1,15 +0,0 @@
|
|||
"use strict"
|
||||
|
||||
var Vnode = require("../render/vnode")
|
||||
|
||||
module.exports = function(redrawService) {
|
||||
return function(root, component) {
|
||||
if (component === null) {
|
||||
redrawService.unsubscribe(root)
|
||||
} else if (component.view == null && typeof component !== "function") {
|
||||
throw new Error("m.mount(element, component) expects a component, not a vnode")
|
||||
} else {
|
||||
redrawService.subscribe(root, function() { return Vnode(component) })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
"use strict"
|
||||
|
||||
var coreRenderer = require("../render/render")
|
||||
|
||||
function throttle(callback) {
|
||||
var pending = null
|
||||
return function() {
|
||||
if (pending === null) {
|
||||
pending = requestAnimationFrame(function() {
|
||||
pending = null
|
||||
callback()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = function($window, throttleMock) {
|
||||
var renderService = coreRenderer($window)
|
||||
var subscriptions = []
|
||||
var rendering = false
|
||||
|
||||
function run(sub) {
|
||||
var vnode = sub.c(sub)
|
||||
if (vnode !== sub) renderService.render(sub.k, vnode)
|
||||
}
|
||||
function subscribe(key, callback, onremove) {
|
||||
var sub = {k: key, c: callback, r: onremove}
|
||||
unsubscribe(key)
|
||||
subscriptions.push(sub)
|
||||
var vnode = sub.c(sub)
|
||||
if (vnode !== sub) renderService.render(sub.k, vnode)
|
||||
}
|
||||
function unsubscribe(key) {
|
||||
for (var i = 0; i < subscriptions.length; i++) {
|
||||
var sub = subscriptions[i]
|
||||
if (sub.k === key) {
|
||||
subscriptions.splice(i, 1)
|
||||
renderService.render(sub.k, [])
|
||||
if (typeof sub.r === "function") sub.r()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
function sync() {
|
||||
if (rendering) throw new Error("Nested m.redraw.sync() call")
|
||||
rendering = true
|
||||
for (var i = 0; i < subscriptions.length; i++) {
|
||||
try { run(subscriptions[i]) }
|
||||
catch (e) { if (typeof console !== "undefined") console.error(e) }
|
||||
}
|
||||
rendering = false
|
||||
}
|
||||
|
||||
var redraw = (throttleMock || throttle)(sync)
|
||||
redraw.sync = sync
|
||||
renderService.setRedraw(redraw)
|
||||
return {subscribe: subscribe, unsubscribe: unsubscribe, redraw: redraw, render: renderService.render}
|
||||
}
|
||||
186
api/router.js
186
api/router.js
|
|
@ -2,55 +2,153 @@
|
|||
|
||||
var Vnode = require("../render/vnode")
|
||||
var Promise = require("../promise/promise")
|
||||
var coreRouter = require("../router/router")
|
||||
|
||||
var buildPathname = require("../pathname/build")
|
||||
var parsePathname = require("../pathname/parse")
|
||||
var compileTemplate = require("../pathname/compileTemplate")
|
||||
var assign = require("../pathname/assign")
|
||||
|
||||
var sentinel = {}
|
||||
|
||||
module.exports = function($window, redrawService) {
|
||||
var routeService = coreRouter($window)
|
||||
module.exports = function($window, mountRedraw) {
|
||||
var callAsync = typeof setImmediate === "function" ? setImmediate : setTimeout
|
||||
var supportsPushState = typeof $window.history.pushState === "function"
|
||||
var routePrefix = "#!"
|
||||
var fireAsync
|
||||
|
||||
function setPath(path, data, options) {
|
||||
path = buildPathname(path, data)
|
||||
if (fireAsync != null) {
|
||||
fireAsync()
|
||||
var state = options ? options.state : null
|
||||
var title = options ? options.title : null
|
||||
if (options && options.replace) $window.history.replaceState(state, title, routePrefix + path)
|
||||
else $window.history.pushState(state, title, routePrefix + path)
|
||||
}
|
||||
else {
|
||||
$window.location.href = routePrefix + path
|
||||
}
|
||||
}
|
||||
|
||||
var currentResolver = sentinel, component, attrs, currentPath, lastUpdate
|
||||
var route = function(root, defaultRoute, routes) {
|
||||
if (root == null) throw new Error("Ensure the DOM element that was passed to `m.route` is not undefined")
|
||||
var init = false
|
||||
var bail = function(path) {
|
||||
if (path !== defaultRoute) routeService.setPath(defaultRoute, null, {replace: true})
|
||||
else throw new Error("Could not resolve default route " + defaultRoute)
|
||||
// 0 = start
|
||||
// 1 = init
|
||||
// 2 = ready
|
||||
var state = 0
|
||||
|
||||
var compiled = Object.keys(routes).map(function(route) {
|
||||
if (route[0] !== "/") throw new SyntaxError("Routes must start with a `/`")
|
||||
if ((/:([^\/\.-]+)(\.{3})?:/).test(route)) {
|
||||
throw new SyntaxError("Route parameter names must be separated with either `/`, `.`, or `-`")
|
||||
}
|
||||
return {
|
||||
route: route,
|
||||
component: routes[route],
|
||||
check: compileTemplate(route),
|
||||
}
|
||||
})
|
||||
var onremove, asyncId
|
||||
|
||||
fireAsync = null
|
||||
|
||||
if (defaultRoute != null) {
|
||||
var defaultData = parsePathname(defaultRoute)
|
||||
|
||||
if (!compiled.some(function (i) { return i.check(defaultData) })) {
|
||||
throw new ReferenceError("Default route doesn't match any known routes")
|
||||
}
|
||||
}
|
||||
function run() {
|
||||
init = true
|
||||
if (sentinel !== currentResolver) {
|
||||
var vnode = Vnode(component, attrs.key, attrs)
|
||||
if (currentResolver) vnode = currentResolver.render(vnode)
|
||||
|
||||
function resolveRoute() {
|
||||
// Consider the pathname holistically. The prefix might even be invalid,
|
||||
// but that's not our problem.
|
||||
var prefix = $window.location.hash
|
||||
if (routePrefix[0] !== "#") {
|
||||
prefix = $window.location.search + prefix
|
||||
if (routePrefix[0] !== "?") {
|
||||
prefix = $window.location.pathname + prefix
|
||||
if (prefix[0] !== "/") prefix = "/" + prefix
|
||||
}
|
||||
}
|
||||
// This seemingly useless `.concat()` speeds up the tests quite a bit,
|
||||
// since the representation is consistently a relatively poorly
|
||||
// optimized cons string.
|
||||
var path = prefix.concat()
|
||||
.replace(/(?:%[a-f89][a-f0-9])+/gim, decodeURIComponent)
|
||||
.slice(routePrefix.length)
|
||||
var data = parsePathname(path)
|
||||
|
||||
assign(data.params, $window.history.state)
|
||||
|
||||
for (var i = 0; i < compiled.length; i++) {
|
||||
if (compiled[i].check(data)) {
|
||||
var payload = compiled[i].component
|
||||
var route = compiled[i].route
|
||||
var update = lastUpdate = function(routeResolver, comp) {
|
||||
if (update !== lastUpdate) return
|
||||
component = comp != null && (typeof comp.view === "function" || typeof comp === "function")? comp : "div"
|
||||
attrs = data.params, currentPath = path, lastUpdate = null
|
||||
currentResolver = routeResolver.render ? routeResolver : null
|
||||
if (state === 2) mountRedraw.redraw()
|
||||
else {
|
||||
state = 2
|
||||
mountRedraw.redraw.sync()
|
||||
}
|
||||
}
|
||||
if (payload.view || typeof payload === "function") update({}, payload)
|
||||
else {
|
||||
if (payload.onmatch) {
|
||||
Promise.resolve(payload.onmatch(data.params, path, route)).then(function(resolved) {
|
||||
update(payload, resolved)
|
||||
}, function () {
|
||||
if (path === defaultRoute) throw new Error("Could not resolve default route " + defaultRoute)
|
||||
setPath(defaultRoute, null, {replace: true})
|
||||
})
|
||||
}
|
||||
else update(payload, "div")
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (path === defaultRoute) throw new Error("Could not resolve default route " + defaultRoute)
|
||||
setPath(defaultRoute, null, {replace: true})
|
||||
}
|
||||
|
||||
if (supportsPushState) {
|
||||
onremove = function() {
|
||||
$window.removeEventListener("popstate", fireAsync, false)
|
||||
}
|
||||
$window.addEventListener("popstate", fireAsync = function() {
|
||||
if (asyncId) return
|
||||
asyncId = callAsync(function() {
|
||||
asyncId = null
|
||||
resolveRoute()
|
||||
})
|
||||
}, false)
|
||||
} else if (routePrefix[0] === "#") {
|
||||
onremove = function() {
|
||||
$window.removeEventListener("hashchange", resolveRoute, false)
|
||||
}
|
||||
$window.addEventListener("hashchange", resolveRoute, false)
|
||||
}
|
||||
|
||||
return mountRedraw.mount(root, {
|
||||
onbeforeupdate: function() {
|
||||
state = state ? 2 : 1
|
||||
return !(!state || sentinel === currentResolver)
|
||||
},
|
||||
oncreate: resolveRoute,
|
||||
onremove: onremove,
|
||||
view: function() {
|
||||
if (!state || sentinel === currentResolver) return
|
||||
// Wrap in a fragment to preserve existing key semantics
|
||||
var vnode = [Vnode(component, attrs.key, attrs)]
|
||||
if (currentResolver) vnode = currentResolver.render(vnode[0])
|
||||
return vnode
|
||||
}
|
||||
}
|
||||
routeService.defineRoutes(routes, function(payload, params, path, route) {
|
||||
var update = lastUpdate = function(routeResolver, comp) {
|
||||
if (update !== lastUpdate) return
|
||||
component = comp != null && (typeof comp.view === "function" || typeof comp === "function")? comp : "div"
|
||||
attrs = params, currentPath = path, lastUpdate = null
|
||||
currentResolver = routeResolver.render ? routeResolver : null
|
||||
if (init) redrawService.redraw()
|
||||
else {
|
||||
init = true
|
||||
redrawService.redraw.sync()
|
||||
}
|
||||
}
|
||||
if (payload.view || typeof payload === "function") update({}, payload)
|
||||
else {
|
||||
if (payload.onmatch) {
|
||||
Promise.resolve(payload.onmatch(params, path, route)).then(function(resolved) {
|
||||
update(payload, resolved)
|
||||
}, function () { bail(path) })
|
||||
}
|
||||
else update(payload, "div")
|
||||
}
|
||||
}, bail, defaultRoute, function (unsubscribe) {
|
||||
redrawService.subscribe(root, function(sub) {
|
||||
sub.c = run
|
||||
return sub
|
||||
}, unsubscribe)
|
||||
},
|
||||
})
|
||||
}
|
||||
route.set = function(path, data, options) {
|
||||
|
|
@ -59,18 +157,18 @@ module.exports = function($window, redrawService) {
|
|||
options.replace = true
|
||||
}
|
||||
lastUpdate = null
|
||||
routeService.setPath(path, data, options)
|
||||
setPath(path, data, options)
|
||||
}
|
||||
route.get = function() {return currentPath}
|
||||
route.prefix = function(prefix) {routeService.prefix = prefix}
|
||||
route.prefix = function(prefix) {routePrefix = prefix}
|
||||
var link = function(options, vnode) {
|
||||
vnode.dom.setAttribute("href", routeService.prefix + vnode.attrs.href)
|
||||
vnode.dom.setAttribute("href", routePrefix + vnode.attrs.href)
|
||||
vnode.dom.onclick = function(e) {
|
||||
if (e.ctrlKey || e.metaKey || e.shiftKey || e.which === 2) return
|
||||
e.preventDefault()
|
||||
e.redraw = false
|
||||
var href = this.getAttribute("href")
|
||||
if (href.indexOf(routeService.prefix) === 0) href = href.slice(routeService.prefix.length)
|
||||
if (href.indexOf(routePrefix) === 0) href = href.slice(routePrefix.length)
|
||||
route.set(href, undefined, options)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,13 +24,11 @@
|
|||
<script src="../../querystring/build.js"></script>
|
||||
<script src="../../querystring/parse.js"></script>
|
||||
<script src="../../request/request.js"></script>
|
||||
<script src="../../router/router.js"></script>
|
||||
<script src="../../api/redraw.js"></script>
|
||||
<script src="../../api/mount.js"></script>
|
||||
<script src="../../api/mount-redraw.js"></script>
|
||||
<script src="../../api/router.js"></script>
|
||||
<script src="./test-redraw.js"></script>
|
||||
<script src="./test-mount.js"></script>
|
||||
<script src="./test-mountRedraw.js"></script>
|
||||
<script src="./test-router.js"></script>
|
||||
<script src="./test-routerGetSet.js"></script>
|
||||
|
||||
<script>require("../../ospec/ospec").run()</script>
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -1,274 +0,0 @@
|
|||
"use strict"
|
||||
|
||||
var o = require("../../ospec/ospec")
|
||||
var components = require("../../test-utils/components")
|
||||
var domMock = require("../../test-utils/domMock")
|
||||
var throttleMocker = require("../../test-utils/throttleMock")
|
||||
|
||||
var m = require("../../render/hyperscript")
|
||||
var apiRedraw = require("../../api/redraw")
|
||||
var apiMounter = require("../../api/mount")
|
||||
|
||||
o.spec("mount", function() {
|
||||
var $window, root, redrawService, mount, render, throttleMock
|
||||
|
||||
o.beforeEach(function() {
|
||||
$window = domMock()
|
||||
throttleMock = throttleMocker()
|
||||
|
||||
root = $window.document.body
|
||||
redrawService = apiRedraw($window, throttleMock.throttle)
|
||||
mount = apiMounter(redrawService)
|
||||
render = redrawService.render
|
||||
})
|
||||
|
||||
o.afterEach(function() {
|
||||
o(throttleMock.queueLength()).equals(0)
|
||||
})
|
||||
|
||||
o("throws on invalid component", function() {
|
||||
var threw = false
|
||||
try {
|
||||
mount(root, {})
|
||||
} catch (e) {
|
||||
threw = true
|
||||
}
|
||||
o(threw).equals(true)
|
||||
})
|
||||
|
||||
components.forEach(function(cmp){
|
||||
o.spec(cmp.kind, function(){
|
||||
var createComponent = cmp.create
|
||||
|
||||
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("renders into `root` synchronoulsy", 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("Mounting a second root doesn't cause the first one to redraw", function() {
|
||||
var view = o.spy(function() {
|
||||
return m("div")
|
||||
})
|
||||
|
||||
render(root, [
|
||||
m("#child0"),
|
||||
m("#child1")
|
||||
])
|
||||
|
||||
mount(root.childNodes[0], createComponent({
|
||||
view : view
|
||||
}))
|
||||
|
||||
o(root.firstChild.nodeName).equals("DIV")
|
||||
o(view.callCount).equals(1)
|
||||
|
||||
mount(root.childNodes[1], createComponent({
|
||||
view : function() {
|
||||
return m("div")
|
||||
}
|
||||
}))
|
||||
|
||||
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 = $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)
|
||||
|
||||
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 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)
|
||||
|
||||
throttleMock.fire()
|
||||
|
||||
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)
|
||||
|
||||
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 = $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)
|
||||
|
||||
throttleMock.fire()
|
||||
|
||||
o(onupdate.callCount).equals(0)
|
||||
})
|
||||
|
||||
o("redraws when the render function is run", function() {
|
||||
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()
|
||||
|
||||
throttleMock.fire()
|
||||
|
||||
o(onupdate.callCount).equals(1)
|
||||
})
|
||||
|
||||
o("throttles", function() {
|
||||
var i = 0
|
||||
mount(root, createComponent({view: function() {i++}}))
|
||||
var before = i
|
||||
|
||||
redrawService.redraw()
|
||||
redrawService.redraw()
|
||||
redrawService.redraw()
|
||||
redrawService.redraw()
|
||||
|
||||
var after = i
|
||||
|
||||
throttleMock.fire()
|
||||
|
||||
o(before).equals(1) // mounts synchronously
|
||||
o(after).equals(1) // throttles rest
|
||||
o(i).equals(2)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
424
api/tests/test-mountRedraw.js
Normal file
424
api/tests/test-mountRedraw.js
Normal file
|
|
@ -0,0 +1,424 @@
|
|||
"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()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -1,195 +0,0 @@
|
|||
"use strict"
|
||||
|
||||
var o = require("../../ospec/ospec")
|
||||
var domMock = require("../../test-utils/domMock")
|
||||
var throttleMocker = require("../../test-utils/throttleMock")
|
||||
var apiRedraw = require("../../api/redraw")
|
||||
|
||||
// Because Node doesn't have this.
|
||||
if (typeof requestAnimationFrame !== "function") {
|
||||
global.requestAnimationFrame = (function (delay, last) {
|
||||
return function(callback) {
|
||||
var elapsed = Date.now() - last
|
||||
return setTimeout(function() {
|
||||
callback()
|
||||
last = Date.now()
|
||||
}, delay - elapsed)
|
||||
}
|
||||
})(16, 0)
|
||||
}
|
||||
|
||||
o.spec("redrawService", function() {
|
||||
var root, redrawService, $document
|
||||
o.beforeEach(function() {
|
||||
var $window = domMock()
|
||||
root = $window.document.body
|
||||
redrawService = apiRedraw($window)
|
||||
$document = $window.document
|
||||
})
|
||||
|
||||
o("shouldn't error if there are no renderers", function() {
|
||||
redrawService.redraw()
|
||||
})
|
||||
|
||||
o("honours throttleMock", function() {
|
||||
var throttleMock = throttleMocker()
|
||||
redrawService = apiRedraw(domMock(), throttleMock.throttle)
|
||||
var spy = o.spy()
|
||||
|
||||
redrawService.subscribe(root, spy)
|
||||
|
||||
o(spy.callCount).equals(1)
|
||||
|
||||
redrawService.redraw()
|
||||
|
||||
o(spy.callCount).equals(1)
|
||||
|
||||
throttleMock.fire()
|
||||
|
||||
o(spy.callCount).equals(2)
|
||||
})
|
||||
|
||||
o("should run a single renderer entry", function(done) {
|
||||
var spy = o.spy()
|
||||
|
||||
redrawService.subscribe(root, spy)
|
||||
|
||||
o(spy.callCount).equals(1)
|
||||
|
||||
redrawService.redraw()
|
||||
redrawService.redraw()
|
||||
redrawService.redraw()
|
||||
|
||||
o(spy.callCount).equals(1)
|
||||
setTimeout(function() {
|
||||
o(spy.callCount).equals(2)
|
||||
|
||||
done()
|
||||
}, 20)
|
||||
})
|
||||
|
||||
o("should run all renderer entries", function(done) {
|
||||
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()
|
||||
|
||||
redrawService.subscribe(el1, spy1)
|
||||
redrawService.subscribe(el2, spy2)
|
||||
redrawService.subscribe(el3, spy3)
|
||||
|
||||
redrawService.redraw()
|
||||
|
||||
o(spy1.callCount).equals(1)
|
||||
o(spy2.callCount).equals(1)
|
||||
o(spy3.callCount).equals(1)
|
||||
|
||||
redrawService.redraw()
|
||||
|
||||
o(spy1.callCount).equals(1)
|
||||
o(spy2.callCount).equals(1)
|
||||
o(spy3.callCount).equals(1)
|
||||
|
||||
setTimeout(function() {
|
||||
o(spy1.callCount).equals(2)
|
||||
o(spy2.callCount).equals(2)
|
||||
o(spy3.callCount).equals(2)
|
||||
|
||||
done()
|
||||
}, 20)
|
||||
})
|
||||
|
||||
o("should stop running after unsubscribe", function(done) {
|
||||
var spy = o.spy()
|
||||
|
||||
redrawService.subscribe(root, spy)
|
||||
o(spy.callCount).equals(1)
|
||||
redrawService.unsubscribe(root)
|
||||
|
||||
redrawService.redraw()
|
||||
|
||||
o(spy.callCount).equals(1)
|
||||
setTimeout(function() {
|
||||
o(spy.callCount).equals(1)
|
||||
|
||||
done()
|
||||
}, 20)
|
||||
})
|
||||
|
||||
o("should invoke remove callback on unsubscribe", function() {
|
||||
var spy = o.spy()
|
||||
var onremove = o.spy()
|
||||
|
||||
redrawService.subscribe(root, spy, onremove)
|
||||
o(spy.callCount).equals(1)
|
||||
redrawService.unsubscribe(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(done) {
|
||||
var spy = o.spy()
|
||||
|
||||
redrawService.subscribe(root, spy)
|
||||
o(spy.callCount).equals(1)
|
||||
|
||||
redrawService.redraw()
|
||||
|
||||
redrawService.unsubscribe(root)
|
||||
|
||||
o(spy.callCount).equals(1)
|
||||
setTimeout(function() {
|
||||
o(spy.callCount).equals(1)
|
||||
|
||||
done()
|
||||
}, 20)
|
||||
})
|
||||
|
||||
o("does nothing on invalid unsubscribe", function(done) {
|
||||
var spy = o.spy()
|
||||
|
||||
redrawService.subscribe(root, spy)
|
||||
o(spy.callCount).equals(1)
|
||||
|
||||
redrawService.unsubscribe(null)
|
||||
redrawService.redraw()
|
||||
|
||||
setTimeout(function() {
|
||||
o(spy.callCount).equals(2)
|
||||
|
||||
done()
|
||||
}, 20)
|
||||
})
|
||||
|
||||
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()
|
||||
|
||||
redrawService.subscribe(el1, spy1)
|
||||
redrawService.subscribe(el2, spy2)
|
||||
redrawService.subscribe(el3, spy3)
|
||||
|
||||
o(spy1.callCount).equals(1)
|
||||
o(spy2.callCount).equals(1)
|
||||
o(spy3.callCount).equals(1)
|
||||
|
||||
redrawService.redraw.sync()
|
||||
|
||||
o(spy1.callCount).equals(2)
|
||||
o(spy2.callCount).equals(2)
|
||||
o(spy3.callCount).equals(2)
|
||||
|
||||
redrawService.redraw.sync()
|
||||
|
||||
o(spy1.callCount).equals(3)
|
||||
o(spy2.callCount).equals(3)
|
||||
o(spy3.callCount).equals(3)
|
||||
})
|
||||
})
|
||||
|
|
@ -1,13 +1,15 @@
|
|||
"use strict"
|
||||
|
||||
// Low-priority TODO: remove the dependency on the renderer here.
|
||||
var o = require("../../ospec/ospec")
|
||||
var callAsync = require("../../test-utils/callAsync")
|
||||
var browserMock = require("../../test-utils/browserMock")
|
||||
var throttleMocker = require("../../test-utils/throttleMock")
|
||||
|
||||
var m = require("../../render/hyperscript")
|
||||
var coreRenderer = require("../../render/render")
|
||||
var callAsync = require("../../test-utils/callAsync")
|
||||
var apiRedraw = require("../../api/redraw")
|
||||
var apiMountRedraw = require("../../api/mount-redraw")
|
||||
var apiRouter = require("../../api/router")
|
||||
var Promise = require("../../promise/promise")
|
||||
|
||||
|
|
@ -15,7 +17,7 @@ o.spec("route", function() {
|
|||
void [{protocol: "http:", hostname: "localhost"}, {protocol: "file:", hostname: "/"}].forEach(function(env) {
|
||||
void ["#", "?", "", "#!", "?!", "/foo"].forEach(function(prefix) {
|
||||
o.spec("using prefix `" + prefix + "` starting on " + env.protocol + "//" + env.hostname, function() {
|
||||
var $window, root, redrawService, route, throttleMock
|
||||
var $window, root, mountRedraw, route, throttleMock
|
||||
|
||||
o.beforeEach(function() {
|
||||
$window = browserMock(env)
|
||||
|
|
@ -23,8 +25,8 @@ o.spec("route", function() {
|
|||
|
||||
root = $window.document.body
|
||||
|
||||
redrawService = apiRedraw($window, throttleMock.throttle)
|
||||
route = apiRouter($window, redrawService)
|
||||
mountRedraw = apiMountRedraw(coreRenderer($window), throttleMock.schedule, console)
|
||||
route = apiRouter($window, mountRedraw)
|
||||
route.prefix(prefix)
|
||||
})
|
||||
|
||||
|
|
@ -55,6 +57,165 @@ o.spec("route", function() {
|
|||
o(root.firstChild.nodeName).equals("DIV")
|
||||
})
|
||||
|
||||
o("resolves to route w/ escaped unicode", function() {
|
||||
$window.location.href = prefix + "/%C3%B6?%C3%B6=%C3%B6"
|
||||
route(root, "/ö", {
|
||||
"/ö" : {
|
||||
view: function() {
|
||||
return m("div")
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
o(root.firstChild.nodeName).equals("DIV")
|
||||
})
|
||||
|
||||
o("resolves to route w/ unicode", function() {
|
||||
$window.location.href = prefix + "/ö?ö=ö"
|
||||
route(root, "/ö", {
|
||||
"/ö" : {
|
||||
view: function() {
|
||||
return JSON.stringify(route.param()) + " " +
|
||||
route.get()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
o(root.firstChild.nodeValue).equals('{"ö":"ö"} /ö?ö=ö')
|
||||
})
|
||||
|
||||
o("handles parameterized route", function() {
|
||||
$window.location.href = prefix + "/test/x"
|
||||
route(root, "/test/:a", {
|
||||
"/test/:a" : {
|
||||
view: function(vnode) {
|
||||
return JSON.stringify(route.param()) + " " +
|
||||
JSON.stringify(vnode.attrs) + " " +
|
||||
route.get()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
o(root.firstChild.nodeValue).equals(
|
||||
'{"a":"x"} {"a":"x"} /test/x'
|
||||
)
|
||||
})
|
||||
|
||||
o("handles multi-parameterized route", function() {
|
||||
$window.location.href = prefix + "/test/x/y"
|
||||
route(root, "/test/:a/:b", {
|
||||
"/test/:a/:b" : {
|
||||
view: function(vnode) {
|
||||
return JSON.stringify(route.param()) + " " +
|
||||
JSON.stringify(vnode.attrs) + " " +
|
||||
route.get()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
o(root.firstChild.nodeValue).equals(
|
||||
'{"a":"x","b":"y"} {"a":"x","b":"y"} /test/x/y'
|
||||
)
|
||||
})
|
||||
|
||||
o("handles rest parameterized route", function() {
|
||||
$window.location.href = prefix + "/test/x/y"
|
||||
route(root, "/test/:a...", {
|
||||
"/test/:a..." : {
|
||||
view: function(vnode) {
|
||||
return JSON.stringify(route.param()) + " " +
|
||||
JSON.stringify(vnode.attrs) + " " +
|
||||
route.get()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
o(root.firstChild.nodeValue).equals(
|
||||
'{"a":"x/y"} {"a":"x/y"} /test/x/y'
|
||||
)
|
||||
})
|
||||
|
||||
o("handles route with search", function() {
|
||||
$window.location.href = prefix + "/test?a=b&c=d"
|
||||
route(root, "/test", {
|
||||
"/test" : {
|
||||
view: function(vnode) {
|
||||
return JSON.stringify(route.param()) + " " +
|
||||
JSON.stringify(vnode.attrs) + " " +
|
||||
route.get()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
o(root.firstChild.nodeValue).equals(
|
||||
'{"a":"b","c":"d"} {"a":"b","c":"d"} /test?a=b&c=d'
|
||||
)
|
||||
})
|
||||
|
||||
o("redirects to default route if no match", function(done) {
|
||||
$window.location.href = prefix + "/test"
|
||||
route(root, "/other", {
|
||||
"/other": {
|
||||
view: function(vnode) {
|
||||
return JSON.stringify(route.param()) + " " +
|
||||
JSON.stringify(vnode.attrs) + " " +
|
||||
route.get()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
callAsync(function() {
|
||||
o(root.firstChild.nodeValue).equals("{} {} /other")
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
o("handles out of order routes", function() {
|
||||
$window.location.href = prefix + "/z/y/x"
|
||||
|
||||
route(root, "/z/y/x", {
|
||||
"/z/y/x": {
|
||||
view: function() { return "1" },
|
||||
},
|
||||
"/:a...": {
|
||||
view: function() { return "2" },
|
||||
},
|
||||
})
|
||||
|
||||
o(root.firstChild.nodeValue).equals("1")
|
||||
})
|
||||
|
||||
o("handles reverse out of order routes", function() {
|
||||
$window.location.href = prefix + "/z/y/x"
|
||||
|
||||
route(root, "/z/y/x", {
|
||||
"/:a...": {
|
||||
view: function() { return "2" },
|
||||
},
|
||||
"/z/y/x": {
|
||||
view: function() { return "1" },
|
||||
},
|
||||
})
|
||||
|
||||
o(root.firstChild.nodeValue).equals("2")
|
||||
})
|
||||
|
||||
o("resolves to route on fallback mode", function() {
|
||||
$window.location.href = "file://" + prefix + "/test"
|
||||
|
||||
route(root, "/test", {
|
||||
"/test" : {
|
||||
view: function(vnode) {
|
||||
return JSON.stringify(route.param()) + " " +
|
||||
JSON.stringify(vnode.attrs) + " " +
|
||||
route.get()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
o(root.firstChild.nodeValue).equals("{} {} /test")
|
||||
})
|
||||
|
||||
o("routed mount points only redraw asynchronously (POJO component)", function() {
|
||||
var view = o.spy()
|
||||
|
||||
|
|
@ -63,7 +224,7 @@ o.spec("route", function() {
|
|||
|
||||
o(view.callCount).equals(1)
|
||||
|
||||
redrawService.redraw()
|
||||
mountRedraw.redraw()
|
||||
|
||||
o(view.callCount).equals(1)
|
||||
|
||||
|
|
@ -83,7 +244,7 @@ o.spec("route", function() {
|
|||
|
||||
o(view.callCount).equals(1)
|
||||
|
||||
redrawService.redraw()
|
||||
mountRedraw.redraw()
|
||||
|
||||
o(view.callCount).equals(1)
|
||||
|
||||
|
|
@ -102,7 +263,7 @@ o.spec("route", function() {
|
|||
|
||||
o(view.callCount).equals(1)
|
||||
|
||||
redrawService.redraw()
|
||||
mountRedraw.redraw()
|
||||
|
||||
o(view.callCount).equals(1)
|
||||
|
||||
|
|
@ -124,8 +285,7 @@ o.spec("route", function() {
|
|||
|
||||
o(root.firstChild.nodeName).equals("DIV")
|
||||
|
||||
// unsubscribe as if via `m.mount(root)`
|
||||
redrawService.unsubscribe(root)
|
||||
mountRedraw.mount(root)
|
||||
|
||||
o(root.childNodes.length).equals(0)
|
||||
})
|
||||
|
|
@ -192,7 +352,7 @@ o.spec("route", function() {
|
|||
|
||||
o(oninit.callCount).equals(1)
|
||||
|
||||
redrawService.redraw()
|
||||
mountRedraw.redraw()
|
||||
throttleMock.fire()
|
||||
|
||||
o(onupdate.callCount).equals(1)
|
||||
|
|
@ -259,8 +419,6 @@ o.spec("route", function() {
|
|||
|
||||
root.firstChild.dispatchEvent(e)
|
||||
|
||||
o(e.redraw).notEquals(false)
|
||||
|
||||
// Wrapped to ensure no redraw fired
|
||||
callAsync(function() {
|
||||
o(onupdate.callCount).equals(0)
|
||||
|
|
@ -550,7 +708,7 @@ o.spec("route", function() {
|
|||
})
|
||||
})
|
||||
|
||||
o("changing `vnode.key` in `render` resets the component", function(done){
|
||||
o("changing `key` param resets the component", function(done){
|
||||
var oninit = o.spy()
|
||||
var Component = {
|
||||
oninit: oninit,
|
||||
|
|
@ -560,9 +718,7 @@ o.spec("route", function() {
|
|||
}
|
||||
$window.location.href = prefix + "/abc"
|
||||
route(root, "/abc", {
|
||||
"/:id": {render: function(vnode) {
|
||||
return m(Component, {key: vnode.attrs.id})
|
||||
}}
|
||||
"/:key": Component,
|
||||
})
|
||||
callAsync(function() {
|
||||
o(oninit.callCount).equals(1)
|
||||
|
|
@ -661,7 +817,7 @@ o.spec("route", function() {
|
|||
o(matchCount).equals(1)
|
||||
o(renderCount).equals(1)
|
||||
|
||||
redrawService.redraw()
|
||||
mountRedraw.redraw()
|
||||
throttleMock.fire()
|
||||
|
||||
o(matchCount).equals(1)
|
||||
|
|
@ -697,7 +853,7 @@ o.spec("route", function() {
|
|||
o(matchCount).equals(1)
|
||||
o(renderCount).equals(1)
|
||||
|
||||
redrawService.redraw()
|
||||
mountRedraw.redraw()
|
||||
throttleMock.fire()
|
||||
|
||||
o(matchCount).equals(1)
|
||||
|
|
@ -1022,7 +1178,7 @@ o.spec("route", function() {
|
|||
o(view.callCount).equals(1)
|
||||
o(onmatch.callCount).equals(1)
|
||||
|
||||
redrawService.redraw()
|
||||
mountRedraw.redraw()
|
||||
throttleMock.fire()
|
||||
|
||||
o(view.callCount).equals(2)
|
||||
|
|
@ -1286,10 +1442,10 @@ o.spec("route", function() {
|
|||
})
|
||||
var before = i
|
||||
|
||||
redrawService.redraw()
|
||||
redrawService.redraw()
|
||||
redrawService.redraw()
|
||||
redrawService.redraw()
|
||||
mountRedraw.redraw()
|
||||
mountRedraw.redraw()
|
||||
mountRedraw.redraw()
|
||||
mountRedraw.redraw()
|
||||
var after = i
|
||||
|
||||
throttleMock.fire()
|
||||
|
|
|
|||
282
api/tests/test-routerGetSet.js
Normal file
282
api/tests/test-routerGetSet.js
Normal file
|
|
@ -0,0 +1,282 @@
|
|||
"use strict"
|
||||
|
||||
// Low-priority TODO: remove the dependency on the renderer here.
|
||||
var o = require("../../ospec/ospec")
|
||||
var callAsync = require("../../test-utils/callAsync")
|
||||
var browserMock = require("../../test-utils/browserMock")
|
||||
var throttleMocker = require("../../test-utils/throttleMock")
|
||||
|
||||
var callAsync = require("../../test-utils/callAsync")
|
||||
var apiMountRedraw = require("../../api/mount-redraw")
|
||||
var coreRenderer = require("../../render/render")
|
||||
var apiRouter = require("../../api/router")
|
||||
|
||||
o.spec("route.get/route.set", function() {
|
||||
void [{protocol: "http:", hostname: "localhost"}, {protocol: "file:", hostname: "/"}].forEach(function(env) {
|
||||
void ["#", "?", "", "#!", "?!", "/foo"].forEach(function(prefix) {
|
||||
o.spec("using prefix `" + prefix + "` starting on " + env.protocol + "//" + env.hostname, function() {
|
||||
var $window, root, mountRedraw, route, throttleMock
|
||||
|
||||
o.beforeEach(function() {
|
||||
$window = browserMock(env)
|
||||
throttleMock = throttleMocker()
|
||||
|
||||
root = $window.document.body
|
||||
|
||||
mountRedraw = apiMountRedraw(coreRenderer($window), throttleMock.schedule, console)
|
||||
route = apiRouter($window, mountRedraw)
|
||||
route.prefix(prefix)
|
||||
})
|
||||
|
||||
o.afterEach(function() {
|
||||
o(throttleMock.queueLength()).equals(0)
|
||||
})
|
||||
|
||||
o("gets route", function() {
|
||||
$window.location.href = prefix + "/test"
|
||||
route(root, "/test", {"/test": {view: function() {}}})
|
||||
|
||||
o(route.get()).equals("/test")
|
||||
})
|
||||
|
||||
o("gets route w/ params", function() {
|
||||
$window.location.href = prefix + "/other/x/y/z?c=d#e=f"
|
||||
|
||||
route(root, "/other/x/y/z?c=d#e=f", {
|
||||
"/test": {view: function() {}},
|
||||
"/other/:a/:b...": {view: function() {}},
|
||||
})
|
||||
|
||||
o(route.get()).equals("/other/x/y/z?c=d#e=f")
|
||||
})
|
||||
|
||||
o("gets route w/ escaped unicode", function() {
|
||||
$window.location.href = prefix + encodeURI("/ö/é/å?ö=ö#ö=ö")
|
||||
|
||||
route(root, "/ö/é/å?ö=ö#ö=ö", {
|
||||
"/test": {view: function() {}},
|
||||
"/ö/:a/:b...": {view: function() {}},
|
||||
})
|
||||
|
||||
o(route.get()).equals("/ö/é/å?ö=ö#ö=ö")
|
||||
})
|
||||
|
||||
o("gets route w/ unicode", function() {
|
||||
$window.location.href = prefix + "/ö/é/å?ö=ö#ö=ö"
|
||||
|
||||
route(root, "/ö/é/å?ö=ö#ö=ö", {
|
||||
"/test": {view: function() {}},
|
||||
"/ö/:a/:b...": {view: function() {}},
|
||||
})
|
||||
|
||||
o(route.get()).equals("/ö/é/å?ö=ö#ö=ö")
|
||||
})
|
||||
|
||||
o("sets path asynchronously", function(done) {
|
||||
$window.location.href = prefix + "/a"
|
||||
var spy1 = o.spy()
|
||||
var spy2 = o.spy()
|
||||
|
||||
route(root, "/a", {
|
||||
"/a": {view: spy1},
|
||||
"/b": {view: spy2},
|
||||
})
|
||||
|
||||
o(spy1.callCount).equals(1)
|
||||
o(spy2.callCount).equals(0)
|
||||
route.set("/b")
|
||||
o(spy1.callCount).equals(1)
|
||||
o(spy2.callCount).equals(0)
|
||||
callAsync(function() {
|
||||
throttleMock.fire()
|
||||
|
||||
o(spy1.callCount).equals(1)
|
||||
o(spy2.callCount).equals(1)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
o("sets fallback asynchronously", function(done) {
|
||||
$window.location.href = prefix + "/b"
|
||||
var spy1 = o.spy()
|
||||
var spy2 = o.spy()
|
||||
|
||||
route(root, "/a", {
|
||||
"/a": {view: spy1},
|
||||
"/b": {view: spy2},
|
||||
})
|
||||
|
||||
o(spy1.callCount).equals(0)
|
||||
o(spy2.callCount).equals(1)
|
||||
route.set("/c")
|
||||
o(spy1.callCount).equals(0)
|
||||
o(spy2.callCount).equals(1)
|
||||
callAsync(function() {
|
||||
// Yep, before even the throttle mechanism takes hold.
|
||||
o(route.get()).equals("/b")
|
||||
callAsync(function() {
|
||||
// Yep, before even the throttle mechanism takes hold.
|
||||
o(route.get()).equals("/a")
|
||||
throttleMock.fire()
|
||||
|
||||
o(spy1.callCount).equals(1)
|
||||
o(spy2.callCount).equals(1)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
o("exposes new route asynchronously", function(done) {
|
||||
$window.location.href = prefix + "/test"
|
||||
route(root, "/test", {
|
||||
"/test": {view: function() {}},
|
||||
"/other/:a/:b...": {view: function() {}},
|
||||
})
|
||||
|
||||
route.set("/other/x/y/z?c=d#e=f")
|
||||
callAsync(function() {
|
||||
// Yep, before even the throttle mechanism takes hold.
|
||||
o(route.get()).equals("/other/x/y/z?c=d#e=f")
|
||||
throttleMock.fire()
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
o("exposes new escaped unicode route asynchronously", function(done) {
|
||||
$window.location.href = prefix + "/test"
|
||||
route(root, "/test", {
|
||||
"/test": {view: function() {}},
|
||||
"/ö": {view: function() {}},
|
||||
})
|
||||
|
||||
route.set(encodeURI("/ö?ö=ö#ö=ö"))
|
||||
callAsync(function() {
|
||||
// Yep, before even the throttle mechanism takes hold.
|
||||
o(route.get()).equals("/ö?ö=ö#ö=ö")
|
||||
throttleMock.fire()
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
o("exposes new unescaped unicode route asynchronously", function(done) {
|
||||
$window.location.href = "file://" + prefix + "/test"
|
||||
route(root, "/test", {
|
||||
"/test": {view: function() {}},
|
||||
"/ö": {view: function() {}},
|
||||
})
|
||||
|
||||
route.set("/ö?ö=ö#ö=ö")
|
||||
callAsync(function() {
|
||||
// Yep, before even the throttle mechanism takes hold.
|
||||
o(route.get()).equals("/ö?ö=ö#ö=ö")
|
||||
throttleMock.fire()
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
o("exposes new route asynchronously on fallback mode", function(done) {
|
||||
$window.location.href = prefix + "/test"
|
||||
route(root, "/test", {
|
||||
"/test": {view: function() {}},
|
||||
"/other/:a/:b...": {view: function() {}},
|
||||
})
|
||||
|
||||
route.set("/other/x/y/z?c=d#e=f")
|
||||
callAsync(function() {
|
||||
// Yep, before even the throttle mechanism takes hold.
|
||||
o(route.get()).equals("/other/x/y/z?c=d#e=f")
|
||||
throttleMock.fire()
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
o("sets route via pushState/onpopstate", function(done) {
|
||||
$window.location.href = prefix + "/test"
|
||||
route(root, "/test", {
|
||||
"/test": {view: function() {}},
|
||||
"/other/:a/:b...": {view: function() {}},
|
||||
})
|
||||
|
||||
callAsync(function() {
|
||||
$window.history.pushState(null, null, prefix + "/other/x/y/z?c=d#e=f")
|
||||
$window.onpopstate()
|
||||
|
||||
callAsync(function() {
|
||||
// Yep, before even the throttle mechanism takes hold.
|
||||
o(route.get()).equals("/other/x/y/z?c=d#e=f")
|
||||
throttleMock.fire()
|
||||
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
o("sets parameterized route", function(done) {
|
||||
$window.location.href = prefix + "/test"
|
||||
route(root, "/test", {
|
||||
"/test": {view: function() {}},
|
||||
"/other/:a/:b...": {view: function() {}},
|
||||
})
|
||||
|
||||
route.set("/other/:a/:b", {a: "x", b: "y/z", c: "d", e: "f"})
|
||||
callAsync(function() {
|
||||
// Yep, before even the throttle mechanism takes hold.
|
||||
o(route.get()).equals("/other/x/y%2Fz?c=d&e=f")
|
||||
throttleMock.fire()
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
o("replace:true works", function(done) {
|
||||
$window.location.href = prefix + "/test"
|
||||
route(root, "/test", {
|
||||
"/test": {view: function() {}},
|
||||
"/other": {view: function() {}},
|
||||
})
|
||||
|
||||
route.set("/other", null, {replace: true})
|
||||
|
||||
callAsync(function() {
|
||||
throttleMock.fire()
|
||||
$window.history.back()
|
||||
o($window.location.href).equals(env.protocol + "//" + (env.hostname === "/" ? "" : env.hostname) + "/")
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
o("replace:false works", function(done) {
|
||||
$window.location.href = prefix + "/test"
|
||||
route(root, "/test", {
|
||||
"/test": {view: function() {}},
|
||||
"/other": {view: function() {}},
|
||||
})
|
||||
|
||||
route.set("/other", null, {replace: false})
|
||||
|
||||
callAsync(function() {
|
||||
throttleMock.fire()
|
||||
$window.history.back()
|
||||
var slash = prefix[0] === "/" ? "" : "/"
|
||||
o($window.location.href).equals(env.protocol + "//" + (env.hostname === "/" ? "" : env.hostname) + slash + (prefix ? prefix + "/" : "") + "test")
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
o("state works", function(done) {
|
||||
$window.location.href = prefix + "/test"
|
||||
route(root, "/test", {
|
||||
"/test": {view: function() {}},
|
||||
"/other": {view: function() {}},
|
||||
})
|
||||
|
||||
route.set("/other", null, {state: {a: 1}})
|
||||
callAsync(function() {
|
||||
throttleMock.fire()
|
||||
o($window.history.state).deepEquals({a: 1})
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue