├── .gitignore ├── Gruntfile.js ├── LICENSE ├── README.md ├── bower.json ├── bower_components ├── angular-mocks │ ├── .bower.json │ ├── README.md │ ├── angular-mocks.js │ └── bower.json └── angular │ ├── .bower.json │ ├── angular.js │ ├── angular.min.js │ ├── angular.min.js.map │ └── bower.json ├── dist └── angularjs-google-places.min.js ├── karma.conf.js ├── package.json ├── src ├── angularjs-google-places.js └── google-api.js └── test ├── angularjs-google-places.js └── mock ├── nearbySearch.js └── placeDetails.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | TODO 3 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(grunt) { 4 | grunt.initConfig({ 5 | pkg: grunt.file.readJSON('package.json'), 6 | jshint: { 7 | files: ['Gruntfile.js', 'src/angularjs-google-places.js', 'test/**/*.js'], 8 | options: { 9 | // options here to override JSHint defaults 10 | globalstrict: true, 11 | globals: { 12 | jQuery: true, 13 | console: true, 14 | module: true, 15 | document: true, 16 | expect: true, 17 | it: true, 18 | spyOn: true, 19 | beforeEach: true, 20 | angular: true, 21 | inject: true, 22 | describe: true, 23 | afterEach: true, 24 | google:true 25 | } 26 | } 27 | }, 28 | uglify: { 29 | options: { 30 | banner: '/*! <%= pkg.name %> <%= grunt.template.today("dd-mm-yyyy") %> */\n' 31 | }, 32 | dist: { 33 | files: { 34 | 'dist/<%= pkg.name %>.min.js': 'src/angularjs-google-places.js' 35 | } 36 | } 37 | }, 38 | karma: { 39 | unit: { 40 | configFile: 'karma.conf.js' 41 | } 42 | } 43 | }); 44 | 45 | grunt.loadNpmTasks('grunt-contrib-jshint'); 46 | grunt.loadNpmTasks('grunt-contrib-uglify'); 47 | grunt.loadNpmTasks('grunt-karma'); 48 | grunt.registerTask('default', ['jshint', 'karma','uglify']); 49 | }; 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Arun Israel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | AngularJS-Google-Places 2 | ========= 3 | 4 | An angular.js wrapper around the Google Places API 5 | 6 | Bower 7 | -- 8 | This module is available as bower package, install it with this command: 9 | 10 | ```bash 11 | bower install angularjs-google-places 12 | ``` 13 | or 14 | 15 | ```bash 16 | bower install git://github.com/arunisrael/angularjs-google-places.git 17 | ``` 18 | 19 | Demo 20 | -- 21 | See this [plunker](http://embed.plnkr.co/6kKlcbafz57lS7HEPPMx/preview) 22 | 23 | Usage 24 | -- 25 | - Include the [Google Maps JS library](http://maps.googleapis.com/maps/api/js?libraries=places&sensor=true_or_false) in your app 26 | - Add ngGPlaces as a dependency 27 | - Inject ngGPlacesAPI as a dependency to your controller or other service 28 | - Invoke the nearbySearch method and pass in a latitude/longitude 29 | - Invoke the placeDetails method and pass in a Google Places reference id 30 | 31 | Example 32 | -- 33 | ``` 34 | var myApp = angular.module('myApp',['ngGPlaces']); 35 | 36 | // optional if you want to modify defaults 37 | myApp.config(function(ngGPlacesAPIProvider){ 38 | ngGPlacesAPIProvider.setDefaults({ 39 | radius:500 40 | }); 41 | }); 42 | 43 | myApp.controller('mainCtrl',function($scope,ngGPlacesAPI){ 44 | $scope.details = ngGPlacesAPI.placeDetails({reference:"really_long_reference_id"}).then( 45 | function (data) { 46 | return data; 47 | }); 48 | 49 | $scope.data = ngGPlacesAPI.nearbySearch({latitude:-33.8665433, longitude:151.1956316}).then( 50 | function(data){ 51 | return data; 52 | }); 53 | }); 54 | ``` 55 | 56 | Further Customizations 57 | -- 58 | You can override any default option below via setDefaults on the ngGPlacesAPIProvider 59 | Or by passing that property to the nearbySearch or placeDetails method 60 | ``` 61 | var defaults = { 62 | radius: 1000, 63 | sensor: false, 64 | latitude: null, 65 | longitude: null, 66 | types: ['food'], 67 | map: null, 68 | elem: null, 69 | nearbySearchKeys: ['name', 'reference', 'vicinity'], 70 | placeDetailsKeys: ['formatted_address', 'formatted_phone_number', 71 | 'reference', 'website' 72 | ], 73 | nearbySearchErr: 'Unable to find nearby places', 74 | placeDetailsErr: 'Unable to find place details', 75 | }; 76 | ``` 77 | 78 | Testing 79 | -- 80 | ``` 81 | grunt test 82 | ``` 83 | 84 | License 85 | -- 86 | MIT -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angularjs-google-places", 3 | "version": "0.1.0", 4 | "homepage": "https://github.com/arunisrael/angularjs-google-places", 5 | "authors": [ 6 | "Arun Israel " 7 | ], 8 | "description": "An angular.js wrapper around the Google Places API", 9 | "main": "src/angularjs-google-places.js", 10 | "keywords": [ 11 | "google places", 12 | "angular.js" 13 | ], 14 | "license": "MIT", 15 | "ignore": [ 16 | "**/.*", 17 | "node_modules", 18 | "bower_components", 19 | "test", 20 | "tests" 21 | ], 22 | "dependencies": { 23 | "angular": "~1.x" 24 | }, 25 | "devDependencies": { 26 | "angular-mocks": "~1.x" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /bower_components/angular-mocks/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-mocks", 3 | "version": "1.2.0-rc.2", 4 | "main": "./angular-mocks.js", 5 | "dependencies": { 6 | "angular": "1.2.0-rc.2" 7 | }, 8 | "homepage": "https://github.com/angular/bower-angular-mocks", 9 | "_release": "1.2.0-rc.2", 10 | "_resolution": { 11 | "type": "version", 12 | "tag": "v1.2.0-rc.2", 13 | "commit": "9bdf39463a7e59c35f4f6163853c8da4fbf81ea3" 14 | }, 15 | "_source": "git://github.com/angular/bower-angular-mocks.git", 16 | "_target": "~1.x", 17 | "_originalSource": "angular-mocks" 18 | } -------------------------------------------------------------------------------- /bower_components/angular-mocks/README.md: -------------------------------------------------------------------------------- 1 | bower-angular-mocks 2 | =================== 3 | 4 | angular-mocks.js bower repo -------------------------------------------------------------------------------- /bower_components/angular-mocks/angular-mocks.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license AngularJS v1.2.0-rc.2 3 | * (c) 2010-2012 Google, Inc. http://angularjs.org 4 | * License: MIT 5 | * 6 | * TODO(vojta): wrap whole file into closure during build 7 | */ 8 | 9 | /** 10 | * @ngdoc overview 11 | * @name angular.mock 12 | * @description 13 | * 14 | * Namespace from 'angular-mocks.js' which contains testing related code. 15 | */ 16 | angular.mock = {}; 17 | 18 | /** 19 | * ! This is a private undocumented service ! 20 | * 21 | * @name ngMock.$browser 22 | * 23 | * @description 24 | * This service is a mock implementation of {@link ng.$browser}. It provides fake 25 | * implementation for commonly used browser apis that are hard to test, e.g. setTimeout, xhr, 26 | * cookies, etc... 27 | * 28 | * The api of this service is the same as that of the real {@link ng.$browser $browser}, except 29 | * that there are several helper methods available which can be used in tests. 30 | */ 31 | angular.mock.$BrowserProvider = function() { 32 | this.$get = function() { 33 | return new angular.mock.$Browser(); 34 | }; 35 | }; 36 | 37 | angular.mock.$Browser = function() { 38 | var self = this; 39 | 40 | this.isMock = true; 41 | self.$$url = "http://server/"; 42 | self.$$lastUrl = self.$$url; // used by url polling fn 43 | self.pollFns = []; 44 | 45 | // TODO(vojta): remove this temporary api 46 | self.$$completeOutstandingRequest = angular.noop; 47 | self.$$incOutstandingRequestCount = angular.noop; 48 | 49 | 50 | // register url polling fn 51 | 52 | self.onUrlChange = function(listener) { 53 | self.pollFns.push( 54 | function() { 55 | if (self.$$lastUrl != self.$$url) { 56 | self.$$lastUrl = self.$$url; 57 | listener(self.$$url); 58 | } 59 | } 60 | ); 61 | 62 | return listener; 63 | }; 64 | 65 | self.cookieHash = {}; 66 | self.lastCookieHash = {}; 67 | self.deferredFns = []; 68 | self.deferredNextId = 0; 69 | 70 | self.defer = function(fn, delay) { 71 | delay = delay || 0; 72 | self.deferredFns.push({time:(self.defer.now + delay), fn:fn, id: self.deferredNextId}); 73 | self.deferredFns.sort(function(a,b){ return a.time - b.time;}); 74 | return self.deferredNextId++; 75 | }; 76 | 77 | 78 | self.defer.now = 0; 79 | 80 | 81 | self.defer.cancel = function(deferId) { 82 | var fnIndex; 83 | 84 | angular.forEach(self.deferredFns, function(fn, index) { 85 | if (fn.id === deferId) fnIndex = index; 86 | }); 87 | 88 | if (fnIndex !== undefined) { 89 | self.deferredFns.splice(fnIndex, 1); 90 | return true; 91 | } 92 | 93 | return false; 94 | }; 95 | 96 | 97 | /** 98 | * @name ngMock.$browser#defer.flush 99 | * @methodOf ngMock.$browser 100 | * 101 | * @description 102 | * Flushes all pending requests and executes the defer callbacks. 103 | * 104 | * @param {number=} number of milliseconds to flush. See {@link #defer.now} 105 | */ 106 | self.defer.flush = function(delay) { 107 | if (angular.isDefined(delay)) { 108 | self.defer.now += delay; 109 | } else { 110 | if (self.deferredFns.length) { 111 | self.defer.now = self.deferredFns[self.deferredFns.length-1].time; 112 | } else { 113 | throw Error('No deferred tasks to be flushed'); 114 | } 115 | } 116 | 117 | while (self.deferredFns.length && self.deferredFns[0].time <= self.defer.now) { 118 | self.deferredFns.shift().fn(); 119 | } 120 | }; 121 | 122 | /** 123 | * @name ngMock.$browser#defer.flushNext 124 | * @methodOf ngMock.$browser 125 | * 126 | * @description 127 | * Flushes next pending request and compares it to the provided delay 128 | * 129 | * @param {number=} expectedDelay the delay value that will be asserted against the delay of the next timeout function 130 | */ 131 | self.defer.flushNext = function(expectedDelay) { 132 | var tick = self.deferredFns.shift(); 133 | expect(tick.time).toEqual(expectedDelay); 134 | tick.fn(); 135 | }; 136 | 137 | /** 138 | * @name ngMock.$browser#defer.now 139 | * @propertyOf ngMock.$browser 140 | * 141 | * @description 142 | * Current milliseconds mock time. 143 | */ 144 | 145 | self.$$baseHref = ''; 146 | self.baseHref = function() { 147 | return this.$$baseHref; 148 | }; 149 | }; 150 | angular.mock.$Browser.prototype = { 151 | 152 | /** 153 | * @name ngMock.$browser#poll 154 | * @methodOf ngMock.$browser 155 | * 156 | * @description 157 | * run all fns in pollFns 158 | */ 159 | poll: function poll() { 160 | angular.forEach(this.pollFns, function(pollFn){ 161 | pollFn(); 162 | }); 163 | }, 164 | 165 | addPollFn: function(pollFn) { 166 | this.pollFns.push(pollFn); 167 | return pollFn; 168 | }, 169 | 170 | url: function(url, replace) { 171 | if (url) { 172 | this.$$url = url; 173 | return this; 174 | } 175 | 176 | return this.$$url; 177 | }, 178 | 179 | cookies: function(name, value) { 180 | if (name) { 181 | if (value == undefined) { 182 | delete this.cookieHash[name]; 183 | } else { 184 | if (angular.isString(value) && //strings only 185 | value.length <= 4096) { //strict cookie storage limits 186 | this.cookieHash[name] = value; 187 | } 188 | } 189 | } else { 190 | if (!angular.equals(this.cookieHash, this.lastCookieHash)) { 191 | this.lastCookieHash = angular.copy(this.cookieHash); 192 | this.cookieHash = angular.copy(this.cookieHash); 193 | } 194 | return this.cookieHash; 195 | } 196 | }, 197 | 198 | notifyWhenNoOutstandingRequests: function(fn) { 199 | fn(); 200 | } 201 | }; 202 | 203 | 204 | /** 205 | * @ngdoc object 206 | * @name ngMock.$exceptionHandlerProvider 207 | * 208 | * @description 209 | * Configures the mock implementation of {@link ng.$exceptionHandler} to rethrow or to log errors passed 210 | * into the `$exceptionHandler`. 211 | */ 212 | 213 | /** 214 | * @ngdoc object 215 | * @name ngMock.$exceptionHandler 216 | * 217 | * @description 218 | * Mock implementation of {@link ng.$exceptionHandler} that rethrows or logs errors passed 219 | * into it. See {@link ngMock.$exceptionHandlerProvider $exceptionHandlerProvider} for configuration 220 | * information. 221 | * 222 | * 223 | *
 224 |  *   describe('$exceptionHandlerProvider', function() {
 225 |  *
 226 |  *     it('should capture log messages and exceptions', function() {
 227 |  *
 228 |  *       module(function($exceptionHandlerProvider) {
 229 |  *         $exceptionHandlerProvider.mode('log');
 230 |  *       });
 231 |  *
 232 |  *       inject(function($log, $exceptionHandler, $timeout) {
 233 |  *         $timeout(function() { $log.log(1); });
 234 |  *         $timeout(function() { $log.log(2); throw 'banana peel'; });
 235 |  *         $timeout(function() { $log.log(3); });
 236 |  *         expect($exceptionHandler.errors).toEqual([]);
 237 |  *         expect($log.assertEmpty());
 238 |  *         $timeout.flush();
 239 |  *         expect($exceptionHandler.errors).toEqual(['banana peel']);
 240 |  *         expect($log.log.logs).toEqual([[1], [2], [3]]);
 241 |  *       });
 242 |  *     });
 243 |  *   });
 244 |  * 
245 | */ 246 | 247 | angular.mock.$ExceptionHandlerProvider = function() { 248 | var handler; 249 | 250 | /** 251 | * @ngdoc method 252 | * @name ngMock.$exceptionHandlerProvider#mode 253 | * @methodOf ngMock.$exceptionHandlerProvider 254 | * 255 | * @description 256 | * Sets the logging mode. 257 | * 258 | * @param {string} mode Mode of operation, defaults to `rethrow`. 259 | * 260 | * - `rethrow`: If any errors are passed into the handler in tests, it typically 261 | * means that there is a bug in the application or test, so this mock will 262 | * make these tests fail. 263 | * - `log`: Sometimes it is desirable to test that an error is thrown, for this case the `log` mode stores an 264 | * array of errors in `$exceptionHandler.errors`, to allow later assertion of them. 265 | * See {@link ngMock.$log#assertEmpty assertEmpty()} and 266 | * {@link ngMock.$log#reset reset()} 267 | */ 268 | this.mode = function(mode) { 269 | switch(mode) { 270 | case 'rethrow': 271 | handler = function(e) { 272 | throw e; 273 | }; 274 | break; 275 | case 'log': 276 | var errors = []; 277 | 278 | handler = function(e) { 279 | if (arguments.length == 1) { 280 | errors.push(e); 281 | } else { 282 | errors.push([].slice.call(arguments, 0)); 283 | } 284 | }; 285 | 286 | handler.errors = errors; 287 | break; 288 | default: 289 | throw Error("Unknown mode '" + mode + "', only 'log'/'rethrow' modes are allowed!"); 290 | } 291 | }; 292 | 293 | this.$get = function() { 294 | return handler; 295 | }; 296 | 297 | this.mode('rethrow'); 298 | }; 299 | 300 | 301 | /** 302 | * @ngdoc service 303 | * @name ngMock.$log 304 | * 305 | * @description 306 | * Mock implementation of {@link ng.$log} that gathers all logged messages in arrays 307 | * (one array per logging level). These arrays are exposed as `logs` property of each of the 308 | * level-specific log function, e.g. for level `error` the array is exposed as `$log.error.logs`. 309 | * 310 | */ 311 | angular.mock.$LogProvider = function() { 312 | var debug = true; 313 | 314 | function concat(array1, array2, index) { 315 | return array1.concat(Array.prototype.slice.call(array2, index)); 316 | } 317 | 318 | this.debugEnabled = function(flag) { 319 | if (angular.isDefined(flag)) { 320 | debug = flag; 321 | return this; 322 | } else { 323 | return debug; 324 | } 325 | }; 326 | 327 | this.$get = function () { 328 | var $log = { 329 | log: function() { $log.log.logs.push(concat([], arguments, 0)); }, 330 | warn: function() { $log.warn.logs.push(concat([], arguments, 0)); }, 331 | info: function() { $log.info.logs.push(concat([], arguments, 0)); }, 332 | error: function() { $log.error.logs.push(concat([], arguments, 0)); }, 333 | debug: function() { 334 | if (debug) { 335 | $log.debug.logs.push(concat([], arguments, 0)); 336 | } 337 | } 338 | }; 339 | 340 | /** 341 | * @ngdoc method 342 | * @name ngMock.$log#reset 343 | * @methodOf ngMock.$log 344 | * 345 | * @description 346 | * Reset all of the logging arrays to empty. 347 | */ 348 | $log.reset = function () { 349 | /** 350 | * @ngdoc property 351 | * @name ngMock.$log#log.logs 352 | * @propertyOf ngMock.$log 353 | * 354 | * @description 355 | * Array of messages logged using {@link ngMock.$log#log}. 356 | * 357 | * @example 358 | *
 359 |        * $log.log('Some Log');
 360 |        * var first = $log.log.logs.unshift();
 361 |        * 
362 | */ 363 | $log.log.logs = []; 364 | /** 365 | * @ngdoc property 366 | * @name ngMock.$log#info.logs 367 | * @propertyOf ngMock.$log 368 | * 369 | * @description 370 | * Array of messages logged using {@link ngMock.$log#info}. 371 | * 372 | * @example 373 | *
 374 |        * $log.info('Some Info');
 375 |        * var first = $log.info.logs.unshift();
 376 |        * 
377 | */ 378 | $log.info.logs = []; 379 | /** 380 | * @ngdoc property 381 | * @name ngMock.$log#warn.logs 382 | * @propertyOf ngMock.$log 383 | * 384 | * @description 385 | * Array of messages logged using {@link ngMock.$log#warn}. 386 | * 387 | * @example 388 | *
 389 |        * $log.warn('Some Warning');
 390 |        * var first = $log.warn.logs.unshift();
 391 |        * 
392 | */ 393 | $log.warn.logs = []; 394 | /** 395 | * @ngdoc property 396 | * @name ngMock.$log#error.logs 397 | * @propertyOf ngMock.$log 398 | * 399 | * @description 400 | * Array of messages logged using {@link ngMock.$log#error}. 401 | * 402 | * @example 403 | *
 404 |        * $log.log('Some Error');
 405 |        * var first = $log.error.logs.unshift();
 406 |        * 
407 | */ 408 | $log.error.logs = []; 409 | /** 410 | * @ngdoc property 411 | * @name ngMock.$log#debug.logs 412 | * @propertyOf ngMock.$log 413 | * 414 | * @description 415 | * Array of messages logged using {@link ngMock.$log#debug}. 416 | * 417 | * @example 418 | *
 419 |        * $log.debug('Some Error');
 420 |        * var first = $log.debug.logs.unshift();
 421 |        * 
422 | */ 423 | $log.debug.logs = [] 424 | }; 425 | 426 | /** 427 | * @ngdoc method 428 | * @name ngMock.$log#assertEmpty 429 | * @methodOf ngMock.$log 430 | * 431 | * @description 432 | * Assert that the all of the logging methods have no logged messages. If messages present, an exception is thrown. 433 | */ 434 | $log.assertEmpty = function() { 435 | var errors = []; 436 | angular.forEach(['error', 'warn', 'info', 'log', 'debug'], function(logLevel) { 437 | angular.forEach($log[logLevel].logs, function(log) { 438 | angular.forEach(log, function (logItem) { 439 | errors.push('MOCK $log (' + logLevel + '): ' + String(logItem) + '\n' + (logItem.stack || '')); 440 | }); 441 | }); 442 | }); 443 | if (errors.length) { 444 | errors.unshift("Expected $log to be empty! Either a message was logged unexpectedly, or an expected " + 445 | "log message was not checked and removed:"); 446 | errors.push(''); 447 | throw new Error(errors.join('\n---------\n')); 448 | } 449 | }; 450 | 451 | $log.reset(); 452 | return $log; 453 | }; 454 | }; 455 | 456 | 457 | (function() { 458 | var R_ISO8061_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?:\:?(\d\d)(?:\:?(\d\d)(?:\.(\d{3}))?)?)?(Z|([+-])(\d\d):?(\d\d)))?$/; 459 | 460 | function jsonStringToDate(string) { 461 | var match; 462 | if (match = string.match(R_ISO8061_STR)) { 463 | var date = new Date(0), 464 | tzHour = 0, 465 | tzMin = 0; 466 | if (match[9]) { 467 | tzHour = int(match[9] + match[10]); 468 | tzMin = int(match[9] + match[11]); 469 | } 470 | date.setUTCFullYear(int(match[1]), int(match[2]) - 1, int(match[3])); 471 | date.setUTCHours(int(match[4]||0) - tzHour, int(match[5]||0) - tzMin, int(match[6]||0), int(match[7]||0)); 472 | return date; 473 | } 474 | return string; 475 | } 476 | 477 | function int(str) { 478 | return parseInt(str, 10); 479 | } 480 | 481 | function padNumber(num, digits, trim) { 482 | var neg = ''; 483 | if (num < 0) { 484 | neg = '-'; 485 | num = -num; 486 | } 487 | num = '' + num; 488 | while(num.length < digits) num = '0' + num; 489 | if (trim) 490 | num = num.substr(num.length - digits); 491 | return neg + num; 492 | } 493 | 494 | 495 | /** 496 | * @ngdoc object 497 | * @name angular.mock.TzDate 498 | * @description 499 | * 500 | * *NOTE*: this is not an injectable instance, just a globally available mock class of `Date`. 501 | * 502 | * Mock of the Date type which has its timezone specified via constructor arg. 503 | * 504 | * The main purpose is to create Date-like instances with timezone fixed to the specified timezone 505 | * offset, so that we can test code that depends on local timezone settings without dependency on 506 | * the time zone settings of the machine where the code is running. 507 | * 508 | * @param {number} offset Offset of the *desired* timezone in hours (fractions will be honored) 509 | * @param {(number|string)} timestamp Timestamp representing the desired time in *UTC* 510 | * 511 | * @example 512 | * !!!! WARNING !!!!! 513 | * This is not a complete Date object so only methods that were implemented can be called safely. 514 | * To make matters worse, TzDate instances inherit stuff from Date via a prototype. 515 | * 516 | * We do our best to intercept calls to "unimplemented" methods, but since the list of methods is 517 | * incomplete we might be missing some non-standard methods. This can result in errors like: 518 | * "Date.prototype.foo called on incompatible Object". 519 | * 520 | *
 521 |    * var newYearInBratislava = new TzDate(-1, '2009-12-31T23:00:00Z');
 522 |    * newYearInBratislava.getTimezoneOffset() => -60;
 523 |    * newYearInBratislava.getFullYear() => 2010;
 524 |    * newYearInBratislava.getMonth() => 0;
 525 |    * newYearInBratislava.getDate() => 1;
 526 |    * newYearInBratislava.getHours() => 0;
 527 |    * newYearInBratislava.getMinutes() => 0;
 528 |    * newYearInBratislava.getSeconds() => 0;
 529 |    * 
530 | * 531 | */ 532 | angular.mock.TzDate = function (offset, timestamp) { 533 | var self = new Date(0); 534 | if (angular.isString(timestamp)) { 535 | var tsStr = timestamp; 536 | 537 | self.origDate = jsonStringToDate(timestamp); 538 | 539 | timestamp = self.origDate.getTime(); 540 | if (isNaN(timestamp)) 541 | throw { 542 | name: "Illegal Argument", 543 | message: "Arg '" + tsStr + "' passed into TzDate constructor is not a valid date string" 544 | }; 545 | } else { 546 | self.origDate = new Date(timestamp); 547 | } 548 | 549 | var localOffset = new Date(timestamp).getTimezoneOffset(); 550 | self.offsetDiff = localOffset*60*1000 - offset*1000*60*60; 551 | self.date = new Date(timestamp + self.offsetDiff); 552 | 553 | self.getTime = function() { 554 | return self.date.getTime() - self.offsetDiff; 555 | }; 556 | 557 | self.toLocaleDateString = function() { 558 | return self.date.toLocaleDateString(); 559 | }; 560 | 561 | self.getFullYear = function() { 562 | return self.date.getFullYear(); 563 | }; 564 | 565 | self.getMonth = function() { 566 | return self.date.getMonth(); 567 | }; 568 | 569 | self.getDate = function() { 570 | return self.date.getDate(); 571 | }; 572 | 573 | self.getHours = function() { 574 | return self.date.getHours(); 575 | }; 576 | 577 | self.getMinutes = function() { 578 | return self.date.getMinutes(); 579 | }; 580 | 581 | self.getSeconds = function() { 582 | return self.date.getSeconds(); 583 | }; 584 | 585 | self.getMilliseconds = function() { 586 | return self.date.getMilliseconds(); 587 | }; 588 | 589 | self.getTimezoneOffset = function() { 590 | return offset * 60; 591 | }; 592 | 593 | self.getUTCFullYear = function() { 594 | return self.origDate.getUTCFullYear(); 595 | }; 596 | 597 | self.getUTCMonth = function() { 598 | return self.origDate.getUTCMonth(); 599 | }; 600 | 601 | self.getUTCDate = function() { 602 | return self.origDate.getUTCDate(); 603 | }; 604 | 605 | self.getUTCHours = function() { 606 | return self.origDate.getUTCHours(); 607 | }; 608 | 609 | self.getUTCMinutes = function() { 610 | return self.origDate.getUTCMinutes(); 611 | }; 612 | 613 | self.getUTCSeconds = function() { 614 | return self.origDate.getUTCSeconds(); 615 | }; 616 | 617 | self.getUTCMilliseconds = function() { 618 | return self.origDate.getUTCMilliseconds(); 619 | }; 620 | 621 | self.getDay = function() { 622 | return self.date.getDay(); 623 | }; 624 | 625 | // provide this method only on browsers that already have it 626 | if (self.toISOString) { 627 | self.toISOString = function() { 628 | return padNumber(self.origDate.getUTCFullYear(), 4) + '-' + 629 | padNumber(self.origDate.getUTCMonth() + 1, 2) + '-' + 630 | padNumber(self.origDate.getUTCDate(), 2) + 'T' + 631 | padNumber(self.origDate.getUTCHours(), 2) + ':' + 632 | padNumber(self.origDate.getUTCMinutes(), 2) + ':' + 633 | padNumber(self.origDate.getUTCSeconds(), 2) + '.' + 634 | padNumber(self.origDate.getUTCMilliseconds(), 3) + 'Z' 635 | } 636 | } 637 | 638 | //hide all methods not implemented in this mock that the Date prototype exposes 639 | var unimplementedMethods = ['getUTCDay', 640 | 'getYear', 'setDate', 'setFullYear', 'setHours', 'setMilliseconds', 641 | 'setMinutes', 'setMonth', 'setSeconds', 'setTime', 'setUTCDate', 'setUTCFullYear', 642 | 'setUTCHours', 'setUTCMilliseconds', 'setUTCMinutes', 'setUTCMonth', 'setUTCSeconds', 643 | 'setYear', 'toDateString', 'toGMTString', 'toJSON', 'toLocaleFormat', 'toLocaleString', 644 | 'toLocaleTimeString', 'toSource', 'toString', 'toTimeString', 'toUTCString', 'valueOf']; 645 | 646 | angular.forEach(unimplementedMethods, function(methodName) { 647 | self[methodName] = function() { 648 | throw Error("Method '" + methodName + "' is not implemented in the TzDate mock"); 649 | }; 650 | }); 651 | 652 | return self; 653 | }; 654 | 655 | //make "tzDateInstance instanceof Date" return true 656 | angular.mock.TzDate.prototype = Date.prototype; 657 | })(); 658 | 659 | angular.mock.animate = angular.module('mock.animate', ['ng']) 660 | 661 | .config(['$provide', function($provide) { 662 | 663 | $provide.decorator('$animate', function($delegate) { 664 | var animate = { 665 | queue : [], 666 | enabled : $delegate.enabled, 667 | flushNext : function(name) { 668 | var tick = animate.queue.shift(); 669 | expect(tick.method).toBe(name); 670 | tick.fn(); 671 | return tick; 672 | } 673 | }; 674 | 675 | forEach(['enter','leave','move','addClass','removeClass'], function(method) { 676 | animate[method] = function() { 677 | var params = arguments; 678 | animate.queue.push({ 679 | method : method, 680 | params : params, 681 | element : angular.isElement(params[0]) && params[0], 682 | parent : angular.isElement(params[1]) && params[1], 683 | after : angular.isElement(params[2]) && params[2], 684 | fn : function() { 685 | $delegate[method].apply($delegate, params); 686 | } 687 | }); 688 | }; 689 | }); 690 | 691 | return animate; 692 | }); 693 | 694 | }]); 695 | 696 | 697 | /** 698 | * @ngdoc function 699 | * @name angular.mock.dump 700 | * @description 701 | * 702 | * *NOTE*: this is not an injectable instance, just a globally available function. 703 | * 704 | * Method for serializing common angular objects (scope, elements, etc..) into strings, useful for debugging. 705 | * 706 | * This method is also available on window, where it can be used to display objects on debug console. 707 | * 708 | * @param {*} object - any object to turn into string. 709 | * @return {string} a serialized string of the argument 710 | */ 711 | angular.mock.dump = function(object) { 712 | return serialize(object); 713 | 714 | function serialize(object) { 715 | var out; 716 | 717 | if (angular.isElement(object)) { 718 | object = angular.element(object); 719 | out = angular.element('
'); 720 | angular.forEach(object, function(element) { 721 | out.append(angular.element(element).clone()); 722 | }); 723 | out = out.html(); 724 | } else if (angular.isArray(object)) { 725 | out = []; 726 | angular.forEach(object, function(o) { 727 | out.push(serialize(o)); 728 | }); 729 | out = '[ ' + out.join(', ') + ' ]'; 730 | } else if (angular.isObject(object)) { 731 | if (angular.isFunction(object.$eval) && angular.isFunction(object.$apply)) { 732 | out = serializeScope(object); 733 | } else if (object instanceof Error) { 734 | out = object.stack || ('' + object.name + ': ' + object.message); 735 | } else { 736 | // TODO(i): this prevents methods to be logged, we should have a better way to serialize objects 737 | out = angular.toJson(object, true); 738 | } 739 | } else { 740 | out = String(object); 741 | } 742 | 743 | return out; 744 | } 745 | 746 | function serializeScope(scope, offset) { 747 | offset = offset || ' '; 748 | var log = [offset + 'Scope(' + scope.$id + '): {']; 749 | for ( var key in scope ) { 750 | if (scope.hasOwnProperty(key) && !key.match(/^(\$|this)/)) { 751 | log.push(' ' + key + ': ' + angular.toJson(scope[key])); 752 | } 753 | } 754 | var child = scope.$$childHead; 755 | while(child) { 756 | log.push(serializeScope(child, offset + ' ')); 757 | child = child.$$nextSibling; 758 | } 759 | log.push('}'); 760 | return log.join('\n' + offset); 761 | } 762 | }; 763 | 764 | /** 765 | * @ngdoc object 766 | * @name ngMock.$httpBackend 767 | * @description 768 | * Fake HTTP backend implementation suitable for unit testing applications that use the 769 | * {@link ng.$http $http service}. 770 | * 771 | * *Note*: For fake HTTP backend implementation suitable for end-to-end testing or backend-less 772 | * development please see {@link ngMockE2E.$httpBackend e2e $httpBackend mock}. 773 | * 774 | * During unit testing, we want our unit tests to run quickly and have no external dependencies so 775 | * we don’t want to send {@link https://developer.mozilla.org/en/xmlhttprequest XHR} or 776 | * {@link http://en.wikipedia.org/wiki/JSONP JSONP} requests to a real server. All we really need is 777 | * to verify whether a certain request has been sent or not, or alternatively just let the 778 | * application make requests, respond with pre-trained responses and assert that the end result is 779 | * what we expect it to be. 780 | * 781 | * This mock implementation can be used to respond with static or dynamic responses via the 782 | * `expect` and `when` apis and their shortcuts (`expectGET`, `whenPOST`, etc). 783 | * 784 | * When an Angular application needs some data from a server, it calls the $http service, which 785 | * sends the request to a real server using $httpBackend service. With dependency injection, it is 786 | * easy to inject $httpBackend mock (which has the same API as $httpBackend) and use it to verify 787 | * the requests and respond with some testing data without sending a request to real server. 788 | * 789 | * There are two ways to specify what test data should be returned as http responses by the mock 790 | * backend when the code under test makes http requests: 791 | * 792 | * - `$httpBackend.expect` - specifies a request expectation 793 | * - `$httpBackend.when` - specifies a backend definition 794 | * 795 | * 796 | * # Request Expectations vs Backend Definitions 797 | * 798 | * Request expectations provide a way to make assertions about requests made by the application and 799 | * to define responses for those requests. The test will fail if the expected requests are not made 800 | * or they are made in the wrong order. 801 | * 802 | * Backend definitions allow you to define a fake backend for your application which doesn't assert 803 | * if a particular request was made or not, it just returns a trained response if a request is made. 804 | * The test will pass whether or not the request gets made during testing. 805 | * 806 | * 807 | * 808 | * 809 | * 810 | * 811 | * 812 | * 813 | * 814 | * 815 | * 816 | * 817 | * 818 | * 819 | * 820 | * 821 | * 822 | * 823 | * 824 | * 825 | * 826 | * 827 | * 828 | * 829 | * 830 | * 831 | * 832 | * 833 | * 834 | * 835 | * 836 | * 837 | * 838 | * 839 | *
Request expectationsBackend definitions
Syntax.expect(...).respond(...).when(...).respond(...)
Typical usagestrict unit testsloose (black-box) unit testing
Fulfills multiple requestsNOYES
Order of requests mattersYESNO
Request requiredYESNO
Response requiredoptional (see below)YES
840 | * 841 | * In cases where both backend definitions and request expectations are specified during unit 842 | * testing, the request expectations are evaluated first. 843 | * 844 | * If a request expectation has no response specified, the algorithm will search your backend 845 | * definitions for an appropriate response. 846 | * 847 | * If a request didn't match any expectation or if the expectation doesn't have the response 848 | * defined, the backend definitions are evaluated in sequential order to see if any of them match 849 | * the request. The response from the first matched definition is returned. 850 | * 851 | * 852 | * # Flushing HTTP requests 853 | * 854 | * The $httpBackend used in production, always responds to requests with responses asynchronously. 855 | * If we preserved this behavior in unit testing, we'd have to create async unit tests, which are 856 | * hard to write, follow and maintain. At the same time the testing mock, can't respond 857 | * synchronously because that would change the execution of the code under test. For this reason the 858 | * mock $httpBackend has a `flush()` method, which allows the test to explicitly flush pending 859 | * requests and thus preserving the async api of the backend, while allowing the test to execute 860 | * synchronously. 861 | * 862 | * 863 | * # Unit testing with mock $httpBackend 864 | * The following code shows how to setup and use the mock backend in unit testing a controller. 865 | * First we create the controller under test 866 | * 867 |
 868 |   // The controller code
 869 |   function MyController($scope, $http) {
 870 |     var authToken;
 871 | 
 872 |     $http.get('/auth.py').success(function(data, status, headers) {
 873 |       authToken = headers('A-Token');
 874 |       $scope.user = data;
 875 |     });
 876 | 
 877 |     $scope.saveMessage = function(message) {
 878 |       var headers = { 'Authorization': authToken };
 879 |       $scope.status = 'Saving...';
 880 | 
 881 |       $http.post('/add-msg.py', message, { headers: headers } ).success(function(response) {
 882 |         $scope.status = '';
 883 |       }).error(function() {
 884 |         $scope.status = 'ERROR!';
 885 |       });
 886 |     };
 887 |   }
 888 |   
889 | * 890 | * Now we setup the mock backend and create the test specs. 891 | * 892 |
 893 |     // testing controller
 894 |     describe('MyController', function() {
 895 |        var $httpBackend, $rootScope, createController;
 896 | 
 897 |        beforeEach(inject(function($injector) {
 898 |          // Set up the mock http service responses
 899 |          $httpBackend = $injector.get('$httpBackend');
 900 |          // backend definition common for all tests
 901 |          $httpBackend.when('GET', '/auth.py').respond({userId: 'userX'}, {'A-Token': 'xxx'});
 902 | 
 903 |          // Get hold of a scope (i.e. the root scope)
 904 |          $rootScope = $injector.get('$rootScope');
 905 |          // The $controller service is used to create instances of controllers
 906 |          var $controller = $injector.get('$controller');
 907 | 
 908 |          createController = function() {
 909 |            return $controller('MyController', {'$scope' : $rootScope });
 910 |          };
 911 |        }));
 912 | 
 913 | 
 914 |        afterEach(function() {
 915 |          $httpBackend.verifyNoOutstandingExpectation();
 916 |          $httpBackend.verifyNoOutstandingRequest();
 917 |        });
 918 | 
 919 | 
 920 |        it('should fetch authentication token', function() {
 921 |          $httpBackend.expectGET('/auth.py');
 922 |          var controller = createController();
 923 |          $httpBackend.flush();
 924 |        });
 925 | 
 926 | 
 927 |        it('should send msg to server', function() {
 928 |          var controller = createController();
 929 |          $httpBackend.flush();
 930 | 
 931 |          // now you don’t care about the authentication, but
 932 |          // the controller will still send the request and
 933 |          // $httpBackend will respond without you having to
 934 |          // specify the expectation and response for this request
 935 | 
 936 |          $httpBackend.expectPOST('/add-msg.py', 'message content').respond(201, '');
 937 |          $rootScope.saveMessage('message content');
 938 |          expect($rootScope.status).toBe('Saving...');
 939 |          $httpBackend.flush();
 940 |          expect($rootScope.status).toBe('');
 941 |        });
 942 | 
 943 | 
 944 |        it('should send auth header', function() {
 945 |          var controller = createController();
 946 |          $httpBackend.flush();
 947 | 
 948 |          $httpBackend.expectPOST('/add-msg.py', undefined, function(headers) {
 949 |            // check if the header was send, if it wasn't the expectation won't
 950 |            // match the request and the test will fail
 951 |            return headers['Authorization'] == 'xxx';
 952 |          }).respond(201, '');
 953 | 
 954 |          $rootScope.saveMessage('whatever');
 955 |          $httpBackend.flush();
 956 |        });
 957 |     });
 958 |    
959 | */ 960 | angular.mock.$HttpBackendProvider = function() { 961 | this.$get = ['$rootScope', createHttpBackendMock]; 962 | }; 963 | 964 | /** 965 | * General factory function for $httpBackend mock. 966 | * Returns instance for unit testing (when no arguments specified): 967 | * - passing through is disabled 968 | * - auto flushing is disabled 969 | * 970 | * Returns instance for e2e testing (when `$delegate` and `$browser` specified): 971 | * - passing through (delegating request to real backend) is enabled 972 | * - auto flushing is enabled 973 | * 974 | * @param {Object=} $delegate Real $httpBackend instance (allow passing through if specified) 975 | * @param {Object=} $browser Auto-flushing enabled if specified 976 | * @return {Object} Instance of $httpBackend mock 977 | */ 978 | function createHttpBackendMock($rootScope, $delegate, $browser) { 979 | var definitions = [], 980 | expectations = [], 981 | responses = [], 982 | responsesPush = angular.bind(responses, responses.push); 983 | 984 | function createResponse(status, data, headers) { 985 | if (angular.isFunction(status)) return status; 986 | 987 | return function() { 988 | return angular.isNumber(status) 989 | ? [status, data, headers] 990 | : [200, status, data]; 991 | }; 992 | } 993 | 994 | // TODO(vojta): change params to: method, url, data, headers, callback 995 | function $httpBackend(method, url, data, callback, headers, timeout, withCredentials) { 996 | var xhr = new MockXhr(), 997 | expectation = expectations[0], 998 | wasExpected = false; 999 | 1000 | function prettyPrint(data) { 1001 | return (angular.isString(data) || angular.isFunction(data) || data instanceof RegExp) 1002 | ? data 1003 | : angular.toJson(data); 1004 | } 1005 | 1006 | function wrapResponse(wrapped) { 1007 | if (!$browser && timeout && timeout.then) timeout.then(handleTimeout); 1008 | 1009 | return handleResponse; 1010 | 1011 | function handleResponse() { 1012 | var response = wrapped.response(method, url, data, headers); 1013 | xhr.$$respHeaders = response[2]; 1014 | callback(response[0], response[1], xhr.getAllResponseHeaders()); 1015 | } 1016 | 1017 | function handleTimeout() { 1018 | for (var i = 0, ii = responses.length; i < ii; i++) { 1019 | if (responses[i] === handleResponse) { 1020 | responses.splice(i, 1); 1021 | callback(-1, undefined, ''); 1022 | break; 1023 | } 1024 | } 1025 | } 1026 | } 1027 | 1028 | if (expectation && expectation.match(method, url)) { 1029 | if (!expectation.matchData(data)) 1030 | throw new Error('Expected ' + expectation + ' with different data\n' + 1031 | 'EXPECTED: ' + prettyPrint(expectation.data) + '\nGOT: ' + data); 1032 | 1033 | if (!expectation.matchHeaders(headers)) 1034 | throw new Error('Expected ' + expectation + ' with different headers\n' + 1035 | 'EXPECTED: ' + prettyPrint(expectation.headers) + '\nGOT: ' + prettyPrint(headers)); 1036 | 1037 | expectations.shift(); 1038 | 1039 | if (expectation.response) { 1040 | responses.push(wrapResponse(expectation)); 1041 | return; 1042 | } 1043 | wasExpected = true; 1044 | } 1045 | 1046 | var i = -1, definition; 1047 | while ((definition = definitions[++i])) { 1048 | if (definition.match(method, url, data, headers || {})) { 1049 | if (definition.response) { 1050 | // if $browser specified, we do auto flush all requests 1051 | ($browser ? $browser.defer : responsesPush)(wrapResponse(definition)); 1052 | } else if (definition.passThrough) { 1053 | $delegate(method, url, data, callback, headers, timeout, withCredentials); 1054 | } else throw Error('No response defined !'); 1055 | return; 1056 | } 1057 | } 1058 | throw wasExpected ? 1059 | new Error('No response defined !') : 1060 | new Error('Unexpected request: ' + method + ' ' + url + '\n' + 1061 | (expectation ? 'Expected ' + expectation : 'No more request expected')); 1062 | } 1063 | 1064 | /** 1065 | * @ngdoc method 1066 | * @name ngMock.$httpBackend#when 1067 | * @methodOf ngMock.$httpBackend 1068 | * @description 1069 | * Creates a new backend definition. 1070 | * 1071 | * @param {string} method HTTP method. 1072 | * @param {string|RegExp} url HTTP url. 1073 | * @param {(string|RegExp|function(string))=} data HTTP request body or function that receives 1074 | * data string and returns true if the data is as expected. 1075 | * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header 1076 | * object and returns true if the headers match the current definition. 1077 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1078 | * request is handled. 1079 | * 1080 | * - respond – `{function([status,] data[, headers])|function(function(method, url, data, headers)}` 1081 | * – The respond method takes a set of static data to be returned or a function that can return 1082 | * an array containing response status (number), response data (string) and response headers 1083 | * (Object). 1084 | */ 1085 | $httpBackend.when = function(method, url, data, headers) { 1086 | var definition = new MockHttpExpectation(method, url, data, headers), 1087 | chain = { 1088 | respond: function(status, data, headers) { 1089 | definition.response = createResponse(status, data, headers); 1090 | } 1091 | }; 1092 | 1093 | if ($browser) { 1094 | chain.passThrough = function() { 1095 | definition.passThrough = true; 1096 | }; 1097 | } 1098 | 1099 | definitions.push(definition); 1100 | return chain; 1101 | }; 1102 | 1103 | /** 1104 | * @ngdoc method 1105 | * @name ngMock.$httpBackend#whenGET 1106 | * @methodOf ngMock.$httpBackend 1107 | * @description 1108 | * Creates a new backend definition for GET requests. For more info see `when()`. 1109 | * 1110 | * @param {string|RegExp} url HTTP url. 1111 | * @param {(Object|function(Object))=} headers HTTP headers. 1112 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1113 | * request is handled. 1114 | */ 1115 | 1116 | /** 1117 | * @ngdoc method 1118 | * @name ngMock.$httpBackend#whenHEAD 1119 | * @methodOf ngMock.$httpBackend 1120 | * @description 1121 | * Creates a new backend definition for HEAD requests. For more info see `when()`. 1122 | * 1123 | * @param {string|RegExp} url HTTP url. 1124 | * @param {(Object|function(Object))=} headers HTTP headers. 1125 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1126 | * request is handled. 1127 | */ 1128 | 1129 | /** 1130 | * @ngdoc method 1131 | * @name ngMock.$httpBackend#whenDELETE 1132 | * @methodOf ngMock.$httpBackend 1133 | * @description 1134 | * Creates a new backend definition for DELETE requests. For more info see `when()`. 1135 | * 1136 | * @param {string|RegExp} url HTTP url. 1137 | * @param {(Object|function(Object))=} headers HTTP headers. 1138 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1139 | * request is handled. 1140 | */ 1141 | 1142 | /** 1143 | * @ngdoc method 1144 | * @name ngMock.$httpBackend#whenPOST 1145 | * @methodOf ngMock.$httpBackend 1146 | * @description 1147 | * Creates a new backend definition for POST requests. For more info see `when()`. 1148 | * 1149 | * @param {string|RegExp} url HTTP url. 1150 | * @param {(string|RegExp|function(string))=} data HTTP request body or function that receives 1151 | * data string and returns true if the data is as expected. 1152 | * @param {(Object|function(Object))=} headers HTTP headers. 1153 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1154 | * request is handled. 1155 | */ 1156 | 1157 | /** 1158 | * @ngdoc method 1159 | * @name ngMock.$httpBackend#whenPUT 1160 | * @methodOf ngMock.$httpBackend 1161 | * @description 1162 | * Creates a new backend definition for PUT requests. For more info see `when()`. 1163 | * 1164 | * @param {string|RegExp} url HTTP url. 1165 | * @param {(string|RegExp|function(string))=} data HTTP request body or function that receives 1166 | * data string and returns true if the data is as expected. 1167 | * @param {(Object|function(Object))=} headers HTTP headers. 1168 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1169 | * request is handled. 1170 | */ 1171 | 1172 | /** 1173 | * @ngdoc method 1174 | * @name ngMock.$httpBackend#whenJSONP 1175 | * @methodOf ngMock.$httpBackend 1176 | * @description 1177 | * Creates a new backend definition for JSONP requests. For more info see `when()`. 1178 | * 1179 | * @param {string|RegExp} url HTTP url. 1180 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1181 | * request is handled. 1182 | */ 1183 | createShortMethods('when'); 1184 | 1185 | 1186 | /** 1187 | * @ngdoc method 1188 | * @name ngMock.$httpBackend#expect 1189 | * @methodOf ngMock.$httpBackend 1190 | * @description 1191 | * Creates a new request expectation. 1192 | * 1193 | * @param {string} method HTTP method. 1194 | * @param {string|RegExp} url HTTP url. 1195 | * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that 1196 | * receives data string and returns true if the data is as expected, or Object if request body 1197 | * is in JSON format. 1198 | * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header 1199 | * object and returns true if the headers match the current expectation. 1200 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1201 | * request is handled. 1202 | * 1203 | * - respond – `{function([status,] data[, headers])|function(function(method, url, data, headers)}` 1204 | * – The respond method takes a set of static data to be returned or a function that can return 1205 | * an array containing response status (number), response data (string) and response headers 1206 | * (Object). 1207 | */ 1208 | $httpBackend.expect = function(method, url, data, headers) { 1209 | var expectation = new MockHttpExpectation(method, url, data, headers); 1210 | expectations.push(expectation); 1211 | return { 1212 | respond: function(status, data, headers) { 1213 | expectation.response = createResponse(status, data, headers); 1214 | } 1215 | }; 1216 | }; 1217 | 1218 | 1219 | /** 1220 | * @ngdoc method 1221 | * @name ngMock.$httpBackend#expectGET 1222 | * @methodOf ngMock.$httpBackend 1223 | * @description 1224 | * Creates a new request expectation for GET requests. For more info see `expect()`. 1225 | * 1226 | * @param {string|RegExp} url HTTP url. 1227 | * @param {Object=} headers HTTP headers. 1228 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1229 | * request is handled. See #expect for more info. 1230 | */ 1231 | 1232 | /** 1233 | * @ngdoc method 1234 | * @name ngMock.$httpBackend#expectHEAD 1235 | * @methodOf ngMock.$httpBackend 1236 | * @description 1237 | * Creates a new request expectation for HEAD requests. For more info see `expect()`. 1238 | * 1239 | * @param {string|RegExp} url HTTP url. 1240 | * @param {Object=} headers HTTP headers. 1241 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1242 | * request is handled. 1243 | */ 1244 | 1245 | /** 1246 | * @ngdoc method 1247 | * @name ngMock.$httpBackend#expectDELETE 1248 | * @methodOf ngMock.$httpBackend 1249 | * @description 1250 | * Creates a new request expectation for DELETE requests. For more info see `expect()`. 1251 | * 1252 | * @param {string|RegExp} url HTTP url. 1253 | * @param {Object=} headers HTTP headers. 1254 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1255 | * request is handled. 1256 | */ 1257 | 1258 | /** 1259 | * @ngdoc method 1260 | * @name ngMock.$httpBackend#expectPOST 1261 | * @methodOf ngMock.$httpBackend 1262 | * @description 1263 | * Creates a new request expectation for POST requests. For more info see `expect()`. 1264 | * 1265 | * @param {string|RegExp} url HTTP url. 1266 | * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that 1267 | * receives data string and returns true if the data is as expected, or Object if request body 1268 | * is in JSON format. 1269 | * @param {Object=} headers HTTP headers. 1270 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1271 | * request is handled. 1272 | */ 1273 | 1274 | /** 1275 | * @ngdoc method 1276 | * @name ngMock.$httpBackend#expectPUT 1277 | * @methodOf ngMock.$httpBackend 1278 | * @description 1279 | * Creates a new request expectation for PUT requests. For more info see `expect()`. 1280 | * 1281 | * @param {string|RegExp} url HTTP url. 1282 | * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that 1283 | * receives data string and returns true if the data is as expected, or Object if request body 1284 | * is in JSON format. 1285 | * @param {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#expectPATCH 1293 | * @methodOf ngMock.$httpBackend 1294 | * @description 1295 | * Creates a new request expectation for PATCH requests. For more info see `expect()`. 1296 | * 1297 | * @param {string|RegExp} url HTTP url. 1298 | * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that 1299 | * receives data string and returns true if the data is as expected, or Object if request body 1300 | * is in JSON format. 1301 | * @param {Object=} headers HTTP headers. 1302 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1303 | * request is handled. 1304 | */ 1305 | 1306 | /** 1307 | * @ngdoc method 1308 | * @name ngMock.$httpBackend#expectJSONP 1309 | * @methodOf ngMock.$httpBackend 1310 | * @description 1311 | * Creates a new request expectation for JSONP requests. For more info see `expect()`. 1312 | * 1313 | * @param {string|RegExp} url HTTP url. 1314 | * @returns {requestHandler} Returns an object with `respond` method that control how a matched 1315 | * request is handled. 1316 | */ 1317 | createShortMethods('expect'); 1318 | 1319 | 1320 | /** 1321 | * @ngdoc method 1322 | * @name ngMock.$httpBackend#flush 1323 | * @methodOf ngMock.$httpBackend 1324 | * @description 1325 | * Flushes all pending requests using the trained responses. 1326 | * 1327 | * @param {number=} count Number of responses to flush (in the order they arrived). If undefined, 1328 | * all pending requests will be flushed. If there are no pending requests when the flush method 1329 | * is called an exception is thrown (as this typically a sign of programming error). 1330 | */ 1331 | $httpBackend.flush = function(count) { 1332 | $rootScope.$digest(); 1333 | if (!responses.length) throw Error('No pending request to flush !'); 1334 | 1335 | if (angular.isDefined(count)) { 1336 | while (count--) { 1337 | if (!responses.length) throw Error('No more pending request to flush !'); 1338 | responses.shift()(); 1339 | } 1340 | } else { 1341 | while (responses.length) { 1342 | responses.shift()(); 1343 | } 1344 | } 1345 | $httpBackend.verifyNoOutstandingExpectation(); 1346 | }; 1347 | 1348 | 1349 | /** 1350 | * @ngdoc method 1351 | * @name ngMock.$httpBackend#verifyNoOutstandingExpectation 1352 | * @methodOf ngMock.$httpBackend 1353 | * @description 1354 | * Verifies that all of the requests defined via the `expect` api were made. If any of the 1355 | * requests were not made, verifyNoOutstandingExpectation throws an exception. 1356 | * 1357 | * Typically, you would call this method following each test case that asserts requests using an 1358 | * "afterEach" clause. 1359 | * 1360 | *
1361 |    *   afterEach($httpBackend.verifyNoOutstandingExpectation);
1362 |    * 
1363 | */ 1364 | $httpBackend.verifyNoOutstandingExpectation = function() { 1365 | $rootScope.$digest(); 1366 | if (expectations.length) { 1367 | throw new Error('Unsatisfied requests: ' + expectations.join(', ')); 1368 | } 1369 | }; 1370 | 1371 | 1372 | /** 1373 | * @ngdoc method 1374 | * @name ngMock.$httpBackend#verifyNoOutstandingRequest 1375 | * @methodOf ngMock.$httpBackend 1376 | * @description 1377 | * Verifies that there are no outstanding requests that need to be flushed. 1378 | * 1379 | * Typically, you would call this method following each test case that asserts requests using an 1380 | * "afterEach" clause. 1381 | * 1382 | *
1383 |    *   afterEach($httpBackend.verifyNoOutstandingRequest);
1384 |    * 
1385 | */ 1386 | $httpBackend.verifyNoOutstandingRequest = function() { 1387 | if (responses.length) { 1388 | throw Error('Unflushed requests: ' + responses.length); 1389 | } 1390 | }; 1391 | 1392 | 1393 | /** 1394 | * @ngdoc method 1395 | * @name ngMock.$httpBackend#resetExpectations 1396 | * @methodOf ngMock.$httpBackend 1397 | * @description 1398 | * Resets all request expectations, but preserves all backend definitions. Typically, you would 1399 | * call resetExpectations during a multiple-phase test when you want to reuse the same instance of 1400 | * $httpBackend mock. 1401 | */ 1402 | $httpBackend.resetExpectations = function() { 1403 | expectations.length = 0; 1404 | responses.length = 0; 1405 | }; 1406 | 1407 | return $httpBackend; 1408 | 1409 | 1410 | function createShortMethods(prefix) { 1411 | angular.forEach(['GET', 'DELETE', 'JSONP'], function(method) { 1412 | $httpBackend[prefix + method] = function(url, headers) { 1413 | return $httpBackend[prefix](method, url, undefined, headers) 1414 | } 1415 | }); 1416 | 1417 | angular.forEach(['PUT', 'POST', 'PATCH'], function(method) { 1418 | $httpBackend[prefix + method] = function(url, data, headers) { 1419 | return $httpBackend[prefix](method, url, data, headers) 1420 | } 1421 | }); 1422 | } 1423 | } 1424 | 1425 | function MockHttpExpectation(method, url, data, headers) { 1426 | 1427 | this.data = data; 1428 | this.headers = headers; 1429 | 1430 | this.match = function(m, u, d, h) { 1431 | if (method != m) return false; 1432 | if (!this.matchUrl(u)) return false; 1433 | if (angular.isDefined(d) && !this.matchData(d)) return false; 1434 | if (angular.isDefined(h) && !this.matchHeaders(h)) return false; 1435 | return true; 1436 | }; 1437 | 1438 | this.matchUrl = function(u) { 1439 | if (!url) return true; 1440 | if (angular.isFunction(url.test)) return url.test(u); 1441 | return url == u; 1442 | }; 1443 | 1444 | this.matchHeaders = function(h) { 1445 | if (angular.isUndefined(headers)) return true; 1446 | if (angular.isFunction(headers)) return headers(h); 1447 | return angular.equals(headers, h); 1448 | }; 1449 | 1450 | this.matchData = function(d) { 1451 | if (angular.isUndefined(data)) return true; 1452 | if (data && angular.isFunction(data.test)) return data.test(d); 1453 | if (data && angular.isFunction(data)) return data(d); 1454 | if (data && !angular.isString(data)) return angular.toJson(data) == d; 1455 | return data == d; 1456 | }; 1457 | 1458 | this.toString = function() { 1459 | return method + ' ' + url; 1460 | }; 1461 | } 1462 | 1463 | function MockXhr() { 1464 | 1465 | // hack for testing $http, $httpBackend 1466 | MockXhr.$$lastInstance = this; 1467 | 1468 | this.open = function(method, url, async) { 1469 | this.$$method = method; 1470 | this.$$url = url; 1471 | this.$$async = async; 1472 | this.$$reqHeaders = {}; 1473 | this.$$respHeaders = {}; 1474 | }; 1475 | 1476 | this.send = function(data) { 1477 | this.$$data = data; 1478 | }; 1479 | 1480 | this.setRequestHeader = function(key, value) { 1481 | this.$$reqHeaders[key] = value; 1482 | }; 1483 | 1484 | this.getResponseHeader = function(name) { 1485 | // the lookup must be case insensitive, that's why we try two quick lookups and full scan at last 1486 | var header = this.$$respHeaders[name]; 1487 | if (header) return header; 1488 | 1489 | name = angular.lowercase(name); 1490 | header = this.$$respHeaders[name]; 1491 | if (header) return header; 1492 | 1493 | header = undefined; 1494 | angular.forEach(this.$$respHeaders, function(headerVal, headerName) { 1495 | if (!header && angular.lowercase(headerName) == name) header = headerVal; 1496 | }); 1497 | return header; 1498 | }; 1499 | 1500 | this.getAllResponseHeaders = function() { 1501 | var lines = []; 1502 | 1503 | angular.forEach(this.$$respHeaders, function(value, key) { 1504 | lines.push(key + ': ' + value); 1505 | }); 1506 | return lines.join('\n'); 1507 | }; 1508 | 1509 | this.abort = angular.noop; 1510 | } 1511 | 1512 | 1513 | /** 1514 | * @ngdoc function 1515 | * @name ngMock.$timeout 1516 | * @description 1517 | * 1518 | * This service is just a simple decorator for {@link ng.$timeout $timeout} service 1519 | * that adds a "flush" and "verifyNoPendingTasks" methods. 1520 | */ 1521 | 1522 | angular.mock.$TimeoutDecorator = function($delegate, $browser) { 1523 | 1524 | /** 1525 | * @ngdoc method 1526 | * @name ngMock.$timeout#flush 1527 | * @methodOf ngMock.$timeout 1528 | * @description 1529 | * 1530 | * Flushes the queue of pending tasks. 1531 | * 1532 | * @param {number=} delay maximum timeout amount to flush up until 1533 | */ 1534 | $delegate.flush = function(delay) { 1535 | $browser.defer.flush(delay); 1536 | }; 1537 | 1538 | /** 1539 | * @ngdoc method 1540 | * @name ngMock.$timeout#flushNext 1541 | * @methodOf ngMock.$timeout 1542 | * @description 1543 | * 1544 | * Flushes the next timeout in the queue and compares it to the provided delay 1545 | * 1546 | * @param {number=} expectedDelay the delay value that will be asserted against the delay of the next timeout function 1547 | */ 1548 | $delegate.flushNext = function(expectedDelay) { 1549 | $browser.defer.flushNext(expectedDelay); 1550 | }; 1551 | 1552 | /** 1553 | * @ngdoc method 1554 | * @name ngMock.$timeout#verifyNoPendingTasks 1555 | * @methodOf ngMock.$timeout 1556 | * @description 1557 | * 1558 | * Verifies that there are no pending tasks that need to be flushed. 1559 | */ 1560 | $delegate.verifyNoPendingTasks = function() { 1561 | if ($browser.deferredFns.length) { 1562 | throw new Error('Deferred tasks to flush (' + $browser.deferredFns.length + '): ' + 1563 | formatPendingTasksAsString($browser.deferredFns)); 1564 | } 1565 | }; 1566 | 1567 | function formatPendingTasksAsString(tasks) { 1568 | var result = []; 1569 | angular.forEach(tasks, function(task) { 1570 | result.push('{id: ' + task.id + ', ' + 'time: ' + task.time + '}'); 1571 | }); 1572 | 1573 | return result.join(', '); 1574 | } 1575 | 1576 | return $delegate; 1577 | }; 1578 | 1579 | /** 1580 | * 1581 | */ 1582 | angular.mock.$RootElementProvider = function() { 1583 | this.$get = function() { 1584 | return angular.element('
'); 1585 | } 1586 | }; 1587 | 1588 | /** 1589 | * @ngdoc overview 1590 | * @name ngMock 1591 | * @description 1592 | * 1593 | * The `ngMock` is an angular module which is used with `ng` module and adds unit-test configuration as well as useful 1594 | * mocks to the {@link AUTO.$injector $injector}. 1595 | */ 1596 | angular.module('ngMock', ['ng']).provider({ 1597 | $browser: angular.mock.$BrowserProvider, 1598 | $exceptionHandler: angular.mock.$ExceptionHandlerProvider, 1599 | $log: angular.mock.$LogProvider, 1600 | $httpBackend: angular.mock.$HttpBackendProvider, 1601 | $rootElement: angular.mock.$RootElementProvider 1602 | }).config(function($provide) { 1603 | $provide.decorator('$timeout', angular.mock.$TimeoutDecorator); 1604 | }); 1605 | 1606 | /** 1607 | * @ngdoc overview 1608 | * @name ngMockE2E 1609 | * @description 1610 | * 1611 | * The `ngMockE2E` is an angular module which contains mocks suitable for end-to-end testing. 1612 | * Currently there is only one mock present in this module - 1613 | * the {@link ngMockE2E.$httpBackend e2e $httpBackend} mock. 1614 | */ 1615 | angular.module('ngMockE2E', ['ng']).config(function($provide) { 1616 | $provide.decorator('$httpBackend', angular.mock.e2e.$httpBackendDecorator); 1617 | }); 1618 | 1619 | /** 1620 | * @ngdoc object 1621 | * @name ngMockE2E.$httpBackend 1622 | * @description 1623 | * Fake HTTP backend implementation suitable for end-to-end testing or backend-less development of 1624 | * applications that use the {@link ng.$http $http service}. 1625 | * 1626 | * *Note*: For fake http backend implementation suitable for unit testing please see 1627 | * {@link ngMock.$httpBackend unit-testing $httpBackend mock}. 1628 | * 1629 | * This implementation can be used to respond with static or dynamic responses via the `when` api 1630 | * and its shortcuts (`whenGET`, `whenPOST`, etc) and optionally pass through requests to the 1631 | * real $httpBackend for specific requests (e.g. to interact with certain remote apis or to fetch 1632 | * templates from a webserver). 1633 | * 1634 | * As opposed to unit-testing, in an end-to-end testing scenario or in scenario when an application 1635 | * is being developed with the real backend api replaced with a mock, it is often desirable for 1636 | * certain category of requests to bypass the mock and issue a real http request (e.g. to fetch 1637 | * templates or static files from the webserver). To configure the backend with this behavior 1638 | * use the `passThrough` request handler of `when` instead of `respond`. 1639 | * 1640 | * Additionally, we don't want to manually have to flush mocked out requests like we do during unit 1641 | * testing. For this reason the e2e $httpBackend automatically flushes mocked out requests 1642 | * automatically, closely simulating the behavior of the XMLHttpRequest object. 1643 | * 1644 | * To setup the application to run with this http backend, you have to create a module that depends 1645 | * on the `ngMockE2E` and your application modules and defines the fake backend: 1646 | * 1647 | *
1648 |  *   myAppDev = angular.module('myAppDev', ['myApp', 'ngMockE2E']);
1649 |  *   myAppDev.run(function($httpBackend) {
1650 |  *     phones = [{name: 'phone1'}, {name: 'phone2'}];
1651 |  *
1652 |  *     // returns the current list of phones
1653 |  *     $httpBackend.whenGET('/phones').respond(phones);
1654 |  *
1655 |  *     // adds a new phone to the phones array
1656 |  *     $httpBackend.whenPOST('/phones').respond(function(method, url, data) {
1657 |  *       phones.push(angular.fromJson(data));
1658 |  *     });
1659 |  *     $httpBackend.whenGET(/^\/templates\//).passThrough();
1660 |  *     //...
1661 |  *   });
1662 |  * 
1663 | * 1664 | * Afterwards, bootstrap your app with this new module. 1665 | */ 1666 | 1667 | /** 1668 | * @ngdoc method 1669 | * @name ngMockE2E.$httpBackend#when 1670 | * @methodOf ngMockE2E.$httpBackend 1671 | * @description 1672 | * Creates a new backend definition. 1673 | * 1674 | * @param {string} method HTTP method. 1675 | * @param {string|RegExp} url HTTP url. 1676 | * @param {(string|RegExp)=} data HTTP request body. 1677 | * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header 1678 | * object and returns true if the headers match the current definition. 1679 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that 1680 | * control how a matched request is handled. 1681 | * 1682 | * - respond – `{function([status,] data[, headers])|function(function(method, url, data, headers)}` 1683 | * – The respond method takes a set of static data to be returned or a function that can return 1684 | * an array containing response status (number), response data (string) and response headers 1685 | * (Object). 1686 | * - passThrough – `{function()}` – Any request matching a backend definition with `passThrough` 1687 | * handler, will be pass through to the real backend (an XHR request will be made to the 1688 | * server. 1689 | */ 1690 | 1691 | /** 1692 | * @ngdoc method 1693 | * @name ngMockE2E.$httpBackend#whenGET 1694 | * @methodOf ngMockE2E.$httpBackend 1695 | * @description 1696 | * Creates a new backend definition for GET requests. For more info see `when()`. 1697 | * 1698 | * @param {string|RegExp} url HTTP url. 1699 | * @param {(Object|function(Object))=} headers HTTP headers. 1700 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that 1701 | * control how a matched request is handled. 1702 | */ 1703 | 1704 | /** 1705 | * @ngdoc method 1706 | * @name ngMockE2E.$httpBackend#whenHEAD 1707 | * @methodOf ngMockE2E.$httpBackend 1708 | * @description 1709 | * Creates a new backend definition for HEAD requests. For more info see `when()`. 1710 | * 1711 | * @param {string|RegExp} url HTTP url. 1712 | * @param {(Object|function(Object))=} headers HTTP headers. 1713 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that 1714 | * control how a matched request is handled. 1715 | */ 1716 | 1717 | /** 1718 | * @ngdoc method 1719 | * @name ngMockE2E.$httpBackend#whenDELETE 1720 | * @methodOf ngMockE2E.$httpBackend 1721 | * @description 1722 | * Creates a new backend definition for DELETE requests. For more info see `when()`. 1723 | * 1724 | * @param {string|RegExp} url HTTP url. 1725 | * @param {(Object|function(Object))=} headers HTTP headers. 1726 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that 1727 | * control how a matched request is handled. 1728 | */ 1729 | 1730 | /** 1731 | * @ngdoc method 1732 | * @name ngMockE2E.$httpBackend#whenPOST 1733 | * @methodOf ngMockE2E.$httpBackend 1734 | * @description 1735 | * Creates a new backend definition for POST requests. For more info see `when()`. 1736 | * 1737 | * @param {string|RegExp} url HTTP url. 1738 | * @param {(string|RegExp)=} data HTTP request body. 1739 | * @param {(Object|function(Object))=} headers HTTP headers. 1740 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that 1741 | * control how a matched request is handled. 1742 | */ 1743 | 1744 | /** 1745 | * @ngdoc method 1746 | * @name ngMockE2E.$httpBackend#whenPUT 1747 | * @methodOf ngMockE2E.$httpBackend 1748 | * @description 1749 | * Creates a new backend definition for PUT requests. For more info see `when()`. 1750 | * 1751 | * @param {string|RegExp} url HTTP url. 1752 | * @param {(string|RegExp)=} data HTTP request body. 1753 | * @param {(Object|function(Object))=} headers HTTP headers. 1754 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that 1755 | * control how a matched request is handled. 1756 | */ 1757 | 1758 | /** 1759 | * @ngdoc method 1760 | * @name ngMockE2E.$httpBackend#whenPATCH 1761 | * @methodOf ngMockE2E.$httpBackend 1762 | * @description 1763 | * Creates a new backend definition for PATCH requests. For more info see `when()`. 1764 | * 1765 | * @param {string|RegExp} url HTTP url. 1766 | * @param {(string|RegExp)=} data HTTP request body. 1767 | * @param {(Object|function(Object))=} headers HTTP headers. 1768 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that 1769 | * control how a matched request is handled. 1770 | */ 1771 | 1772 | /** 1773 | * @ngdoc method 1774 | * @name ngMockE2E.$httpBackend#whenJSONP 1775 | * @methodOf ngMockE2E.$httpBackend 1776 | * @description 1777 | * Creates a new backend definition for JSONP requests. For more info see `when()`. 1778 | * 1779 | * @param {string|RegExp} url HTTP url. 1780 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that 1781 | * control how a matched request is handled. 1782 | */ 1783 | angular.mock.e2e = {}; 1784 | angular.mock.e2e.$httpBackendDecorator = ['$rootScope', '$delegate', '$browser', createHttpBackendMock]; 1785 | 1786 | 1787 | angular.mock.clearDataCache = function() { 1788 | var key, 1789 | cache = angular.element.cache; 1790 | 1791 | for(key in cache) { 1792 | if (cache.hasOwnProperty(key)) { 1793 | var handle = cache[key].handle; 1794 | 1795 | handle && angular.element(handle.elem).off(); 1796 | delete cache[key]; 1797 | } 1798 | } 1799 | }; 1800 | 1801 | 1802 | 1803 | (window.jasmine || window.mocha) && (function(window) { 1804 | 1805 | var currentSpec = null; 1806 | 1807 | beforeEach(function() { 1808 | currentSpec = this; 1809 | }); 1810 | 1811 | afterEach(function() { 1812 | var injector = currentSpec.$injector; 1813 | 1814 | currentSpec.$injector = null; 1815 | currentSpec.$modules = null; 1816 | currentSpec = null; 1817 | 1818 | if (injector) { 1819 | injector.get('$rootElement').off(); 1820 | injector.get('$browser').pollFns.length = 0; 1821 | } 1822 | 1823 | angular.mock.clearDataCache(); 1824 | 1825 | // clean up jquery's fragment cache 1826 | angular.forEach(angular.element.fragments, function(val, key) { 1827 | delete angular.element.fragments[key]; 1828 | }); 1829 | 1830 | MockXhr.$$lastInstance = null; 1831 | 1832 | angular.forEach(angular.callbacks, function(val, key) { 1833 | delete angular.callbacks[key]; 1834 | }); 1835 | angular.callbacks.counter = 0; 1836 | }); 1837 | 1838 | function isSpecRunning() { 1839 | return currentSpec && (window.mocha || currentSpec.queue.running); 1840 | } 1841 | 1842 | /** 1843 | * @ngdoc function 1844 | * @name angular.mock.module 1845 | * @description 1846 | * 1847 | * *NOTE*: This function is also published on window for easy access.
1848 | * 1849 | * This function registers a module configuration code. It collects the configuration information 1850 | * which will be used when the injector is created by {@link angular.mock.inject inject}. 1851 | * 1852 | * See {@link angular.mock.inject inject} for usage example 1853 | * 1854 | * @param {...(string|Function|Object)} fns any number of modules which are represented as string 1855 | * aliases or as anonymous module initialization functions. The modules are used to 1856 | * configure the injector. The 'ng' and 'ngMock' modules are automatically loaded. If an 1857 | * object literal is passed they will be register as values in the module, the key being 1858 | * the module name and the value being what is returned. 1859 | */ 1860 | window.module = angular.mock.module = function() { 1861 | var moduleFns = Array.prototype.slice.call(arguments, 0); 1862 | return isSpecRunning() ? workFn() : workFn; 1863 | ///////////////////// 1864 | function workFn() { 1865 | if (currentSpec.$injector) { 1866 | throw Error('Injector already created, can not register a module!'); 1867 | } else { 1868 | var modules = currentSpec.$modules || (currentSpec.$modules = []); 1869 | angular.forEach(moduleFns, function(module) { 1870 | if (angular.isObject(module) && !angular.isArray(module)) { 1871 | modules.push(function($provide) { 1872 | angular.forEach(module, function(value, key) { 1873 | $provide.value(key, value); 1874 | }); 1875 | }); 1876 | } else { 1877 | modules.push(module); 1878 | } 1879 | }); 1880 | } 1881 | } 1882 | }; 1883 | 1884 | /** 1885 | * @ngdoc function 1886 | * @name angular.mock.inject 1887 | * @description 1888 | * 1889 | * *NOTE*: This function is also published on window for easy access.
1890 | * 1891 | * The inject function wraps a function into an injectable function. The inject() creates new 1892 | * instance of {@link AUTO.$injector $injector} per test, which is then used for 1893 | * resolving references. 1894 | * 1895 | * See also {@link angular.mock.module module} 1896 | * 1897 | * Example of what a typical jasmine tests looks like with the inject method. 1898 | *
1899 |    *
1900 |    *   angular.module('myApplicationModule', [])
1901 |    *       .value('mode', 'app')
1902 |    *       .value('version', 'v1.0.1');
1903 |    *
1904 |    *
1905 |    *   describe('MyApp', function() {
1906 |    *
1907 |    *     // You need to load modules that you want to test,
1908 |    *     // it loads only the "ng" module by default.
1909 |    *     beforeEach(module('myApplicationModule'));
1910 |    *
1911 |    *
1912 |    *     // inject() is used to inject arguments of all given functions
1913 |    *     it('should provide a version', inject(function(mode, version) {
1914 |    *       expect(version).toEqual('v1.0.1');
1915 |    *       expect(mode).toEqual('app');
1916 |    *     }));
1917 |    *
1918 |    *
1919 |    *     // The inject and module method can also be used inside of the it or beforeEach
1920 |    *     it('should override a version and test the new version is injected', function() {
1921 |    *       // module() takes functions or strings (module aliases)
1922 |    *       module(function($provide) {
1923 |    *         $provide.value('version', 'overridden'); // override version here
1924 |    *       });
1925 |    *
1926 |    *       inject(function(version) {
1927 |    *         expect(version).toEqual('overridden');
1928 |    *       });
1929 |    *     ));
1930 |    *   });
1931 |    *
1932 |    * 
1933 | * 1934 | * @param {...Function} fns any number of functions which will be injected using the injector. 1935 | */ 1936 | window.inject = angular.mock.inject = function() { 1937 | var blockFns = Array.prototype.slice.call(arguments, 0); 1938 | var errorForStack = new Error('Declaration Location'); 1939 | return isSpecRunning() ? workFn() : workFn; 1940 | ///////////////////// 1941 | function workFn() { 1942 | var modules = currentSpec.$modules || []; 1943 | 1944 | modules.unshift('ngMock'); 1945 | modules.unshift('ng'); 1946 | var injector = currentSpec.$injector; 1947 | if (!injector) { 1948 | injector = currentSpec.$injector = angular.injector(modules); 1949 | } 1950 | for(var i = 0, ii = blockFns.length; i < ii; i++) { 1951 | try { 1952 | injector.invoke(blockFns[i] || angular.noop, this); 1953 | } catch (e) { 1954 | if(e.stack && errorForStack) e.stack += '\n' + errorForStack.stack; 1955 | throw e; 1956 | } finally { 1957 | errorForStack = null; 1958 | } 1959 | } 1960 | } 1961 | }; 1962 | })(window); 1963 | -------------------------------------------------------------------------------- /bower_components/angular-mocks/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-mocks", 3 | "version": "1.2.0-rc.2", 4 | "main": "./angular-mocks.js", 5 | "dependencies": { 6 | "angular": "1.2.0-rc.2" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /bower_components/angular/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular", 3 | "version": "1.2.0-rc.2", 4 | "main": "./angular.js", 5 | "dependencies": {}, 6 | "homepage": "https://github.com/angular/bower-angular", 7 | "_release": "1.2.0-rc.2", 8 | "_resolution": { 9 | "type": "version", 10 | "tag": "v1.2.0-rc.2", 11 | "commit": "53fbc69eae679b7a57fb3d1a413e768ba92696b5" 12 | }, 13 | "_source": "git://github.com/angular/bower-angular.git", 14 | "_target": "~1.x", 15 | "_originalSource": "angular" 16 | } -------------------------------------------------------------------------------- /bower_components/angular/angular.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | AngularJS v1.2.0-rc.2 3 | (c) 2010-2012 Google, Inc. http://angularjs.org 4 | License: MIT 5 | */ 6 | (function(Y,T,s){'use strict';function P(a){return function(){var b=arguments[0],c,b="["+(a?a+":":"")+b+"] http://errors.angularjs.org/undefined/"+(a?a+"/":"")+b;for(c=1;c").append(a).html();try{return 3===a[0].nodeType?J(c):c.match(/^(<[^>]+>)/)[1].replace(/^<([\w\-]+)/,function(b,a){return"<"+J(a)})}catch(d){return J(c)}}function Jb(a){try{return decodeURIComponent(a)}catch(b){}}function Kb(a){var b={},c,d;q((a||"").split("&"),function(a){a&&(c=a.split("="),d=Jb(c[0]),z(d)&&(a=z(c[1])? 15 | Jb(c[1]):!0,b[d]?D(b[d])?b[d].push(a):b[d]=[b[d],a]:b[d]=a))});return b}function Lb(a){var b=[];q(a,function(a,d){D(a)?q(a,function(a){b.push(ua(d,!0)+(!0===a?"":"="+ua(a,!0)))}):b.push(ua(d,!0)+(!0===a?"":"="+ua(a,!0)))});return b.length?b.join("&"):""}function ob(a){return ua(a,!0).replace(/%26/gi,"&").replace(/%3D/gi,"=").replace(/%2B/gi,"+")}function ua(a,b){return encodeURIComponent(a).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,b?"%20":"+")} 16 | function Hc(a,b){function c(a){a&&d.push(a)}var d=[a],e,g,h=["ng:app","ng-app","x-ng-app","data-ng-app"],f=/\sng[:\-]app(:\s*([\w\d_]+);?)?\s/;q(h,function(b){h[b]=!0;c(T.getElementById(b));b=b.replace(":","\\:");a.querySelectorAll&&(q(a.querySelectorAll("."+b),c),q(a.querySelectorAll("."+b+"\\:"),c),q(a.querySelectorAll("["+b+"]"),c))});q(d,function(b){if(!e){var a=f.exec(" "+b.className+" ");a?(e=b,g=(a[2]||"").replace(/\s+/g,",")):q(b.attributes,function(a){!e&&h[a.name]&&(e=b,g=a.value)})}}); 17 | e&&b(e,g?[g]:[])}function Mb(a,b){var c=function(){a=w(a);if(a.injector()){var c=a[0]===T?"document":ia(a);throw Wa("btstrpd",c);}b=b||[];b.unshift(["$provide",function(b){b.value("$rootElement",a)}]);b.unshift("ng");c=Nb(b);c.invoke(["$rootScope","$rootElement","$compile","$injector","$animate",function(b,a,c,d,e){b.$apply(function(){a.data("$injector",d);c(a)(b)});e.enabled(!0)}]);return c},d=/^NG_DEFER_BOOTSTRAP!/;if(Y&&!d.test(Y.name))return c();Y.name=Y.name.replace(d,"");Ha.resumeBootstrap= 18 | function(a){q(a,function(a){b.push(a)});c()}}function pb(a,b){b=b||"_";return a.replace(Ic,function(a,d){return(d?b:"")+a.toLowerCase()})}function qb(a,b,c){if(!a)throw Wa("areq",b||"?",c||"required");return a}function Ia(a,b,c){c&&D(a)&&(a=a[a.length-1]);qb(B(a),b,"not a function, got "+(a&&"object"==typeof a?a.constructor.name||"Object":typeof a));return a}function rb(a,b,c){if(!b)return a;b=b.split(".");for(var d,e=a,g=b.length,h=0;h "+a;b.removeChild(b.firstChild);ub(this,b.childNodes);w(T.createDocumentFragment()).append(this)}else ub(this, 22 | a)}function vb(a){return a.cloneNode(!0)}function Ka(a){Ob(a);var b=0;for(a=a.childNodes||[];b=Q?(c.preventDefault=null,c.stopPropagation=null,c.isDefaultPrevented=null):(delete c.preventDefault,delete c.stopPropagation,delete c.isDefaultPrevented)}; 26 | c.elem=a;return c}function za(a){var b=typeof a,c;"object"==b&&null!==a?"function"==typeof(c=a.$$hashKey)?c=a.$$hashKey():c===s&&(c=a.$$hashKey=Ta()):c=a;return b+":"+c}function Ma(a){q(a,this.put,this)}function Wb(a){var b,c;"function"==typeof a?(b=a.$inject)||(b=[],a.length&&(c=a.toString().replace(Oc,""),c=c.match(Pc),q(c[1].split(Qc),function(a){a.replace(Rc,function(a,c,d){b.push(d)})})),a.$inject=b):D(a)?(c=a.length-1,Ia(a[c],"fn"),b=a.slice(0,c)):Ia(a,"fn",!0);return b}function Nb(a){function b(a){return function(b, 27 | c){if(U(b))q(b,Gb(a));else return a(b,c)}}function c(a,b){if(B(b)||D(b))b=p.instantiate(b);if(!b.$get)throw Na("pget",a);return m[a+f]=b}function d(a,b){return c(a,{$get:b})}function e(a){var b=[];q(a,function(a){if(!l.get(a)){l.put(a,!0);try{if(H(a)){var c=Oa(a);b=b.concat(e(c.requires)).concat(c._runBlocks);for(var d=c._invokeQueue,c=0,f=d.length;c 4096 bytes)!"));else{if(k.cookie!==ba)for(ba=k.cookie,d=ba.split("; "),Z={},f=0;fl&&this.remove(n.key),b},get:function(a){var b=m[a];if(b)return e(b),k[a]},remove:function(a){var b=m[a];b&&(b==p&&(p=b.p),b==n&&(n=b.n),g(b.n,b.p),delete m[a],delete k[a],h--)},removeAll:function(){k={};h=0;m={};p=n=null},destroy:function(){m=f=k=null;delete b[a]},info:function(){return E({},f,{size:h})}}}var b={};a.info=function(){var a={};q(b,function(b,e){a[e]=b.info()});return a};a.get=function(a){return b[a]};return a}} 38 | function Xc(){this.$get=["$cacheFactory",function(a){return a("templates")}]}function Xb(a){var b={},c="Directive",d=/^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/,e=/(([\d\w\-_]+)(?:\:([^;]+))?;?)/,g=/^\s*(https?|ftp|mailto|file):/,h=/^\s*(https?|ftp|file):|data:image\//,f=/^(on[a-z]*|formaction)$/;this.directive=function l(d,e){H(d)?(qb(e,"directiveFactory"),b.hasOwnProperty(d)||(b[d]=[],a.factory(d+c,["$injector","$exceptionHandler",function(a,c){var e=[];q(b[d],function(b){try{var f=a.invoke(b);B(f)? 39 | f={compile:$(f)}:!f.compile&&f.link&&(f.compile=$(f.link));f.priority=f.priority||0;f.name=f.name||d;f.require=f.require||f.controller&&f.name;f.restrict=f.restrict||"A";e.push(f)}catch(g){c(g)}});return e}])),b[d].push(e)):q(d,Gb(l));return this};this.aHrefSanitizationWhitelist=function(a){return z(a)?(g=a,this):g};this.imgSrcSanitizationWhitelist=function(a){return z(a)?(h=a,this):h};this.$get=["$injector","$interpolate","$exceptionHandler","$http","$templateCache","$parse","$controller","$rootScope", 40 | "$document","$sce","$$urlUtils","$animate",function(a,m,p,n,t,r,y,x,R,N,u,v){function F(a,b,c,d){a instanceof w||(a=w(a));q(a,function(b,c){3==b.nodeType&&b.nodeValue.match(/\S+/)&&(a[c]=w(b).wrap("").parent()[0])});var e=Z(a,b,a,c,d);return function(b,c){qb(b,"scope");for(var d=c?Pa.clone.call(a):a,f=0,g=d.length;fu.priority)break;if(O=u.scope)C("isolated scope",v,u,K),U(O)&&(I(K,"ng-isolate-scope"),v=u),I(K,"ng-scope"),x=x||u;R=u.name;if(O=u.controller)pa= 48 | pa||{},C("'"+R+"' controller",pa[R],u,K),pa[R]=u;if(O=u.transclude)C("transclusion",N,u,K),N=u,n=u.priority,"element"==O?(O=W(b,A,z),K=c.$$element=w(T.createComment(" "+R+": "+c[R]+" ")),b=K[0],bb(e,w(ta.call(O,0)),b),qa=F(O,d,n,f&&f.name)):(O=w(vb(b)).contents(),K.html(""),qa=F(O,d));if(u.template)if(C("template",G,u,K),G=u,O=B(u.template)?u.template(K,c):u.template,O=Yb(O),u.replace){f=u;O=w("
"+aa(O)+"
").contents();b=O[0];if(1!=O.length||1!==b.nodeType)throw ga("tplrt",R,"");bb(e,K,b); 49 | ma={$attr:{}};a=a.concat(ba(b,a.splice(L+1,a.length-(L+1)),ma));ab(c,ma);ma=a.length}else K.html(O);if(u.templateUrl)C("template",G,u,K),G=u,u.replace&&(f=u),l=Uc(a.splice(L,a.length-L),l,K,c,e,qa),ma=a.length;else if(u.compile)try{ka=u.compile(K,c,qa),B(ka)?g(null,ka,A,z):ka&&g(ka.pre,ka.post,A,z)}catch(E){p(E,ia(K))}u.terminal&&(l.terminal=!0,n=Math.max(n,u.priority))}l.scope=x&&x.scope;l.transclude=N&&qa;return l}function G(d,e,f,g,h,n,m){if(e===h)return null;h=null;if(b.hasOwnProperty(e)){var t; 50 | e=a.get(e+c);for(var r=0,C=e.length;rt.priority)&&-1!=t.restrict.indexOf(f)&&(n&&(t=Cc(t,{$$start:n,$$end:m})),d.push(t),h=t)}catch(u){p(u)}}return h}function ab(a,b){var c=b.$attr,d=a.$attr,e=a.$$element;q(a,function(d,e){"$"!=e.charAt(0)&&(b[e]&&(d+=("style"===e?";":" ")+b[e]),a.$set(e,d,!0,c[e]))});q(b,function(b,f){"class"==f?(I(e,b),a["class"]=(a["class"]?a["class"]+" ":"")+b):"style"==f?e.attr("style",e.attr("style")+";"+b):"$"==f.charAt(0)||a.hasOwnProperty(f)|| 51 | (a[f]=b,d[f]=c[f])})}function Uc(a,b,c,d,e,f){var g=[],h,p,l=c[0],m=a.shift(),r=E({},m,{controller:null,templateUrl:null,transclude:null,scope:null,replace:null}),C=B(m.templateUrl)?m.templateUrl(c,d):m.templateUrl;c.html("");n.get(N.getTrustedResourceUrl(C),{cache:t}).success(function(n){var t;n=Yb(n);if(m.replace){n=w("
"+aa(n)+"
").contents();t=n[0];if(1!=n.length||1!==t.nodeType)throw ga("tplrt",m.name,C);n={$attr:{}};bb(e,c,t);ba(t,a,n);ab(d,n)}else t=l,c.html(n);a.unshift(r);h=ka(a, 52 | t,d,f,c,m);q(e,function(a,b){a==t&&(e[b]=c[0])});for(p=Z(c[0].childNodes,f);g.length;){n=g.shift();var u=g.shift(),y=g.shift(),I=g.shift(),x=c[0];u!==l&&(x=vb(t),bb(y,w(u),x));h(b(p,n,x,e,I),n,x,e,I)}g=null}).error(function(a,b,c,d){throw ga("tpload",d.url);});return function(a,c,d,e,f){g?(g.push(c),g.push(d),g.push(e),g.push(f)):h(function(){b(p,c,d,e,f)},c,d,e,f)}}function L(a,b){return b.priority-a.priority}function C(a,b,c,d){if(b)throw ga("multidir",b.name,c.name,a,ia(d));}function K(a,b){var c= 53 | m(b,!0);c&&a.push({priority:0,compile:$(function(a,b){var d=b.parent(),e=d.data("$binding")||[];e.push(c);I(d.data("$binding",e),"ng-binding");a.$watch(c,function(a){b[0].nodeValue=a})})})}function O(a,b){if("xlinkHref"==b||"IMG"!=Aa(a)&&("src"==b||"ngSrc"==b))return N.RESOURCE_URL}function pa(a,b,c,d){var e=m(c,!0);if(e){if("multiple"===d&&"SELECT"===Aa(a))throw ga("selmulti",ia(a));b.push({priority:100,compile:$(function(b,c,g){c=g.$$observers||(g.$$observers={});if(f.test(d))throw ga("nodomevents"); 54 | if(e=m(g[d],!0,O(a,d)))g[d]=e(b),(c[d]||(c[d]=[])).$$inter=!0,(g.$$observers&&g.$$observers[d].$$scope||b).$watch(e,function(a){g.$set(d,a)})})})}}function bb(a,b,c){var d=b[0],e=b.length,f=d.parentNode,g,h;if(a)for(g=0,h=a.length;ga.status?b:p.reject(b)}var d={transformRequest:e.transformRequest,transformResponse:e.transformResponse},f=function(a){function b(a){var c;q(a,function(b,d){B(b)&&(c=b(),null!=c?a[d]=c:delete a[d])})}var c=e.headers,d=E({},a.headers),f,g,c=E({},c.common,c[J(a.method)]);b(c);b(d);a:for(f in c){a=J(f);for(g in d)if(J(g)===a)continue a;d[f]=c[f]}return d}(a);E(d,a);d.headers=f;d.method=Ba(d.method);(a=t.isSameOrigin(d.url)?b.cookies()[d.xsrfCookieName||e.xsrfCookieName]:s)&&(f[d.xsrfHeaderName|| 62 | e.xsrfHeaderName]=a);var g=[function(a){f=a.headers;var b=ac(a.data,$b(f),a.transformRequest);M(a.data)&&q(f,function(a,b){"content-type"===J(b)&&delete f[b]});M(a.withCredentials)&&!M(e.withCredentials)&&(a.withCredentials=e.withCredentials);return y(a,b,f).then(c,c)},s],h=p.when(d);for(q(N,function(a){(a.request||a.requestError)&&g.unshift(a.request,a.requestError);(a.response||a.responseError)&&g.push(a.response,a.responseError)});g.length;){a=g.shift();var n=g.shift(),h=h.then(a,n)}h.success= 63 | function(a){h.then(function(b){a(b.data,b.status,b.headers,d)});return h};h.error=function(a){h.then(null,function(b){a(b.data,b.status,b.headers,d)});return h};return h}function y(b,c,g){function k(a,b,c){y&&(200<=a&&300>a?y.put(s,[a,b,Zb(c)]):y.remove(s));h(b,a,c);d.$$phase||d.$apply()}function h(a,c,d){c=Math.max(c,0);(200<=c&&300>c?l.resolve:l.reject)({data:a,status:c,headers:$b(d),config:b})}function n(){var a=Va(r.pendingRequests,b);-1!==a&&r.pendingRequests.splice(a,1)}var l=p.defer(),t=l.promise, 64 | y,q,s=x(b.url,b.params);r.pendingRequests.push(b);t.then(n,n);(b.cache||e.cache)&&(!1!==b.cache&&"GET"==b.method)&&(y=U(b.cache)?b.cache:U(e.cache)?e.cache:R);if(y)if(q=y.get(s),z(q)){if(q.then)return q.then(n,n),q;D(q)?h(q[1],q[0],da(q[2])):h(q,200,{})}else y.put(s,t);M(q)&&a(b.method,s,c,k,g,b.timeout,b.withCredentials,b.responseType);return t}function x(a,b){if(!b)return a;var c=[];Bc(b,function(a,b){null!=a&&a!=s&&(D(a)||(a=[a]),q(a,function(a){U(a)&&(a=oa(a));c.push(ua(b)+"="+ua(a))}))});return a+ 65 | (-1==a.indexOf("?")?"?":"&")+c.join("&")}var R=c("$http"),N=[];q(g,function(a){N.unshift(H(a)?n.get(a):n.invoke(a))});q(h,function(a,b){var c=H(a)?n.get(a):n.invoke(a);N.splice(b,0,{response:function(a){return c(p.when(a))},responseError:function(a){return c(p.reject(a))}})});r.pendingRequests=[];(function(a){q(arguments,function(a){r[a]=function(b,c){return r(E(c||{},{method:a,url:b}))}})})("get","delete","head","jsonp");(function(a){q(arguments,function(a){r[a]=function(b,c,d){return r(E(d||{}, 66 | {method:a,url:b,data:c}))}})})("post","put");r.defaults=e;return r}]}function cd(){this.$get=["$browser","$window","$document",function(a,b,c){return dd(a,ed,a.defer,b.angular.callbacks,c[0],b.location.protocol.replace(":",""))}]}function dd(a,b,c,d,e,g){function h(a,b){var c=e.createElement("script"),d=function(){e.body.removeChild(c);b&&b()};c.type="text/javascript";c.src=a;Q?c.onreadystatechange=function(){/loaded|complete/.test(c.readyState)&&d()}:c.onload=c.onerror=d;e.body.appendChild(c);return d} 67 | return function(e,k,l,m,p,n,t,r){function y(){R=-1;u&&u();v&&v.abort()}function x(b,d,e,f){var h=(k.match(bc)||["",g])[1];F&&c.cancel(F);u=v=null;d="file"==h?e?200:404:d;b(1223==d?204:d,e,f);a.$$completeOutstandingRequest(A)}var R;a.$$incOutstandingRequestCount();k=k||a.url();if("jsonp"==J(e)){var s="_"+(d.counter++).toString(36);d[s]=function(a){d[s].data=a};var u=h(k.replace("JSON_CALLBACK","angular.callbacks."+s),function(){d[s].data?x(m,200,d[s].data):x(m,R||-2);delete d[s]})}else{var v=new b; 68 | v.open(e,k,!0);q(p,function(a,b){a&&v.setRequestHeader(b,a)});v.onreadystatechange=function(){if(4==v.readyState){var a=v.getAllResponseHeaders(),b="Cache-Control Content-Language Content-Type Expires Last-Modified Pragma".split(" ");a||(a="",q(b,function(b){var c=v.getResponseHeader(b);c&&(a+=b+": "+c+"\n")}));x(m,R||v.status,v.responseType?v.response:v.responseText,a)}};t&&(v.withCredentials=!0);r&&(v.responseType=r);v.send(l||"")}if(0=a}function g(a){return" "==a||"\r"==a||"\t"==a||"\n"==a||"\v"==a||"\u00a0"==a}function h(a){return"a"<=a&&"z">=a||"A"<=a&&"Z">=a||"_"==a||"$"==a}function f(a){return"-"==a||"+"==a||e(a)}function k(b,c,d){d=d||r;c=z(c)?"s "+c+"-"+r+" ["+a.substring(c,d)+"]":" "+d;throw Ra("lexerr",b,c,a);}function l(){for(var b= 81 | "",c=r;r","<=",">="))a=p(a,b.fn,N());return a}function u(){for(var a=v(),b;b=f("*","/","%");)a=p(a,b.fn,v());return a}function v(){var a;return f("+")?F():(a=f("-"))?p(ba,a.fn,v()):(a=f("!"))?l(a.fn,v()):F()}function F(){var a; 88 | if(f("("))a=L(),k(")");else if(f("["))a=I();else if(f("{"))a=w();else{var b=f();(a=b.fn)||e("not a primary expression",b);b.json&&(a.constant=a.literal=!0)}for(var c;b=f("(","[",".");)"("===b.text?(a=G(a,c),c=null):"["===b.text?(c=a,a=D(a)):"."===b.text?(c=a,a=H(a)):e("IMPOSSIBLE");return a}function I(){var a=[],b=!0;if("]"!=g().text){do{var c=z();a.push(c);c.constant||(b=!1)}while(f(","))}k("]");return E(function(b,c){for(var d=[],e=0;ee?mc(d[0],d[1], 94 | d[2],d[3],d[4],c):function(a,b){var g=0,l;do l=mc(d[g++],d[g++],d[g++],d[g++],d[g++],c)(a,b),b=s,a=l;while(ga)for(b in g++,d)d.hasOwnProperty(b)&&!f.hasOwnProperty(b)&&(m--,delete d[b])}else d!== 103 | f&&(d=f,g++);return g},function(){b(f,d,c)})},$digest:function(){var c,e,g,h,k=this.$$asyncQueue,q=this.$$postDigestQueue,s,N,u=a,v,F=[],I,z;f("$digest");do{N=!1;for(v=this;k.length;)try{v.$eval(k.shift())}catch(ba){d(ba)}do{if(h=v.$$watchers)for(s=h.length;s--;)try{(c=h[s])&&((e=c.get(v))!==(g=c.last)&&!(c.eq?xa(e,g):"number"==typeof e&&"number"==typeof g&&isNaN(e)&&isNaN(g)))&&(N=!0,c.last=c.eq?da(e):e,c.fn(e,g===l?e:g,v),5>u&&(I=4-u,F[I]||(F[I]=[]),z=B(c.exp)?"fn: "+(c.exp.name||c.exp.toString()): 104 | c.exp,z+="; newVal: "+oa(e)+"; oldVal: "+oa(g),F[I].push(z)))}catch(W){d(W)}if(!(h=v.$$childHead||v!==this&&v.$$nextSibling))for(;v!==this&&!(h=v.$$nextSibling);)v=v.$parent}while(v=h);if(N&&!u--)throw m.$$phase=null,b("infdig",a,oa(F));}while(N||k.length);for(m.$$phase=null;q.length;)try{q.shift()()}catch(w){d(w)}},$destroy:function(){if(m!=this&&!this.$$destroyed){var a=this.$parent;this.$broadcast("$destroy");this.$$destroyed=!0;a.$$childHead==this&&(a.$$childHead=this.$$nextSibling);a.$$childTail== 105 | this&&(a.$$childTail=this.$$prevSibling);this.$$prevSibling&&(this.$$prevSibling.$$nextSibling=this.$$nextSibling);this.$$nextSibling&&(this.$$nextSibling.$$prevSibling=this.$$prevSibling);this.$parent=this.$$nextSibling=this.$$prevSibling=this.$$childHead=this.$$childTail=null}},$eval:function(a,b){return e(a)(this,b)},$evalAsync:function(a){m.$$phase||m.$$asyncQueue.length||g.defer(function(){m.$$asyncQueue.length&&m.$digest()});this.$$asyncQueue.push(a)},$$postDigest:function(a){this.$$postDigestQueue.push(a)}, 106 | $apply:function(a){try{return f("$apply"),this.$eval(a)}catch(b){d(b)}finally{m.$$phase=null;try{m.$digest()}catch(c){throw d(c),c;}}},$on:function(a,b){var c=this.$$listeners[a];c||(this.$$listeners[a]=c=[]);c.push(b);return function(){c[Va(c,b)]=null}},$emit:function(a,b){var c=[],e,f=this,g=!1,h={name:a,targetScope:f,stopPropagation:function(){g=!0},preventDefault:function(){h.defaultPrevented=!0},defaultPrevented:!1},k=[h].concat(ta.call(arguments,1)),l,m;do{e=f.$$listeners[a]||c;h.currentScope= 107 | f;l=0;for(m=e.length;lc))throw Ca("iequirks");var e=da(fa);e.isEnabled=function(){return a};e.trustAs=d.trustAs;e.getTrusted=d.getTrusted;e.valueOf=d.valueOf;a||(e.trustAs=e.getTrusted=function(a,b){return b},e.valueOf=wa);e.parseAs=function(a,c){var d=b(c);return d.literal&& 112 | d.constant?d:function(b,c){return e.getTrusted(a,d(b,c))}};var g=e.parseAs,h=e.getTrusted,f=e.trustAs;Ha.forEach(fa,function(a,b){var c=J(b);e[Ja("parse_as_"+c)]=function(b){return g(a,b)};e[Ja("get_trusted_"+c)]=function(b){return h(a,b)};e[Ja("trust_as_"+c)]=function(b){return f(a,b)}});return e}]}function td(){this.$get=["$window","$document",function(a,b){var c={},d=V((/android (\d+)/.exec(J((a.navigator||{}).userAgent))||[])[1]),e=/Boxee/i.test((a.navigator||{}).userAgent),g=b[0]||{},h,f=/^(Moz|webkit|O|ms)(?=[A-Z])/, 113 | k=g.body&&g.body.style,l=!1,m=!1;if(k){for(var p in k)if(l=f.exec(p)){h=l[0];h=h.substr(0,1).toUpperCase()+h.substr(1);break}h||(h="WebkitOpacity"in k&&"webkit");l=!!("transition"in k||h+"Transition"in k);m=!!("animation"in k||h+"Animation"in k);!d||l&&m||(l=H(g.body.style.webkitTransition),m=H(g.body.style.webkitAnimation))}return{history:!(!a.history||!a.history.pushState||4>d||e),hashchange:"onhashchange"in a&&(!g.documentMode||7=Q&&(b.setAttribute("href",g),g=b.href);b.setAttribute("href",g);return c?{href:b.href,protocol:b.protocol,host:b.host}:b.href}var b=T.createElement("a"),c=a(Y.location.href,!0);return{resolve:a,isSameOrigin:function(b){b="string"===typeof b?a(b,!0):b;return b.protocol===c.protocol&&b.host===c.host}}}]}function wd(){this.$get= 116 | $(Y)}function nc(a){function b(b,e){return a.factory(b+c,e)}var c="Filter";this.register=b;this.$get=["$injector",function(a){return function(b){return a.get(b+c)}}];b("currency",oc);b("date",pc);b("filter",xd);b("json",yd);b("limitTo",zd);b("lowercase",Ad);b("number",qc);b("orderBy",rc);b("uppercase",Bd)}function xd(){return function(a,b,c){if(!D(a))return a;var d=[];d.check=function(a){for(var b=0;ba;a=Math.abs(a);var h=a+"",f="",k=[],l=!1;if(-1!==h.indexOf("e")){var m=h.match(/([\d\.]+)e(-?)(\d+)/);m&&"-"==m[2]&&m[3]>e+1?h="0":(f=h,l=!0)}if(l)0a)&&(f=a.toFixed(e));else{h=(h.split(tc)[1]||"").length;M(e)&&(e=Math.min(Math.max(b.minFrac,h),b.maxFrac)); 120 | h=Math.pow(10,e);a=Math.round(a*h)/h;a=(""+a).split(tc);h=a[0];a=a[1]||"";var l=0,m=b.lgSize,p=b.gSize;if(h.length>=m+p)for(var l=h.length-m,n=0;na&&(d="-",a=-a);for(a=""+a;a.length-c)e+=c;0===e&&-12==c&&(e=12);return Cb(e,b,d)}}function eb(a,b){return function(c,d){var e=c["get"+a](),g=Ba(b?"SHORT"+a:a);return d[g][e]}}function pc(a){function b(a){var b;if(b=a.match(c)){a=new Date(0);var g=0,h=0,f=b[8]?a.setUTCFullYear:a.setFullYear,k=b[8]?a.setUTCHours:a.setHours;b[9]&&(g=V(b[9]+b[10]),h=V(b[9]+b[11]));f.call(a,V(b[1]),V(b[2])-1,V(b[3]));g=V(b[4]||0)-g;h=V(b[5]||0)-h;f=V(b[6]||0);b=Math.round(1E3* 122 | parseFloat("0."+(b[7]||0)));k.call(a,g,h,f,b)}return a}var c=/^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;return function(c,e){var g="",h=[],f,k;e=e||"mediumDate";e=a.DATETIME_FORMATS[e]||e;H(c)&&(c=Cd.test(c)?V(c):b(c));lb(c)&&(c=new Date(c));if(!Ea(c))return c;for(;e;)(k=Dd.exec(e))?(h=h.concat(ta.call(k,1)),e=h.pop()):(h.push(e),e=null);q(h,function(b){f=Ed[b];g+=f?f(c,a.DATETIME_FORMATS):b.replace(/(^'|'$)/g,"").replace(/''/g,"'")});return g}} 123 | function yd(){return function(a){return oa(a,!0)}}function zd(){return function(a,b){if(!D(a)&&!H(a))return a;b=V(b);if(H(a))return b?0<=b?a.slice(0,b):a.slice(b,a.length):"";var c=[],d,e;b>a.length?b=a.length:b<-a.length&&(b=-a.length);0a||37<=a&&40>=a)||k()});b.on("change",h);if(e.hasEvent("paste"))b.on("paste cut",k)}d.$render=function(){b.val(ca(d.$viewValue)?"":d.$viewValue)};var l=c.ngPattern,m=function(a,b){if(ca(b)||a.test(b))return d.$setValidity("pattern", 128 | !0),b;d.$setValidity("pattern",!1);return s};l&&((e=l.match(/^\/(.*)\/([gim]*)$/))?(l=RegExp(e[1],e[2]),e=function(a){return m(l,a)}):e=function(c){var d=a.$eval(l);if(!d||!d.test)throw P("ngPattern")("noregexp",l,d,ia(b));return m(d,c)},d.$formatters.push(e),d.$parsers.push(e));if(c.ngMinlength){var p=V(c.ngMinlength);e=function(a){if(!ca(a)&&a.lengthn)return d.$setValidity("maxlength",!1),s;d.$setValidity("maxlength",!0);return a};d.$parsers.push(e);d.$formatters.push(e)}}function Db(a,b){a="ngClass"+a;return function(){return{restrict:"AC",link:function(c,d,e){function g(a){if(!0===b||c.$index%2===b)f&&!xa(a,f)&&e.$removeClass(h(f)),e.$addClass(h(a));f=da(a)}function h(a){if(D(a))return a.join(" ");if(U(a)){var b=[];q(a,function(a,c){a&&b.push(c)});return b.join(" ")}return a}var f=s;c.$watch(e[a], 130 | g,!0);e.$observe("class",function(b){g(c.$eval(e[a]))});"ngClass"!==a&&c.$watch("$index",function(d,f){var g=d&1;g!==f&1&&(g===b?(g=c.$eval(e[a]),e.$addClass(h(g))):(g=c.$eval(e[a]),e.$removeClass(h(g))))})}}}}var J=function(a){return H(a)?a.toLowerCase():a},Ba=function(a){return H(a)?a.toUpperCase():a},Q,w,ya,ta=[].slice,Fd=[].push,Ua=Object.prototype.toString,Wa=P("ng"),Ha=Y.angular||(Y.angular={}),Oa,Aa,ha=["0","0","0"];Q=V((/msie (\d+)/.exec(J(navigator.userAgent))||[])[1]);isNaN(Q)&&(Q=V((/trident\/.*; rv:(\d+)/.exec(J(navigator.userAgent))|| 131 | [])[1]));A.$inject=[];wa.$inject=[];var aa=function(){return String.prototype.trim?function(a){return H(a)?a.trim():a}:function(a){return H(a)?a.replace(/^\s*/,"").replace(/\s*$/,""):a}}();Aa=9>Q?function(a){a=a.nodeName?a:a[0];return a.scopeName&&"HTML"!=a.scopeName?Ba(a.scopeName+":"+a.nodeName):a.nodeName}:function(a){return a.nodeName?a.nodeName:a[0].nodeName};var Ic=/[A-Z]/g,Gd={full:"1.2.0-rc.2",major:1,minor:2,dot:0,codeName:"barehand-atomsplitting"},La=S.cache={},Xa=S.expando="ng-"+(new Date).getTime(), 132 | Mc=1,vc=Y.document.addEventListener?function(a,b,c){a.addEventListener(b,c,!1)}:function(a,b,c){a.attachEvent("on"+b,c)},wb=Y.document.removeEventListener?function(a,b,c){a.removeEventListener(b,c,!1)}:function(a,b,c){a.detachEvent("on"+b,c)},Kc=/([\:\-\_]+(.))/g,Lc=/^moz([A-Z])/,tb=P("jqLite"),Pa=S.prototype={ready:function(a){function b(){c||(c=!0,a())}var c=!1;"complete"===T.readyState?setTimeout(b):(this.on("DOMContentLoaded",b),S(Y).on("load",b))},toString:function(){var a=[];q(this,function(b){a.push(""+ 133 | b)});return"["+a.join(", ")+"]"},eq:function(a){return 0<=a?w(this[a]):w(this[this.length+a])},length:0,push:Fd,sort:[].sort,splice:[].splice},$a={};q("multiple selected checked disabled readOnly required open".split(" "),function(a){$a[J(a)]=a});var Vb={};q("input select option textarea button form details".split(" "),function(a){Vb[Ba(a)]=!0});q({data:Qb,inheritedData:Za,scope:function(a){return Za(a,"$scope")},controller:Tb,injector:function(a){return Za(a,"$injector")},removeAttr:function(a,b){a.removeAttribute(b)}, 134 | hasClass:Ya,css:function(a,b,c){b=Ja(b);if(z(c))a.style[b]=c;else{var d;8>=Q&&(d=a.currentStyle&&a.currentStyle[b],""===d&&(d="auto"));d=d||a.style[b];8>=Q&&(d=""===d?s:d);return d}},attr:function(a,b,c){var d=J(b);if($a[d])if(z(c))c?(a[b]=!0,a.setAttribute(b,d)):(a[b]=!1,a.removeAttribute(d));else return a[b]||(a.attributes.getNamedItem(b)||A).specified?d:s;else if(z(c))a.setAttribute(b,c);else if(a.getAttribute)return a=a.getAttribute(b,2),null===a?s:a},prop:function(a,b,c){if(z(c))a[b]=c;else return a[b]}, 135 | text:function(){function a(a,d){var e=b[a.nodeType];if(M(d))return e?a[e]:"";a[e]=d}var b=[];9>Q?(b[1]="innerText",b[3]="nodeValue"):b[1]=b[3]="textContent";a.$dv="";return a}(),val:function(a,b){if(M(b)){if("SELECT"===Aa(a)&&a.multiple){var c=[];q(a.options,function(a){a.selected&&c.push(a.value||a.text)});return 0===c.length?null:c}return a.value}a.value=b},html:function(a,b){if(M(b))return a.innerHTML;for(var c=0,d=a.childNodes;c":function(b,c,d,e){return d(b,c)>e(b,c)},"<=":function(b,c,d,e){return d(b,c)<=e(b,c)},">=":function(b,c,d,e){return d(b,c)>=e(b,c)},"&&":function(b,c,d,e){return d(b,c)&&e(b,c)},"||":function(b,c,d,e){return d(b, 147 | c)||e(b,c)},"&":function(b,c,d,e){return d(b,c)&e(b,c)},"|":function(b,c,d,e){return e(b,c)(b,c,d(b,c))},"!":function(b,c,d){return!d(b,c)}},ld={n:"\n",f:"\f",r:"\r",t:"\t",v:"\v","'":"'",'"':'"'},Bb={},Ca=P("$sce"),fa={HTML:"html",CSS:"css",URL:"url",RESOURCE_URL:"resourceUrl",JS:"js"};nc.$inject=["$provide"];oc.$inject=["$locale"];qc.$inject=["$locale"];var tc=".",Ed={yyyy:X("FullYear",4),yy:X("FullYear",2,0,!0),y:X("FullYear",1),MMMM:eb("Month"),MMM:eb("Month",!0),MM:X("Month",2,1),M:X("Month", 148 | 1,1),dd:X("Date",2),d:X("Date",1),HH:X("Hours",2),H:X("Hours",1),hh:X("Hours",2,-12),h:X("Hours",1,-12),mm:X("Minutes",2),m:X("Minutes",1),ss:X("Seconds",2),s:X("Seconds",1),sss:X("Milliseconds",3),EEEE:eb("Day"),EEE:eb("Day",!0),a:function(b,c){return 12>b.getHours()?c.AMPMS[0]:c.AMPMS[1]},Z:function(b){b=-1*b.getTimezoneOffset();return b=(0<=b?"+":"")+(Cb(Math[0=Q&&(c.href||c.name||c.$set("href",""),b.append(T.createComment("IE fix")));return function(b,c){c.on("click",function(b){c.attr("href")||b.preventDefault()})}}}),Eb={};q($a,function(b,c){if("multiple"!=b){var d=la("ng-"+c);Eb[d]=function(){return{priority:100,compile:function(){return function(b,g,h){b.$watch(h[d],function(b){h.$set(c,!!b)})}}}}}});q(["src","srcset","href"],function(b){var c= 150 | la("ng-"+b);Eb[c]=function(){return{priority:99,link:function(d,e,g){g.$observe(c,function(c){c&&(g.$set(b,c),Q&&e.prop(b,g[b]))})}}}});var hb={$addControl:A,$removeControl:A,$setValidity:A,$setDirty:A,$setPristine:A};uc.$inject=["$element","$attrs","$scope"];var wc=function(b){return["$timeout",function(c){var d={name:"form",restrict:"E",controller:uc,compile:function(){return{pre:function(b,d,h,f){if(!h.action){var k=function(b){b.preventDefault?b.preventDefault():b.returnValue=!1};vc(d[0],"submit", 151 | k);d.on("$destroy",function(){c(function(){wb(d[0],"submit",k)},0,!1)})}var l=d.parent().controller("form"),m=h.name||h.ngForm;m&&db(b,m,f,m);if(l)d.on("$destroy",function(){l.$removeControl(f);m&&db(b,m,s,m);E(f,hb)})}}}};return b?E(da(d),{restrict:"EAC"}):d}]},Kd=wc(),Ld=wc(!0),Md=/^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/,Nd=/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,6}$/,Od=/^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/,xc={text:jb,number:function(b,c,d,e, 152 | g,h){jb(b,c,d,e,g,h);e.$parsers.push(function(b){var c=ca(b);if(c||Od.test(b))return e.$setValidity("number",!0),""===b?null:c?b:parseFloat(b);e.$setValidity("number",!1);return s});e.$formatters.push(function(b){return ca(b)?"":""+b});if(d.min){var f=parseFloat(d.min);b=function(b){if(!ca(b)&&bk)return e.$setValidity("max",!1), 153 | s;e.$setValidity("max",!0);return b};e.$parsers.push(d);e.$formatters.push(d)}e.$formatters.push(function(b){if(ca(b)||lb(b))return e.$setValidity("number",!0),b;e.$setValidity("number",!1);return s})},url:function(b,c,d,e,g,h){jb(b,c,d,e,g,h);b=function(b){if(ca(b)||Md.test(b))return e.$setValidity("url",!0),b;e.$setValidity("url",!1);return s};e.$formatters.push(b);e.$parsers.push(b)},email:function(b,c,d,e,g,h){jb(b,c,d,e,g,h);b=function(b){if(ca(b)||Nd.test(b))return e.$setValidity("email",!0), 154 | b;e.$setValidity("email",!1);return s};e.$formatters.push(b);e.$parsers.push(b)},radio:function(b,c,d,e){M(d.name)&&c.attr("name",Ta());c.on("click",function(){c[0].checked&&b.$apply(function(){e.$setViewValue(d.value)})});e.$render=function(){c[0].checked=d.value==e.$viewValue};d.$observe("value",e.$render)},checkbox:function(b,c,d,e){var g=d.ngTrueValue,h=d.ngFalseValue;H(g)||(g=!0);H(h)||(h=!1);c.on("click",function(){b.$apply(function(){e.$setViewValue(c[0].checked)})});e.$render=function(){c[0].checked= 155 | e.$viewValue};e.$formatters.push(function(b){return b===g});e.$parsers.push(function(b){return b?g:h})},hidden:A,button:A,submit:A,reset:A},yc=["$browser","$sniffer",function(b,c){return{restrict:"E",require:"?ngModel",link:function(d,e,g,h){h&&(xc[J(g.type)]||xc.text)(d,e,g,h,c,b)}}}],gb="ng-valid",fb="ng-invalid",Da="ng-pristine",ib="ng-dirty",Pd=["$scope","$exceptionHandler","$attrs","$element","$parse",function(b,c,d,e,g){function h(b,c){c=c?"-"+pb(c,"-"):"";e.removeClass((b?fb:gb)+c).addClass((b? 156 | gb:fb)+c)}this.$modelValue=this.$viewValue=Number.NaN;this.$parsers=[];this.$formatters=[];this.$viewChangeListeners=[];this.$pristine=!0;this.$dirty=!1;this.$valid=!0;this.$invalid=!1;this.$name=d.name;var f=g(d.ngModel),k=f.assign;if(!k)throw P("ngModel")("nonassign",d.ngModel,ia(e));this.$render=A;var l=e.inheritedData("$formController")||hb,m=0,p=this.$error={};e.addClass(Da);h(!0);this.$setValidity=function(b,c){p[b]!==!c&&(c?(p[b]&&m--,m||(h(!0),this.$valid=!0,this.$invalid=!1)):(h(!1),this.$invalid= 157 | !0,this.$valid=!1,m++),p[b]=!c,h(c,b),l.$setValidity(b,c,this))};this.$setPristine=function(){this.$dirty=!1;this.$pristine=!0;e.removeClass(ib).addClass(Da)};this.$setViewValue=function(d){this.$viewValue=d;this.$pristine&&(this.$dirty=!0,this.$pristine=!1,e.removeClass(Da).addClass(ib),l.$setDirty());q(this.$parsers,function(b){d=b(d)});this.$modelValue!==d&&(this.$modelValue=d,k(b,d),q(this.$viewChangeListeners,function(b){try{b()}catch(d){c(d)}}))};var n=this;b.$watch(function(){var c=f(b);if(n.$modelValue!== 158 | c){var d=n.$formatters,e=d.length;for(n.$modelValue=c;e--;)c=d[e](c);n.$viewValue!==c&&(n.$viewValue=c,n.$render())}})}],Qd=function(){return{require:["ngModel","^?form"],controller:Pd,link:function(b,c,d,e){var g=e[0],h=e[1]||hb;h.$addControl(g);c.on("$destroy",function(){h.$removeControl(g)})}}},Rd=$({require:"ngModel",link:function(b,c,d,e){e.$viewChangeListeners.push(function(){b.$eval(d.ngChange)})}}),zc=function(){return{require:"?ngModel",link:function(b,c,d,e){if(e){d.required=!0;var g=function(b){if(d.required&& 159 | (ca(b)||!1===b))e.$setValidity("required",!1);else return e.$setValidity("required",!0),b};e.$formatters.push(g);e.$parsers.unshift(g);d.$observe("required",function(){g(e.$viewValue)})}}}},Sd=function(){return{require:"ngModel",link:function(b,c,d,e){var g=(b=/\/(.*)\//.exec(d.ngList))&&RegExp(b[1])||d.ngList||",";e.$parsers.push(function(b){var c=[];b&&q(b.split(g),function(b){b&&c.push(aa(b))});return c});e.$formatters.push(function(b){return D(b)?b.join(", "):s})}}},Td=/^(true|false|\d+)$/,Ud= 160 | function(){return{priority:100,compile:function(b,c){return Td.test(c.ngValue)?function(b,c,g){g.$set("value",b.$eval(g.ngValue))}:function(b,c,g){b.$watch(g.ngValue,function(b){g.$set("value",b)})}}}},Vd=sa(function(b,c,d){c.addClass("ng-binding").data("$binding",d.ngBind);b.$watch(d.ngBind,function(b){c.text(b==s?"":b)})}),Wd=["$interpolate",function(b){return function(c,d,e){c=b(d.attr(e.$attr.ngBindTemplate));d.addClass("ng-binding").data("$binding",c);e.$observe("ngBindTemplate",function(b){d.text(b)})}}], 161 | Xd=["$sce",function(b){return function(c,d,e){d.addClass("ng-binding").data("$binding",e.ngBindHtml);c.$watch(e.ngBindHtml,function(c){d.html(b.getTrustedHtml(c)||"")})}}],Yd=Db("",!0),Zd=Db("Odd",0),$d=Db("Even",1),ae=sa({compile:function(b,c){c.$set("ngCloak",s);b.removeClass("ng-cloak")}}),be=[function(){return{scope:!0,controller:"@"}}],ce=["$sniffer",function(b){return{priority:1E3,compile:function(){b.csp=!0}}}],Ac={};q("click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur".split(" "), 162 | function(b){var c=la("ng-"+b);Ac[c]=["$parse",function(d){return function(e,g,h){var f=d(h[c]);g.on(J(b),function(b){e.$apply(function(){f(e,{$event:b})})})}}]});var de=["$animate",function(b){return{transclude:"element",priority:1E3,terminal:!0,restrict:"A",compile:function(c,d,e){return function(c,d,f){var k,l;c.$watch(f.ngIf,function(f){k&&(b.leave(k),k=s);l&&(l.$destroy(),l=s);Ga(f)&&(l=c.$new(),e(l,function(c){k=c;b.enter(c,d.parent(),d)}))})}}}}],ee=["$http","$templateCache","$anchorScroll", 163 | "$compile","$animate","$sce",function(b,c,d,e,g,h){return{restrict:"ECA",terminal:!0,transclude:"element",compile:function(f,k,l){var m=k.ngInclude||k.src,p=k.onload||"",n=k.autoscroll;return function(f,k){var q=0,s,w,A=function(){s&&(s.$destroy(),s=null);w&&(g.leave(w),w=null)};f.$watch(h.parseAsResourceUrl(m),function(h){var m=++q;h?(b.get(h,{cache:c}).success(function(b){if(m===q){var c=f.$new();l(c,function(h){A();s=c;w=h;w.html(b);g.enter(w,null,k);e(w.contents())(s);!z(n)||n&&!f.$eval(n)||d(); 164 | s.$emit("$includeContentLoaded");f.$eval(p)})}}).error(function(){m===q&&A()}),f.$emit("$includeContentRequested")):A()})}}}}],fe=sa({compile:function(){return{pre:function(b,c,d){b.$eval(d.ngInit)}}}}),ge=sa({terminal:!0,priority:1E3}),he=["$locale","$interpolate",function(b,c){var d=/{}/g;return{restrict:"EA",link:function(e,g,h){var f=h.count,k=h.$attr.when&&g.attr(h.$attr.when),l=h.offset||0,m=e.$eval(k)||{},p={},n=c.startSymbol(),t=c.endSymbol(),r=/^when(Minus)?(.+)$/;q(h,function(b,c){r.test(c)&& 165 | (m[J(c.replace("when","").replace("Minus","-"))]=g.attr(h.$attr[c]))});q(m,function(b,e){p[e]=c(b.replace(d,n+f+"-"+l+t))});e.$watch(function(){var c=parseFloat(e.$eval(f));if(isNaN(c))return"";c in m||(c=b.pluralCat(c-l));return p[c](e,g,!0)},function(b){g.text(b)})}}}],ie=["$parse","$animate",function(b,c){var d=P("ngRepeat");return{transclude:"element",priority:1E3,terminal:!0,compile:function(e,g,h){return function(e,g,l){var m=l.ngRepeat,p=m.match(/^\s*(.+)\s+in\s+(.*?)\s*(\s+track\s+by\s+(.+)\s*)?$/), 166 | n,t,r,s,x,z,A,u={$id:za};if(!p)throw d("iexp",m);l=p[1];x=p[2];(p=p[4])?(n=b(p),t=function(b,c,d){A&&(u[A]=b);u[z]=c;u.$index=d;return n(e,u)}):(r=function(b,c){return za(c)},s=function(b){return b});p=l.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/);if(!p)throw d("iidexp",l);z=p[3]||p[1];A=p[2];var v={};e.$watchCollection(x,function(b){var l,n,p=g[0],u,x={},H,G,D,E,L,C,K=[];if(kb(b))L=b,t=t||r;else{t=t||s;L=[];for(D in b)b.hasOwnProperty(D)&&"$"!=D.charAt(0)&&L.push(D);L.sort()}H=L.length; 167 | n=K.length=L.length;for(l=0;lF;)z.pop().element.remove()}for(;A.length>B;)A.pop()[0].element.remove()}var k;if(!(k= 177 | y.match(d)))throw P("ngOptions")("iexp",y,ia(f));var l=c(k[2]||k[1]),m=k[4]||k[6],n=k[5],p=c(k[3]||""),q=c(k[2]?k[1]:m),t=c(k[7]),v=k[8]?c(k[8]):null,A=[[{element:f,label:""}]];x&&(b(x)(e),x.removeClass("ng-scope"),x.remove());f.html("");f.on("change",function(){e.$apply(function(){var b,c=t(e)||[],d={},h,k,l,p,u,x;if(r)for(k=[],p=0,x=A.length;p@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak,.ng-hide{display:none !important;}ng\\:form{display:block;}'); 184 | /* 185 | //@ sourceMappingURL=angular.min.js.map 186 | */ 187 | -------------------------------------------------------------------------------- /bower_components/angular/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular", 3 | "version": "1.2.0-rc.2", 4 | "main": "./angular.js", 5 | "dependencies": { 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /dist/angularjs-google-places.min.js: -------------------------------------------------------------------------------- 1 | /*! angularjs-google-places 25-09-2013 */ 2 | "use strict";angular.module("ngGPlaces",[]),angular.module("ngGPlaces").value("gPlaces",google.maps.places),angular.module("ngGPlaces").value("gMaps",google.maps),angular.module("ngGPlaces").provider("ngGPlacesAPI",function(){var a={radius:1e3,sensor:!1,latitude:null,longitude:null,types:["food"],map:null,elem:null,nearbySearchKeys:["name","reference","vicinity"],placeDetailsKeys:["formatted_address","formatted_phone_number","reference","website"],nearbySearchErr:"Unable to find nearby places",placeDetailsErr:"Unable to find place details",_nearbySearchApiFnCall:"nearbySearch",_placeDetailsApiFnCall:"getDetails"},b=function(b){var c=[],d=a.nearbySearchKeys;return b.map(function(a){var b={};angular.forEach(d,function(c){b[c]=a[c]}),c.push(b)}),c},c=function(b){var c={},d=a.placeDetailsKeys;return angular.forEach(d,function(a){c[a]=b[a]}),c};this.$get=function(d,e,f,g,h){function i(b){function c(a,b){b==g.PlacesServiceStatus.OK?d.$apply(function(){return l.resolve(i._parser(a))}):d.$apply(function(){l.reject(i._errorMsg)})}var i=angular.copy(a,{});angular.extend(i,b);var j,k,l=e.defer();return i._genLocation&&(i.location=new f.LatLng(i.latitude,i.longitude)),j=i.map?i.map:i.elem?i.elem:h.document.createElement("div"),k=new g.PlacesService(j),k[i._apiFnCall](i,c),l.promise}return{getDefaults:function(){return a},nearbySearch:function(c){return c._genLocation=!0,c._errorMsg=a.nearbySearchErr,c._parser=b,c._apiFnCall=a._nearbySearchApiFnCall,i(c)},placeDetails:function(b){return b._errorMsg=a.placeDetailsErr,b._parser=c,b._apiFnCall=a._placeDetailsApiFnCall,i(b)}}},this.$get.$inject=["$rootScope","$q","gMaps","gPlaces","$window"],this.setDefaults=function(b){angular.extend(a,b)}}); -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | 3 | module.exports = function ( config ) { 4 | config.set({ 5 | // base path, that will be used to resolve files and exclude 6 | basePath : '', 7 | 8 | frameworks : ['jasmine'], 9 | 10 | // list of files / patterns to load in the browser 11 | files : [ 12 | 'bower_components/angular/angular.js', 'bower_components/angular-mocks/angular-mocks.js', 'src/google-api.js','src/angularjs-google-places.js', 'test/*.js', 'test/mock/*.js' 13 | ], 14 | 15 | // list of files to exclude 16 | exclude : [], 17 | 18 | // test results reporter to use 19 | // possible values: 'dots', 'progress', 'junit' 20 | reporters : ['progress'], 21 | 22 | // web server port 23 | port : 9876, 24 | 25 | // cli runner port 26 | runnerPort : 9100, 27 | 28 | // enable / disable colors in the output (reporters and logs) 29 | colors : true, 30 | 31 | // level of logging 32 | // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG 33 | logLevel : 'karma.LOG_INFO', 34 | 35 | // enable / disable watching file and executing tests whenever any file changes 36 | autoWatch : true, 37 | 38 | // Start these browsers, currently available: 39 | // - Chrome 40 | // - ChromeCanary 41 | // - Firefox 42 | // - Opera 43 | // - Safari (only Mac) 44 | // - PhantomJS 45 | // - IE (only Windows) 46 | browsers : ['PhantomJS'], 47 | 48 | // If browser does not capture in given timeout [ms], kill it 49 | captureTimeout : 60000, 50 | 51 | // Continuous Integration mode 52 | // if true, it capture browsers, run tests and exit 53 | singleRun : true 54 | }); 55 | }; 56 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angularjs-google-places", 3 | "devDependencies": { 4 | "karma": "~0.10", 5 | "karma-jasmine": "~0.1", 6 | "grunt": "~0.4.1", 7 | "grunt-contrib-jshint": "~0.6.4", 8 | "grunt-contrib-uglify": "~0.2.4", 9 | "grunt-karma": "~0.6.2" 10 | }, 11 | "scripts": { 12 | "test": "./node_modules/.bin/karma start --single-run --browsers Chrome" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/angularjs-google-places.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('ngGPlaces', []); 4 | angular.module('ngGPlaces').value('gPlaces', google.maps.places); 5 | angular.module('ngGPlaces').value('gMaps', google.maps); 6 | 7 | angular.module('ngGPlaces'). 8 | provider('ngGPlacesAPI', function () { 9 | 10 | var defaults = { 11 | radius: 1000, 12 | sensor: false, 13 | latitude: null, 14 | longitude: null, 15 | types: ['food'], 16 | map: null, 17 | elem: null, 18 | nearbySearchKeys: ['name', 'reference', 'vicinity'], 19 | placeDetailsKeys: ['formatted_address', 'formatted_phone_number', 20 | 'reference', 'website' 21 | ], 22 | nearbySearchErr: 'Unable to find nearby places', 23 | placeDetailsErr: 'Unable to find place details', 24 | _nearbySearchApiFnCall: 'nearbySearch', 25 | _placeDetailsApiFnCall: 'getDetails' 26 | }; 27 | 28 | var parseNSJSON = function (response) { 29 | var pResp = []; 30 | var keys = defaults.nearbySearchKeys; 31 | response.map(function (result) { 32 | var obj = {}; 33 | angular.forEach(keys, function (k) { 34 | obj[k] = result[k]; 35 | }); 36 | pResp.push(obj); 37 | }); 38 | return pResp; 39 | }; 40 | 41 | var parsePDJSON = function (response) { 42 | var pResp = {}; 43 | var keys = defaults.placeDetailsKeys; 44 | angular.forEach(keys, function (k) { 45 | pResp[k] = response[k]; 46 | }); 47 | return pResp; 48 | }; 49 | 50 | this.$get = function ($q, gMaps, gPlaces, $window) { 51 | 52 | function commonAPI(args) { 53 | var req = angular.copy(defaults, {}); 54 | angular.extend(req, args); 55 | var deferred = $q.defer(); 56 | var elem, service; 57 | 58 | function callback(results, status) { 59 | if (status == gPlaces.PlacesServiceStatus.OK) { 60 | return deferred.resolve(req._parser(results)); 61 | } else { 62 | deferred.reject(req._errorMsg); 63 | } 64 | } 65 | if (req._genLocation) { 66 | req.location = new gMaps.LatLng(req.latitude, req.longitude); 67 | } 68 | if (req.map) { 69 | elem = req.map; 70 | } else if (req.elem) { 71 | elem = req.elem; 72 | } else { 73 | elem = $window.document.createElement('div'); 74 | } 75 | service = new gPlaces.PlacesService(elem); 76 | service[req._apiFnCall](req, callback); 77 | return deferred.promise; 78 | } 79 | 80 | return { 81 | getDefaults: function () { 82 | return defaults; 83 | }, 84 | nearbySearch: function (args) { 85 | args._genLocation = true; 86 | args._errorMsg = defaults.nearbySearchErr; 87 | args._parser = parseNSJSON; 88 | args._apiFnCall = defaults._nearbySearchApiFnCall; 89 | return commonAPI(args); 90 | }, 91 | placeDetails: function (args) { 92 | args._errorMsg = defaults.placeDetailsErr; 93 | args._parser = parsePDJSON; 94 | args._apiFnCall = defaults._placeDetailsApiFnCall; 95 | return commonAPI(args); 96 | } 97 | }; 98 | }; 99 | 100 | this.$get.$inject = ['$q', 'gMaps', 'gPlaces', '$window']; 101 | 102 | this.setDefaults = function (args) { 103 | angular.extend(defaults, args); 104 | }; 105 | 106 | }); -------------------------------------------------------------------------------- /src/google-api.js: -------------------------------------------------------------------------------- 1 | window.google = window.google || {}; 2 | google.maps = google.maps || {}; 3 | (function() { 4 | 5 | function getScript(src) { 6 | document.write('<' + 'script src="' + src + '"' + 7 | ' type="text/javascript"><' + '/script>'); 8 | } 9 | 10 | var modules = google.maps.modules = {}; 11 | google.maps.__gjsload__ = function(name, text) { 12 | modules[name] = text; 13 | }; 14 | 15 | google.maps.Load = function(apiLoad) { 16 | delete google.maps.Load; 17 | apiLoad([0.009999999776482582,[[["http://mt0.googleapis.com/vt?lyrs=m@231000000\u0026src=api\u0026hl=en-US\u0026","http://mt1.googleapis.com/vt?lyrs=m@231000000\u0026src=api\u0026hl=en-US\u0026"],null,null,null,null,"m@231000000"],[["http://khm0.googleapis.com/kh?v=137\u0026hl=en-US\u0026","http://khm1.googleapis.com/kh?v=137\u0026hl=en-US\u0026"],null,null,null,1,"137"],[["http://mt0.googleapis.com/vt?lyrs=h@231000000\u0026src=api\u0026hl=en-US\u0026","http://mt1.googleapis.com/vt?lyrs=h@231000000\u0026src=api\u0026hl=en-US\u0026"],null,null,null,null,"h@231000000"],[["http://mt0.googleapis.com/vt?lyrs=t@131,r@231000000\u0026src=api\u0026hl=en-US\u0026","http://mt1.googleapis.com/vt?lyrs=t@131,r@231000000\u0026src=api\u0026hl=en-US\u0026"],null,null,null,null,"t@131,r@231000000"],null,null,[["http://cbk0.googleapis.com/cbk?","http://cbk1.googleapis.com/cbk?"]],[["http://khm0.googleapis.com/kh?v=81\u0026hl=en-US\u0026","http://khm1.googleapis.com/kh?v=81\u0026hl=en-US\u0026"],null,null,null,null,"81"],[["http://mt0.googleapis.com/mapslt?hl=en-US\u0026","http://mt1.googleapis.com/mapslt?hl=en-US\u0026"]],[["http://mt0.googleapis.com/mapslt/ft?hl=en-US\u0026","http://mt1.googleapis.com/mapslt/ft?hl=en-US\u0026"]],[["http://mt0.googleapis.com/vt?hl=en-US\u0026","http://mt1.googleapis.com/vt?hl=en-US\u0026"]],[["http://mt0.googleapis.com/mapslt/loom?hl=en-US\u0026","http://mt1.googleapis.com/mapslt/loom?hl=en-US\u0026"]],[["https://mts0.googleapis.com/mapslt?hl=en-US\u0026","https://mts1.googleapis.com/mapslt?hl=en-US\u0026"]],[["https://mts0.googleapis.com/mapslt/ft?hl=en-US\u0026","https://mts1.googleapis.com/mapslt/ft?hl=en-US\u0026"]]],["en-US","US",null,0,null,null,"http://maps.gstatic.com/mapfiles/","http://csi.gstatic.com","https://maps.googleapis.com","http://maps.googleapis.com"],["http://maps.gstatic.com/intl/en_us/mapfiles/api-3/14/5","3.14.5"],[3460005893],1,null,null,null,null,0,"",["places"],null,0,"http://khm.googleapis.com/mz?v=137\u0026",null,"https://earthbuilder.googleapis.com","https://earthbuilder.googleapis.com",null,"http://mt.googleapis.com/vt/icon",[["http://mt0.googleapis.com/vt","http://mt1.googleapis.com/vt"],["https://mts0.googleapis.com/vt","https://mts1.googleapis.com/vt"],[null,[[0,"m",231000000]],[null,"en-US","US",null,18,null,null,null,null,null,null,[[47],[37,[["smartmaps"]]]]],0],[null,[[0,"m",231000000]],[null,"en-US","US",null,18,null,null,null,null,null,null,[[47],[37,[["smartmaps"]]]]],3],[null,[[0,"h",231000000]],[null,"en-US","US",null,18,null,null,null,null,null,null,[[50],[37,[["smartmaps"]]]]],0],[null,[[0,"h",231000000]],[null,"en-US","US",null,18,null,null,null,null,null,null,[[50],[37,[["smartmaps"]]]]],3],[null,[[4,"t",131],[0,"r",131000000]],[null,"en-US","US",null,18,null,null,null,null,null,null,[[5],[37,[["smartmaps"]]]]],0],[null,[[4,"t",131],[0,"r",131000000]],[null,"en-US","US",null,18,null,null,null,null,null,null,[[5],[37,[["smartmaps"]]]]],3],[null,null,[null,"en-US","US",null,18],0],[null,null,[null,"en-US","US",null,18],3],[null,null,[null,"en-US","US",null,18],6],[null,null,[null,"en-US","US",null,18],0]]], loadScriptTime); 18 | }; 19 | var loadScriptTime = (new Date).getTime(); 20 | getScript("http://maps.gstatic.com/cat_js/intl/en_us/mapfiles/api-3/14/5/%7Bmain,places%7D.js"); 21 | })(); -------------------------------------------------------------------------------- /test/angularjs-google-places.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Service: ngGPlacesAPI', function () { 4 | var ngGPlacesAPI, $rootScope; 5 | 6 | beforeEach(module('ngGPlaces', 'mockedNearbySearch', 7 | 'mockedPlaceDetails', function ($provide, ngGPlacesAPIProvider, 8 | defaultNSJSON, defaultPDJSON) { 9 | $provide.value('gPlaces', { 10 | PlacesService: function () { 11 | this.nearbySearch = function (req, cb) { 12 | cb(defaultNSJSON, true); 13 | }; 14 | this.getDetails = function (req, cb) { 15 | cb(defaultPDJSON, true); 16 | }; 17 | }, 18 | PlacesServiceStatus: { 19 | 'OK': true 20 | } 21 | }); 22 | $provide.value('gMaps', { 23 | LatLng: function () {} 24 | }); 25 | ngGPlacesAPIProvider.setDefaults({ 26 | sensor: true 27 | }); 28 | })); 29 | 30 | describe('Overriding Defaults', function () { 31 | var defaults; 32 | 33 | beforeEach(inject(function (_ngGPlacesAPI_) { 34 | ngGPlacesAPI = _ngGPlacesAPI_; 35 | defaults = ngGPlacesAPI.getDefaults(); 36 | })); 37 | 38 | it('should return default option for radius', function () { 39 | expect(defaults.radius).toEqual(1000); 40 | }); 41 | 42 | it('should return custom setting for sensor', function () { 43 | expect(defaults.sensor).toEqual(true); 44 | }); 45 | }); 46 | 47 | describe('Nearby Search', function () { 48 | beforeEach(inject(function (_ngGPlacesAPI_, _$rootScope_) { 49 | ngGPlacesAPI = _ngGPlacesAPI_; 50 | $rootScope = _$rootScope_; 51 | })); 52 | 53 | it('should return nearby places for a location', function () { 54 | var results = ngGPlacesAPI.nearbySearch({ 55 | latitude: -33.8665433, 56 | longitude: 151.1956316 57 | }).then(function (data) { 58 | results = data; 59 | }); 60 | $rootScope.$apply(); 61 | expect(results.length).toEqual(20); 62 | expect(results[0].name).not.toBeUndefined(); 63 | expect(results[1].vicinity).not.toBeUndefined(); 64 | expect(results[2].reference).not.toBeUndefined(); 65 | }); 66 | }); 67 | 68 | describe('Place Details Search', function () { 69 | beforeEach(inject(function (_ngGPlacesAPI_, _$rootScope_) { 70 | ngGPlacesAPI = _ngGPlacesAPI_; 71 | $rootScope = _$rootScope_; 72 | })); 73 | 74 | it('should return nearby places for a location', function () { 75 | var results; 76 | ngGPlacesAPI.nearbySearch({ 77 | latitude: -33.8665433, 78 | longitude: 151.1956316 79 | }).then(function (data) { 80 | results = data; 81 | }); 82 | $rootScope.$apply(); 83 | expect(results.length).toEqual(20); 84 | expect(results[0].name).not.toBeUndefined(); 85 | expect(results[1].vicinity).not.toBeUndefined(); 86 | expect(results[2].reference).not.toBeUndefined(); 87 | }); 88 | 89 | 90 | it('should return place details', function () { 91 | var result; 92 | ngGPlacesAPI.placeDetails({ 93 | reference: "CnRnAAAARpMYRKXwEl8UhHfPX84GQNP7HHPru_ry8P9he6SiQ2l8MUv7t1qA8zbq09mTyg2uzr0Cp4h1eJYVtoDEUPL_9zi-ug8w-oVJ8wnz-9xKjdw9yL9mZOuP8-lc57zLNSkqaRTdR-A1jFL_yi7e6KhbeBIQqABCCuYmIMFtkHyBx6Cp7hoUeHx1wbnovws61axvxyUOjR9mINU" 94 | }).then(function (data) { 95 | result = data; 96 | }); 97 | $rootScope.$apply(); 98 | expect(result.formatted_address).toEqual( 99 | '529 Kent Street, Sydney NSW, Australia'); 100 | expect(result.formatted_phone_number).toEqual( 101 | '(02) 9267 2900'); 102 | expect(result.website).toEqual( 103 | 'http://www.tetsuyas.com/'); 104 | expect(result.reference).not.toBeUndefined(); 105 | }); 106 | }); 107 | 108 | }); -------------------------------------------------------------------------------- /test/mock/nearbySearch.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('mockedNearbySearch',[]) 4 | .constant('defaultNSJSON', 5 | [ 6 | { 7 | "geometry": { 8 | "location": { 9 | "ob": -33.867217, 10 | "pb": 151.192465 11 | } 12 | }, 13 | "icon": "http:\/\/maps.gstatic.com\/mapfiles\/place_api\/icons\/generic_business-71.png", 14 | "id": "989df9aadca97a738b18e81d11d05603015cafc9", 15 | "name": "John St Square Supermarket", 16 | "reference": "CoQBdwAAAIN_LDZUruKYVhJR46_QWitT9oCvyxe73dQye19Fzo8jsxopFgH5gmG6Eu1Mp-Ue0VaxqYfhl4WkArm1XlZ0AnPi7SU9icQ8y9BqeuTTAwYM4isTQmcaP9CBdvkm6-Sbv1DMKd0FzrEjv3VaAYH6ruRhs3RmeDs4ehi4QLUK9PxfEhC440Bn2hvmrhhllARMcdLUGhRB5r8cyB7xvngiQ7fz6OFiHw6-5w", 17 | "types": [ 18 | "food", 19 | "store", 20 | "establishment" 21 | ], 22 | "vicinity": "45 Harris Street, Pyrmont", 23 | "html_attributions": [ 24 | "Listings by Yellow Pages<\/a>" 25 | ] 26 | }, 27 | { 28 | "geometry": { 29 | "location": { 30 | "ob": -33.868173, 31 | "pb": 151.194652 32 | } 33 | }, 34 | "icon": "http:\/\/maps.gstatic.com\/mapfiles\/place_api\/icons\/restaurant-71.png", 35 | "id": "a6e36e20e63dfff2f8d5ef4dfa7c55f2c32d524c", 36 | "name": "Adriano Zumbo at the Star", 37 | "opening_hours": { 38 | "open_now": true 39 | }, 40 | "photos": [ 41 | { 42 | "height": 160, 43 | "html_attributions": [ 44 | 45 | ], 46 | "width": 200 47 | } 48 | ], 49 | "rating": 4.3, 50 | "reference": "CoQBdwAAACDKVUXsp4egrkA8kYEySnMhgauV3uDBSHeLnVLcNAWa1DJsItcU1-Xx_nIsitoEmO9JMIZGtHtum5IHL_WJti_tIDmx7wq1NDyOJCHGzB6NkmM63Ic1oNr3z9-HFCBWOHyWCzfGOSOgnS-kmOPJI3rHCRYozXG5kYX_DgYuu5TIEhB7VAD-iCaeONjCWcKt4kY6GhTChVoXZ8pNSPp8Gp0Pzzpzn2ZplA", 51 | "types": [ 52 | "bakery", 53 | "store", 54 | "food", 55 | "establishment" 56 | ], 57 | "vicinity": "Shop 1, Cafe Court, The Star\/80 Pyrmont Street, Pyrmont", 58 | "html_attributions": [ 59 | "Listings by Yellow Pages<\/a>" 60 | ] 61 | }, 62 | { 63 | "geometry": { 64 | "location": { 65 | "ob": -33.87083, 66 | "pb": 151.194466 67 | } 68 | }, 69 | "icon": "http:\/\/maps.gstatic.com\/mapfiles\/place_api\/icons\/generic_business-71.png", 70 | "id": "9a22bf3e2f2d8b28f489d679abdd6c7643770f06", 71 | "name": "Simon Johnson Purveyor of Quality Foods", 72 | "photos": [ 73 | { 74 | "height": 853, 75 | "html_attributions": [ 76 | 77 | ], 78 | "width": 1280 79 | } 80 | ], 81 | "reference": "CpQBhAAAAIbXziVyd16hbYTXvK47ICbl_uV32cg8S5I8g8c-pGLsgnHJsYa7cgB3BDuPTGquc61kj-KhZeLpG8uzsaUVIlc41st1R2DQJhF9njFbVRdFVko8XiRdomsBeQyXSAdf3IxZenVHmORskr7-MEWo3e2YBAoxD0DK63XT4iuEZ7W-Yhk37a_9nkkNPJWxHAUbmxIQdlPWoIxSwdIsr3g9suLAAxoUNhlU2hIlElky-XsqXM6QQ7Sxbm0", 82 | "types": [ 83 | "grocery_or_supermarket", 84 | "food", 85 | "store", 86 | "establishment" 87 | ], 88 | "vicinity": "181 Harris Street, Pyrmont", 89 | "html_attributions": [ 90 | "Listings by Yellow Pages<\/a>" 91 | ] 92 | }, 93 | { 94 | "geometry": { 95 | "location": { 96 | "ob": -33.870468, 97 | "pb": 151.19223 98 | } 99 | }, 100 | "icon": "http:\/\/maps.gstatic.com\/mapfiles\/place_api\/icons\/generic_recreational-71.png", 101 | "id": "d46a505fcf31127fd03684df9a9b87d4a9c70700", 102 | "name": "Panpuri Organic Spa Sydney", 103 | "opening_hours": { 104 | "open_now": true 105 | }, 106 | "photos": [ 107 | { 108 | "height": 800, 109 | "html_attributions": [ 110 | 111 | ], 112 | "width": 534 113 | } 114 | ], 115 | "reference": "CoQBdwAAAI1TnY4y5rRmYUNs4p4OvzjeshgSO0hSRaCAmsQ0UcjJW_zHDgW3rAHAc_601SWpCfafJg1Vwo97dtHY795aLhyzPBEDfwdU-TfL8itLoDJHmI9Ih3junCvZK3gR48JqWYAFpxghXJUjPTfRGo-_9fNg3Dzs90IXwOEkaGihkLHAEhBQrw8ImUkuiQBAUd2NOngpGhR7GLG5Tx9O_tXHFe8hDqxOPWwYAg", 116 | "types": [ 117 | "store", 118 | "spa", 119 | "establishment" 120 | ], 121 | "vicinity": "Suite G.02\/55 Miller Street, Pyrmont", 122 | "html_attributions": [ 123 | "Listings by Yellow Pages<\/a>" 124 | ] 125 | }, 126 | { 127 | "geometry": { 128 | "location": { 129 | "ob": -33.868401, 130 | "pb": 151.194482 131 | } 132 | }, 133 | "icon": "http:\/\/maps.gstatic.com\/mapfiles\/place_api\/icons\/generic_business-71.png", 134 | "id": "cb1001b033f082340237c216f7c8bee2bb1dfc2b", 135 | "name": "Gelato Messina - Pyrmont", 136 | "opening_hours": { 137 | "open_now": true 138 | }, 139 | "photos": [ 140 | { 141 | "height": 1000, 142 | "html_attributions": [ 143 | 144 | ], 145 | "width": 1002 146 | } 147 | ], 148 | "rating": 4.4, 149 | "reference": "CoQBdgAAALJdxk9cWrEKGALjasBKhpvGpNDIVfWG4iGYjl21bjmjjjGukbXruwHUuP8K05o5c6xb1mR9a-ATaYnkVqq3lTfiwr3Azgzd7jvl5ciWDqEXJyXRFF3b00f5OtV6UM3jjoFUBkTTTYBs99q-hcVR5mCsKA9ZdUf11xt-3DX0p7X3EhDFmcd0hBUKlS7OzBpDCKM8GhQcSQqJofkGpGwlI-zJAIzFY6p6MA", 150 | "types": [ 151 | "store", 152 | "food", 153 | "establishment" 154 | ], 155 | "vicinity": "80 Pyrmont Street, Pyrmont", 156 | "html_attributions": [ 157 | "Listings by Yellow Pages<\/a>" 158 | ] 159 | }, 160 | { 161 | "geometry": { 162 | "location": { 163 | "ob": -33.870889, 164 | "pb": 151.196752 165 | } 166 | }, 167 | "icon": "http:\/\/maps.gstatic.com\/mapfiles\/place_api\/icons\/generic_business-71.png", 168 | "id": "33f54dd451cde7093a3b4dfddf1a4180269511ff", 169 | "name": "Kwik Kopy Darling Harbour", 170 | "opening_hours": { 171 | "open_now": true 172 | }, 173 | "photos": [ 174 | { 175 | "height": 155, 176 | "html_attributions": [ 177 | 178 | ], 179 | "width": 220 180 | } 181 | ], 182 | "rating": 4.8, 183 | "reference": "CoQBdgAAAPR3lBLamOezc_hqrf7O4-xbPfaaqnl1CdiG2fQbIN-9UB049Gy5j7uKmaXekPU3a29uUQN6I-QLu7mpz0CfuXOaa7QdShGUQHzQbU1r6mGe9345Y7xD9g7e04TenkHZpr-fFEO6bM6CbOIjQBijnZQkt0-XHHt4zHIqg8D1jOITEhCZQQVNuYedh4Qi27DK8B_dGhThmGGUkXzVDvEFtk7EQreIjjVudw", 184 | "types": [ 185 | "store", 186 | "establishment" 187 | ], 188 | "vicinity": "97\/1-5 Harwood Street, Pyrmont", 189 | "html_attributions": [ 190 | "Listings by Yellow Pages<\/a>" 191 | ] 192 | }, 193 | { 194 | "geometry": { 195 | "location": { 196 | "ob": -33.870136, 197 | "pb": 151.197158 198 | } 199 | }, 200 | "icon": "http:\/\/maps.gstatic.com\/mapfiles\/place_api\/icons\/generic_business-71.png", 201 | "id": "0ffae97910a140a21ee26aa23bf38652320ba37b", 202 | "name": "Sydney Electric Bikes", 203 | "opening_hours": { 204 | "open_now": true 205 | }, 206 | "photos": [ 207 | { 208 | "height": 349, 209 | "html_attributions": [ 210 | 211 | ], 212 | "width": 625 213 | } 214 | ], 215 | "reference": "CoQBcwAAAJZc1y7erTmq2kXZ4agBSMpfQRgOn7whzqbBJjtGgJLAAqKcxYN6hXIRJ5G7ltI3YuMoGxHeZDyxDxvCKruwYYRdedF3AFZ6SzSd1zg8wAEAtHrIOel5VhRaekSf3aB8GCUR3ndHmI91ZrHf8UAjrViDg3Q3g6cGBoeFBHmcgTljEhC6l-8GES0k13K9Nd9p_mnmGhQoSVpfqFeMOemseNN6BY-DN8PRqw", 216 | "types": [ 217 | "bicycle_store", 218 | "store", 219 | "establishment" 220 | ], 221 | "vicinity": "2\/1-9 Pyrmont Bridge Road, Sydney", 222 | "html_attributions": [ 223 | "Listings by Yellow Pages<\/a>" 224 | ] 225 | }, 226 | { 227 | "geometry": { 228 | "location": { 229 | "ob": -33.869657, 230 | "pb": 151.194648 231 | } 232 | }, 233 | "icon": "http:\/\/maps.gstatic.com\/mapfiles\/place_api\/icons\/cafe-71.png", 234 | "id": "902dc97fe7182ef8be9e00b6301802d6695af6de", 235 | "name": "Harrogate Teas NSW", 236 | "reference": "CnRwAAAASgT02o0ORTlyJ1hyUlMkFsXOmfcNPKOwkKEiUkxahjVB0rCeBZfUKwMGXd2nYfVsFQAtbdOMZOkc67ZBiM5GIpHAsUmhCZ4w_r8DBuI_-VJzLWGl9RJwXIghDEusgPmgS7OFW9Eth-CB0s2Ymwf4CRIQ0AyP8UtvfKVKHHAf01xDDBoUn8sBLF50UcrDBX1p34SSwYK8Hsk", 237 | "types": [ 238 | "store", 239 | "cafe", 240 | "food", 241 | "establishment" 242 | ], 243 | "vicinity": "3 Union Street, Pyrmont", 244 | "html_attributions": [ 245 | "Listings by Yellow Pages<\/a>" 246 | ] 247 | }, 248 | { 249 | "geometry": { 250 | "location": { 251 | "ob": -33.869552, 252 | "pb": 151.193474 253 | } 254 | }, 255 | "icon": "http:\/\/maps.gstatic.com\/mapfiles\/place_api\/icons\/wine-71.png", 256 | "id": "b65d54d2c17f107a3cab5ff7a08b42c82cc4552e", 257 | "name": "Porters Liquor Pyrmont Pymont Cellars", 258 | "opening_hours": { 259 | "open_now": true 260 | }, 261 | "photos": [ 262 | { 263 | "height": 853, 264 | "html_attributions": [ 265 | 266 | ], 267 | "width": 1280 268 | } 269 | ], 270 | "reference": "CpQBgwAAAKgUiT46ldtOsIhKFAp_pNlXbEZv-zrqzPMPUewPReN0vxp1BZ5stxHCEhx2WMX6LWOQbVQKCUE-b_NoyxXng_EHfb7BR3FUIEb5zZMB6Q-HJEwnwKJFrQMN0U-SU8MAn-j8WO3ZXLOw3wYMFDSJYn5XdZCWweuhdeyqwHdCFsgseNccImwi9V-lz84UdKA_zhIQEri-IWI8_JRUI2Q0QintaBoUf2VZCegnmUbAA_uiqNLkhxAvC0Y", 271 | "types": [ 272 | "liquor_store", 273 | "store", 274 | "establishment" 275 | ], 276 | "vicinity": "119 Harris Street, Pyrmont", 277 | "html_attributions": [ 278 | "Listings by Yellow Pages<\/a>" 279 | ] 280 | }, 281 | { 282 | "geometry": { 283 | "location": { 284 | "ob": -33.870591, 285 | "pb": 151.19576 286 | } 287 | }, 288 | "icon": "http:\/\/maps.gstatic.com\/mapfiles\/place_api\/icons\/shopping-71.png", 289 | "id": "5fc4a9c674eada22542342da30f447357b0fe33f", 290 | "name": "Life Interiors", 291 | "opening_hours": { 292 | "open_now": true 293 | }, 294 | "photos": [ 295 | { 296 | "height": 430, 297 | "html_attributions": [ 298 | 299 | ], 300 | "width": 450 301 | } 302 | ], 303 | "reference": "CnRsAAAAt3SBMHvbY5VbApvBXcgMtN36oxJNkcCQJ6cFp8lPdGke7usn-XSrHKf1eTfqWWWNofljYhfQtNZ-P4-Ls_HTykjUzUA9yjbBGFGTZy3dANqMcjQeUknZ1UvOuuP5JDpHZX_x6abXodj2l1JmEu0RABIQ-ni8Ydh01mh4MPNRzA2mDRoU-ipbsPGcI4Wb2uxn-TkKE_ouZmo", 304 | "types": [ 305 | "furniture_store", 306 | "general_contractor", 307 | "home_goods_store", 308 | "store", 309 | "establishment" 310 | ], 311 | "vicinity": "2\/104 Pyrmont Street, Pyrmont", 312 | "html_attributions": [ 313 | "Listings by Yellow Pages<\/a>" 314 | ] 315 | }, 316 | { 317 | "geometry": { 318 | "location": { 319 | "ob": -33.869578, 320 | "pb": 151.193734 321 | } 322 | }, 323 | "icon": "http:\/\/maps.gstatic.com\/mapfiles\/place_api\/icons\/generic_business-71.png", 324 | "id": "84c9d028f3f8bc773ad772b19cb6433a0d036fd7", 325 | "name": "Nomadic Rug Traders", 326 | "opening_hours": { 327 | "open_now": false 328 | }, 329 | "reference": "CnRwAAAAMUb17MNFT9Xv13XMtWYCD18e9ps7PeabXm5hoTk2QWsaqiHMdCBBahCoTh_Gwj-BM_jrO7LigZFZMCAbuEeRVhfi_bSrar_9bmNIXYu3Fg0gQRcZwRhdqLwVOZXXc3T9C568g2NaG0_q5-Npux0VWRIQEgRsI5yc0W2RF6PMFx2xUxoUf7uHQNlvIEz1rdGelYK5w0cBUx0", 330 | "types": [ 331 | "store", 332 | "establishment" 333 | ], 334 | "vicinity": "123 Harris Street, Pyrmont", 335 | "html_attributions": [ 336 | "Listings by Yellow Pages<\/a>" 337 | ] 338 | }, 339 | { 340 | "geometry": { 341 | "location": { 342 | "ob": -33.870345, 343 | "pb": 151.19632 344 | } 345 | }, 346 | "icon": "http:\/\/maps.gstatic.com\/mapfiles\/place_api\/icons\/shopping-71.png", 347 | "id": "c5f6eda786046de4419a6a1172feb207d97c8c53", 348 | "name": "Esprit Retail", 349 | "reference": "CnRrAAAAwtq95-Cx7R_PXpVeql39Na4uI5-0mFNcAAEEovkTNKOh3m_AlbRh42EDIIrOXZOXQjFI-0TkdyaMgLIhj7SkLIqt1dp0UELIv2P4PaiqJVgeV0qhp6LuCupSH2Wh5Hwx295DV9XJfJ-udGAIhepjlxIQTIVOhlNCHpTDvD3yen4VzRoU9K90b9HGblfWLlpefg8_w-o2Wn0", 350 | "types": [ 351 | "clothing_store", 352 | "store", 353 | "establishment" 354 | ], 355 | "vicinity": "2 Pyrmont Bridge Road, Pyrmont", 356 | "html_attributions": [ 357 | "Listings by Yellow Pages<\/a>" 358 | ] 359 | }, 360 | { 361 | "geometry": { 362 | "location": { 363 | "ob": -33.86985, 364 | "pb": 151.195975 365 | } 366 | }, 367 | "icon": "http:\/\/maps.gstatic.com\/mapfiles\/place_api\/icons\/restaurant-71.png", 368 | "id": "ac984c4b54c4f0be3c4fa962008d7fd575da63db", 369 | "name": "Francois Pastries", 370 | "price_level": 1, 371 | "reference": "CnRvAAAA-4x2lXWbQyOorWdsNnLwy5eHRfvY1CoeCdQjIU9mPwytSG4xZINvlWqxWOc-e1tbFXH-d0TRHP8pSMgDJt_b2s7TbMdLftJVXE6HVFMPccwDF9qQ0c154UQQY_OzGxjNtJV6dfhgUCZU7iBRtDO2GhIQ19nad8PjISWmyIHd5F5zPRoUBVO9_Ap5v5_sNd2z9Kiov40vRlM", 372 | "types": [ 373 | "bakery", 374 | "store", 375 | "cafe", 376 | "restaurant", 377 | "food", 378 | "establishment" 379 | ], 380 | "vicinity": "35 Union Street, Pyrmont", 381 | "html_attributions": [ 382 | "Listings by Yellow Pages<\/a>" 383 | ] 384 | }, 385 | { 386 | "geometry": { 387 | "location": { 388 | "ob": -33.866518, 389 | "pb": 151.192193 390 | } 391 | }, 392 | "icon": "http:\/\/maps.gstatic.com\/mapfiles\/place_api\/icons\/wine-71.png", 393 | "id": "a24f009db1d39b147b7def691023c46b1c60e895", 394 | "name": "Red Bottle", 395 | "opening_hours": { 396 | "open_now": true 397 | }, 398 | "photos": [ 399 | { 400 | "height": 465, 401 | "html_attributions": [ 402 | "From a Google User" 403 | ], 404 | "width": 700 405 | } 406 | ], 407 | "reference": "CnRoAAAAh8aqyb8DFbVZ-giSpOIjqvvmxpRZQXPylsDNCPUEBV1Qooi-NWm3VdRejufY8F_gDD6ZitFt0DGjdY2-YemRRBSUXihXE9gC0w2uI6ZrO9qlFd0I5Civr8twOyosI72BjQMrKMMlsyby2o3Cj5m-4BIQcGm9S2LSsoGN2pGGkWughBoUdi7362p2GoRpDpVVW2zC5byZ95Q", 408 | "types": [ 409 | "liquor_store", 410 | "store", 411 | "establishment" 412 | ], 413 | "vicinity": "42 Harris Street, Pyrmont", 414 | "html_attributions": [ 415 | "Listings by Yellow Pages<\/a>" 416 | ] 417 | }, 418 | { 419 | "geometry": { 420 | "location": { 421 | "ob": -33.866558, 422 | "pb": 151.191638 423 | } 424 | }, 425 | "icon": "http:\/\/maps.gstatic.com\/mapfiles\/place_api\/icons\/shopping-71.png", 426 | "id": "f603c175cd342b152c51857a7dc2d57d75b89559", 427 | "name": "Narta International", 428 | "reference": "CnRwAAAASJzcHeMkrbw51l46cCeyra2WOWkJGhalrpBtsSv0BPVFK3A0HqtuusLZEbyk11QR-sK3bLcroBRCgipaBhsKV_bdY9rs3TVU6AaBW9N6RyZbclq7F7YFBiJM4oq4ZWGNuosTWO-q9i8w4fU3ML9t9xIQyo99IEV2YCdg_x-siDCk4hoUPgUGWfhVTWn_xuqMnwrbelDMV68", 429 | "types": [ 430 | "home_goods_store", 431 | "store", 432 | "establishment" 433 | ], 434 | "vicinity": "2\/19 Harris Street, Pyrmont", 435 | "html_attributions": [ 436 | "Listings by Yellow Pages<\/a>" 437 | ] 438 | }, 439 | { 440 | "geometry": { 441 | "location": { 442 | "ob": -33.870569, 443 | "pb": 151.19884 444 | } 445 | }, 446 | "icon": "http:\/\/maps.gstatic.com\/mapfiles\/place_api\/icons\/generic_business-71.png", 447 | "id": "12e2b8e9aeb6901029aa6e3d375b80778438087f", 448 | "name": "Harbourside Pharmacy", 449 | "reference": "CoQBcgAAAAu16-aympMLGSUYKSsbQDzlW5Hv9HQum3eO2IZpjRSRgJtcr7ojSvrHxjuW-skO6EnxXwQ9Kx8Kj9znAOe5NMogjh9vTfVwmblk2mbrD-O6zRjdSgOAIm3jjafyWxX0JxyNdz6U11fumr57-T1ofcq0xrFjXKTRKB1eyiCU2AHjEhC9wuqKqmcNXiT9DbPJ5NwsGhQGICWT4dI-M12QUoQEK8ybdhPX3g", 450 | "types": [ 451 | "pharmacy", 452 | "store", 453 | "health", 454 | "establishment" 455 | ], 456 | "vicinity": "2-10 Darling Drive Darling Harbour NSW 2000", 457 | "html_attributions": [ 458 | "Listings by Yellow Pages<\/a>" 459 | ] 460 | }, 461 | { 462 | "geometry": { 463 | "location": { 464 | "ob": -33.870203, 465 | "pb": 151.192924 466 | } 467 | }, 468 | "icon": "http:\/\/maps.gstatic.com\/mapfiles\/place_api\/icons\/generic_business-71.png", 469 | "id": "af16622b1ec256deb574ebccc9bbe27a9e0c3e99", 470 | "name": "SUPA IGA Pyrmont", 471 | "opening_hours": { 472 | "open_now": true 473 | }, 474 | "photos": [ 475 | { 476 | "height": 480, 477 | "html_attributions": [ 478 | 479 | ], 480 | "width": 640 481 | } 482 | ], 483 | "reference": "CnRtAAAAeZMR_sqGGcc-89l-y6sC4vJCfc5ig82Pm911PVz2O87TiOPjIC7VdtI1Vbdgam9NjGqsNpVeXNP7OeTpCuViaqMYZ3u-X5u5X3ARUi5G8CH-0dziN123oNbyKbDkVysV_JAB8HWCmfOKfmhEoTkuvhIQVR2ESnnPIaw13j3X_qNPjxoUfVfXmjxsk8jziwaTk2g6zJpwliw", 484 | "types": [ 485 | "grocery_or_supermarket", 486 | "food", 487 | "store", 488 | "establishment" 489 | ], 490 | "vicinity": "63 Miller Street, Pyrmont", 491 | "html_attributions": [ 492 | "Listings by Yellow Pages<\/a>" 493 | ] 494 | }, 495 | { 496 | "geometry": { 497 | "location": { 498 | "ob": -33.870693, 499 | "pb": 151.191088 500 | } 501 | }, 502 | "icon": "http:\/\/maps.gstatic.com\/mapfiles\/place_api\/icons\/shopping-71.png", 503 | "id": "f9c72de92c000271d139db77a2d0ccce322942b7", 504 | "name": "Unifor Australia", 505 | "reference": "CnRtAAAAIujf7sCzzUnylrJx5S_-AWtNAqoXN0-ndmneMfhR05sup0iss-eyi1ODmnVTTKFWKnahEII4eB09ZibuZB7SOK0YC9zB8x4K7IiUMgI22jNC0nEiahXv0EWR_Cui-1xAX6__IlZ-hF8QUEdYGhImWRIQwd1FCuK97dk0sU4g4sEorxoUY0G-EvvOZ0n6p52w8HeRQBTlHMQ", 506 | "types": [ 507 | "furniture_store", 508 | "general_contractor", 509 | "home_goods_store", 510 | "store", 511 | "establishment" 512 | ], 513 | "vicinity": "140 Bank Street, Pyrmont", 514 | "html_attributions": [ 515 | "Listings by Yellow Pages<\/a>" 516 | ] 517 | }, 518 | { 519 | "geometry": { 520 | "location": { 521 | "ob": -33.864349, 522 | "pb": 151.192666 523 | } 524 | }, 525 | "icon": "http:\/\/maps.gstatic.com\/mapfiles\/place_api\/icons\/shopping-71.png", 526 | "id": "d0738b8807be73c69b298334b2d8a1057f7a93a1", 527 | "name": "Lime Living", 528 | "opening_hours": { 529 | "open_now": true 530 | }, 531 | "reference": "CnRpAAAAw6gb3lUmUzfzAVH9cgDLhh9XvP16KPEl37oofoKdEIqd9J-mskK8CqgJe7De9GsIuc0ByWxtBUmwknB1FtqJczMElL7wGAbK78FlMWFKPoB2H7PA_WLE_x9BP2240xEkdvbqpJKkkW6f3m5NGIQdohIQKdgp7NNlzh-1XfMwGjKvFxoUNg5OCj8MNNSnFmALfuRbxkHju94", 532 | "types": [ 533 | "home_goods_store", 534 | "store", 535 | "establishment" 536 | ], 537 | "vicinity": "Shop 32, Northpoint Plaza, 100 Miller Street, North Sydney", 538 | "html_attributions": [ 539 | "Listings by Yellow Pages<\/a>" 540 | ] 541 | }, 542 | { 543 | "geometry": { 544 | "location": { 545 | "ob": -33.86978, 546 | "pb": 151.196936 547 | } 548 | }, 549 | "icon": "http:\/\/maps.gstatic.com\/mapfiles\/place_api\/icons\/generic_business-71.png", 550 | "id": "5e5c1b219283949a7309bf7bf544ed458be059c5", 551 | "name": "Pulse Foods & Health", 552 | "opening_hours": { 553 | "open_now": false 554 | }, 555 | "photos": [ 556 | { 557 | "height": 1200, 558 | "html_attributions": [ 559 | "From a Google User" 560 | ], 561 | "width": 1200 562 | } 563 | ], 564 | "reference": "CoQBcQAAAGJX4rlRpl6YdX1qUyTbu8lwA22FnnMUsizIAdT93YjkfKaWkdI6dTxzkvwj4c_YrqGB22piznkFneFn5_g-6qoYtoMQFm3-oTGgKSi7F10a_drXW6PLGVjunDwas6MhWIyQeJaHwDVaXmvwGUn9_lP7I0XPpXsIotB-fjW5I7ycEhAS4-W7zpSO3XVDOr6Q-PLNGhRRo2Dqlsj4Yw7rnk8tmpYNMT96JA", 565 | "types": [ 566 | "health", 567 | "grocery_or_supermarket", 568 | "food", 569 | "store", 570 | "establishment" 571 | ], 572 | "vicinity": "60 Union Street, Pyrmont", 573 | "html_attributions": [ 574 | "Listings by Yellow Pages<\/a>" 575 | ] 576 | } 577 | ] 578 | ); -------------------------------------------------------------------------------- /test/mock/placeDetails.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('mockedPlaceDetails', []) 4 | .constant('defaultPDJSON', 5 | { 6 | "formatted_address": "529 Kent Street, Sydney NSW, Australia", 7 | "formatted_phone_number": "(02) 9267 2900", 8 | "reference": "CnRnAAAARpMYRKXwEl8UhHfPX84GQNP7HHPru_ry8P9he6SiQ2l8MUv7t1qA8zbq09mTyg2uzr0Cp4h1eJYVtoDEUPL_9zi-ug8w-oVJ8wnz-9xKjdw9yL9mZOuP8-lc57zLNSkqaRTdR-A1jFL_yi7e6KhbeBIQqABCCuYmIMFtkHyBx6Cp7hoUeHx1wbnovws61axvxyUOjR9mINU", 9 | "website": "http:\/\/www.tetsuyas.com\/" 10 | } 11 | ); --------------------------------------------------------------------------------