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