├── .gitignore
├── .travis.yml
├── conf
├── karma.conf.js
└── karma-common.js
├── package.json
├── gulpFile.js
├── lrCloudinary.min.js
├── test
└── lrCloudinary-spec.js
├── readme.md
├── lrCloudinary.js
└── lib
└── angular-mocks.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | node_modules
3 | bower_components
4 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "0.10"
4 |
5 | before_script:
6 | - npm install
7 |
8 | script:
9 | - npm test
--------------------------------------------------------------------------------
/conf/karma.conf.js:
--------------------------------------------------------------------------------
1 | module.exports = function (config) {
2 | var conf = require('./karma-common.js');
3 | conf.basePath = '../';
4 | config.set(conf);
5 | };
--------------------------------------------------------------------------------
/conf/karma-common.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | files: [
3 | 'lib/angular.js',
4 | 'lib/angular-mocks.js',
5 | 'lrCloudinary.js',
6 | 'test/*.js'
7 | ],
8 | frameworks: ['jasmine'],
9 | browsers: ['Chrome'],
10 |
11 | port: 9876
12 | };
13 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "lrCloudinary",
3 | "version": "0.1.0",
4 | "description": "Angular filter to perform image transformation to images hosted with cloudinary in a declarative way",
5 | "main": "lrCloudinary.js",
6 | "scripts": {
7 | "test": "node ./node_modules/.bin/gulp"
8 | },
9 | "author": "Laurent RENARD",
10 | "license": "MIT",
11 | "devDependencies": {
12 | "gulp": "^3.8.6",
13 | "gulp-rename": "^1.2.0",
14 | "gulp-uglify": "^0.3.1",
15 | "karma": "^0.12.17",
16 | "karma-chrome-launcher": "^0.1.4",
17 | "karma-jasmine": "^0.1.5",
18 | "karma-phantomjs-launcher": "^0.1.4"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/gulpFile.js:
--------------------------------------------------------------------------------
1 | var gulp = require('gulp');
2 | var uglify = require('gulp-uglify');
3 | var rename = require('gulp-rename');
4 | var karma = require('karma').server;
5 |
6 | gulp.task('build', function () {
7 | gulp.src('lrCloudinary.js')
8 | .pipe(uglify({
9 | mangle: false
10 | }))
11 | .pipe(rename('lrCloudinary.min.js'))
12 | .pipe(gulp.dest('./'));
13 | });
14 |
15 | gulp.task('karma-CI', function (done) {
16 | var conf = require('./conf/karma-common.js');
17 | conf.singleRun = true;
18 | conf.browsers = ['PhantomJS'];
19 | conf.basePath = './';
20 | karma.start(conf, done);
21 | });
22 |
23 | gulp.task('default', ['karma-CI', 'build']);
--------------------------------------------------------------------------------
/lrCloudinary.min.js:
--------------------------------------------------------------------------------
1 | !function(ng){"use strict";ng.module("lrCloudinary",[]).constant("lrCloudinary.config",{baseUrl:"https://res.cloudinary.com/"}).provider("lrCloudinaryUrl",["lrCloudinary.config",function(defaultConfig){var config=ng.extend({},defaultConfig);this.setConfig=function(configObject){ng.extend(config,configObject)},this.$get=[function(){function generateTransformationString(options){var params=[],output="/";return options.width&¶ms.push("w_"+options.width),options.height&¶ms.push("h_"+options.height),options.crop&¶ms.push("c_"+options.crop),params.length>0&&(output+=params.join(","),output+="/"),output}return function(input,options){var output="",optionsOrDefault=ng.extend({format:"jpg"},options),transformString=generateTransformationString(optionsOrDefault);return input&&config.cloudName&&(output=config.baseUrl+config.cloudName+"/image/upload"+transformString+input+"."+optionsOrDefault.format),output}}]}]).filter("lrCloudinaryFilter",["lrCloudinaryUrl",function(cloudinary){return cloudinary}])}(angular);
--------------------------------------------------------------------------------
/test/lrCloudinary-spec.js:
--------------------------------------------------------------------------------
1 | describe('lrCloudinary module', function () {
2 |
3 | var filter;
4 |
5 | beforeEach(module('lrCloudinary', function (lrCloudinaryUrlProvider) {
6 | lrCloudinaryUrlProvider.setConfig({cloudName: 'test', baseUrl: 'http://example.com/'});
7 | }));
8 |
9 | beforeEach(inject(function ($filter) {
10 | filter = $filter('lrCloudinaryFilter');
11 | }));
12 |
13 | it('should return url based on cloud name configuration and cloudinary public_id (default to jpg extension)', function () {
14 | expect(filter('public')).toEqual('http://example.com/test/image/upload/public.jpg');
15 | });
16 |
17 | it('should support partial transformations', function () {
18 | expect(filter('public', {height: 150})).toEqual('http://example.com/test/image/upload/h_150/public.jpg')
19 | });
20 |
21 | it('should support multiple transformation', function () {
22 | expect(filter('public', {width: 100, height: 150, crop: 'fill'})).toEqual('http://example.com/test/image/upload/w_100,h_150,c_fill/public.jpg');
23 | });
24 |
25 | it('should support other image format', function () {
26 | expect(filter('public', {format: 'gif'})).toEqual('http://example.com/test/image/upload/public.gif');
27 | });
28 | });
29 |
30 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | [](https://travis-ci.org/lorenzofox3/lrCloudinary)
2 |
3 | # lrCloudinary
4 |
5 | An Angular filter to perform image transformations on [cloudinary](http://cloudinary.com/) hosted images in a declarative way. There is no dependency other than Angular framework itself.
6 |
7 | ## Getting started
8 |
9 | 1. Add the script `lrCloudinary.js` or the minified version (lrCloudinary.min.js) in your application.
10 | 2. Register the module `angular.module('myApp',['lrCloudinary'])
11 | 3. Configure the module to use at least your cloud name
12 | ```javascript
13 | angular.module('myApp',['lrCloudinary']).config(function(lrCloudinaryUrlProvider){
14 | lrCloudinaryUrlProvider.setConfig({cloudName: 'YOUR CLOUD NAME'});
15 | };
16 | ```
17 |
18 | ### syntax
19 |
20 | you can bind an image src to a cloudinary public_id and then use the filter options if you want to perform transformations (or pass no arguments if you simply want to display the image as it has been uploaded
21 | ```html
22 |
23 | ```
24 | the supported options are
25 |
26 | 1. size
27 | 2. height
28 | 3. crop mode
29 | 4. format
30 |
31 | ## developers
32 |
33 | simply run `npm install` to download the developement dependencies then run `npm test`.
34 | You cun alternatively run `gulp karma-CI`
35 |
36 | ## Licence
37 |
38 | > Copyright (C) 2014 Laurent Renard.
39 | >
40 | > Permission is hereby granted, free of charge, to any person
41 | > obtaining a copy of this software and associated documentation files
42 | > (the "Software"), to deal in the Software without restriction,
43 | > including without limitation the rights to use, copy, modify, merge,
44 | > publish, distribute, sublicense, and/or sell copies of the Software,
45 | > and to permit persons to whom the Software is furnished to do so,
46 | > subject to the following conditions:
47 | >
48 | > The above copyright notice and this permission notice shall be
49 | > included in all copies or substantial portions of the Software.
50 | >
51 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
52 | > EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
53 | > MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
54 | > NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
55 | > BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
56 | > ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
57 | > CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
58 | > SOFTWARE.
59 |
--------------------------------------------------------------------------------
/lrCloudinary.js:
--------------------------------------------------------------------------------
1 | (function (ng, undefined) {
2 | 'use strict';
3 | ng.module('lrCloudinary', [])
4 | .constant('lrCloudinary.config', {
5 | // cloudName: 'you cloud name',
6 | baseUrl: 'https://res.cloudinary.com/'
7 | })
8 | .provider('lrCloudinaryUrl', ['lrCloudinary.config', function CloudinaryProvider(defaultConfig) {
9 |
10 | var config = ng.extend({}, defaultConfig);
11 |
12 | this.setConfig = function setConfig(configObject) {
13 | ng.extend(config, configObject);
14 | };
15 |
16 | this.$get = [function () {
17 |
18 | function generateTransformationString(options) {
19 | var params = [];
20 | var output = '/';
21 | if (options.width) {
22 | params.push('w_' + options.width);
23 | }
24 | if (options.height) {
25 | params.push('h_' + options.height);
26 | }
27 | if (options.crop) {
28 | params.push('c_' + options.crop);
29 | }
30 |
31 | if (params.length > 0) {
32 | output += params.join(',');
33 | output += '/';
34 | }
35 |
36 | return output;
37 |
38 | }
39 |
40 | /**
41 | * create a cloudinary url from a cloudinary public_id passing all transformation parameters
42 | * @input the cloudinary public_id
43 | * @options transformation options
44 | *
45 | *
46 | * {
47 | * format:'gif',//the requested format (default jpeg)
48 | * width:150,//the width resize prop
49 | * height:100,//the height resize prop
50 | * crop:'fill'//the crop mode
51 | * }
52 | *
53 | *
54 | */
55 | return function generateUrl(input, options) {
56 | var output = '';
57 | var optionsOrDefault = ng.extend({format: 'jpg'}, options);
58 | var transformString = generateTransformationString(optionsOrDefault);
59 |
60 | if (input && config.cloudName) {
61 | output = config.baseUrl + config.cloudName + '/image/upload' + transformString + input + '.' + optionsOrDefault.format;
62 | }
63 | return output;
64 | };
65 | }];
66 | }])
67 | .filter('lrCloudinaryFilter', ['lrCloudinaryUrl', function (cloudinary) {
68 | return cloudinary;
69 | }]);
70 | })(angular);
71 |
--------------------------------------------------------------------------------
/lib/angular-mocks.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @license AngularJS v1.2.20
3 | * (c) 2010-2014 Google, Inc. http://angularjs.org
4 | * License: MIT
5 | */
6 | (function(window, angular, undefined) {
7 |
8 | 'use strict';
9 |
10 | /**
11 | * @ngdoc object
12 | * @name angular.mock
13 | * @description
14 | *
15 | * Namespace from 'angular-mocks.js' which contains testing related code.
16 | */
17 | angular.mock = {};
18 |
19 | /**
20 | * ! This is a private undocumented service !
21 | *
22 | * @name $browser
23 | *
24 | * @description
25 | * This service is a mock implementation of {@link ng.$browser}. It provides fake
26 | * implementation for commonly used browser apis that are hard to test, e.g. setTimeout, xhr,
27 | * cookies, etc...
28 | *
29 | * The api of this service is the same as that of the real {@link ng.$browser $browser}, except
30 | * that there are several helper methods available which can be used in tests.
31 | */
32 | angular.mock.$BrowserProvider = function() {
33 | this.$get = function() {
34 | return new angular.mock.$Browser();
35 | };
36 | };
37 |
38 | angular.mock.$Browser = function() {
39 | var self = this;
40 |
41 | this.isMock = true;
42 | self.$$url = "http://server/";
43 | self.$$lastUrl = self.$$url; // used by url polling fn
44 | self.pollFns = [];
45 |
46 | // TODO(vojta): remove this temporary api
47 | self.$$completeOutstandingRequest = angular.noop;
48 | self.$$incOutstandingRequestCount = angular.noop;
49 |
50 |
51 | // register url polling fn
52 |
53 | self.onUrlChange = function(listener) {
54 | self.pollFns.push(
55 | function() {
56 | if (self.$$lastUrl != self.$$url) {
57 | self.$$lastUrl = self.$$url;
58 | listener(self.$$url);
59 | }
60 | }
61 | );
62 |
63 | return listener;
64 | };
65 |
66 | self.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 $browser#defer.now
81 | *
82 | * @description
83 | * Current milliseconds mock time.
84 | */
85 | self.defer.now = 0;
86 |
87 |
88 | self.defer.cancel = function(deferId) {
89 | var fnIndex;
90 |
91 | angular.forEach(self.deferredFns, function(fn, index) {
92 | if (fn.id === deferId) fnIndex = index;
93 | });
94 |
95 | if (fnIndex !== undefined) {
96 | self.deferredFns.splice(fnIndex, 1);
97 | return true;
98 | }
99 |
100 | return false;
101 | };
102 |
103 |
104 | /**
105 | * @name $browser#defer.flush
106 | *
107 | * @description
108 | * Flushes all pending requests and executes the defer callbacks.
109 | *
110 | * @param {number=} number of milliseconds to flush. See {@link #defer.now}
111 | */
112 | self.defer.flush = function(delay) {
113 | if (angular.isDefined(delay)) {
114 | self.defer.now += delay;
115 | } else {
116 | if (self.deferredFns.length) {
117 | self.defer.now = self.deferredFns[self.deferredFns.length-1].time;
118 | } else {
119 | throw new Error('No deferred tasks to be flushed');
120 | }
121 | }
122 |
123 | while (self.deferredFns.length && self.deferredFns[0].time <= self.defer.now) {
124 | self.deferredFns.shift().fn();
125 | }
126 | };
127 |
128 | self.$$baseHref = '';
129 | self.baseHref = function() {
130 | return this.$$baseHref;
131 | };
132 | };
133 | angular.mock.$Browser.prototype = {
134 |
135 | /**
136 | * @name $browser#poll
137 | *
138 | * @description
139 | * run all fns in pollFns
140 | */
141 | poll: function poll() {
142 | angular.forEach(this.pollFns, function(pollFn){
143 | pollFn();
144 | });
145 | },
146 |
147 | addPollFn: function(pollFn) {
148 | this.pollFns.push(pollFn);
149 | return pollFn;
150 | },
151 |
152 | url: function(url, replace) {
153 | if (url) {
154 | this.$$url = url;
155 | return this;
156 | }
157 |
158 | return this.$$url;
159 | },
160 |
161 | cookies: function(name, value) {
162 | if (name) {
163 | if (angular.isUndefined(value)) {
164 | delete this.cookieHash[name];
165 | } else {
166 | if (angular.isString(value) && //strings only
167 | value.length <= 4096) { //strict cookie storage limits
168 | this.cookieHash[name] = value;
169 | }
170 | }
171 | } else {
172 | if (!angular.equals(this.cookieHash, this.lastCookieHash)) {
173 | this.lastCookieHash = angular.copy(this.cookieHash);
174 | this.cookieHash = angular.copy(this.cookieHash);
175 | }
176 | return this.cookieHash;
177 | }
178 | },
179 |
180 | notifyWhenNoOutstandingRequests: function(fn) {
181 | fn();
182 | }
183 | };
184 |
185 |
186 | /**
187 | * @ngdoc provider
188 | * @name $exceptionHandlerProvider
189 | *
190 | * @description
191 | * Configures the mock implementation of {@link ng.$exceptionHandler} to rethrow or to log errors
192 | * passed into the `$exceptionHandler`.
193 | */
194 |
195 | /**
196 | * @ngdoc service
197 | * @name $exceptionHandler
198 | *
199 | * @description
200 | * Mock implementation of {@link ng.$exceptionHandler} that rethrows or logs errors passed
201 | * into it. See {@link ngMock.$exceptionHandlerProvider $exceptionHandlerProvider} for configuration
202 | * information.
203 | *
204 | *
205 | * ```js
206 | * describe('$exceptionHandlerProvider', function() {
207 | *
208 | * it('should capture log messages and exceptions', function() {
209 | *
210 | * module(function($exceptionHandlerProvider) {
211 | * $exceptionHandlerProvider.mode('log');
212 | * });
213 | *
214 | * inject(function($log, $exceptionHandler, $timeout) {
215 | * $timeout(function() { $log.log(1); });
216 | * $timeout(function() { $log.log(2); throw 'banana peel'; });
217 | * $timeout(function() { $log.log(3); });
218 | * expect($exceptionHandler.errors).toEqual([]);
219 | * expect($log.assertEmpty());
220 | * $timeout.flush();
221 | * expect($exceptionHandler.errors).toEqual(['banana peel']);
222 | * expect($log.log.logs).toEqual([[1], [2], [3]]);
223 | * });
224 | * });
225 | * });
226 | * ```
227 | */
228 |
229 | angular.mock.$ExceptionHandlerProvider = function() {
230 | var handler;
231 |
232 | /**
233 | * @ngdoc method
234 | * @name $exceptionHandlerProvider#mode
235 | *
236 | * @description
237 | * Sets the logging mode.
238 | *
239 | * @param {string} mode Mode of operation, defaults to `rethrow`.
240 | *
241 | * - `rethrow`: If any errors are passed into the handler in tests, it typically
242 | * means that there is a bug in the application or test, so this mock will
243 | * make these tests fail.
244 | * - `log`: Sometimes it is desirable to test that an error is thrown, for this case the `log`
245 | * mode stores an array of errors in `$exceptionHandler.errors`, to allow later
246 | * assertion of them. See {@link ngMock.$log#assertEmpty assertEmpty()} and
247 | * {@link ngMock.$log#reset reset()}
248 | */
249 | this.mode = function(mode) {
250 | switch(mode) {
251 | case 'rethrow':
252 | handler = function(e) {
253 | throw e;
254 | };
255 | break;
256 | case 'log':
257 | var errors = [];
258 |
259 | handler = function(e) {
260 | if (arguments.length == 1) {
261 | errors.push(e);
262 | } else {
263 | errors.push([].slice.call(arguments, 0));
264 | }
265 | };
266 |
267 | handler.errors = errors;
268 | break;
269 | default:
270 | throw new Error("Unknown mode '" + mode + "', only 'log'/'rethrow' modes are allowed!");
271 | }
272 | };
273 |
274 | this.$get = function() {
275 | return handler;
276 | };
277 |
278 | this.mode('rethrow');
279 | };
280 |
281 |
282 | /**
283 | * @ngdoc service
284 | * @name $log
285 | *
286 | * @description
287 | * Mock implementation of {@link ng.$log} that gathers all logged messages in arrays
288 | * (one array per logging level). These arrays are exposed as `logs` property of each of the
289 | * level-specific log function, e.g. for level `error` the array is exposed as `$log.error.logs`.
290 | *
291 | */
292 | angular.mock.$LogProvider = function() {
293 | var debug = true;
294 |
295 | function concat(array1, array2, index) {
296 | return array1.concat(Array.prototype.slice.call(array2, index));
297 | }
298 |
299 | this.debugEnabled = function(flag) {
300 | if (angular.isDefined(flag)) {
301 | debug = flag;
302 | return this;
303 | } else {
304 | return debug;
305 | }
306 | };
307 |
308 | this.$get = function () {
309 | var $log = {
310 | log: function() { $log.log.logs.push(concat([], arguments, 0)); },
311 | warn: function() { $log.warn.logs.push(concat([], arguments, 0)); },
312 | info: function() { $log.info.logs.push(concat([], arguments, 0)); },
313 | error: function() { $log.error.logs.push(concat([], arguments, 0)); },
314 | debug: function() {
315 | if (debug) {
316 | $log.debug.logs.push(concat([], arguments, 0));
317 | }
318 | }
319 | };
320 |
321 | /**
322 | * @ngdoc method
323 | * @name $log#reset
324 | *
325 | * @description
326 | * Reset all of the logging arrays to empty.
327 | */
328 | $log.reset = function () {
329 | /**
330 | * @ngdoc property
331 | * @name $log#log.logs
332 | *
333 | * @description
334 | * Array of messages logged using {@link ngMock.$log#log}.
335 | *
336 | * @example
337 | * ```js
338 | * $log.log('Some Log');
339 | * var first = $log.log.logs.unshift();
340 | * ```
341 | */
342 | $log.log.logs = [];
343 | /**
344 | * @ngdoc property
345 | * @name $log#info.logs
346 | *
347 | * @description
348 | * Array of messages logged using {@link ngMock.$log#info}.
349 | *
350 | * @example
351 | * ```js
352 | * $log.info('Some Info');
353 | * var first = $log.info.logs.unshift();
354 | * ```
355 | */
356 | $log.info.logs = [];
357 | /**
358 | * @ngdoc property
359 | * @name $log#warn.logs
360 | *
361 | * @description
362 | * Array of messages logged using {@link ngMock.$log#warn}.
363 | *
364 | * @example
365 | * ```js
366 | * $log.warn('Some Warning');
367 | * var first = $log.warn.logs.unshift();
368 | * ```
369 | */
370 | $log.warn.logs = [];
371 | /**
372 | * @ngdoc property
373 | * @name $log#error.logs
374 | *
375 | * @description
376 | * Array of messages logged using {@link ngMock.$log#error}.
377 | *
378 | * @example
379 | * ```js
380 | * $log.error('Some Error');
381 | * var first = $log.error.logs.unshift();
382 | * ```
383 | */
384 | $log.error.logs = [];
385 | /**
386 | * @ngdoc property
387 | * @name $log#debug.logs
388 | *
389 | * @description
390 | * Array of messages logged using {@link ngMock.$log#debug}.
391 | *
392 | * @example
393 | * ```js
394 | * $log.debug('Some Error');
395 | * var first = $log.debug.logs.unshift();
396 | * ```
397 | */
398 | $log.debug.logs = [];
399 | };
400 |
401 | /**
402 | * @ngdoc method
403 | * @name $log#assertEmpty
404 | *
405 | * @description
406 | * Assert that the all of the logging methods have no logged messages. If messages present, an
407 | * exception is thrown.
408 | */
409 | $log.assertEmpty = function() {
410 | var errors = [];
411 | angular.forEach(['error', 'warn', 'info', 'log', 'debug'], function(logLevel) {
412 | angular.forEach($log[logLevel].logs, function(log) {
413 | angular.forEach(log, function (logItem) {
414 | errors.push('MOCK $log (' + logLevel + '): ' + String(logItem) + '\n' +
415 | (logItem.stack || ''));
416 | });
417 | });
418 | });
419 | if (errors.length) {
420 | errors.unshift("Expected $log to be empty! Either a message was logged unexpectedly, or "+
421 | "an expected log message was not checked and removed:");
422 | errors.push('');
423 | throw new Error(errors.join('\n---------\n'));
424 | }
425 | };
426 |
427 | $log.reset();
428 | return $log;
429 | };
430 | };
431 |
432 |
433 | /**
434 | * @ngdoc service
435 | * @name $interval
436 | *
437 | * @description
438 | * Mock implementation of the $interval service.
439 | *
440 | * Use {@link ngMock.$interval#flush `$interval.flush(millis)`} to
441 | * move forward by `millis` milliseconds and trigger any functions scheduled to run in that
442 | * time.
443 | *
444 | * @param {function()} fn A function that should be called repeatedly.
445 | * @param {number} delay Number of milliseconds between each function call.
446 | * @param {number=} [count=0] Number of times to repeat. If not set, or 0, will repeat
447 | * indefinitely.
448 | * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise
449 | * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block.
450 | * @returns {promise} A promise which will be notified on each iteration.
451 | */
452 | angular.mock.$IntervalProvider = function() {
453 | this.$get = ['$rootScope', '$q',
454 | function($rootScope, $q) {
455 | var repeatFns = [],
456 | nextRepeatId = 0,
457 | now = 0;
458 |
459 | var $interval = function(fn, delay, count, invokeApply) {
460 | var deferred = $q.defer(),
461 | promise = deferred.promise,
462 | iteration = 0,
463 | skipApply = (angular.isDefined(invokeApply) && !invokeApply);
464 |
465 | count = (angular.isDefined(count)) ? count : 0;
466 | promise.then(null, null, fn);
467 |
468 | promise.$$intervalId = nextRepeatId;
469 |
470 | function tick() {
471 | deferred.notify(iteration++);
472 |
473 | if (count > 0 && iteration >= count) {
474 | var fnIndex;
475 | deferred.resolve(iteration);
476 |
477 | angular.forEach(repeatFns, function(fn, index) {
478 | if (fn.id === promise.$$intervalId) fnIndex = index;
479 | });
480 |
481 | if (fnIndex !== undefined) {
482 | repeatFns.splice(fnIndex, 1);
483 | }
484 | }
485 |
486 | if (!skipApply) $rootScope.$apply();
487 | }
488 |
489 | repeatFns.push({
490 | nextTime:(now + delay),
491 | delay: delay,
492 | fn: tick,
493 | id: nextRepeatId,
494 | deferred: deferred
495 | });
496 | repeatFns.sort(function(a,b){ return a.nextTime - b.nextTime;});
497 |
498 | nextRepeatId++;
499 | return promise;
500 | };
501 | /**
502 | * @ngdoc method
503 | * @name $interval#cancel
504 | *
505 | * @description
506 | * Cancels a task associated with the `promise`.
507 | *
508 | * @param {promise} promise A promise from calling the `$interval` function.
509 | * @returns {boolean} Returns `true` if the task was successfully cancelled.
510 | */
511 | $interval.cancel = function(promise) {
512 | if(!promise) return false;
513 | var fnIndex;
514 |
515 | angular.forEach(repeatFns, function(fn, index) {
516 | if (fn.id === promise.$$intervalId) fnIndex = index;
517 | });
518 |
519 | if (fnIndex !== undefined) {
520 | repeatFns[fnIndex].deferred.reject('canceled');
521 | repeatFns.splice(fnIndex, 1);
522 | return true;
523 | }
524 |
525 | return false;
526 | };
527 |
528 | /**
529 | * @ngdoc method
530 | * @name $interval#flush
531 | * @description
532 | *
533 | * Runs interval tasks scheduled to be run in the next `millis` milliseconds.
534 | *
535 | * @param {number=} millis maximum timeout amount to flush up until.
536 | *
537 | * @return {number} The amount of time moved forward.
538 | */
539 | $interval.flush = function(millis) {
540 | now += millis;
541 | while (repeatFns.length && repeatFns[0].nextTime <= now) {
542 | var task = repeatFns[0];
543 | task.fn();
544 | task.nextTime += task.delay;
545 | repeatFns.sort(function(a,b){ return a.nextTime - b.nextTime;});
546 | }
547 | return millis;
548 | };
549 |
550 | return $interval;
551 | }];
552 | };
553 |
554 |
555 | /* jshint -W101 */
556 | /* The R_ISO8061_STR regex is never going to fit into the 100 char limit!
557 | * This directive should go inside the anonymous function but a bug in JSHint means that it would
558 | * not be enacted early enough to prevent the warning.
559 | */
560 | var R_ISO8061_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?:\:?(\d\d)(?:\:?(\d\d)(?:\.(\d{3}))?)?)?(Z|([+-])(\d\d):?(\d\d)))?$/;
561 |
562 | function jsonStringToDate(string) {
563 | var match;
564 | if (match = string.match(R_ISO8061_STR)) {
565 | var date = new Date(0),
566 | tzHour = 0,
567 | tzMin = 0;
568 | if (match[9]) {
569 | tzHour = int(match[9] + match[10]);
570 | tzMin = int(match[9] + match[11]);
571 | }
572 | date.setUTCFullYear(int(match[1]), int(match[2]) - 1, int(match[3]));
573 | date.setUTCHours(int(match[4]||0) - tzHour,
574 | int(match[5]||0) - tzMin,
575 | int(match[6]||0),
576 | int(match[7]||0));
577 | return date;
578 | }
579 | return string;
580 | }
581 |
582 | function int(str) {
583 | return parseInt(str, 10);
584 | }
585 |
586 | function padNumber(num, digits, trim) {
587 | var neg = '';
588 | if (num < 0) {
589 | neg = '-';
590 | num = -num;
591 | }
592 | num = '' + num;
593 | while(num.length < digits) num = '0' + num;
594 | if (trim)
595 | num = num.substr(num.length - digits);
596 | return neg + num;
597 | }
598 |
599 |
600 | /**
601 | * @ngdoc type
602 | * @name angular.mock.TzDate
603 | * @description
604 | *
605 | * *NOTE*: this is not an injectable instance, just a globally available mock class of `Date`.
606 | *
607 | * Mock of the Date type which has its timezone specified via constructor arg.
608 | *
609 | * The main purpose is to create Date-like instances with timezone fixed to the specified timezone
610 | * offset, so that we can test code that depends on local timezone settings without dependency on
611 | * the time zone settings of the machine where the code is running.
612 | *
613 | * @param {number} offset Offset of the *desired* timezone in hours (fractions will be honored)
614 | * @param {(number|string)} timestamp Timestamp representing the desired time in *UTC*
615 | *
616 | * @example
617 | * !!!! WARNING !!!!!
618 | * This is not a complete Date object so only methods that were implemented can be called safely.
619 | * To make matters worse, TzDate instances inherit stuff from Date via a prototype.
620 | *
621 | * We do our best to intercept calls to "unimplemented" methods, but since the list of methods is
622 | * incomplete we might be missing some non-standard methods. This can result in errors like:
623 | * "Date.prototype.foo called on incompatible Object".
624 | *
625 | * ```js
626 | * var newYearInBratislava = new TzDate(-1, '2009-12-31T23:00:00Z');
627 | * newYearInBratislava.getTimezoneOffset() => -60;
628 | * newYearInBratislava.getFullYear() => 2010;
629 | * newYearInBratislava.getMonth() => 0;
630 | * newYearInBratislava.getDate() => 1;
631 | * newYearInBratislava.getHours() => 0;
632 | * newYearInBratislava.getMinutes() => 0;
633 | * newYearInBratislava.getSeconds() => 0;
634 | * ```
635 | *
636 | */
637 | angular.mock.TzDate = function (offset, timestamp) {
638 | var self = new Date(0);
639 | if (angular.isString(timestamp)) {
640 | var tsStr = timestamp;
641 |
642 | self.origDate = jsonStringToDate(timestamp);
643 |
644 | timestamp = self.origDate.getTime();
645 | if (isNaN(timestamp))
646 | throw {
647 | name: "Illegal Argument",
648 | message: "Arg '" + tsStr + "' passed into TzDate constructor is not a valid date string"
649 | };
650 | } else {
651 | self.origDate = new Date(timestamp);
652 | }
653 |
654 | var localOffset = new Date(timestamp).getTimezoneOffset();
655 | self.offsetDiff = localOffset*60*1000 - offset*1000*60*60;
656 | self.date = new Date(timestamp + self.offsetDiff);
657 |
658 | self.getTime = function() {
659 | return self.date.getTime() - self.offsetDiff;
660 | };
661 |
662 | self.toLocaleDateString = function() {
663 | return self.date.toLocaleDateString();
664 | };
665 |
666 | self.getFullYear = function() {
667 | return self.date.getFullYear();
668 | };
669 |
670 | self.getMonth = function() {
671 | return self.date.getMonth();
672 | };
673 |
674 | self.getDate = function() {
675 | return self.date.getDate();
676 | };
677 |
678 | self.getHours = function() {
679 | return self.date.getHours();
680 | };
681 |
682 | self.getMinutes = function() {
683 | return self.date.getMinutes();
684 | };
685 |
686 | self.getSeconds = function() {
687 | return self.date.getSeconds();
688 | };
689 |
690 | self.getMilliseconds = function() {
691 | return self.date.getMilliseconds();
692 | };
693 |
694 | self.getTimezoneOffset = function() {
695 | return offset * 60;
696 | };
697 |
698 | self.getUTCFullYear = function() {
699 | return self.origDate.getUTCFullYear();
700 | };
701 |
702 | self.getUTCMonth = function() {
703 | return self.origDate.getUTCMonth();
704 | };
705 |
706 | self.getUTCDate = function() {
707 | return self.origDate.getUTCDate();
708 | };
709 |
710 | self.getUTCHours = function() {
711 | return self.origDate.getUTCHours();
712 | };
713 |
714 | self.getUTCMinutes = function() {
715 | return self.origDate.getUTCMinutes();
716 | };
717 |
718 | self.getUTCSeconds = function() {
719 | return self.origDate.getUTCSeconds();
720 | };
721 |
722 | self.getUTCMilliseconds = function() {
723 | return self.origDate.getUTCMilliseconds();
724 | };
725 |
726 | self.getDay = function() {
727 | return self.date.getDay();
728 | };
729 |
730 | // provide this method only on browsers that already have it
731 | if (self.toISOString) {
732 | self.toISOString = function() {
733 | return padNumber(self.origDate.getUTCFullYear(), 4) + '-' +
734 | padNumber(self.origDate.getUTCMonth() + 1, 2) + '-' +
735 | padNumber(self.origDate.getUTCDate(), 2) + 'T' +
736 | padNumber(self.origDate.getUTCHours(), 2) + ':' +
737 | padNumber(self.origDate.getUTCMinutes(), 2) + ':' +
738 | padNumber(self.origDate.getUTCSeconds(), 2) + '.' +
739 | padNumber(self.origDate.getUTCMilliseconds(), 3) + 'Z';
740 | };
741 | }
742 |
743 | //hide all methods not implemented in this mock that the Date prototype exposes
744 | var unimplementedMethods = ['getUTCDay',
745 | 'getYear', 'setDate', 'setFullYear', 'setHours', 'setMilliseconds',
746 | 'setMinutes', 'setMonth', 'setSeconds', 'setTime', 'setUTCDate', 'setUTCFullYear',
747 | 'setUTCHours', 'setUTCMilliseconds', 'setUTCMinutes', 'setUTCMonth', 'setUTCSeconds',
748 | 'setYear', 'toDateString', 'toGMTString', 'toJSON', 'toLocaleFormat', 'toLocaleString',
749 | 'toLocaleTimeString', 'toSource', 'toString', 'toTimeString', 'toUTCString', 'valueOf'];
750 |
751 | angular.forEach(unimplementedMethods, function(methodName) {
752 | self[methodName] = function() {
753 | throw new Error("Method '" + methodName + "' is not implemented in the TzDate mock");
754 | };
755 | });
756 |
757 | return self;
758 | };
759 |
760 | //make "tzDateInstance instanceof Date" return true
761 | angular.mock.TzDate.prototype = Date.prototype;
762 | /* jshint +W101 */
763 |
764 | angular.mock.animate = angular.module('ngAnimateMock', ['ng'])
765 |
766 | .config(['$provide', function($provide) {
767 |
768 | var reflowQueue = [];
769 | $provide.value('$$animateReflow', function(fn) {
770 | var index = reflowQueue.length;
771 | reflowQueue.push(fn);
772 | return function cancel() {
773 | reflowQueue.splice(index, 1);
774 | };
775 | });
776 |
777 | $provide.decorator('$animate', function($delegate, $$asyncCallback) {
778 | var animate = {
779 | queue : [],
780 | enabled : $delegate.enabled,
781 | triggerCallbacks : function() {
782 | $$asyncCallback.flush();
783 | },
784 | triggerReflow : function() {
785 | angular.forEach(reflowQueue, function(fn) {
786 | fn();
787 | });
788 | reflowQueue = [];
789 | }
790 | };
791 |
792 | angular.forEach(
793 | ['enter','leave','move','addClass','removeClass','setClass'], function(method) {
794 | animate[method] = function() {
795 | animate.queue.push({
796 | event : method,
797 | element : arguments[0],
798 | args : arguments
799 | });
800 | $delegate[method].apply($delegate, arguments);
801 | };
802 | });
803 |
804 | return animate;
805 | });
806 |
807 | }]);
808 |
809 |
810 | /**
811 | * @ngdoc function
812 | * @name angular.mock.dump
813 | * @description
814 | *
815 | * *NOTE*: this is not an injectable instance, just a globally available function.
816 | *
817 | * Method for serializing common angular objects (scope, elements, etc..) into strings, useful for
818 | * debugging.
819 | *
820 | * This method is also available on window, where it can be used to display objects on debug
821 | * console.
822 | *
823 | * @param {*} object - any object to turn into string.
824 | * @return {string} a serialized string of the argument
825 | */
826 | angular.mock.dump = function(object) {
827 | return serialize(object);
828 |
829 | function serialize(object) {
830 | var out;
831 |
832 | if (angular.isElement(object)) {
833 | object = angular.element(object);
834 | out = angular.element('');
835 | angular.forEach(object, function(element) {
836 | out.append(angular.element(element).clone());
837 | });
838 | out = out.html();
839 | } else if (angular.isArray(object)) {
840 | out = [];
841 | angular.forEach(object, function(o) {
842 | out.push(serialize(o));
843 | });
844 | out = '[ ' + out.join(', ') + ' ]';
845 | } else if (angular.isObject(object)) {
846 | if (angular.isFunction(object.$eval) && angular.isFunction(object.$apply)) {
847 | out = serializeScope(object);
848 | } else if (object instanceof Error) {
849 | out = object.stack || ('' + object.name + ': ' + object.message);
850 | } else {
851 | // TODO(i): this prevents methods being logged,
852 | // we should have a better way to serialize objects
853 | out = angular.toJson(object, true);
854 | }
855 | } else {
856 | out = String(object);
857 | }
858 |
859 | return out;
860 | }
861 |
862 | function serializeScope(scope, offset) {
863 | offset = offset || ' ';
864 | var log = [offset + 'Scope(' + scope.$id + '): {'];
865 | for ( var key in scope ) {
866 | if (Object.prototype.hasOwnProperty.call(scope, key) && !key.match(/^(\$|this)/)) {
867 | log.push(' ' + key + ': ' + angular.toJson(scope[key]));
868 | }
869 | }
870 | var child = scope.$$childHead;
871 | while(child) {
872 | log.push(serializeScope(child, offset + ' '));
873 | child = child.$$nextSibling;
874 | }
875 | log.push('}');
876 | return log.join('\n' + offset);
877 | }
878 | };
879 |
880 | /**
881 | * @ngdoc service
882 | * @name $httpBackend
883 | * @description
884 | * Fake HTTP backend implementation suitable for unit testing applications that use the
885 | * {@link ng.$http $http service}.
886 | *
887 | * *Note*: For fake HTTP backend implementation suitable for end-to-end testing or backend-less
888 | * development please see {@link ngMockE2E.$httpBackend e2e $httpBackend mock}.
889 | *
890 | * During unit testing, we want our unit tests to run quickly and have no external dependencies so
891 | * we don’t want to send [XHR](https://developer.mozilla.org/en/xmlhttprequest) or
892 | * [JSONP](http://en.wikipedia.org/wiki/JSONP) requests to a real server. All we really need is
893 | * to verify whether a certain request has been sent or not, or alternatively just let the
894 | * application make requests, respond with pre-trained responses and assert that the end result is
895 | * what we expect it to be.
896 | *
897 | * This mock implementation can be used to respond with static or dynamic responses via the
898 | * `expect` and `when` apis and their shortcuts (`expectGET`, `whenPOST`, etc).
899 | *
900 | * When an Angular application needs some data from a server, it calls the $http service, which
901 | * sends the request to a real server using $httpBackend service. With dependency injection, it is
902 | * easy to inject $httpBackend mock (which has the same API as $httpBackend) and use it to verify
903 | * the requests and respond with some testing data without sending a request to a real server.
904 | *
905 | * There are two ways to specify what test data should be returned as http responses by the mock
906 | * backend when the code under test makes http requests:
907 | *
908 | * - `$httpBackend.expect` - specifies a request expectation
909 | * - `$httpBackend.when` - specifies a backend definition
910 | *
911 | *
912 | * # Request Expectations vs Backend Definitions
913 | *
914 | * Request expectations provide a way to make assertions about requests made by the application and
915 | * to define responses for those requests. The test will fail if the expected requests are not made
916 | * or they are made in the wrong order.
917 | *
918 | * Backend definitions allow you to define a fake backend for your application which doesn't assert
919 | * if a particular request was made or not, it just returns a trained response if a request is made.
920 | * The test will pass whether or not the request gets made during testing.
921 | *
922 | *
923 | * | Request expectations | Backend definitions | |
|---|---|---|
| Syntax | 927 | *.expect(...).respond(...) | 928 | *.when(...).respond(...) | 929 | *
| Typical usage | 932 | *strict unit tests | 933 | *loose (black-box) unit testing | 934 | *
| Fulfills multiple requests | 937 | *NO | 938 | *YES | 939 | *
| Order of requests matters | 942 | *YES | 943 | *NO | 944 | *
| Request required | 947 | *YES | 948 | *NO | 949 | *
| Response required | 952 | *optional (see below) | 953 | *YES | 954 | *