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