├── .gitignore
├── .jshintrc
├── .travis.yml
├── Gruntfile.js
├── README.md
├── demo
├── api
│ ├── config-next.json
│ └── config.json
├── error.html
├── multi.html
└── simple.html
├── karma.conf.js
├── lib
├── angular
│ ├── angular-mocks.js
│ └── angular.js
└── jquery
│ └── jquery.js
├── package.json
├── src
└── bootstrap.js
├── test
└── bootstrap.spec.js
└── webserver.js
/.gitignore:
--------------------------------------------------------------------------------
1 | dist
2 | coverage
3 | node_modules
4 | .idea
5 | *.iml
6 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | /*
3 | JSHint Default Configuration File (as on JSHint website)
4 | See http://jshint.com/docs/ for more details
5 | */
6 |
7 | // See http://jshint.com/docs/ for more details
8 |
9 | "maxerr" : 50, // {int} Maximum error before stopping
10 |
11 | // Enforcing
12 | "bitwise" : true, // true: Prohibit bitwise operators (&, |, ^, etc.)
13 | "camelcase" : false, // true: Identifiers must be in camelCase
14 | "curly" : true, // true: Require {} for every new block or scope
15 | "eqeqeq" : true, // true: Require triple equals (===) for comparison
16 | "forin" : true, // true: Require filtering for..in loops with obj.hasOwnProperty()
17 | "immed" : true, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());`
18 | "indent" : 4, // {int} Number of spaces to use for indentation
19 | "latedef" : true, // true: Require variables/functions to be defined before being used
20 | "newcap" : true, // true: Require capitalization of all constructor functions e.g. `new F()`
21 | "noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee`
22 | "noempty" : true, // true: Prohibit use of empty blocks
23 | "nonew" : false, // true: Prohibit use of constructors for side-effects (without assignment)
24 | "plusplus" : false, // true: Prohibit use of `++` & `--`
25 | "quotmark" : "single", // Quotation mark consistency:
26 | // false : do nothing (default)
27 | // true : ensure whatever is used is consistent
28 | // "single" : require single quotes
29 | // "double" : require double quotes
30 | "undef" : true, // true: Require all non-global variables to be declared (prevents global leaks)
31 | "unused" : true, // true: Require all defined variables be used
32 | "strict" : false, // true: Requires all functions run in ES5 Strict Mode
33 | "trailing" : false, // true: Prohibit trailing whitespaces
34 | "maxparams" : false, // {int} Max number of formal params allowed per function
35 | "maxdepth" : false, // {int} Max depth of nested blocks (within functions)
36 | "maxstatements" : false, // {int} Max number statements per function
37 | "maxcomplexity" : false, // {int} Max cyclomatic complexity per function
38 | "maxlen" : false, // {int} Max number of characters per line
39 |
40 | // Relaxing
41 | "asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons)
42 | "boss" : false, // true: Tolerate assignments where comparisons would be expected
43 | "debug" : false, // true: Allow debugger statements e.g. browser breakpoints.
44 | "eqnull" : true, // true: Tolerate use of `== null`
45 | "es5" : false, // true: Allow ES5 syntax (ex: getters and setters)
46 | "esnext" : false, // true: Allow ES.next (ES6) syntax (ex: `const`)
47 | "moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features)
48 | // (ex: `for each`, multiple try/catch, function expression…)
49 | "evil" : false, // true: Tolerate use of `eval` and `new Function()`
50 | "expr" : false, // true: Tolerate `ExpressionStatement` as Programs
51 | "funcscope" : false, // true: Tolerate defining variables inside control statements"
52 | "globalstrict" : false, // true: Allow global "use strict" (also enables 'strict')
53 | "iterator" : false, // true: Tolerate using the `__iterator__` property
54 | "lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block
55 | "laxbreak" : true, // true: Tolerate possibly unsafe line breakings
56 | "laxcomma" : false, // true: Tolerate comma-first style coding
57 | "loopfunc" : false, // true: Tolerate functions being defined in loops
58 | "multistr" : false, // true: Tolerate multi-line strings
59 | "proto" : false, // true: Tolerate using the `__proto__` property
60 | "scripturl" : false, // true: Tolerate script-targeted URLs
61 | "smarttabs" : false, // true: Tolerate mixed tabs/spaces when used for alignment
62 | "shadow" : false, // true: Allows re-define variables later in code e.g. `var x=1; x=2;`
63 | "sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation
64 | "supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;`
65 | "validthis" : false, // true: Tolerate using this in a non-constructor function
66 |
67 | // Environments
68 | "browser" : true, // Web Browser (window, document, etc)
69 | "couch" : false, // CouchDB
70 | "devel" : true, // Development/debugging (alert, confirm, etc)
71 | "jquery" : true, // jQuery
72 | "node" : true, // Node.js
73 | "nonstandard" : false, // Widely adopted globals (escape, unescape, etc)
74 | "rhino" : false, // Rhino
75 | "worker" : false, // Web Workers
76 |
77 | // Legacy
78 | "nomen" : false, // true: Prohibit dangling `_` in variables
79 | "onevar" : false, // true: Allow only one `var` statement per function
80 | "passfail" : false, // true: Stop on first error
81 | "white" : false, // true: Check against strict whitespace and indentation rules
82 |
83 | // Custom Globals
84 | "globals" : {
85 |
86 | //3rd party Utility framework globals
87 | "$": true,
88 | "jQuery": true,
89 | "_": true,
90 |
91 | //AngularJS globals
92 | "angular": true,
93 | "inject": true,
94 |
95 | //Jasmine globals
96 | "jasmine": true,
97 | "describe": true,
98 | "it": true,
99 | "expect": true,
100 | "spyOn": true,
101 | "spyOnEvent": true,
102 | "waitsFor": true,
103 | "module" : true,
104 | "afterEach": true,
105 | "beforeEach": true,
106 |
107 | //Grunt Global
108 | "__dirname": true
109 |
110 | }
111 | }
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "0.11"
4 | - "0.10"
5 |
6 | before_install:
7 | npm install -g grunt-cli
8 |
9 | install:
10 | npm install
11 |
12 | before_script:
13 | grunt default
14 |
15 |
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = function (grunt) {
4 |
5 | grunt.initConfig({
6 |
7 | pkg: grunt.file.readJSON('package.json'),
8 |
9 | language: grunt.option('lang') || 'en',
10 |
11 | meta: {
12 | banner: '/**\n * <%= pkg.title || pkg.name %> - v<%= pkg.version %> - ' +
13 | '<%= grunt.template.today("yyyy-mm-dd") %>\n' +
14 | ' * <%= pkg.homepage %>\n' +
15 | ' * Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %>\n' +
16 | ' * License: <%= pkg.license %>\n */\n'
17 | },
18 |
19 | build_dir: 'dist',
20 |
21 | files: {
22 |
23 | core: [
24 | 'src/bootstrap.js'
25 | ],
26 |
27 | test: ['test/**/*.js']
28 | },
29 |
30 | jshint: {
31 |
32 | options: {
33 | jshintrc: true
34 | },
35 |
36 | all: ['Gruntfile.js', '<%= files.core %>', '<%= files.test %>'],
37 |
38 | core: {
39 | files: {
40 | src: ['<%= files.core %>']
41 | }
42 | },
43 |
44 | test: {
45 | files: {
46 | src: ['<%= files.test %>']
47 | }
48 | }
49 | },
50 |
51 | uglify: {
52 | core: {
53 | files: {
54 | '<%= build_dir %>/bootstrap.min.js': '<%= files.core %>'
55 | }
56 | }
57 | },
58 |
59 | karma: {
60 | 'unit': {
61 | configFile: 'karma.conf.js',
62 | singleRun: true,
63 | browsers: ['PhantomJS']
64 | }
65 | }
66 |
67 | });
68 |
69 | grunt.loadNpmTasks('grunt-contrib-jshint');
70 | grunt.loadNpmTasks('grunt-contrib-uglify');
71 | grunt.loadNpmTasks('grunt-karma');
72 |
73 | grunt.registerTask('process', ['jshint:all', 'uglify:core']);
74 | grunt.registerTask('test', ['karma:unit']);
75 |
76 | grunt.registerTask('default', ['process', 'test']);
77 |
78 | };
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | angular-lazy-bootstrap
2 | ======================
3 |
4 | [](https://travis-ci.org/rajeshsegu/angular-lazy-bootstrap)
5 | [](https://gitter.im/rajeshsegu/angular-lazy-bootstrap)
6 |
7 | This elegant piece of code would help any Angular based single page web application to ideally load the initial configuration, global variables, server flags and UI flags before initializing the UI. It deffers the Angular bootstrapping process until we resolve all the chained promises.
8 |
9 | ## Usage
10 |
11 | Instead of using the ```ng-app``` directive or ```angular.bootstrap()```, use the ```angular.lazy().resolve().bootstrap()``` to initialize your app.
12 |
13 | ### Simple Case
14 |
15 | ```js
16 |
17 | angular.lazy("demoApp")
18 | .resolve(['$http', function ($http) {
19 | return $http.get('/demo/api/config.json')
20 | .then(function (resp) {
21 | window.app.config = resp.data;
22 | });
23 | }])
24 | .bootstrap();
25 |
26 | ```
27 | In this code sample, ```demoApp``` would be lazily initialized once you fetch the config from the server and make it available to the angular app though global variables or any angular service as designed by the application.
28 |
29 | ### Multiple Dependecies Case
30 |
31 | ```js
32 |
33 | angular.lazy("demoApp")
34 | .resolve(['$http', function ($http) {
35 | return $http.get('/demo/api/config.json')
36 | .then(function (resp) {
37 | window.app.config = resp.data;
38 | }, function (err){
39 | angular.element('#error').show();
40 | });
41 | }])
42 | .resolve(['$http', function ($http) {
43 | return $http.get('/demo/api/config-next.json')
44 | .then(function (resp) {
45 | window.app.config = resp.data;
46 | }, function (err){
47 | angular.element('#error').show();
48 | });
49 | }])
50 | .resolve(['$q', '$timeout', function ($q, $timeout) {
51 | var deferred = $q.defer();
52 | $timeout(function () {
53 | window.app.const = 'My Constant';
54 | deferred.resolve();
55 | }, 2000);
56 | return deferred.promise;
57 | }])
58 | .bootstrap();
59 |
60 | ```
61 | In this code sample, ```demoApp``` would be lazily initialized once resolve each promise in the given order. In this case, it fetches initial config and then fetches a next config for partner website and then has a timeout resolve ( just an example ) before it boostraps the actual angular application.
62 |
63 | **Note** You can handle errors individually per promise or globally ( below example )
64 |
65 | ### Error Case
66 |
67 | ```js
68 |
69 | angular.lazy("demoApp")
70 | .resolve(['$q', '$timeout', function ($q, $timeout) {
71 | var deferred = $q.defer();
72 | $timeout(function () {
73 | deferred.reject('EXCEPTION');
74 | }, 2000);
75 | return deferred.promise;
76 | }])
77 | .loading(function(){
78 | angular.element('#loading').show();
79 | })
80 | .error(function(){
81 | angular.element('#error').show();
82 | })
83 | .done(function() {
84 | angular.element('#loading').hide();
85 | })
86 | .bootstrap();
87 |
88 | ```
89 | In this code sample, ```demoApp``` would not load since the promise is rejected. We have an ```error()``` handler that gives UI a chance to handle error initializing the app or to start a new flow.
90 |
91 | ### More Control
92 |
93 | ```js
94 |
95 | angular.lazy("demoApp")
96 | .resolve(['$http', function ($http) {
97 | return $http.get('/demo/api/config.json')
98 | .then(function (resp) {
99 | window.app.config = resp.data;
100 | });
101 | }])
102 | .loading(function(){
103 | angular.element('#loading').show();
104 | })
105 | .error(function(){
106 | angular.element('#error').show();
107 | })
108 | .done(function() {
109 | angular.element('#loading').hide();
110 | })
111 | .bootstrap();
112 |
113 | ```
114 | This framework allows you to tap into different states during initializing the angular app,
115 |
116 | ```loading()```: Gives control for the UI on loading phase ( promises are resolving during this state )
117 |
118 | ```error()``` : Gives control if an error occurs ( if any of the promise results in failure )
119 |
120 | ```done()``` : Gives control once the app is initialized
121 |
122 | ### DEMO
123 |
124 | I have included a node webserver to make this easy to try:
125 |
126 | ```
127 | git clone https://github.com/rajeshsegu/angular-lazy-bootstrap.git
128 | npm install
129 | node webserver.js
130 | [ access server at http://localhost:5050/ ]
131 | ```
132 | You can try out working samples at:
133 |
134 | ```Simple Case``` : http://localhost:5050/demo/simple.html
135 |
136 | ```Multi Case``` : http://localhost:5050/demo/multi.html
137 |
138 | ```Error Case``` : http://localhost:5050/demo/error.html
139 |
140 | ## License
141 |
142 | [MIT](http://opensource.org/licenses/MIT) © Rajesh Segu
143 |
--------------------------------------------------------------------------------
/demo/api/config-next.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Demo App is live with next config!"
3 | }
--------------------------------------------------------------------------------
/demo/api/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Demo App is live with config!"
3 | }
--------------------------------------------------------------------------------
/demo/error.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Lazy Bootstrap Demo
6 |
7 |
8 |
9 |
10 |
11 | Loading...
12 |
13 |
14 |
15 | Could not load configuration!
16 |
17 |
18 |
19 | Value: {{ value }}
20 | Constant: {{ const }}
21 |
22 |
23 |
24 |
25 |
26 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/demo/multi.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Lazy Bootstrap Demo
6 |
7 |
8 |
9 |
10 |
11 | Loading...
12 |
13 |
14 |
15 | Could not load configuration!
16 |
17 |
18 |
19 | Value: {{ value }}
20 | Constant: {{ const }}
21 |
22 |
23 |
24 |
25 |
26 |
78 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/demo/simple.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Lazy Bootstrap Demo
6 |
7 |
8 |
9 |
10 |
11 | Loading...
12 |
13 |
14 |
15 | Could not load configuration!
16 |
17 |
18 |
19 | Value: {{ value }}
20 |
21 |
22 |
23 |
24 |
25 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration
2 |
3 | module.exports = function (config) {
4 |
5 | config.set({
6 |
7 | basePath: '.',
8 |
9 | frameworks: ['jasmine'],
10 |
11 | files: [
12 | 'lib/jquery/jquery.js',
13 | 'lib/angular/angular.js',
14 | 'lib/angular/angular-mocks.js',
15 | 'src/bootstrap.js',
16 | 'test/bootstrap.spec.js'
17 | ],
18 |
19 | exclude: [],
20 |
21 | reporters: ['progress', 'coverage'],
22 |
23 | port: 9876,
24 |
25 | colors: true,
26 |
27 | // LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
28 | logLevel: config.LOG_INFO,
29 |
30 | // Start these browsers, currently available:
31 | // - Chrome
32 | // - ChromeCanary
33 | // - Firefox
34 | // - Opera
35 | // - Safari (only Mac)
36 | // - PhantomJS
37 | // - IE (only Windows)
38 | browsers: [process.env.TRAVIS ? 'Firefox' : 'Chrome'],
39 |
40 | captureTimeout: 60000,
41 |
42 | singleRun: false,
43 |
44 | preprocessors: {
45 | 'src/**/!(*.spec)+(.js)': ['coverage']
46 | },
47 |
48 | coverageReporter: {
49 | reporters: [{
50 | type: 'lcov',
51 | dir: 'coverage/'
52 | },{
53 | type: 'text-summary'
54 | }]
55 | },
56 |
57 | plugins: [
58 | 'karma-jasmine',
59 | 'karma-phantomjs-launcher',
60 | 'karma-coverage'
61 | ]
62 | });
63 | };
--------------------------------------------------------------------------------
/lib/angular/angular-mocks.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @license AngularJS v1.2.27
3 | * (c) 2010-2014 Google, Inc. http://angularjs.org
4 | * License: MIT
5 | */
6 | (function(window, angular, undefined) {
7 |
8 | 'use strict';
9 |
10 | /**
11 | * @ngdoc object
12 | * @name angular.mock
13 | * @description
14 | *
15 | * Namespace from 'angular-mocks.js' which contains testing related code.
16 | */
17 | angular.mock = {};
18 |
19 | /**
20 | * ! This is a private undocumented service !
21 | *
22 | * @name $browser
23 | *
24 | * @description
25 | * This service is a mock implementation of {@link ng.$browser}. It provides fake
26 | * implementation for commonly used browser apis that are hard to test, e.g. setTimeout, xhr,
27 | * cookies, etc...
28 | *
29 | * The api of this service is the same as that of the real {@link ng.$browser $browser}, except
30 | * that there are several helper methods available which can be used in tests.
31 | */
32 | angular.mock.$BrowserProvider = function() {
33 | this.$get = function() {
34 | return new angular.mock.$Browser();
35 | };
36 | };
37 |
38 | angular.mock.$Browser = function() {
39 | var self = this;
40 |
41 | this.isMock = true;
42 | self.$$url = "http://server/";
43 | self.$$lastUrl = self.$$url; // used by url polling fn
44 | self.pollFns = [];
45 |
46 | // TODO(vojta): remove this temporary api
47 | self.$$completeOutstandingRequest = angular.noop;
48 | self.$$incOutstandingRequestCount = angular.noop;
49 |
50 |
51 | // register url polling fn
52 |
53 | self.onUrlChange = function(listener) {
54 | self.pollFns.push(
55 | function() {
56 | if (self.$$lastUrl != self.$$url) {
57 | self.$$lastUrl = self.$$url;
58 | listener(self.$$url);
59 | }
60 | }
61 | );
62 |
63 | return listener;
64 | };
65 |
66 | self.$$checkUrlChange = angular.noop;
67 |
68 | self.cookieHash = {};
69 | self.lastCookieHash = {};
70 | self.deferredFns = [];
71 | self.deferredNextId = 0;
72 |
73 | self.defer = function(fn, delay) {
74 | delay = delay || 0;
75 | self.deferredFns.push({time:(self.defer.now + delay), fn:fn, id: self.deferredNextId});
76 | self.deferredFns.sort(function(a,b){ return a.time - b.time;});
77 | return self.deferredNextId++;
78 | };
79 |
80 |
81 | /**
82 | * @name $browser#defer.now
83 | *
84 | * @description
85 | * Current milliseconds mock time.
86 | */
87 | self.defer.now = 0;
88 |
89 |
90 | self.defer.cancel = function(deferId) {
91 | var fnIndex;
92 |
93 | angular.forEach(self.deferredFns, function(fn, index) {
94 | if (fn.id === deferId) fnIndex = index;
95 | });
96 |
97 | if (fnIndex !== undefined) {
98 | self.deferredFns.splice(fnIndex, 1);
99 | return true;
100 | }
101 |
102 | return false;
103 | };
104 |
105 |
106 | /**
107 | * @name $browser#defer.flush
108 | *
109 | * @description
110 | * Flushes all pending requests and executes the defer callbacks.
111 | *
112 | * @param {number=} number of milliseconds to flush. See {@link #defer.now}
113 | */
114 | self.defer.flush = function(delay) {
115 | if (angular.isDefined(delay)) {
116 | self.defer.now += delay;
117 | } else {
118 | if (self.deferredFns.length) {
119 | self.defer.now = self.deferredFns[self.deferredFns.length-1].time;
120 | } else {
121 | throw new Error('No deferred tasks to be flushed');
122 | }
123 | }
124 |
125 | while (self.deferredFns.length && self.deferredFns[0].time <= self.defer.now) {
126 | self.deferredFns.shift().fn();
127 | }
128 | };
129 |
130 | self.$$baseHref = '';
131 | self.baseHref = function() {
132 | return this.$$baseHref;
133 | };
134 | };
135 | angular.mock.$Browser.prototype = {
136 |
137 | /**
138 | * @name $browser#poll
139 | *
140 | * @description
141 | * run all fns in pollFns
142 | */
143 | poll: function poll() {
144 | angular.forEach(this.pollFns, function(pollFn){
145 | pollFn();
146 | });
147 | },
148 |
149 | addPollFn: function(pollFn) {
150 | this.pollFns.push(pollFn);
151 | return pollFn;
152 | },
153 |
154 | url: function(url, replace) {
155 | if (url) {
156 | this.$$url = url;
157 | return this;
158 | }
159 |
160 | return this.$$url;
161 | },
162 |
163 | cookies: function(name, value) {
164 | if (name) {
165 | if (angular.isUndefined(value)) {
166 | delete this.cookieHash[name];
167 | } else {
168 | if (angular.isString(value) && //strings only
169 | value.length <= 4096) { //strict cookie storage limits
170 | this.cookieHash[name] = value;
171 | }
172 | }
173 | } else {
174 | if (!angular.equals(this.cookieHash, this.lastCookieHash)) {
175 | this.lastCookieHash = angular.copy(this.cookieHash);
176 | this.cookieHash = angular.copy(this.cookieHash);
177 | }
178 | return this.cookieHash;
179 | }
180 | },
181 |
182 | notifyWhenNoOutstandingRequests: function(fn) {
183 | fn();
184 | }
185 | };
186 |
187 |
188 | /**
189 | * @ngdoc provider
190 | * @name $exceptionHandlerProvider
191 | *
192 | * @description
193 | * Configures the mock implementation of {@link ng.$exceptionHandler} to rethrow or to log errors
194 | * passed into the `$exceptionHandler`.
195 | */
196 |
197 | /**
198 | * @ngdoc service
199 | * @name $exceptionHandler
200 | *
201 | * @description
202 | * Mock implementation of {@link ng.$exceptionHandler} that rethrows or logs errors passed
203 | * into it. See {@link ngMock.$exceptionHandlerProvider $exceptionHandlerProvider} for configuration
204 | * information.
205 | *
206 | *
207 | * ```js
208 | * describe('$exceptionHandlerProvider', function() {
209 | *
210 | * it('should capture log messages and exceptions', function() {
211 | *
212 | * module(function($exceptionHandlerProvider) {
213 | * $exceptionHandlerProvider.mode('log');
214 | * });
215 | *
216 | * inject(function($log, $exceptionHandler, $timeout) {
217 | * $timeout(function() { $log.log(1); });
218 | * $timeout(function() { $log.log(2); throw 'banana peel'; });
219 | * $timeout(function() { $log.log(3); });
220 | * expect($exceptionHandler.errors).toEqual([]);
221 | * expect($log.assertEmpty());
222 | * $timeout.flush();
223 | * expect($exceptionHandler.errors).toEqual(['banana peel']);
224 | * expect($log.log.logs).toEqual([[1], [2], [3]]);
225 | * });
226 | * });
227 | * });
228 | * ```
229 | */
230 |
231 | angular.mock.$ExceptionHandlerProvider = function() {
232 | var handler;
233 |
234 | /**
235 | * @ngdoc method
236 | * @name $exceptionHandlerProvider#mode
237 | *
238 | * @description
239 | * Sets the logging mode.
240 | *
241 | * @param {string} mode Mode of operation, defaults to `rethrow`.
242 | *
243 | * - `rethrow`: If any errors are passed into the handler in tests, it typically
244 | * means that there is a bug in the application or test, so this mock will
245 | * make these tests fail.
246 | * - `log`: Sometimes it is desirable to test that an error is thrown, for this case the `log`
247 | * mode stores an array of errors in `$exceptionHandler.errors`, to allow later
248 | * assertion of them. See {@link ngMock.$log#assertEmpty assertEmpty()} and
249 | * {@link ngMock.$log#reset reset()}
250 | */
251 | this.mode = function(mode) {
252 | switch(mode) {
253 | case 'rethrow':
254 | handler = function(e) {
255 | throw e;
256 | };
257 | break;
258 | case 'log':
259 | var errors = [];
260 |
261 | handler = function(e) {
262 | if (arguments.length == 1) {
263 | errors.push(e);
264 | } else {
265 | errors.push([].slice.call(arguments, 0));
266 | }
267 | };
268 |
269 | handler.errors = errors;
270 | break;
271 | default:
272 | throw new Error("Unknown mode '" + mode + "', only 'log'/'rethrow' modes are allowed!");
273 | }
274 | };
275 |
276 | this.$get = function() {
277 | return handler;
278 | };
279 |
280 | this.mode('rethrow');
281 | };
282 |
283 |
284 | /**
285 | * @ngdoc service
286 | * @name $log
287 | *
288 | * @description
289 | * Mock implementation of {@link ng.$log} that gathers all logged messages in arrays
290 | * (one array per logging level). These arrays are exposed as `logs` property of each of the
291 | * level-specific log function, e.g. for level `error` the array is exposed as `$log.error.logs`.
292 | *
293 | */
294 | angular.mock.$LogProvider = function() {
295 | var debug = true;
296 |
297 | function concat(array1, array2, index) {
298 | return array1.concat(Array.prototype.slice.call(array2, index));
299 | }
300 |
301 | this.debugEnabled = function(flag) {
302 | if (angular.isDefined(flag)) {
303 | debug = flag;
304 | return this;
305 | } else {
306 | return debug;
307 | }
308 | };
309 |
310 | this.$get = function () {
311 | var $log = {
312 | log: function() { $log.log.logs.push(concat([], arguments, 0)); },
313 | warn: function() { $log.warn.logs.push(concat([], arguments, 0)); },
314 | info: function() { $log.info.logs.push(concat([], arguments, 0)); },
315 | error: function() { $log.error.logs.push(concat([], arguments, 0)); },
316 | debug: function() {
317 | if (debug) {
318 | $log.debug.logs.push(concat([], arguments, 0));
319 | }
320 | }
321 | };
322 |
323 | /**
324 | * @ngdoc method
325 | * @name $log#reset
326 | *
327 | * @description
328 | * Reset all of the logging arrays to empty.
329 | */
330 | $log.reset = function () {
331 | /**
332 | * @ngdoc property
333 | * @name $log#log.logs
334 | *
335 | * @description
336 | * Array of messages logged using {@link ngMock.$log#log}.
337 | *
338 | * @example
339 | * ```js
340 | * $log.log('Some Log');
341 | * var first = $log.log.logs.unshift();
342 | * ```
343 | */
344 | $log.log.logs = [];
345 | /**
346 | * @ngdoc property
347 | * @name $log#info.logs
348 | *
349 | * @description
350 | * Array of messages logged using {@link ngMock.$log#info}.
351 | *
352 | * @example
353 | * ```js
354 | * $log.info('Some Info');
355 | * var first = $log.info.logs.unshift();
356 | * ```
357 | */
358 | $log.info.logs = [];
359 | /**
360 | * @ngdoc property
361 | * @name $log#warn.logs
362 | *
363 | * @description
364 | * Array of messages logged using {@link ngMock.$log#warn}.
365 | *
366 | * @example
367 | * ```js
368 | * $log.warn('Some Warning');
369 | * var first = $log.warn.logs.unshift();
370 | * ```
371 | */
372 | $log.warn.logs = [];
373 | /**
374 | * @ngdoc property
375 | * @name $log#error.logs
376 | *
377 | * @description
378 | * Array of messages logged using {@link ngMock.$log#error}.
379 | *
380 | * @example
381 | * ```js
382 | * $log.error('Some Error');
383 | * var first = $log.error.logs.unshift();
384 | * ```
385 | */
386 | $log.error.logs = [];
387 | /**
388 | * @ngdoc property
389 | * @name $log#debug.logs
390 | *
391 | * @description
392 | * Array of messages logged using {@link ngMock.$log#debug}.
393 | *
394 | * @example
395 | * ```js
396 | * $log.debug('Some Error');
397 | * var first = $log.debug.logs.unshift();
398 | * ```
399 | */
400 | $log.debug.logs = [];
401 | };
402 |
403 | /**
404 | * @ngdoc method
405 | * @name $log#assertEmpty
406 | *
407 | * @description
408 | * Assert that the all of the logging methods have no logged messages. If messages present, an
409 | * exception is thrown.
410 | */
411 | $log.assertEmpty = function() {
412 | var errors = [];
413 | angular.forEach(['error', 'warn', 'info', 'log', 'debug'], function(logLevel) {
414 | angular.forEach($log[logLevel].logs, function(log) {
415 | angular.forEach(log, function (logItem) {
416 | errors.push('MOCK $log (' + logLevel + '): ' + String(logItem) + '\n' +
417 | (logItem.stack || ''));
418 | });
419 | });
420 | });
421 | if (errors.length) {
422 | errors.unshift("Expected $log to be empty! Either a message was logged unexpectedly, or "+
423 | "an expected log message was not checked and removed:");
424 | errors.push('');
425 | throw new Error(errors.join('\n---------\n'));
426 | }
427 | };
428 |
429 | $log.reset();
430 | return $log;
431 | };
432 | };
433 |
434 |
435 | /**
436 | * @ngdoc service
437 | * @name $interval
438 | *
439 | * @description
440 | * Mock implementation of the $interval service.
441 | *
442 | * Use {@link ngMock.$interval#flush `$interval.flush(millis)`} to
443 | * move forward by `millis` milliseconds and trigger any functions scheduled to run in that
444 | * time.
445 | *
446 | * @param {function()} fn A function that should be called repeatedly.
447 | * @param {number} delay Number of milliseconds between each function call.
448 | * @param {number=} [count=0] Number of times to repeat. If not set, or 0, will repeat
449 | * indefinitely.
450 | * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise
451 | * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block.
452 | * @returns {promise} A promise which will be notified on each iteration.
453 | */
454 | angular.mock.$IntervalProvider = function() {
455 | this.$get = ['$rootScope', '$q',
456 | function($rootScope, $q) {
457 | var repeatFns = [],
458 | nextRepeatId = 0,
459 | now = 0;
460 |
461 | var $interval = function(fn, delay, count, invokeApply) {
462 | var deferred = $q.defer(),
463 | promise = deferred.promise,
464 | iteration = 0,
465 | skipApply = (angular.isDefined(invokeApply) && !invokeApply);
466 |
467 | count = (angular.isDefined(count)) ? count : 0;
468 | promise.then(null, null, fn);
469 |
470 | promise.$$intervalId = nextRepeatId;
471 |
472 | function tick() {
473 | deferred.notify(iteration++);
474 |
475 | if (count > 0 && iteration >= count) {
476 | var fnIndex;
477 | deferred.resolve(iteration);
478 |
479 | angular.forEach(repeatFns, function(fn, index) {
480 | if (fn.id === promise.$$intervalId) fnIndex = index;
481 | });
482 |
483 | if (fnIndex !== undefined) {
484 | repeatFns.splice(fnIndex, 1);
485 | }
486 | }
487 |
488 | if (!skipApply) $rootScope.$apply();
489 | }
490 |
491 | repeatFns.push({
492 | nextTime:(now + delay),
493 | delay: delay,
494 | fn: tick,
495 | id: nextRepeatId,
496 | deferred: deferred
497 | });
498 | repeatFns.sort(function(a,b){ return a.nextTime - b.nextTime;});
499 |
500 | nextRepeatId++;
501 | return promise;
502 | };
503 | /**
504 | * @ngdoc method
505 | * @name $interval#cancel
506 | *
507 | * @description
508 | * Cancels a task associated with the `promise`.
509 | *
510 | * @param {promise} promise A promise from calling the `$interval` function.
511 | * @returns {boolean} Returns `true` if the task was successfully cancelled.
512 | */
513 | $interval.cancel = function(promise) {
514 | if(!promise) return false;
515 | var fnIndex;
516 |
517 | angular.forEach(repeatFns, function(fn, index) {
518 | if (fn.id === promise.$$intervalId) fnIndex = index;
519 | });
520 |
521 | if (fnIndex !== undefined) {
522 | repeatFns[fnIndex].deferred.reject('canceled');
523 | repeatFns.splice(fnIndex, 1);
524 | return true;
525 | }
526 |
527 | return false;
528 | };
529 |
530 | /**
531 | * @ngdoc method
532 | * @name $interval#flush
533 | * @description
534 | *
535 | * Runs interval tasks scheduled to be run in the next `millis` milliseconds.
536 | *
537 | * @param {number=} millis maximum timeout amount to flush up until.
538 | *
539 | * @return {number} The amount of time moved forward.
540 | */
541 | $interval.flush = function(millis) {
542 | now += millis;
543 | while (repeatFns.length && repeatFns[0].nextTime <= now) {
544 | var task = repeatFns[0];
545 | task.fn();
546 | task.nextTime += task.delay;
547 | repeatFns.sort(function(a,b){ return a.nextTime - b.nextTime;});
548 | }
549 | return millis;
550 | };
551 |
552 | return $interval;
553 | }];
554 | };
555 |
556 |
557 | /* jshint -W101 */
558 | /* The R_ISO8061_STR regex is never going to fit into the 100 char limit!
559 | * This directive should go inside the anonymous function but a bug in JSHint means that it would
560 | * not be enacted early enough to prevent the warning.
561 | */
562 | var R_ISO8061_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?:\:?(\d\d)(?:\:?(\d\d)(?:\.(\d{3}))?)?)?(Z|([+-])(\d\d):?(\d\d)))?$/;
563 |
564 | function jsonStringToDate(string) {
565 | var match;
566 | if (match = string.match(R_ISO8061_STR)) {
567 | var date = new Date(0),
568 | tzHour = 0,
569 | tzMin = 0;
570 | if (match[9]) {
571 | tzHour = int(match[9] + match[10]);
572 | tzMin = int(match[9] + match[11]);
573 | }
574 | date.setUTCFullYear(int(match[1]), int(match[2]) - 1, int(match[3]));
575 | date.setUTCHours(int(match[4]||0) - tzHour,
576 | int(match[5]||0) - tzMin,
577 | int(match[6]||0),
578 | int(match[7]||0));
579 | return date;
580 | }
581 | return string;
582 | }
583 |
584 | function int(str) {
585 | return parseInt(str, 10);
586 | }
587 |
588 | function padNumber(num, digits, trim) {
589 | var neg = '';
590 | if (num < 0) {
591 | neg = '-';
592 | num = -num;
593 | }
594 | num = '' + num;
595 | while(num.length < digits) num = '0' + num;
596 | if (trim)
597 | num = num.substr(num.length - digits);
598 | return neg + num;
599 | }
600 |
601 |
602 | /**
603 | * @ngdoc type
604 | * @name angular.mock.TzDate
605 | * @description
606 | *
607 | * *NOTE*: this is not an injectable instance, just a globally available mock class of `Date`.
608 | *
609 | * Mock of the Date type which has its timezone specified via constructor arg.
610 | *
611 | * The main purpose is to create Date-like instances with timezone fixed to the specified timezone
612 | * offset, so that we can test code that depends on local timezone settings without dependency on
613 | * the time zone settings of the machine where the code is running.
614 | *
615 | * @param {number} offset Offset of the *desired* timezone in hours (fractions will be honored)
616 | * @param {(number|string)} timestamp Timestamp representing the desired time in *UTC*
617 | *
618 | * @example
619 | * !!!! WARNING !!!!!
620 | * This is not a complete Date object so only methods that were implemented can be called safely.
621 | * To make matters worse, TzDate instances inherit stuff from Date via a prototype.
622 | *
623 | * We do our best to intercept calls to "unimplemented" methods, but since the list of methods is
624 | * incomplete we might be missing some non-standard methods. This can result in errors like:
625 | * "Date.prototype.foo called on incompatible Object".
626 | *
627 | * ```js
628 | * var newYearInBratislava = new TzDate(-1, '2009-12-31T23:00:00Z');
629 | * newYearInBratislava.getTimezoneOffset() => -60;
630 | * newYearInBratislava.getFullYear() => 2010;
631 | * newYearInBratislava.getMonth() => 0;
632 | * newYearInBratislava.getDate() => 1;
633 | * newYearInBratislava.getHours() => 0;
634 | * newYearInBratislava.getMinutes() => 0;
635 | * newYearInBratislava.getSeconds() => 0;
636 | * ```
637 | *
638 | */
639 | angular.mock.TzDate = function (offset, timestamp) {
640 | var self = new Date(0);
641 | if (angular.isString(timestamp)) {
642 | var tsStr = timestamp;
643 |
644 | self.origDate = jsonStringToDate(timestamp);
645 |
646 | timestamp = self.origDate.getTime();
647 | if (isNaN(timestamp))
648 | throw {
649 | name: "Illegal Argument",
650 | message: "Arg '" + tsStr + "' passed into TzDate constructor is not a valid date string"
651 | };
652 | } else {
653 | self.origDate = new Date(timestamp);
654 | }
655 |
656 | var localOffset = new Date(timestamp).getTimezoneOffset();
657 | self.offsetDiff = localOffset*60*1000 - offset*1000*60*60;
658 | self.date = new Date(timestamp + self.offsetDiff);
659 |
660 | self.getTime = function() {
661 | return self.date.getTime() - self.offsetDiff;
662 | };
663 |
664 | self.toLocaleDateString = function() {
665 | return self.date.toLocaleDateString();
666 | };
667 |
668 | self.getFullYear = function() {
669 | return self.date.getFullYear();
670 | };
671 |
672 | self.getMonth = function() {
673 | return self.date.getMonth();
674 | };
675 |
676 | self.getDate = function() {
677 | return self.date.getDate();
678 | };
679 |
680 | self.getHours = function() {
681 | return self.date.getHours();
682 | };
683 |
684 | self.getMinutes = function() {
685 | return self.date.getMinutes();
686 | };
687 |
688 | self.getSeconds = function() {
689 | return self.date.getSeconds();
690 | };
691 |
692 | self.getMilliseconds = function() {
693 | return self.date.getMilliseconds();
694 | };
695 |
696 | self.getTimezoneOffset = function() {
697 | return offset * 60;
698 | };
699 |
700 | self.getUTCFullYear = function() {
701 | return self.origDate.getUTCFullYear();
702 | };
703 |
704 | self.getUTCMonth = function() {
705 | return self.origDate.getUTCMonth();
706 | };
707 |
708 | self.getUTCDate = function() {
709 | return self.origDate.getUTCDate();
710 | };
711 |
712 | self.getUTCHours = function() {
713 | return self.origDate.getUTCHours();
714 | };
715 |
716 | self.getUTCMinutes = function() {
717 | return self.origDate.getUTCMinutes();
718 | };
719 |
720 | self.getUTCSeconds = function() {
721 | return self.origDate.getUTCSeconds();
722 | };
723 |
724 | self.getUTCMilliseconds = function() {
725 | return self.origDate.getUTCMilliseconds();
726 | };
727 |
728 | self.getDay = function() {
729 | return self.date.getDay();
730 | };
731 |
732 | // provide this method only on browsers that already have it
733 | if (self.toISOString) {
734 | self.toISOString = function() {
735 | return padNumber(self.origDate.getUTCFullYear(), 4) + '-' +
736 | padNumber(self.origDate.getUTCMonth() + 1, 2) + '-' +
737 | padNumber(self.origDate.getUTCDate(), 2) + 'T' +
738 | padNumber(self.origDate.getUTCHours(), 2) + ':' +
739 | padNumber(self.origDate.getUTCMinutes(), 2) + ':' +
740 | padNumber(self.origDate.getUTCSeconds(), 2) + '.' +
741 | padNumber(self.origDate.getUTCMilliseconds(), 3) + 'Z';
742 | };
743 | }
744 |
745 | //hide all methods not implemented in this mock that the Date prototype exposes
746 | var unimplementedMethods = ['getUTCDay',
747 | 'getYear', 'setDate', 'setFullYear', 'setHours', 'setMilliseconds',
748 | 'setMinutes', 'setMonth', 'setSeconds', 'setTime', 'setUTCDate', 'setUTCFullYear',
749 | 'setUTCHours', 'setUTCMilliseconds', 'setUTCMinutes', 'setUTCMonth', 'setUTCSeconds',
750 | 'setYear', 'toDateString', 'toGMTString', 'toJSON', 'toLocaleFormat', 'toLocaleString',
751 | 'toLocaleTimeString', 'toSource', 'toString', 'toTimeString', 'toUTCString', 'valueOf'];
752 |
753 | angular.forEach(unimplementedMethods, function(methodName) {
754 | self[methodName] = function() {
755 | throw new Error("Method '" + methodName + "' is not implemented in the TzDate mock");
756 | };
757 | });
758 |
759 | return self;
760 | };
761 |
762 | //make "tzDateInstance instanceof Date" return true
763 | angular.mock.TzDate.prototype = Date.prototype;
764 | /* jshint +W101 */
765 |
766 | angular.mock.animate = angular.module('ngAnimateMock', ['ng'])
767 |
768 | .config(['$provide', function($provide) {
769 |
770 | var reflowQueue = [];
771 | $provide.value('$$animateReflow', function(fn) {
772 | var index = reflowQueue.length;
773 | reflowQueue.push(fn);
774 | return function cancel() {
775 | reflowQueue.splice(index, 1);
776 | };
777 | });
778 |
779 | $provide.decorator('$animate', function($delegate, $$asyncCallback) {
780 | var animate = {
781 | queue : [],
782 | enabled : $delegate.enabled,
783 | triggerCallbacks : function() {
784 | $$asyncCallback.flush();
785 | },
786 | triggerReflow : function() {
787 | angular.forEach(reflowQueue, function(fn) {
788 | fn();
789 | });
790 | reflowQueue = [];
791 | }
792 | };
793 |
794 | angular.forEach(
795 | ['enter','leave','move','addClass','removeClass','setClass'], function(method) {
796 | animate[method] = function() {
797 | animate.queue.push({
798 | event : method,
799 | element : arguments[0],
800 | args : arguments
801 | });
802 | $delegate[method].apply($delegate, arguments);
803 | };
804 | });
805 |
806 | return animate;
807 | });
808 |
809 | }]);
810 |
811 |
812 | /**
813 | * @ngdoc function
814 | * @name angular.mock.dump
815 | * @description
816 | *
817 | * *NOTE*: this is not an injectable instance, just a globally available function.
818 | *
819 | * Method for serializing common angular objects (scope, elements, etc..) into strings, useful for
820 | * debugging.
821 | *
822 | * This method is also available on window, where it can be used to display objects on debug
823 | * console.
824 | *
825 | * @param {*} object - any object to turn into string.
826 | * @return {string} a serialized string of the argument
827 | */
828 | angular.mock.dump = function(object) {
829 | return serialize(object);
830 |
831 | function serialize(object) {
832 | var out;
833 |
834 | if (angular.isElement(object)) {
835 | object = angular.element(object);
836 | out = angular.element('');
837 | angular.forEach(object, function(element) {
838 | out.append(angular.element(element).clone());
839 | });
840 | out = out.html();
841 | } else if (angular.isArray(object)) {
842 | out = [];
843 | angular.forEach(object, function(o) {
844 | out.push(serialize(o));
845 | });
846 | out = '[ ' + out.join(', ') + ' ]';
847 | } else if (angular.isObject(object)) {
848 | if (angular.isFunction(object.$eval) && angular.isFunction(object.$apply)) {
849 | out = serializeScope(object);
850 | } else if (object instanceof Error) {
851 | out = object.stack || ('' + object.name + ': ' + object.message);
852 | } else {
853 | // TODO(i): this prevents methods being logged,
854 | // we should have a better way to serialize objects
855 | out = angular.toJson(object, true);
856 | }
857 | } else {
858 | out = String(object);
859 | }
860 |
861 | return out;
862 | }
863 |
864 | function serializeScope(scope, offset) {
865 | offset = offset || ' ';
866 | var log = [offset + 'Scope(' + scope.$id + '): {'];
867 | for ( var key in scope ) {
868 | if (Object.prototype.hasOwnProperty.call(scope, key) && !key.match(/^(\$|this)/)) {
869 | log.push(' ' + key + ': ' + angular.toJson(scope[key]));
870 | }
871 | }
872 | var child = scope.$$childHead;
873 | while(child) {
874 | log.push(serializeScope(child, offset + ' '));
875 | child = child.$$nextSibling;
876 | }
877 | log.push('}');
878 | return log.join('\n' + offset);
879 | }
880 | };
881 |
882 | /**
883 | * @ngdoc service
884 | * @name $httpBackend
885 | * @description
886 | * Fake HTTP backend implementation suitable for unit testing applications that use the
887 | * {@link ng.$http $http service}.
888 | *
889 | * *Note*: For fake HTTP backend implementation suitable for end-to-end testing or backend-less
890 | * development please see {@link ngMockE2E.$httpBackend e2e $httpBackend mock}.
891 | *
892 | * During unit testing, we want our unit tests to run quickly and have no external dependencies so
893 | * we don’t want to send [XHR](https://developer.mozilla.org/en/xmlhttprequest) or
894 | * [JSONP](http://en.wikipedia.org/wiki/JSONP) requests to a real server. All we really need is
895 | * to verify whether a certain request has been sent or not, or alternatively just let the
896 | * application make requests, respond with pre-trained responses and assert that the end result is
897 | * what we expect it to be.
898 | *
899 | * This mock implementation can be used to respond with static or dynamic responses via the
900 | * `expect` and `when` apis and their shortcuts (`expectGET`, `whenPOST`, etc).
901 | *
902 | * When an Angular application needs some data from a server, it calls the $http service, which
903 | * sends the request to a real server using $httpBackend service. With dependency injection, it is
904 | * easy to inject $httpBackend mock (which has the same API as $httpBackend) and use it to verify
905 | * the requests and respond with some testing data without sending a request to a real server.
906 | *
907 | * There are two ways to specify what test data should be returned as http responses by the mock
908 | * backend when the code under test makes http requests:
909 | *
910 | * - `$httpBackend.expect` - specifies a request expectation
911 | * - `$httpBackend.when` - specifies a backend definition
912 | *
913 | *
914 | * # Request Expectations vs Backend Definitions
915 | *
916 | * Request expectations provide a way to make assertions about requests made by the application and
917 | * to define responses for those requests. The test will fail if the expected requests are not made
918 | * or they are made in the wrong order.
919 | *
920 | * Backend definitions allow you to define a fake backend for your application which doesn't assert
921 | * if a particular request was made or not, it just returns a trained response if a request is made.
922 | * The test will pass whether or not the request gets made during testing.
923 | *
924 | *
925 | *
926 | * | Request expectations | Backend definitions |
927 | *
928 | * Syntax |
929 | * .expect(...).respond(...) |
930 | * .when(...).respond(...) |
931 | *
932 | *
933 | * Typical usage |
934 | * strict unit tests |
935 | * loose (black-box) unit testing |
936 | *
937 | *
938 | * Fulfills multiple requests |
939 | * NO |
940 | * YES |
941 | *
942 | *
943 | * Order of requests matters |
944 | * YES |
945 | * NO |
946 | *
947 | *
948 | * Request required |
949 | * YES |
950 | * NO |
951 | *
952 | *
953 | * Response required |
954 | * optional (see below) |
955 | * YES |
956 | *
957 | *
958 | *
959 | * In cases where both backend definitions and request expectations are specified during unit
960 | * testing, the request expectations are evaluated first.
961 | *
962 | * If a request expectation has no response specified, the algorithm will search your backend
963 | * definitions for an appropriate response.
964 | *
965 | * If a request didn't match any expectation or if the expectation doesn't have the response
966 | * defined, the backend definitions are evaluated in sequential order to see if any of them match
967 | * the request. The response from the first matched definition is returned.
968 | *
969 | *
970 | * # Flushing HTTP requests
971 | *
972 | * The $httpBackend used in production always responds to requests asynchronously. If we preserved
973 | * this behavior in unit testing, we'd have to create async unit tests, which are hard to write,
974 | * to follow and to maintain. But neither can the testing mock respond synchronously; that would
975 | * change the execution of the code under test. For this reason, the mock $httpBackend has a
976 | * `flush()` method, which allows the test to explicitly flush pending requests. This preserves
977 | * the async api of the backend, while allowing the test to execute synchronously.
978 | *
979 | *
980 | * # Unit testing with mock $httpBackend
981 | * The following code shows how to setup and use the mock backend when unit testing a controller.
982 | * First we create the controller under test:
983 | *
984 | ```js
985 | // The controller code
986 | function MyController($scope, $http) {
987 | var authToken;
988 |
989 | $http.get('/auth.py').success(function(data, status, headers) {
990 | authToken = headers('A-Token');
991 | $scope.user = data;
992 | });
993 |
994 | $scope.saveMessage = function(message) {
995 | var headers = { 'Authorization': authToken };
996 | $scope.status = 'Saving...';
997 |
998 | $http.post('/add-msg.py', message, { headers: headers } ).success(function(response) {
999 | $scope.status = '';
1000 | }).error(function() {
1001 | $scope.status = 'ERROR!';
1002 | });
1003 | };
1004 | }
1005 | ```
1006 | *
1007 | * Now we setup the mock backend and create the test specs:
1008 | *
1009 | ```js
1010 | // testing controller
1011 | describe('MyController', function() {
1012 | var $httpBackend, $rootScope, createController;
1013 |
1014 | beforeEach(inject(function($injector) {
1015 | // Set up the mock http service responses
1016 | $httpBackend = $injector.get('$httpBackend');
1017 | // backend definition common for all tests
1018 | $httpBackend.when('GET', '/auth.py').respond({userId: 'userX'}, {'A-Token': 'xxx'});
1019 |
1020 | // Get hold of a scope (i.e. the root scope)
1021 | $rootScope = $injector.get('$rootScope');
1022 | // The $controller service is used to create instances of controllers
1023 | var $controller = $injector.get('$controller');
1024 |
1025 | createController = function() {
1026 | return $controller('MyController', {'$scope' : $rootScope });
1027 | };
1028 | }));
1029 |
1030 |
1031 | afterEach(function() {
1032 | $httpBackend.verifyNoOutstandingExpectation();
1033 | $httpBackend.verifyNoOutstandingRequest();
1034 | });
1035 |
1036 |
1037 | it('should fetch authentication token', function() {
1038 | $httpBackend.expectGET('/auth.py');
1039 | var controller = createController();
1040 | $httpBackend.flush();
1041 | });
1042 |
1043 |
1044 | it('should send msg to server', function() {
1045 | var controller = createController();
1046 | $httpBackend.flush();
1047 |
1048 | // now you don’t care about the authentication, but
1049 | // the controller will still send the request and
1050 | // $httpBackend will respond without you having to
1051 | // specify the expectation and response for this request
1052 |
1053 | $httpBackend.expectPOST('/add-msg.py', 'message content').respond(201, '');
1054 | $rootScope.saveMessage('message content');
1055 | expect($rootScope.status).toBe('Saving...');
1056 | $httpBackend.flush();
1057 | expect($rootScope.status).toBe('');
1058 | });
1059 |
1060 |
1061 | it('should send auth header', function() {
1062 | var controller = createController();
1063 | $httpBackend.flush();
1064 |
1065 | $httpBackend.expectPOST('/add-msg.py', undefined, function(headers) {
1066 | // check if the header was send, if it wasn't the expectation won't
1067 | // match the request and the test will fail
1068 | return headers['Authorization'] == 'xxx';
1069 | }).respond(201, '');
1070 |
1071 | $rootScope.saveMessage('whatever');
1072 | $httpBackend.flush();
1073 | });
1074 | });
1075 | ```
1076 | */
1077 | angular.mock.$HttpBackendProvider = function() {
1078 | this.$get = ['$rootScope', createHttpBackendMock];
1079 | };
1080 |
1081 | /**
1082 | * General factory function for $httpBackend mock.
1083 | * Returns instance for unit testing (when no arguments specified):
1084 | * - passing through is disabled
1085 | * - auto flushing is disabled
1086 | *
1087 | * Returns instance for e2e testing (when `$delegate` and `$browser` specified):
1088 | * - passing through (delegating request to real backend) is enabled
1089 | * - auto flushing is enabled
1090 | *
1091 | * @param {Object=} $delegate Real $httpBackend instance (allow passing through if specified)
1092 | * @param {Object=} $browser Auto-flushing enabled if specified
1093 | * @return {Object} Instance of $httpBackend mock
1094 | */
1095 | function createHttpBackendMock($rootScope, $delegate, $browser) {
1096 | var definitions = [],
1097 | expectations = [],
1098 | responses = [],
1099 | responsesPush = angular.bind(responses, responses.push),
1100 | copy = angular.copy;
1101 |
1102 | function createResponse(status, data, headers, statusText) {
1103 | if (angular.isFunction(status)) return status;
1104 |
1105 | return function() {
1106 | return angular.isNumber(status)
1107 | ? [status, data, headers, statusText]
1108 | : [200, status, data];
1109 | };
1110 | }
1111 |
1112 | // TODO(vojta): change params to: method, url, data, headers, callback
1113 | function $httpBackend(method, url, data, callback, headers, timeout, withCredentials) {
1114 | var xhr = new MockXhr(),
1115 | expectation = expectations[0],
1116 | wasExpected = false;
1117 |
1118 | function prettyPrint(data) {
1119 | return (angular.isString(data) || angular.isFunction(data) || data instanceof RegExp)
1120 | ? data
1121 | : angular.toJson(data);
1122 | }
1123 |
1124 | function wrapResponse(wrapped) {
1125 | if (!$browser && timeout && timeout.then) timeout.then(handleTimeout);
1126 |
1127 | return handleResponse;
1128 |
1129 | function handleResponse() {
1130 | var response = wrapped.response(method, url, data, headers);
1131 | xhr.$$respHeaders = response[2];
1132 | callback(copy(response[0]), copy(response[1]), xhr.getAllResponseHeaders(),
1133 | copy(response[3] || ''));
1134 | }
1135 |
1136 | function handleTimeout() {
1137 | for (var i = 0, ii = responses.length; i < ii; i++) {
1138 | if (responses[i] === handleResponse) {
1139 | responses.splice(i, 1);
1140 | callback(-1, undefined, '');
1141 | break;
1142 | }
1143 | }
1144 | }
1145 | }
1146 |
1147 | if (expectation && expectation.match(method, url)) {
1148 | if (!expectation.matchData(data))
1149 | throw new Error('Expected ' + expectation + ' with different data\n' +
1150 | 'EXPECTED: ' + prettyPrint(expectation.data) + '\nGOT: ' + data);
1151 |
1152 | if (!expectation.matchHeaders(headers))
1153 | throw new Error('Expected ' + expectation + ' with different headers\n' +
1154 | 'EXPECTED: ' + prettyPrint(expectation.headers) + '\nGOT: ' +
1155 | prettyPrint(headers));
1156 |
1157 | expectations.shift();
1158 |
1159 | if (expectation.response) {
1160 | responses.push(wrapResponse(expectation));
1161 | return;
1162 | }
1163 | wasExpected = true;
1164 | }
1165 |
1166 | var i = -1, definition;
1167 | while ((definition = definitions[++i])) {
1168 | if (definition.match(method, url, data, headers || {})) {
1169 | if (definition.response) {
1170 | // if $browser specified, we do auto flush all requests
1171 | ($browser ? $browser.defer : responsesPush)(wrapResponse(definition));
1172 | } else if (definition.passThrough) {
1173 | $delegate(method, url, data, callback, headers, timeout, withCredentials);
1174 | } else throw new Error('No response defined !');
1175 | return;
1176 | }
1177 | }
1178 | throw wasExpected ?
1179 | new Error('No response defined !') :
1180 | new Error('Unexpected request: ' + method + ' ' + url + '\n' +
1181 | (expectation ? 'Expected ' + expectation : 'No more request expected'));
1182 | }
1183 |
1184 | /**
1185 | * @ngdoc method
1186 | * @name $httpBackend#when
1187 | * @description
1188 | * Creates a new backend definition.
1189 | *
1190 | * @param {string} method HTTP method.
1191 | * @param {string|RegExp} url HTTP url.
1192 | * @param {(string|RegExp|function(string))=} data HTTP request body or function that receives
1193 | * data string and returns true if the data is as expected.
1194 | * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
1195 | * object and returns true if the headers match the current definition.
1196 | * @returns {requestHandler} Returns an object with a `respond` method that controls how a matched
1197 | * request is handled.
1198 | *
1199 | * - respond –
1200 | * `{function([status,] data[, headers, statusText])
1201 | * | function(function(method, url, data, headers)}`
1202 | * – The respond method takes a set of static data to be returned or a function that can
1203 | * return an array containing response status (number), response data (string), response
1204 | * headers (Object), and the text for the status (string).
1205 | */
1206 | $httpBackend.when = function(method, url, data, headers) {
1207 | var definition = new MockHttpExpectation(method, url, data, headers),
1208 | chain = {
1209 | respond: function(status, data, headers, statusText) {
1210 | definition.response = createResponse(status, data, headers, statusText);
1211 | }
1212 | };
1213 |
1214 | if ($browser) {
1215 | chain.passThrough = function() {
1216 | definition.passThrough = true;
1217 | };
1218 | }
1219 |
1220 | definitions.push(definition);
1221 | return chain;
1222 | };
1223 |
1224 | /**
1225 | * @ngdoc method
1226 | * @name $httpBackend#whenGET
1227 | * @description
1228 | * Creates a new backend definition for GET requests. For more info see `when()`.
1229 | *
1230 | * @param {string|RegExp} url HTTP url.
1231 | * @param {(Object|function(Object))=} headers HTTP headers.
1232 | * @returns {requestHandler} Returns an object with a `respond` method that controls how a matched
1233 | * request is handled.
1234 | */
1235 |
1236 | /**
1237 | * @ngdoc method
1238 | * @name $httpBackend#whenHEAD
1239 | * @description
1240 | * Creates a new backend definition for HEAD requests. For more info see `when()`.
1241 | *
1242 | * @param {string|RegExp} url HTTP url.
1243 | * @param {(Object|function(Object))=} headers HTTP headers.
1244 | * @returns {requestHandler} Returns an object with a `respond` method that controls how a matched
1245 | * request is handled.
1246 | */
1247 |
1248 | /**
1249 | * @ngdoc method
1250 | * @name $httpBackend#whenDELETE
1251 | * @description
1252 | * Creates a new backend definition for DELETE requests. For more info see `when()`.
1253 | *
1254 | * @param {string|RegExp} url HTTP url.
1255 | * @param {(Object|function(Object))=} headers HTTP headers.
1256 | * @returns {requestHandler} Returns an object with a `respond` method that controls how a matched
1257 | * request is handled.
1258 | */
1259 |
1260 | /**
1261 | * @ngdoc method
1262 | * @name $httpBackend#whenPOST
1263 | * @description
1264 | * Creates a new backend definition for POST requests. For more info see `when()`.
1265 | *
1266 | * @param {string|RegExp} url HTTP url.
1267 | * @param {(string|RegExp|function(string))=} data HTTP request body or function that receives
1268 | * data string and returns true if the data is as expected.
1269 | * @param {(Object|function(Object))=} headers HTTP headers.
1270 | * @returns {requestHandler} Returns an object with a `respond` method that controls how a matched
1271 | * request is handled.
1272 | */
1273 |
1274 | /**
1275 | * @ngdoc method
1276 | * @name $httpBackend#whenPUT
1277 | * @description
1278 | * Creates a new backend definition for PUT requests. For more info see `when()`.
1279 | *
1280 | * @param {string|RegExp} url HTTP url.
1281 | * @param {(string|RegExp|function(string))=} data HTTP request body or function that receives
1282 | * data string and returns true if the data is as expected.
1283 | * @param {(Object|function(Object))=} headers HTTP headers.
1284 | * @returns {requestHandler} Returns an object with a `respond` method that controls how a matched
1285 | * request is handled.
1286 | */
1287 |
1288 | /**
1289 | * @ngdoc method
1290 | * @name $httpBackend#whenPATCH
1291 | * @description
1292 | * Creates a new backend definition for PATCH requests. For more info see `when()`.
1293 | *
1294 | * @param {string|RegExp} url HTTP url.
1295 | * @param {(string|RegExp|function(string))=} data HTTP request body or function that receives
1296 | * data string and returns true if the data is as expected.
1297 | * @param {(Object|function(Object))=} headers HTTP headers.
1298 | * @returns {requestHandler} Returns an object with a `respond` method that controls how a matched
1299 | * request is handled.
1300 | */
1301 |
1302 | /**
1303 | * @ngdoc method
1304 | * @name $httpBackend#whenJSONP
1305 | * @description
1306 | * Creates a new backend definition for JSONP requests. For more info see `when()`.
1307 | *
1308 | * @param {string|RegExp} url HTTP url.
1309 | * @returns {requestHandler} Returns an object with a `respond` method that controls how a matched
1310 | * request is handled.
1311 | */
1312 | createShortMethods('when');
1313 |
1314 |
1315 | /**
1316 | * @ngdoc method
1317 | * @name $httpBackend#expect
1318 | * @description
1319 | * Creates a new request expectation.
1320 | *
1321 | * @param {string} method HTTP method.
1322 | * @param {string|RegExp} url HTTP url.
1323 | * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that
1324 | * receives data string and returns true if the data is as expected, or Object if request body
1325 | * is in JSON format.
1326 | * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
1327 | * object and returns true if the headers match the current expectation.
1328 | * @returns {requestHandler} Returns an object with a `respond` method that controls how a matched
1329 | * request is handled.
1330 | *
1331 | * - respond –
1332 | * `{function([status,] data[, headers, statusText])
1333 | * | function(function(method, url, data, headers)}`
1334 | * – The respond method takes a set of static data to be returned or a function that can
1335 | * return an array containing response status (number), response data (string), response
1336 | * headers (Object), and the text for the status (string).
1337 | */
1338 | $httpBackend.expect = function(method, url, data, headers) {
1339 | var expectation = new MockHttpExpectation(method, url, data, headers);
1340 | expectations.push(expectation);
1341 | return {
1342 | respond: function (status, data, headers, statusText) {
1343 | expectation.response = createResponse(status, data, headers, statusText);
1344 | }
1345 | };
1346 | };
1347 |
1348 |
1349 | /**
1350 | * @ngdoc method
1351 | * @name $httpBackend#expectGET
1352 | * @description
1353 | * Creates a new request expectation for GET requests. For more info see `expect()`.
1354 | *
1355 | * @param {string|RegExp} url HTTP url.
1356 | * @param {Object=} headers HTTP headers.
1357 | * @returns {requestHandler} Returns an object with a `respond` method that controls how a matched
1358 | * request is handled. See #expect for more info.
1359 | */
1360 |
1361 | /**
1362 | * @ngdoc method
1363 | * @name $httpBackend#expectHEAD
1364 | * @description
1365 | * Creates a new request expectation for HEAD requests. For more info see `expect()`.
1366 | *
1367 | * @param {string|RegExp} url HTTP url.
1368 | * @param {Object=} headers HTTP headers.
1369 | * @returns {requestHandler} Returns an object with a `respond` method that controls how a matched
1370 | * request is handled.
1371 | */
1372 |
1373 | /**
1374 | * @ngdoc method
1375 | * @name $httpBackend#expectDELETE
1376 | * @description
1377 | * Creates a new request expectation for DELETE requests. For more info see `expect()`.
1378 | *
1379 | * @param {string|RegExp} url HTTP url.
1380 | * @param {Object=} headers HTTP headers.
1381 | * @returns {requestHandler} Returns an object with a `respond` method that controls how a matched
1382 | * request is handled.
1383 | */
1384 |
1385 | /**
1386 | * @ngdoc method
1387 | * @name $httpBackend#expectPOST
1388 | * @description
1389 | * Creates a new request expectation for POST requests. For more info see `expect()`.
1390 | *
1391 | * @param {string|RegExp} url HTTP url.
1392 | * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that
1393 | * receives data string and returns true if the data is as expected, or Object if request body
1394 | * is in JSON format.
1395 | * @param {Object=} headers HTTP headers.
1396 | * @returns {requestHandler} Returns an object with a `respond` method that controls how a matched
1397 | * request is handled.
1398 | */
1399 |
1400 | /**
1401 | * @ngdoc method
1402 | * @name $httpBackend#expectPUT
1403 | * @description
1404 | * Creates a new request expectation for PUT requests. For more info see `expect()`.
1405 | *
1406 | * @param {string|RegExp} url HTTP url.
1407 | * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that
1408 | * receives data string and returns true if the data is as expected, or Object if request body
1409 | * is in JSON format.
1410 | * @param {Object=} headers HTTP headers.
1411 | * @returns {requestHandler} Returns an object with a `respond` method that controls how a matched
1412 | * request is handled.
1413 | */
1414 |
1415 | /**
1416 | * @ngdoc method
1417 | * @name $httpBackend#expectPATCH
1418 | * @description
1419 | * Creates a new request expectation for PATCH requests. For more info see `expect()`.
1420 | *
1421 | * @param {string|RegExp} url HTTP url.
1422 | * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that
1423 | * receives data string and returns true if the data is as expected, or Object if request body
1424 | * is in JSON format.
1425 | * @param {Object=} headers HTTP headers.
1426 | * @returns {requestHandler} Returns an object with a `respond` method that controls how a matched
1427 | * request is handled.
1428 | */
1429 |
1430 | /**
1431 | * @ngdoc method
1432 | * @name $httpBackend#expectJSONP
1433 | * @description
1434 | * Creates a new request expectation for JSONP requests. For more info see `expect()`.
1435 | *
1436 | * @param {string|RegExp} url HTTP url.
1437 | * @returns {requestHandler} Returns an object with a `respond` method that controls how a matched
1438 | * request is handled.
1439 | */
1440 | createShortMethods('expect');
1441 |
1442 |
1443 | /**
1444 | * @ngdoc method
1445 | * @name $httpBackend#flush
1446 | * @description
1447 | * Flushes all pending requests using the trained responses.
1448 | *
1449 | * @param {number=} count Number of responses to flush (in the order they arrived). If undefined,
1450 | * all pending requests will be flushed. If there are no pending requests when the flush method
1451 | * is called an exception is thrown (as this typically a sign of programming error).
1452 | */
1453 | $httpBackend.flush = function(count) {
1454 | $rootScope.$digest();
1455 | if (!responses.length) throw new Error('No pending request to flush !');
1456 |
1457 | if (angular.isDefined(count)) {
1458 | while (count--) {
1459 | if (!responses.length) throw new Error('No more pending request to flush !');
1460 | responses.shift()();
1461 | }
1462 | } else {
1463 | while (responses.length) {
1464 | responses.shift()();
1465 | }
1466 | }
1467 | $httpBackend.verifyNoOutstandingExpectation();
1468 | };
1469 |
1470 |
1471 | /**
1472 | * @ngdoc method
1473 | * @name $httpBackend#verifyNoOutstandingExpectation
1474 | * @description
1475 | * Verifies that all of the requests defined via the `expect` api were made. If any of the
1476 | * requests were not made, verifyNoOutstandingExpectation throws an exception.
1477 | *
1478 | * Typically, you would call this method following each test case that asserts requests using an
1479 | * "afterEach" clause.
1480 | *
1481 | * ```js
1482 | * afterEach($httpBackend.verifyNoOutstandingExpectation);
1483 | * ```
1484 | */
1485 | $httpBackend.verifyNoOutstandingExpectation = function() {
1486 | $rootScope.$digest();
1487 | if (expectations.length) {
1488 | throw new Error('Unsatisfied requests: ' + expectations.join(', '));
1489 | }
1490 | };
1491 |
1492 |
1493 | /**
1494 | * @ngdoc method
1495 | * @name $httpBackend#verifyNoOutstandingRequest
1496 | * @description
1497 | * Verifies that there are no outstanding requests that need to be flushed.
1498 | *
1499 | * Typically, you would call this method following each test case that asserts requests using an
1500 | * "afterEach" clause.
1501 | *
1502 | * ```js
1503 | * afterEach($httpBackend.verifyNoOutstandingRequest);
1504 | * ```
1505 | */
1506 | $httpBackend.verifyNoOutstandingRequest = function() {
1507 | if (responses.length) {
1508 | throw new Error('Unflushed requests: ' + responses.length);
1509 | }
1510 | };
1511 |
1512 |
1513 | /**
1514 | * @ngdoc method
1515 | * @name $httpBackend#resetExpectations
1516 | * @description
1517 | * Resets all request expectations, but preserves all backend definitions. Typically, you would
1518 | * call resetExpectations during a multiple-phase test when you want to reuse the same instance of
1519 | * $httpBackend mock.
1520 | */
1521 | $httpBackend.resetExpectations = function() {
1522 | expectations.length = 0;
1523 | responses.length = 0;
1524 | };
1525 |
1526 | return $httpBackend;
1527 |
1528 |
1529 | function createShortMethods(prefix) {
1530 | angular.forEach(['GET', 'DELETE', 'JSONP', 'HEAD'], function(method) {
1531 | $httpBackend[prefix + method] = function(url, headers) {
1532 | return $httpBackend[prefix](method, url, undefined, headers);
1533 | };
1534 | });
1535 |
1536 | angular.forEach(['PUT', 'POST', 'PATCH'], function(method) {
1537 | $httpBackend[prefix + method] = function(url, data, headers) {
1538 | return $httpBackend[prefix](method, url, data, headers);
1539 | };
1540 | });
1541 | }
1542 | }
1543 |
1544 | function MockHttpExpectation(method, url, data, headers) {
1545 |
1546 | this.data = data;
1547 | this.headers = headers;
1548 |
1549 | this.match = function(m, u, d, h) {
1550 | if (method != m) return false;
1551 | if (!this.matchUrl(u)) return false;
1552 | if (angular.isDefined(d) && !this.matchData(d)) return false;
1553 | if (angular.isDefined(h) && !this.matchHeaders(h)) return false;
1554 | return true;
1555 | };
1556 |
1557 | this.matchUrl = function(u) {
1558 | if (!url) return true;
1559 | if (angular.isFunction(url.test)) return url.test(u);
1560 | return url == u;
1561 | };
1562 |
1563 | this.matchHeaders = function(h) {
1564 | if (angular.isUndefined(headers)) return true;
1565 | if (angular.isFunction(headers)) return headers(h);
1566 | return angular.equals(headers, h);
1567 | };
1568 |
1569 | this.matchData = function(d) {
1570 | if (angular.isUndefined(data)) return true;
1571 | if (data && angular.isFunction(data.test)) return data.test(d);
1572 | if (data && angular.isFunction(data)) return data(d);
1573 | if (data && !angular.isString(data)) {
1574 | return angular.equals(angular.fromJson(angular.toJson(data)), angular.fromJson(d));
1575 | }
1576 | return data == d;
1577 | };
1578 |
1579 | this.toString = function() {
1580 | return method + ' ' + url;
1581 | };
1582 | }
1583 |
1584 | function createMockXhr() {
1585 | return new MockXhr();
1586 | }
1587 |
1588 | function MockXhr() {
1589 |
1590 | // hack for testing $http, $httpBackend
1591 | MockXhr.$$lastInstance = this;
1592 |
1593 | this.open = function(method, url, async) {
1594 | this.$$method = method;
1595 | this.$$url = url;
1596 | this.$$async = async;
1597 | this.$$reqHeaders = {};
1598 | this.$$respHeaders = {};
1599 | };
1600 |
1601 | this.send = function(data) {
1602 | this.$$data = data;
1603 | };
1604 |
1605 | this.setRequestHeader = function(key, value) {
1606 | this.$$reqHeaders[key] = value;
1607 | };
1608 |
1609 | this.getResponseHeader = function(name) {
1610 | // the lookup must be case insensitive,
1611 | // that's why we try two quick lookups first and full scan last
1612 | var header = this.$$respHeaders[name];
1613 | if (header) return header;
1614 |
1615 | name = angular.lowercase(name);
1616 | header = this.$$respHeaders[name];
1617 | if (header) return header;
1618 |
1619 | header = undefined;
1620 | angular.forEach(this.$$respHeaders, function(headerVal, headerName) {
1621 | if (!header && angular.lowercase(headerName) == name) header = headerVal;
1622 | });
1623 | return header;
1624 | };
1625 |
1626 | this.getAllResponseHeaders = function() {
1627 | var lines = [];
1628 |
1629 | angular.forEach(this.$$respHeaders, function(value, key) {
1630 | lines.push(key + ': ' + value);
1631 | });
1632 | return lines.join('\n');
1633 | };
1634 |
1635 | this.abort = angular.noop;
1636 | }
1637 |
1638 |
1639 | /**
1640 | * @ngdoc service
1641 | * @name $timeout
1642 | * @description
1643 | *
1644 | * This service is just a simple decorator for {@link ng.$timeout $timeout} service
1645 | * that adds a "flush" and "verifyNoPendingTasks" methods.
1646 | */
1647 |
1648 | angular.mock.$TimeoutDecorator = function($delegate, $browser) {
1649 |
1650 | /**
1651 | * @ngdoc method
1652 | * @name $timeout#flush
1653 | * @description
1654 | *
1655 | * Flushes the queue of pending tasks.
1656 | *
1657 | * @param {number=} delay maximum timeout amount to flush up until
1658 | */
1659 | $delegate.flush = function(delay) {
1660 | $browser.defer.flush(delay);
1661 | };
1662 |
1663 | /**
1664 | * @ngdoc method
1665 | * @name $timeout#verifyNoPendingTasks
1666 | * @description
1667 | *
1668 | * Verifies that there are no pending tasks that need to be flushed.
1669 | */
1670 | $delegate.verifyNoPendingTasks = function() {
1671 | if ($browser.deferredFns.length) {
1672 | throw new Error('Deferred tasks to flush (' + $browser.deferredFns.length + '): ' +
1673 | formatPendingTasksAsString($browser.deferredFns));
1674 | }
1675 | };
1676 |
1677 | function formatPendingTasksAsString(tasks) {
1678 | var result = [];
1679 | angular.forEach(tasks, function(task) {
1680 | result.push('{id: ' + task.id + ', ' + 'time: ' + task.time + '}');
1681 | });
1682 |
1683 | return result.join(', ');
1684 | }
1685 |
1686 | return $delegate;
1687 | };
1688 |
1689 | angular.mock.$RAFDecorator = function($delegate) {
1690 | var queue = [];
1691 | var rafFn = function(fn) {
1692 | var index = queue.length;
1693 | queue.push(fn);
1694 | return function() {
1695 | queue.splice(index, 1);
1696 | };
1697 | };
1698 |
1699 | rafFn.supported = $delegate.supported;
1700 |
1701 | rafFn.flush = function() {
1702 | if(queue.length === 0) {
1703 | throw new Error('No rAF callbacks present');
1704 | }
1705 |
1706 | var length = queue.length;
1707 | for(var i=0;i');
1737 | };
1738 | };
1739 |
1740 | /**
1741 | * @ngdoc module
1742 | * @name ngMock
1743 | * @packageName angular-mocks
1744 | * @description
1745 | *
1746 | * # ngMock
1747 | *
1748 | * The `ngMock` module provides support to inject and mock Angular services into unit tests.
1749 | * In addition, ngMock also extends various core ng services such that they can be
1750 | * inspected and controlled in a synchronous manner within test code.
1751 | *
1752 | *
1753 | *
1754 | *
1755 | */
1756 | angular.module('ngMock', ['ng']).provider({
1757 | $browser: angular.mock.$BrowserProvider,
1758 | $exceptionHandler: angular.mock.$ExceptionHandlerProvider,
1759 | $log: angular.mock.$LogProvider,
1760 | $interval: angular.mock.$IntervalProvider,
1761 | $httpBackend: angular.mock.$HttpBackendProvider,
1762 | $rootElement: angular.mock.$RootElementProvider
1763 | }).config(['$provide', function($provide) {
1764 | $provide.decorator('$timeout', angular.mock.$TimeoutDecorator);
1765 | $provide.decorator('$$rAF', angular.mock.$RAFDecorator);
1766 | $provide.decorator('$$asyncCallback', angular.mock.$AsyncCallbackDecorator);
1767 | }]);
1768 |
1769 | /**
1770 | * @ngdoc module
1771 | * @name ngMockE2E
1772 | * @module ngMockE2E
1773 | * @packageName angular-mocks
1774 | * @description
1775 | *
1776 | * The `ngMockE2E` is an angular module which contains mocks suitable for end-to-end testing.
1777 | * Currently there is only one mock present in this module -
1778 | * the {@link ngMockE2E.$httpBackend e2e $httpBackend} mock.
1779 | */
1780 | angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
1781 | $provide.decorator('$httpBackend', angular.mock.e2e.$httpBackendDecorator);
1782 | }]);
1783 |
1784 | /**
1785 | * @ngdoc service
1786 | * @name $httpBackend
1787 | * @module ngMockE2E
1788 | * @description
1789 | * Fake HTTP backend implementation suitable for end-to-end testing or backend-less development of
1790 | * applications that use the {@link ng.$http $http service}.
1791 | *
1792 | * *Note*: For fake http backend implementation suitable for unit testing please see
1793 | * {@link ngMock.$httpBackend unit-testing $httpBackend mock}.
1794 | *
1795 | * This implementation can be used to respond with static or dynamic responses via the `when` api
1796 | * and its shortcuts (`whenGET`, `whenPOST`, etc) and optionally pass through requests to the
1797 | * real $httpBackend for specific requests (e.g. to interact with certain remote apis or to fetch
1798 | * templates from a webserver).
1799 | *
1800 | * As opposed to unit-testing, in an end-to-end testing scenario or in scenario when an application
1801 | * is being developed with the real backend api replaced with a mock, it is often desirable for
1802 | * certain category of requests to bypass the mock and issue a real http request (e.g. to fetch
1803 | * templates or static files from the webserver). To configure the backend with this behavior
1804 | * use the `passThrough` request handler of `when` instead of `respond`.
1805 | *
1806 | * Additionally, we don't want to manually have to flush mocked out requests like we do during unit
1807 | * testing. For this reason the e2e $httpBackend flushes mocked out requests
1808 | * automatically, closely simulating the behavior of the XMLHttpRequest object.
1809 | *
1810 | * To setup the application to run with this http backend, you have to create a module that depends
1811 | * on the `ngMockE2E` and your application modules and defines the fake backend:
1812 | *
1813 | * ```js
1814 | * myAppDev = angular.module('myAppDev', ['myApp', 'ngMockE2E']);
1815 | * myAppDev.run(function($httpBackend) {
1816 | * phones = [{name: 'phone1'}, {name: 'phone2'}];
1817 | *
1818 | * // returns the current list of phones
1819 | * $httpBackend.whenGET('/phones').respond(phones);
1820 | *
1821 | * // adds a new phone to the phones array
1822 | * $httpBackend.whenPOST('/phones').respond(function(method, url, data) {
1823 | * var phone = angular.fromJson(data);
1824 | * phones.push(phone);
1825 | * return [200, phone, {}];
1826 | * });
1827 | * $httpBackend.whenGET(/^\/templates\//).passThrough();
1828 | * //...
1829 | * });
1830 | * ```
1831 | *
1832 | * Afterwards, bootstrap your app with this new module.
1833 | */
1834 |
1835 | /**
1836 | * @ngdoc method
1837 | * @name $httpBackend#when
1838 | * @module ngMockE2E
1839 | * @description
1840 | * Creates a new backend definition.
1841 | *
1842 | * @param {string} method HTTP method.
1843 | * @param {string|RegExp} url HTTP url.
1844 | * @param {(string|RegExp)=} data HTTP request body.
1845 | * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
1846 | * object and returns true if the headers match the current definition.
1847 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
1848 | * control how a matched request is handled.
1849 | *
1850 | * - respond –
1851 | * `{function([status,] data[, headers, statusText])
1852 | * | function(function(method, url, data, headers)}`
1853 | * – The respond method takes a set of static data to be returned or a function that can return
1854 | * an array containing response status (number), response data (string), response headers
1855 | * (Object), and the text for the status (string).
1856 | * - passThrough – `{function()}` – Any request matching a backend definition with
1857 | * `passThrough` handler will be passed through to the real backend (an XHR request will be made
1858 | * to the server.)
1859 | */
1860 |
1861 | /**
1862 | * @ngdoc method
1863 | * @name $httpBackend#whenGET
1864 | * @module ngMockE2E
1865 | * @description
1866 | * Creates a new backend definition for GET requests. For more info see `when()`.
1867 | *
1868 | * @param {string|RegExp} url HTTP url.
1869 | * @param {(Object|function(Object))=} headers HTTP headers.
1870 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
1871 | * control how a matched request is handled.
1872 | */
1873 |
1874 | /**
1875 | * @ngdoc method
1876 | * @name $httpBackend#whenHEAD
1877 | * @module ngMockE2E
1878 | * @description
1879 | * Creates a new backend definition for HEAD requests. For more info see `when()`.
1880 | *
1881 | * @param {string|RegExp} url HTTP url.
1882 | * @param {(Object|function(Object))=} headers HTTP headers.
1883 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
1884 | * control how a matched request is handled.
1885 | */
1886 |
1887 | /**
1888 | * @ngdoc method
1889 | * @name $httpBackend#whenDELETE
1890 | * @module ngMockE2E
1891 | * @description
1892 | * Creates a new backend definition for DELETE requests. For more info see `when()`.
1893 | *
1894 | * @param {string|RegExp} url HTTP url.
1895 | * @param {(Object|function(Object))=} headers HTTP headers.
1896 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
1897 | * control how a matched request is handled.
1898 | */
1899 |
1900 | /**
1901 | * @ngdoc method
1902 | * @name $httpBackend#whenPOST
1903 | * @module ngMockE2E
1904 | * @description
1905 | * Creates a new backend definition for POST requests. For more info see `when()`.
1906 | *
1907 | * @param {string|RegExp} url HTTP url.
1908 | * @param {(string|RegExp)=} data HTTP request body.
1909 | * @param {(Object|function(Object))=} headers HTTP headers.
1910 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
1911 | * control how a matched request is handled.
1912 | */
1913 |
1914 | /**
1915 | * @ngdoc method
1916 | * @name $httpBackend#whenPUT
1917 | * @module ngMockE2E
1918 | * @description
1919 | * Creates a new backend definition for PUT requests. For more info see `when()`.
1920 | *
1921 | * @param {string|RegExp} url HTTP url.
1922 | * @param {(string|RegExp)=} data HTTP request body.
1923 | * @param {(Object|function(Object))=} headers HTTP headers.
1924 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
1925 | * control how a matched request is handled.
1926 | */
1927 |
1928 | /**
1929 | * @ngdoc method
1930 | * @name $httpBackend#whenPATCH
1931 | * @module ngMockE2E
1932 | * @description
1933 | * Creates a new backend definition for PATCH requests. For more info see `when()`.
1934 | *
1935 | * @param {string|RegExp} url HTTP url.
1936 | * @param {(string|RegExp)=} data HTTP request body.
1937 | * @param {(Object|function(Object))=} headers HTTP headers.
1938 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
1939 | * control how a matched request is handled.
1940 | */
1941 |
1942 | /**
1943 | * @ngdoc method
1944 | * @name $httpBackend#whenJSONP
1945 | * @module ngMockE2E
1946 | * @description
1947 | * Creates a new backend definition for JSONP requests. For more info see `when()`.
1948 | *
1949 | * @param {string|RegExp} url HTTP url.
1950 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
1951 | * control how a matched request is handled.
1952 | */
1953 | angular.mock.e2e = {};
1954 | angular.mock.e2e.$httpBackendDecorator =
1955 | ['$rootScope', '$delegate', '$browser', createHttpBackendMock];
1956 |
1957 |
1958 | angular.mock.clearDataCache = function() {
1959 | var key,
1960 | cache = angular.element.cache;
1961 |
1962 | for(key in cache) {
1963 | if (Object.prototype.hasOwnProperty.call(cache,key)) {
1964 | var handle = cache[key].handle;
1965 |
1966 | handle && angular.element(handle.elem).off();
1967 | delete cache[key];
1968 | }
1969 | }
1970 | };
1971 |
1972 |
1973 | if(window.jasmine || window.mocha) {
1974 |
1975 | var currentSpec = null,
1976 | isSpecRunning = function() {
1977 | return !!currentSpec;
1978 | };
1979 |
1980 |
1981 | (window.beforeEach || window.setup)(function() {
1982 | currentSpec = this;
1983 | });
1984 |
1985 | (window.afterEach || window.teardown)(function() {
1986 | var injector = currentSpec.$injector;
1987 |
1988 | angular.forEach(currentSpec.$modules, function(module) {
1989 | if (module && module.$$hashKey) {
1990 | module.$$hashKey = undefined;
1991 | }
1992 | });
1993 |
1994 | currentSpec.$injector = null;
1995 | currentSpec.$modules = null;
1996 | currentSpec = null;
1997 |
1998 | if (injector) {
1999 | injector.get('$rootElement').off();
2000 | injector.get('$browser').pollFns.length = 0;
2001 | }
2002 |
2003 | angular.mock.clearDataCache();
2004 |
2005 | // clean up jquery's fragment cache
2006 | angular.forEach(angular.element.fragments, function(val, key) {
2007 | delete angular.element.fragments[key];
2008 | });
2009 |
2010 | MockXhr.$$lastInstance = null;
2011 |
2012 | angular.forEach(angular.callbacks, function(val, key) {
2013 | delete angular.callbacks[key];
2014 | });
2015 | angular.callbacks.counter = 0;
2016 | });
2017 |
2018 | /**
2019 | * @ngdoc function
2020 | * @name angular.mock.module
2021 | * @description
2022 | *
2023 | * *NOTE*: This function is also published on window for easy access.
2024 | * *NOTE*: This function is declared ONLY WHEN running tests with jasmine or mocha
2025 | *
2026 | * This function registers a module configuration code. It collects the configuration information
2027 | * which will be used when the injector is created by {@link angular.mock.inject inject}.
2028 | *
2029 | * See {@link angular.mock.inject inject} for usage example
2030 | *
2031 | * @param {...(string|Function|Object)} fns any number of modules which are represented as string
2032 | * aliases or as anonymous module initialization functions. The modules are used to
2033 | * configure the injector. The 'ng' and 'ngMock' modules are automatically loaded. If an
2034 | * object literal is passed they will be registered as values in the module, the key being
2035 | * the module name and the value being what is returned.
2036 | */
2037 | window.module = angular.mock.module = function() {
2038 | var moduleFns = Array.prototype.slice.call(arguments, 0);
2039 | return isSpecRunning() ? workFn() : workFn;
2040 | /////////////////////
2041 | function workFn() {
2042 | if (currentSpec.$injector) {
2043 | throw new Error('Injector already created, can not register a module!');
2044 | } else {
2045 | var modules = currentSpec.$modules || (currentSpec.$modules = []);
2046 | angular.forEach(moduleFns, function(module) {
2047 | if (angular.isObject(module) && !angular.isArray(module)) {
2048 | modules.push(function($provide) {
2049 | angular.forEach(module, function(value, key) {
2050 | $provide.value(key, value);
2051 | });
2052 | });
2053 | } else {
2054 | modules.push(module);
2055 | }
2056 | });
2057 | }
2058 | }
2059 | };
2060 |
2061 | /**
2062 | * @ngdoc function
2063 | * @name angular.mock.inject
2064 | * @description
2065 | *
2066 | * *NOTE*: This function is also published on window for easy access.
2067 | * *NOTE*: This function is declared ONLY WHEN running tests with jasmine or mocha
2068 | *
2069 | * The inject function wraps a function into an injectable function. The inject() creates new
2070 | * instance of {@link auto.$injector $injector} per test, which is then used for
2071 | * resolving references.
2072 | *
2073 | *
2074 | * ## Resolving References (Underscore Wrapping)
2075 | * Often, we would like to inject a reference once, in a `beforeEach()` block and reuse this
2076 | * in multiple `it()` clauses. To be able to do this we must assign the reference to a variable
2077 | * that is declared in the scope of the `describe()` block. Since we would, most likely, want
2078 | * the variable to have the same name of the reference we have a problem, since the parameter
2079 | * to the `inject()` function would hide the outer variable.
2080 | *
2081 | * To help with this, the injected parameters can, optionally, be enclosed with underscores.
2082 | * These are ignored by the injector when the reference name is resolved.
2083 | *
2084 | * For example, the parameter `_myService_` would be resolved as the reference `myService`.
2085 | * Since it is available in the function body as _myService_, we can then assign it to a variable
2086 | * defined in an outer scope.
2087 | *
2088 | * ```
2089 | * // Defined out reference variable outside
2090 | * var myService;
2091 | *
2092 | * // Wrap the parameter in underscores
2093 | * beforeEach( inject( function(_myService_){
2094 | * myService = _myService_;
2095 | * }));
2096 | *
2097 | * // Use myService in a series of tests.
2098 | * it('makes use of myService', function() {
2099 | * myService.doStuff();
2100 | * });
2101 | *
2102 | * ```
2103 | *
2104 | * See also {@link angular.mock.module angular.mock.module}
2105 | *
2106 | * ## Example
2107 | * Example of what a typical jasmine tests looks like with the inject method.
2108 | * ```js
2109 | *
2110 | * angular.module('myApplicationModule', [])
2111 | * .value('mode', 'app')
2112 | * .value('version', 'v1.0.1');
2113 | *
2114 | *
2115 | * describe('MyApp', function() {
2116 | *
2117 | * // You need to load modules that you want to test,
2118 | * // it loads only the "ng" module by default.
2119 | * beforeEach(module('myApplicationModule'));
2120 | *
2121 | *
2122 | * // inject() is used to inject arguments of all given functions
2123 | * it('should provide a version', inject(function(mode, version) {
2124 | * expect(version).toEqual('v1.0.1');
2125 | * expect(mode).toEqual('app');
2126 | * }));
2127 | *
2128 | *
2129 | * // The inject and module method can also be used inside of the it or beforeEach
2130 | * it('should override a version and test the new version is injected', function() {
2131 | * // module() takes functions or strings (module aliases)
2132 | * module(function($provide) {
2133 | * $provide.value('version', 'overridden'); // override version here
2134 | * });
2135 | *
2136 | * inject(function(version) {
2137 | * expect(version).toEqual('overridden');
2138 | * });
2139 | * });
2140 | * });
2141 | *
2142 | * ```
2143 | *
2144 | * @param {...Function} fns any number of functions which will be injected using the injector.
2145 | */
2146 |
2147 |
2148 |
2149 | var ErrorAddingDeclarationLocationStack = function(e, errorForStack) {
2150 | this.message = e.message;
2151 | this.name = e.name;
2152 | if (e.line) this.line = e.line;
2153 | if (e.sourceId) this.sourceId = e.sourceId;
2154 | if (e.stack && errorForStack)
2155 | this.stack = e.stack + '\n' + errorForStack.stack;
2156 | if (e.stackArray) this.stackArray = e.stackArray;
2157 | };
2158 | ErrorAddingDeclarationLocationStack.prototype.toString = Error.prototype.toString;
2159 |
2160 | window.inject = angular.mock.inject = function() {
2161 | var blockFns = Array.prototype.slice.call(arguments, 0);
2162 | var errorForStack = new Error('Declaration Location');
2163 | return isSpecRunning() ? workFn.call(currentSpec) : workFn;
2164 | /////////////////////
2165 | function workFn() {
2166 | var modules = currentSpec.$modules || [];
2167 |
2168 | modules.unshift('ngMock');
2169 | modules.unshift('ng');
2170 | var injector = currentSpec.$injector;
2171 | if (!injector) {
2172 | injector = currentSpec.$injector = angular.injector(modules);
2173 | }
2174 | for(var i = 0, ii = blockFns.length; i < ii; i++) {
2175 | try {
2176 | /* jshint -W040 *//* Jasmine explicitly provides a `this` object when calling functions */
2177 | injector.invoke(blockFns[i] || angular.noop, this);
2178 | /* jshint +W040 */
2179 | } catch (e) {
2180 | if (e.stack && errorForStack) {
2181 | throw new ErrorAddingDeclarationLocationStack(e, errorForStack);
2182 | }
2183 | throw e;
2184 | } finally {
2185 | errorForStack = null;
2186 | }
2187 | }
2188 | }
2189 | };
2190 | }
2191 |
2192 |
2193 | })(window, window.angular);
2194 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-lazy-bootstarp",
3 | "description": "Bootstarp Angular app after a few resolves",
4 | "version": "1.0.0",
5 | "private": true,
6 | "server": {
7 | "host": "localhost",
8 | "port": "5050"
9 | },
10 | "devDependencies": {
11 |
12 | "grunt": "~0.4.5",
13 | "grunt-cli": "~0.1.13",
14 | "grunt-contrib-jshint": "~0.10.0",
15 | "grunt-contrib-uglify": "~0.6.0",
16 | "grunt-express": "~1.2.1",
17 |
18 | "karma": "~0.12.19",
19 | "grunt-karma": "~0.8.3",
20 | "karma-phantomjs-launcher": "~0.1.4",
21 | "karma-jasmine": "~0.1.5",
22 | "karma-coverage": "~0.2.5"
23 |
24 | },
25 | "dependencies": {
26 | "requirejs": "*",
27 | "express": "3.4.8"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/bootstrap.js:
--------------------------------------------------------------------------------
1 | (function (angular) {
2 |
3 | 'use strict';
4 |
5 | //Generic
6 |
7 | function makeArray(arr) {
8 | if(!arr){
9 | return [];
10 | }
11 | return angular.isArray(arr) ? arr : [arr];
12 | }
13 |
14 | //Angular
15 |
16 | function provideRootElement(modules, element) {
17 | element = angular.element(element);
18 | modules.unshift(['$provide',
19 | function ($provide) {
20 | $provide.value('$rootElement', element);
21 | }]);
22 | }
23 |
24 | function createInjector(injectorModules, element) {
25 | var modules = ['ng'].concat(makeArray(injectorModules));
26 | if (element) {
27 | provideRootElement(modules, element);
28 | }
29 | return angular.injector(modules);
30 | }
31 |
32 | function bootstrapApplication(angularApp) {
33 | angular.element(document).ready(function () {
34 | angular.bootstrap(document, [angularApp]);
35 | });
36 | }
37 |
38 | angular.lazy = function (app, modules) {
39 |
40 | var injector = createInjector(modules),
41 | $q = injector.get('$q'),
42 | promises = [],
43 | errorCallback = angular.noop,
44 | loadingCallback = angular.noop,
45 | doneCallback = angular.noop;
46 |
47 | return {
48 |
49 | resolve: function (promise) {
50 | promise = $q.when(injector.instantiate(promise));
51 | promises.push(promise);
52 | return this;
53 | },
54 |
55 | bootstrap: function () {
56 |
57 | loadingCallback();
58 |
59 | return $q.all(promises)
60 | .then(function () {
61 | bootstrapApplication(app);
62 | }, errorCallback)
63 | .finally(doneCallback);
64 | },
65 |
66 | loading: function(callback){
67 | loadingCallback = callback;
68 | return this;
69 | },
70 |
71 | done: function(callback){
72 | doneCallback = callback;
73 | return this;
74 | },
75 |
76 | error: function(callback){
77 | errorCallback = callback;
78 | return this;
79 | }
80 | };
81 |
82 | };
83 |
84 | })(angular);
--------------------------------------------------------------------------------
/test/bootstrap.spec.js:
--------------------------------------------------------------------------------
1 | describe('lazyBootstrap', function () {
2 |
3 | it('should provide lazy bootstrap function', function () {
4 | expect(angular.lazy).toEqual(jasmine.any(Function));
5 | });
6 |
7 | it('should provide bootstrap function chaining', function () {
8 |
9 | function check(link){
10 | expect(link.resolve).toEqual(jasmine.any(Function));
11 | expect(link.error).toEqual(jasmine.any(Function));
12 | expect(link.done).toEqual(jasmine.any(Function));
13 | expect(link.loading).toEqual(jasmine.any(Function));
14 | expect(link.bootstrap).toEqual(jasmine.any(Function));
15 | }
16 |
17 | var link = angular.lazy('demoApp');
18 | check(link);
19 |
20 | link = link.resolve(['$timeout', '$q', function($timeout, $q){
21 | var deferred = $q.defer();
22 | $timeout(function () {
23 | deferred.resolve('VALUE');
24 | }, 2000);
25 | return deferred.promise;
26 | }]);
27 | check(link);
28 |
29 | link = link.error();
30 | check(link);
31 |
32 | link = link.done();
33 | check(link);
34 |
35 | link = link.loading();
36 | check(link);
37 |
38 | });
39 |
40 | it('should bootstrap the application', function(done){
41 |
42 | window.appConfig = {};
43 |
44 | angular.lazy('demoApp')
45 | .resolve(['$q', '$timeout', function ($q, $timeout) {
46 | var deferred = $q.defer();
47 | $timeout(function () {
48 | window.appConfig.const = 'My Constant';
49 | deferred.resolve();
50 | }, 2000);
51 | return deferred.promise;
52 | }])
53 | .bootstrap();
54 |
55 | angular.module('demoApp', [])
56 | .config(function () {
57 | expect(window.appConfig).toEqual({const: 'My Constant'});
58 | done();
59 | });
60 |
61 | });
62 |
63 | it('should bootstrap the application with various hooks', function(done){
64 |
65 | window.app = {};
66 |
67 | angular.lazy('demoApp')
68 | .resolve(['$q', '$timeout', function ($q, $timeout) {
69 | var deferred = $q.defer();
70 | $timeout(function () {
71 | window.app.const = 'My Constant';
72 | deferred.resolve();
73 | }, 2000);
74 | return deferred.promise;
75 | }])
76 | .loading(function(){
77 | expect(window.app.const).not.toBeDefined();
78 | })
79 | .done(function() {
80 | expect(window.app.const).toEqual('My Constant');
81 | })
82 | .bootstrap();
83 |
84 | angular.module('demoApp', [])
85 | .config(function () {
86 | expect(window.appConfig).toEqual({const: 'My Constant'});
87 | done();
88 | });
89 |
90 | });
91 |
92 |
93 | });
--------------------------------------------------------------------------------
/webserver.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Web Server
3 | * MIT Licensed
4 | */
5 |
6 | /**
7 | * Module dependencies.
8 | */
9 |
10 | var express = require('express'),
11 | info = require("./package.json");
12 |
13 | //Setup Express
14 | var app = express();
15 |
16 | console.log('Node Server Started!');
17 | console.log('Running Version ' + info.version);
18 |
19 | //Static files can be served with express' static middleware.
20 | //I usually make a directory for all static files to be served from the "public" folder.
21 | app.use('/', express.static(__dirname + '/', { maxAge: 86400000 /*one-day*/ }));
22 |
23 | app.configure(function(){
24 |
25 | //The order of which middleware are "defined" using app.use() is very important,
26 | // they are invoked sequentially, thus this defines middleware precedence.
27 |
28 | app.use(express.logger({ format: 'dev' }));
29 |
30 | app.use(express.cookieParser());
31 | app.use(express.methodOverride());
32 | app.use(express.compress());
33 | app.use(express.responseTime());
34 |
35 | //Handle favicon
36 | app.use(express.favicon());
37 |
38 | //Log Errors
39 | app.use(function (err, req, res, next) {
40 | console.error(err.stack);
41 | next(err);
42 | });
43 |
44 | });
45 |
46 |
47 | //PROCESS
48 | process.on('uncaughtException', function (err) {
49 | console.log('PROCESS: Caught exception: ' + err);
50 | });
51 |
52 | process.on('exit', function() {
53 | console.log('PROCESS: !!!!!EXITED!!!!');
54 | });
55 |
56 |
57 | //Kick-Start the server
58 |
59 | app.listen(
60 | parseInt(info.server.port) || 5050, //PORT
61 | info.server.host || "localhost" //HOSTNAME
62 | );
63 |
64 | console.log(['Server running at http://', info.server.host, ':', info.server.port, '/'].join(""));
--------------------------------------------------------------------------------