UNPKG

angular-state-router

Version:

An AngularJS state-based router designed for flexibility and ease of use.

919 lines (733 loc) 25.7 kB
'use strict'; describe('$state', function() { beforeEach(angular.mock.module('angular-state-router')); describe('#state', function() { it('Should allow definition of states continually without error', function() { var myState; angular.mock.module(function($stateProvider) { $stateProvider // A state .state('dashboard', { url: '/dashboard' }); myState = $stateProvider.state('dashboard'); }); angular.mock.inject(function($state) { $state // With parameters .state('profile', { url: '/profile?p&j&sd', params: { p: 0, j: 'lorem' } }) // Detail view with required id .state('product', { url: '/product/:id' }) // Index listing and detail view (optional "id") .state('catalog', { url: '/catelog/[:id]' }) // Sub-state without parent state .state('terms.legal', { url: '/legal' }); // Previously defined states exist expect($state.state('dashboard')).toEqual({ name: 'dashboard', url: '/dashboard', inherit: true }); // Cached was reset expect($state.state('dashboard')).not.toBe(myState); }); }); it('Should return defined state heirarchy parameters inherit from parent chain', function() { angular.mock.inject(function($state) { $state // Parent state .state('organism', { url: '/organisms', params: { organic: true } }) // Parent state .state('organism.plant', { url: '/plants', params: { chlorophyll: 'green' } }) // Parent state .state('organism.plant.tree', { url: '/trees', params: { bark: 1 } }) // Child state .state('organism.plant.tree.apple', { url: '/trees/apples', params: { fruit: ['apple'] } }) // Child's child state .state('organism.plant.tree.apple.fuji', { params: { location: 'Japan' } }) // Alienated state .state('organism.plant.tree.hybrid', { url: '/trees/hybrid', params: { organic: true }, inherit: false }); var organism = $state.state('organism'); expect(organism).toBeTruthy(); var plant = $state.state('organism.plant'); expect(plant).toBeTruthy(); var tree = $state.state('organism.plant.tree'); expect(tree).toBeTruthy(); var apple = $state.state('organism.plant.tree.apple'); expect(apple).toBeTruthy(); var fuji = $state.state('organism.plant.tree.apple.fuji'); expect(fuji).toBeTruthy(); var hybrid = $state.state('organism.plant.tree.hybrid'); expect(hybrid).toBeTruthy(); // Nonexisting with parents var nonexisting = $state.state('organism.plant.tree.nonexisting'); expect(nonexisting).toBeTruthy(); // Non-existing without parents var invalid = $state.state('does.not.exist.invalid'); expect(invalid).toBe(null); // Inherit by default expect(organism.url).toBe('/organisms'); expect(plant.url).toBe('/plants'); expect(tree.url).toBe('/trees'); expect(apple.url).toBe('/trees/apples'); expect(fuji.url).toBe('/trees/apples'); // Inherit properties by default expect(fuji.params.location).toBe('Japan'); expect(fuji.params.chlorophyll).toBe('green'); expect(fuji.params.bark).toBe(1); // Do not inherit expect(hybrid.url).toBe('/trees/hybrid'); expect(hybrid.params).toEqual({ organic: true }); }); }); it('Should use cache until next update', function() { angular.mock.inject(function($state) { $state .state('organism.plant.tree', { url: '/trees', params: { bark: 1 } }); var tree1 = $state.state('organism.plant.tree'); expect(tree1).toBeTruthy(); expect(tree1).toBe($state.state('organism.plant.tree')); $state .state('organism.plant.tree', { url: '/trees', params: { bark: 1, replacement: true } }); var tree2 = $state.state('organism.plant.tree'); expect(tree2).not.toBe(tree1); }); }); }); describe('#change', function() { it('Should change state with set parameters', function(done) { angular.mock.module(function($stateProvider) { $stateProvider // Define states .state('companies', { url: '/companies/:company', params: { company: 'XYZ Co' } }) .state('rooms', { url: '/buildings/:building/rooms/:room', params: { building: 'f2', room: 'j203' } }); }); angular.mock.inject(function($state, $rootScope) { $rootScope.$digest(); expect($state.current()).toBeNull(); $state.change('companies', { lorem: 'ipsum' }); $rootScope.$digest(); expect($state.current().params).toEqual({ company: 'XYZ Co', lorem: 'ipsum' }); done(); }); }); it('Should interpret single dot as current state when state does not exist', function(done) { angular.mock.module(function($stateProvider) { $stateProvider // Define states .state('companies', { url: '/companies/:company', params: { company: 'XYZ Co' } }) .state('rooms', { url: '/buildings/:building/rooms/:room', params: { building: 'f2', room: 'j203' } }) .init('companies'); }); angular.mock.inject(function($state, $rootScope) { $rootScope.$digest(); $state.change('.', { lorem: 'sed' }); $rootScope.$digest(); expect($state.current().name).toBe('companies'); expect($state.current().params).toEqual({ company: 'XYZ Co', lorem: 'sed' }); done(); }); }); it('Should dispatch events during state change', function(done) { angular.mock.module(function($stateProvider) { $stateProvider // Define states .state('companies', { url: '/companies/:company', params: { company: 'XYZ Co' } }) .state('rooms', { url: '/buildings/:building/rooms/:room', params: { building: 'f2', room: 'j203' } }); }); angular.mock.inject(function($state, $rootScope) { $rootScope.$digest(); // Spies var onBegin = jasmine.createSpy('onBegin'); var onEnd = jasmine.createSpy('onEnd'); var onError = jasmine.createSpy('onError'); var onComplete = jasmine.createSpy('onComplete'); $rootScope.$on('$stateChangeBegin', onBegin); $rootScope.$on('$stateChangeEnd', onEnd); $rootScope.$on('$stateChangeError', onError); $rootScope.$on('$stateChangeComplete', onComplete); $state.change('companies'); $rootScope.$digest(); expect(onBegin).toHaveBeenCalled(); expect(onEnd).toHaveBeenCalled(); expect(onError).not.toHaveBeenCalled(); expect(onComplete).toHaveBeenCalled(); expect(onComplete.calls.count()).toEqual(1); done(); }); }); it('Should not transition state when next state is the same but should still notify process is complete', function(done) { angular.mock.module(function($stateProvider) { $stateProvider // Define states .state('companies', { url: '/companies/:company', params: { company: 'XYZ Co' } }) .state('rooms', { url: '/buildings/:building/rooms/:room', params: { building: 'f2', room: 'j203' } }); }); angular.mock.inject(function($state, $rootScope) { $rootScope.$digest(); var onBegin = jasmine.createSpy('onBegin'); var onEnd = jasmine.createSpy('onEnd'); var onComplete = jasmine.createSpy('onComplete'); $rootScope.$on('$stateChangeBegin', onBegin); $rootScope.$on('$stateChangeEnd', onEnd); $rootScope.$on('$stateChangeComplete', onComplete); $state.change('companies', { lorem: 'ipsum' }); $rootScope.$digest(); $state.change('companies', { lorem: 'ipsum' }); $rootScope.$digest(); expect($state.current().params).toEqual({ company: 'XYZ Co', lorem: 'ipsum' }); expect(onBegin.calls.count()).toEqual(1); expect(onEnd.calls.count()).toEqual(1); expect(onComplete.calls.count()).toEqual(2); done(); }); }); it('Should emit "$stateChangeErrorNotFound" when requested state does not exist', function(done) { angular.mock.module(function($stateProvider) { $stateProvider // Define states .state('companies', { url: '/companies/:company', params: { company: 'XYZ Co' } }) .state('rooms', { url: '/buildings/:building/rooms/:room', params: { building: 'f2', room: 'j203' } }); }); angular.mock.inject(function($state, $rootScope) { var onBegin = jasmine.createSpy('onBegin'); var onEnd = jasmine.createSpy('onEnd'); var onError = jasmine.createSpy('onError'); var onComplete = jasmine.createSpy('onComplete'); $rootScope.$on('$stateChangeBegin', onBegin); $rootScope.$on('$stateChangeEnd', onEnd); $rootScope.$on('$stateChangeErrorNotFound', onError); $rootScope.$on('$stateChangeComplete', onComplete); $state.change('missingstate'); $rootScope.$digest(); expect($state.current()).toBeNull(); expect(onBegin.calls.count()).toEqual(0); expect(onEnd.calls.count()).toEqual(0); expect(onError.calls.count()).toEqual(1); expect(onComplete.calls.count()).toEqual(1); done(); }); }); it('Should await all promises in resolve property and set as locals', function(done) { angular.mock.module(function($stateProvider) { $stateProvider // Define states .state('companies', { url: '/companies/:company', params: { company: 'XYZ Co' } }) .state('employees', { url: '/employees/:employee', params: { employee: '01321471448-3145-1' }, resolve: { 'slowService': function($timeout, $q) { return $q(function(resolve, reject) { $timeout(function() { resolve('someSpecificValue'); }, 1000); }); } } }) .state('rooms', { url: '/buildings/:building/rooms/:room', params: { building: 'f2', room: 'j203' } }); }); angular.mock.inject(function($state, $rootScope, $timeout) { var onBegin = jasmine.createSpy('onBegin'); var onEnd = jasmine.createSpy('onEnd'); var onError = jasmine.createSpy('onError'); var onComplete = jasmine.createSpy('onComplete'); $rootScope.$on('$stateChangeBegin', onBegin); $rootScope.$on('$stateChangeEnd', onEnd); $rootScope.$on('$stateChangeError', onError); $rootScope.$on('$stateChangeComplete', onComplete); // Initialize $rootScope.$digest(); expect($state.current()).toBe(null); // Transition $state.change('employees'); $rootScope.$digest(); // Resolve everything immediately $timeout.flush(); $rootScope.$digest(); expect($state.current().name).toBe('employees'); expect(onBegin).toHaveBeenCalled(); expect(onEnd).toHaveBeenCalled(); expect(onError).not.toHaveBeenCalled(); expect(onComplete).toHaveBeenCalled(); expect($state.current().locals).toEqual({ slowService: 'someSpecificValue' }); done(); }); }); it('Should broadcast "$stateResolveError" if promise is rejected in resolve property', function(done) { angular.mock.module(function($stateProvider) { $stateProvider // Define states .state('companies', { url: '/companies/:company', params: { company: 'XYZ Co' } }) .state('employees', { url: '/employees/:employee', params: { employee: '01321471448-3145-1' }, resolve: { 'errorService': function($timeout, $q) { return $q(function(resolve, reject) { $timeout(function() { reject(new Error('Looks like we have a problem resolving.')); }, 1000); }); } } }) .state('rooms', { url: '/buildings/:building/rooms/:room', params: { building: 'f2', room: 'j203' } }); }); angular.mock.inject(function($state, $rootScope, $timeout) { $rootScope.$digest(); var onBegin = jasmine.createSpy('onBegin'); var onEnd = jasmine.createSpy('onEnd'); var onError = jasmine.createSpy('onError'); var onErrorResolve = jasmine.createSpy('onErrorResolve'); var onComplete = jasmine.createSpy('onComplete'); $rootScope.$on('$stateChangeBegin', onBegin); $rootScope.$on('$stateChangeEnd', onEnd); $rootScope.$on('$stateChangeError', onError); $rootScope.$on('$stateResolveError', onErrorResolve); $rootScope.$on('$stateChangeComplete', onComplete); // Initialize $rootScope.$digest(); expect($state.current()).toBe(null); // Transition $state.change('employees'); $rootScope.$digest(); // Resolve everything immediately $timeout.flush(); expect($state.current().name).toBe('employees'); // State transition begins expect(onBegin).toHaveBeenCalled(); // But does not finish expect(onEnd).not.toHaveBeenCalled(); // Error response expect(onError).toHaveBeenCalled(); expect(onErrorResolve).toHaveBeenCalled(); // Always called expect(onComplete).toHaveBeenCalled(); expect($state.current().locals).toEqual({ }); done(); }); }); }); describe('#$use', function() { it('Should call middleware during state transition', function(done) { angular.mock.module(function($stateProvider) { $stateProvider // Define states .state('companies', { url: '/companies/:company', params: { company: 'XYZ Co' } }) .state('rooms', { url: '/buildings/:building/rooms/:room', params: { building: 'f2', room: 'j203' } }); }); angular.mock.inject(function($state, $rootScope) { var _testLayer = { onMiddle: function(request, next) { next(); } }; spyOn(_testLayer, 'onMiddle').and.callThrough(); $state.$use(_testLayer.onMiddle); $state.change('companies'); $rootScope.$digest(); expect(_testLayer.onMiddle).toHaveBeenCalled(); done(); }); }); it('Should require middleware to be function', function() { angular.mock.inject(function($state) { expect(function() { $state.$use(null); }).toThrow(new Error('Middleware must be a function.')); }); }); it('Should call middleware according to priority properties with higher first', function(done) { angular.mock.module(function($stateProvider) { $stateProvider // Define states .state('companies', { url: '/companies/:company', params: { company: 'XYZ Co' } }) .state('rooms', { url: '/buildings/:building/rooms/:room', params: { building: 'f2', room: 'j203' } }); }); angular.mock.inject(function($state, $rootScope) { var order = []; var _createMiddlewareLayer = function(priority) { var _middleware; _middleware = { handle: function(data, next) { order.push(priority); next(); } }; spyOn(_middleware, 'handle').and.callThrough(); _middleware.handle.priority = priority; return _middleware.handle; }; var handler1; var handler2; var handler3; $state.$use(handler1 = _createMiddlewareLayer(400)); $state.$use(handler2 = _createMiddlewareLayer(500)); $state.$use(handler3 = _createMiddlewareLayer(10)); $state.change('rooms'); $rootScope.$digest(); expect(handler1).toHaveBeenCalled(); expect(handler2).toHaveBeenCalled(); expect(handler3).toHaveBeenCalled(); expect(order).toEqual([500, 400, 10]); done(); }); }); }); describe('#current', function() { it('Should retrieve copy of current state', function(done) { var companyLobbyState; angular.mock.module(function($stateProvider) { $stateProvider // Define states .state('company.lobby', { url: '/main', params: { a: 11 } }); companyLobbyState = $stateProvider.state('company.lobby'); }); angular.mock.inject(function($state, $rootScope) { $rootScope.$digest(); $state.change('company.lobby'); $rootScope.$digest(); // Not same instance expect($state.current()).not.toBe(companyLobbyState); // Same values expect($state.current().name).toBe('company.lobby'); expect($state.current().url).toBe(companyLobbyState.url); expect($state.current().params).toEqual(companyLobbyState.params); done(); }); }); }); describe('#active', function() { it('Should check for active state using query with state notation', function(done) { angular.mock.module(function($stateProvider) { $stateProvider // Define .state('company.lobby', { url: '/main/atrium' }) .state('company.lobby.personel', { url: '/persons' }); }); angular.mock.inject(function($state, $rootScope) { // Initial condition expect($state.active('company.lobby.personel')).toBe(false); $state.change('company.lobby.personel'); $rootScope.$digest(); expect($state.active('company.lobby.personel')).toBe(true); // Parent expect($state.active('company.lobby.personel')).toBe(true); expect($state.active('company.lobby')).toBe(true); expect($state.active('company')).toBe(true); // RegExp expect($state.active(/.*/)).toBe(true); expect($state.active('/.*/')).toBe(true); // Wildcards expect($state.active('company.*.personel')).toBe(true); expect($state.active('company.*.*')).toBe(true); expect($state.active('*.lobby')).toBe(true); expect($state.active('*.lobby.*')).toBe(true); expect($state.active('*.lobby.*.doesnotexist')).toBe(false); expect($state.active('*.lobby.doesnotexist.*')).toBe(false); expect($state.active('doesnotexist.*.lobby.*')).toBe(false); // Double wildcards expect($state.active('company.**')).toBe(true); expect($state.active('company.lobby.**')).toBe(true); expect($state.active('company.**.personel')).toBe(true); expect($state.active('company.**.doesnotexist')).toBe(false); expect($state.active('doesnotexist.**.lobby.*')).toBe(false); // Invalid expect($state.active('doesnotexist')).toBeFalsy(); // Validate expect($state.current().name).toBe('company.lobby.personel'); done(); }); }); }); describe('#library', function() { it('Should get defined states', function() { angular.mock.inject(function($state) { $state .state('students', { url: '/students/:id', params: { homeroom: 'Room 52' }, inherit: false }) .state('teachers', { url: '/teachers/:id', params: { payroll: 'B Stat' } }) .state('classrooms', { url: '/classrooms/:id' }); expect($state.library()).toEqual({ 'students': { name: 'students', url: '/students/:id', params: { homeroom: 'Room 52' }, inherit: false }, 'teachers': { name: 'teachers', url: '/teachers/:id', params: { payroll: 'B Stat' }, inherit: true }, 'classrooms': { name: 'classrooms', url: '/classrooms/:id', inherit: true } }); }); }); }); describe('#validate.name', function() { it('Should test for valid state names', function() { angular.mock.inject(function($state) { expect($state.validate.name('lorem.ipsum.dolor.sed.ut')).toBe(true); expect($state.validate.name('lorem.ipsum')).toBe(true); expect($state.validate.name('lorem')).toBe(true); expect($state.validate.name('lorem.0')).toBe(true); expect($state.validate.name('Lorem.0')).toBe(true); expect($state.validate.name('DOLOR.dolor')).toBe(true); }); }); it('Should test for invalid state names', function() { angular.mock.inject(function($state) { expect($state.validate.name('lorem..sed.ut')).toBe(false); expect($state.validate.name('lorem.*.dolor.sed.ut')).toBe(false); expect($state.validate.name('lorem.**.dolor.sed.ut')).toBe(false); expect($state.validate.name('.lorem.dolor.ut')).toBe(false); expect($state.validate.name('lorem.dolor.ut.')).toBe(false); expect($state.validate.name('lorem..dolor.sed.ut')).toBe(false); }); }); }); describe('#validate.query', function() { it('Should test for valid state queries', function() { angular.mock.inject(function($state) { expect($state.validate.query('lorem.ipsum.dolor.sed.ut')).toBe(true); expect($state.validate.query('lorem.ipsum')).toBe(true); expect($state.validate.query('lorem')).toBe(true); expect($state.validate.query('lorem.0')).toBe(true); expect($state.validate.query('Lorem.0')).toBe(true); expect($state.validate.query('DOLOR.dolor')).toBe(true); expect($state.validate.query('lorem.*.dolor.sed.ut')).toBe(true); expect($state.validate.query('lorem.**.dolor.sed.ut')).toBe(true); }); }); it('Should test for invalid state queries', function() { angular.mock.inject(function($state) { expect($state.validate.query('lorem..sed.ut')).toBe(false); expect($state.validate.query('.lorem.dolor.ut')).toBe(false); expect($state.validate.query('lorem.dolor.ut.')).toBe(false); expect($state.validate.query('lorem..dolor.sed.ut')).toBe(false); }); }); }); describe('#parse', function() { it('Should parse name and params from name-params string', function() { angular.mock.inject(function($state) { expect($state.parse("lorem.sed.ut({id:'lorem', solution:2.7329e-29})")).toEqual({ name:'lorem.sed.ut', params:{id:'lorem', solution:2.7329e-29}}); }); }); it('Should accept spacing around parameters', function() { angular.mock.inject(function($state) { expect($state.parse("lorem.sed.ut( {id:'lorem', solution:2.7329e-29} )")).toEqual({ name:'lorem.sed.ut', params:{id:'lorem', solution:2.7329e-29}}); }); }); }); });