├── .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 |
--------------------------------------------------------------------------------