diff --git a/api/router.js b/api/router.js index 18e59c0c..3bb05f07 100644 --- a/api/router.js +++ b/api/router.js @@ -1,14 +1,27 @@ "use strict" -var coreRenderer = require("../render/render") +var Node = require("../render/node") var coreRouter = require("../router/router") var autoredraw = require("../api/autoredraw") module.exports = function($window, renderer, pubsub) { var router = coreRouter($window) var route = function(root, defaultRoute, routes) { - var replay = router.defineRoutes(routes, function(component, args) { - renderer.render(root, {tag: component, attrs: args}) + var current = {route: null, component: null} + var replay = router.defineRoutes(routes, function(payload, args, path, route) { + if (typeof payload.view !== "function") { + if (typeof payload.render !== "function") payload.render = function(vnode) {return vnode} + var render = function(component) { + current.route = route, current.component = component + renderer.render(root, payload.render(Node(component, null, args, undefined, undefined, undefined))) + } + if (typeof payload.resolve !== "function") payload.resolve = function() {render(current.component)} + if (route !== current.route) payload.resolve(render, args, path, route) + else render(current.component) + } + else { + renderer.render(root, Node(payload, null, args, undefined, undefined, undefined)) + } }, function() { router.setPath(defaultRoute) }) diff --git a/api/tests/test-router.js b/api/tests/test-router.js index 2c2dd8de..98c1a8e9 100644 --- a/api/tests/test-router.js +++ b/api/tests/test-router.js @@ -11,175 +11,287 @@ var apiPubSub = require("../../api/pubsub") var apiRouter = require("../../api/router") o.spec("route", function() { - var FRAME_BUDGET = Math.floor(1000 / 60) - var $window, root, redraw, route + void ["#", "?", "", "#!", "?!", "/foo"].forEach(function(prefix) { + o.spec("using prefix `" + prefix + "`", function() { + var FRAME_BUDGET = Math.floor(1000 / 60) + var $window, root, redraw, route - o.beforeEach(function() { - $window = {} + o.beforeEach(function() { + $window = {} - var dom = domMock() - for (var key in dom) $window[key] = dom[key] + var dom = domMock() + for (var key in dom) $window[key] = dom[key] - var loc = pushStateMock() - for (var key in loc) $window[key] = loc[key] + var loc = pushStateMock() + for (var key in loc) $window[key] = loc[key] - root = $window.document.body + root = $window.document.body - redraw = apiPubSub() - route = apiRouter($window, coreRenderer($window), redraw) - }) + redraw = apiPubSub() + route = apiRouter($window, coreRenderer($window), redraw) + route.prefix(prefix) + }) - o("renders into `root`", function(done) { - route(root, "/", { - "/" : { - view: function() { - return m("div") - } - } - }) + o("renders into `root`", function(done) { + $window.location.href = prefix + "/" + route(root, "/", { + "/" : { + view: function() { + return m("div") + } + } + }) - callAsync(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) { + var onupdate = o.spy() + var oninit = o.spy() + + $window.location.href = prefix + "/" + route(root, "/", { + "/" : { + view: function() { + return m("div", { + oninit: oninit, + onupdate: onupdate + }) + } + } + }) + + callAsync(function() { + o(oninit.callCount).equals(1) + + redraw.publish() + + // Wrapped to give time for the rate-limited redraw to fire + setTimeout(function() { + o(onupdate.callCount).equals(1) + + done() + }, FRAME_BUDGET) + }) + }) + + o("redraws on events", function(done) { + var onupdate = o.spy() + var oninit = o.spy() + var onclick = o.spy() + var e = $window.document.createEvent("MouseEvents") + + e.initEvent("click", true, true) + + $window.location.href = prefix + "/" + route(root, "/", { + "/" : { + view: function() { + return m("div", { + oninit: oninit, + onupdate: onupdate, + onclick: onclick, + }) + } + } + }) + + callAsync(function() { + root.firstChild.dispatchEvent(e) + + 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) + + // Wrapped to give time for the rate-limited redraw to fire + setTimeout(function() { + o(onupdate.callCount).equals(1) + + done() + }, FRAME_BUDGET) + }) + }) + + o("event handlers can skip redraw", function(done) { + var onupdate = o.spy() + var oninit = o.spy() + var onclick = o.spy() + var e = $window.document.createEvent("MouseEvents") + + e.initEvent("click", true, true) + + $window.location.href = prefix + "/" + route(root, "/", { + "/" : { + view: function() { + return m("div", { + oninit: oninit, + onupdate: onupdate, + onclick: function(e) { + e.redraw = false + }, + }) + } + } + }) + + callAsync(function() { + root.firstChild.dispatchEvent(e) + + o(oninit.callCount).equals(1) + + // Wrapped to ensure no redraw fired + setTimeout(function() { + o(onupdate.callCount).equals(0) + + done() + }, FRAME_BUDGET) + }) + }) + + o("changes location on route.link", function(done) { + var e = $window.document.createEvent("MouseEvents") + + e.initEvent("click", true, true) + + $window.location.href = prefix + "/" + route(root, "/", { + "/" : { + view: function() { + return m("a", { + href: "/test", + oncreate: route.link + }) + } + }, + "/test" : { + view : function() { + return m("div") + } + } + }) + + callAsync(function() { + var slash = prefix[0] === "/" ? "" : "/" + + o($window.location.href).equals("http://localhost" + slash + (prefix ? prefix + "/" : "")) + + root.firstChild.dispatchEvent(e) + + o($window.location.href).equals("http://localhost" + slash + (prefix ? prefix + "/" : "") + "test") + + done() + }) + }) - done() - }) - }) - - o("redraws when render function is executed", function(done) { - var onupdate = o.spy() - var oninit = o.spy() - - route(root, "/", { - "/" : { - view: function() { - return m("div", { - oninit: oninit, - onupdate: onupdate - }) + o("accepts object as payload", function(done) { + var Component = { + view: function() { + return m("div") + } } - } - }) - - callAsync(function() { - o(oninit.callCount).equals(1) - - redraw.publish() - - // Wrapped to give time for the rate-limited redraw to fire - setTimeout(function() { - o(onupdate.callCount).equals(1) - - done() - }, FRAME_BUDGET) - }) - }) - - o("redraws on events", function(done) { - var onupdate = o.spy() - var oninit = o.spy() - var onclick = o.spy() - var e = $window.document.createEvent("MouseEvents") - - e.initEvent("click", true, true) - - route(root, "/", { - "/" : { - view: function() { - return m("div", { - oninit: oninit, - onupdate: onupdate, - onclick: onclick, - }) + + $window.location.href = prefix + "/" + route(root, "/", { + "/" : { + resolve: function(resolve) {resolve(Component)}, + render: function(vnode) {return vnode}, + }, + }) + + callAsync(function() { + o(root.firstChild.nodeName).equals("DIV") + + done() + }) + }) + + o("accepts object without `render` method as payload", function(done) { + var Component = { + view: function() { + return m("div") + } } - } - }) + + $window.location.href = prefix + "/" + route(root, "/", { + "/" : { + resolve: function(resolve) {resolve(Component)}, + }, + }) + + callAsync(function() { + o(root.firstChild.nodeName).equals("DIV") + + done() + }) + }) + + o("accepts object without `resolve` method as payload", function(done) { + var Component = { + view: function() { + return m("div") + } + } + + $window.location.href = prefix + "/" + route(root, "/", { + "/" : { + render: function() {return m(Component)}, + }, + }) + + callAsync(function() { + o(root.firstChild.nodeName).equals("DIV") + + done() + }) + }) - callAsync(function() { - root.firstChild.dispatchEvent(e) - - 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) - - // Wrapped to give time for the rate-limited redraw to fire - setTimeout(function() { - o(onupdate.callCount).equals(1) - - done() - }, FRAME_BUDGET) - }) - }) - - o("event handlers can skip redraw", function(done) { - var onupdate = o.spy() - var oninit = o.spy() - var onclick = o.spy() - var e = $window.document.createEvent("MouseEvents") - - e.initEvent("click", true, true) - - route(root, "/", { - "/" : { - view: function() { - return m("div", { - oninit: oninit, - onupdate: onupdate, - onclick: function(e) { - e.redraw = false + o("calls resolve and render correct number of times", function(done) { + var resolveCount = 0 + var renderCount = 0 + var Component = { + view: function() { + return m("div") + } + } + + $window.location.href = prefix + "/" + route(root, "/", { + "/" : { + resolve: function(resolve) { + resolveCount++ + resolve(Component) }, - }) - } - } - }) + render: function(vnode) { + renderCount++ + return vnode + }, + }, + }) - callAsync(function() { - root.firstChild.dispatchEvent(e) + callAsync(function() { + o(resolveCount).equals(1) + o(renderCount).equals(1) + + redraw.publish() - o(oninit.callCount).equals(1) - - // Wrapped to ensure no redraw fired - setTimeout(function() { - o(onupdate.callCount).equals(0) - - done() - }, FRAME_BUDGET) - }) - }) - - o("changes location on route.link", function(done) { - var e = $window.document.createEvent("MouseEvents") - - e.initEvent("click", true, true) - - route.prefix("?") - - route(root, "/", { - "/" : { - view: function() { - return m("a", { - href: "/test", - oncreate: route.link - }) - } - }, - "/test" : { - view : function() { - return m("div") - } - } - }) - - callAsync(function() { - o($window.location.href).equals("http://localhost/?/") - - root.firstChild.dispatchEvent(e) - - o($window.location.href).equals("http://localhost/?/test") - - done() + setTimeout(function() { + o(resolveCount).equals(1) + o(renderCount).equals(2) + + done() + }, FRAME_BUDGET) + }) + }) }) }) }) diff --git a/mithril.js b/mithril.js index 2b53fefe..4c9795e9 100644 --- a/mithril.js +++ b/mithril.js @@ -471,10 +471,15 @@ var renderService = function($window) { vnode.instance = Node.normalize(vnode.tag.view.call(vnode.state, vnode)) updateLifecycle(vnode.tag, vnode, hooks, recycling) if (vnode.instance != null) { - updateNode(parent, old.instance, vnode.instance, hooks, nextSibling, recycling, ns) + if (old.instance == null) insertNode(parent, createNode(vnode.instance, hooks, ns), nextSibling) + else updateNode(parent, old.instance, vnode.instance, hooks, nextSibling, recycling, ns) vnode.dom = vnode.instance.dom vnode.domSize = vnode.instance.domSize } + else if (old.instance != null) { + removeNode(parent, old.instance, null, false) + vnode.dom = vnode.domSize = undefined + } } function isRecyclable(old, vnodes) { if (old.pool != null && Math.abs(old.pool.length - vnodes.length) <= Math.abs(old.length - vnodes.length)) { @@ -557,7 +562,7 @@ var renderService = function($window) { } } if (vnode.dom.parentNode != null) parent.removeChild(vnode.dom) - if (context != null && vnode.domSize == null && !hasIntegrationMethods(vnode.attrs) && !(typeof vnode.tag !== "string" && hasIntegrationMethods(vnode.tag))) { //TODO test custom elements + if (context != null && vnode.domSize == null && !hasIntegrationMethods(vnode.attrs) && typeof vnode.tag === "string") { //TODO test custom elements if (!context.pool) context.pool = [vnode] else context.pool.push(vnode) } @@ -836,7 +841,6 @@ var requestService = function($window) { args.data[args.callbackKey || "callback"] = callbackName script.src = assemble(args.url, args.data) $window.document.documentElement.appendChild(script) - return stream } function interpolate(url, data) { @@ -958,7 +962,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) - callAsync($window.onpopstate) + $window.onpopstate() } else $window.location.href = prefix + path } @@ -966,25 +970,28 @@ var coreRouter = function($window) { if (supportsPushState) $window.onpopstate = resolveRoute else if (prefix.charAt(0) === "#") $window.onhashchange = resolveRoute resolveRoute() + function resolveRoute() { var path = getPath() var params = {} var pathname = parsePath(path, params, params) - for (var route in routes) { - var matcher = new RegExp("^" + route.replace(/:[^\/]+?\.{3}/g, "(.*?)").replace(/:[^\/]+/g, "([^\\/]+)") + "\/?$") - if (matcher.test(pathname)) { - pathname.replace(matcher, function() { - var keys = route.match(/:[^\/]+/g) || [] - var values = [].slice.call(arguments, 1, -2) - for (var i = 0; i < keys.length; i++) { - params[keys[i].replace(/:|\./g, "")] = decodeURIComponent(values[i]) - } - resolve(routes[route], params, path, route) - }) - return + callAsync(function() { + for (var route in routes) { + var matcher = new RegExp("^" + route.replace(/:[^\/]+?\.{3}/g, "(.*?)").replace(/:[^\/]+/g, "([^\\/]+)") + "\/?$") + if (matcher.test(pathname)) { + pathname.replace(matcher, function() { + var keys = route.match(/:[^\/]+/g) || [] + var values = [].slice.call(arguments, 1, -2) + for (var i = 0; i < keys.length; i++) { + params[keys[i].replace(/:|\./g, "")] = decodeURIComponent(values[i]) + } + resolve(routes[route], params, path, route) + }) + return + } } - } - reject(path, params) + reject(path, params) + }) } return resolveRoute } @@ -992,6 +999,7 @@ var coreRouter = function($window) { vnode.dom.setAttribute("href", prefix + vnode.attrs.href) vnode.dom.onclick = function(e) { e.preventDefault() + e.redraw = false setPath(vnode.attrs.href, undefined, undefined) } } @@ -1033,8 +1041,21 @@ var autoredraw = function(root, renderer, pubsub, callback) { m.route = function($window, renderer, pubsub) { var router = coreRouter($window) var route = function(root, defaultRoute, routes) { - var replay = router.defineRoutes(routes, function(component, args) { - renderer.render(root, {tag: component, attrs: args}) + var current = {route: null, component: null} + var replay = router.defineRoutes(routes, function(payload, args, path, route) { + if (typeof payload.view !== "function") { + if (typeof payload.render !== "function") payload.render = function(vnode) {return vnode} + var render = function(component) { + current.route = route, current.component = component + renderer.render(root, payload.render(Node(component, null, args, undefined, undefined, undefined))) + } + if (typeof payload.resolve !== "function") payload.resolve = function() {render(current.component)} + if (route !== current.route) payload.resolve(render, args, path, route) + else render(current.component) + } + else { + renderer.render(root, Node(payload, null, args, undefined, undefined, undefined)) + } }, function() { router.setPath(defaultRoute) }) @@ -1042,8 +1063,8 @@ m.route = function($window, renderer, pubsub) { } route.link = router.link route.prefix = router.setPrefix - route.setPath = router.setPath - route.getPath = router.getPath + route.set = router.setPath + route.get = router.getPath return route }(window, renderService, redrawService) m.mount = function(renderer, pubsub) { diff --git a/ospec/tests/test-ospec.js b/ospec/tests/test-ospec.js index 5239b4dc..138d185d 100644 --- a/ospec/tests/test-ospec.js +++ b/ospec/tests/test-ospec.js @@ -22,13 +22,13 @@ o.spec("ospec", function() { o.spec("sync", function() { var a = 0, b = 0 - o.before(function test() {a = 1}) - o.after(function test() {a = 0}) + o.before(function() {a = 1}) + o.after(function() {a = 0}) - o.beforeEach(function test() {b = 1}) - o.afterEach(function test() {b = 0}) + o.beforeEach(function() {b = 1}) + o.afterEach(function() {b = 0}) - o("assertions", function test() { + o("assertions", function() { var spy = o.spy() spy(a) @@ -53,33 +53,33 @@ o.spec("ospec", function() { o.spec("async", function() { var a = 0, b = 0 - o.before(function test(done) { + o.before(function(done) { callAsync(function() { a = 1 done() }) }) - o.after(function test(done) { + o.after(function(done) { callAsync(function() { a = 0 done() }) }) - o.beforeEach(function test(done) { + o.beforeEach(function(done) { callAsync(function() { b = 1 done() }) }) - o.afterEach(function test(done) { + o.afterEach(function(done) { callAsync(function() { b = 0 done() }) }) - o("async hooks", function test(done) { + o("async hooks", function(done) { callAsync(function() { var spy = o.spy() spy(a) diff --git a/render/render.js b/render/render.js index dfcd7bf3..afe4cb8c 100644 --- a/render/render.js +++ b/render/render.js @@ -337,7 +337,7 @@ module.exports = function($window) { } } if (vnode.dom.parentNode != null) parent.removeChild(vnode.dom) - if (context != null && vnode.domSize == null && !hasIntegrationMethods(vnode.attrs) && !(typeof vnode.tag !== "string" && hasIntegrationMethods(vnode.tag))) { //TODO test custom elements + if (context != null && vnode.domSize == null && !hasIntegrationMethods(vnode.attrs) && typeof vnode.tag === "string") { //TODO test custom elements if (!context.pool) context.pool = [vnode] else context.pool.push(vnode) } diff --git a/render/tests/test-updateNodes.js b/render/tests/test-updateNodes.js index ec4d9c11..da4b29d7 100644 --- a/render/tests/test-updateNodes.js +++ b/render/tests/test-updateNodes.js @@ -771,6 +771,7 @@ o.spec("updateNodes", function() { render(root, temp) render(root, updated) + o(root.childNodes.length).equals(1) o(vnodes[0].dom).equals(updated[0].dom) o(updated[0].dom.nodeName).equals("DIV") }) diff --git a/request/request.js b/request/request.js index 488166c9..b14b5e8a 100644 --- a/request/request.js +++ b/request/request.js @@ -92,7 +92,6 @@ module.exports = function($window) { args.data[args.callbackKey || "callback"] = callbackName script.src = assemble(args.url, args.data) $window.document.documentElement.appendChild(script) - return stream } diff --git a/router/router.js b/router/router.js index a6054424..6d522916 100644 --- a/router/router.js +++ b/router/router.js @@ -61,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) - callAsync($window.onpopstate) + $window.onpopstate() } else $window.location.href = prefix + path } @@ -70,29 +70,31 @@ module.exports = function($window) { if (supportsPushState) $window.onpopstate = resolveRoute else if (prefix.charAt(0) === "#") $window.onhashchange = resolveRoute resolveRoute() - + function resolveRoute() { var path = getPath() var params = {} var pathname = parsePath(path, params, params) - for (var route in routes) { - var matcher = new RegExp("^" + route.replace(/:[^\/]+?\.{3}/g, "(.*?)").replace(/:[^\/]+/g, "([^\\/]+)") + "\/?$") + callAsync(function() { + for (var route in routes) { + var matcher = new RegExp("^" + route.replace(/:[^\/]+?\.{3}/g, "(.*?)").replace(/:[^\/]+/g, "([^\\/]+)") + "\/?$") - if (matcher.test(pathname)) { - pathname.replace(matcher, function() { - var keys = route.match(/:[^\/]+/g) || [] - var values = [].slice.call(arguments, 1, -2) - for (var i = 0; i < keys.length; i++) { - params[keys[i].replace(/:|\./g, "")] = decodeURIComponent(values[i]) - } - resolve(routes[route], params, path, route) - }) - return + if (matcher.test(pathname)) { + pathname.replace(matcher, function() { + var keys = route.match(/:[^\/]+/g) || [] + var values = [].slice.call(arguments, 1, -2) + for (var i = 0; i < keys.length; i++) { + params[keys[i].replace(/:|\./g, "")] = decodeURIComponent(values[i]) + } + resolve(routes[route], params, path, route) + }) + return + } } - } - reject(path, params) + reject(path, params) + }) } return resolveRoute } @@ -101,6 +103,7 @@ module.exports = function($window) { vnode.dom.setAttribute("href", prefix + vnode.attrs.href) vnode.dom.onclick = function(e) { e.preventDefault() + e.redraw = false setPath(vnode.attrs.href, undefined, undefined) } } diff --git a/router/tests/test-defineRoutes.js b/router/tests/test-defineRoutes.js index 8c4f5055..646660d5 100644 --- a/router/tests/test-defineRoutes.js +++ b/router/tests/test-defineRoutes.js @@ -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,41 +18,57 @@ o.spec("Router.defineRoutes", function() { onFail = o.spy() }) - o("calls onRouteChange on init", function() { + o("calls onRouteChange on init", function(done) { $window.location.href = prefix + "/a" router.defineRoutes({"/a": {data: 1}}, onRouteChange, onFail) - o(onRouteChange.callCount).equals(1) + callAsync(function() { + o(onRouteChange.callCount).equals(1) + + done() + }) }) - o("resolves to route", function() { + o("resolves to route", function(done) { $window.location.href = prefix + "/test" router.defineRoutes({"/test": {data: 1}}, onRouteChange, onFail) - o(onRouteChange.callCount).equals(1) - o(onRouteChange.args).deepEquals([{data: 1}, {}, "/test", "/test"]) - o(onFail.callCount).equals(0) + callAsync(function() { + o(onRouteChange.callCount).equals(1) + o(onRouteChange.args).deepEquals([{data: 1}, {}, "/test", "/test"]) + o(onFail.callCount).equals(0) + + done() + }) }) - o("resolves to route w/ escaped unicode", function() { + o("resolves to route w/ escaped unicode", function(done) { $window.location.href = prefix + "/%C3%B6?%C3%B6=%C3%B6#%C3%B6=%C3%B6" router.defineRoutes({"/ö": {data: 2}}, onRouteChange, onFail) - o(onRouteChange.callCount).equals(1) - o(onRouteChange.args).deepEquals([{data: 2}, {"ö": "ö"}, "/ö?ö=ö#ö=ö", "/ö"]) - o(onFail.callCount).equals(0) + callAsync(function() { + o(onRouteChange.callCount).equals(1) + o(onRouteChange.args).deepEquals([{data: 2}, {"ö": "ö"}, "/ö?ö=ö#ö=ö", "/ö"]) + o(onFail.callCount).equals(0) + + done() + }) }) - o("resolves to route w/ unicode", function() { + o("resolves to route w/ unicode", function(done) { $window.location.href = prefix + "/ö?ö=ö#ö=ö" router.defineRoutes({"/ö": {data: 2}}, onRouteChange, onFail) - o(onRouteChange.callCount).equals(1) - o(onRouteChange.args).deepEquals([{data: 2}, {"ö": "ö"}, "/ö?ö=ö#ö=ö", "/ö"]) - o(onFail.callCount).equals(0) + callAsync(function() { + o(onRouteChange.callCount).equals(1) + o(onRouteChange.args).deepEquals([{data: 2}, {"ö": "ö"}, "/ö?ö=ö#ö=ö", "/ö"]) + o(onFail.callCount).equals(0) + + done() + }) }) - o("resolves to route on fallback mode", function() { + o("resolves to route on fallback mode", function(done) { $window.location.href = "file://" + prefix + "/test" router = new Router($window) @@ -59,98 +76,142 @@ o.spec("Router.defineRoutes", function() { router.defineRoutes({"/test": {data: 1}}, onRouteChange, onFail) - o(onRouteChange.callCount).equals(1) - o(onRouteChange.args).deepEquals([{data: 1}, {}, "/test", "/test"]) - o(onFail.callCount).equals(0) + callAsync(function() { + o(onRouteChange.callCount).equals(1) + o(onRouteChange.args).deepEquals([{data: 1}, {}, "/test", "/test"]) + o(onFail.callCount).equals(0) + + done() + }) }) - o("handles parameterized route", function() { + o("handles parameterized route", function(done) { $window.location.href = prefix + "/test/x" router.defineRoutes({"/test/:a": {data: 1}}, onRouteChange, onFail) - o(onRouteChange.callCount).equals(1) - o(onRouteChange.args).deepEquals([{data: 1}, {a: "x"}, "/test/x", "/test/:a"]) - o(onFail.callCount).equals(0) + callAsync(function() { + o(onRouteChange.callCount).equals(1) + o(onRouteChange.args).deepEquals([{data: 1}, {a: "x"}, "/test/x", "/test/:a"]) + o(onFail.callCount).equals(0) + + done() + }) }) - o("handles multi-parameterized route", function() { + o("handles multi-parameterized route", function(done) { $window.location.href = prefix + "/test/x/y" router.defineRoutes({"/test/:a/:b": {data: 1}}, onRouteChange, onFail) - o(onRouteChange.callCount).equals(1) - o(onRouteChange.args).deepEquals([{data: 1}, {a: "x", b: "y"}, "/test/x/y", "/test/:a/:b"]) - o(onFail.callCount).equals(0) + callAsync(function() { + o(onRouteChange.callCount).equals(1) + o(onRouteChange.args).deepEquals([{data: 1}, {a: "x", b: "y"}, "/test/x/y", "/test/:a/:b"]) + o(onFail.callCount).equals(0) + + done() + }) }) - o("handles rest parameterized route", function() { + o("handles rest parameterized route", function(done) { $window.location.href = prefix + "/test/x/y" router.defineRoutes({"/test/:a...": {data: 1}}, onRouteChange, onFail) - o(onRouteChange.callCount).equals(1) - o(onRouteChange.args).deepEquals([{data: 1}, {a: "x/y"}, "/test/x/y", "/test/:a..."]) - o(onFail.callCount).equals(0) + callAsync(function() { + o(onRouteChange.callCount).equals(1) + o(onRouteChange.args).deepEquals([{data: 1}, {a: "x/y"}, "/test/x/y", "/test/:a..."]) + o(onFail.callCount).equals(0) + + done() + }) }) - o("handles route with search", function() { + o("handles route with search", function(done) { $window.location.href = prefix + "/test?a=b&c=d" router.defineRoutes({"/test": {data: 1}}, onRouteChange, onFail) - o(onRouteChange.callCount).equals(1) - o(onRouteChange.args).deepEquals([{data: 1}, {a: "b", c: "d"}, "/test?a=b&c=d", "/test"]) - o(onFail.callCount).equals(0) + callAsync(function() { + o(onRouteChange.callCount).equals(1) + o(onRouteChange.args).deepEquals([{data: 1}, {a: "b", c: "d"}, "/test?a=b&c=d", "/test"]) + o(onFail.callCount).equals(0) + + done() + }) }) - o("handles route with hash", function() { + o("handles route with hash", function(done) { $window.location.href = prefix + "/test#a=b&c=d" router.defineRoutes({"/test": {data: 1}}, onRouteChange, onFail) - o(onRouteChange.callCount).equals(1) - o(onRouteChange.args).deepEquals([{data: 1}, {a: "b", c: "d"}, "/test#a=b&c=d", "/test"]) - o(onFail.callCount).equals(0) + callAsync(function() { + o(onRouteChange.callCount).equals(1) + o(onRouteChange.args).deepEquals([{data: 1}, {a: "b", c: "d"}, "/test#a=b&c=d", "/test"]) + o(onFail.callCount).equals(0) + + done() + }) }) - o("handles route with search and hash", function() { + o("handles route with search and hash", function(done) { $window.location.href = prefix + "/test?a=b#c=d" router.defineRoutes({"/test": {data: 1}}, onRouteChange, onFail) - o(onRouteChange.callCount).equals(1) - o(onRouteChange.args).deepEquals([{data: 1}, {a: "b", c: "d"}, "/test?a=b#c=d", "/test"]) - o(onFail.callCount).equals(0) + callAsync(function() { + o(onRouteChange.callCount).equals(1) + o(onRouteChange.args).deepEquals([{data: 1}, {a: "b", c: "d"}, "/test?a=b#c=d", "/test"]) + o(onFail.callCount).equals(0) + + done() + }) }) - o("calls reject", function() { + o("calls reject", function(done) { $window.location.href = prefix + "/test" router.defineRoutes({"/other": {data: 1}}, onRouteChange, onFail) - o(onFail.callCount).equals(1) - o(onFail.args).deepEquals(["/test", {}]) + callAsync(function() { + o(onFail.callCount).equals(1) + o(onFail.args).deepEquals(["/test", {}]) + + done() + }) }) - o("calls reject w/ search and hash", function() { + o("calls reject w/ search and hash", function(done) { $window.location.href = prefix + "/test?a=b#c=d" router.defineRoutes({"/other": {data: 1}}, onRouteChange, onFail) - o(onFail.callCount).equals(1) - o(onFail.args).deepEquals(["/test?a=b#c=d", {a: "b", c: "d"}]) + callAsync(function() { + o(onFail.callCount).equals(1) + o(onFail.args).deepEquals(["/test?a=b#c=d", {a: "b", c: "d"}]) + + done() + }) }) - o("handles out of order routes", function() { + o("handles out of order routes", function(done) { $window.location.href = prefix + "/z/y/x" router.defineRoutes({"/z/y/x": {data: 1}, "/:a...": {data: 2}}, onRouteChange, onFail) - o(onRouteChange.callCount).equals(1) - o(onRouteChange.args).deepEquals([{data: 1}, {}, "/z/y/x", "/z/y/x"]) + callAsync(function() { + o(onRouteChange.callCount).equals(1) + o(onRouteChange.args).deepEquals([{data: 1}, {}, "/z/y/x", "/z/y/x"]) + + done() + }) }) - o("handles reverse out of order routes", function() { + o("handles reverse out of order routes", function(done) { $window.location.href = prefix + "/z/y/x" router.defineRoutes({"/:a...": {data: 2}, "/z/y/x": {data: 1}}, onRouteChange, onFail) - o(onRouteChange.callCount).equals(1) - o(onRouteChange.args).deepEquals([{data: 2}, {a: "z/y/x"}, "/z/y/x", "/:a..."]) + callAsync(function() { + o(onRouteChange.callCount).equals(1) + o(onRouteChange.args).deepEquals([{data: 2}, {a: "z/y/x"}, "/z/y/x", "/:a..."]) + + done() + }) }) - o("handles dynamically added out of order routes", function() { + o("handles dynamically added out of order routes", function(done) { var routes = {} routes["/z/y/x"] = {data: 1} routes["/:a..."] = {data: 2} @@ -158,11 +219,15 @@ o.spec("Router.defineRoutes", function() { $window.location.href = prefix + "/z/y/x" router.defineRoutes(routes, onRouteChange, onFail) - o(onRouteChange.callCount).equals(1) - o(onRouteChange.args).deepEquals([{data: 1}, {}, "/z/y/x", "/z/y/x"]) + callAsync(function() { + o(onRouteChange.callCount).equals(1) + o(onRouteChange.args).deepEquals([{data: 1}, {}, "/z/y/x", "/z/y/x"]) + + done() + }) }) - o("handles reversed dynamically added out of order routes", function() { + o("handles reversed dynamically added out of order routes", function(done) { var routes = {} routes["/:a..."] = {data: 2} routes["/z/y/x"] = {data: 1} @@ -170,47 +235,67 @@ o.spec("Router.defineRoutes", function() { $window.location.href = prefix + "/z/y/x" router.defineRoutes(routes, onRouteChange, onFail) - o(onRouteChange.callCount).equals(1) - o(onRouteChange.args).deepEquals([{data: 2}, {a: "z/y/x"}, "/z/y/x", "/:a..."]) + callAsync(function() { + o(onRouteChange.callCount).equals(1) + o(onRouteChange.args).deepEquals([{data: 2}, {a: "z/y/x"}, "/z/y/x", "/:a..."]) + + done() + }) }) - o("handles mixed out of order routes", function() { + o("handles mixed out of order routes", function(done) { var routes = {"/z/y/x": {data: 1}} routes["/:a..."] = {data: 2} $window.location.href = prefix + "/z/y/x" router.defineRoutes(routes, onRouteChange, onFail) - o(onRouteChange.callCount).equals(1) - o(onRouteChange.args).deepEquals([{data: 1}, {}, "/z/y/x", "/z/y/x"]) + callAsync(function() { + o(onRouteChange.callCount).equals(1) + o(onRouteChange.args).deepEquals([{data: 1}, {}, "/z/y/x", "/z/y/x"]) + + done() + }) }) - o("handles reverse mixed out of order routes", function() { + o("handles reverse mixed out of order routes", function(done) { var routes = {"/:a...": {data: 2}} routes["/z/y/x"] = {data: 12} $window.location.href = prefix + "/z/y/x" router.defineRoutes(routes, onRouteChange, onFail) - o(onRouteChange.callCount).equals(1) - o(onRouteChange.args).deepEquals([{data: 2}, {a: "z/y/x"}, "/z/y/x", "/:a..."]) + callAsync(function() { + o(onRouteChange.callCount).equals(1) + o(onRouteChange.args).deepEquals([{data: 2}, {a: "z/y/x"}, "/z/y/x", "/:a..."]) + + done() + }) }) - o("handles non-ascii routes", function() { + o("handles non-ascii routes", function(done) { $window.location.href = prefix + "/ö" router.defineRoutes({"/ö": "aaa"}, onRouteChange, onFail) - o(onRouteChange.callCount).equals(1) + callAsync(function() { + o(onRouteChange.callCount).equals(1) + + done() + }) }) - o("replays", function() { + o("replays", function(done) { $window.location.href = prefix + "/test" var replay = router.defineRoutes({"/test": {data: 1}}, onRouteChange, onFail) replay() - o(onRouteChange.callCount).equals(2) - o(onRouteChange.args).deepEquals([{data: 1}, {}, "/test", "/test"]) - o(onFail.callCount).equals(0) + callAsync(function() { + o(onRouteChange.callCount).equals(2) + o(onRouteChange.args).deepEquals([{data: 1}, {}, "/test", "/test"]) + o(onFail.callCount).equals(0) + + done() + }) }) }) }) diff --git a/router/tests/test-setPath.js b/router/tests/test-setPath.js index a9ef6450..2d78ee8f 100644 --- a/router/tests/test-setPath.js +++ b/router/tests/test-setPath.js @@ -21,90 +21,136 @@ o.spec("Router.setPath", function() { 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() + 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) + router.setPath("/c") + + o(onFail.callCount).equals(0) + callAsync(function() { + o(onFail.callCount).equals(1) + done() + }) + }) + }) + o("sets route via API", function(done) { + $window.location.href = prefix + "/test" + router.defineRoutes({"/test": {data: 1}, "/other/:a/:b...": {data: 2}}, onRouteChange, onFail) + + callAsync(function() { + router.setPath("/other/x/y/z?c=d#e=f") + + o(router.getPath()).equals("/other/x/y/z?c=d#e=f") + done() }) }) - o("sets route via API", function() { - $window.location.href = prefix + "/test" - router.defineRoutes({"/test": {data: 1}, "/other/:a/:b...": {data: 2}}, onRouteChange, onFail) - router.setPath("/other/x/y/z?c=d#e=f") - - o(router.getPath()).equals("/other/x/y/z?c=d#e=f") - }) - o("sets route w/ escaped unicode", function() { + o("sets route w/ escaped unicode", function(done) { $window.location.href = prefix + "/test" router.defineRoutes({"/test": {data: 1}, "/ö/:a/:b...": {data: 2}}, onRouteChange, onFail) - router.setPath("/%C3%B6?%C3%B6=%C3%B6#%C3%B6=%C3%B6") - o(router.getPath()).equals("/ö?ö=ö#ö=ö") + callAsync(function() { + router.setPath("/%C3%B6?%C3%B6=%C3%B6#%C3%B6=%C3%B6") + + o(router.getPath()).equals("/ö?ö=ö#ö=ö") + + done() + }) }) - o("sets route w/ unicode", function() { + o("sets route w/ unicode", function(done) { $window.location.href = prefix + "/test" router.defineRoutes({"/test": {data: 1}, "/ö/:a/:b...": {data: 2}}, onRouteChange, onFail) - router.setPath("/ö?ö=ö#ö=ö") - o(router.getPath()).equals("/ö?ö=ö#ö=ö") + callAsync(function() { + router.setPath("/ö?ö=ö#ö=ö") + + o(router.getPath()).equals("/ö?ö=ö#ö=ö") + + done() + }) }) - o("sets route on fallback mode", function() { + o("sets route on fallback mode", function(done) { $window.location.href = "file://" + prefix + "/test" router = new Router($window) router.setPrefix(prefix) router.defineRoutes({"/test": {data: 1}, "/other/:a/:b...": {data: 2}}, onRouteChange, onFail) - router.setPath("/other/x/y/z?c=d#e=f") - o(router.getPath()).equals("/other/x/y/z?c=d#e=f") + callAsync(function() { + router.setPath("/other/x/y/z?c=d#e=f") + + o(router.getPath()).equals("/other/x/y/z?c=d#e=f") + + done() + }) }) - o("sets route via pushState/onpopstate", function() { + o("sets route via pushState/onpopstate", function(done) { $window.location.href = prefix + "/test" router.defineRoutes({"/test": {data: 1}, "/other/:a/:b...": {data: 2}}, onRouteChange, onFail) - $window.history.pushState(null, null, prefix + "/other/x/y/z?c=d#e=f") - $window.onpopstate() - o(router.getPath()).equals("/other/x/y/z?c=d#e=f") + callAsync(function() { + $window.history.pushState(null, null, prefix + "/other/x/y/z?c=d#e=f") + $window.onpopstate() + + o(router.getPath()).equals("/other/x/y/z?c=d#e=f") + + done() + }) }) - o("sets parameterized route", function() { + o("sets parameterized route", function(done) { $window.location.href = prefix + "/test" router.defineRoutes({"/test": {data: 1}, "/other/:a/:b...": {data: 2}}, onRouteChange, onFail) - router.setPath("/other/:a/:b", {a: "x", b: "y/z", c: "d", e: "f"}) - o(router.getPath()).equals("/other/x/y/z?c=d&e=f") + callAsync(function() { + router.setPath("/other/:a/:b", {a: "x", b: "y/z", c: "d", e: "f"}) + + o(router.getPath()).equals("/other/x/y/z?c=d&e=f") + + done() + }) }) - o("replace:true works", function() { + o("replace:true works", function(done) { $window.location.href = prefix + "/test" router.defineRoutes({"/test": {data: 1}, "/other": {data: 2}}, onRouteChange, onFail) - router.setPath("/other", null, {replace: true}) - $window.history.back() - o($window.location.href).equals("http://localhost/") + callAsync(function() { + router.setPath("/other", null, {replace: true}) + $window.history.back() + + o($window.location.href).equals("http://localhost/") + + done() + }) }) - o("replace:false works", function() { + o("replace:false works", function(done) { $window.location.href = prefix + "/test" router.defineRoutes({"/test": {data: 1}, "/other": {data: 2}}, onRouteChange, onFail) - router.setPath("/other", null, {replace: false}) - $window.history.back() - var slash = prefix[0] === "/" ? "" : "/" + callAsync(function() { + router.setPath("/other", null, {replace: false}) + $window.history.back() - o($window.location.href).equals("http://localhost" + slash + (prefix ? prefix + "/" : "") + "test") + var slash = prefix[0] === "/" ? "" : "/" + + o($window.location.href).equals("http://localhost" + slash + (prefix ? prefix + "/" : "") + "test") + + done() + }) }) }) })