UNPKG

can-route

Version:

Observable front-end application routing for CanJS.

513 lines (393 loc) 11.9 kB
/* jshint asi:true */ /* jshint -W079 */ require("./route-define-iframe-test"); var canRoute = require("can-route"); var QUnit = require("steal-qunit"); var DefineMap = require("can-define/map/map"); var canReflect = require("can-reflect"); var stacheKey = require("can-stache-key"); var Observation = require("can-observation"); var queues = require("can-queues"); window.queues = queues; var mockRoute = require("./mock-route-binding"); var stringify = require("../src/string-coercion").stringify; require("can-observation"); QUnit.module("can-route with can-define/map/map", { beforeEach: function(assert) { canRoute.routes = {}; canRoute._teardown(); canRoute.urlData = canRoute.bindings.hashchange //canRoute.defaultBinding = "hashchange"; this.fixture = document.getElementById("qunit-fixture"); } }); if (("onhashchange" in window)) { if (typeof steal !== "undefined") { QUnit.test("canRoute.map: conflicting route values, hash should win (canjs/canjs#979)", function(assert) { var done = assert.async(); mockRoute.start(); var AppState = DefineMap.extend({seal: false},{}); var appState = new AppState({type: "dog", id: "4"}); canRoute.data = appState; canRoute.register("{type}/{id}"); canRoute._onStartComplete = function () { var after = mockRoute.hash.get(); assert.equal(after, "cat/5", "same URL"); assert.equal(appState.get("type"), "cat", "conflicts should be won by the URL"); assert.equal(appState.get("id"), "5", "conflicts should be won by the URL"); done(); mockRoute.stop(); }; mockRoute.hash.value = "#!cat/5"; canRoute.start(); }); QUnit.test("canRoute.map: route is initialized from URL first, then URL params are added from canRoute.data (canjs/canjs#979)", function(assert) { var done = assert.async(); mockRoute.start(); var AppState = DefineMap.extend({seal: false},{}); var appState = new AppState({section: "home"}); canRoute.data = appState; canRoute.register("{type}/{id}"); canRoute._onStartComplete = function () { assert.equal(mockRoute.hash.value, "cat/5&section=home", "same URL"); assert.equal(appState.get("type"), "cat", "hash populates the appState"); assert.equal(appState.get("id"), "5", "hash populates the appState"); assert.equal(appState.get("section"), "home", "appState keeps its properties"); assert.ok(canRoute.data === appState, "canRoute.data is the same as appState"); mockRoute.stop(); done(); }; mockRoute.hash.value = "#!cat/5"; // type and id get added ... this will call update url to add everything canRoute.start(); }); QUnit.test("sticky enough routes (canjs#36)", function(assert) { var done = assert.async(); mockRoute.start(); canRoute.data = new DefineMap(); canRoute.register("active"); canRoute.register(""); mockRoute.hash.set("#active"); canRoute.start() setTimeout(function () { var after = mockRoute.hash.get(); assert.equal(after, "active"); mockRoute.stop(); done(); }, 30); }); QUnit.test("canRoute.current is live-bindable (#1156)", function(assert) { var ready = assert.async(); mockRoute.start(); canRoute.data = new DefineMap(); canRoute.start(); var isOnTestPage = new Observation(function isCurrent(){ return canRoute.isCurrent({page: "test"}); }); canReflect.onValue(isOnTestPage, function isCurrentChanged(){ // unbind now because isCurrent depends on urlData isOnTestPage.off(); mockRoute.stop(); ready(); }); assert.equal(canRoute.isCurrent({page: "test"}), false, "initially not on test page") setTimeout(function(){ canRoute.data.set("page","test"); },20); }); QUnit.test("can.compute.read should not call canRoute (#1154)", function(assert) { var ready = assert.async(); mockRoute.start(); canRoute.data = new DefineMap(); canRoute.attr("page","test"); canRoute.start(); var val = stacheKey.read({route: canRoute},stacheKey.reads("route")).value; setTimeout(function(){ assert.equal(val,canRoute,"read correctly"); mockRoute.stop(); ready(); },1); }); QUnit.test("routes should deep clean", function(assert) { var ready = assert.async(); assert.expect(2); mockRoute.start(); var hash1 = canRoute.url({ panelA: { name: "fruit", id: 15, show: true } }); var hash2 = canRoute.url({ panelA: { name: "fruit", id: 20, read: false } }); mockRoute.hash.value = hash1; mockRoute.hash.value = hash2; canRoute._onStartComplete = function() { assert.equal(canRoute.data.get("panelA").id, 20, "id should change"); assert.equal(canRoute.data.get("panelA").show, undefined, "show should be removed"); mockRoute.stop(); ready(); }; canRoute.data = new DefineMap(); canRoute.start(); }); QUnit.test("updating bound DefineMap causes single update with a coerced string value", function(assert) { var ready = assert.async(); assert.expect(1); canRoute.start(); var MyMap = DefineMap.extend({seal: false},{"*": "stringOrObservable"}); var appVM = new MyMap(); canRoute.data = appVM; canRoute._onStartComplete = function(){ appVM.on("action", function(ev, newVal) { assert.strictEqual(newVal, "10"); }); appVM.set("action", 10); // check after 30ms to see that we only have a single call setTimeout(function() { mockRoute.stop(); ready(); }, 5); }; canRoute.start(); }); QUnit.test("hash doesn't update to itself with a !", function(assert) { var done = assert.async(); window.routeTestReady = function (iCanRoute, loc) { iCanRoute.start(); iCanRoute.register("{path}"); iCanRoute.attr("path", "foo"); setTimeout(function() { var counter = 0; try { assert.equal(loc.hash, "#!foo"); } catch(e) { done(); throw e; } iCanRoute.serializedCompute.bind("change", function() { counter++; }); loc.hash = "bar"; setTimeout(function() { try { assert.equal(loc.hash, "#bar"); assert.equal(counter, 1); //sanity check -- bindings only ran once before this change. } finally { done(); } }, 100); }, 100); }; var iframe = document.createElement("iframe"); iframe.src = __dirname+"/define-testing.html?1"; this.fixture.appendChild(iframe); }); } QUnit.test("escaping periods", function(assert) { canRoute.data = new DefineMap({}); canRoute.routes = {}; canRoute.register("{page}\\.html", { page: "index" }); var obj = canRoute.deparam("can.Control.html"); assert.deepEqual(obj, { page: "can.Control" }); assert.equal(canRoute.param({ page: "can.Control" }), "can.Control.html"); }); if (typeof require !== "undefined") { QUnit.test("correct stringing", function(assert) { mockRoute.start(); var RouteData = DefineMap.extend("RouteData", { seal: false }, { "*": { type: stringify } }); canRoute.data = new RouteData({}); canRoute.routes = {}; canRoute.attr({ number: 1, bool: true, string: "hello", array: [1, true, "hello"] }); assert.deepEqual(canRoute.attr(),{ number: "1", bool: "true", string: "hello", array: ["1", "true", "hello"] }); canReflect.update(canRoute.data, {}); canRoute.attr({ number: 1, bool: true, string: "hello", array: [2, false, "world"], obj: { number: 3, array: [4, true] } }); assert.deepEqual(canRoute.attr(), { number: "1", bool: "true", string: "hello", array: ["2", "false", "world"], obj: { number: "3", array: ["4", "true"] } }, "nested object"); canRoute.routes = {}; canRoute.register("{type}/{id}"); canReflect.update(canRoute.data, {}); canRoute.attr({ type: "page", id: 10, sort_by_name: true }); assert.propEqual(canRoute.attr(), { type: "page", id: "10", sort_by_name: "true" }); }); } QUnit.test("on/off binding", function(assert) { canRoute.data = new DefineMap(); canRoute.routes = {}; assert.expect(1) canRoute.on("foo", function () { assert.ok(true, "foo called"); canRoute.off("foo"); canRoute.attr("foo", "baz"); }); canRoute.attr("foo", "bar"); }); QUnit.test("two way binding canRoute.map with DefineMap instance", function(assert) { assert.expect(2); var done = assert.async(); mockRoute.start(); var AppState = DefineMap.extend({seal: false},{"*": "stringOrObservable"}); var appState = new AppState(); canRoute.data = appState; canRoute.start(); canRoute.serializedCompute.bind("change", function(){ assert.equal(canRoute.attr("name"), "Brian", "appState is bound to canRoute"); canRoute.serializedCompute.unbind("change"); appState.name = undefined; setTimeout(function(){ assert.equal( mockRoute.hash.get(), ""); mockRoute.stop(); done(); },20); }); appState.set("name", "Brian"); }); QUnit.test(".url with merge=true", function(assert) { mockRoute.start() var AppState = DefineMap.extend({seal: false},{"*": "stringOrObservable"}); var appState = new AppState({}); canRoute.data = appState; canRoute.start(); var done = assert.async(); appState.set("foo", "bar"); // TODO: expose a way to know when the url has changed. setTimeout(function(){ var result = canRoute.url({page: "recipe", id: 5}, true); assert.equal(result, "#!&foo=bar&page=recipe&id=5"); mockRoute.stop(); done(); },20); }); } QUnit.test("param with whitespace in interpolated string (#45)", function(assert) { canRoute.data = new DefineMap({}); canRoute.routes = {}; canRoute.register("{ page }", { page: "index" }) var res = canRoute.param({ page: "index" }); assert.equal(res, "") canRoute.register("pages/{ p1 }/{ p2 }/{ p3 }", { p1: "index", p2: "foo", p3: "bar" }) res = canRoute.param({ p1: "index", p2: "foo", p3: "bar" }); assert.equal(res, "pages///") res = canRoute.param({ p1: "index", p2: "baz", p3: "bar" }); assert.equal(res, "pages//baz/") }); QUnit.test("triggers __url event anytime a there's a change to individual properties", function(assert) { mockRoute.start(); var AppState = DefineMap.extend({seal: false},{"*": "stringOrObservable", page: "string", section: "string"}); var appState = new AppState({}); canRoute.data = appState; canRoute.register("{page}"); canRoute.register("{page}/{section}"); var done = assert.async(); canRoute.start(); var timeoutID = setTimeout(function page_two() { canRoute.data.page = "two"; }, 50); var matchedCount = 0; var onMatchCall = { 1: function section_a() { canRoute.data.section = "a"; }, 2: function section_b() { canRoute.data.section = "b"; }, 3: function(){ // 1st call is going from undefined to empty string assert.equal(matchedCount, 3, "calls __url event every time a property is changed"); mockRoute.stop(); clearTimeout(timeoutID); done(); } } canRoute.on("__url", function updateMatchedCount() { // any time a route property is changed, not just the matched route matchedCount++; onMatchCall[matchedCount](); }); }); QUnit.test("updating unserialized prop on bound DefineMap causes single update without a coerced string value", function(assert) { var ready = assert.async(); assert.expect(1); canRoute.routes = {}; mockRoute.start(); var appVM = new (DefineMap.extend({ action: {serialize: false, type: "*"} }))(); canRoute.data = appVM; canRoute.start(); appVM.bind("action", function(ev, newVal) { assert.equal(typeof newVal, "function"); }); appVM.set("action", function() {}); // check after 30ms to see that we only have a single call setTimeout(function() { mockRoute.stop(); ready(); }, 5); });