make setPath always async
This commit is contained in:
parent
520d15a060
commit
e8e0bae726
10 changed files with 154 additions and 61 deletions
|
|
@ -38,7 +38,7 @@ Mithril's `config` method is now replaced by several lifecycle methods to improv
|
|||
|
||||
## Robustness
|
||||
|
||||
There are over 2600 assertions in the test suite, and tests cover even difficult-to-test things like `location.href`, `element.innerHTML` and `XMLHttpRequest` usage.
|
||||
There are over 2700 assertions in the test suite, and tests cover even difficult-to-test things like `location.href`, `element.innerHTML` and `XMLHttpRequest` usage.
|
||||
|
||||
## Modularity
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
<script src="../../test-utils/domMock.js"></script>
|
||||
<script src="../../test-utils/pushStateMock.js"></script>
|
||||
|
||||
<script src="../../util/stream.js"></script>
|
||||
<script src="../../render/node.js"></script>
|
||||
<script src="../../render/trust.js"></script>
|
||||
<script src="../../render/hyperscript.js"></script>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
"use strict"
|
||||
|
||||
var o = require("../../ospec/ospec")
|
||||
var callAsync = require("../../test-utils/callAsync")
|
||||
var pushStateMock = require("../../test-utils/pushStateMock")
|
||||
var domMock = require("../../test-utils/domMock")
|
||||
|
||||
|
|
@ -28,7 +29,7 @@ o.spec("route", function() {
|
|||
route = apiRouter($window, coreRenderer($window), redraw)
|
||||
})
|
||||
|
||||
o("renders into `root`", function() {
|
||||
o("renders into `root`", function(done) {
|
||||
route(root, "/", {
|
||||
"/" : {
|
||||
view: function() {
|
||||
|
|
@ -37,7 +38,11 @@ o.spec("route", function() {
|
|||
}
|
||||
})
|
||||
|
||||
o(root.firstChild.nodeName).equals("DIV")
|
||||
callAsync(function() {
|
||||
o(root.firstChild.nodeName).equals("DIV")
|
||||
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
o("redraws when render function is executed", function(done) {
|
||||
|
|
@ -55,19 +60,21 @@ o.spec("route", function() {
|
|||
}
|
||||
})
|
||||
|
||||
o(oninit.callCount).equals(1)
|
||||
callAsync(function() {
|
||||
o(oninit.callCount).equals(1)
|
||||
|
||||
redraw.publish()
|
||||
redraw.publish()
|
||||
|
||||
// Wrapped to give time for the rate-limited redraw to fire
|
||||
setTimeout(function() {
|
||||
o(onupdate.callCount).equals(1)
|
||||
// Wrapped to give time for the rate-limited redraw to fire
|
||||
setTimeout(function() {
|
||||
o(onupdate.callCount).equals(1)
|
||||
|
||||
done()
|
||||
}, FRAME_BUDGET)
|
||||
done()
|
||||
}, FRAME_BUDGET)
|
||||
})
|
||||
})
|
||||
|
||||
o("redraws on events", function(done, timeout) {
|
||||
o("redraws on events", function(done) {
|
||||
var onupdate = o.spy()
|
||||
var oninit = o.spy()
|
||||
var onclick = o.spy()
|
||||
|
|
@ -87,21 +94,23 @@ o.spec("route", function() {
|
|||
}
|
||||
})
|
||||
|
||||
root.firstChild.dispatchEvent(e)
|
||||
callAsync(function() {
|
||||
root.firstChild.dispatchEvent(e)
|
||||
|
||||
o(oninit.callCount).equals(1)
|
||||
o(oninit.callCount).equals(1)
|
||||
|
||||
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)
|
||||
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)
|
||||
|
||||
// Wrapped to give time for the rate-limited redraw to fire
|
||||
setTimeout(function() {
|
||||
o(onupdate.callCount).equals(1)
|
||||
// Wrapped to give time for the rate-limited redraw to fire
|
||||
setTimeout(function() {
|
||||
o(onupdate.callCount).equals(1)
|
||||
|
||||
done()
|
||||
}, FRAME_BUDGET)
|
||||
done()
|
||||
}, FRAME_BUDGET)
|
||||
})
|
||||
})
|
||||
|
||||
o("event handlers can skip redraw", function(done) {
|
||||
|
|
@ -126,19 +135,21 @@ o.spec("route", function() {
|
|||
}
|
||||
})
|
||||
|
||||
root.firstChild.dispatchEvent(e)
|
||||
callAsync(function() {
|
||||
root.firstChild.dispatchEvent(e)
|
||||
|
||||
o(oninit.callCount).equals(1)
|
||||
o(oninit.callCount).equals(1)
|
||||
|
||||
// Wrapped to ensure no redraw fired
|
||||
setTimeout(function() {
|
||||
o(onupdate.callCount).equals(0)
|
||||
// Wrapped to ensure no redraw fired
|
||||
setTimeout(function() {
|
||||
o(onupdate.callCount).equals(0)
|
||||
|
||||
done()
|
||||
}, FRAME_BUDGET)
|
||||
done()
|
||||
}, FRAME_BUDGET)
|
||||
})
|
||||
})
|
||||
|
||||
o("changes location on route.link", function() {
|
||||
o("changes location on route.link", function(done) {
|
||||
var e = $window.document.createEvent("MouseEvents")
|
||||
|
||||
e.initEvent("click", true, true)
|
||||
|
|
@ -161,10 +172,14 @@ o.spec("route", function() {
|
|||
}
|
||||
})
|
||||
|
||||
o($window.location.href).equals("http://localhost/?/")
|
||||
callAsync(function() {
|
||||
o($window.location.href).equals("http://localhost/?/")
|
||||
|
||||
root.firstChild.dispatchEvent(e)
|
||||
root.firstChild.dispatchEvent(e)
|
||||
|
||||
o($window.location.href).equals("http://localhost/?/test")
|
||||
o($window.location.href).equals("http://localhost/?/test")
|
||||
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
73
mithril.js
73
mithril.js
|
|
@ -16,8 +16,8 @@ function initStream(stream, args) {
|
|||
stream.constructor = createStream
|
||||
stream._state = {id: guid++, value: undefined, error: undefined, state: 0, derive: undefined, recover: undefined, deps: {}, parents: [], errorStream: undefined, endStream: undefined}
|
||||
stream.map = map, stream.ap = ap, stream.of = createStream
|
||||
stream.valueOf = valueOf
|
||||
stream.catch = doCatch
|
||||
stream.valueOf = valueOf, stream.toJSON = toJSON
|
||||
stream.run = run, stream.catch = doCatch
|
||||
|
||||
Object.defineProperties(stream, {
|
||||
error: {get: function() {
|
||||
|
|
@ -46,15 +46,18 @@ function initStream(stream, args) {
|
|||
})
|
||||
}
|
||||
function updateStream(stream, value, error) {
|
||||
if (!absorbStream(stream, value, false) && !absorbStream(stream, error, true)) {
|
||||
updateState(stream, value, error)
|
||||
for (var id in stream._state.deps) updateDependency(stream._state.deps[id], false)
|
||||
finalize(stream)
|
||||
}
|
||||
updateState(stream, value, error)
|
||||
for (var id in stream._state.deps) updateDependency(stream._state.deps[id], false)
|
||||
finalize(stream)
|
||||
}
|
||||
function updateState(stream, value, error) {
|
||||
error = unwrapError(value, error)
|
||||
if (error !== undefined && typeof stream._state.recover === "function") {
|
||||
try {updateValues(stream, stream._state.recover(), undefined)}
|
||||
try {
|
||||
var recovered = stream._state.recover()
|
||||
if (recovered === HALT) return
|
||||
updateValues(stream, recovered, undefined)
|
||||
}
|
||||
catch (e) {updateValues(stream, undefined, e)}
|
||||
}
|
||||
else updateValues(stream, value, error)
|
||||
|
|
@ -73,7 +76,8 @@ function updateDependency(stream, mustSync) {
|
|||
else {
|
||||
try {
|
||||
var value = state.derive()
|
||||
if (!absorbStream(stream, value)) updateState(stream, value, undefined)
|
||||
if (value === HALT) return
|
||||
updateState(stream, value, undefined)
|
||||
}
|
||||
catch (e) {
|
||||
updateState(stream, undefined, e)
|
||||
|
|
@ -81,29 +85,28 @@ function updateDependency(stream, mustSync) {
|
|||
}
|
||||
}
|
||||
}
|
||||
function absorbStream(stream, value, isError) {
|
||||
function unwrapError(value, error) {
|
||||
if (value != null && value.constructor === createStream) {
|
||||
if (value._state.state === 2) {
|
||||
stream.end(true)
|
||||
stream(value())
|
||||
}
|
||||
else if (value._state.error) stream.error(value.error())
|
||||
else if (value._state.state === 0) return true
|
||||
else if (!isError) stream(value())
|
||||
else stream.error(value())
|
||||
return true
|
||||
if (value._state.error !== undefined) error = value._state.error
|
||||
else error = unwrapError(value._state.value, value._state.error)
|
||||
}
|
||||
return false
|
||||
return error
|
||||
}
|
||||
function finalize(stream) {
|
||||
stream._state.changed = false
|
||||
for (var id in stream._state.deps) stream._state.deps[id]._state.changed = false
|
||||
}
|
||||
function run(fn) {
|
||||
var self = createStream(), stream = this
|
||||
return initDependency(self, [stream], function() {
|
||||
return absorb(self, fn(stream()))
|
||||
}, undefined)
|
||||
}
|
||||
function doCatch(fn) {
|
||||
var stream = this
|
||||
var self = createStream(), stream = this
|
||||
var derive = function() {return stream._state.value}
|
||||
var recover = function() {return fn(stream._state.error)}
|
||||
return initDependency(createStream(), [stream], derive, recover)
|
||||
var recover = function() {return absorb(self, fn(stream._state.error))}
|
||||
return initDependency(self, [stream], derive, recover)
|
||||
}
|
||||
function combine(fn, streams) {
|
||||
return initDependency(createStream(), streams, function() {
|
||||
|
|
@ -112,6 +115,16 @@ function combine(fn, streams) {
|
|||
return fn.apply(this, streams.concat([streams.filter(changed)]))
|
||||
}, undefined)
|
||||
}
|
||||
function absorb(stream, value) {
|
||||
if (value != null && value.constructor === createStream) {
|
||||
value.error.map(stream.error)
|
||||
value.map(stream)
|
||||
if (value._state.state === 0) return HALT
|
||||
if (value._state.error) throw value._state.error
|
||||
value = value._state.value
|
||||
}
|
||||
return value
|
||||
}
|
||||
function initDependency(dep, streams, derive, recover) {
|
||||
var state = dep._state
|
||||
state.derive = derive
|
||||
|
|
@ -145,6 +158,7 @@ function unregisterStream(stream) {
|
|||
function map(fn) {return combine(function(stream) {return fn(stream())}, [this])}
|
||||
function ap(stream) {return combine(function(s1, s2) {return s1()(s2())}, [this, stream])}
|
||||
function valueOf() {return this._state.value}
|
||||
function toJSON() {return JSON.stringify(this._state.value)}
|
||||
function active(stream) {return stream._state.state === 1}
|
||||
function changed(stream) {return stream._state.changed}
|
||||
function notEnded(stream) {return stream._state.state !== 2}
|
||||
|
|
@ -154,7 +168,7 @@ function reject(e) {
|
|||
stream.error(e)
|
||||
return stream
|
||||
}
|
||||
var Stream = {stream: createStream, combine: combine, reject: reject}
|
||||
var Stream = {stream: createStream, combine: combine, reject: reject, HALT: HALT}
|
||||
function Node(tag, key, attrs, children, text, dom) {
|
||||
return {tag: tag, key: key, attrs: attrs, children: children, text: text, dom: dom, domSize: undefined, state: {}, events: undefined, instance: undefined}
|
||||
}
|
||||
|
|
@ -782,7 +796,11 @@ var requestService = function($window) {
|
|||
|
||||
stream(response)
|
||||
}
|
||||
else stream.error(new Error(xhr.responseText))
|
||||
else {
|
||||
var error = new Error(xhr.responseText)
|
||||
for (var key in response) error[key] = response[key]
|
||||
stream.error(error)
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
stream.error(e)
|
||||
|
|
@ -892,6 +910,7 @@ var parseQueryString = function(string) {
|
|||
}
|
||||
var coreRouter = function($window) {
|
||||
var supportsPushState = typeof $window.history.pushState === "function" && $window.location.protocol !== "file:"
|
||||
var callAsync = typeof setImmediate === "function" ? setImmediate : setTimeout
|
||||
var prefix = "#!"
|
||||
function setPrefix(value) {prefix = value}
|
||||
function normalize(fragment) {
|
||||
|
|
@ -939,7 +958,7 @@ var coreRouter = function($window) {
|
|||
if (supportsPushState) {
|
||||
if (options && options.replace) $window.history.replaceState(null, null, prefix + path)
|
||||
else $window.history.pushState(null, null, prefix + path)
|
||||
$window.onpopstate()
|
||||
callAsync($window.onpopstate)
|
||||
}
|
||||
else $window.location.href = prefix + path
|
||||
}
|
||||
|
|
@ -991,7 +1010,7 @@ var throttle = function(callback) {
|
|||
}
|
||||
else if (pending === null) {
|
||||
pending = timeout(function() {
|
||||
pending = 0
|
||||
pending = null
|
||||
callback()
|
||||
last = new Date().getTime()
|
||||
}, time - (now - last))
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ var parseQueryString = require("../querystring/parse")
|
|||
|
||||
module.exports = function($window) {
|
||||
var supportsPushState = typeof $window.history.pushState === "function" && $window.location.protocol !== "file:"
|
||||
var callAsync = typeof setImmediate === "function" ? setImmediate : setTimeout
|
||||
|
||||
var prefix = "#!"
|
||||
function setPrefix(value) {prefix = value}
|
||||
|
|
@ -60,7 +61,7 @@ module.exports = function($window) {
|
|||
if (supportsPushState) {
|
||||
if (options && options.replace) $window.history.replaceState(null, null, prefix + path)
|
||||
else $window.history.pushState(null, null, prefix + path)
|
||||
$window.onpopstate()
|
||||
callAsync($window.onpopstate)
|
||||
}
|
||||
else $window.location.href = prefix + path
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
<body>
|
||||
<script src="../../module/module.js"></script>
|
||||
<script src="../../ospec/ospec.js"></script>
|
||||
<script src="../../test-utils/callAsync.js"></script>
|
||||
<script src="../../test-utils/parseURL.js"></script>
|
||||
<script src="../../test-utils/pushStateMock.js"></script>
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,13 @@ o.spec("Router.defineRoutes", function() {
|
|||
onFail = o.spy()
|
||||
})
|
||||
|
||||
o("calls onRouteChange on init", function() {
|
||||
$window.location.href = prefix + "/a"
|
||||
router.defineRoutes({"/a": {data: 1}}, onRouteChange, onFail)
|
||||
|
||||
o(onRouteChange.callCount).equals(1)
|
||||
})
|
||||
|
||||
o("resolves to route", function() {
|
||||
$window.location.href = prefix + "/test"
|
||||
router.defineRoutes({"/test": {data: 1}}, onRouteChange, onFail)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
"use strict"
|
||||
|
||||
var o = require("../../ospec/ospec")
|
||||
var callAsync = require("../../test-utils/callAsync")
|
||||
var pushStateMock = require("../../test-utils/pushStateMock")
|
||||
var Router = require("../../router/router")
|
||||
|
||||
|
|
@ -17,6 +18,28 @@ o.spec("Router.setPath", function() {
|
|||
onFail = o.spy()
|
||||
})
|
||||
|
||||
o("setPath calls onRouteChange asynchronously", function(done) {
|
||||
$window.location.href = prefix + "/a"
|
||||
router.defineRoutes({"/a": {data: 1}, "/b": {data: 2}}, onRouteChange, onFail)
|
||||
router.setPath("/b")
|
||||
|
||||
o(onRouteChange.callCount).equals(1)
|
||||
callAsync(function() {
|
||||
o(onRouteChange.callCount).equals(2)
|
||||
done()
|
||||
})
|
||||
})
|
||||
o("setPath calls onFail asynchronously", function(done) {
|
||||
$window.location.href = prefix + "/a"
|
||||
router.defineRoutes({"/a": {data: 1}, "/b": {data: 2}}, onRouteChange, onFail)
|
||||
router.setPath("/c")
|
||||
|
||||
o(onFail.callCount).equals(0)
|
||||
callAsync(function() {
|
||||
o(onFail.callCount).equals(1)
|
||||
done()
|
||||
})
|
||||
})
|
||||
o("sets route via API", function() {
|
||||
$window.location.href = prefix + "/test"
|
||||
router.defineRoutes({"/test": {data: 1}, "/other/:a/:b...": {data: 2}}, onRouteChange, onFail)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
<script src="../../test-utils/pushStateMock.js"></script>
|
||||
<script src="../../test-utils/xhrMock.js"></script>
|
||||
<script src="../../test-utils/domMock.js"></script>
|
||||
<script src="test-callAsync.js"></script>
|
||||
<script src="test-parseURL.js"></script>
|
||||
<script src="test-pushStateMock.js"></script>
|
||||
<script src="test-xhrMock.js"></script>
|
||||
|
|
|
|||
25
test-utils/tests/test-callAsync.js
Normal file
25
test-utils/tests/test-callAsync.js
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
"use strict"
|
||||
|
||||
var o = require("../../ospec/ospec")
|
||||
var callAsync = require("../../test-utils/callAsync")
|
||||
|
||||
o.spec("callAsync", function() {
|
||||
o("works", function(done) {
|
||||
var count = 0
|
||||
callAsync(function() {
|
||||
o(count).equals(1)
|
||||
done()
|
||||
})
|
||||
count++
|
||||
})
|
||||
o("gets called before setTimeout", function(done) {
|
||||
var timeout
|
||||
callAsync(function() {
|
||||
clearTimeout(timeout)
|
||||
done()
|
||||
})
|
||||
timeout = setTimeout(function() {
|
||||
throw new Error("callAsync was called too slow")
|
||||
}, 0)
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue