├── .bowerrc ├── .gitignore ├── Gruntfile.js ├── README.md ├── app ├── index.html ├── js │ ├── app.js │ ├── routing.js │ └── routing.spec.js └── views │ ├── 404.html │ └── home.html ├── bower.json └── package.json /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "app/bower_components" 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | bower_components/ -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = function (grunt) { 3 | grunt.loadNpmTasks('grunt-contrib-jasmine'); 4 | grunt.loadNpmTasks('grunt-contrib-connect'); 5 | 6 | grunt.initConfig({ 7 | pkg: grunt.file.readJSON('package.json'), 8 | jasmine: { 9 | dev: { 10 | src: ['app/js/**/*.js', '!app/js/**/*.spec.js'], 11 | options: { 12 | vendor: [ 13 | 'app/bower_components/angular/angular.js', 14 | 'app/bower_components/angular-ui-router/release/angular-ui-router.js', 15 | 'app/bower_components/angular-mocks/angular-mocks.js' 16 | ], 17 | specs: 'app/js/**/*.spec.js' 18 | } 19 | } 20 | }, 21 | connect: { 22 | server: { 23 | options: { 24 | base: 'app', 25 | open: true, 26 | useAvailablePort: true 27 | } 28 | } 29 | } 30 | }); 31 | 32 | grunt.registerTask('test', ['jasmine']); 33 | grunt.registerTask('serve', ['connect:server', 'connect:server:keepalive']); 34 | grunt.registerTask('default', ['test']); 35 | }; 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # example-ui-router-testing 2 | 3 | > Example repo showcasing how [testing ui-router](http://nikas.praninskas.com/test-ui-router) might look like 4 | 5 | ## Running it 6 | 7 | * `npm install` 8 | * `npm test` to run tests 9 | * `npm run-script serve` to run a server with the example 10 | 11 | ## Extra 12 | 13 | * Using `sinon` and `bardjs`: [#1](https://github.com/nikaspran/example-ui-router-testing/pull/1) (Thanks [Kos](https://github.com/koshuang)) 14 | 15 | ## License 16 | 17 | MIT 18 | -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Example ui-router test app 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | Current State: 16 |
17 | 18 |
19 | Navigation: 20 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /app/js/app.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | angular.module('example.app', ['example.routing']) 4 | .service('someRepository', function () { 5 | this.getModel = function () { 6 | return ['some item 1', 'some item 2']; 7 | } 8 | }) 9 | .service('otherRepository', function ($timeout) { 10 | this.getModel = function () { 11 | return $timeout(function () { 12 | return ['other item 1', 'other item 2']; 13 | }, 1000); 14 | } 15 | }) 16 | .service('modal', function () { 17 | this.open = function () { 18 | console.log('Modal opening...'); 19 | }; 20 | this.close = function () { 21 | console.log('Modal closing...'); 22 | }; 23 | }) 24 | }()); -------------------------------------------------------------------------------- /app/js/routing.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | angular.module('example.routing', ['ui.router']) 4 | .config(function ($urlRouterProvider, $stateProvider) { 5 | $urlRouterProvider 6 | .when('', '/home') 7 | .when('/', '/home') 8 | .otherwise(function ($injector) { 9 | $injector.get('$state').go('404', {}, {location: false}); 10 | }); 11 | 12 | var modalInstance; 13 | $stateProvider 14 | .state('home', { 15 | url: '/home', 16 | templateUrl: 'views/home.html' 17 | }) 18 | .state('layout', { 19 | abstract: true, 20 | template: '
' 21 | }) 22 | .state('stateWithUrlParams', { 23 | url: '/users/:someParam', 24 | template: 'State with a url parameter: {{someParam}}', 25 | controller: function ($scope, $stateParams) { 26 | $scope.someParam = $stateParams.someParam 27 | } 28 | }) 29 | .state('404', { 30 | templateUrl: 'views/404.html' 31 | }) 32 | .state('modal', { 33 | url: '/modalState', 34 | template: 'Pretend this is a modal...', 35 | onEnter: function (modal) { 36 | modalInstance = modal.open(); 37 | }, 38 | onExit: function () { 39 | modalInstance.close(); 40 | } 41 | }) 42 | .state('onEnterWithResolveDependency', { 43 | url: '/resolveDependency', 44 | template: 'Resolved onEnter dependency', 45 | resolve: { 46 | resolveDep: function ($q) { 47 | return $q.when({ 48 | performAction: function () { 49 | alert('action performed from resolveDep'); 50 | } 51 | }); 52 | } 53 | }, 54 | onEnter: function (resolveDep) { 55 | resolveDep.performAction(); 56 | } 57 | }) 58 | .state('stateWithoutViews', { 59 | url: '/stateWithoutViews', 60 | template: 'State Without Views, model: {{model}}', 61 | controller: function ($scope, someModel) { 62 | $scope.model = someModel; 63 | }, 64 | resolve: { 65 | someModel: function (someRepository) { 66 | return someRepository.getModel(); 67 | } 68 | } 69 | }) 70 | .state('stateWithViews', { 71 | parent: 'layout', 72 | url: '/stateWithViews', 73 | views: { 74 | 'main@layout': { 75 | template: 'State With Views, model: {{model}}', 76 | controller: function ($scope, otherModel) { 77 | $scope.model = otherModel; 78 | }, 79 | resolve: { 80 | otherModel: function (otherRepository) { 81 | return otherRepository.getModel(); 82 | } 83 | } 84 | } 85 | } 86 | }); 87 | }); 88 | }()); 89 | -------------------------------------------------------------------------------- /app/js/routing.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | describe('example.routing', function () { 3 | var $state, $stateParams, $q, $templateCache, $location, $rootScope, $injector, mockSomeRepository, mockOtherRepository, mockModal; 4 | 5 | function mockTemplate(templateRoute, tmpl) { 6 | $templateCache.put(templateRoute, tmpl || templateRoute); 7 | } 8 | 9 | function goFrom(url) { 10 | return { 11 | toState: function (state, params) { 12 | $location.replace().url(url); //Don't actually trigger a reload 13 | $state.go(state, params); 14 | $rootScope.$digest(); 15 | } 16 | }; 17 | } 18 | 19 | function getViewDefinition(state, view) { 20 | return view ? $state.get(state).views[view] : $state.get(state); 21 | } 22 | 23 | function mockResolve(name, mock) { 24 | return { 25 | forStateAndView: function (state, view) { 26 | var viewDefinition = getViewDefinition(state, view); 27 | viewDefinition.resolve[name] = function () { 28 | return mock; 29 | } 30 | } 31 | }; 32 | } 33 | 34 | beforeEach(module('example.routing', function ($provide) { 35 | $provide.value('someRepository', mockSomeRepository = {getModel: jasmine.createSpy('getModel')}); 36 | $provide.value('otherRepository', mockOtherRepository = {getModel: jasmine.createSpy('getModel')}); 37 | $provide.value('modal', mockModal = {open: jasmine.createSpy('modalOpen')}); 38 | })); 39 | beforeEach(inject(function (_$state_, _$stateParams_, _$q_, _$templateCache_, _$location_, _$rootScope_, _$injector_) { 40 | $state = _$state_; 41 | $stateParams = _$stateParams_; 42 | $q = _$q_; 43 | $templateCache = _$templateCache_; 44 | $location = _$location_; 45 | $rootScope = _$rootScope_; 46 | $injector = _$injector_; 47 | })); 48 | 49 | describe('path', function () { 50 | function goTo(url) { 51 | $location.url(url); 52 | $rootScope.$digest(); 53 | } 54 | 55 | describe('when empty', function () { 56 | beforeEach(function () { 57 | mockTemplate('views/home.html'); 58 | }); 59 | 60 | it('should go to the home state', function () { 61 | goTo(''); 62 | expect($state.current.name).toEqual('home'); 63 | }); 64 | }); 65 | 66 | describe('/', function () { 67 | beforeEach(function () { 68 | mockTemplate('views/home.html'); 69 | }); 70 | 71 | it('should go to the home state', function () { 72 | goTo('/'); 73 | expect($state.current.name).toEqual('home'); 74 | }); 75 | }); 76 | 77 | describe('/home', function () { 78 | beforeEach(function () { 79 | mockTemplate('views/home.html'); 80 | }); 81 | 82 | it('should go to the home state', function () { 83 | goTo('/home'); 84 | expect($state.current.name).toEqual('home'); 85 | }); 86 | }); 87 | 88 | describe('/users/:someParam', function () { 89 | it('should go to the stateWithUrlParams state', function () { 90 | goTo('/users/123'); 91 | expect($state.current.name).toEqual('stateWithUrlParams'); 92 | }); 93 | it('should have $stateParams.someParam', function () { 94 | goTo('/users/321'); 95 | expect($stateParams.someParam).toEqual('321'); 96 | }); 97 | }); 98 | 99 | describe('otherwise', function () { 100 | beforeEach(function () { 101 | mockTemplate('views/404.html'); 102 | }); 103 | 104 | it('should go to the 404 state', function () { 105 | goTo('someNonExistentUrl'); 106 | expect($state.current.name).toEqual('404'); 107 | }); 108 | 109 | it('should not change the url', function () { 110 | var badUrl = '/someNonExistentUrl'; 111 | goTo(badUrl); 112 | expect($location.url()).toEqual(badUrl); 113 | }); 114 | }); 115 | }); 116 | 117 | describe('state', function () { 118 | function resolve(value) { 119 | return { 120 | forStateAndView: function (state, view) { 121 | var viewDefinition = getViewDefinition(state, view); 122 | return $injector.invoke(viewDefinition.resolve[value]); 123 | } 124 | }; 125 | } 126 | 127 | beforeEach(function () { 128 | mockTemplate('views/home.html'); // state transition occurs, mock the basic template 129 | }); 130 | 131 | describe('stateWithoutViews', function () { 132 | it('should resolve someModel', function () { 133 | var onResolved = jasmine.createSpy('resolve'); // just a spy of any sort 134 | mockSomeRepository.getModel = function () { // just a mock for the service 135 | return $q.when('something'); 136 | }; 137 | 138 | resolve('someModel').forStateAndView('stateWithoutViews').then(onResolved); 139 | $rootScope.$digest(); 140 | expect(onResolved).toHaveBeenCalledWith('something'); 141 | }); 142 | }); 143 | 144 | describe('stateWithViews', function () { 145 | it('should resolve otherModel', function () { 146 | var onResolved = jasmine.createSpy('resolve'); // just a spy of any sort 147 | mockOtherRepository.getModel = function () { // just a mock for the service 148 | return $q.when('other'); 149 | }; 150 | 151 | resolve('otherModel').forStateAndView('stateWithViews', 'main@layout').then(onResolved); 152 | $rootScope.$digest(); 153 | expect(onResolved).toHaveBeenCalledWith('other'); 154 | }); 155 | }); 156 | }); 157 | 158 | describe('onEnter', function () { 159 | it('should open a modal', function () { 160 | goFrom('/modalState').toState('modal'); 161 | expect(mockModal.open).toHaveBeenCalled(); 162 | }); 163 | 164 | it('should have mockable resolve dependencies', function () { 165 | var mockResolveDep = {performAction: jasmine.createSpy('performAction')}; 166 | mockResolve('resolveDep', mockResolveDep).forStateAndView('onEnterWithResolveDependency'); 167 | goFrom('/resolveDependency').toState('onEnterWithResolveDependency'); 168 | expect(mockResolveDep.performAction).toHaveBeenCalled(); 169 | }); 170 | }); 171 | 172 | describe('onExit', function () { 173 | it('should close the modal', function () { 174 | mockTemplate('views/home.html'); 175 | var modal = {close: jasmine.createSpy('modalClose')}; 176 | mockModal.open = function () { 177 | return modal; 178 | }; 179 | goFrom('/modalState').toState('modal'); 180 | goFrom('/home').toState('home'); 181 | expect(modal.close).toHaveBeenCalled(); 182 | }); 183 | }); 184 | }); 185 | -------------------------------------------------------------------------------- /app/views/404.html: -------------------------------------------------------------------------------- 1 | 404 State -------------------------------------------------------------------------------- /app/views/home.html: -------------------------------------------------------------------------------- 1 | Home State -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-ui-router-testing", 3 | "version": "1.0.0", 4 | "homepage": "https://github.com/nikaspran/example-ui-router-testing", 5 | "authors": [ 6 | "Nikas Praninskas " 7 | ], 8 | "license": "MIT", 9 | "ignore": [ 10 | "**/.*", 11 | "node_modules", 12 | "bower_components", 13 | "test", 14 | "tests" 15 | ], 16 | "dependencies": { 17 | "angular": "~1.4.7", 18 | "angular-ui-router": "ui-router#~0.2.15" 19 | }, 20 | "devDependencies": { 21 | "angular-mocks": "~1.4.7" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-ui-router-testing", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "postinstall": "./node_modules/.bin/bower install", 8 | "test": "./node_modules/.bin/grunt test", 9 | "serve": "./node_modules/.bin/grunt serve" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/nikaspran/example-ui-router-testing.git" 14 | }, 15 | "author": "Nikas Praninskas ", 16 | "license": "MIT", 17 | "bugs": { 18 | "url": "https://github.com/nikaspran/example-ui-router-testing/issues" 19 | }, 20 | "homepage": "https://github.com/nikaspran/example-ui-router-testing#readme", 21 | "devDependencies": { 22 | "grunt": "^0.4.5", 23 | "grunt-cli": "^0.1.13", 24 | "grunt-contrib-connect": "^0.11.2", 25 | "grunt-contrib-jasmine": "^0.9.2" 26 | }, 27 | "dependencies": { 28 | "grunt": "^1.0.1", 29 | "grunt-cli": "^1.2.0", 30 | "grunt-contrib-connect": "^1.0.2", 31 | "grunt-contrib-jasmine": "^1.0.3" 32 | } 33 | } 34 | --------------------------------------------------------------------------------