spa-hash-router
Version:
Simple hash router for Single Page Application (SPA)
849 lines (742 loc) • 29.9 kB
JavaScript
suite('#Router', function() {
var router;
suite('Constructor', function() {
setup(function(){
HashRouter.instance = null;
});
test('witout "new" operator', function() {
assert.throw(function() {
HashRouter();
}, Error, '"new" operator is required');
});
[
{
prop: 'prefix',
incorrectInputs: [undefined, null, true, 1, [], {}, function(){}],
expected: '',
correctInputs: ['1', 'someValue']
},
{
prop: 'fallbackRoute',
incorrectInputs: [undefined, null, true, 1, [], {}, function(){}],
expected: '',
correctInputs: ['1', 'someValue']
},
{
prop: 'before',
incorrectInputs: [undefined, null, true, 1, 'str', [], {}],
expected: HashRouter.prototype._before,
correctInputs: [function(){}]
},
{
prop: 'after',
incorrectInputs: [undefined, null, true, 1, 'str', [], {}],
expected: HashRouter.prototype._after,
correctInputs: [function(){}]
}
].forEach(function(testCase) {
testCase.incorrectInputs.forEach(function(input) {
test('incorrect type of ' + testCase.prop + ' = ' + input, function() {
var options = {};
options[testCase.prop] = input;
var router;
assert.doesNotThrow(function() {
router = new HashRouter(options);
});
assert.equal(router['_' + testCase.prop], testCase.expected, '"_' + testCase.prop + '" property should not change its value by incorrect option type');
});
});
testCase.correctInputs.forEach(function(input) {
test('correct type of ' + testCase.prop + ' = ' + input, function() {
var options = {};
options[testCase.prop] = input;
var router;
assert.doesNotThrow(function() {
router = new HashRouter(options);
});
assert.equal(router['_' + testCase.prop], input, '"_' + testCase.prop + '" property was not set');
});
});
});
[undefined, null, false, 0, {}, [], function(){}].forEach(function(testCase) {
test('default values for "prefix" and "fallbackRoute", when input is as ' + Object.prototype.toString.call(testCase), function(){
var options = {};
options.prefix = testCase;
options.fallbackRoute = testCase;
var ref;
assert.doesNotThrow(function() {
ref = new HashRouter(options);
});
assert.equal(ref._prefix, '', '"_prefix" was incorrectly set by default');
assert.equal(ref._fallbackRoute, '', '"_fallbackRoute" was incorrectly set by default');
});
});
suite('adding of routes', function(){
setup(function(){
sinon.stub(HashRouter.prototype, 'add');
sinon.stub(HashRouter.prototype, 'addContextualMap');
});
teardown(function(){
HashRouter.prototype.add.restore();
HashRouter.prototype.addContextualMap.restore();
});
[undefined, null, true, 1, 'str', [], function(){}].forEach(function(input) {
test('incorrect type of routes = ' + input, function() {
var router;
assert.doesNotThrow(function() {
router = new HashRouter({routes: input});
});
assert.isFalse(HashRouter.prototype.addContextualMap.called, 'addContextualMap() should not be called for routes with incorrect type');
assert.isFalse(HashRouter.prototype.add.called, 'add() should not be called for routes with incorrect type');
});
});
test('correct type of routes = {}', function() {
var routes = {};
var router;
assert.doesNotThrow(function() {
router = new HashRouter({routes: routes});
});
assert.isFalse(HashRouter.prototype.addContextualMap.called, 'addContextualMap() should not be called for routes with incorrect type');
assert.isTrue(HashRouter.prototype.add.called, 'add() should be called when type of routes is object');
assert.equal(HashRouter.prototype.add.args[0][0], routes, 'incorrect first argument was passed into add()');
});
[undefined, null, true, 1, 'str', [], function(){}].forEach(function(input) {
test('incorrect type of map: ' + input, function() {
var router;
assert.doesNotThrow(function() {
router = new HashRouter({
map: input,
context: {}
});
});
assert.isFalse(HashRouter.prototype.add.called, 'add() should not be called for routes with incorrect type');
assert.isFalse(HashRouter.prototype.addContextualMap.called, 'addContextualMap() should not be called for routes with incorrect type');
});
test('incorrect type of context: ' + input, function() {
var router;
assert.doesNotThrow(function() {
router = new HashRouter({
map: {},
context: input
});
});
assert.isFalse(HashRouter.prototype.add.called, 'add() should not be called for routes with incorrect type');
assert.isFalse(HashRouter.prototype.addContextualMap.called, 'addContextualMap() should not be called for routes with incorrect type');
});
});
test('correct type of map and context: {}', function() {
var map = {};
var context = {};
var router;
assert.doesNotThrow(function() {
router = new HashRouter({
map: map,
context: context
});
});
assert.isFalse(HashRouter.prototype.add.called, 'add() should not be called for routes with incorrect type');
assert.isTrue(HashRouter.prototype.addContextualMap.called, 'addContextualMap() should be called when type of routes is object');
assert.equal(HashRouter.prototype.addContextualMap.args[0][0], map, 'incorrect first argument was passed into addContextualMap()');
assert.equal(HashRouter.prototype.addContextualMap.args[0][1], context, 'incorrect second argument was passed into addContextualMap()');
});
});
test('is singleton', function() {
assert.doesNotThrow(function(){
assert.equal(new HashRouter(), new HashRouter(), 'HashRouter should be a singleton in order to have the only one place where all routes and handlers are stored');
});
});
});
suite('Methods', function() {
setup(function() {
router = new HashRouter();
});
teardown(function() {
HashRouter.instance = null;
});
suite('add', function() {
setup(function(){
sinon.spy(router, 'add');
});
teardown(function(){
router.add.restore();
});
test('incorrect amount of input arguments', function(){
[[], [1,1,1]].forEach(function(testCase){
assert.throw(function() {
router.add.apply(router, testCase);
}, Error, 'Incorrect amount of input arguments. Expected 1 or 2, but got ' + testCase.length);
});
});
test('incorrect type of "route" when one input argument is used', function(){
[undefined, null, true, 1, '', [], function(){}].forEach(function(testCase) {
assert.throw(function() {
router.add(testCase);
}, Error, 'Incorrect type of the single argument. Expected "Object", but got ' + Object.prototype.toString.call(testCase));
});
});
test('incorrect type of "route" when two input arguments are used', function(){
[undefined, null, true, 1, [], {}, function(){}].forEach(function(testCase) {
assert.throw(function() {
router.add(testCase, []);
}, Error, 'Incorrect type of "route". Expected "String", but got ' + Object.prototype.toString.call(testCase));
});
});
test('incorrect type of "methods"', function(){
[undefined, null, true, 1, {}].forEach(function(testCase) {
assert.throw(function() {
router.add('', testCase);
}, Error, 'Incorrect type of "methods". Expected "Array" or "Function", but got ' + Object.prototype.toString.call(testCase));
});
});
test('map structure', function(){
var fn = function(){};
assert.doesNotThrow(function() {
router.add('r', fn);
});
assert.property(router._routingMap, 'r', 'Route map object for route "r" was incorrectly created');
assert.isObject(router._routingMap.r, 'A route should be an Object');
assert.instanceOf(router._routingMap.r.regExp, RegExp, '"regExp" route property should be a RegExp');
assert.isArray(router._routingMap.r.methods, '"methods" route property should be an Array');
assert.equal(router._routingMap.r.methods[0], fn, 'Incorrect context was set');
});
test('add routes and handlers', function(){
var routes = ['route1', 'route2'];
var methods1 = [function(){}, function(){}];
var methods2 = [function(){}, function(){}];
assert.doesNotThrow(function() {
router.add(routes[0], methods1[0]);
router.add(routes[1], methods2);
router.add(routes[0], methods1[1]);
});
assert.property(router._routingMap, routes[0], 'Route map object for route the first added route was incorrectly created');
assert.equal(router._routingMap[routes[0]].methods[0], methods1[0], 'First handler contains incorrect method');
assert.equal(router._routingMap[routes[0]].methods[1], methods1[1], 'Second handler contains incorrect method');
assert.property(router._routingMap, routes[1], 'Route map object for route the second added route was incorrectly created');
assert.equal(router._routingMap[routes[1]].methods[0], methods2[0], 'First handler contains incorrect method');
assert.equal(router._routingMap[routes[1]].methods[1], methods2[1], 'Second handler contains incorrect method');
});
test('unwrapping of input object', function(){
var obj = {
route: [function(){}]
};
assert.doesNotThrow(function(){
router.add(obj);
});
assert.isTrue(router.add.calledTwice, 'add() should be called twice: 1 - with object input argument; 2 - with unwrapped object');
assert.equal(router.add.args[1][0], 'route', 'route name was incorrectly unwrapped');
assert.equal(router.add.args[1][1], obj.route, 'array of methods was incorrectly unwrapped');
});
});
suite('addContextualMap', function(){
setup(function(){
sinon.stub(router, 'add');
});
teardown(function(){
router.add.restore();
});
test('incorrect amount of input arguments', function(){
[[], [1], [1,1,1]].forEach(function(testCase){
assert.throw(function() {
router.addContextualMap.apply(router, testCase);
}, Error, 'Incorrect amount of input arguments. Expected 2, but got ' + testCase.length);
});
});
test('incorrect type of "routingMap"', function(){
[undefined, null, true, 1, '', [], function(){}].forEach(function(testCase) {
assert.throw(function() {
router.addContextualMap(testCase, {});
}, Error, 'Incorrect type of "routingMap". Expected "Object", but got ' + Object.prototype.toString.call(testCase));
});
});
test('calling of "add" method', function(){
var obj = {
route1: 'm1',
route2: ['m2', 'm3', 'm1']
};
var context = {
m1: function(){},
m2: function(){}
}
assert.doesNotThrow(function() {
router.addContextualMap(obj, context);
});
assert.isTrue(router.add.calledOnce, '"add" should be called once with the new map');
var arg = router.add.args[0][0];
assert.isObject(arg, 'incorrect type of input argument was passind into add()');
assert.property(arg, 'route1', 'property "route1" of input object was not transfered into object for add()');
assert.isArray(arg.route1, 'simple method name should be converted into an array');
assert.equal(arg.route1.length, 1, 'inorrect amount of methods was converted for "route1"');
assert.isFunction(arg.route1[0], 'method name wshould be converted into function');
assert.property(arg, 'route2', 'property "route2" of input object was not transfered into object for add()');
assert.isArray(arg.route2, 'array of method names should stay as array');
assert.equal(arg.route2.length, 2, 'inorrect amount of methods was converted for "route1"');
});
});
suite('start', function(){
setup(function(){
sinon.stub(router, '_hashChange');
sinon.stub(router, '_getHash');
sinon.stub(router, '_getQuery');
});
teardown(function(){
router._hashChange.restore();
router._getHash.restore();
router._getQuery.restore();
window.removeEventListener('hashchange', router._evfn);
});
test('without start url', function(){
assert.doesNotThrow(function(){
router.start();
});
assert.isFalse(router._hashChange.called, '_hashChange should not be called');
assert.isFalse(router._getHash.called, '_getHash should not be called');
assert.isFalse(router._getQuery.called, '_getQuery should not be called');
});
test('with start url', function(){
var url = 'http://localhost:8080/index.html';
assert.doesNotThrow(function(){
router.start(url);
});
assert.isTrue(router._hashChange.calledOnce, '_hashChange should be called once');
assert.isTrue(router._getHash.calledOnce, '_getHash should be called once');
assert.isTrue(router._getQuery.calledOnce, '_getQuery should be called once');
assert.equal(url, router._getHash.args[0][0], 'The full unchanged url should be passed in _getHash method');
assert.equal(url, router._getQuery.args[0][0], 'The full unchanged url should be passed in _getQuery method');
});
});
suite('onHashChange', function(){
setup(function(){
sinon.stub(router, '_hashChange');
sinon.stub(router, '_getHash');
sinon.stub(router, '_getQuery');
});
teardown(function(){
router._hashChange.restore();
router._getHash.restore();
router._getQuery.restore();
});
test('check called methods and input arguments', function(){
var url = 'http://localhost:8080/index.html#hash';
assert.doesNotThrow(function(){
router.onHashChange({
newURL: url
});
});
assert.isTrue(router._hashChange.calledOnce, '_hashChange should be called once');
assert.isTrue(router._getHash.calledOnce, '_getHash should be called once');
assert.isTrue(router._getQuery.calledOnce, '_getQuery should be called once');
assert.equal(url, router._getHash.args[0][0], 'The full unchanged url should be passed in _getHash method');
assert.equal(url, router._getQuery.args[0][0], 'The full unchanged url should be passed in _getQuery method');
});
});
suite('routeTo', function(){
setup(function(){
sinon.spy(router, 'object2Query');
});
teardown(function(){
router.object2Query.restore();
location.hash = '';
});
test('without queries', function(){
var hash='route';
assert.doesNotThrow(function(){
router.routeTo(hash);
});
assert.isFalse(router.object2Query.called, 'When object for queries is not specified, object2Query should not be called');
assert.equal('#'+hash, location.hash, 'Browser\'s hash should be changed');
});
test('with queries', function(){
var hash='route';
assert.doesNotThrow(function(){
router.routeTo(hash, {key: 'value'});
});
assert.isTrue(router.object2Query.calledOnce, 'object2Query should not be called');
assert.equal('#'+hash+'?key=value', location.hash, 'Browser\'s hash should be changed');
});
});
suite('back', function(){
setup(function(){
router._history = [0,1,2];
sinon.stub(window.history, 'back');
sinon.stub(router, 'routeTo');
});
teardown(function(){
window.history.back.restore();
router.routeTo.restore();
});
test('no fallback route', function(){
assert.doesNotThrow(function(){
router.back();
router.back();
router.back();
});
assert.isTrue(window.history.back.calledThrice, 'Native back() should be called without limits');
});
test('with fallback route', function(){
router._fallbackRoute = 'home';
assert.doesNotThrow(function(){
router.back();
router.back();
router.back();
});
assert.isTrue(window.history.back.calledTwice, 'Class of native back() should be limited when the fallbackRoute is defined');
assert.isTrue(router.routeTo.calledOnce, 'Router should route to some route');
assert.equal(router._fallbackRoute, router.routeTo.args[0][0], 'Incorrect route is used for fallback routing');
});
});
suite('_getHash', function(){
[
{
url: 'http://localhost:8080/index.html',
hash: '#'
},
{
url: 'http://localhost:8080/index.html#route',
hash: '#route'
},
{
url: 'http://localhost:8080/index.html#route?key=value',
hash: '#route'
},
{
url: 'http://localhost:8080/index.html?key=value#route',
hash: '#route'
},
{
url: 'http://localhost:8080/index.html#route/path?key=value',
hash: '#route/path'
}
].forEach(function(testCase) {
test('url: ' + testCase.url, function() {
var hash;
assert.doesNotThrow(function(){
hash = router._getHash(testCase.url);
});
assert.equal(testCase.hash, hash, 'Hash was incorrectly extracted');
});
});
});
suite('_getQuery', function(){
[
{
url: 'http://localhost:8080/index.html'
},
{
url: 'http://localhost:8080/index.html?key=value',
query: '?key=value'
},
{
url: 'http://localhost:8080/index.html#route?key=value',
query: '?key=value'
},
{
url: 'http://localhost:8080/index.html?key=value#route',
query: '?key=value'
},
{
url: 'http://localhost:8080/index.html?key=value#route?key2=value2',
query: '?key=value;key2=value2'
},
{
url: 'http://localhost:8080/index.html?key=value#route?key2=value2&key3=value3',
query: '?key=value;key2=value2&key3=value3'
},
{
url: 'http://localhost:8080/index.html?key=value#route?key2=value2;key3=value3',
query: '?key=value;key2=value2;key3=value3'
},
{
url: 'http://localhost:8080/index.html?&key=value',
query: '?&key=value'
},
{
url: 'http://localhost:8080/index.html?;key=value',
query: '?;key=value'
},
{
url: 'http://localhost:8080/index.html?key=value&',
query: '?key=value&'
},
{
url: 'http://localhost:8080/index.html?key=value;',
query: '?key=value;'
},
{
url: 'http://localhost:8080/index.html?key=value&key2=value2',
query: '?key=value&key2=value2'
},
{
url: 'http://localhost:8080/index.html?key=value;key2=value2',
query: '?key=value;key2=value2'
}
].forEach(function(testCase) {
test('url: ' + testCase.url, function() {
var query;
assert.doesNotThrow(function(){
query = router._getQuery(testCase.url);
});
assert.equal(testCase.query, query, 'Query was incorrectly extracted');
});
});
});
suite('_preprocessRouteName', function() {
['', '!', 'PRFX'].forEach(function(prefix) {
var rePrefix = prefix ? '(?:'+prefix+')?' : '';
suite('prefix: "' + prefix + '"', function(){
test('no convertion required', function() {
assert.equal('^#' + rePrefix + 'route/?$', router._preprocessRouteName('route', prefix), 'Route without required convertion was incorrectly converted');
assert.equal('^#' + rePrefix + 'route/path/?$', router._preprocessRouteName('route/path', prefix), 'Route with two constants was incorrectly converted');
});
test('convert simple human readeble route variable', function() {
assert.equal('^#' + rePrefix + 'route/([\\w\\-\\.]+/?)/?$', router._preprocessRouteName('route/:path', prefix), 'Simple human readeble route variable was incorrectly converted');
assert.equal('^#' + rePrefix + 'route/([\\w\\-\\.]+/?)/([\\w\\-\\.]+/?)/?$', router._preprocessRouteName('route/:path/:path-two', prefix), 'Converting of two human readeble route variables is incorrect');
});
test('convert simple RegExp-like route variable', function() {
assert.equal('^#' + rePrefix + 'route/(\\w+)/?$', router._preprocessRouteName('route/(\\w+)', prefix), 'Simple RegExp-like route was incorrectly converted');
});
});
});
});
suite('query2Object', function() {
[
{
query: undefined,
expected: {}
},
{
query: '?',
expected: {}
},
{
query: 'key=value',
expected: {}
},
{
query: '?key=value',
expected: {'key':'value'}
},
{
query: '?key=value&key2=value2',
expected: {'key':'value','key2':'value2'}
},
{
query: '?key=value;key2=value2',
expected: {'key':'value','key2':'value2'}
},
{
query: '?key=&key2=value2',
expected: {'key':'','key2':'value2'}
},
{
query: '?key=;key2=value2',
expected: {'key':'','key2':'value2'}
},
{
query: '?key=value&',
expected: {'key':'value'}
},
{
query: '?key=value;',
expected: {'key':'value'}
},
{
query: '?&key=value',
expected: {'key':'value'}
},
{
query: '?;key=value',
expected: {'key':'value'}
},
{
query: '?key=value;key2=value2&key3=value3',
expected: {'key':'value', 'key2': 'value2', 'key3': 'value3'}
}
].forEach(function(testCase) {
test('query: ' + testCase.query, function() {
var result;
assert.doesNotThrow(function(){
result = router.query2Object(testCase.query);
});
assert.deepEqual(testCase.expected, result, 'Query was incorrectly converted into the object');
});
});
});
suite('object2Query', function() {
test('{}', function() {
assert.deepEqual('', router.object2Query({}), 'Empty object was incorrectly converted');
});
test('{"key":"value"}', function() {
assert.deepEqual('?key=value', router.object2Query({"key":"value"}), 'Simple with obe key-value pair was incorrectly converted');
});
test('{"key":"value","key2":"value2"}', function() {
assert.deepEqual('?key=value;key2=value2', router.object2Query({"key":"value","key2":"value2"}), 'Object with two key-value pairs were incorrectly converted');
});
test('{"key":"","key2":"value2"}', function() {
assert.deepEqual('?key2=value2', router.object2Query({"key":"","key2":"value2"}), 'Object with two key-value pairs where the key of one pair doesn\'t have a value were incorrectly converted');
});
});
suite('_hashChange', function(){
var fn1, fn2;
setup(function(){
fn1 = sinon.spy();
fn2 = sinon.spy();
router._history = [];
router._routingMap = {};
router.addContextualMap({
'route1': [],
'route2': ['999'],
'route3': ['fn1'],
'route4': ['fn1', 'fn2'],
'route5/:data': ['fn1'],
'route6/:data/:id': ['fn1'],
'route7/\\w+': ['fn1']
},{
fn1: fn1,
fn2: fn2
});
sinon.spy(router, '_before');
sinon.spy(router, '_after');
sinon.spy(router, 'routeTo');
});
teardown(function(){
router._history = [];
router._before.restore();
router._after.restore();
router.routeTo.restore();
});
suite('local history', function(){
test('new route should not be added by navigation forward to unhandled route', function(){
assert.doesNotThrow(function(){
router._hashChange('#route1');
});
assert.equal(router._history.length, 0);
});
test('navigate forward', function(){
assert.doesNotThrow(function(){
router._hashChange('#route3');
});
assert.equal(1, router._history.length, 'New route was not added');
});
test('navigate twice forward', function(){
assert.doesNotThrow(function(){
router._hashChange('#route3');
router._hashChange('#route4');
});
assert.equal(2, router._history.length, 'New route was not added');
});
test('navigate twice forward and once backward', function(){
assert.doesNotThrow(function(){
router._hashChange('#route3');
router._hashChange('#route4');
router._hashChange('#route3');
});
assert.equal(1, router._history.length, 'Last route was not removed from history');
});
});
suite('detect route and call assigned methods', function(){
test('navigating to not existing route right at the start should navigate to the fallback route', function(){
router._fallbackRoute = 'fallback';
router._hashChange('#123');
assert.isTrue(router.routeTo.calledOnce, 'rerouting should be explicitly called');
assert.equal(router.routeTo.args[0][0], router._fallbackRoute, 'user should be rerouted to the fallback route');
assert.isFalse(fn1.called, 'fn1 should not be called');
assert.isFalse(fn2.called, 'fn2 should not be called');
assert.isFalse(router._before.called, '_before should not be called');
assert.isFalse(router._after.called, '_after should not be called');
});
test('navigating to not existing route after some successful redirection should not call any route handlers, event fallback route', function(){
router._hashChange('#route3');
assert.isTrue(fn1.calledOnce, 'fn1 should not be called');
assert.isFalse(fn2.called, 'fn2 should not be called');
assert.isTrue(router._before.calledOnce, '_before should be called for existing route handler');
assert.isTrue(router._after.calledOnce, '_after should be called for existing route handler');
assert.isFalse(router.routeTo.called, 'no additional rerouting should happen here');
router._hashChange('#123');
assert.isTrue(fn1.calledOnce, 'fn1 should not be called once, because the first existing route wos processed');
assert.isFalse(fn2.called, 'fn2 should not be called');
assert.isTrue(router._before.calledOnce, '_before should be called for existing route handler');
assert.isTrue(router._after.calledOnce, '_after should be called for existing route handler');
assert.isFalse(router.routeTo.called, 'no additional rerouting should happen here to not move user out of the current route and give the user posibility to fix his interaction');
});
test('navigating to "route1" but without registered methods', function(){
assert.doesNotThrow(function(){
router._hashChange('#route1');
});
assert.isFalse(fn1.called, 'fn1 should not be called');
assert.isFalse(fn2.called, 'fn2 should not be called');
assert.isFalse(router._before.called, '_before should not be called');
assert.isFalse(router._after.called, '_after should not be called');
});
test('navigating to "route2" with link to not existing method', function(){
assert.doesNotThrow(function(){
router._hashChange('#route2');
});
assert.isFalse(fn1.called, 'fn1 should not be called');
assert.isFalse(fn2.called, 'fn2 should not be called');
assert.isFalse(router._before.called, '_before should not be called');
assert.isFalse(router._after.called, '_after should not be called');
});
test('navigating to "route3" with one registered existing method', function(){
assert.doesNotThrow(function(){
router._hashChange('#route3');
});
assert.isTrue(fn1.calledOnce, 'fn1 should be called');
assert.isFalse(fn2.called, 'fn2 should not be called');
assert.isTrue(router._before.calledOnce, 'before should be called');
assert.isTrue(router._after.calledOnce, 'after should be called');
});
test('navigating to "route4" with two registered existing methods', function(){
assert.doesNotThrow(function(){
router._hashChange('#route4');
});
assert.isTrue(fn1.calledOnce, 'fn1 should be called');
assert.isTrue(fn2.calledOnce, 'fn2 should be called');
assert.isTrue(router._before.calledOnce, 'before should be called');
assert.isTrue(router._after.calledOnce, 'after should be called');
});
test('call method with single path variable', function(){
var v = 'value';
assert.doesNotThrow(function(){
router._hashChange('#route5/'+v);
});
assert.isTrue(fn1.calledOnce, 'fn1 should be called');
assert.equal(2, fn1.args[0].length, 'Incorrect amount of arguments was passed into the function');
assert.equal(v, fn1.args[0][0], 'incorrect first argument was passed into the function');
assert.deepEqual({}, fn1.args[0][1], 'incorrect second argument was passed into the function');
});
test('call method with two path variable', function(){
var v1 = 'value1';
var v2 = 'value2';
assert.doesNotThrow(function(){
router._hashChange('#route6/'+v1 + '/' + v2);
});
assert.isTrue(fn1.calledOnce, 'fn1 should be called once');
assert.equal(3, fn1.args[0].length, 'Incorrect amount of arguments was passed into the function');
assert.equal(v1, fn1.args[0][0], 'incorrect first argument was passed into the function');
assert.equal(v2, fn1.args[0][1], 'incorrect second argument was passed into the function');
assert.deepEqual({}, fn1.args[0][2], 'incorrect third argument was passed into the function');
});
test('call method with two path variable and query parameters', function(){
var v1 = 'value1';
var v2 = 'value2';
assert.doesNotThrow(function(){
router._hashChange('#route6/'+v1 + '/' + v2, '?key=value');
});
assert.isTrue(fn1.calledOnce, 'fn1 should be called once');
assert.equal(3, fn1.args[0].length, 'Incorrect amount of arguments was passed into the function');
assert.equal(v1, fn1.args[0][0], 'incorrect first argument passed into the function');
assert.equal(v2, fn1.args[0][1], 'incorrect second argument passed into the function');
assert.deepEqual({key: 'value'}, fn1.args[0][2], 'incorrect third argument was passed into the function');
});
test('route pattern without capturing group', function(){
assert.doesNotThrow(function(){
router._hashChange('#route7/word');
});
assert.isTrue(fn1.calledOnce, 'fn1 should be called once');
assert.equal(1, fn1.args[0].length, 'Incorrect amount of arguments was passed into the function');
assert.deepEqual({}, fn1.args[0][0], 'incorrect first argument was passed into the function');
});
});
});
});
});