UNPKG

ampersand-router

Version:

Clientside router with fallbacks for browsers that don't support pushState. Mostly lifted from Backbone.js.

1,019 lines (894 loc) 33.6 kB
//Don't want to call these, but it breaks IE9, so welp window.history.pushState = window.history.replaceState = function () {}; var tape = require('tape'); var test = tape; tape.Test.prototype.strictEqual = function () { this.equal.apply(this, arguments); }; var AmpHistory = require('../ampersand-history'); var AmpRouter = require('../ampersand-router'); var AmpHistoryConstructor = AmpHistory.constructor; function module(moduleName, opts) { test = function (name, n, cb) { if (!cb && typeof n === 'function') { cb = n; n = null; } tape(moduleName + ': ' + name, function (t) { if (opts.setup) opts.setup(); if (n) t.plan(n); cb(t); if (opts.teardown) opts.teardown(); if (!n) t.end(); }); }; test.only = function (name, n, cb) { tape.only(moduleName + ': ' + name, function (t) { if (opts.setup) opts.setup(); if (n) t.plan(n); cb(t); if (opts.teardown) opts.teardown(); if (!n) t.end(); }); }; } function restartHistoryWithoutPushState() { AmpHistory.stop(); AmpHistory.start({pushState: false}); } (function () { var bind = require('lodash/bind'); var extend = require('lodash/assign'); var each = require('lodash/forEach'); var pick = require('lodash/pick'); var router = null; var location = null; var lastRoute = null; var lastArgs = []; var onRoute = function (router, route, args) { lastRoute = route; lastArgs = args; }; var Location = function (href) { this.replace(href); }; extend(Location.prototype, { parser: document.createElement('a'), replace: function (href) { this.parser.href = href; extend(this, pick(this.parser, 'href', 'hash', 'host', 'search', 'fragment', 'pathname', 'protocol' )); // In IE, anchor.pathname does not contain a leading slash though // window.location.pathname does. if (!/^\//.test(this.pathname)) this.pathname = '/' + this.pathname; }, toString: function () { return this.href; } }); module("Router", { setup: function () { location = new Location('http://example.com'); history.location = location; AmpHistory = extend(new AmpHistoryConstructor(), {location: location}); router = new Router({testing: 101, history: AmpHistory}); AmpHistory.interval = 9; AmpHistory.start(); lastRoute = null; lastArgs = []; AmpHistory.on('route', onRoute); }, teardown: function () { AmpHistory.stop(); AmpHistory.off('route', onRoute); } }); var ExternalObject = { value: 'unset', routingFunction: function (value) { this.value = value; } }; ExternalObject.routingFunction = bind(ExternalObject.routingFunction, ExternalObject); var Router = AmpRouter.extend({ count: 0, routes: { "noCallback": "noCallback", "counter": "counter", "noargs": "noargs", "search/:query": "search", "search/:query/p:page": "search", "charñ": "charUTF", "char%C3%B1": "charEscaped", "contacts": "contacts", "contacts/new": "newContact", "contacts/:id": "loadContact", "route-event/:arg": "routeEvent", "optional(/:item)": "optionalItem", "named/optional/(y:z)": "namedOptional", "splat/*args/end": "splat", ":repo/compare/*from...*to": "github", "decode/:named/*splat": "decode", "*first/complex-*part/*rest": "complex", "query/:entity": "query", "function/:value": ExternalObject.routingFunction, "*anything": "anything" }, initialize: function (options) { this.testing = options.testing; this.route('implicit', 'implicit'); }, counter: function () { this.count++; }, implicit: function () { this.count++; }, search: function (query, page) { this.query = query; this.page = page; }, charUTF: function () { this.charType = 'UTF'; }, charEscaped: function () { this.charType = 'escaped'; }, contacts: function () { this.contact = 'index'; }, newContact: function () { this.contact = 'new'; }, loadContact: function () { this.contact = 'load'; }, optionalItem: function (arg) { this.arg = arg != void 0 ? arg : null; }, splat: function (args) { this.args = args; }, github: function (repo, from, to) { this.repo = repo; this.from = from; this.to = to; }, complex: function (first, part, rest) { this.first = first; this.part = part; this.rest = rest; }, noargs: function() { this.args = arguments; }, query: function (entity, args) { this.entity = entity; this.queryArgs = args; }, anything: function (whatever) { this.anything = whatever; }, namedOptional: function (z) { this.z = z; }, decode: function (named, path) { this.named = named; this.path = path; }, routeEvent: function (arg) { } }); test("initialize", 1, function (t) { t.equal(router.testing, 101); }); test("routes (simple)", 4, function (t) { restartHistoryWithoutPushState(); location.replace('http://example.com#search/news'); AmpHistory.checkUrl(); t.equal(router.query, 'news'); t.equal(router.page, undefined); t.equal(lastRoute, 'search'); t.equal(lastArgs[0], 'news'); }); test("routes (simple, but unicode)", 4, function (t) { restartHistoryWithoutPushState(); location.replace('http://example.com#search/' + encodeURIComponent('тест')); AmpHistory.checkUrl(); t.equal(router.query, 'тест'); t.equal(router.page, undefined); t.equal(lastRoute, 'search'); t.equal(lastArgs[0], 'тест'); }); test("routes (two part)", 2, function (t) { restartHistoryWithoutPushState(); location.replace('http://example.com#search/nyc/p10'); AmpHistory.checkUrl(); t.equal(router.query, 'nyc'); t.equal(router.page, '10'); }); test("routes via navigate", 2, function (t) { AmpHistory.navigate('search/manhattan/p20'); t.equal(router.query, 'manhattan'); t.equal(router.page, '20'); }); test("routes via navigate with params", 1, function (t) { AmpHistory.navigate('query/test?a=b'); t.equal(router.queryArgs, 'a=b'); }); test("routes via navigate for backwards-compatibility", 2, function (t) { AmpHistory.navigate('search/manhattan/p20', true); t.equal(router.query, 'manhattan'); t.equal(router.page, '20'); }); test("reports matched route via nagivate", 1, function (t) { t.ok(AmpHistory.navigate('search/manhattan/p20', true)); }); // I guess that this test is currently redundant after we switched to `{trigger: true}` by default test("route precedence via navigate", 6, function (t) { // check both 0.9.x and backwards-compatibility options each([ { trigger: true }, true ], function (options) { AmpHistory.navigate('contacts', options); t.equal(router.contact, 'index'); AmpHistory.navigate('contacts/new', options); t.equal(router.contact, 'new'); AmpHistory.navigate('contacts/foo', options); t.equal(router.contact, 'load'); }); }); test("loadUrl is not called for identical routes.", 1, function (t) { restartHistoryWithoutPushState(); AmpHistory.navigate('route'); AmpHistory.loadUrl = function () { t.ok(false); }; AmpHistory.navigate('route'); AmpHistory.navigate('/route'); AmpHistory.navigate('/route'); t.ok(true); }); test("use implicit callback if none provided", 1, function (t) { router.count = 0; router.navigate('implicit'); t.equal(router.count, 1); }); test("routes via navigate with {replace: true}", 1, function (t) { restartHistoryWithoutPushState(); location.replace('http://example.com#start_here'); AmpHistory.checkUrl(); location.replace = function (href) { t.strictEqual(href, new Location('http://example.com#end_here').href); }; AmpHistory.navigate('end_here', {replace: true}); }); test("routes (splats)", 1, function (t) { restartHistoryWithoutPushState(); location.replace('http://example.com#splat/long-list/of/splatted_99args/end'); AmpHistory.checkUrl(); t.equal(router.args, 'long-list/of/splatted_99args'); }); test("routes (github)", 3, function (t) { restartHistoryWithoutPushState(); location.replace('http://example.com#backbone/compare/1.0...braddunbar:with/slash'); AmpHistory.checkUrl(); t.equal(router.repo, 'backbone'); t.equal(router.from, '1.0'); t.equal(router.to, 'braddunbar:with/slash'); }); test("routes (optional)", 2, function (t) { restartHistoryWithoutPushState(); location.replace('http://example.com#optional'); AmpHistory.checkUrl(); t.ok(!router.arg); location.replace('http://example.com#optional/thing'); AmpHistory.checkUrl(); t.equal(router.arg, 'thing'); }); test("routes (complex)", 3, function (t) { restartHistoryWithoutPushState(); location.replace('http://example.com#one/two/three/complex-part/four/five/six/seven'); AmpHistory.checkUrl(); t.equal(router.first, 'one/two/three'); t.equal(router.part, 'part'); t.equal(router.rest, 'four/five/six/seven'); }); test("routes (query)", 5, function (t) { restartHistoryWithoutPushState(); location.replace('http://example.com#query/mandel?a=b&c=d'); AmpHistory.checkUrl(); t.equal(router.entity, 'mandel'); t.equal(router.queryArgs, 'a=b&c=d'); t.equal(lastRoute, 'query'); t.equal(lastArgs[0], 'mandel'); t.equal(lastArgs[1], 'a=b&c=d'); }); test("routes (anything)", 1, function (t) { restartHistoryWithoutPushState(); location.replace('http://example.com#doesnt-match-a-route'); AmpHistory.checkUrl(); t.equal(router.anything, 'doesnt-match-a-route'); }); test("routes (function)", 3, function (t) { restartHistoryWithoutPushState(); router.on('route', function (name) { t.ok(name === ''); }); t.equal(ExternalObject.value, 'unset'); location.replace('http://example.com#function/set'); AmpHistory.checkUrl(); t.equal(ExternalObject.value, 'set'); }); test("Decode named parameters, not splats.", 2, function (t) { restartHistoryWithoutPushState(); location.replace('http://example.com#decode/a%2Fb/c%2Fd/e'); AmpHistory.checkUrl(); t.strictEqual(router.named, 'a/b'); t.strictEqual(router.path, 'c/d/e'); }); test("fires event when router doesn't have callback on it", 1, function (t) { restartHistoryWithoutPushState(); router.on("route:noCallback", function () { t.ok(true); }); location.replace('http://example.com#noCallback'); AmpHistory.checkUrl(); }); test("No events are triggered if #execute returns false.", 1, function (t) { restartHistoryWithoutPushState(); var Router = AmpRouter.extend({ routes: { foo: function () { t.ok(true); } }, execute: function (callback, args) { callback.apply(this, args); return false; } }); var router = new Router({ history: AmpHistory }); router.on('route route:foo', function () { t.ok(false); }); AmpHistory.on('route', function () { t.ok(false); }); location.replace('http://example.com#foo'); AmpHistory.checkUrl(); }); test("Passes nothing when no arguments are defined", 1, function(t) { restartHistoryWithoutPushState(); location.replace('http://example.com#noargs'); AmpHistory.checkUrl(); t.strictEqual(Object.keys(router.args).length, 0); }); test("#933, #908 - leading slash", 2, function (t) { location.replace('http://example.com/root/foo'); AmpHistory.stop(); AmpHistory = extend(new AmpHistoryConstructor(), {location: location}); AmpHistory.start({root: '/root', hashChange: false, silent: true}); t.strictEqual(AmpHistory.getFragment(), 'foo'); AmpHistory.stop(); AmpHistory = extend(new AmpHistoryConstructor(), {location: location}); AmpHistory.start({root: '/root/', hashChange: false, silent: true}); t.strictEqual(AmpHistory.getFragment(), 'foo'); }); test("#1003 - History is started before navigate is called", 1, function (t) { AmpHistory.stop(); AmpHistory.navigate = function () { t.ok(AmpHistoryConstructor.started); }; AmpHistory.start({pushState: false}); // If this is not an old IE navigate will not be called. if (!AmpHistory.iframe) t.ok(true); }); test("#967 - Route callback gets passed encoded values.", 3, function (t) { var route = 'has%2Fslash/complex-has%23hash/has%20space'; AmpHistory.navigate(route); t.strictEqual(router.first, 'has/slash'); t.strictEqual(router.part, 'has#hash'); t.strictEqual(router.rest, 'has space'); }); test("correctly handles URLs with % (#868)", 3, function (t) { restartHistoryWithoutPushState(); location.replace('http://example.com#search/fat%3A1.5%25'); AmpHistory.checkUrl(); location.replace('http://example.com#search/fat'); AmpHistory.checkUrl(); t.equal(router.query, 'fat'); t.equal(router.page, undefined); t.equal(lastRoute, 'search'); }); test("#2666 - Hashes with UTF8 in them.", 2, function (t) { AmpHistory.navigate('charñ'); t.equal(router.charType, 'UTF'); AmpHistory.navigate('char%C3%B1'); t.equal(router.charType, 'UTF'); }); test("#1185 - Use pathname when hashChange is not wanted.", 1, function (t) { AmpHistory.stop(); location.replace('http://example.com/path/name#hash'); AmpHistory = extend(new AmpHistoryConstructor(), {location: location}); AmpHistory.start({hashChange: false}); var fragment = AmpHistory.getFragment(); t.strictEqual(fragment, location.pathname.replace(/^\//, '')); }); test("#1206 - Strip leading slash before location.assign.", 1, function (t) { AmpHistory.stop(); location.replace('http://example.com/root/'); AmpHistory = extend(new AmpHistoryConstructor(), {location: location}); AmpHistory.start({pushState: false, hashChange: false, root: '/root/'}); location.assign = function (pathname) { t.strictEqual(pathname, '/root/fragment'); }; AmpHistory.navigate('/fragment'); }); test("#1387 - Root fragment without trailing slash.", 1, function (t) { AmpHistory.stop(); location.replace('http://example.com/root'); AmpHistory = extend(new AmpHistoryConstructor(), {location: location}); AmpHistory.start({hashChange: false, root: '/root/', silent: true}); t.strictEqual(AmpHistory.getFragment(), ''); }); test("#1366 - History does not prepend root to fragment.", 2, function (t) { AmpHistory.stop(); location.replace('http://example.com/root/'); AmpHistory = extend(new AmpHistoryConstructor(), { location: location, history: { pushState: function (state, title, url) { t.strictEqual(url, '/root/x'); } } }); AmpHistory.start({root: '/root/', hashChange: false}); AmpHistory.navigate('x'); t.strictEqual(AmpHistory.fragment, 'x'); }); test("Normalize root.", 1, function (t) { AmpHistory.stop(); location.replace('http://example.com/root'); AmpHistory = extend(new AmpHistoryConstructor(), { location: location, history: { pushState: function (state, title, url) { t.strictEqual(url, '/root/fragment'); } } }); AmpHistory.start({root: '/root', hashChange: false}); AmpHistory.navigate('fragment'); }); test("Normalize root.", 1, function (t) { AmpHistory.stop(); location.replace('http://example.com/root#fragment'); AmpHistory = extend(new AmpHistoryConstructor(), { location: location, history: { pushState: function (state, title, url) {}, replaceState: function (state, title, url) { t.strictEqual(url, '/root/fragment'); } } }); AmpHistory.start({root: '/root'}); }); test("Normalize root.", 1, function (t) { AmpHistory.stop(); location.replace('http://example.com/root'); AmpHistory = extend(new AmpHistoryConstructor(), {location: location}); AmpHistory.loadUrl = function () { t.ok(true); }; AmpHistory.start({root: '/root'}); }); test("Normalize root - leading slash.", 1, function (t) { AmpHistory.stop(); location.replace('http://example.com/root'); AmpHistory = extend(new AmpHistoryConstructor(), { location: location, history: { pushState: function () {}, replaceState: function () {} } }); AmpHistory.start({root: 'root'}); t.strictEqual(AmpHistory.root, '/root/'); }); test("Transition from hashChange to pushState.", 1, function (t) { AmpHistory.stop(); location.replace('http://example.com/root#x/y'); AmpHistory = extend(new AmpHistoryConstructor(), { location: location, history: { pushState: function () {}, replaceState: function (state, title, url) { t.strictEqual(url, '/root/x/y'); } } }); AmpHistory.start({root: 'root'}); }); test("#1619: Router: Normalize empty root", 1, function (t) { AmpHistory.stop(); location.replace('http://example.com/'); AmpHistory = extend(new AmpHistoryConstructor(), { location: location, history: { pushState: function () {}, replaceState: function () {} } }); AmpHistory.start({root: ''}); t.strictEqual(AmpHistory.root, '/'); }); test("#1619: Router: nagivate with empty root", 1, function (t) { AmpHistory.stop(); location.replace('http://example.com/'); AmpHistory = extend(new AmpHistoryConstructor(), { location: location, history: { pushState: function (state, title, url) { t.strictEqual(url, '/fragment'); } } }); AmpHistory.start({root: '', hashChange: false}); AmpHistory.navigate('fragment'); }); test("Transition from pushState to hashChange.", 1, function (t) { AmpHistory.stop(); location.replace('http://example.com/root/x/y?a=b'); location.replace = function (url) { t.strictEqual(url, '/root/#x/y?a=b'); }; AmpHistory = extend(new AmpHistoryConstructor(), { location: location, history: { pushState: null, replaceState: null } }); AmpHistory.start({root: 'root'}); }); test("#1695 - hashChange to pushState with search.", 1, function (t) { AmpHistory.stop(); location.replace('http://example.com/root#x/y?a=b'); AmpHistory = extend(new AmpHistoryConstructor(), { location: location, history: { pushState: function () {}, replaceState: function (state, title, url) { t.strictEqual(url, '/root/x/y?a=b'); } } }); AmpHistory.start({root: 'root'}); }); test("#1746 - Router allows empty route.", 1, function (t) { var Router = AmpRouter.extend({ routes: {'': 'empty'}, empty: function () {}, route: function (route) { t.strictEqual(route, ''); } }); new Router(); }); test("#1794 - Trailing space in fragments.", 1, function (t) { var history = new AmpHistoryConstructor(); t.strictEqual(history.getFragment('fragment '), 'fragment'); }); test("#1820 - Leading slash and trailing space.", 1, function (t) { var history = new AmpHistoryConstructor(); t.strictEqual(history.getFragment('/fragment '), 'fragment'); }); test("#1980 - Optional parameters.", 2, function (t) { restartHistoryWithoutPushState(); location.replace('http://example.com#named/optional/y'); AmpHistory.checkUrl(); t.strictEqual(router.z, undefined); location.replace('http://example.com#named/optional/y123'); AmpHistory.checkUrl(); t.strictEqual(router.z, '123'); }); test("#2062 - Trigger 'route' event on router instance.", 2, function (t) { restartHistoryWithoutPushState(); router.on('route', function (name, args) { t.strictEqual(name, 'routeEvent'); t.deepEqual(args, ['x']); }); location.replace('http://example.com#route-event/x'); AmpHistory.checkUrl(); }); test("#2255 - Extend routes by making routes a function.", 1, function (t) { var RouterBase = AmpRouter.extend({ routes: function () { return { home: "root", index: "index.html" }; } }); var RouterExtended = RouterBase.extend({ routes: function () { var _super = RouterExtended.__super__.routes; return extend(_super(), { show: 'show', search: 'search' }); } }); var router = new RouterExtended(); t.deepEqual({home: "root", index: "index.html", show: "show", search: "search"}, router.routes); }); test("#2538 - hashChange to pushState only if both requested.", 1, function (t) { AmpHistory.stop(); location.replace('http://example.com/root?a=b#x/y'); AmpHistory = extend(new AmpHistoryConstructor(), { location: location, history: { pushState: function () {}, replaceState: function () { t.ok(false); } } }); AmpHistory.start({root: 'root', hashChange: false}); t.ok(true); }); test('No hash fallback.', 1, function (t) { AmpHistory.stop(); AmpHistory = extend(new AmpHistoryConstructor(), { location: location, history: { pushState: function () {}, replaceState: function () {} } }); var Router = AmpRouter.extend({ routes: { hash: function () { t.ok(false); } } }); var router = new Router({ history: AmpHistory }); location.replace('http://example.com/'); AmpHistory.start({hashChange: false}); location.replace('http://example.com/nomatch#hash'); AmpHistory.checkUrl(); t.ok(true); }); test('#2656 - No trailing slash on root.', 1, function (t) { AmpHistory.stop(); AmpHistory = extend(new AmpHistoryConstructor(), { location: location, history: { pushState: function (state, title, url) { t.strictEqual(url, '/root'); } } }); location.replace('http://example.com/root/path'); AmpHistory.start({hashChange: false, root: 'root'}); AmpHistory.navigate(''); }); test('#2656 - No trailing slash on root.', 1, function (t) { AmpHistory.stop(); AmpHistory = extend(new AmpHistoryConstructor(), { location: location, history: { pushState: function (state, title, url) { t.strictEqual(url, '/'); } } }); location.replace('http://example.com/path'); AmpHistory.start({hashChange: false}); AmpHistory.navigate(''); }); test('#2765 - Fragment matching sans query/hash.', 2, function (t) { AmpHistory.stop(); AmpHistory = extend(new AmpHistoryConstructor(), { location: location, history: { pushState: function (state, title, url) { t.strictEqual(url, '/path?query#hash'); } } }); var Router = AmpRouter.extend({ routes: { path: function () { t.ok(true); } } }); var router = new Router({ history: AmpHistory }); location.replace('http://example.com/'); AmpHistory.start({hashChange: false}); AmpHistory.navigate('path?query#hash', true); }); test('Do not decode the search params.', 1, function (t) { var Router = AmpRouter.extend({ routes: { path: function (params) { t.strictEqual(params, 'x=y z'); } } }); var router = new Router({ history: AmpHistory }); AmpHistory.navigate('path?x=y%20z', true); }); test('Navigate to a hash url.', 1, function (t) { AmpHistory.stop(); AmpHistory = extend(new AmpHistoryConstructor(), {location: location}); AmpHistory.start(); var Router = AmpRouter.extend({ routes: { path: function (params) { t.strictEqual(params, 'x=y'); } } }); var router = new Router({ history: AmpHistory }); location.replace('http://example.com/path?x=y#hash'); AmpHistory.checkUrl(); }); test('#navigate to a hash url.', 1, function (t) { AmpHistory.stop(); AmpHistory = extend(new AmpHistoryConstructor(), {location: location}); AmpHistory.start(); var Router = AmpRouter.extend({ routes: { path: function (params) { t.strictEqual(params, 'x=y'); } } }); var router = new Router({ history: AmpHistory }); AmpHistory.navigate('path?x=y#hash', true); }); test('unicode pathname', 1, function (t) { location.replace('http://example.com/myyjä'); AmpHistory.stop(); AmpHistory = extend(new AmpHistoryConstructor(), {location: location}); var Router = AmpRouter.extend({ routes: { 'myyjä': function () { t.ok(true); } } }); var router = new Router({ history: AmpHistory }); AmpHistory.start(); }); test('newline in route', 1, function (t) { location.replace('http://example.com/stuff%0Anonsense?param=foo%0Abar'); AmpHistory.stop(); AmpHistory = extend(new AmpHistoryConstructor(), {location: location}); var Router = AmpRouter.extend({ routes: { 'stuff\nnonsense': function () { t.ok(true); } } }); new Router({ history: AmpHistory }); AmpHistory.start(); }); test('Router#execute receives callback, args, name.', 3, function (t) { location.replace('http://example.com#foo/123/bar?x=y'); AmpHistory.stop(); AmpHistory = extend(new AmpHistoryConstructor(), {location: location}); var Router = AmpRouter.extend({ routes: {'foo/:id/bar': 'foo'}, foo: function () {}, execute: function (callback, args, name) { t.strictEqual(callback, this.foo); t.deepEqual(args, ['123', 'x=y']); t.strictEqual(name, 'foo'); } }); var router = new Router({ history: AmpHistory }); AmpHistory.start({pushState: false}); }); test("pushState to hashChange with only search params.", 1, function (t) { AmpHistory.stop(); location.replace('http://example.com?a=b'); location.replace = function (url) { t.strictEqual(url, '/#?a=b'); }; AmpHistory = extend(new AmpHistoryConstructor(), { location: location, history: null }); AmpHistory.start(); }); test("#3123 - History#navigate decodes before comparison.", 1, function (t) { AmpHistory.stop(); location.replace('http://example.com/shop/search?keyword=short%20dress'); AmpHistory = extend(new AmpHistoryConstructor(), { location: location, history: { pushState: function () { t.ok(false); }, replaceState: function () { t.ok(false); } } }); AmpHistory.start(); AmpHistory.navigate('shop/search?keyword=short%20dress', true); t.strictEqual(AmpHistory.fragment, 'shop/search?keyword=short dress'); }); test('#3175 - Urls in the params', 1, function (t) { AmpHistory.stop(); location.replace('http://example.com#login?a=value&backUrl=https%3A%2F%2Fwww.msn.com%2Fidp%2Fidpdemo%3Fspid%3Dspdemo%26target%3Db'); AmpHistory = extend(new AmpHistoryConstructor(), {location: location}); var router = new AmpRouter({ history: AmpHistory }); router.route('login', function (params) { t.strictEqual(params, 'a=value&backUrl=https%3A%2F%2Fwww.msn.com%2Fidp%2Fidpdemo%3Fspid%3Dspdemo%26target%3Db'); }); AmpHistory.start({pushState: false}); }); test("redirectTo", 2, function (t) { AmpHistory.stop(); location.replace('http://example.com/redirect'); AmpHistory = extend(new AmpHistoryConstructor(), {location: location}); var router = new AmpRouter({ history: AmpHistory }); router.route('redirect', function () { t.ok('yup'); this.redirectTo('other'); }); router.route('other', function () { t.pass(); }); AmpHistory.start(); }); test("app can know when the history has started", 2, function (t) { AmpHistory.stop(); var router = new AmpRouter({ history: AmpHistory }); t.notOk(router.history.started()); AmpHistory.start(); t.ok(router.history.started()); }); test("reload", 2, function (t) { AmpHistory.stop(); location.replace('http://example.com/foo'); var Router = AmpRouter.extend({ routes: {'foo': 'foo'}, foo: function () { t.ok('yep');//Should get called twice } }); AmpHistory = extend(new AmpHistoryConstructor(), {location: location}); var router = new Router({ history: AmpHistory }); AmpHistory.start(); router.navigate('foo', {trigger: true}); router.reload(); }); test("router accepts routes through extend or instantiation", 2, function (t) { var router1 = new (AmpRouter.extend({ routes: { 'test': 'test' } }))(); var router2 = new AmpRouter({ routes: { 'test': 'test' } }); t.ok(router1.routes.test, 'test'); t.ok(router2.routes.test, 'test'); }); })();