From 3134202d2461dd0d86c223ae8eca1f4bc5f4438b Mon Sep 17 00:00:00 2001 From: Leo Horie Date: Tue, 6 Dec 2016 00:09:09 -0500 Subject: [PATCH] fix tests --- README.md | 2 +- api/router.js | 16 +++-- api/tests/test-router.js | 152 +++++++++++++++++++++++---------------- docs/route.md | 44 ++++++------ mithril.js | 17 +++-- mithril.min.js | 8 +-- router/router.js | 24 +++---- router/tests/index.html | 1 + 8 files changed, 145 insertions(+), 119 deletions(-) diff --git a/README.md b/README.md index 5adbc680..ef729c3b 100644 --- a/README.md +++ b/README.md @@ -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 7.42 KB min+gzip +Despite the huge improvements in performance and modularity, the new codebase is smaller than v0.2.x, currently clocking at 7.44 KB 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 diff --git a/api/router.js b/api/router.js index a1f29203..c5911b90 100644 --- a/api/router.js +++ b/api/router.js @@ -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) diff --git a/api/tests/test-router.js b/api/tests/test-router.js index 09c46eee..5ea27511 100644 --- a/api/tests/test-router.js +++ b/api/tests/test-router.js @@ -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"} }, diff --git a/docs/route.md b/docs/route.md index b16d1336..9089773e 100644 --- a/docs/route.md +++ b/docs/route.md @@ -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|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) + }) }, }, }) diff --git a/mithril.js b/mithril.js index 03017df3..a473784a 100644 --- a/mithril.js +++ b/mithril.js @@ -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) diff --git a/mithril.min.js b/mithril.min.js index c64fe7e3..0b8fec4e 100644 --- a/mithril.min.js +++ b/mithril.min.js @@ -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 +