From 7cab626980934b2e5b26e90c11d3c1d874ed3629 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Gerardy Date: Wed, 7 Dec 2016 13:32:18 +0100 Subject: [PATCH 1/3] Add a short browserMock test suite --- test-utils/tests/index.html | 2 ++ test-utils/tests/test-browserMock.js | 40 ++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 test-utils/tests/test-browserMock.js diff --git a/test-utils/tests/index.html b/test-utils/tests/index.html index 4e5a6234..e24fa2f8 100644 --- a/test-utils/tests/index.html +++ b/test-utils/tests/index.html @@ -13,11 +13,13 @@ + + diff --git a/test-utils/tests/test-browserMock.js b/test-utils/tests/test-browserMock.js new file mode 100644 index 00000000..d47c58ce --- /dev/null +++ b/test-utils/tests/test-browserMock.js @@ -0,0 +1,40 @@ +"use strict" + +var o = require("../../ospec/ospec") +var browserMock = require("../../test-utils/browserMock") +var callAsync = require("../../test-utils/callAsync") +o.spec("browserMock", function() { + + var $window + o.beforeEach(function() { + $window = browserMock() + }) + + o("Mocks DOM, pushState and XHR", function() { + o($window.location).notEquals(undefined) + o($window.document).notEquals(undefined) + o($window.XMLHttpRequest).notEquals(undefined) + }) + o("$window.onhashchange can be reached from the pushStateMock functions", function(done) { + $window.onhashchange = o.spy() + $window.location.hash = '#a' + + callAsync(function(){ + o($window.onhashchange.callCount).equals(1) + done() + }) + }) + o("$window.onpopstate can be reached from the pushStateMock functions", function() { + $window.onpopstate = o.spy() + $window.history.pushState(null, null, "#a") + $window.history.back() + + o($window.onpopstate.callCount).equals(1) + }) + o("$window.onunload can be reached from the pushStateMock functions", function() { + $window.onunload = o.spy() + $window.location.href = '/a' + + o($window.onunload.callCount).equals(1) + }) +}) From bd792979054ad7b27056312485368b7a4f34a434 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Gerardy Date: Wed, 7 Dec 2016 13:42:27 +0100 Subject: [PATCH 2/3] The browserMock $window must be reachable from the pushStateMock --- test-utils/browserMock.js | 6 +- test-utils/pushStateMock.js | 233 ++++++++++++++++++------------------ 2 files changed, 120 insertions(+), 119 deletions(-) diff --git a/test-utils/browserMock.js b/test-utils/browserMock.js index d667772a..ead6e9e4 100644 --- a/test-utils/browserMock.js +++ b/test-utils/browserMock.js @@ -5,14 +5,14 @@ var domMock = require("./domMock") var xhrMock = require("./xhrMock") module.exports = function(env) { - var $window = {} + env = env || {} + var $window = env.window = {} var dom = domMock() var xhr = xhrMock() - var ps = pushStateMock(env) for (var key in dom) if (!$window[key]) $window[key] = dom[key] for (var key in xhr) if (!$window[key]) $window[key] = xhr[key] - for (var key in ps) if (!$window[key]) $window[key] = ps[key] + pushStateMock(env) return $window } \ No newline at end of file diff --git a/test-utils/pushStateMock.js b/test-utils/pushStateMock.js index 3e4ed1f7..d23b3a1f 100644 --- a/test-utils/pushStateMock.js +++ b/test-utils/pushStateMock.js @@ -5,6 +5,7 @@ var parseURL = require("../test-utils/parseURL") module.exports = function(options) { if (options == null) options = {} + var $window = options.window || {} var protocol = options.protocol || "http:" var hostname = options.hostname || "localhost" var port = "" @@ -32,7 +33,7 @@ module.exports = function(options) { } return isNew } - + function prefix(prefix, value) { if (value === "") return "" return (value.charAt(0) !== prefix ? prefix : "") + value @@ -46,125 +47,125 @@ module.exports = function(options) { function unload() { if (typeof $window.onunload === "function") $window.onunload({type: "unload"}) } - var $window = { - location: { - get protocol() { - return protocol - }, - get hostname() { - return hostname - }, - get port() { - return port - }, - get pathname() { - return pathname - }, - get search() { - return search - }, - get hash() { - return hash - }, - get origin() { - if (protocol === "file:") return "null" - return protocol + "//" + hostname + prefix(":", port) - }, - get host() { - if (protocol === "file:") return "" - return hostname + prefix(":", port) - }, - get href() { - return getURL() - }, - set protocol(value) { - throw new Error("Protocol is read-only") - }, - set hostname(value) { - unload() - past.push({url: getURL(), isNew: true}) - future = [] - hostname = value - }, - set port(value) { - if (protocol === "file:") throw new Error("Port is read-only under `file://` protocol") - unload() - past.push({url: getURL(), isNew: true}) - future = [] - port = value - }, - set pathname(value) { - if (protocol === "file:") throw new Error("Pathname is read-only under `file://` protocol") - unload() - past.push({url: getURL(), isNew: true}) - future = [] - pathname = prefix("/", value) - }, - set search(value) { - unload() - past.push({url: getURL(), isNew: true}) - future = [] - search = prefix("?", value) - }, - set hash(value) { - var oldHash = hash - past.push({url: getURL(), isNew: false}) - future = [] - hash = prefix("#", value) - if (oldHash != hash) hashchange() - }, + $window.location = { + get protocol() { + return protocol + }, + get hostname() { + return hostname + }, + get port() { + return port + }, + get pathname() { + return pathname + }, + get search() { + return search + }, + get hash() { + return hash + }, + get origin() { + if (protocol === "file:") return "null" + return protocol + "//" + hostname + prefix(":", port) + }, + get host() { + if (protocol === "file:") return "" + return hostname + prefix(":", port) + }, + get href() { + return getURL() + }, - set origin(value) { - //origin is writable but ignored - }, - set host(value) { - //host is writable but ignored in Chrome - }, - set href(value) { - var url = getURL() - var isNew = setURL(value) - if (isNew) { - setURL(url) - unload() - setURL(value) - } - past.push({url: url, isNew: isNew}) - future = [] - }, + set protocol(value) { + throw new Error("Protocol is read-only") }, - history: { - pushState: function(data, title, url) { - past.push({url: getURL(), isNew: false}) - future = [] - setURL(url) - }, - replaceState: function(data, title, url) { - future = [] - setURL(url) - }, - back: function() { - var entry = past.pop() - if (entry != null) { - if (entry.isNew) unload() - future.push({url: getURL(), isNew: false}) - setURL(entry.url) - if (!entry.isNew) popstate() - } - }, - forward: function() { - var entry = future.pop() - if (entry != null) { - if (entry.isNew) unload() - past.push({url: getURL(), isNew: false}) - setURL(entry.url) - if (!entry.isNew) popstate() - } - }, + set hostname(value) { + unload() + past.push({url: getURL(), isNew: true}) + future = [] + hostname = value + }, + set port(value) { + if (protocol === "file:") throw new Error("Port is read-only under `file://` protocol") + unload() + past.push({url: getURL(), isNew: true}) + future = [] + port = value + }, + set pathname(value) { + if (protocol === "file:") throw new Error("Pathname is read-only under `file://` protocol") + unload() + past.push({url: getURL(), isNew: true}) + future = [] + pathname = prefix("/", value) + }, + set search(value) { + unload() + past.push({url: getURL(), isNew: true}) + future = [] + search = prefix("?", value) + }, + set hash(value) { + var oldHash = hash + past.push({url: getURL(), isNew: false}) + future = [] + hash = prefix("#", value) + if (oldHash != hash) hashchange() + }, + + set origin(value) { + //origin is writable but ignored + }, + set host(value) { + //host is writable but ignored in Chrome + }, + set href(value) { + var url = getURL() + var isNew = setURL(value) + if (isNew) { + setURL(url) + unload() + setURL(value) + } + past.push({url: url, isNew: isNew}) + future = [] }, - onpopstate: null, - onhashchange: null, - onunload: null, } + $window.history = { + pushState: function(data, title, url) { + past.push({url: getURL(), isNew: false}) + future = [] + setURL(url) + }, + replaceState: function(data, title, url) { + future = [] + setURL(url) + }, + back: function() { + var entry = past.pop() + if (entry != null) { + if (entry.isNew) unload() + future.push({url: getURL(), isNew: false}) + setURL(entry.url) + if (!entry.isNew) popstate() + } + }, + forward: function() { + var entry = future.pop() + if (entry != null) { + if (entry.isNew) unload() + past.push({url: getURL(), isNew: false}) + setURL(entry.url) + if (!entry.isNew) popstate() + } + }, + } + $window.onpopstate = null, + $window.onhashchange = null, + $window.onunload = null + return $window } From 3a671fc5cad4d485098fffd82ef1f8fa85db73f1 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Gerardy Date: Wed, 7 Dec 2016 15:03:49 +0100 Subject: [PATCH 3/3] [router] add tests for onmatch corner cases --- api/tests/test-router.js | 128 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 123 insertions(+), 5 deletions(-) diff --git a/api/tests/test-router.js b/api/tests/test-router.js index 8fc6c3bc..7ea00195 100644 --- a/api/tests/test-router.js +++ b/api/tests/test-router.js @@ -627,7 +627,7 @@ o.spec("route", function() { render: render }, "/b" : { - view: function(vnode){ + view: function() { redirected = true } } @@ -644,6 +644,7 @@ o.spec("route", function() { o("onmatch can redirect to another route that has RouteResolver w/ only onmatch", function(done) { var redirected = false var render = o.spy() + var view = o.spy(function() {return m("div")}) $window.location.href = prefix + "/a" route(root, "/a", { @@ -656,16 +657,21 @@ o.spec("route", function() { "/b" : { onmatch: function() { redirected = true - return {view: function() {}} + return {view: view} } } }) callAsync(function() { - o(render.callCount).equals(0) - o(redirected).equals(true) + callAsync(function() { + o(render.callCount).equals(0) + o(redirected).equals(true) + o(view.callCount).equals(1) + o(root.childNodes.length).equals(1) + o(root.firstChild.nodeName).equals("DIV") - done() + done() + }) }) }) @@ -696,6 +702,118 @@ o.spec("route", function() { }) }) + o("onmatch can redirect to another route that has RouteResolver whose onmatch resolves asynchronously", function(done) { + var redirected = false + var render = o.spy() + var view = o.spy() + + $window.location.href = prefix + "/a" + route(root, "/a", { + "/a" : { + onmatch: function() { + route.set("/b") + }, + render: render + }, + "/b" : { + onmatch: function() { + redirected = true + return new Promise(function(fulfill){ + callAsync(function(){ + fulfill({view: view}) + }) + }) + } + } + }) + + callAsync(function() { + callAsync(function() { + callAsync(function() { + o(render.callCount).equals(0) + o(redirected).equals(true) + o(view.callCount).equals(1) + + done() + }) + }) + }) + }) + + o("onmatch can redirect to another route asynchronously", function(done) { + var redirected = false + var render = o.spy() + var view = o.spy() + + $window.location.href = prefix + "/a" + route(root, "/a", { + "/a" : { + onmatch: function() { + callAsync(function() {route.set("/b")}) + return new Promise(function() {}) + }, + render: render + }, + "/b" : { + onmatch: function() { + redirected = true + return {view: view} + } + } + }) + + callAsync(function() { + callAsync(function() { + callAsync(function() { + o(render.callCount).equals(0) + o(redirected).equals(true) + o(view.callCount).equals(1) + + done() + }) + }) + }) + }) + + o("onmatch can redirect w/ window.history.back()", function(done) { + + var render = o.spy() + var component = {view: o.spy()} + + $window.location.href = prefix + "/a" + route(root, "/a", { + "/a" : { + onmatch: function() { + return component + }, + render: function(vnode) { + return vnode + } + }, + "/b" : { + onmatch: function() { + $window.history.back() + return new Promise(function() {}) + }, + render: render + } + }) + + callAsync(function() { + route.set('/b') + callAsync(function() { + callAsync(function() { + callAsync(function() { + o(render.callCount).equals(0) + o(component.view.callCount).equals(2) + + done() + }) + }) + }) + }) + }) + o("onmatch can redirect to a non-existent route that defaults to a RouteResolver w/ onmatch", function(done) { var redirected = false var render = o.spy()