UNPKG

spa-hash-router

Version:

Simple hash router for Single Page Application (SPA)

849 lines (742 loc) 29.9 kB
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'); }); }); }); }); });