diff --git a/api/tests/test-autoredraw.js b/api/tests/test-autoredraw.js index 6f685bc7..6f71adeb 100644 --- a/api/tests/test-autoredraw.js +++ b/api/tests/test-autoredraw.js @@ -26,6 +26,16 @@ o.spec("autoredraw", function() { o(spy.callCount).equals(1) }) + o("null renderer doesn't throw", function(done) { + autoredraw(root, null, pubsub, spy) + done() + }) + + o("null pubsub doesn't throw", function(done) { + autoredraw(root, renderer, null, spy) + done() + }) + o("registers onevent", function() { autoredraw(root, renderer, pubsub, spy) @@ -45,6 +55,16 @@ o.spec("autoredraw", function() { o(spy.callCount).equals(1) }) + + o("re-registering pubsub works", function() { + autoredraw(root, renderer, pubsub, spy) + autoredraw(root, renderer, pubsub, spy) + + pubsub.publish() + + o(spy.callCount).equals(1) + }) + o("throttles", function(done) { var run = autoredraw(root, renderer, pubsub, spy) @@ -60,4 +80,16 @@ o.spec("autoredraw", function() { }, FRAME_BUDGET) }) + o("does not redraw if e.redraw is false", function() { + autoredraw(root, renderer, pubsub, spy) + + renderer.render(root, {tag: "div", attrs: {onclick: function(e) {e.redraw = false}}}) + + var e = $window.document.createEvent("MouseEvents") + e.initEvent("click", true, true) + root.firstChild.dispatchEvent(e) + + o(spy.callCount).equals(0) + }) + }) \ No newline at end of file diff --git a/api/tests/test-pubsub.js b/api/tests/test-pubsub.js index 1d5a37d5..619299d0 100644 --- a/api/tests/test-pubsub.js +++ b/api/tests/test-pubsub.js @@ -4,50 +4,72 @@ var o = require("../../ospec/ospec") var apiPubSub = require("../../api/pubsub") o.spec("pubsub", function() { - var pubsub + var pubsub o.beforeEach(function() { pubsub = apiPubSub() }) - - o("it shouldn't error if there are no renderers", function() { - pubsub.publish() - }) - - o("it should run a single renderer entry", function() { - var spy = o.spy() - - pubsub.subscribe(spy) - - pubsub.publish() - - o(spy.callCount).equals(1) - - pubsub.publish() - pubsub.publish() - pubsub.publish() - - o(spy.callCount).equals(4) - }) - - o("it should run all renderer entries", function() { - var spy1 = o.spy() - var spy2 = o.spy() - var spy3 = o.spy() - - pubsub.subscribe(spy1) - pubsub.subscribe(spy2) - pubsub.subscribe(spy3) - - pubsub.publish() - - o(spy1.callCount).equals(1) - o(spy2.callCount).equals(1) - o(spy3.callCount).equals(1) - - pubsub.publish() - - o(spy1.callCount).equals(2) - o(spy2.callCount).equals(2) - o(spy3.callCount).equals(2) - }) + + o("shouldn't error if there are no renderers", function() { + pubsub.publish() + }) + + o("should run a single renderer entry", function() { + var spy = o.spy() + + pubsub.subscribe(spy) + + pubsub.publish() + + o(spy.callCount).equals(1) + + pubsub.publish() + pubsub.publish() + pubsub.publish() + + o(spy.callCount).equals(4) + }) + + o("should run all renderer entries", function() { + var spy1 = o.spy() + var spy2 = o.spy() + var spy3 = o.spy() + + pubsub.subscribe(spy1) + pubsub.subscribe(spy2) + pubsub.subscribe(spy3) + + pubsub.publish() + + o(spy1.callCount).equals(1) + o(spy2.callCount).equals(1) + o(spy3.callCount).equals(1) + + pubsub.publish() + + o(spy1.callCount).equals(2) + o(spy2.callCount).equals(2) + o(spy3.callCount).equals(2) + }) + + o("should stop running after unsubscribe", function() { + var spy = o.spy() + + pubsub.subscribe(spy) + pubsub.unsubscribe(spy) + + pubsub.publish() + + o(spy.callCount).equals(0) + }) + + o("does nothing on invalid unsubscribe", function() { + var spy = o.spy() + + pubsub.subscribe(spy) + pubsub.unsubscribe(null) + + pubsub.publish() + + o(spy.callCount).equals(1) + }) }) diff --git a/render/render.js b/render/render.js index 1f22fff6..216454b1 100644 --- a/render/render.js +++ b/render/render.js @@ -4,6 +4,7 @@ var Node = require("../render/node") module.exports = function($window) { var $doc = $window.document + var $emptyFragment = $doc.createDocumentFragment() var onevent function setEventCallback(callback) {return onevent = callback} @@ -91,10 +92,13 @@ module.exports = function($window) { initLifecycle(vnode.tag, vnode, hooks) vnode.instance = Node.normalize(vnode.tag.view.call(vnode.state, vnode)) - var element = createNode(vnode.instance, hooks) - vnode.dom = vnode.instance.dom - vnode.domSize = vnode.instance.domSize - return element + if (vnode.instance != null) { + var element = createNode(vnode.instance, hooks) + vnode.dom = vnode.instance.dom + vnode.domSize = vnode.instance.domSize + return element + } + else return $emptyFragment } //update @@ -231,9 +235,11 @@ module.exports = function($window) { function updateComponent(parent, old, vnode, hooks, nextSibling, recycling) { vnode.instance = Node.normalize(vnode.tag.view.call(vnode.state, vnode)) updateLifecycle(vnode.tag, vnode, hooks, recycling) - updateNode(parent, old.instance, vnode.instance, hooks, nextSibling, recycling) - vnode.dom = vnode.instance.dom - vnode.domSize = vnode.instance.domSize + if (vnode.instance != null) { + updateNode(parent, old.instance, vnode.instance, hooks, nextSibling, recycling) + vnode.dom = vnode.instance.dom + vnode.domSize = vnode.instance.domSize + } } function isRecyclable(old, vnodes) { if (old.pool != null && Math.abs(old.pool.length - vnodes.length) <= Math.abs(old.length - vnodes.length)) { diff --git a/render/tests/test-component.js b/render/tests/test-component.js index 87c5e00e..4fc8a8b8 100644 --- a/render/tests/test-component.js +++ b/render/tests/test-component.js @@ -150,6 +150,26 @@ o.spec("component", function() { o(root.firstChild.nodeType).equals(3) o(root.firstChild.nodeValue).equals("false") }) + o("can return null", function() { + var component = { + view: function(vnode) { + return null + } + } + render(root, [{tag: component}]) + + o(root.childNodes.length).equals(0) + }) + o("can return undefined", function() { + var component = { + view: function(vnode) { + return undefined + } + } + render(root, [{tag: component}]) + + o(root.childNodes.length).equals(0) + }) o("can update when returning fragments", function() { var component = { view: function(vnode) { @@ -178,6 +198,17 @@ o.spec("component", function() { o(root.firstChild.nodeType).equals(3) o(root.firstChild.nodeValue).equals("a") }) + o("can update when returning null", function() { + var component = { + view: function(vnode) { + return null + } + } + render(root, [{tag: component}]) + render(root, [{tag: component}]) + + o(root.childNodes.length).equals(0) + }) o("can remove when returning fragments", function() { var component = { view: function(vnode) { @@ -507,4 +538,22 @@ o.spec("component", function() { o(root.childNodes.length).equals(0) }) }) + o.spec("state", function() { + o("deep copies state", function() { + var called = 0 + var component = { + data: [{a: 1}], + oninit: init, + view: function() { + return "" + } + } + + render(root, [{tag: component}]) + + function init(vnode) { + o(vnode.state.data).deepEquals([{a: 1}]) + } + }) + }) }) \ No newline at end of file diff --git a/request/request.js b/request/request.js index 54266d12..5ddc620d 100644 --- a/request/request.js +++ b/request/request.js @@ -40,7 +40,7 @@ module.exports = function($window, Promise) { response[i] = new args.type(response[i]) } } - else response = new args.type(response[i]) + else response = new args.type(response) } resolve(response) diff --git a/request/tests/test-ajax.js b/request/tests/test-ajax.js index 22ff8f60..682c7a8e 100644 --- a/request/tests/test-ajax.js +++ b/request/tests/test-ajax.js @@ -115,6 +115,132 @@ o.spec("ajax", function() { o(data).deepEquals({a: "/item/:x"}) }).then(done) }) + o("type parameter works for Array responses", function(done) { + var Entity = function(args) { + return {_id: args.id} + } + + mock.$defineRoutes({ + "GET /item": function(request) { + return {status: 200, responseText: JSON.stringify([{id: 1}, {id: 2}, {id: 3}])} + } + }) + ajax({method: "GET", url: "/item", type: Entity}).then(function(data) { + o(data).deepEquals([{_id: 1}, {_id: 2}, {_id: 3}]) + }).then(done) + }) + o("type parameter works for Object responses", function(done) { + var Entity = function(args) { + return {_id: args.id} + } + + mock.$defineRoutes({ + "GET /item": function(request) { + return {status: 200, responseText: JSON.stringify({id: 1})} + } + }) + ajax({method: "GET", url: "/item", type: Entity}).then(function(data) { + o(data).deepEquals({_id: 1}) + }).then(done) + }) + o("serialize parameter works in GET", function(done) { + var serialize = function(data) { + return "id=" + data.id + } + + mock.$defineRoutes({ + "GET /item": function(request) { + return {status: 200, responseText: JSON.stringify({body: request.query})} + } + }) + ajax({method: "GET", url: "/item", serialize: serialize, data: {id: 1}}).then(function(data) { + o(data.body).equals("?id=1") + }).then(done) + }) + o("serialize parameter works in POST", function(done) { + var serialize = function(data) { + return "id=" + data.id + } + + mock.$defineRoutes({ + "POST /item": function(request) { + return {status: 200, responseText: JSON.stringify({body: request.body})} + } + }) + ajax({method: "POST", url: "/item", serialize: serialize, data: {id: 1}}).then(function(data) { + o(data.body).equals("id=1") + }).then(done) + }) + o("deserialize parameter works in GET", function(done) { + var deserialize = function(data) { + return data + } + + mock.$defineRoutes({ + "GET /item": function(request) { + return {status: 200, responseText: JSON.stringify({test: 123})} + } + }) + ajax({method: "GET", url: "/item", deserialize: deserialize}).then(function(data) { + o(data).equals("{\"test\":123}") + }).then(done) + }) + o("deserialize parameter works in POST", function(done) { + var deserialize = function(data) { + return data + } + + mock.$defineRoutes({ + "POST /item": function(request) { + return {status: 200, responseText: JSON.stringify({test: 123})} + } + }) + ajax({method: "POST", url: "/item", deserialize: deserialize}).then(function(data) { + o(data).equals("{\"test\":123}") + }).then(done) + }) + o("extract parameter works in GET", function(done) { + var extract = function(data) { + return JSON.stringify({test: 123}) + } + + mock.$defineRoutes({ + "GET /item": function(request) { + return {status: 200, responseText: ""} + } + }) + ajax({method: "GET", url: "/item", extract: extract}).then(function(data) { + o(data).deepEquals({test: 123}) + }).then(done) + }) + o("extract parameter works in POST", function(done) { + var extract = function(data) { + return JSON.stringify({test: 123}) + } + + mock.$defineRoutes({ + "POST /item": function(request) { + return {status: 200, responseText: ""} + } + }) + ajax({method: "POST", url: "/item", extract: extract}).then(function(data) { + o(data).deepEquals({test: 123}) + }).then(done) + }) + o("config parameter works", function(done) { + mock.$defineRoutes({ + "POST /item": function(request) { + return {status: 200, responseText: ""} + } + }) + ajax({method: "POST", url: "/item", config: config}).then(done) + + function config(xhr) { + o(typeof xhr.setRequestHeader).equals("function") + o(typeof xhr.open).equals("function") + o(typeof xhr.send).equals("function") + } + }) }) o.spec("failure", function() { o("rejects on server error", function(done) { diff --git a/router/tests/test-defineRoutes.js b/router/tests/test-defineRoutes.js index b396036e..bb6ba98e 100644 --- a/router/tests/test-defineRoutes.js +++ b/router/tests/test-defineRoutes.js @@ -17,182 +17,193 @@ o.spec("Router.defineRoutes", function() { onFail = o.spy() }) - o.spec("defineRoutes", function() { - o("resolves to route", function() { - $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) - }) + o("resolves to route", function() { + $window.location.href = prefix + "/test" + router.defineRoutes({"/test": {data: 1}}, onRouteChange, onFail) - o("resolves to route w/ escaped unicode", function() { - $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) - }) + o(onRouteChange.callCount).equals(1) + o(onRouteChange.args).deepEquals([{data: 1}, {}, "/test", "/test"]) + o(onFail.callCount).equals(0) + }) + + o("resolves to route w/ escaped unicode", function() { + $window.location.href = prefix + "/%C3%B6?%C3%B6=%C3%B6#%C3%B6=%C3%B6" + router.defineRoutes({"/ö": {data: 2}}, onRouteChange, onFail) - o("resolves to route w/ unicode", function() { - $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) - }) + o(onRouteChange.callCount).equals(1) + o(onRouteChange.args).deepEquals([{data: 2}, {"ö": "ö"}, "/ö?ö=ö#ö=ö", "/ö"]) + o(onFail.callCount).equals(0) + }) + + o("resolves to route w/ unicode", function() { + $window.location.href = prefix + "/ö?ö=ö#ö=ö" + router.defineRoutes({"/ö": {data: 2}}, onRouteChange, onFail) - o("handles parameterized route", function() { - $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) - }) + o(onRouteChange.callCount).equals(1) + o(onRouteChange.args).deepEquals([{data: 2}, {"ö": "ö"}, "/ö?ö=ö#ö=ö", "/ö"]) + o(onFail.callCount).equals(0) + }) + + o("resolves to route on fallback mode", function() { + $window.location.href = "file://" + prefix + "/test" - o("handles multi-parameterized route", function() { - $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) - }) + router = new Router($window) + router.setPrefix(prefix) - o("handles rest parameterized route", function() { - $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) - }) + router.defineRoutes({"/test": {data: 1}}, onRouteChange, onFail) - o("handles route with search", function() { - $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) - }) + o(onRouteChange.callCount).equals(1) + o(onRouteChange.args).deepEquals([{data: 1}, {}, "/test", "/test"]) + o(onFail.callCount).equals(0) + }) + + o("handles parameterized route", function() { + $window.location.href = prefix + "/test/x" + router.defineRoutes({"/test/:a": {data: 1}}, onRouteChange, onFail) - o("handles route with hash", function() { - $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) - }) + o(onRouteChange.callCount).equals(1) + o(onRouteChange.args).deepEquals([{data: 1}, {a: "x"}, "/test/x", "/test/:a"]) + o(onFail.callCount).equals(0) + }) + + o("handles multi-parameterized route", function() { + $window.location.href = prefix + "/test/x/y" + router.defineRoutes({"/test/:a/:b": {data: 1}}, onRouteChange, onFail) - o("handles route with search and hash", function() { - $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) - }) + 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) + }) + + o("handles rest parameterized route", function() { + $window.location.href = prefix + "/test/x/y" + router.defineRoutes({"/test/:a...": {data: 1}}, onRouteChange, onFail) - o("calls reject", function() { - $window.location.href = prefix + "/test" - router.defineRoutes({"/other": {data: 1}}, onRouteChange, onFail) - - o(onFail.callCount).equals(1) - o(onFail.args).deepEquals(["/test", {}]) - }) + o(onRouteChange.callCount).equals(1) + o(onRouteChange.args).deepEquals([{data: 1}, {a: "x/y"}, "/test/x/y", "/test/:a..."]) + o(onFail.callCount).equals(0) + }) + + o("handles route with search", function() { + $window.location.href = prefix + "/test?a=b&c=d" + router.defineRoutes({"/test": {data: 1}}, onRouteChange, onFail) - o("calls reject w/ search and hash", function() { - $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"}]) - }) + 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) + }) + + o("handles route with hash", function() { + $window.location.href = prefix + "/test#a=b&c=d" + router.defineRoutes({"/test": {data: 1}}, onRouteChange, onFail) - o("handles out of order routes", function() { - $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"]) - }) + 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) + }) + + o("handles route with search and hash", function() { + $window.location.href = prefix + "/test?a=b#c=d" + router.defineRoutes({"/test": {data: 1}}, onRouteChange, onFail) - o("handles reverse out of order routes", function() { - $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..."]) - }) + 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) + }) + + o("calls reject", function() { + $window.location.href = prefix + "/test" + router.defineRoutes({"/other": {data: 1}}, onRouteChange, onFail) - o("handles dynamically added out of order routes", function() { - var routes = {} - 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"]) - }) + o(onFail.callCount).equals(1) + o(onFail.args).deepEquals(["/test", {}]) + }) + + o("calls reject w/ search and hash", function() { + $window.location.href = prefix + "/test?a=b#c=d" + router.defineRoutes({"/other": {data: 1}}, onRouteChange, onFail) - o("handles reversed dynamically added out of order routes", function() { - var routes = {} - routes["/:a..."] = {data: 2} - routes["/z/y/x"] = {data: 1} - - $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..."]) - }) + o(onFail.callCount).equals(1) + o(onFail.args).deepEquals(["/test?a=b#c=d", {a: "b", c: "d"}]) + }) + + o("handles out of order routes", function() { + $window.location.href = prefix + "/z/y/x" + router.defineRoutes({"/z/y/x": {data: 1}, "/:a...": {data: 2}}, onRouteChange, onFail) - o("handles mixed out of order routes", function() { - 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"]) - }) + o(onRouteChange.callCount).equals(1) + o(onRouteChange.args).deepEquals([{data: 1}, {}, "/z/y/x", "/z/y/x"]) + }) + + o("handles reverse out of order routes", function() { + $window.location.href = prefix + "/z/y/x" + router.defineRoutes({"/:a...": {data: 2}, "/z/y/x": {data: 1}}, onRouteChange, onFail) - o("handles reverse mixed out of order routes", function() { - 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..."]) - }) + o(onRouteChange.callCount).equals(1) + o(onRouteChange.args).deepEquals([{data: 2}, {a: "z/y/x"}, "/z/y/x", "/:a..."]) + }) + + o("handles dynamically added out of order routes", function() { + var routes = {} + routes["/z/y/x"] = {data: 1} + routes["/:a..."] = {data: 2} - o("handles non-ascii routes", function() { - $window.location.href = prefix + "/ö" - router.defineRoutes({"/ö": "aaa"}, onRouteChange, onFail) - - o(onRouteChange.callCount).equals(1) - }) + $window.location.href = prefix + "/z/y/x" + router.defineRoutes(routes, onRouteChange, onFail) - o("replays", function() { - $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) - }) + o(onRouteChange.callCount).equals(1) + o(onRouteChange.args).deepEquals([{data: 1}, {}, "/z/y/x", "/z/y/x"]) + }) + + o("handles reversed dynamically added out of order routes", function() { + var routes = {} + routes["/:a..."] = {data: 2} + routes["/z/y/x"] = {data: 1} + + $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..."]) + }) + + o("handles mixed out of order routes", function() { + 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"]) + }) + + o("handles reverse mixed out of order routes", function() { + 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..."]) + }) + + o("handles non-ascii routes", function() { + $window.location.href = prefix + "/ö" + router.defineRoutes({"/ö": "aaa"}, onRouteChange, onFail) + + o(onRouteChange.callCount).equals(1) + }) + + o("replays", function() { + $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) }) }) }) diff --git a/router/tests/test-setPath.js b/router/tests/test-setPath.js index f178ce88..b85c97b2 100644 --- a/router/tests/test-setPath.js +++ b/router/tests/test-setPath.js @@ -38,6 +38,18 @@ o.spec("Router.setPath", function() { o(router.getPath()).equals("/ö?ö=ö#ö=ö") }) + + o("sets route on fallback mode", function() { + $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") + }) o("sets route via pushState/onpopstate", function() { $window.location.href = prefix + "/test" router.defineRoutes({"/test": {data: 1}, "/other/:a/:b...": {data: 2}}, onRouteChange, onFail) diff --git a/test-utils/ajaxMock.js b/test-utils/ajaxMock.js index a8b68666..cc246c5c 100644 --- a/test-utils/ajaxMock.js +++ b/test-utils/ajaxMock.js @@ -8,7 +8,7 @@ module.exports = function() { var routes = {} var callback = "callback" var serverErrorHandler = function() { - return {status: 500, responseText: "server error"} + return {status: 500, responseText: "server error, most likely the URL was not defined"} } var $window = { diff --git a/test-utils/domMock.js b/test-utils/domMock.js index e8bf9095..2270df74 100644 --- a/test-utils/domMock.js +++ b/test-utils/domMock.js @@ -275,8 +275,7 @@ module.exports = function() { get: function() { var options = getOptions(this.parentNode) var index = options.indexOf(this) - if (index > -1) return index === this.parentNode.selectedIndex - return false + return index === this.parentNode.selectedIndex }, set: function(value) { if (value) { diff --git a/test-utils/pushStateMock.js b/test-utils/pushStateMock.js index 1ab14c72..918883f7 100644 --- a/test-utils/pushStateMock.js +++ b/test-utils/pushStateMock.js @@ -114,10 +114,10 @@ module.exports = function() { }, set origin(value) { - console.warn("Origin is writable but ignored") + //origin is writable but ignored }, set host(value) { - console.warn("Host is writable but ignored in Chrome") + //host is writable but ignored in Chrome }, set href(value) { var url = getURL() @@ -160,7 +160,6 @@ module.exports = function() { } }, }, - scrollTo: function(x, y) {}, onpopstate: null, onhashchange: null, onunload: null, diff --git a/test-utils/tests/test-domMock.js b/test-utils/tests/test-domMock.js index b7dac365..1a9dc8d1 100644 --- a/test-utils/tests/test-domMock.js +++ b/test-utils/tests/test-domMock.js @@ -345,6 +345,13 @@ o.spec("domMock", function() { o(div.attributes["id"].nodeValue).equals("[object Object]") }) + o("setting via attributes map stringifies", function() { + var div = $document.createElement("div") + div.setAttribute("id", "a") + div.attributes["id"].nodeValue = 123 + + o(div.attributes["id"].nodeValue).equals("123") + }) }) o.spec("setAttributeNS", function() { @@ -681,10 +688,16 @@ o.spec("domMock", function() { var option1 = $document.createElement("option") option1.appendChild($document.createTextNode("a")) + var option2 = $document.createElement("option") + option2.appendChild($document.createTextNode("b")) select.appendChild(option1) + select.appendChild(option2) o(select.value).equals("a") o(select.selectedIndex).equals(0) + o(select.childNodes[0].selected).equals(true) + o(select.childNodes[0].value).equals("a") + o(select.childNodes[1].value).equals("b") }) o("value defaults to invalid if no options", function() { var select = $document.createElement("select") @@ -708,6 +721,29 @@ o.spec("domMock", function() { o(select.value).equals("b") o(select.selectedIndex).equals(1) }) + o("setting valid value works with optgroup", function() { + var select = $document.createElement("select") + + var option1 = $document.createElement("option") + option1.setAttribute("value", "a") + + var option2 = $document.createElement("option") + option2.setAttribute("value", "b") + + var option3 = $document.createElement("option") + option3.setAttribute("value", "c") + + var optgroup = $document.createElement("optgroup") + optgroup.appendChild(option1) + optgroup.appendChild(option2) + select.appendChild(optgroup) + select.appendChild(option3) + + select.value = "b" + + o(select.value).equals("b") + o(select.selectedIndex).equals(1) + }) o("setting valid selectedIndex works", function() { var select = $document.createElement("select") @@ -740,6 +776,23 @@ o.spec("domMock", function() { o(select.value).equals("b") o(select.selectedIndex).equals(1) }) + o("unsetting option[selected] works", function() { + var select = $document.createElement("select") + + var option1 = $document.createElement("option") + option1.setAttribute("value", "a") + select.appendChild(option1) + + var option2 = $document.createElement("option") + option2.setAttribute("value", "b") + select.appendChild(option2) + + select.childNodes[1].selected = true + select.childNodes[1].selected = false + + o(select.value).equals("a") + o(select.selectedIndex).equals(0) + }) o("setting invalid value yields a selectedIndex of -1 and value of empty string", function() { var select = $document.createElement("select") diff --git a/test-utils/tests/test-pushStateMock.js b/test-utils/tests/test-pushStateMock.js index ff811487..ab943ef2 100644 --- a/test-utils/tests/test-pushStateMock.js +++ b/test-utils/tests/test-pushStateMock.js @@ -156,6 +156,65 @@ o.spec("pushStateMock", function() { o($window.location.hash).equals("#b") }) }) + o.spec("set pathname", function() { + o("changes url on location.pathname change", function() { + var old = $window.location.href + $window.location.pathname = "/a" + + o(old).equals("http://localhost/") + o($window.location.href).equals("http://localhost/a") + o($window.location.pathname).equals("/a") + }) + }) + o.spec("set protocol", function() { + o("setting protocol throws", function(done) { + var old = $window.location.href + try { + $window.location.protocol = "https://" + } + catch (e) { + done() + } + }) + }) + o.spec("set port", function() { + o("setting origin changes href", function() { + var old = $window.location.href + $window.location.port = "81" + + o(old).equals("http://localhost/") + o($window.location.port).equals("81") + o($window.location.href).equals("http://localhost:81/") + }) + }) + o.spec("set hostname", function() { + o("setting hostname changes href", function() { + var old = $window.location.href + $window.location.hostname = "127.0.0.1" + + o(old).equals("http://localhost/") + o($window.location.hostname).equals("127.0.0.1") + o($window.location.href).equals("http://127.0.0.1/") + }) + }) + o.spec("set origin", function() { + o("setting origin is ignored", function() { + var old = $window.location.href + $window.location.origin = "http://127.0.0.1" + + o(old).equals("http://localhost/") + o($window.location.origin).equals("http://localhost") + }) + }) + o.spec("set host", function() { + o("setting host is ignored", function() { + var old = $window.location.href + $window.location.host = "http://127.0.0.1" + + o(old).equals("http://localhost/") + o($window.location.host).equals("localhost") + }) + }) o.spec("pushState", function() { o("changes url on pushstate", function() { var old = $window.location.href diff --git a/util/tests/test-withAttr.js b/util/tests/test-withAttr.js index d6fd6f4f..44ee0f14 100644 --- a/util/tests/test-withAttr.js +++ b/util/tests/test-withAttr.js @@ -14,6 +14,19 @@ o.spec("withAttr", function() { o(spy.args).deepEquals([1]) o(spy.this).equals(context) }) + o("works with attribute", function() { + var target = { + getAttribute: function() {return "readonly"} + } + var spy = o.spy() + var context = { + handler: withAttr("readonly", spy) + } + context.handler({currentTarget: target}) + + o(spy.args).deepEquals(["readonly"]) + o(spy.this).equals(context) + }) o("context arg works", function() { var spy = o.spy() var context = {}