fix tests

This commit is contained in:
Leo Horie 2016-12-06 00:09:09 -05:00
parent adabc37fd7
commit 3134202d24
8 changed files with 145 additions and 119 deletions

View file

@ -34,6 +34,6 @@ There are over 4000 assertions in the test suite, and tests cover even difficult
## Modularity
Despite the huge improvements in performance and modularity, the new codebase is smaller than v0.2.x, currently clocking at <!-- size -->7.42 KB<!-- /size --> min+gzip
Despite the huge improvements in performance and modularity, the new codebase is smaller than v0.2.x, currently clocking at <!-- size -->7.44 KB<!-- /size --> min+gzip
In addition, Mithril is now completely modular: you can import only the modules that you need and easily integrate 3rd party modules if you wish to use a different library for routing, ajax, and even rendering

View file

@ -7,25 +7,29 @@ module.exports = function($window, redrawService) {
var routeService = coreRouter($window)
var identity = function(v) {return v}
var resolver, component, attrs, currentPath, waiting
var resolver, component, attrs, currentPath, resolve
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 update = function(routeResolver, comp, params, path) {
resolver = routeResolver, component = comp, attrs = params, currentPath = path, waiting = null
resolver = routeResolver, component = comp, attrs = params, currentPath = path, resolve = null
resolver.render = routeResolver.render || identity
render()
}
var render = function() {
if (resolver != null) redrawService.render(root, resolver.render(Vnode(component, attrs.key, attrs)))
if (resolver != null) redrawService.render(root, resolver.render(Vnode(component || "div", attrs.key, attrs)))
}
routeService.defineRoutes(routes, function(payload, params, path) {
if (payload.view) update({}, payload, params, path)
else {
if (payload.onmatch) {
if (waiting != null) update(payload, component, params, path)
if (resolve != null) update(payload, component, params, path)
else {
waiting = Promise.resolve(payload.onmatch(params, path))
.then(function(comp) {update(payload, comp != null ? comp : component, params, path)})
resolve = function(resolved) {
update(payload, resolved, params, path)
}
Promise.resolve(payload.onmatch(params, path)).then(function(resolved) {
if (resolve != null) resolve(resolved)
})
}
}
else update(payload, "div", params, path)

View file

@ -8,6 +8,7 @@ var m = require("../../render/hyperscript")
var coreRenderer = require("../../render/render")
var apiRedraw = require("../../api/redraw")
var apiRouter = require("../../api/router")
var Promise = require("../../promise/promise")
o.spec("route", function() {
void [{protocol: "http:", hostname: "localhost"}, {protocol: "file:", hostname: "/"}].forEach(function(env) {
@ -229,7 +230,7 @@ o.spec("route", function() {
o($window.location.href).equals(env.protocol + "//" + (env.hostname === "/" ? "" : env.hostname) + slash + (prefix ? prefix + "/" : "") + "test")
})
o("accepts RouteResolver", function() {
o("accepts RouteResolver", function(done) {
var matchCount = 0
var renderCount = 0
var Component = {
@ -239,13 +240,13 @@ o.spec("route", function() {
}
var resolver = {
onmatch: function(resolve, args, requestedPath) {
onmatch: function(args, requestedPath) {
matchCount++
o(args.id).equals("abc")
o(requestedPath).equals("/abc")
o(this).equals(resolver)
resolve(Component)
return Component
},
render: function(vnode) {
renderCount++
@ -262,12 +263,15 @@ o.spec("route", function() {
"/:id" : resolver
})
o(matchCount).equals(1)
o(renderCount).equals(1)
o(root.firstChild.nodeName).equals("DIV")
setTimeout(function() {
o(matchCount).equals(1)
o(renderCount).equals(1)
o(root.firstChild.nodeName).equals("DIV")
done()
}, 20)
})
o("accepts RouteResolver without `render` method as payload", function() {
o("accepts RouteResolver without `render` method as payload", function(done) {
var matchCount = 0
var Component = {
view: function() {
@ -278,20 +282,22 @@ o.spec("route", function() {
$window.location.href = prefix + "/abc"
route(root, "/abc", {
"/:id" : {
onmatch: function(resolve, args, requestedPath) {
onmatch: function(args, requestedPath) {
matchCount++
o(args.id).equals("abc")
o(requestedPath).equals("/abc")
resolve(Component)
return Component
},
},
})
o(matchCount).equals(1)
o(root.firstChild.nodeName).equals("DIV")
setTimeout(function() {
o(matchCount).equals(1)
o(root.firstChild.nodeName).equals("DIV")
done()
}, 20)
})
o("changing `vnode.key` in `render` resets the component", function(done, timeout){
@ -378,7 +384,7 @@ o.spec("route", function() {
}, FRAME_BUDGET)
})
o("calls onmatch and view correct number of times", function() {
o("calls onmatch and view correct number of times", function(done) {
var matchCount = 0
var renderCount = 0
var Component = {
@ -390,9 +396,9 @@ o.spec("route", function() {
$window.location.href = prefix + "/"
route(root, "/", {
"/" : {
onmatch: function(resolve) {
onmatch: function() {
matchCount++
resolve(Component)
return Component
},
render: function(vnode) {
renderCount++
@ -401,13 +407,52 @@ o.spec("route", function() {
},
})
o(matchCount).equals(1)
o(renderCount).equals(1)
setTimeout(function() {
o(matchCount).equals(1)
o(renderCount).equals(1)
redrawService.redraw()
redrawService.redraw()
o(matchCount).equals(1)
o(renderCount).equals(2)
o(matchCount).equals(1)
o(renderCount).equals(2)
done()
}, 20)
})
o("calls onmatch and view correct number of times when not onmatch returns undefined", function(done) {
var matchCount = 0
var renderCount = 0
var Component = {
view: function() {
return m("div")
}
}
$window.location.href = prefix + "/"
route(root, "/", {
"/" : {
onmatch: function() {
matchCount++
},
render: function(vnode) {
renderCount++
return {tag: Component}
},
},
})
setTimeout(function() {
o(matchCount).equals(1)
o(renderCount).equals(1)
redrawService.redraw()
o(matchCount).equals(1)
o(renderCount).equals(2)
done()
}, 20)
})
o("onmatch can redirect to another route", function(done) {
@ -458,38 +503,11 @@ o.spec("route", function() {
}, FRAME_BUDGET)
})
o("onmatch resolution callback resolves at most once", function(done) {
var resolveCount = 0
var resolvedComponent
var A = {view: function() {}}
var B = {view: function() {}}
var C = {view: function() {}}
$window.location.href = prefix + "/"
route(root, "/", {
"/": {
onmatch: function(resolve) {
resolve(A)
resolve(B)
callAsync(function() {resolve(C)})
},
render: function(vnode) {
resolveCount++
resolvedComponent = vnode.tag
}
},
})
setTimeout(function() {
o(resolveCount).equals(1)
o(resolvedComponent).equals(A)
done()
}, FRAME_BUDGET)
})
o("the previous view redraws while onmatch resolution is pending (#1268)", function(done) {
var view = o.spy()
var onmatch = o.spy()
var onmatch = o.spy(function() {
return new Promise(function() {})
})
$window.location.href = prefix + "/a"
route(root, "/", {
@ -516,7 +534,7 @@ o.spec("route", function() {
})
o("m.route.set(m.route.get()) re-runs the resolution logic (#1180)", function(done){
var onmatch = o.spy(function(resolve) {resolve()})
var onmatch = o.spy()
var render = o.spy(function(){return m("div")})
$window.location.href = prefix + "/"
@ -527,16 +545,18 @@ o.spec("route", function() {
}
})
o(onmatch.callCount).equals(1)
o(render.callCount).equals(1)
route.set(route.get())
setTimeout(function() {
o(onmatch.callCount).equals(2)
o(render.callCount).equals(2)
o(onmatch.callCount).equals(1)
o(render.callCount).equals(1)
done()
route.set(route.get())
setTimeout(function() {
o(onmatch.callCount).equals(2)
o(render.callCount).equals(2)
done()
}, FRAME_BUDGET)
}, FRAME_BUDGET)
})
@ -544,8 +564,12 @@ o.spec("route", function() {
$window.location.href = prefix + "/"
route(root, "/", {
"/": {view: function(){}},
"/2": {onmatch: function(){}}
"/": {view: function() {}},
"/2": {
onmatch: function() {
return new Promise(function() {})
}
}
})
@ -596,8 +620,10 @@ o.spec("route", function() {
$window.location.href = prefix + "/a"
route(root, "/a", {
"/a": {
onmatch: function(resolve) {
setTimeout(resolve, 20)
onmatch: function() {
return new Promise(function(resolve) {
setTimeout(resolve, 20)
})
},
render: function(vnode) {resolved = "a"}
},

View file

@ -107,18 +107,17 @@ A RouterResolver is an object that contains an `onmatch` method and/or a `render
##### routeResolver.onmatch
The `onmatch` hook is called when the router needs to find a component to render. It is called once when a router path changes, but not on subsequent redraws. It can be used to run logic before a component initializes (for example authentication logic)
The `onmatch` hook is called when the router needs to find a component to render. It is called once per router path changes, but not on subsequent redraws while on the same path. It can be used to run logic before a component initializes (for example authentication logic or analytics tracking)
This method also allows you to asynchronously define what component will be rendered, making it suitable for code splitting and asynchronous module loading.
This method also allows you to asynchronously define what component will be rendered, making it suitable for code splitting and asynchronous module loading. To render a component asynchronously return a promise that resolves to a component.
`routeResolver.onmatch(resolve, args, requestedPath)`
`routeResolver.onmatch(args, requestedPath)`
Argument | Type | Description
--------------- | ------------------------ | ---
`resolve` | `Component -> undefined` | Call this function with a component as the first argument to use it as the route's component
`args` | `Object` | The [routing parameters](#routing-parameters)
`requestedPath` | `String` | The router path requested by the last routing action, including interpolated routing parameter values, but without the prefix. When `onmatch` is called, the resolution for this path is not complete and `m.route.get()` still returns the previous path.
**returns** | | Returns `undefined`
Argument | Type | Description
--------------- | ------------------------------ | ---
`args` | `Object` | The [routing parameters](#routing-parameters)
`requestedPath` | `String` | The router path requested by the last routing action, including interpolated routing parameter values, but without the prefix. When `onmatch` is called, the resolution for this path is not complete and `m.route.get()` still returns the previous path.
**returns** | `Promise<Component>|undefined` | Returns a promise that resolves to a component, or undefined
##### routeResolver.render
@ -286,8 +285,8 @@ Instead of mapping a component to a route, you can specify a RouteResolver objec
```javascript
m.route(document.body, "/", {
"/": {
onmatch: function(resolve, args, requestedPath) {
resolve(Home)
onmatch: function(args, requestedPath) {
return Home
},
render: function(vnode) {
return vnode // equivalent to m(Home)
@ -366,10 +365,12 @@ var Login = {
m.route(document.body, "/secret", {
"/secret": {
onmatch: function(resolve) {
if (isLoggedIn) resolve(Home)
else m.route.set("/login")
onmatch: function() {
if (!isLoggedIn) m.route.set("/login")
},
render: function() {
return m(Home)
}
},
"/login": Login
})
@ -401,21 +402,20 @@ module.export = {
```javascript
// index.js
function load(file, done) {
m.request({
function load(file) {
return m.request({
method: "GET",
url: file,
extract: function(xhr) {
return new Function("var module = {};" + xhr.responseText + ";return module.exports;")
}
})
.run(done)
}
m.route(document.body, "/", {
"/": {
onmatch: function(resolve) {
load("Home.js", resolve)
onmatch: function() {
return load("Home.js")
},
},
})
@ -428,9 +428,11 @@ Fortunately, there are a number of tools that facilitate the task of bundling mo
```javascript
m.route(document.body, "/", {
"/": {
onmatch: function(resolve) {
onmatch: function() {
// using Webpack async code splitting
require(['./Home.js'], resolve)
return new Promise(function(resolve) {
require(['./Home.js'], resolve)
})
},
},
})

View file

@ -995,12 +995,12 @@ var coreRouter = function($window) {
return data
}
var asyncId
function debounceAsync(f) {
function debounceAsync(callback0) {
return function() {
if (asyncId != null) return
asyncId = callAsync0(function() {
asyncId = null
f()
callback0()
})
}
}
@ -1044,7 +1044,7 @@ var coreRouter = function($window) {
if (supportsPushState) {
if (options && options.replace) $window.history.replaceState(null, null, prefix1 + path)
else $window.history.pushState(null, null, prefix1 + path)
$window.onpopstate(true)
$window.onpopstate()
}
else $window.location.href = prefix1 + path
}
@ -1090,7 +1090,6 @@ var coreRouter = function($window) {
}
var _20 = function($window, redrawService0) {
var routeService = coreRouter($window)
var identity = function(v) {return v}
var resolver, component, attrs3, currentPath, resolve
var route = function(root, defaultRoute, routes) {
@ -1101,7 +1100,7 @@ var _20 = function($window, redrawService0) {
render1()
}
var render1 = function() {
if (resolver != null) redrawService0.render(root, resolver.render(Vnode(component, attrs3.key, attrs3)))
if (resolver != null) redrawService0.render(root, resolver.render(Vnode(component || "div", attrs3.key, attrs3)))
}
routeService.defineRoutes(routes, function(payload, params, path) {
if (payload.view) update({}, payload, params, path)
@ -1112,9 +1111,9 @@ var _20 = function($window, redrawService0) {
resolve = function(resolved) {
update(payload, resolved, params, path)
}
payload.onmatch(function(resolved) {
Promise.resolve(payload.onmatch(params, path)).then(function(resolved) {
if (resolve != null) resolve(resolved)
}, params, path)
})
}
}
else update(payload, "div", params, path)
@ -1131,9 +1130,9 @@ var _20 = function($window, redrawService0) {
return route
}
m.route = _20(window, redrawService)
m.withAttr = function(attrName, callback0, context) {
m.withAttr = function(attrName, callback1, context) {
return function(e) {
return callback0.call(context || this, attrName in e.currentTarget ? e.currentTarget[attrName] : e.currentTarget.getAttribute(attrName))
return callback1.call(context || this, attrName in e.currentTarget ? e.currentTarget[attrName] : e.currentTarget.getAttribute(attrName))
}
}
var _27 = coreRenderer(window)

8
mithril.min.js vendored
View file

@ -34,8 +34,8 @@ null==a.vnodes&&(a.textContent="");b instanceof Array||(b=[b]);g(a,a.vnodes,r.no
K.setCompletionCallback(H.redraw);y.mount=function(a){return function(c,h){if(null===h)a.render(c,[]),a.unsubscribe(c);else{if(null==h.view)throw Error("m.mount(element, component) expects a component, not a vnode");a.subscribe(c,function(){a.render(c,r(h))});a.redraw()}}}(H);var L=function(a){if(""===a||null==a)return{};"?"===a.charAt(0)&&(a=a.slice(1));a=a.split("&");for(var c={},h={},d=0;d<a.length;d++){var g=a[d].split("="),l=decodeURIComponent(g[0]),g=2===g.length?decodeURIComponent(g[1]):"";
"true"===g?g=!0:"false"===g&&(g=!1);var m=l.split(/\]\[?|\[/),r=c;-1<l.indexOf("[")&&m.pop();for(var n=0;n<m.length;n++){var l=m[n],t=m[n+1],t=""==t||!isNaN(parseInt(t,10)),k=n===m.length-1;""===l&&(l=m.slice(0,n).join(),null==h[l]&&(h[l]=0),l=h[l]++);null==r[l]&&(r[l]=k?g:t?[]:{});r=r[l]}}return c},R=function(a){function c(c){var b=a.location[c].replace(/(?:%[a-f89][a-f0-9])+/gim,decodeURIComponent);"pathname"===c&&"/"!==b[0]&&(b="/"+b);return b}function h(a){return function(){null==t&&(t=r(function(){t=
null;a()}))}}function d(a,b,c){var d=a.indexOf("?"),g=a.indexOf("#"),k=-1<d?d:-1<g?g:a.length;if(-1<d){var d=L(a.slice(d+1,-1<g?g:a.length)),h;for(h in d)b[h]=d[h]}if(-1<g)for(h in b=L(a.slice(g+1)),b)c[h]=b[h];return a.slice(0,k)}function g(){switch(n.charAt(0)){case "#":return c("hash").slice(n.length);case "?":return c("search").slice(n.length)+c("hash");default:return c("pathname").slice(n.length)+c("search")+c("hash")}}function l(c,b,g){var k={},h={};c=d(c,k,h);if(null!=b){for(var l in b)k[l]=
b[l];c=c.replace(/:([^\/]+)/g,function(a,c){delete k[c];return b[c]})}(l=E(k))&&(c+="?"+l);(h=E(h))&&(c+="#"+h);m?(g&&g.replace?a.history.replaceState(null,null,n+c):a.history.pushState(null,null,n+c),a.onpopstate(!0)):a.location.href=n+c}var m="function"===typeof a.history.pushState,r="function"===typeof setImmediate?setImmediate:setTimeout,n="#!",t;return{setPrefix:function(a){n=a},getPath:g,setPath:l,defineRoutes:function(c,b,l){function k(){var a=g(),h={},k=d(a,h,h),m;for(m in c){var n=new RegExp("^"+
b[l];c=c.replace(/:([^\/]+)/g,function(a,c){delete k[c];return b[c]})}(l=E(k))&&(c+="?"+l);(h=E(h))&&(c+="#"+h);m?(g&&g.replace?a.history.replaceState(null,null,n+c):a.history.pushState(null,null,n+c),a.onpopstate()):a.location.href=n+c}var m="function"===typeof a.history.pushState,r="function"===typeof setImmediate?setImmediate:setTimeout,n="#!",t;return{setPrefix:function(a){n=a},getPath:g,setPath:l,defineRoutes:function(c,b,l){function k(){var a=g(),h={},k=d(a,h,h),m;for(m in c){var n=new RegExp("^"+
m.replace(/:[^\/]+?\.{3}/g,"(.*?)").replace(/:[^\/]+/g,"([^\\/]+)")+"/?$");if(n.test(k)){k.replace(n,function(){for(var d=m.match(/:[^\/]+/g)||[],g=[].slice.call(arguments,1,-2),k=0;k<d.length;k++)h[d[k].replace(/:|\./g,"")]=decodeURIComponent(g[k]);b(c[m],h,a,m)});return}}l(a,h)}m?a.onpopstate=h(k):"#"===n.charAt(0)&&(a.onhashchange=k);k()},link:function(a){a.dom.setAttribute("href",n+a.attrs.href);a.dom.onclick=function(a){a.ctrlKey||a.metaKey||a.shiftKey||2===a.which||(a.preventDefault(),a.redraw=
!1,a=this.getAttribute("href"),0===a.indexOf(n)&&(a=a.slice(n.length)),l(a,void 0,void 0))}}}};y.route=function(a,c){var h=R(a),d=function(a){return a},g,l,m,t,n,y=function(a,b,y){if(null==a)throw Error("Ensure the DOM element that was passed to `m.route` is not undefined");var k=function(a,b,c,h){g=a;l=b;m=c;t=h;n=null;g.render=a.render||d;u()},u=function(){null!=g&&c.render(a,g.render(r(l,m.key,m)))};h.defineRoutes(y,function(a,b,c){a.view?k({},a,b,c):a.onmatch?null!=n?k(a,l,b,c):(n=function(d){k(a,
d,b,c)},a.onmatch(function(a){null!=n&&n(a)},b,c)):k(a,"div",b,c)},function(){h.setPath(b)});c.subscribe(a,u)};y.set=h.setPath;y.get=function(){return t};y.prefix=h.setPrefix;y.link=h.link;return y}(window,H);y.withAttr=function(a,c,h){return function(d){return c.call(h||this,a in d.currentTarget?d.currentTarget[a]:d.currentTarget.getAttribute(a))}};var S=O(window);y.render=S.render;y.redraw=H.redraw;y.request=K.request;y.jsonp=K.jsonp;y.parseQueryString=L;y.buildQueryString=E;y.version="1.0.0-rc.6";
"undefined"!==typeof module?module.exports=y:window.m=y};
!1,a=this.getAttribute("href"),0===a.indexOf(n)&&(a=a.slice(n.length)),l(a,void 0,void 0))}}}};y.route=function(a,c){var h=R(a),d=function(a){return a},g,l,m,t,n,y=function(a,b,y){if(null==a)throw Error("Ensure the DOM element that was passed to `m.route` is not undefined");var k=function(a,b,c,h){g=a;l=b;m=c;t=h;n=null;g.render=a.render||d;u()},u=function(){null!=g&&c.render(a,g.render(r(l||"div",m.key,m)))};h.defineRoutes(y,function(a,b,c){a.view?k({},a,b,c):a.onmatch?null!=n?k(a,l,b,c):(n=function(d){k(a,
d,b,c)},Promise.resolve(a.onmatch(b,c)).then(function(a){null!=n&&n(a)})):k(a,"div",b,c)},function(){h.setPath(b)});c.subscribe(a,u)};y.set=h.setPath;y.get=function(){return t};y.prefix=h.setPrefix;y.link=h.link;return y}(window,H);y.withAttr=function(a,c,h){return function(d){return c.call(h||this,a in d.currentTarget?d.currentTarget[a]:d.currentTarget.getAttribute(a))}};var S=O(window);y.render=S.render;y.redraw=H.redraw;y.request=K.request;y.jsonp=K.jsonp;y.parseQueryString=L;y.buildQueryString=
E;y.version="1.0.0-rc.6";"undefined"!==typeof module?module.exports=y:window.m=y};

View file

@ -1,6 +1,5 @@
"use strict"
var Promise = require("../promise/promise")
var buildQueryString = require("../querystring/build")
var parseQueryString = require("../querystring/parse")
@ -18,15 +17,13 @@ module.exports = function($window) {
}
var asyncId
function debounceAsync(f) {
function debounceAsync(callback) {
return function() {
return new Promise(function(resolve, reject) {
if (asyncId != null) return reject()
asyncId = callAsync(function() {
asyncId = null
resolve(f())
})
});
if (asyncId != null) return
asyncId = callAsync(function() {
asyncId = null
callback()
})
}
}
@ -75,12 +72,9 @@ 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)
return $window.onpopstate()
}
else {
$window.location.href = prefix + path
return Promise.resolve(prefix + path)
$window.onpopstate()
}
else $window.location.href = prefix + path
}
function defineRoutes(routes, resolve, reject) {
@ -121,7 +115,7 @@ module.exports = function($window) {
e.redraw = false
var href = this.getAttribute("href")
if (href.indexOf(prefix) === 0) href = href.slice(prefix.length)
return setPath(href, undefined, undefined)
setPath(href, undefined, undefined)
}
}

View file

@ -11,6 +11,7 @@
<script src="../../test-utils/pushStateMock.js"></script>
<script src="../../test-utils/domMock.js"></script>
<script src="../../promise/promise.js"></script>
<script src="../../render/vnode.js"></script>
<script src="../../render/render.js"></script>
<script src="../../querystring/build.js"></script>