├── .editorconfig ├── .gitignore ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── bower.json ├── dist ├── angular-numeraljs.js └── angular-numeraljs.min.js ├── example ├── bower │ ├── bower.json │ ├── index.html │ └── js │ │ └── app.js ├── browserify │ ├── .gitignore │ ├── Gruntfile.js │ ├── index.html │ ├── js │ │ └── app.js │ └── package.json ├── changeLanguages │ ├── index.html │ └── js │ │ └── app.js ├── config │ ├── index.html │ └── js │ │ └── app.js └── simple │ ├── index.html │ └── js │ └── app.js ├── gruntfile.js ├── package.json ├── src └── angular-numeraljs.js └── test ├── karma.conf.js ├── lib ├── angular │ ├── angular-mocks.js │ └── angular.js └── de.js └── unit └── filtersSpec.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # If using Sublime or Atom, download plugin here http://editorconfig.org/#download 2 | root = true 3 | 4 | [src/**.*] 5 | indent_style = space 6 | indent_size = 2 7 | charset = utf-8 8 | insert_final_newline = true 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.DS_Store 2 | node_modules 3 | 4 | *.sublime-project 5 | *.sublime-workspace 6 | 7 | bower_components 8 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .git* 2 | *.DS_Store 3 | *.sublime-project 4 | *.sublime-workspace 5 | bower_components 6 | test/ 7 | example/ 8 | src/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "7" 4 | - "6" 5 | - "4" 6 | before_install: npm install -g grunt-cli 7 | install: npm install 8 | before_script: grunt build 9 | sudo: false 10 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 2.0.0 4 | 5 | * Upgrade to Numeral.js 2.x, with several breaking changes 6 | * $numeraljsConfigProvider's interface has been modified to better match Numeral.js 7 | * `$numeraljsConfigProvider.setCurrentLanguage(lang)` has been renamed to `locale(locale)` 8 | * `$numeraljsConfigProvider.setDefaultFormat(format)` has been renamed to `defaultFormat(format)` 9 | * `$numeraljsConfigProvider.setFormat(name, formatString)` has been renamed to `namedFormat(name, formatString)` 10 | * `$numeraljsConfigProvider.setLanguage(lang, def)` has been renamed to `register('locale', name, def)` 11 | * Fixed indentation to match EditorConfig settings 12 | * Upgraded Karma & related test dependencies 13 | 14 | ## 1.3.0 15 | 16 | * Remove the separate CommonJS build in favor of a single UMD module. 17 | 18 | ## 1.2.0 19 | 20 | * Adds support for reconfiguring the library at runtime via injectable $numeraljsConfig. 21 | * Added bower example. 22 | * Updated various build dependencies to recent versions. 23 | 24 | ## 1.1.6 25 | 26 | * Bug fix 27 | 28 | ## 1.1.5 29 | 30 | * Fix minified version by annotating the Angular dependencies 31 | 32 | ## 1.1.4 33 | 34 | * Exclude files from the npm package 35 | * Add commonjs version 36 | * Add Browserify/Grunt example 37 | * Updated examples, unit tests 38 | 39 | ## 1.1.0 40 | 41 | * Added $numeraljsConfigProvider and various configurations 42 | 43 | ## 1.0.3 44 | 45 | * Added language option to the 'numeraljs' filter 46 | * Bug fixes 47 | 48 | ## 1.0.0 49 | 50 | * First version 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | Copyright (c) 2013-2017 Dave Bauman 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Angular Numeral.js filter 2 | 3 | [![npm](https://img.shields.io/npm/v/angular-numeraljs.svg)](https://www.npmjs.com/package/angular-numeraljs) [![Build Status](https://travis-ci.org/baumandm/angular-numeraljs.svg?branch=master)](https://travis-ci.org/baumandm/angular-numeraljs) 4 | 5 | This is an Angular.js filter that applies [Numeral.js](http://numeraljs.com/) formatting. 6 | 7 | The latest version of this library uses Numeral.js 2.x branch. There are several breaking changes in Numeral.js, as well as breaking changes in this library. If you depend on Numeral.js 1.x and cannot upgrade, please use the latest 1.x release (and corresponding `1.x` branch). 8 | 9 | ## Breaking Changes in 2.x 10 | 11 | For details on breaking changes in Numeral.js itself, please see its [changelog](https://github.com/adamwdraper/Numeral-js/tree/master#200). 12 | 13 | In addition, angular-numeraljs has the following breaking changes from in 1.3.0 to 2.0.0: 14 | 15 | * `$numeraljsConfigProvider.setCurrentLanguage(lang)` has been renamed to `locale(locale)` 16 | * `$numeraljsConfigProvider.setDefaultFormat(format)` has been renamed to `defaultFormat(format)` 17 | * `$numeraljsConfigProvider.setFormat(name, formatString)` has been renamed to `namedFormat(name, formatString)` 18 | * `$numeraljsConfigProvider.setLanguage(lang, def)` has been renamed to `register('locale', name, def)` 19 | 20 | ## How to Use 21 | 22 | 1. Include Numeral.js in your project 23 | 24 | 2. Include either the minified or non-minified javascript file from the `/dist/` folder: 25 | 26 | ```html 27 | 28 | ``` 29 | 30 | 3. Inject the `ngNumeraljs` filter into your app module: 31 | 32 | ```javascript 33 | var myApp = angular.module('myApp', ['ngNumeraljs']); 34 | ``` 35 | 36 | 4. Apply the filter with the desired format string: 37 | ```html 38 |

39 | {{ price | numeraljs:'$0,0.00' }} 40 |

41 | ``` 42 | 43 | ## Advanced Usage 44 | 45 | You can configure `ngNumeraljs` during Angular's configuration phase using the $numeraljsConfigProvider: 46 | 47 | ```js 48 | var app = angular.module('exampleApp', ['ngNumeraljs']); 49 | 50 | app.config(['$numeraljsConfigProvider', function ($numeraljsConfigProvider) { 51 | // place configuration here 52 | }]); 53 | ``` 54 | 55 | Numeral.js must be already loaded in the browser prior to using `$numeraljsConfigProvider`. 56 | 57 | ### Named Formats 58 | 59 | `$numeraljsConfigProvider.namedFormat(name, formatString)` - defines a named format which can be used in place of the format string in the filter. 60 | 61 | ```js 62 | app.config(['$numeraljsConfigProvider', function ($numeraljsConfigProvider) { 63 | $numeraljsConfigProvider.namedFormat('currency', '$ 0,0.00'); 64 | }]); 65 | ``` 66 | 67 | In markup, 68 | 69 | ```html 70 |

71 | {{ price | numeraljs:'currency' }} 72 |

73 | ``` 74 | 75 | ### Default Format 76 | 77 | Numeral.js defines the default format as '0,0', so this format is used if none is provided to the filter. 78 | 79 | `$numeraljsConfigProvider.defaultFormat(format)` - overrides the built-in default format. 80 | 81 | ```js 82 | app.config(['$numeraljsConfigProvider', function ($numeraljsConfigProvider) { 83 | $numeraljsConfigProvider.defaultFormat('0.0 $'); 84 | }]); 85 | ``` 86 | 87 | In markup, 88 | 89 | ```html 90 |

91 | {{ price | numeraljs }} 92 |

93 | ``` 94 | 95 | ### Custom Locales and Formats 96 | 97 | `$numeraljsConfigProvider.register(type, name, definition)` - adds new locale or format definitions to Numeral.js. `type` must be either `'locale'` or `'format'`. For complete details, please refer to the Numeral.js [documentation](http://numeraljs.com/#locales). 98 | 99 | ```js 100 | app.config(['$numeraljsConfigProvider', function ($numeraljsConfigProvider) { 101 | // Register a new Locale 102 | $numeraljsConfigProvider.register('locale', 'de', { 103 | delimiters: { 104 | thousands: ' ', 105 | decimal: ',' 106 | }, 107 | abbreviations: { 108 | thousand: 'k', 109 | million: 'm', 110 | billion: 'b', 111 | trillion: 't' 112 | }, 113 | ordinal: function (number) { 114 | return '.'; 115 | }, 116 | currency: { 117 | symbol: '€' 118 | } 119 | }); 120 | 121 | // Register a new Format 122 | $numeraljsConfigProvider.register('format', 'percentage', { 123 | regexps: { 124 | format: /(%)/, 125 | unformat: /(%)/ 126 | }, 127 | format: function(value, format, roundingFunction) { 128 | var space = numeral._.includes(format, ' %') ? ' ' : '', 129 | output; 130 | 131 | value = value * 100; 132 | 133 | // check for space before % 134 | format = format.replace(/\s?\%/, ''); 135 | 136 | output = numeral._.numberToFormat(value, format, roundingFunction); 137 | 138 | if (numeral._.includes(output, ')')) { 139 | output = output.split(''); 140 | 141 | output.splice(-1, 0, space + '%'); 142 | 143 | output = output.join(''); 144 | } else { 145 | output = output + space + '%'; 146 | } 147 | 148 | return output; 149 | }, 150 | unformat: function(string) { 151 | return numeral._.stringToNumber(string) * 0.01; 152 | } 153 | }); 154 | 155 | $numeraljsConfigProvider.defaultFormat('0%'); 156 | $numeraljsConfigProvider.locale('de'); 157 | }]); 158 | ``` 159 | 160 | Please note that registering a new format will add new formatting functionality, e.g. adding a new character to the format string. If you want to add a short name for a specific format string, see `namedFormat()` above. 161 | 162 | See a list of available locales here: [locale](https://github.com/adamwdraper/Numeral-js/tree/master/locales). 163 | 164 | Locales or formats can be loaded directly into Numeral.js as well, e.g. by loading the [locale files](https://github.com/adamwdraper/Numeral-js/tree/master/locales) after Numeral.js is loaded. Angular-numeraljs can use these locales or formats even if they are not set via this provider. 165 | 166 | ### Select Locale 167 | 168 | `$numeraljsConfigProvider.locale(locale)` - selects the current locale. The locale must be loaded either by `$numeraljsConfigProvider.register()` or by loading the Numeral.js locale file. 169 | 170 | ```js 171 | app.config(['$numeraljsConfigProvider', function ($numeraljsConfigProvider) { 172 | $numeraljsConfigProvider.locale('de'); 173 | }]); 174 | ``` 175 | 176 | ### Runtime Configuration 177 | 178 | It is possible to change all of the configurations at runtime by injecting `$numeraljsConfig`: 179 | 180 | app.controller('numeralExample', function ($scope, $numeraljsConfig) { 181 | $numeraljsConfig.defaultFormat('0,0.0'); 182 | $numeraljsConfig.locale($scope.locale); 183 | }); 184 | 185 | This may be useful for websites with a language switcher, saved user preferences, etc. 186 | 187 | ## Examples 188 | 189 | There are several examples in the `example/` folder which can be used for reference: 190 | 191 | * _Simple_: using this library in the most basic way possible 192 | * _Config_: using $numeraljsConfigProvider to configure this library 193 | * _ChangingLanguages_: changing languages (or other properties) at runtime (vs initialization) 194 | * _Bower_: adding a dependency through Bower 195 | * _Browserify_: adding a dependency through Browserify 196 | 197 | ## Bower 198 | 199 | This filter can be installed via Bower with the following dependency in the `bower.json` file. 200 | 201 | "dependencies": { 202 | "angular-numeraljs": "^2.0" 203 | } 204 | 205 | ## Browserify 206 | 207 | This project is published in NPM as `angular-numeraljs`. 208 | 209 | "dependencies": { 210 | "angular-numeraljs": "^2.0" 211 | } 212 | 213 | The `example/browserify` folder has a working example with Browserify and Grunt. To build this project, install [Grunt](http://gruntjs.com/) and [Browserify](http://browserify.org/) and run the following: 214 | 215 | cd example/browserify 216 | npm install 217 | grunt build 218 | 219 | Then open `example/browserify/dist/index.html` in a browser. 220 | 221 | # Building 222 | 223 | 1. Install [Grunt CLI](http://gruntjs.com/getting-started) and [Node.js](http://nodejs.org/) 224 | 225 | 2. Install Node packages 226 | 227 | npm install 228 | 229 | 3. Build via Grunt 230 | 231 | grunt build 232 | 233 | The `/dist/` folder contains the regular and minified Javascript files. 234 | 235 | 4. Tests are automatically run during the build, but they can be run manually as well 236 | 237 | grunt test 238 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-numeraljs", 3 | "author": "Dave Bauman", 4 | "version": "1.3.0", 5 | "license": "MIT", 6 | "main": ["./dist/angular-numeraljs.js"], 7 | "repository": { 8 | "type": "git", 9 | "url": "git@github.com:baumandm/angular-numeraljs.git" 10 | }, 11 | "dependencies": { 12 | "numeral": "~2.0.4" 13 | }, 14 | "keywords": [ 15 | "numeral", 16 | "numeraljs", 17 | "angular", 18 | "angularjs" 19 | ], 20 | "ignore": [ 21 | "src", 22 | "test", 23 | "bower_components", 24 | "bower.json", 25 | "gruntfile.js" 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /dist/angular-numeraljs.js: -------------------------------------------------------------------------------- 1 | /** 2 | * AngularJS filter for Numeral.js: number formatting as a filter 3 | * @version v2.0.1 - 2017-05-06 4 | * @link https://github.com/baumandm/angular-numeraljs 5 | * @author Dave Bauman 6 | * @license MIT License, http://www.opensource.org/licenses/MIT 7 | */ 8 | 'use strict'; 9 | 10 | (function (root, factory) { 11 | if (typeof exports === 'object') { 12 | // CommonJS 13 | module.exports = factory(require('numeral')); 14 | } else if (typeof define === 'function' && define.amd) { 15 | // AMD 16 | define(['numeral'], function (numeral) { 17 | return (root.ngNumeraljs = factory(numeral)); 18 | }); 19 | } else { 20 | // Global Variables 21 | root.ngNumeraljs = factory(root.numeral); 22 | } 23 | }(this, function (numeral) { 24 | return angular.module('ngNumeraljs', []) 25 | .provider('$numeraljsConfig', function () { 26 | var formats = {}; 27 | 28 | this.defaultFormat = function (format) { 29 | numeral.defaultFormat(format); 30 | }; 31 | 32 | this.locale = function (locale) { 33 | numeral.locale(locale); 34 | }; 35 | 36 | this.namedFormat = function (name, format) { 37 | formats[name] = format; 38 | }; 39 | 40 | this.register = function (type, name, def) { 41 | numeral.register(type, name, def); 42 | }; 43 | 44 | this.$get = function () { 45 | return { 46 | customFormat: function (name) { 47 | return formats[name] || name; 48 | }, 49 | defaultFormat: this.defaultFormat, 50 | locale: this.locale, 51 | register: this.register, 52 | namedFormat: this.namedFormat 53 | }; 54 | }; 55 | }) 56 | .filter('numeraljs', ['$numeraljsConfig', function ($numeraljsConfig) { 57 | return function (input, format) { 58 | if (input == null) { 59 | return input; 60 | } 61 | 62 | format = $numeraljsConfig.customFormat(format); 63 | 64 | return numeral(input).format(format); 65 | }; 66 | }]); 67 | })); 68 | -------------------------------------------------------------------------------- /dist/angular-numeraljs.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * AngularJS filter for Numeral.js: number formatting as a filter 3 | * @version v2.0.1 - 2017-05-06 4 | * @link https://github.com/baumandm/angular-numeraljs 5 | * @author Dave Bauman 6 | * @license MIT License, http://www.opensource.org/licenses/MIT 7 | */ 8 | 9 | "use strict";!function(a,b){"object"==typeof exports?module.exports=b(require("numeral")):"function"==typeof define&&define.amd?define(["numeral"],function(c){return a.ngNumeraljs=b(c)}):a.ngNumeraljs=b(a.numeral)}(this,function(a){return angular.module("ngNumeraljs",[]).provider("$numeraljsConfig",function(){var b={};this.defaultFormat=function(b){a.defaultFormat(b)},this.locale=function(b){a.locale(b)},this.namedFormat=function(a,c){b[a]=c},this.register=function(b,c,d){a.register(b,c,d)},this.$get=function(){return{customFormat:function(a){return b[a]||a},defaultFormat:this.defaultFormat,locale:this.locale,register:this.register,namedFormat:this.namedFormat}}}).filter("numeraljs",["$numeraljsConfig",function(b){return function(c,d){return null==c?c:(d=b.customFormat(d),a(c).format(d))}}])}); -------------------------------------------------------------------------------- /example/bower/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-numeraljs-bower-example", 3 | "author": "Dave Bauman", 4 | "version": "1.2.0", 5 | "license": "MIT", 6 | "private": true, 7 | "repository": { 8 | "type": "git", 9 | "url": "git@github.com:baumandm/angular-numeraljs.git" 10 | }, 11 | "dependencies": { 12 | "angularjs": "v1.3.14", 13 | "angular-numeraljs": "^1.0" 14 | }, 15 | "ignore": [ 16 | "src", 17 | "test", 18 | "bower_components", 19 | "bower.json", 20 | "gruntfile.js" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /example/bower/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Angular-numeraljs Bower Example 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 32 |

Angular-numeraljs Bower Example

33 |
34 | 37 |
38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 |
NameFormatValue
{{ format.name }}{{ format.format }}{{ number | numeraljs:format.format }}
51 | 52 | 53 | -------------------------------------------------------------------------------- /example/bower/js/app.js: -------------------------------------------------------------------------------- 1 | var app = angular.module('exampleApp', ['ngNumeraljs']); 2 | 3 | app.controller('numeralExample', function ($scope) { 4 | $scope.formats = [{ 5 | name: 'Default Format', 6 | }, { 7 | name: 'Number', 8 | format: '0,0' 9 | }, { 10 | name: 'Currency', 11 | format: '$0,0.00' 12 | },{ 13 | name: 'Bytes', 14 | format: '0b' 15 | }, { 16 | name: 'Percentages', 17 | format: '0.0%' 18 | }, { 19 | name: 'Time', 20 | format: '00:00:00' 21 | }]; 22 | }); -------------------------------------------------------------------------------- /example/browserify/.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | node_modules -------------------------------------------------------------------------------- /example/browserify/Gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = function(grunt) { 3 | 4 | grunt.initConfig({ 5 | 6 | clean: ['dist/*'], 7 | 8 | browserify: { 9 | js: { 10 | /* A single entry point the app */ 11 | src: 'js/app.js', 12 | 13 | /* Compile to a single file */ 14 | dest: 'dist/js/app.js', 15 | }, 16 | }, 17 | 18 | copy: { 19 | all: { 20 | /* Copy all html into the dist/ folder */ 21 | expand: true, 22 | src: ['**/*.html', '!node_modules/**'], 23 | dest: 'dist/', 24 | } 25 | }, 26 | 27 | concat: { 28 | main:{ 29 | src: [ 30 | 'dist/js/*.js', 31 | ], 32 | dest: 'dist/js/all.js' 33 | } 34 | }, 35 | 36 | watch: { 37 | files: ['js/*.js'], 38 | tasks: ['js'] 39 | } 40 | }); 41 | 42 | grunt.loadNpmTasks('grunt-browserify'); 43 | grunt.loadNpmTasks('grunt-contrib-copy'); 44 | grunt.loadNpmTasks('grunt-contrib-concat'); 45 | grunt.loadNpmTasks('grunt-contrib-clean'); 46 | grunt.loadNpmTasks('grunt-contrib-watch'); 47 | 48 | grunt.registerTask('js', ['clean', 'browserify', 'concat']); 49 | 50 | // Build task. 51 | grunt.registerTask('build', ['js', 'copy']); 52 | 53 | grunt.registerTask('default', ['build']); 54 | 55 | }; 56 | -------------------------------------------------------------------------------- /example/browserify/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Angular-numeraljs Example 7 | 8 | 9 | 10 | 11 | 12 | 13 | 28 |

Angular-numeraljs Browserify Example

29 |
30 | 33 |
34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 |
NameFormatValue
{{ format.name }}{{ format.format }}{{ number | numeraljs:format.format }}
47 | 48 | -------------------------------------------------------------------------------- /example/browserify/js/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* Include Angular via Browserify */ 4 | require('angular/angular'); 5 | require('angular-numeraljs/dist/angular-numeraljs'); 6 | 7 | var app = angular.module('exampleApp', ['ngNumeraljs']); 8 | 9 | app.config(['$numeraljsConfigProvider', function ($numeraljsConfigProvider) { 10 | 11 | $numeraljsConfigProvider.setDefaultFormat('0,0.00'); 12 | 13 | }]); 14 | 15 | app.controller('numeralExample', function ($scope) { 16 | $scope.formats = [{ 17 | name: 'Default Format', 18 | }, { 19 | name: 'Number', 20 | format: '0,0' 21 | }, { 22 | name: 'Currency', 23 | format: '$0,0.00' 24 | },{ 25 | name: 'Bytes', 26 | format: '0b' 27 | }, { 28 | name: 'Percentages', 29 | format: '0.0%' 30 | }, { 31 | name: 'Time', 32 | format: '00:00:00' 33 | }]; 34 | }); 35 | -------------------------------------------------------------------------------- /example/browserify/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-numeraljs-browserify-sample", 3 | "description": "Browserify sample for Angular-numeraljs", 4 | "version": "1.3.0", 5 | "homepage": "https://github.com/baumandm/angular-numeraljs", 6 | "author": "Dave Bauman ", 7 | "repository": { 8 | "type": "git", 9 | "url": "git@github.com:baumandm/angular-numeraljs.git" 10 | }, 11 | "licenses": [ 12 | { 13 | "type": "MIT", 14 | "url": "http://mit-license.org" 15 | } 16 | ], 17 | "dependencies": { 18 | "angular": "^1.4.7", 19 | "angular-numeraljs": "^1.0" 20 | }, 21 | "devDependencies": { 22 | "grunt": ">= 0.4.0", 23 | "grunt-browserify": "*", 24 | "grunt-contrib-clean": "*", 25 | "grunt-contrib-concat": "*", 26 | "grunt-contrib-copy": "*", 27 | "grunt-contrib-jshint": "*", 28 | "grunt-contrib-uglify": "*", 29 | "grunt-contrib-watch": "*" 30 | }, 31 | "scripts": { 32 | "test": "grunt karma:unit" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /example/changeLanguages/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Angular-numeraljs Example - Changing Languages 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 32 |

Angular-numeraljs Example: Changing Languages

33 |
34 | 37 | 44 |
47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 |
NameFormatValue
{{ format.name }}{{ format.format }}{{ number | numeraljs:format.format }}
60 | 61 | -------------------------------------------------------------------------------- /example/changeLanguages/js/app.js: -------------------------------------------------------------------------------- 1 | var app = angular.module('exampleApp', ['ngNumeraljs']); 2 | 3 | app.config(['$numeraljsConfigProvider', function ($numeraljsConfigProvider) { 4 | // Load some additional languages 5 | $numeraljsConfigProvider.register('locale', 'fr', { 6 | delimiters: { 7 | thousands: ' ', 8 | decimal: ',' 9 | }, 10 | abbreviations: { 11 | thousand: 'k', 12 | million: 'm', 13 | billion: 'b', 14 | trillion: 't' 15 | }, 16 | ordinal : function (number) { 17 | return number === 1 ? 'er' : 'ème'; 18 | }, 19 | currency: { 20 | symbol: '€' 21 | } 22 | }); 23 | 24 | $numeraljsConfigProvider.register('locale', 'de', { 25 | delimiters: { 26 | thousands: ' ', 27 | decimal: ',' 28 | }, 29 | abbreviations: { 30 | thousand: 'k', 31 | million: 'm', 32 | billion: 'b', 33 | trillion: 't' 34 | }, 35 | ordinal: function (number) { 36 | return '.'; 37 | }, 38 | currency: { 39 | symbol: '€' 40 | } 41 | }); 42 | 43 | $numeraljsConfigProvider.locale('en'); 44 | }]); 45 | 46 | app.controller('numeralExample', function ($scope, $numeraljsConfig) { 47 | $scope.formats = [{ 48 | name: 'Default Format', 49 | }, { 50 | name: 'Number', 51 | format: '0,0' 52 | }, { 53 | name: 'Currency', 54 | format: '$0,0.00' 55 | },{ 56 | name: 'Bytes', 57 | format: '0b' 58 | }, { 59 | name: 'Percentages', 60 | format: '0.0%' 61 | }, { 62 | name: 'Time', 63 | format: '00:00:00' 64 | }]; 65 | 66 | $scope.currentLanguage = 'en'; 67 | 68 | $scope.$watch('currentLanguage', function (language) { 69 | $numeraljsConfig.locale(language); 70 | }); 71 | 72 | 73 | $scope.defaultFormat = null; 74 | 75 | $scope.$watch('defaultFormat', function (format) { 76 | if (format == null) return; 77 | $numeraljsConfig.defaultFormat(format); 78 | }); 79 | }); 80 | -------------------------------------------------------------------------------- /example/config/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Angular-numeraljs Example with Configuration 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 33 |

Angular-numeraljs Example with Configuration

34 |
35 | 38 |
39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 |
NameFormatValue
{{ format.name }}{{ format.format }}{{ number | numeraljs:format.format }}
52 | 53 | 54 | -------------------------------------------------------------------------------- /example/config/js/app.js: -------------------------------------------------------------------------------- 1 | var app = angular.module('exampleApp', ['ngNumeraljs']); 2 | 3 | app.config(['$numeraljsConfigProvider', function ($numeraljsConfigProvider) { 4 | var locale = { 5 | delimiters: { 6 | thousands: ' ', 7 | decimal: ',' 8 | }, 9 | abbreviations: { 10 | thousand: 'k', 11 | million: 'm', 12 | billion: 'b', 13 | trillion: 't' 14 | }, 15 | ordinal: function (number) { 16 | return '.'; 17 | }, 18 | currency: { 19 | symbol: '€' 20 | } 21 | }; 22 | 23 | $numeraljsConfigProvider.defaultFormat('0,0.00'); 24 | 25 | // Add some named formats 26 | $numeraljsConfigProvider.namedFormat('currency', '$ 0,0.00'); 27 | $numeraljsConfigProvider.namedFormat('currencySuffix', '0,0.00 $'); 28 | $numeraljsConfigProvider.namedFormat('number', '0.00'); 29 | 30 | // Custom locale 31 | $numeraljsConfigProvider.register('locale', 'de', locale); 32 | $numeraljsConfigProvider.locale('de'); 33 | }]); 34 | 35 | app.controller('numeralExample', function ($scope) { 36 | $scope.formats = [{ 37 | name: 'Default Format', 38 | }, { 39 | name: 'Number', 40 | format: '0,0' 41 | }, { 42 | name: 'Currency', 43 | format: '$0,0.00' 44 | },{ 45 | name: 'Bytes', 46 | format: '0b' 47 | }, { 48 | name: 'Percentages', 49 | format: '0.0%' 50 | }, { 51 | name: 'Time', 52 | format: '00:00:00' 53 | }]; 54 | }); 55 | -------------------------------------------------------------------------------- /example/simple/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Angular-numeraljs Example 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 32 |

Angular-numeraljs Example

33 |
34 | 37 |
38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 |
NameFormatValue
{{ format.name }}{{ format.format }}{{ number | numeraljs:format.format }}
51 | 52 | 53 | -------------------------------------------------------------------------------- /example/simple/js/app.js: -------------------------------------------------------------------------------- 1 | var app = angular.module('exampleApp', ['ngNumeraljs']); 2 | 3 | app.controller('numeralExample', function ($scope) { 4 | $scope.formats = [{ 5 | name: 'Default Format', 6 | }, { 7 | name: 'Number', 8 | format: '0,0' 9 | }, { 10 | name: 'Currency', 11 | format: '$0,0.00' 12 | },{ 13 | name: 'Bytes', 14 | format: '0b' 15 | }, { 16 | name: 'Percentages', 17 | format: '0.0%' 18 | }, { 19 | name: 'Time', 20 | format: '00:00:00' 21 | }]; 22 | }); 23 | -------------------------------------------------------------------------------- /gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = function(grunt) { 3 | 4 | // Project configuration. 5 | grunt.initConfig({ 6 | pkg: grunt.file.readJSON('package.json'), 7 | meta: { 8 | banner: '/**\n' + 9 | ' * <%= pkg.description %>\n' + 10 | ' * @version v<%= pkg.version %> - <%= grunt.template.today("yyyy-mm-dd") %>\n' + 11 | ' * @link <%= pkg.homepage %>\n' + 12 | ' * @author <%= pkg.author %>\n' + 13 | ' * @license MIT License, http://www.opensource.org/licenses/MIT\n' + 14 | ' */\n' 15 | }, 16 | dirs: { 17 | dest: 'dist' 18 | }, 19 | concat: { 20 | options: { 21 | banner: '<%= meta.banner %>' 22 | }, 23 | dist: { 24 | src: ['src/*.js'], 25 | dest: '<%= dirs.dest %>/<%= pkg.name %>.js' 26 | } 27 | }, 28 | ngAnnotate: { 29 | options: { 30 | singleQuotes: true 31 | }, 32 | dist: { 33 | files: { 34 | '<%= dirs.dest %>/<%= pkg.name %>.js': ['<%= dirs.dest %>/<%= pkg.name %>.js'] 35 | } 36 | } 37 | }, 38 | uglify: { 39 | options: { 40 | banner: '<%= meta.banner %>' 41 | }, 42 | dist: { 43 | src: ['<%= concat.dist.dest %>'], 44 | dest: '<%= dirs.dest %>/<%= pkg.name %>.min.js' 45 | } 46 | }, 47 | jshint: { 48 | files: ['Gruntfile.js', 'src/*.js', 'test/unit/*.js'], 49 | options: { 50 | bitwise: true, 51 | boss: true, 52 | browser: true, 53 | camelcase: true, 54 | curly: true, 55 | eqeqeq: true, 56 | eqnull: true, 57 | expr: true, 58 | freeze: true, 59 | immed: true, 60 | latedef: true, 61 | newcap: true, 62 | noarg: true, 63 | node: true, 64 | quotmark: 'single', 65 | strict: true, 66 | sub: true, 67 | undef: true, 68 | unused: true, 69 | validthis: true, 70 | globals: { 71 | exports: true, 72 | angular: false, 73 | require: false, 74 | define: false, 75 | $: false 76 | } 77 | } 78 | }, 79 | karma: { 80 | unit: { 81 | singleRun: true, 82 | configFile: 'test/karma.conf.js' 83 | } 84 | } 85 | }); 86 | 87 | grunt.loadNpmTasks('grunt-contrib-concat'); 88 | grunt.loadNpmTasks('grunt-contrib-jshint'); 89 | grunt.loadNpmTasks('grunt-contrib-uglify'); 90 | grunt.loadNpmTasks('grunt-ng-annotate'); 91 | grunt.loadNpmTasks('grunt-karma'); 92 | 93 | // Default task. 94 | grunt.registerTask('default', ['test']); 95 | 96 | // Test tasks. 97 | grunt.registerTask('test', ['jshint', 'karma:unit']); 98 | 99 | // Build task. 100 | grunt.registerTask('build', ['test', 'concat', 'ngAnnotate', 'uglify']); 101 | 102 | // run devserver 103 | grunt.registerTask('webserver', ['connect:devserver']); 104 | 105 | 106 | }; 107 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-numeraljs", 3 | "description": "AngularJS filter for Numeral.js: number formatting as a filter", 4 | "version": "2.0.1", 5 | "homepage": "https://github.com/baumandm/angular-numeraljs", 6 | "author": "Dave Bauman ", 7 | "repository": { 8 | "type": "git", 9 | "url": "git@github.com:baumandm/angular-numeraljs.git" 10 | }, 11 | "bugs": { 12 | "url": "https://github.com/baumandm/angular-numeraljs/issues" 13 | }, 14 | "license": "MIT", 15 | "dependencies": { 16 | "numeral": ">=2.0.0" 17 | }, 18 | "devDependencies": { 19 | "grunt": "~1.0.1", 20 | "grunt-contrib-concat": "*", 21 | "grunt-contrib-jshint": "*", 22 | "grunt-contrib-uglify": "*", 23 | "grunt-karma": "2.0.0", 24 | "grunt-ng-annotate": "~3.0.0", 25 | "jasmine-core": "~2.6.1", 26 | "karma": "~1.7.0", 27 | "karma-jasmine": "~1.1.0", 28 | "karma-phantomjs-launcher": "~1.0.2", 29 | "phantomjs-prebuilt": "~2.1" 30 | }, 31 | "scripts": { 32 | "test": "grunt test" 33 | }, 34 | "main": "./dist/angular-numeraljs.js" 35 | } 36 | -------------------------------------------------------------------------------- /src/angular-numeraljs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | (function (root, factory) { 4 | if (typeof exports === 'object') { 5 | // CommonJS 6 | module.exports = factory(require('numeral')); 7 | } else if (typeof define === 'function' && define.amd) { 8 | // AMD 9 | define(['numeral'], function (numeral) { 10 | return (root.ngNumeraljs = factory(numeral)); 11 | }); 12 | } else { 13 | // Global Variables 14 | root.ngNumeraljs = factory(root.numeral); 15 | } 16 | }(this, function (numeral) { 17 | return angular.module('ngNumeraljs', []) 18 | .provider('$numeraljsConfig', function () { 19 | var formats = {}; 20 | 21 | this.defaultFormat = function (format) { 22 | numeral.defaultFormat(format); 23 | }; 24 | 25 | this.locale = function (locale) { 26 | numeral.locale(locale); 27 | }; 28 | 29 | this.namedFormat = function (name, format) { 30 | formats[name] = format; 31 | }; 32 | 33 | this.register = function (type, name, def) { 34 | numeral.register(type, name, def); 35 | }; 36 | 37 | this.$get = function () { 38 | return { 39 | customFormat: function (name) { 40 | return formats[name] || name; 41 | }, 42 | defaultFormat: this.defaultFormat, 43 | locale: this.locale, 44 | register: this.register, 45 | namedFormat: this.namedFormat 46 | }; 47 | }; 48 | }) 49 | .filter('numeraljs', function ($numeraljsConfig) { 50 | return function (input, format) { 51 | if (input == null) { 52 | return input; 53 | } 54 | 55 | format = $numeraljsConfig.customFormat(format); 56 | 57 | return numeral(input).format(format); 58 | }; 59 | }); 60 | })); 61 | -------------------------------------------------------------------------------- /test/karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function(config) { 2 | config.set({ 3 | 4 | // base path, that will be used to resolve files and exclude 5 | basePath: './..', 6 | 7 | // frameworks to use 8 | frameworks: ['jasmine'], 9 | 10 | // list of files / patterns to load in the browser 11 | files: [ 12 | 'test/lib/angular/angular.js', 13 | 'test/lib/angular/angular-mocks.js', 14 | 'node_modules/numeral/numeral.js', 15 | 'src/*.js', 16 | 'test/unit/*.js' 17 | ], 18 | 19 | // list of files to exclude 20 | exclude: [], 21 | 22 | // use dots reporter, as travis terminal does not support escaping sequences 23 | // possible values: 'dots', 'progress', 'junit' 24 | // CLI --reporters progress 25 | // 'coverage', 26 | reporters: ['progress'], 27 | 28 | // web server port 29 | // CLI --port 9876 30 | port: 9876, 31 | 32 | // enable / disable colors in the output (reporters and logs) 33 | // CLI --colors --no-colors 34 | colors: true, 35 | 36 | // level of logging 37 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 38 | // CLI --log-level debug 39 | logLevel: config.LOG_INFO, 40 | 41 | // enable / disable watching file and executing tests whenever any file changes 42 | // CLI --auto-watch --no-auto-watch 43 | autoWatch: false, 44 | 45 | // Start these browsers, currently available: 46 | // - Chrome 47 | // - ChromeCanary 48 | // - Firefox 49 | // - Opera 50 | // - Safari (only Mac) 51 | // - PhantomJS 52 | // - IE (only Windows) 53 | // CLI --browsers Chrome,Firefox,Safari 54 | browsers: [ 55 | 'PhantomJS' 56 | ], 57 | 58 | // If browser does not capture in given timeout [ms], kill it 59 | // CLI --capture-timeout 5000 60 | captureTimeout: 20000, 61 | 62 | // Auto run tests on start (when browsers are captured) and exit 63 | // CLI --single-run --no-single-run 64 | singleRun: true, 65 | 66 | // report which specs are slower than 500ms 67 | // CLI --report-slower-than 500 68 | reportSlowerThan: 500, 69 | 70 | plugins: [ 71 | 'karma-jasmine', 72 | 'karma-phantomjs-launcher' 73 | ] 74 | }); 75 | }; 76 | -------------------------------------------------------------------------------- /test/lib/angular/angular-mocks.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license AngularJS v1.5.1 3 | * (c) 2010-2016 Google, Inc. http://angularjs.org 4 | * License: MIT 5 | */ 6 | (function(window, angular, undefined) { 7 | 8 | 'use strict'; 9 | 10 | /** 11 | * @ngdoc object 12 | * @name angular.mock 13 | * @description 14 | * 15 | * Namespace from 'angular-mocks.js' which contains testing related code. 16 | */ 17 | angular.mock = {}; 18 | 19 | /** 20 | * ! This is a private undocumented service ! 21 | * 22 | * @name $browser 23 | * 24 | * @description 25 | * This service is a mock implementation of {@link ng.$browser}. It provides fake 26 | * implementation for commonly used browser apis that are hard to test, e.g. setTimeout, xhr, 27 | * cookies, etc... 28 | * 29 | * The api of this service is the same as that of the real {@link ng.$browser $browser}, except 30 | * that there are several helper methods available which can be used in tests. 31 | */ 32 | angular.mock.$BrowserProvider = function() { 33 | this.$get = function() { 34 | return new angular.mock.$Browser(); 35 | }; 36 | }; 37 | 38 | angular.mock.$Browser = function() { 39 | var self = this; 40 | 41 | this.isMock = true; 42 | self.$$url = "http://server/"; 43 | self.$$lastUrl = self.$$url; // used by url polling fn 44 | self.pollFns = []; 45 | 46 | // TODO(vojta): remove this temporary api 47 | self.$$completeOutstandingRequest = angular.noop; 48 | self.$$incOutstandingRequestCount = angular.noop; 49 | 50 | 51 | // register url polling fn 52 | 53 | self.onUrlChange = function(listener) { 54 | self.pollFns.push( 55 | function() { 56 | if (self.$$lastUrl !== self.$$url || self.$$state !== self.$$lastState) { 57 | self.$$lastUrl = self.$$url; 58 | self.$$lastState = self.$$state; 59 | listener(self.$$url, self.$$state); 60 | } 61 | } 62 | ); 63 | 64 | return listener; 65 | }; 66 | 67 | self.$$applicationDestroyed = angular.noop; 68 | self.$$checkUrlChange = angular.noop; 69 | 70 | self.deferredFns = []; 71 | self.deferredNextId = 0; 72 | 73 | self.defer = function(fn, delay) { 74 | delay = delay || 0; 75 | self.deferredFns.push({time:(self.defer.now + delay), fn:fn, id: self.deferredNextId}); 76 | self.deferredFns.sort(function(a, b) { return a.time - b.time;}); 77 | return self.deferredNextId++; 78 | }; 79 | 80 | 81 | /** 82 | * @name $browser#defer.now 83 | * 84 | * @description 85 | * Current milliseconds mock time. 86 | */ 87 | self.defer.now = 0; 88 | 89 | 90 | self.defer.cancel = function(deferId) { 91 | var fnIndex; 92 | 93 | angular.forEach(self.deferredFns, function(fn, index) { 94 | if (fn.id === deferId) fnIndex = index; 95 | }); 96 | 97 | if (angular.isDefined(fnIndex)) { 98 | self.deferredFns.splice(fnIndex, 1); 99 | return true; 100 | } 101 | 102 | return false; 103 | }; 104 | 105 | 106 | /** 107 | * @name $browser#defer.flush 108 | * 109 | * @description 110 | * Flushes all pending requests and executes the defer callbacks. 111 | * 112 | * @param {number=} number of milliseconds to flush. See {@link #defer.now} 113 | */ 114 | self.defer.flush = function(delay) { 115 | if (angular.isDefined(delay)) { 116 | self.defer.now += delay; 117 | } else { 118 | if (self.deferredFns.length) { 119 | self.defer.now = self.deferredFns[self.deferredFns.length - 1].time; 120 | } else { 121 | throw new Error('No deferred tasks to be flushed'); 122 | } 123 | } 124 | 125 | while (self.deferredFns.length && self.deferredFns[0].time <= self.defer.now) { 126 | self.deferredFns.shift().fn(); 127 | } 128 | }; 129 | 130 | self.$$baseHref = '/'; 131 | self.baseHref = function() { 132 | return this.$$baseHref; 133 | }; 134 | }; 135 | angular.mock.$Browser.prototype = { 136 | 137 | /** 138 | * @name $browser#poll 139 | * 140 | * @description 141 | * run all fns in pollFns 142 | */ 143 | poll: function poll() { 144 | angular.forEach(this.pollFns, function(pollFn) { 145 | pollFn(); 146 | }); 147 | }, 148 | 149 | url: function(url, replace, state) { 150 | if (angular.isUndefined(state)) { 151 | state = null; 152 | } 153 | if (url) { 154 | this.$$url = url; 155 | // Native pushState serializes & copies the object; simulate it. 156 | this.$$state = angular.copy(state); 157 | return this; 158 | } 159 | 160 | return this.$$url; 161 | }, 162 | 163 | state: function() { 164 | return this.$$state; 165 | }, 166 | 167 | notifyWhenNoOutstandingRequests: function(fn) { 168 | fn(); 169 | } 170 | }; 171 | 172 | 173 | /** 174 | * @ngdoc provider 175 | * @name $exceptionHandlerProvider 176 | * 177 | * @description 178 | * Configures the mock implementation of {@link ng.$exceptionHandler} to rethrow or to log errors 179 | * passed to the `$exceptionHandler`. 180 | */ 181 | 182 | /** 183 | * @ngdoc service 184 | * @name $exceptionHandler 185 | * 186 | * @description 187 | * Mock implementation of {@link ng.$exceptionHandler} that rethrows or logs errors passed 188 | * to it. See {@link ngMock.$exceptionHandlerProvider $exceptionHandlerProvider} for configuration 189 | * information. 190 | * 191 | * 192 | * ```js 193 | * describe('$exceptionHandlerProvider', function() { 194 | * 195 | * it('should capture log messages and exceptions', function() { 196 | * 197 | * module(function($exceptionHandlerProvider) { 198 | * $exceptionHandlerProvider.mode('log'); 199 | * }); 200 | * 201 | * inject(function($log, $exceptionHandler, $timeout) { 202 | * $timeout(function() { $log.log(1); }); 203 | * $timeout(function() { $log.log(2); throw 'banana peel'; }); 204 | * $timeout(function() { $log.log(3); }); 205 | * expect($exceptionHandler.errors).toEqual([]); 206 | * expect($log.assertEmpty()); 207 | * $timeout.flush(); 208 | * expect($exceptionHandler.errors).toEqual(['banana peel']); 209 | * expect($log.log.logs).toEqual([[1], [2], [3]]); 210 | * }); 211 | * }); 212 | * }); 213 | * ``` 214 | */ 215 | 216 | angular.mock.$ExceptionHandlerProvider = function() { 217 | var handler; 218 | 219 | /** 220 | * @ngdoc method 221 | * @name $exceptionHandlerProvider#mode 222 | * 223 | * @description 224 | * Sets the logging mode. 225 | * 226 | * @param {string} mode Mode of operation, defaults to `rethrow`. 227 | * 228 | * - `log`: Sometimes it is desirable to test that an error is thrown, for this case the `log` 229 | * mode stores an array of errors in `$exceptionHandler.errors`, to allow later 230 | * assertion of them. See {@link ngMock.$log#assertEmpty assertEmpty()} and 231 | * {@link ngMock.$log#reset reset()} 232 | * - `rethrow`: If any errors are passed to the handler in tests, it typically means that there 233 | * is a bug in the application or test, so this mock will make these tests fail. 234 | * For any implementations that expect exceptions to be thrown, the `rethrow` mode 235 | * will also maintain a log of thrown errors. 236 | */ 237 | this.mode = function(mode) { 238 | 239 | switch (mode) { 240 | case 'log': 241 | case 'rethrow': 242 | var errors = []; 243 | handler = function(e) { 244 | if (arguments.length == 1) { 245 | errors.push(e); 246 | } else { 247 | errors.push([].slice.call(arguments, 0)); 248 | } 249 | if (mode === "rethrow") { 250 | throw e; 251 | } 252 | }; 253 | handler.errors = errors; 254 | break; 255 | default: 256 | throw new Error("Unknown mode '" + mode + "', only 'log'/'rethrow' modes are allowed!"); 257 | } 258 | }; 259 | 260 | this.$get = function() { 261 | return handler; 262 | }; 263 | 264 | this.mode('rethrow'); 265 | }; 266 | 267 | 268 | /** 269 | * @ngdoc service 270 | * @name $log 271 | * 272 | * @description 273 | * Mock implementation of {@link ng.$log} that gathers all logged messages in arrays 274 | * (one array per logging level). These arrays are exposed as `logs` property of each of the 275 | * level-specific log function, e.g. for level `error` the array is exposed as `$log.error.logs`. 276 | * 277 | */ 278 | angular.mock.$LogProvider = function() { 279 | var debug = true; 280 | 281 | function concat(array1, array2, index) { 282 | return array1.concat(Array.prototype.slice.call(array2, index)); 283 | } 284 | 285 | this.debugEnabled = function(flag) { 286 | if (angular.isDefined(flag)) { 287 | debug = flag; 288 | return this; 289 | } else { 290 | return debug; 291 | } 292 | }; 293 | 294 | this.$get = function() { 295 | var $log = { 296 | log: function() { $log.log.logs.push(concat([], arguments, 0)); }, 297 | warn: function() { $log.warn.logs.push(concat([], arguments, 0)); }, 298 | info: function() { $log.info.logs.push(concat([], arguments, 0)); }, 299 | error: function() { $log.error.logs.push(concat([], arguments, 0)); }, 300 | debug: function() { 301 | if (debug) { 302 | $log.debug.logs.push(concat([], arguments, 0)); 303 | } 304 | } 305 | }; 306 | 307 | /** 308 | * @ngdoc method 309 | * @name $log#reset 310 | * 311 | * @description 312 | * Reset all of the logging arrays to empty. 313 | */ 314 | $log.reset = function() { 315 | /** 316 | * @ngdoc property 317 | * @name $log#log.logs 318 | * 319 | * @description 320 | * Array of messages logged using {@link ng.$log#log `log()`}. 321 | * 322 | * @example 323 | * ```js 324 | * $log.log('Some Log'); 325 | * var first = $log.log.logs.unshift(); 326 | * ``` 327 | */ 328 | $log.log.logs = []; 329 | /** 330 | * @ngdoc property 331 | * @name $log#info.logs 332 | * 333 | * @description 334 | * Array of messages logged using {@link ng.$log#info `info()`}. 335 | * 336 | * @example 337 | * ```js 338 | * $log.info('Some Info'); 339 | * var first = $log.info.logs.unshift(); 340 | * ``` 341 | */ 342 | $log.info.logs = []; 343 | /** 344 | * @ngdoc property 345 | * @name $log#warn.logs 346 | * 347 | * @description 348 | * Array of messages logged using {@link ng.$log#warn `warn()`}. 349 | * 350 | * @example 351 | * ```js 352 | * $log.warn('Some Warning'); 353 | * var first = $log.warn.logs.unshift(); 354 | * ``` 355 | */ 356 | $log.warn.logs = []; 357 | /** 358 | * @ngdoc property 359 | * @name $log#error.logs 360 | * 361 | * @description 362 | * Array of messages logged using {@link ng.$log#error `error()`}. 363 | * 364 | * @example 365 | * ```js 366 | * $log.error('Some Error'); 367 | * var first = $log.error.logs.unshift(); 368 | * ``` 369 | */ 370 | $log.error.logs = []; 371 | /** 372 | * @ngdoc property 373 | * @name $log#debug.logs 374 | * 375 | * @description 376 | * Array of messages logged using {@link ng.$log#debug `debug()`}. 377 | * 378 | * @example 379 | * ```js 380 | * $log.debug('Some Error'); 381 | * var first = $log.debug.logs.unshift(); 382 | * ``` 383 | */ 384 | $log.debug.logs = []; 385 | }; 386 | 387 | /** 388 | * @ngdoc method 389 | * @name $log#assertEmpty 390 | * 391 | * @description 392 | * Assert that all of the logging methods have no logged messages. If any messages are present, 393 | * an exception is thrown. 394 | */ 395 | $log.assertEmpty = function() { 396 | var errors = []; 397 | angular.forEach(['error', 'warn', 'info', 'log', 'debug'], function(logLevel) { 398 | angular.forEach($log[logLevel].logs, function(log) { 399 | angular.forEach(log, function(logItem) { 400 | errors.push('MOCK $log (' + logLevel + '): ' + String(logItem) + '\n' + 401 | (logItem.stack || '')); 402 | }); 403 | }); 404 | }); 405 | if (errors.length) { 406 | errors.unshift("Expected $log to be empty! Either a message was logged unexpectedly, or " + 407 | "an expected log message was not checked and removed:"); 408 | errors.push(''); 409 | throw new Error(errors.join('\n---------\n')); 410 | } 411 | }; 412 | 413 | $log.reset(); 414 | return $log; 415 | }; 416 | }; 417 | 418 | 419 | /** 420 | * @ngdoc service 421 | * @name $interval 422 | * 423 | * @description 424 | * Mock implementation of the $interval service. 425 | * 426 | * Use {@link ngMock.$interval#flush `$interval.flush(millis)`} to 427 | * move forward by `millis` milliseconds and trigger any functions scheduled to run in that 428 | * time. 429 | * 430 | * @param {function()} fn A function that should be called repeatedly. 431 | * @param {number} delay Number of milliseconds between each function call. 432 | * @param {number=} [count=0] Number of times to repeat. If not set, or 0, will repeat 433 | * indefinitely. 434 | * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise 435 | * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. 436 | * @param {...*=} Pass additional parameters to the executed function. 437 | * @returns {promise} A promise which will be notified on each iteration. 438 | */ 439 | angular.mock.$IntervalProvider = function() { 440 | this.$get = ['$browser', '$rootScope', '$q', '$$q', 441 | function($browser, $rootScope, $q, $$q) { 442 | var repeatFns = [], 443 | nextRepeatId = 0, 444 | now = 0; 445 | 446 | var $interval = function(fn, delay, count, invokeApply) { 447 | var hasParams = arguments.length > 4, 448 | args = hasParams ? Array.prototype.slice.call(arguments, 4) : [], 449 | iteration = 0, 450 | skipApply = (angular.isDefined(invokeApply) && !invokeApply), 451 | deferred = (skipApply ? $$q : $q).defer(), 452 | promise = deferred.promise; 453 | 454 | count = (angular.isDefined(count)) ? count : 0; 455 | promise.then(null, null, (!hasParams) ? fn : function() { 456 | fn.apply(null, args); 457 | }); 458 | 459 | promise.$$intervalId = nextRepeatId; 460 | 461 | function tick() { 462 | deferred.notify(iteration++); 463 | 464 | if (count > 0 && iteration >= count) { 465 | var fnIndex; 466 | deferred.resolve(iteration); 467 | 468 | angular.forEach(repeatFns, function(fn, index) { 469 | if (fn.id === promise.$$intervalId) fnIndex = index; 470 | }); 471 | 472 | if (angular.isDefined(fnIndex)) { 473 | repeatFns.splice(fnIndex, 1); 474 | } 475 | } 476 | 477 | if (skipApply) { 478 | $browser.defer.flush(); 479 | } else { 480 | $rootScope.$apply(); 481 | } 482 | } 483 | 484 | repeatFns.push({ 485 | nextTime:(now + delay), 486 | delay: delay, 487 | fn: tick, 488 | id: nextRepeatId, 489 | deferred: deferred 490 | }); 491 | repeatFns.sort(function(a, b) { return a.nextTime - b.nextTime;}); 492 | 493 | nextRepeatId++; 494 | return promise; 495 | }; 496 | /** 497 | * @ngdoc method 498 | * @name $interval#cancel 499 | * 500 | * @description 501 | * Cancels a task associated with the `promise`. 502 | * 503 | * @param {promise} promise A promise from calling the `$interval` function. 504 | * @returns {boolean} Returns `true` if the task was successfully cancelled. 505 | */ 506 | $interval.cancel = function(promise) { 507 | if (!promise) return false; 508 | var fnIndex; 509 | 510 | angular.forEach(repeatFns, function(fn, index) { 511 | if (fn.id === promise.$$intervalId) fnIndex = index; 512 | }); 513 | 514 | if (angular.isDefined(fnIndex)) { 515 | repeatFns[fnIndex].deferred.reject('canceled'); 516 | repeatFns.splice(fnIndex, 1); 517 | return true; 518 | } 519 | 520 | return false; 521 | }; 522 | 523 | /** 524 | * @ngdoc method 525 | * @name $interval#flush 526 | * @description 527 | * 528 | * Runs interval tasks scheduled to be run in the next `millis` milliseconds. 529 | * 530 | * @param {number=} millis maximum timeout amount to flush up until. 531 | * 532 | * @return {number} The amount of time moved forward. 533 | */ 534 | $interval.flush = function(millis) { 535 | now += millis; 536 | while (repeatFns.length && repeatFns[0].nextTime <= now) { 537 | var task = repeatFns[0]; 538 | task.fn(); 539 | task.nextTime += task.delay; 540 | repeatFns.sort(function(a, b) { return a.nextTime - b.nextTime;}); 541 | } 542 | return millis; 543 | }; 544 | 545 | return $interval; 546 | }]; 547 | }; 548 | 549 | 550 | /* jshint -W101 */ 551 | /* The R_ISO8061_STR regex is never going to fit into the 100 char limit! 552 | * This directive should go inside the anonymous function but a bug in JSHint means that it would 553 | * not be enacted early enough to prevent the warning. 554 | */ 555 | var R_ISO8061_STR = /^(-?\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?:\:?(\d\d)(?:\:?(\d\d)(?:\.(\d{3}))?)?)?(Z|([+-])(\d\d):?(\d\d)))?$/; 556 | 557 | function jsonStringToDate(string) { 558 | var match; 559 | if (match = string.match(R_ISO8061_STR)) { 560 | var date = new Date(0), 561 | tzHour = 0, 562 | tzMin = 0; 563 | if (match[9]) { 564 | tzHour = toInt(match[9] + match[10]); 565 | tzMin = toInt(match[9] + match[11]); 566 | } 567 | date.setUTCFullYear(toInt(match[1]), toInt(match[2]) - 1, toInt(match[3])); 568 | date.setUTCHours(toInt(match[4] || 0) - tzHour, 569 | toInt(match[5] || 0) - tzMin, 570 | toInt(match[6] || 0), 571 | toInt(match[7] || 0)); 572 | return date; 573 | } 574 | return string; 575 | } 576 | 577 | function toInt(str) { 578 | return parseInt(str, 10); 579 | } 580 | 581 | function padNumberInMock(num, digits, trim) { 582 | var neg = ''; 583 | if (num < 0) { 584 | neg = '-'; 585 | num = -num; 586 | } 587 | num = '' + num; 588 | while (num.length < digits) num = '0' + num; 589 | if (trim) { 590 | num = num.substr(num.length - digits); 591 | } 592 | return neg + num; 593 | } 594 | 595 | 596 | /** 597 | * @ngdoc type 598 | * @name angular.mock.TzDate 599 | * @description 600 | * 601 | * *NOTE*: this is not an injectable instance, just a globally available mock class of `Date`. 602 | * 603 | * Mock of the Date type which has its timezone specified via constructor arg. 604 | * 605 | * The main purpose is to create Date-like instances with timezone fixed to the specified timezone 606 | * offset, so that we can test code that depends on local timezone settings without dependency on 607 | * the time zone settings of the machine where the code is running. 608 | * 609 | * @param {number} offset Offset of the *desired* timezone in hours (fractions will be honored) 610 | * @param {(number|string)} timestamp Timestamp representing the desired time in *UTC* 611 | * 612 | * @example 613 | * !!!! WARNING !!!!! 614 | * This is not a complete Date object so only methods that were implemented can be called safely. 615 | * To make matters worse, TzDate instances inherit stuff from Date via a prototype. 616 | * 617 | * We do our best to intercept calls to "unimplemented" methods, but since the list of methods is 618 | * incomplete we might be missing some non-standard methods. This can result in errors like: 619 | * "Date.prototype.foo called on incompatible Object". 620 | * 621 | * ```js 622 | * var newYearInBratislava = new TzDate(-1, '2009-12-31T23:00:00Z'); 623 | * newYearInBratislava.getTimezoneOffset() => -60; 624 | * newYearInBratislava.getFullYear() => 2010; 625 | * newYearInBratislava.getMonth() => 0; 626 | * newYearInBratislava.getDate() => 1; 627 | * newYearInBratislava.getHours() => 0; 628 | * newYearInBratislava.getMinutes() => 0; 629 | * newYearInBratislava.getSeconds() => 0; 630 | * ``` 631 | * 632 | */ 633 | angular.mock.TzDate = function(offset, timestamp) { 634 | var self = new Date(0); 635 | if (angular.isString(timestamp)) { 636 | var tsStr = timestamp; 637 | 638 | self.origDate = jsonStringToDate(timestamp); 639 | 640 | timestamp = self.origDate.getTime(); 641 | if (isNaN(timestamp)) { 642 | throw { 643 | name: "Illegal Argument", 644 | message: "Arg '" + tsStr + "' passed into TzDate constructor is not a valid date string" 645 | }; 646 | } 647 | } else { 648 | self.origDate = new Date(timestamp); 649 | } 650 | 651 | var localOffset = new Date(timestamp).getTimezoneOffset(); 652 | self.offsetDiff = localOffset * 60 * 1000 - offset * 1000 * 60 * 60; 653 | self.date = new Date(timestamp + self.offsetDiff); 654 | 655 | self.getTime = function() { 656 | return self.date.getTime() - self.offsetDiff; 657 | }; 658 | 659 | self.toLocaleDateString = function() { 660 | return self.date.toLocaleDateString(); 661 | }; 662 | 663 | self.getFullYear = function() { 664 | return self.date.getFullYear(); 665 | }; 666 | 667 | self.getMonth = function() { 668 | return self.date.getMonth(); 669 | }; 670 | 671 | self.getDate = function() { 672 | return self.date.getDate(); 673 | }; 674 | 675 | self.getHours = function() { 676 | return self.date.getHours(); 677 | }; 678 | 679 | self.getMinutes = function() { 680 | return self.date.getMinutes(); 681 | }; 682 | 683 | self.getSeconds = function() { 684 | return self.date.getSeconds(); 685 | }; 686 | 687 | self.getMilliseconds = function() { 688 | return self.date.getMilliseconds(); 689 | }; 690 | 691 | self.getTimezoneOffset = function() { 692 | return offset * 60; 693 | }; 694 | 695 | self.getUTCFullYear = function() { 696 | return self.origDate.getUTCFullYear(); 697 | }; 698 | 699 | self.getUTCMonth = function() { 700 | return self.origDate.getUTCMonth(); 701 | }; 702 | 703 | self.getUTCDate = function() { 704 | return self.origDate.getUTCDate(); 705 | }; 706 | 707 | self.getUTCHours = function() { 708 | return self.origDate.getUTCHours(); 709 | }; 710 | 711 | self.getUTCMinutes = function() { 712 | return self.origDate.getUTCMinutes(); 713 | }; 714 | 715 | self.getUTCSeconds = function() { 716 | return self.origDate.getUTCSeconds(); 717 | }; 718 | 719 | self.getUTCMilliseconds = function() { 720 | return self.origDate.getUTCMilliseconds(); 721 | }; 722 | 723 | self.getDay = function() { 724 | return self.date.getDay(); 725 | }; 726 | 727 | // provide this method only on browsers that already have it 728 | if (self.toISOString) { 729 | self.toISOString = function() { 730 | return padNumberInMock(self.origDate.getUTCFullYear(), 4) + '-' + 731 | padNumberInMock(self.origDate.getUTCMonth() + 1, 2) + '-' + 732 | padNumberInMock(self.origDate.getUTCDate(), 2) + 'T' + 733 | padNumberInMock(self.origDate.getUTCHours(), 2) + ':' + 734 | padNumberInMock(self.origDate.getUTCMinutes(), 2) + ':' + 735 | padNumberInMock(self.origDate.getUTCSeconds(), 2) + '.' + 736 | padNumberInMock(self.origDate.getUTCMilliseconds(), 3) + 'Z'; 737 | }; 738 | } 739 | 740 | //hide all methods not implemented in this mock that the Date prototype exposes 741 | var unimplementedMethods = ['getUTCDay', 742 | 'getYear', 'setDate', 'setFullYear', 'setHours', 'setMilliseconds', 743 | 'setMinutes', 'setMonth', 'setSeconds', 'setTime', 'setUTCDate', 'setUTCFullYear', 744 | 'setUTCHours', 'setUTCMilliseconds', 'setUTCMinutes', 'setUTCMonth', 'setUTCSeconds', 745 | 'setYear', 'toDateString', 'toGMTString', 'toJSON', 'toLocaleFormat', 'toLocaleString', 746 | 'toLocaleTimeString', 'toSource', 'toString', 'toTimeString', 'toUTCString', 'valueOf']; 747 | 748 | angular.forEach(unimplementedMethods, function(methodName) { 749 | self[methodName] = function() { 750 | throw new Error("Method '" + methodName + "' is not implemented in the TzDate mock"); 751 | }; 752 | }); 753 | 754 | return self; 755 | }; 756 | 757 | //make "tzDateInstance instanceof Date" return true 758 | angular.mock.TzDate.prototype = Date.prototype; 759 | /* jshint +W101 */ 760 | 761 | 762 | /** 763 | * @ngdoc service 764 | * @name $animate 765 | * 766 | * @description 767 | * Mock implementation of the {@link ng.$animate `$animate`} service. Exposes two additional methods 768 | * for testing animations. 769 | */ 770 | angular.mock.animate = angular.module('ngAnimateMock', ['ng']) 771 | 772 | .config(['$provide', function($provide) { 773 | 774 | $provide.factory('$$forceReflow', function() { 775 | function reflowFn() { 776 | reflowFn.totalReflows++; 777 | } 778 | reflowFn.totalReflows = 0; 779 | return reflowFn; 780 | }); 781 | 782 | $provide.factory('$$animateAsyncRun', function() { 783 | var queue = []; 784 | var queueFn = function() { 785 | return function(fn) { 786 | queue.push(fn); 787 | }; 788 | }; 789 | queueFn.flush = function() { 790 | if (queue.length === 0) return false; 791 | 792 | for (var i = 0; i < queue.length; i++) { 793 | queue[i](); 794 | } 795 | queue = []; 796 | 797 | return true; 798 | }; 799 | return queueFn; 800 | }); 801 | 802 | $provide.decorator('$$animateJs', ['$delegate', function($delegate) { 803 | var runners = []; 804 | 805 | var animateJsConstructor = function() { 806 | var animator = $delegate.apply($delegate, arguments); 807 | // If no javascript animation is found, animator is undefined 808 | if (animator) { 809 | runners.push(animator); 810 | } 811 | return animator; 812 | }; 813 | 814 | animateJsConstructor.$closeAndFlush = function() { 815 | runners.forEach(function(runner) { 816 | runner.end(); 817 | }); 818 | runners = []; 819 | }; 820 | 821 | return animateJsConstructor; 822 | }]); 823 | 824 | $provide.decorator('$animateCss', ['$delegate', function($delegate) { 825 | var runners = []; 826 | 827 | var animateCssConstructor = function(element, options) { 828 | var animator = $delegate(element, options); 829 | runners.push(animator); 830 | return animator; 831 | }; 832 | 833 | animateCssConstructor.$closeAndFlush = function() { 834 | runners.forEach(function(runner) { 835 | runner.end(); 836 | }); 837 | runners = []; 838 | }; 839 | 840 | return animateCssConstructor; 841 | }]); 842 | 843 | $provide.decorator('$animate', ['$delegate', '$timeout', '$browser', '$$rAF', '$animateCss', '$$animateJs', 844 | '$$forceReflow', '$$animateAsyncRun', '$rootScope', 845 | function($delegate, $timeout, $browser, $$rAF, $animateCss, $$animateJs, 846 | $$forceReflow, $$animateAsyncRun, $rootScope) { 847 | var animate = { 848 | queue: [], 849 | cancel: $delegate.cancel, 850 | on: $delegate.on, 851 | off: $delegate.off, 852 | pin: $delegate.pin, 853 | get reflows() { 854 | return $$forceReflow.totalReflows; 855 | }, 856 | enabled: $delegate.enabled, 857 | /** 858 | * @ngdoc method 859 | * @name $animate#closeAndFlush 860 | * @description 861 | * 862 | * This method will close all pending animations (both {@link ngAnimate#javascript-based-animations Javascript} 863 | * and {@link ngAnimate.$animateCss CSS}) and it will also flush any remaining animation frames and/or callbacks. 864 | */ 865 | closeAndFlush: function() { 866 | // we allow the flush command to swallow the errors 867 | // because depending on whether CSS or JS animations are 868 | // used, there may not be a RAF flush. The primary flush 869 | // at the end of this function must throw an exception 870 | // because it will track if there were pending animations 871 | this.flush(true); 872 | $animateCss.$closeAndFlush(); 873 | $$animateJs.$closeAndFlush(); 874 | this.flush(); 875 | }, 876 | /** 877 | * @ngdoc method 878 | * @name $animate#flush 879 | * @description 880 | * 881 | * This method is used to flush the pending callbacks and animation frames to either start 882 | * an animation or conclude an animation. Note that this will not actually close an 883 | * actively running animation (see {@link ngMock.$animate#closeAndFlush `closeAndFlush()`} for that). 884 | */ 885 | flush: function(hideErrors) { 886 | $rootScope.$digest(); 887 | 888 | var doNextRun, somethingFlushed = false; 889 | do { 890 | doNextRun = false; 891 | 892 | if ($$rAF.queue.length) { 893 | $$rAF.flush(); 894 | doNextRun = somethingFlushed = true; 895 | } 896 | 897 | if ($$animateAsyncRun.flush()) { 898 | doNextRun = somethingFlushed = true; 899 | } 900 | } while (doNextRun); 901 | 902 | if (!somethingFlushed && !hideErrors) { 903 | throw new Error('No pending animations ready to be closed or flushed'); 904 | } 905 | 906 | $rootScope.$digest(); 907 | } 908 | }; 909 | 910 | angular.forEach( 911 | ['animate','enter','leave','move','addClass','removeClass','setClass'], function(method) { 912 | animate[method] = function() { 913 | animate.queue.push({ 914 | event: method, 915 | element: arguments[0], 916 | options: arguments[arguments.length - 1], 917 | args: arguments 918 | }); 919 | return $delegate[method].apply($delegate, arguments); 920 | }; 921 | }); 922 | 923 | return animate; 924 | }]); 925 | 926 | }]); 927 | 928 | 929 | /** 930 | * @ngdoc function 931 | * @name angular.mock.dump 932 | * @description 933 | * 934 | * *NOTE*: this is not an injectable instance, just a globally available function. 935 | * 936 | * Method for serializing common angular objects (scope, elements, etc..) into strings, useful for 937 | * debugging. 938 | * 939 | * This method is also available on window, where it can be used to display objects on debug 940 | * console. 941 | * 942 | * @param {*} object - any object to turn into string. 943 | * @return {string} a serialized string of the argument 944 | */ 945 | angular.mock.dump = function(object) { 946 | return serialize(object); 947 | 948 | function serialize(object) { 949 | var out; 950 | 951 | if (angular.isElement(object)) { 952 | object = angular.element(object); 953 | out = angular.element('
'); 954 | angular.forEach(object, function(element) { 955 | out.append(angular.element(element).clone()); 956 | }); 957 | out = out.html(); 958 | } else if (angular.isArray(object)) { 959 | out = []; 960 | angular.forEach(object, function(o) { 961 | out.push(serialize(o)); 962 | }); 963 | out = '[ ' + out.join(', ') + ' ]'; 964 | } else if (angular.isObject(object)) { 965 | if (angular.isFunction(object.$eval) && angular.isFunction(object.$apply)) { 966 | out = serializeScope(object); 967 | } else if (object instanceof Error) { 968 | out = object.stack || ('' + object.name + ': ' + object.message); 969 | } else { 970 | // TODO(i): this prevents methods being logged, 971 | // we should have a better way to serialize objects 972 | out = angular.toJson(object, true); 973 | } 974 | } else { 975 | out = String(object); 976 | } 977 | 978 | return out; 979 | } 980 | 981 | function serializeScope(scope, offset) { 982 | offset = offset || ' '; 983 | var log = [offset + 'Scope(' + scope.$id + '): {']; 984 | for (var key in scope) { 985 | if (Object.prototype.hasOwnProperty.call(scope, key) && !key.match(/^(\$|this)/)) { 986 | log.push(' ' + key + ': ' + angular.toJson(scope[key])); 987 | } 988 | } 989 | var child = scope.$$childHead; 990 | while (child) { 991 | log.push(serializeScope(child, offset + ' ')); 992 | child = child.$$nextSibling; 993 | } 994 | log.push('}'); 995 | return log.join('\n' + offset); 996 | } 997 | }; 998 | 999 | /** 1000 | * @ngdoc service 1001 | * @name $httpBackend 1002 | * @description 1003 | * Fake HTTP backend implementation suitable for unit testing applications that use the 1004 | * {@link ng.$http $http service}. 1005 | * 1006 | * *Note*: For fake HTTP backend implementation suitable for end-to-end testing or backend-less 1007 | * development please see {@link ngMockE2E.$httpBackend e2e $httpBackend mock}. 1008 | * 1009 | * During unit testing, we want our unit tests to run quickly and have no external dependencies so 1010 | * we don’t want to send [XHR](https://developer.mozilla.org/en/xmlhttprequest) or 1011 | * [JSONP](http://en.wikipedia.org/wiki/JSONP) requests to a real server. All we really need is 1012 | * to verify whether a certain request has been sent or not, or alternatively just let the 1013 | * application make requests, respond with pre-trained responses and assert that the end result is 1014 | * what we expect it to be. 1015 | * 1016 | * This mock implementation can be used to respond with static or dynamic responses via the 1017 | * `expect` and `when` apis and their shortcuts (`expectGET`, `whenPOST`, etc). 1018 | * 1019 | * When an Angular application needs some data from a server, it calls the $http service, which 1020 | * sends the request to a real server using $httpBackend service. With dependency injection, it is 1021 | * easy to inject $httpBackend mock (which has the same API as $httpBackend) and use it to verify 1022 | * the requests and respond with some testing data without sending a request to a real server. 1023 | * 1024 | * There are two ways to specify what test data should be returned as http responses by the mock 1025 | * backend when the code under test makes http requests: 1026 | * 1027 | * - `$httpBackend.expect` - specifies a request expectation 1028 | * - `$httpBackend.when` - specifies a backend definition 1029 | * 1030 | * 1031 | * ## Request Expectations vs Backend Definitions 1032 | * 1033 | * Request expectations provide a way to make assertions about requests made by the application and 1034 | * to define responses for those requests. The test will fail if the expected requests are not made 1035 | * or they are made in the wrong order. 1036 | * 1037 | * Backend definitions allow you to define a fake backend for your application which doesn't assert 1038 | * if a particular request was made or not, it just returns a trained response if a request is made. 1039 | * The test will pass whether or not the request gets made during testing. 1040 | * 1041 | * 1042 | * 1043 | * 1044 | * 1045 | * 1046 | * 1047 | * 1048 | * 1049 | * 1050 | * 1051 | * 1052 | * 1053 | * 1054 | * 1055 | * 1056 | * 1057 | * 1058 | * 1059 | * 1060 | * 1061 | * 1062 | * 1063 | * 1064 | * 1065 | * 1066 | * 1067 | * 1068 | * 1069 | * 1070 | * 1071 | * 1072 | * 1073 | * 1074 | *
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
1075 | * 1076 | * In cases where both backend definitions and request expectations are specified during unit 1077 | * testing, the request expectations are evaluated first. 1078 | * 1079 | * If a request expectation has no response specified, the algorithm will search your backend 1080 | * definitions for an appropriate response. 1081 | * 1082 | * If a request didn't match any expectation or if the expectation doesn't have the response 1083 | * defined, the backend definitions are evaluated in sequential order to see if any of them match 1084 | * the request. The response from the first matched definition is returned. 1085 | * 1086 | * 1087 | * ## Flushing HTTP requests 1088 | * 1089 | * The $httpBackend used in production always responds to requests asynchronously. If we preserved 1090 | * this behavior in unit testing, we'd have to create async unit tests, which are hard to write, 1091 | * to follow and to maintain. But neither can the testing mock respond synchronously; that would 1092 | * change the execution of the code under test. For this reason, the mock $httpBackend has a 1093 | * `flush()` method, which allows the test to explicitly flush pending requests. This preserves 1094 | * the async api of the backend, while allowing the test to execute synchronously. 1095 | * 1096 | * 1097 | * ## Unit testing with mock $httpBackend 1098 | * The following code shows how to setup and use the mock backend when unit testing a controller. 1099 | * First we create the controller under test: 1100 | * 1101 | ```js 1102 | // The module code 1103 | angular 1104 | .module('MyApp', []) 1105 | .controller('MyController', MyController); 1106 | 1107 | // The controller code 1108 | function MyController($scope, $http) { 1109 | var authToken; 1110 | 1111 | $http.get('/auth.py').then(function(response) { 1112 | authToken = response.headers('A-Token'); 1113 | $scope.user = response.data; 1114 | }); 1115 | 1116 | $scope.saveMessage = function(message) { 1117 | var headers = { 'Authorization': authToken }; 1118 | $scope.status = 'Saving...'; 1119 | 1120 | $http.post('/add-msg.py', message, { headers: headers } ).then(function(response) { 1121 | $scope.status = ''; 1122 | }).catch(function() { 1123 | $scope.status = 'Failed...'; 1124 | }); 1125 | }; 1126 | } 1127 | ``` 1128 | * 1129 | * Now we setup the mock backend and create the test specs: 1130 | * 1131 | ```js 1132 | // testing controller 1133 | describe('MyController', function() { 1134 | var $httpBackend, $rootScope, createController, authRequestHandler; 1135 | 1136 | // Set up the module 1137 | beforeEach(module('MyApp')); 1138 | 1139 | beforeEach(inject(function($injector) { 1140 | // Set up the mock http service responses 1141 | $httpBackend = $injector.get('$httpBackend'); 1142 | // backend definition common for all tests 1143 | authRequestHandler = $httpBackend.when('GET', '/auth.py') 1144 | .respond({userId: 'userX'}, {'A-Token': 'xxx'}); 1145 | 1146 | // Get hold of a scope (i.e. the root scope) 1147 | $rootScope = $injector.get('$rootScope'); 1148 | // The $controller service is used to create instances of controllers 1149 | var $controller = $injector.get('$controller'); 1150 | 1151 | createController = function() { 1152 | return $controller('MyController', {'$scope' : $rootScope }); 1153 | }; 1154 | })); 1155 | 1156 | 1157 | afterEach(function() { 1158 | $httpBackend.verifyNoOutstandingExpectation(); 1159 | $httpBackend.verifyNoOutstandingRequest(); 1160 | }); 1161 | 1162 | 1163 | it('should fetch authentication token', function() { 1164 | $httpBackend.expectGET('/auth.py'); 1165 | var controller = createController(); 1166 | $httpBackend.flush(); 1167 | }); 1168 | 1169 | 1170 | it('should fail authentication', function() { 1171 | 1172 | // Notice how you can change the response even after it was set 1173 | authRequestHandler.respond(401, ''); 1174 | 1175 | $httpBackend.expectGET('/auth.py'); 1176 | var controller = createController(); 1177 | $httpBackend.flush(); 1178 | expect($rootScope.status).toBe('Failed...'); 1179 | }); 1180 | 1181 | 1182 | it('should send msg to server', function() { 1183 | var controller = createController(); 1184 | $httpBackend.flush(); 1185 | 1186 | // now you don’t care about the authentication, but 1187 | // the controller will still send the request and 1188 | // $httpBackend will respond without you having to 1189 | // specify the expectation and response for this request 1190 | 1191 | $httpBackend.expectPOST('/add-msg.py', 'message content').respond(201, ''); 1192 | $rootScope.saveMessage('message content'); 1193 | expect($rootScope.status).toBe('Saving...'); 1194 | $httpBackend.flush(); 1195 | expect($rootScope.status).toBe(''); 1196 | }); 1197 | 1198 | 1199 | it('should send auth header', function() { 1200 | var controller = createController(); 1201 | $httpBackend.flush(); 1202 | 1203 | $httpBackend.expectPOST('/add-msg.py', undefined, function(headers) { 1204 | // check if the header was sent, if it wasn't the expectation won't 1205 | // match the request and the test will fail 1206 | return headers['Authorization'] == 'xxx'; 1207 | }).respond(201, ''); 1208 | 1209 | $rootScope.saveMessage('whatever'); 1210 | $httpBackend.flush(); 1211 | }); 1212 | }); 1213 | ``` 1214 | * 1215 | * ## Dynamic responses 1216 | * 1217 | * You define a response to a request by chaining a call to `respond()` onto a definition or expectation. 1218 | * If you provide a **callback** as the first parameter to `respond(callback)` then you can dynamically generate 1219 | * a response based on the properties of the request. 1220 | * 1221 | * The `callback` function should be of the form `function(method, url, data, headers, params)`. 1222 | * 1223 | * ### Query parameters 1224 | * 1225 | * By default, query parameters on request URLs are parsed into the `params` object. So a request URL 1226 | * of `/list?q=searchstr&orderby=-name` would set `params` to be `{q: 'searchstr', orderby: '-name'}`. 1227 | * 1228 | * ### Regex parameter matching 1229 | * 1230 | * If an expectation or definition uses a **regex** to match the URL, you can provide an array of **keys** via a 1231 | * `params` argument. The index of each **key** in the array will match the index of a **group** in the 1232 | * **regex**. 1233 | * 1234 | * The `params` object in the **callback** will now have properties with these keys, which hold the value of the 1235 | * corresponding **group** in the **regex**. 1236 | * 1237 | * This also applies to the `when` and `expect` shortcut methods. 1238 | * 1239 | * 1240 | * ```js 1241 | * $httpBackend.expect('GET', /\/user\/(.+)/, undefined, undefined, ['id']) 1242 | * .respond(function(method, url, data, headers, params) { 1243 | * // for requested url of '/user/1234' params is {id: '1234'} 1244 | * }); 1245 | * 1246 | * $httpBackend.whenPATCH(/\/user\/(.+)\/article\/(.+)/, undefined, undefined, ['user', 'article']) 1247 | * .respond(function(method, url, data, headers, params) { 1248 | * // for url of '/user/1234/article/567' params is {user: '1234', article: '567'} 1249 | * }); 1250 | * ``` 1251 | * 1252 | * ## Matching route requests 1253 | * 1254 | * For extra convenience, `whenRoute` and `expectRoute` shortcuts are available. These methods offer colon 1255 | * delimited matching of the url path, ignoring the query string. This allows declarations 1256 | * similar to how application routes are configured with `$routeProvider`. Because these methods convert 1257 | * the definition url to regex, declaration order is important. Combined with query parameter parsing, 1258 | * the following is possible: 1259 | * 1260 | ```js 1261 | $httpBackend.whenRoute('GET', '/users/:id') 1262 | .respond(function(method, url, data, headers, params) { 1263 | return [200, MockUserList[Number(params.id)]]; 1264 | }); 1265 | 1266 | $httpBackend.whenRoute('GET', '/users') 1267 | .respond(function(method, url, data, headers, params) { 1268 | var userList = angular.copy(MockUserList), 1269 | defaultSort = 'lastName', 1270 | count, pages, isPrevious, isNext; 1271 | 1272 | // paged api response '/v1/users?page=2' 1273 | params.page = Number(params.page) || 1; 1274 | 1275 | // query for last names '/v1/users?q=Archer' 1276 | if (params.q) { 1277 | userList = $filter('filter')({lastName: params.q}); 1278 | } 1279 | 1280 | pages = Math.ceil(userList.length / pagingLength); 1281 | isPrevious = params.page > 1; 1282 | isNext = params.page < pages; 1283 | 1284 | return [200, { 1285 | count: userList.length, 1286 | previous: isPrevious, 1287 | next: isNext, 1288 | // sort field -> '/v1/users?sortBy=firstName' 1289 | results: $filter('orderBy')(userList, params.sortBy || defaultSort) 1290 | .splice((params.page - 1) * pagingLength, pagingLength) 1291 | }]; 1292 | }); 1293 | ``` 1294 | */ 1295 | angular.mock.$HttpBackendProvider = function() { 1296 | this.$get = ['$rootScope', '$timeout', createHttpBackendMock]; 1297 | }; 1298 | 1299 | /** 1300 | * General factory function for $httpBackend mock. 1301 | * Returns instance for unit testing (when no arguments specified): 1302 | * - passing through is disabled 1303 | * - auto flushing is disabled 1304 | * 1305 | * Returns instance for e2e testing (when `$delegate` and `$browser` specified): 1306 | * - passing through (delegating request to real backend) is enabled 1307 | * - auto flushing is enabled 1308 | * 1309 | * @param {Object=} $delegate Real $httpBackend instance (allow passing through if specified) 1310 | * @param {Object=} $browser Auto-flushing enabled if specified 1311 | * @return {Object} Instance of $httpBackend mock 1312 | */ 1313 | function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) { 1314 | var definitions = [], 1315 | expectations = [], 1316 | responses = [], 1317 | responsesPush = angular.bind(responses, responses.push), 1318 | copy = angular.copy; 1319 | 1320 | function createResponse(status, data, headers, statusText) { 1321 | if (angular.isFunction(status)) return status; 1322 | 1323 | return function() { 1324 | return angular.isNumber(status) 1325 | ? [status, data, headers, statusText] 1326 | : [200, status, data, headers]; 1327 | }; 1328 | } 1329 | 1330 | // TODO(vojta): change params to: method, url, data, headers, callback 1331 | function $httpBackend(method, url, data, callback, headers, timeout, withCredentials, responseType) { 1332 | 1333 | var xhr = new MockXhr(), 1334 | expectation = expectations[0], 1335 | wasExpected = false; 1336 | 1337 | function prettyPrint(data) { 1338 | return (angular.isString(data) || angular.isFunction(data) || data instanceof RegExp) 1339 | ? data 1340 | : angular.toJson(data); 1341 | } 1342 | 1343 | function wrapResponse(wrapped) { 1344 | if (!$browser && timeout) { 1345 | timeout.then ? timeout.then(handleTimeout) : $timeout(handleTimeout, timeout); 1346 | } 1347 | 1348 | return handleResponse; 1349 | 1350 | function handleResponse() { 1351 | var response = wrapped.response(method, url, data, headers, wrapped.params(url)); 1352 | xhr.$$respHeaders = response[2]; 1353 | callback(copy(response[0]), copy(response[1]), xhr.getAllResponseHeaders(), 1354 | copy(response[3] || '')); 1355 | } 1356 | 1357 | function handleTimeout() { 1358 | for (var i = 0, ii = responses.length; i < ii; i++) { 1359 | if (responses[i] === handleResponse) { 1360 | responses.splice(i, 1); 1361 | callback(-1, undefined, ''); 1362 | break; 1363 | } 1364 | } 1365 | } 1366 | } 1367 | 1368 | if (expectation && expectation.match(method, url)) { 1369 | if (!expectation.matchData(data)) { 1370 | throw new Error('Expected ' + expectation + ' with different data\n' + 1371 | 'EXPECTED: ' + prettyPrint(expectation.data) + '\nGOT: ' + data); 1372 | } 1373 | 1374 | if (!expectation.matchHeaders(headers)) { 1375 | throw new Error('Expected ' + expectation + ' with different headers\n' + 1376 | 'EXPECTED: ' + prettyPrint(expectation.headers) + '\nGOT: ' + 1377 | prettyPrint(headers)); 1378 | } 1379 | 1380 | expectations.shift(); 1381 | 1382 | if (expectation.response) { 1383 | responses.push(wrapResponse(expectation)); 1384 | return; 1385 | } 1386 | wasExpected = true; 1387 | } 1388 | 1389 | var i = -1, definition; 1390 | while ((definition = definitions[++i])) { 1391 | if (definition.match(method, url, data, headers || {})) { 1392 | if (definition.response) { 1393 | // if $browser specified, we do auto flush all requests 1394 | ($browser ? $browser.defer : responsesPush)(wrapResponse(definition)); 1395 | } else if (definition.passThrough) { 1396 | $delegate(method, url, data, callback, headers, timeout, withCredentials, responseType); 1397 | } else throw new Error('No response defined !'); 1398 | return; 1399 | } 1400 | } 1401 | throw wasExpected ? 1402 | new Error('No response defined !') : 1403 | new Error('Unexpected request: ' + method + ' ' + url + '\n' + 1404 | (expectation ? 'Expected ' + expectation : 'No more request expected')); 1405 | } 1406 | 1407 | /** 1408 | * @ngdoc method 1409 | * @name $httpBackend#when 1410 | * @description 1411 | * Creates a new backend definition. 1412 | * 1413 | * @param {string} method HTTP method. 1414 | * @param {string|RegExp|function(string)} url HTTP url or function that receives a url 1415 | * and returns true if the url matches the current definition. 1416 | * @param {(string|RegExp|function(string))=} data HTTP request body or function that receives 1417 | * data string and returns true if the data is as expected. 1418 | * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header 1419 | * object and returns true if the headers match the current definition. 1420 | * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above. 1421 | * @returns {requestHandler} Returns an object with `respond` method that controls how a matched 1422 | * request is handled. You can save this object for later use and invoke `respond` again in 1423 | * order to change how a matched request is handled. 1424 | * 1425 | * - respond – 1426 | * `{function([status,] data[, headers, statusText]) 1427 | * | function(function(method, url, data, headers, params)}` 1428 | * – The respond method takes a set of static data to be returned or a function that can 1429 | * return an array containing response status (number), response data (string), response 1430 | * headers (Object), and the text for the status (string). The respond method returns the 1431 | * `requestHandler` object for possible overrides. 1432 | */ 1433 | $httpBackend.when = function(method, url, data, headers, keys) { 1434 | var definition = new MockHttpExpectation(method, url, data, headers, keys), 1435 | chain = { 1436 | respond: function(status, data, headers, statusText) { 1437 | definition.passThrough = undefined; 1438 | definition.response = createResponse(status, data, headers, statusText); 1439 | return chain; 1440 | } 1441 | }; 1442 | 1443 | if ($browser) { 1444 | chain.passThrough = function() { 1445 | definition.response = undefined; 1446 | definition.passThrough = true; 1447 | return chain; 1448 | }; 1449 | } 1450 | 1451 | definitions.push(definition); 1452 | return chain; 1453 | }; 1454 | 1455 | /** 1456 | * @ngdoc method 1457 | * @name $httpBackend#whenGET 1458 | * @description 1459 | * Creates a new backend definition for GET requests. For more info see `when()`. 1460 | * 1461 | * @param {string|RegExp|function(string)} url HTTP url or function that receives a url 1462 | * and returns true if the url matches the current definition. 1463 | * @param {(Object|function(Object))=} headers HTTP headers. 1464 | * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above. 1465 | * @returns {requestHandler} Returns an object with `respond` method that controls how a matched 1466 | * request is handled. You can save this object for later use and invoke `respond` again in 1467 | * order to change how a matched request is handled. 1468 | */ 1469 | 1470 | /** 1471 | * @ngdoc method 1472 | * @name $httpBackend#whenHEAD 1473 | * @description 1474 | * Creates a new backend definition for HEAD requests. For more info see `when()`. 1475 | * 1476 | * @param {string|RegExp|function(string)} url HTTP url or function that receives a url 1477 | * and returns true if the url matches the current definition. 1478 | * @param {(Object|function(Object))=} headers HTTP headers. 1479 | * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above. 1480 | * @returns {requestHandler} Returns an object with `respond` method that controls how a matched 1481 | * request is handled. You can save this object for later use and invoke `respond` again in 1482 | * order to change how a matched request is handled. 1483 | */ 1484 | 1485 | /** 1486 | * @ngdoc method 1487 | * @name $httpBackend#whenDELETE 1488 | * @description 1489 | * Creates a new backend definition for DELETE requests. For more info see `when()`. 1490 | * 1491 | * @param {string|RegExp|function(string)} url HTTP url or function that receives a url 1492 | * and returns true if the url matches the current definition. 1493 | * @param {(Object|function(Object))=} headers HTTP headers. 1494 | * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above. 1495 | * @returns {requestHandler} Returns an object with `respond` method that controls how a matched 1496 | * request is handled. You can save this object for later use and invoke `respond` again in 1497 | * order to change how a matched request is handled. 1498 | */ 1499 | 1500 | /** 1501 | * @ngdoc method 1502 | * @name $httpBackend#whenPOST 1503 | * @description 1504 | * Creates a new backend definition for POST requests. For more info see `when()`. 1505 | * 1506 | * @param {string|RegExp|function(string)} url HTTP url or function that receives a url 1507 | * and returns true if the url matches the current definition. 1508 | * @param {(string|RegExp|function(string))=} data HTTP request body or function that receives 1509 | * data string and returns true if the data is as expected. 1510 | * @param {(Object|function(Object))=} headers HTTP headers. 1511 | * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above. 1512 | * @returns {requestHandler} Returns an object with `respond` method that controls how a matched 1513 | * request is handled. You can save this object for later use and invoke `respond` again in 1514 | * order to change how a matched request is handled. 1515 | */ 1516 | 1517 | /** 1518 | * @ngdoc method 1519 | * @name $httpBackend#whenPUT 1520 | * @description 1521 | * Creates a new backend definition for PUT requests. For more info see `when()`. 1522 | * 1523 | * @param {string|RegExp|function(string)} url HTTP url or function that receives a url 1524 | * and returns true if the url matches the current definition. 1525 | * @param {(string|RegExp|function(string))=} data HTTP request body or function that receives 1526 | * data string and returns true if the data is as expected. 1527 | * @param {(Object|function(Object))=} headers HTTP headers. 1528 | * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above. 1529 | * @returns {requestHandler} Returns an object with `respond` method that controls how a matched 1530 | * request is handled. You can save this object for later use and invoke `respond` again in 1531 | * order to change how a matched request is handled. 1532 | */ 1533 | 1534 | /** 1535 | * @ngdoc method 1536 | * @name $httpBackend#whenJSONP 1537 | * @description 1538 | * Creates a new backend definition for JSONP requests. For more info see `when()`. 1539 | * 1540 | * @param {string|RegExp|function(string)} url HTTP url or function that receives a url 1541 | * and returns true if the url matches the current definition. 1542 | * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above. 1543 | * @returns {requestHandler} Returns an object with `respond` method that controls how a matched 1544 | * request is handled. You can save this object for later use and invoke `respond` again in 1545 | * order to change how a matched request is handled. 1546 | */ 1547 | createShortMethods('when'); 1548 | 1549 | /** 1550 | * @ngdoc method 1551 | * @name $httpBackend#whenRoute 1552 | * @description 1553 | * Creates a new backend definition that compares only with the requested route. 1554 | * 1555 | * @param {string} method HTTP method. 1556 | * @param {string} url HTTP url string that supports colon param matching. 1557 | * @returns {requestHandler} Returns an object with `respond` method that controls how a matched 1558 | * request is handled. You can save this object for later use and invoke `respond` again in 1559 | * order to change how a matched request is handled. See #when for more info. 1560 | */ 1561 | $httpBackend.whenRoute = function(method, url) { 1562 | var pathObj = parseRoute(url); 1563 | return $httpBackend.when(method, pathObj.regexp, undefined, undefined, pathObj.keys); 1564 | }; 1565 | 1566 | function parseRoute(url) { 1567 | var ret = { 1568 | regexp: url 1569 | }, 1570 | keys = ret.keys = []; 1571 | 1572 | if (!url || !angular.isString(url)) return ret; 1573 | 1574 | url = url 1575 | .replace(/([().])/g, '\\$1') 1576 | .replace(/(\/)?:(\w+)([\?\*])?/g, function(_, slash, key, option) { 1577 | var optional = option === '?' ? option : null; 1578 | var star = option === '*' ? option : null; 1579 | keys.push({ name: key, optional: !!optional }); 1580 | slash = slash || ''; 1581 | return '' 1582 | + (optional ? '' : slash) 1583 | + '(?:' 1584 | + (optional ? slash : '') 1585 | + (star && '(.+?)' || '([^/]+)') 1586 | + (optional || '') 1587 | + ')' 1588 | + (optional || ''); 1589 | }) 1590 | .replace(/([\/$\*])/g, '\\$1'); 1591 | 1592 | ret.regexp = new RegExp('^' + url, 'i'); 1593 | return ret; 1594 | } 1595 | 1596 | /** 1597 | * @ngdoc method 1598 | * @name $httpBackend#expect 1599 | * @description 1600 | * Creates a new request expectation. 1601 | * 1602 | * @param {string} method HTTP method. 1603 | * @param {string|RegExp|function(string)} url HTTP url or function that receives a url 1604 | * and returns true if the url matches the current definition. 1605 | * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that 1606 | * receives data string and returns true if the data is as expected, or Object if request body 1607 | * is in JSON format. 1608 | * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header 1609 | * object and returns true if the headers match the current expectation. 1610 | * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above. 1611 | * @returns {requestHandler} Returns an object with `respond` method that controls how a matched 1612 | * request is handled. You can save this object for later use and invoke `respond` again in 1613 | * order to change how a matched request is handled. 1614 | * 1615 | * - respond – 1616 | * `{function([status,] data[, headers, statusText]) 1617 | * | function(function(method, url, data, headers, params)}` 1618 | * – The respond method takes a set of static data to be returned or a function that can 1619 | * return an array containing response status (number), response data (string), response 1620 | * headers (Object), and the text for the status (string). The respond method returns the 1621 | * `requestHandler` object for possible overrides. 1622 | */ 1623 | $httpBackend.expect = function(method, url, data, headers, keys) { 1624 | var expectation = new MockHttpExpectation(method, url, data, headers, keys), 1625 | chain = { 1626 | respond: function(status, data, headers, statusText) { 1627 | expectation.response = createResponse(status, data, headers, statusText); 1628 | return chain; 1629 | } 1630 | }; 1631 | 1632 | expectations.push(expectation); 1633 | return chain; 1634 | }; 1635 | 1636 | /** 1637 | * @ngdoc method 1638 | * @name $httpBackend#expectGET 1639 | * @description 1640 | * Creates a new request expectation for GET requests. For more info see `expect()`. 1641 | * 1642 | * @param {string|RegExp|function(string)} url HTTP url or function that receives a url 1643 | * and returns true if the url matches the current definition. 1644 | * @param {Object=} headers HTTP headers. 1645 | * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above. 1646 | * @returns {requestHandler} Returns an object with `respond` method that controls how a matched 1647 | * request is handled. You can save this object for later use and invoke `respond` again in 1648 | * order to change how a matched request is handled. See #expect for more info. 1649 | */ 1650 | 1651 | /** 1652 | * @ngdoc method 1653 | * @name $httpBackend#expectHEAD 1654 | * @description 1655 | * Creates a new request expectation for HEAD requests. For more info see `expect()`. 1656 | * 1657 | * @param {string|RegExp|function(string)} url HTTP url or function that receives a url 1658 | * and returns true if the url matches the current definition. 1659 | * @param {Object=} headers HTTP headers. 1660 | * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above. 1661 | * @returns {requestHandler} Returns an object with `respond` method that controls how a matched 1662 | * request is handled. You can save this object for later use and invoke `respond` again in 1663 | * order to change how a matched request is handled. 1664 | */ 1665 | 1666 | /** 1667 | * @ngdoc method 1668 | * @name $httpBackend#expectDELETE 1669 | * @description 1670 | * Creates a new request expectation for DELETE requests. For more info see `expect()`. 1671 | * 1672 | * @param {string|RegExp|function(string)} url HTTP url or function that receives a url 1673 | * and returns true if the url matches the current definition. 1674 | * @param {Object=} headers HTTP headers. 1675 | * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above. 1676 | * @returns {requestHandler} Returns an object with `respond` method that controls how a matched 1677 | * request is handled. You can save this object for later use and invoke `respond` again in 1678 | * order to change how a matched request is handled. 1679 | */ 1680 | 1681 | /** 1682 | * @ngdoc method 1683 | * @name $httpBackend#expectPOST 1684 | * @description 1685 | * Creates a new request expectation for POST requests. For more info see `expect()`. 1686 | * 1687 | * @param {string|RegExp|function(string)} url HTTP url or function that receives a url 1688 | * and returns true if the url matches the current definition. 1689 | * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that 1690 | * receives data string and returns true if the data is as expected, or Object if request body 1691 | * is in JSON format. 1692 | * @param {Object=} headers HTTP headers. 1693 | * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above. 1694 | * @returns {requestHandler} Returns an object with `respond` method that controls how a matched 1695 | * request is handled. You can save this object for later use and invoke `respond` again in 1696 | * order to change how a matched request is handled. 1697 | */ 1698 | 1699 | /** 1700 | * @ngdoc method 1701 | * @name $httpBackend#expectPUT 1702 | * @description 1703 | * Creates a new request expectation for PUT requests. For more info see `expect()`. 1704 | * 1705 | * @param {string|RegExp|function(string)} url HTTP url or function that receives a url 1706 | * and returns true if the url matches the current definition. 1707 | * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that 1708 | * receives data string and returns true if the data is as expected, or Object if request body 1709 | * is in JSON format. 1710 | * @param {Object=} headers HTTP headers. 1711 | * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above. 1712 | * @returns {requestHandler} Returns an object with `respond` method that controls how a matched 1713 | * request is handled. You can save this object for later use and invoke `respond` again in 1714 | * order to change how a matched request is handled. 1715 | */ 1716 | 1717 | /** 1718 | * @ngdoc method 1719 | * @name $httpBackend#expectPATCH 1720 | * @description 1721 | * Creates a new request expectation for PATCH requests. For more info see `expect()`. 1722 | * 1723 | * @param {string|RegExp|function(string)} url HTTP url or function that receives a url 1724 | * and returns true if the url matches the current definition. 1725 | * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that 1726 | * receives data string and returns true if the data is as expected, or Object if request body 1727 | * is in JSON format. 1728 | * @param {Object=} headers HTTP headers. 1729 | * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above. 1730 | * @returns {requestHandler} Returns an object with `respond` method that controls how a matched 1731 | * request is handled. You can save this object for later use and invoke `respond` again in 1732 | * order to change how a matched request is handled. 1733 | */ 1734 | 1735 | /** 1736 | * @ngdoc method 1737 | * @name $httpBackend#expectJSONP 1738 | * @description 1739 | * Creates a new request expectation for JSONP requests. For more info see `expect()`. 1740 | * 1741 | * @param {string|RegExp|function(string)} url HTTP url or function that receives an url 1742 | * and returns true if the url matches the current definition. 1743 | * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above. 1744 | * @returns {requestHandler} Returns an object with `respond` method that controls how a matched 1745 | * request is handled. You can save this object for later use and invoke `respond` again in 1746 | * order to change how a matched request is handled. 1747 | */ 1748 | createShortMethods('expect'); 1749 | 1750 | /** 1751 | * @ngdoc method 1752 | * @name $httpBackend#expectRoute 1753 | * @description 1754 | * Creates a new request expectation that compares only with the requested route. 1755 | * 1756 | * @param {string} method HTTP method. 1757 | * @param {string} url HTTP url string that supports colon param matching. 1758 | * @returns {requestHandler} Returns an object with `respond` method that controls how a matched 1759 | * request is handled. You can save this object for later use and invoke `respond` again in 1760 | * order to change how a matched request is handled. See #expect for more info. 1761 | */ 1762 | $httpBackend.expectRoute = function(method, url) { 1763 | var pathObj = parseRoute(url); 1764 | return $httpBackend.expect(method, pathObj.regexp, undefined, undefined, pathObj.keys); 1765 | }; 1766 | 1767 | 1768 | /** 1769 | * @ngdoc method 1770 | * @name $httpBackend#flush 1771 | * @description 1772 | * Flushes all pending requests using the trained responses. 1773 | * 1774 | * @param {number=} count Number of responses to flush (in the order they arrived). If undefined, 1775 | * all pending requests will be flushed. If there are no pending requests when the flush method 1776 | * is called an exception is thrown (as this typically a sign of programming error). 1777 | */ 1778 | $httpBackend.flush = function(count, digest) { 1779 | if (digest !== false) $rootScope.$digest(); 1780 | if (!responses.length) throw new Error('No pending request to flush !'); 1781 | 1782 | if (angular.isDefined(count) && count !== null) { 1783 | while (count--) { 1784 | if (!responses.length) throw new Error('No more pending request to flush !'); 1785 | responses.shift()(); 1786 | } 1787 | } else { 1788 | while (responses.length) { 1789 | responses.shift()(); 1790 | } 1791 | } 1792 | $httpBackend.verifyNoOutstandingExpectation(digest); 1793 | }; 1794 | 1795 | 1796 | /** 1797 | * @ngdoc method 1798 | * @name $httpBackend#verifyNoOutstandingExpectation 1799 | * @description 1800 | * Verifies that all of the requests defined via the `expect` api were made. If any of the 1801 | * requests were not made, verifyNoOutstandingExpectation throws an exception. 1802 | * 1803 | * Typically, you would call this method following each test case that asserts requests using an 1804 | * "afterEach" clause. 1805 | * 1806 | * ```js 1807 | * afterEach($httpBackend.verifyNoOutstandingExpectation); 1808 | * ``` 1809 | */ 1810 | $httpBackend.verifyNoOutstandingExpectation = function(digest) { 1811 | if (digest !== false) $rootScope.$digest(); 1812 | if (expectations.length) { 1813 | throw new Error('Unsatisfied requests: ' + expectations.join(', ')); 1814 | } 1815 | }; 1816 | 1817 | 1818 | /** 1819 | * @ngdoc method 1820 | * @name $httpBackend#verifyNoOutstandingRequest 1821 | * @description 1822 | * Verifies that there are no outstanding requests that need to be flushed. 1823 | * 1824 | * Typically, you would call this method following each test case that asserts requests using an 1825 | * "afterEach" clause. 1826 | * 1827 | * ```js 1828 | * afterEach($httpBackend.verifyNoOutstandingRequest); 1829 | * ``` 1830 | */ 1831 | $httpBackend.verifyNoOutstandingRequest = function() { 1832 | if (responses.length) { 1833 | throw new Error('Unflushed requests: ' + responses.length); 1834 | } 1835 | }; 1836 | 1837 | 1838 | /** 1839 | * @ngdoc method 1840 | * @name $httpBackend#resetExpectations 1841 | * @description 1842 | * Resets all request expectations, but preserves all backend definitions. Typically, you would 1843 | * call resetExpectations during a multiple-phase test when you want to reuse the same instance of 1844 | * $httpBackend mock. 1845 | */ 1846 | $httpBackend.resetExpectations = function() { 1847 | expectations.length = 0; 1848 | responses.length = 0; 1849 | }; 1850 | 1851 | return $httpBackend; 1852 | 1853 | 1854 | function createShortMethods(prefix) { 1855 | angular.forEach(['GET', 'DELETE', 'JSONP', 'HEAD'], function(method) { 1856 | $httpBackend[prefix + method] = function(url, headers, keys) { 1857 | return $httpBackend[prefix](method, url, undefined, headers, keys); 1858 | }; 1859 | }); 1860 | 1861 | angular.forEach(['PUT', 'POST', 'PATCH'], function(method) { 1862 | $httpBackend[prefix + method] = function(url, data, headers, keys) { 1863 | return $httpBackend[prefix](method, url, data, headers, keys); 1864 | }; 1865 | }); 1866 | } 1867 | } 1868 | 1869 | function MockHttpExpectation(method, url, data, headers, keys) { 1870 | 1871 | this.data = data; 1872 | this.headers = headers; 1873 | 1874 | this.match = function(m, u, d, h) { 1875 | if (method != m) return false; 1876 | if (!this.matchUrl(u)) return false; 1877 | if (angular.isDefined(d) && !this.matchData(d)) return false; 1878 | if (angular.isDefined(h) && !this.matchHeaders(h)) return false; 1879 | return true; 1880 | }; 1881 | 1882 | this.matchUrl = function(u) { 1883 | if (!url) return true; 1884 | if (angular.isFunction(url.test)) return url.test(u); 1885 | if (angular.isFunction(url)) return url(u); 1886 | return url == u; 1887 | }; 1888 | 1889 | this.matchHeaders = function(h) { 1890 | if (angular.isUndefined(headers)) return true; 1891 | if (angular.isFunction(headers)) return headers(h); 1892 | return angular.equals(headers, h); 1893 | }; 1894 | 1895 | this.matchData = function(d) { 1896 | if (angular.isUndefined(data)) return true; 1897 | if (data && angular.isFunction(data.test)) return data.test(d); 1898 | if (data && angular.isFunction(data)) return data(d); 1899 | if (data && !angular.isString(data)) { 1900 | return angular.equals(angular.fromJson(angular.toJson(data)), angular.fromJson(d)); 1901 | } 1902 | return data == d; 1903 | }; 1904 | 1905 | this.toString = function() { 1906 | return method + ' ' + url; 1907 | }; 1908 | 1909 | this.params = function(u) { 1910 | return angular.extend(parseQuery(), pathParams()); 1911 | 1912 | function pathParams() { 1913 | var keyObj = {}; 1914 | if (!url || !angular.isFunction(url.test) || !keys || keys.length === 0) return keyObj; 1915 | 1916 | var m = url.exec(u); 1917 | if (!m) return keyObj; 1918 | for (var i = 1, len = m.length; i < len; ++i) { 1919 | var key = keys[i - 1]; 1920 | var val = m[i]; 1921 | if (key && val) { 1922 | keyObj[key.name || key] = val; 1923 | } 1924 | } 1925 | 1926 | return keyObj; 1927 | } 1928 | 1929 | function parseQuery() { 1930 | var obj = {}, key_value, key, 1931 | queryStr = u.indexOf('?') > -1 1932 | ? u.substring(u.indexOf('?') + 1) 1933 | : ""; 1934 | 1935 | angular.forEach(queryStr.split('&'), function(keyValue) { 1936 | if (keyValue) { 1937 | key_value = keyValue.replace(/\+/g,'%20').split('='); 1938 | key = tryDecodeURIComponent(key_value[0]); 1939 | if (angular.isDefined(key)) { 1940 | var val = angular.isDefined(key_value[1]) ? tryDecodeURIComponent(key_value[1]) : true; 1941 | if (!hasOwnProperty.call(obj, key)) { 1942 | obj[key] = val; 1943 | } else if (angular.isArray(obj[key])) { 1944 | obj[key].push(val); 1945 | } else { 1946 | obj[key] = [obj[key],val]; 1947 | } 1948 | } 1949 | } 1950 | }); 1951 | return obj; 1952 | } 1953 | function tryDecodeURIComponent(value) { 1954 | try { 1955 | return decodeURIComponent(value); 1956 | } catch (e) { 1957 | // Ignore any invalid uri component 1958 | } 1959 | } 1960 | }; 1961 | } 1962 | 1963 | function createMockXhr() { 1964 | return new MockXhr(); 1965 | } 1966 | 1967 | function MockXhr() { 1968 | 1969 | // hack for testing $http, $httpBackend 1970 | MockXhr.$$lastInstance = this; 1971 | 1972 | this.open = function(method, url, async) { 1973 | this.$$method = method; 1974 | this.$$url = url; 1975 | this.$$async = async; 1976 | this.$$reqHeaders = {}; 1977 | this.$$respHeaders = {}; 1978 | }; 1979 | 1980 | this.send = function(data) { 1981 | this.$$data = data; 1982 | }; 1983 | 1984 | this.setRequestHeader = function(key, value) { 1985 | this.$$reqHeaders[key] = value; 1986 | }; 1987 | 1988 | this.getResponseHeader = function(name) { 1989 | // the lookup must be case insensitive, 1990 | // that's why we try two quick lookups first and full scan last 1991 | var header = this.$$respHeaders[name]; 1992 | if (header) return header; 1993 | 1994 | name = angular.lowercase(name); 1995 | header = this.$$respHeaders[name]; 1996 | if (header) return header; 1997 | 1998 | header = undefined; 1999 | angular.forEach(this.$$respHeaders, function(headerVal, headerName) { 2000 | if (!header && angular.lowercase(headerName) == name) header = headerVal; 2001 | }); 2002 | return header; 2003 | }; 2004 | 2005 | this.getAllResponseHeaders = function() { 2006 | var lines = []; 2007 | 2008 | angular.forEach(this.$$respHeaders, function(value, key) { 2009 | lines.push(key + ': ' + value); 2010 | }); 2011 | return lines.join('\n'); 2012 | }; 2013 | 2014 | this.abort = angular.noop; 2015 | } 2016 | 2017 | 2018 | /** 2019 | * @ngdoc service 2020 | * @name $timeout 2021 | * @description 2022 | * 2023 | * This service is just a simple decorator for {@link ng.$timeout $timeout} service 2024 | * that adds a "flush" and "verifyNoPendingTasks" methods. 2025 | */ 2026 | 2027 | angular.mock.$TimeoutDecorator = ['$delegate', '$browser', function($delegate, $browser) { 2028 | 2029 | /** 2030 | * @ngdoc method 2031 | * @name $timeout#flush 2032 | * @description 2033 | * 2034 | * Flushes the queue of pending tasks. 2035 | * 2036 | * @param {number=} delay maximum timeout amount to flush up until 2037 | */ 2038 | $delegate.flush = function(delay) { 2039 | $browser.defer.flush(delay); 2040 | }; 2041 | 2042 | /** 2043 | * @ngdoc method 2044 | * @name $timeout#verifyNoPendingTasks 2045 | * @description 2046 | * 2047 | * Verifies that there are no pending tasks that need to be flushed. 2048 | */ 2049 | $delegate.verifyNoPendingTasks = function() { 2050 | if ($browser.deferredFns.length) { 2051 | throw new Error('Deferred tasks to flush (' + $browser.deferredFns.length + '): ' + 2052 | formatPendingTasksAsString($browser.deferredFns)); 2053 | } 2054 | }; 2055 | 2056 | function formatPendingTasksAsString(tasks) { 2057 | var result = []; 2058 | angular.forEach(tasks, function(task) { 2059 | result.push('{id: ' + task.id + ', ' + 'time: ' + task.time + '}'); 2060 | }); 2061 | 2062 | return result.join(', '); 2063 | } 2064 | 2065 | return $delegate; 2066 | }]; 2067 | 2068 | angular.mock.$RAFDecorator = ['$delegate', function($delegate) { 2069 | var rafFn = function(fn) { 2070 | var index = rafFn.queue.length; 2071 | rafFn.queue.push(fn); 2072 | return function() { 2073 | rafFn.queue.splice(index, 1); 2074 | }; 2075 | }; 2076 | 2077 | rafFn.queue = []; 2078 | rafFn.supported = $delegate.supported; 2079 | 2080 | rafFn.flush = function() { 2081 | if (rafFn.queue.length === 0) { 2082 | throw new Error('No rAF callbacks present'); 2083 | } 2084 | 2085 | var length = rafFn.queue.length; 2086 | for (var i = 0; i < length; i++) { 2087 | rafFn.queue[i](); 2088 | } 2089 | 2090 | rafFn.queue = rafFn.queue.slice(i); 2091 | }; 2092 | 2093 | return rafFn; 2094 | }]; 2095 | 2096 | /** 2097 | * 2098 | */ 2099 | var originalRootElement; 2100 | angular.mock.$RootElementProvider = function() { 2101 | this.$get = ['$injector', function($injector) { 2102 | originalRootElement = angular.element('
').data('$injector', $injector); 2103 | return originalRootElement; 2104 | }]; 2105 | }; 2106 | 2107 | /** 2108 | * @ngdoc service 2109 | * @name $controller 2110 | * @description 2111 | * A decorator for {@link ng.$controller} with additional `bindings` parameter, useful when testing 2112 | * controllers of directives that use {@link $compile#-bindtocontroller- `bindToController`}. 2113 | * 2114 | * 2115 | * ## Example 2116 | * 2117 | * ```js 2118 | * 2119 | * // Directive definition ... 2120 | * 2121 | * myMod.directive('myDirective', { 2122 | * controller: 'MyDirectiveController', 2123 | * bindToController: { 2124 | * name: '@' 2125 | * } 2126 | * }); 2127 | * 2128 | * 2129 | * // Controller definition ... 2130 | * 2131 | * myMod.controller('MyDirectiveController', ['$log', function($log) { 2132 | * $log.info(this.name); 2133 | * }]); 2134 | * 2135 | * 2136 | * // In a test ... 2137 | * 2138 | * describe('myDirectiveController', function() { 2139 | * it('should write the bound name to the log', inject(function($controller, $log) { 2140 | * var ctrl = $controller('MyDirectiveController', { /* no locals */ }, { name: 'Clark Kent' }); 2141 | * expect(ctrl.name).toEqual('Clark Kent'); 2142 | * expect($log.info.logs).toEqual(['Clark Kent']); 2143 | * })); 2144 | * }); 2145 | * 2146 | * ``` 2147 | * 2148 | * @param {Function|string} constructor If called with a function then it's considered to be the 2149 | * controller constructor function. Otherwise it's considered to be a string which is used 2150 | * to retrieve the controller constructor using the following steps: 2151 | * 2152 | * * check if a controller with given name is registered via `$controllerProvider` 2153 | * * check if evaluating the string on the current scope returns a constructor 2154 | * * if $controllerProvider#allowGlobals, check `window[constructor]` on the global 2155 | * `window` object (not recommended) 2156 | * 2157 | * The string can use the `controller as property` syntax, where the controller instance is published 2158 | * as the specified property on the `scope`; the `scope` must be injected into `locals` param for this 2159 | * to work correctly. 2160 | * 2161 | * @param {Object} locals Injection locals for Controller. 2162 | * @param {Object=} bindings Properties to add to the controller before invoking the constructor. This is used 2163 | * to simulate the `bindToController` feature and simplify certain kinds of tests. 2164 | * @return {Object} Instance of given controller. 2165 | */ 2166 | angular.mock.$ControllerDecorator = ['$delegate', function($delegate) { 2167 | return function(expression, locals, later, ident) { 2168 | if (later && typeof later === 'object') { 2169 | var create = $delegate(expression, locals, true, ident); 2170 | angular.extend(create.instance, later); 2171 | return create(); 2172 | } 2173 | return $delegate(expression, locals, later, ident); 2174 | }; 2175 | }]; 2176 | 2177 | /** 2178 | * @ngdoc service 2179 | * @name $componentController 2180 | * @description 2181 | * A service that can be used to create instances of component controllers. 2182 | *
2183 | * Be aware that the controller will be instantiated and attached to the scope as specified in 2184 | * the component definition object. That means that you must always provide a `$scope` object 2185 | * in the `locals` param. 2186 | *
2187 | * @param {string} componentName the name of the component whose controller we want to instantiate 2188 | * @param {Object} locals Injection locals for Controller. 2189 | * @param {Object=} bindings Properties to add to the controller before invoking the constructor. This is used 2190 | * to simulate the `bindToController` feature and simplify certain kinds of tests. 2191 | * @param {string=} ident Override the property name to use when attaching the controller to the scope. 2192 | * @return {Object} Instance of requested controller. 2193 | */ 2194 | angular.mock.$ComponentControllerProvider = ['$compileProvider', function($compileProvider) { 2195 | this.$get = ['$controller','$injector', function($controller,$injector) { 2196 | return function $componentController(componentName, locals, bindings, ident) { 2197 | // get all directives associated to the component name 2198 | var directives = $injector.get(componentName + 'Directive'); 2199 | // look for those directives that are components 2200 | var candidateDirectives = directives.filter(function(directiveInfo) { 2201 | // components have controller, controllerAs and restrict:'E' 2202 | return directiveInfo.controller && directiveInfo.controllerAs && directiveInfo.restrict === 'E'; 2203 | }); 2204 | // check if valid directives found 2205 | if (candidateDirectives.length === 0) { 2206 | throw new Error('No component found'); 2207 | } 2208 | if (candidateDirectives.length > 1) { 2209 | throw new Error('Too many components found'); 2210 | } 2211 | // get the info of the component 2212 | var directiveInfo = candidateDirectives[0]; 2213 | return $controller(directiveInfo.controller, locals, bindings, ident || directiveInfo.controllerAs); 2214 | }; 2215 | }]; 2216 | }]; 2217 | 2218 | 2219 | /** 2220 | * @ngdoc module 2221 | * @name ngMock 2222 | * @packageName angular-mocks 2223 | * @description 2224 | * 2225 | * # ngMock 2226 | * 2227 | * The `ngMock` module provides support to inject and mock Angular services into unit tests. 2228 | * In addition, ngMock also extends various core ng services such that they can be 2229 | * inspected and controlled in a synchronous manner within test code. 2230 | * 2231 | * 2232 | *
2233 | * 2234 | */ 2235 | angular.module('ngMock', ['ng']).provider({ 2236 | $browser: angular.mock.$BrowserProvider, 2237 | $exceptionHandler: angular.mock.$ExceptionHandlerProvider, 2238 | $log: angular.mock.$LogProvider, 2239 | $interval: angular.mock.$IntervalProvider, 2240 | $httpBackend: angular.mock.$HttpBackendProvider, 2241 | $rootElement: angular.mock.$RootElementProvider, 2242 | $componentController: angular.mock.$ComponentControllerProvider 2243 | }).config(['$provide', function($provide) { 2244 | $provide.decorator('$timeout', angular.mock.$TimeoutDecorator); 2245 | $provide.decorator('$$rAF', angular.mock.$RAFDecorator); 2246 | $provide.decorator('$rootScope', angular.mock.$RootScopeDecorator); 2247 | $provide.decorator('$controller', angular.mock.$ControllerDecorator); 2248 | }]); 2249 | 2250 | /** 2251 | * @ngdoc module 2252 | * @name ngMockE2E 2253 | * @module ngMockE2E 2254 | * @packageName angular-mocks 2255 | * @description 2256 | * 2257 | * The `ngMockE2E` is an angular module which contains mocks suitable for end-to-end testing. 2258 | * Currently there is only one mock present in this module - 2259 | * the {@link ngMockE2E.$httpBackend e2e $httpBackend} mock. 2260 | */ 2261 | angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) { 2262 | $provide.decorator('$httpBackend', angular.mock.e2e.$httpBackendDecorator); 2263 | }]); 2264 | 2265 | /** 2266 | * @ngdoc service 2267 | * @name $httpBackend 2268 | * @module ngMockE2E 2269 | * @description 2270 | * Fake HTTP backend implementation suitable for end-to-end testing or backend-less development of 2271 | * applications that use the {@link ng.$http $http service}. 2272 | * 2273 | * *Note*: For fake http backend implementation suitable for unit testing please see 2274 | * {@link ngMock.$httpBackend unit-testing $httpBackend mock}. 2275 | * 2276 | * This implementation can be used to respond with static or dynamic responses via the `when` api 2277 | * and its shortcuts (`whenGET`, `whenPOST`, etc) and optionally pass through requests to the 2278 | * real $httpBackend for specific requests (e.g. to interact with certain remote apis or to fetch 2279 | * templates from a webserver). 2280 | * 2281 | * As opposed to unit-testing, in an end-to-end testing scenario or in scenario when an application 2282 | * is being developed with the real backend api replaced with a mock, it is often desirable for 2283 | * certain category of requests to bypass the mock and issue a real http request (e.g. to fetch 2284 | * templates or static files from the webserver). To configure the backend with this behavior 2285 | * use the `passThrough` request handler of `when` instead of `respond`. 2286 | * 2287 | * Additionally, we don't want to manually have to flush mocked out requests like we do during unit 2288 | * testing. For this reason the e2e $httpBackend flushes mocked out requests 2289 | * automatically, closely simulating the behavior of the XMLHttpRequest object. 2290 | * 2291 | * To setup the application to run with this http backend, you have to create a module that depends 2292 | * on the `ngMockE2E` and your application modules and defines the fake backend: 2293 | * 2294 | * ```js 2295 | * myAppDev = angular.module('myAppDev', ['myApp', 'ngMockE2E']); 2296 | * myAppDev.run(function($httpBackend) { 2297 | * phones = [{name: 'phone1'}, {name: 'phone2'}]; 2298 | * 2299 | * // returns the current list of phones 2300 | * $httpBackend.whenGET('/phones').respond(phones); 2301 | * 2302 | * // adds a new phone to the phones array 2303 | * $httpBackend.whenPOST('/phones').respond(function(method, url, data) { 2304 | * var phone = angular.fromJson(data); 2305 | * phones.push(phone); 2306 | * return [200, phone, {}]; 2307 | * }); 2308 | * $httpBackend.whenGET(/^\/templates\//).passThrough(); 2309 | * //... 2310 | * }); 2311 | * ``` 2312 | * 2313 | * Afterwards, bootstrap your app with this new module. 2314 | */ 2315 | 2316 | /** 2317 | * @ngdoc method 2318 | * @name $httpBackend#when 2319 | * @module ngMockE2E 2320 | * @description 2321 | * Creates a new backend definition. 2322 | * 2323 | * @param {string} method HTTP method. 2324 | * @param {string|RegExp|function(string)} url HTTP url or function that receives a url 2325 | * and returns true if the url matches the current definition. 2326 | * @param {(string|RegExp)=} data HTTP request body. 2327 | * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header 2328 | * object and returns true if the headers match the current definition. 2329 | * @param {(Array)=} keys Array of keys to assign to regex matches in request url described on 2330 | * {@link ngMock.$httpBackend $httpBackend mock}. 2331 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that 2332 | * control how a matched request is handled. You can save this object for later use and invoke 2333 | * `respond` or `passThrough` again in order to change how a matched request is handled. 2334 | * 2335 | * - respond – 2336 | * `{function([status,] data[, headers, statusText]) 2337 | * | function(function(method, url, data, headers, params)}` 2338 | * – The respond method takes a set of static data to be returned or a function that can return 2339 | * an array containing response status (number), response data (string), response headers 2340 | * (Object), and the text for the status (string). 2341 | * - passThrough – `{function()}` – Any request matching a backend definition with 2342 | * `passThrough` handler will be passed through to the real backend (an XHR request will be made 2343 | * to the server.) 2344 | * - Both methods return the `requestHandler` object for possible overrides. 2345 | */ 2346 | 2347 | /** 2348 | * @ngdoc method 2349 | * @name $httpBackend#whenGET 2350 | * @module ngMockE2E 2351 | * @description 2352 | * Creates a new backend definition for GET requests. For more info see `when()`. 2353 | * 2354 | * @param {string|RegExp|function(string)} url HTTP url or function that receives a url 2355 | * and returns true if the url matches the current definition. 2356 | * @param {(Object|function(Object))=} headers HTTP headers. 2357 | * @param {(Array)=} keys Array of keys to assign to regex matches in request url described on 2358 | * {@link ngMock.$httpBackend $httpBackend mock}. 2359 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that 2360 | * control how a matched request is handled. You can save this object for later use and invoke 2361 | * `respond` or `passThrough` again in order to change how a matched request is handled. 2362 | */ 2363 | 2364 | /** 2365 | * @ngdoc method 2366 | * @name $httpBackend#whenHEAD 2367 | * @module ngMockE2E 2368 | * @description 2369 | * Creates a new backend definition for HEAD requests. For more info see `when()`. 2370 | * 2371 | * @param {string|RegExp|function(string)} url HTTP url or function that receives a url 2372 | * and returns true if the url matches the current definition. 2373 | * @param {(Object|function(Object))=} headers HTTP headers. 2374 | * @param {(Array)=} keys Array of keys to assign to regex matches in request url described on 2375 | * {@link ngMock.$httpBackend $httpBackend mock}. 2376 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that 2377 | * control how a matched request is handled. You can save this object for later use and invoke 2378 | * `respond` or `passThrough` again in order to change how a matched request is handled. 2379 | */ 2380 | 2381 | /** 2382 | * @ngdoc method 2383 | * @name $httpBackend#whenDELETE 2384 | * @module ngMockE2E 2385 | * @description 2386 | * Creates a new backend definition for DELETE requests. For more info see `when()`. 2387 | * 2388 | * @param {string|RegExp|function(string)} url HTTP url or function that receives a url 2389 | * and returns true if the url matches the current definition. 2390 | * @param {(Object|function(Object))=} headers HTTP headers. 2391 | * @param {(Array)=} keys Array of keys to assign to regex matches in request url described on 2392 | * {@link ngMock.$httpBackend $httpBackend mock}. 2393 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that 2394 | * control how a matched request is handled. You can save this object for later use and invoke 2395 | * `respond` or `passThrough` again in order to change how a matched request is handled. 2396 | */ 2397 | 2398 | /** 2399 | * @ngdoc method 2400 | * @name $httpBackend#whenPOST 2401 | * @module ngMockE2E 2402 | * @description 2403 | * Creates a new backend definition for POST requests. For more info see `when()`. 2404 | * 2405 | * @param {string|RegExp|function(string)} url HTTP url or function that receives a url 2406 | * and returns true if the url matches the current definition. 2407 | * @param {(string|RegExp)=} data HTTP request body. 2408 | * @param {(Object|function(Object))=} headers HTTP headers. 2409 | * @param {(Array)=} keys Array of keys to assign to regex matches in request url described on 2410 | * {@link ngMock.$httpBackend $httpBackend mock}. 2411 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that 2412 | * control how a matched request is handled. You can save this object for later use and invoke 2413 | * `respond` or `passThrough` again in order to change how a matched request is handled. 2414 | */ 2415 | 2416 | /** 2417 | * @ngdoc method 2418 | * @name $httpBackend#whenPUT 2419 | * @module ngMockE2E 2420 | * @description 2421 | * Creates a new backend definition for PUT requests. For more info see `when()`. 2422 | * 2423 | * @param {string|RegExp|function(string)} url HTTP url or function that receives a url 2424 | * and returns true if the url matches the current definition. 2425 | * @param {(string|RegExp)=} data HTTP request body. 2426 | * @param {(Object|function(Object))=} headers HTTP headers. 2427 | * @param {(Array)=} keys Array of keys to assign to regex matches in request url described on 2428 | * {@link ngMock.$httpBackend $httpBackend mock}. 2429 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that 2430 | * control how a matched request is handled. You can save this object for later use and invoke 2431 | * `respond` or `passThrough` again in order to change how a matched request is handled. 2432 | */ 2433 | 2434 | /** 2435 | * @ngdoc method 2436 | * @name $httpBackend#whenPATCH 2437 | * @module ngMockE2E 2438 | * @description 2439 | * Creates a new backend definition for PATCH requests. For more info see `when()`. 2440 | * 2441 | * @param {string|RegExp|function(string)} url HTTP url or function that receives a url 2442 | * and returns true if the url matches the current definition. 2443 | * @param {(string|RegExp)=} data HTTP request body. 2444 | * @param {(Object|function(Object))=} headers HTTP headers. 2445 | * @param {(Array)=} keys Array of keys to assign to regex matches in request url described on 2446 | * {@link ngMock.$httpBackend $httpBackend mock}. 2447 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that 2448 | * control how a matched request is handled. You can save this object for later use and invoke 2449 | * `respond` or `passThrough` again in order to change how a matched request is handled. 2450 | */ 2451 | 2452 | /** 2453 | * @ngdoc method 2454 | * @name $httpBackend#whenJSONP 2455 | * @module ngMockE2E 2456 | * @description 2457 | * Creates a new backend definition for JSONP requests. For more info see `when()`. 2458 | * 2459 | * @param {string|RegExp|function(string)} url HTTP url or function that receives a url 2460 | * and returns true if the url matches the current definition. 2461 | * @param {(Array)=} keys Array of keys to assign to regex matches in request url described on 2462 | * {@link ngMock.$httpBackend $httpBackend mock}. 2463 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that 2464 | * control how a matched request is handled. You can save this object for later use and invoke 2465 | * `respond` or `passThrough` again in order to change how a matched request is handled. 2466 | */ 2467 | /** 2468 | * @ngdoc method 2469 | * @name $httpBackend#whenRoute 2470 | * @module ngMockE2E 2471 | * @description 2472 | * Creates a new backend definition that compares only with the requested route. 2473 | * 2474 | * @param {string} method HTTP method. 2475 | * @param {string} url HTTP url string that supports colon param matching. 2476 | * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that 2477 | * control how a matched request is handled. You can save this object for later use and invoke 2478 | * `respond` or `passThrough` again in order to change how a matched request is handled. 2479 | */ 2480 | angular.mock.e2e = {}; 2481 | angular.mock.e2e.$httpBackendDecorator = 2482 | ['$rootScope', '$timeout', '$delegate', '$browser', createHttpBackendMock]; 2483 | 2484 | 2485 | /** 2486 | * @ngdoc type 2487 | * @name $rootScope.Scope 2488 | * @module ngMock 2489 | * @description 2490 | * {@link ng.$rootScope.Scope Scope} type decorated with helper methods useful for testing. These 2491 | * methods are automatically available on any {@link ng.$rootScope.Scope Scope} instance when 2492 | * `ngMock` module is loaded. 2493 | * 2494 | * In addition to all the regular `Scope` methods, the following helper methods are available: 2495 | */ 2496 | angular.mock.$RootScopeDecorator = ['$delegate', function($delegate) { 2497 | 2498 | var $rootScopePrototype = Object.getPrototypeOf($delegate); 2499 | 2500 | $rootScopePrototype.$countChildScopes = countChildScopes; 2501 | $rootScopePrototype.$countWatchers = countWatchers; 2502 | 2503 | return $delegate; 2504 | 2505 | // ------------------------------------------------------------------------------------------ // 2506 | 2507 | /** 2508 | * @ngdoc method 2509 | * @name $rootScope.Scope#$countChildScopes 2510 | * @module ngMock 2511 | * @description 2512 | * Counts all the direct and indirect child scopes of the current scope. 2513 | * 2514 | * The current scope is excluded from the count. The count includes all isolate child scopes. 2515 | * 2516 | * @returns {number} Total number of child scopes. 2517 | */ 2518 | function countChildScopes() { 2519 | // jshint validthis: true 2520 | var count = 0; // exclude the current scope 2521 | var pendingChildHeads = [this.$$childHead]; 2522 | var currentScope; 2523 | 2524 | while (pendingChildHeads.length) { 2525 | currentScope = pendingChildHeads.shift(); 2526 | 2527 | while (currentScope) { 2528 | count += 1; 2529 | pendingChildHeads.push(currentScope.$$childHead); 2530 | currentScope = currentScope.$$nextSibling; 2531 | } 2532 | } 2533 | 2534 | return count; 2535 | } 2536 | 2537 | 2538 | /** 2539 | * @ngdoc method 2540 | * @name $rootScope.Scope#$countWatchers 2541 | * @module ngMock 2542 | * @description 2543 | * Counts all the watchers of direct and indirect child scopes of the current scope. 2544 | * 2545 | * The watchers of the current scope are included in the count and so are all the watchers of 2546 | * isolate child scopes. 2547 | * 2548 | * @returns {number} Total number of watchers. 2549 | */ 2550 | function countWatchers() { 2551 | // jshint validthis: true 2552 | var count = this.$$watchers ? this.$$watchers.length : 0; // include the current scope 2553 | var pendingChildHeads = [this.$$childHead]; 2554 | var currentScope; 2555 | 2556 | while (pendingChildHeads.length) { 2557 | currentScope = pendingChildHeads.shift(); 2558 | 2559 | while (currentScope) { 2560 | count += currentScope.$$watchers ? currentScope.$$watchers.length : 0; 2561 | pendingChildHeads.push(currentScope.$$childHead); 2562 | currentScope = currentScope.$$nextSibling; 2563 | } 2564 | } 2565 | 2566 | return count; 2567 | } 2568 | }]; 2569 | 2570 | 2571 | !(function(jasmineOrMocha) { 2572 | 2573 | if (!jasmineOrMocha) { 2574 | return; 2575 | } 2576 | 2577 | var currentSpec = null, 2578 | injectorState = new InjectorState(), 2579 | annotatedFunctions = [], 2580 | wasInjectorCreated = function() { 2581 | return !!currentSpec; 2582 | }; 2583 | 2584 | angular.mock.$$annotate = angular.injector.$$annotate; 2585 | angular.injector.$$annotate = function(fn) { 2586 | if (typeof fn === 'function' && !fn.$inject) { 2587 | annotatedFunctions.push(fn); 2588 | } 2589 | return angular.mock.$$annotate.apply(this, arguments); 2590 | }; 2591 | 2592 | /** 2593 | * @ngdoc function 2594 | * @name angular.mock.module 2595 | * @description 2596 | * 2597 | * *NOTE*: This function is also published on window for easy access.
2598 | * *NOTE*: This function is declared ONLY WHEN running tests with jasmine or mocha 2599 | * 2600 | * This function registers a module configuration code. It collects the configuration information 2601 | * which will be used when the injector is created by {@link angular.mock.inject inject}. 2602 | * 2603 | * See {@link angular.mock.inject inject} for usage example 2604 | * 2605 | * @param {...(string|Function|Object)} fns any number of modules which are represented as string 2606 | * aliases or as anonymous module initialization functions. The modules are used to 2607 | * configure the injector. The 'ng' and 'ngMock' modules are automatically loaded. If an 2608 | * object literal is passed each key-value pair will be registered on the module via 2609 | * {@link auto.$provide $provide}.value, the key being the string name (or token) to associate 2610 | * with the value on the injector. 2611 | */ 2612 | var module = window.module = angular.mock.module = function() { 2613 | var moduleFns = Array.prototype.slice.call(arguments, 0); 2614 | return wasInjectorCreated() ? workFn() : workFn; 2615 | ///////////////////// 2616 | function workFn() { 2617 | if (currentSpec.$injector) { 2618 | throw new Error('Injector already created, can not register a module!'); 2619 | } else { 2620 | var fn, modules = currentSpec.$modules || (currentSpec.$modules = []); 2621 | angular.forEach(moduleFns, function(module) { 2622 | if (angular.isObject(module) && !angular.isArray(module)) { 2623 | fn = ['$provide', function($provide) { 2624 | angular.forEach(module, function(value, key) { 2625 | $provide.value(key, value); 2626 | }); 2627 | }]; 2628 | } else { 2629 | fn = module; 2630 | } 2631 | if (currentSpec.$providerInjector) { 2632 | currentSpec.$providerInjector.invoke(fn); 2633 | } else { 2634 | modules.push(fn); 2635 | } 2636 | }); 2637 | } 2638 | } 2639 | }; 2640 | 2641 | module.$$beforeAllHook = (window.before || window.beforeAll); 2642 | module.$$afterAllHook = (window.after || window.afterAll); 2643 | 2644 | // purely for testing ngMock itself 2645 | module.$$currentSpec = function(to) { 2646 | if (arguments.length === 0) return to; 2647 | currentSpec = to; 2648 | }; 2649 | 2650 | /** 2651 | * @ngdoc function 2652 | * @name angular.mock.module.sharedInjector 2653 | * @description 2654 | * 2655 | * *NOTE*: This function is declared ONLY WHEN running tests with jasmine or mocha 2656 | * 2657 | * This function ensures a single injector will be used for all tests in a given describe context. 2658 | * This contrasts with the default behaviour where a new injector is created per test case. 2659 | * 2660 | * Use sharedInjector when you want to take advantage of Jasmine's `beforeAll()`, or mocha's 2661 | * `before()` methods. Call `module.sharedInjector()` before you setup any other hooks that 2662 | * will create (i.e call `module()`) or use (i.e call `inject()`) the injector. 2663 | * 2664 | * You cannot call `sharedInjector()` from within a context already using `sharedInjector()`. 2665 | * 2666 | * ##Â Example 2667 | * 2668 | * Typically beforeAll is used to make many assertions about a single operation. This can 2669 | * cut down test run-time as the test setup doesn't need to be re-run, and enabling focussed 2670 | * tests each with a single assertion. 2671 | * 2672 | * ```js 2673 | * describe("Deep Thought", function() { 2674 | * 2675 | * module.sharedInjector(); 2676 | * 2677 | * beforeAll(module("UltimateQuestion")); 2678 | * 2679 | * beforeAll(inject(function(DeepThought) { 2680 | * expect(DeepThought.answer).toBeUndefined(); 2681 | * DeepThought.generateAnswer(); 2682 | * })); 2683 | * 2684 | * it("has calculated the answer correctly", inject(function(DeepThought) { 2685 | * // Because of sharedInjector, we have access to the instance of the DeepThought service 2686 | * // that was provided to the beforeAll() hook. Therefore we can test the generated answer 2687 | * expect(DeepThought.answer).toBe(42); 2688 | * })); 2689 | * 2690 | * it("has calculated the answer within the expected time", inject(function(DeepThought) { 2691 | * expect(DeepThought.runTimeMillennia).toBeLessThan(8000); 2692 | * })); 2693 | * 2694 | * it("has double checked the answer", inject(function(DeepThought) { 2695 | * expect(DeepThought.absolutelySureItIsTheRightAnswer).toBe(true); 2696 | * })); 2697 | * 2698 | * }); 2699 | * 2700 | * ``` 2701 | */ 2702 | module.sharedInjector = function() { 2703 | if (!(module.$$beforeAllHook && module.$$afterAllHook)) { 2704 | throw Error("sharedInjector() cannot be used unless your test runner defines beforeAll/afterAll"); 2705 | } 2706 | 2707 | var initialized = false; 2708 | 2709 | module.$$beforeAllHook(function() { 2710 | if (injectorState.shared) { 2711 | injectorState.sharedError = Error("sharedInjector() cannot be called inside a context that has already called sharedInjector()"); 2712 | throw injectorState.sharedError; 2713 | } 2714 | initialized = true; 2715 | currentSpec = this; 2716 | injectorState.shared = true; 2717 | }); 2718 | 2719 | module.$$afterAllHook(function() { 2720 | if (initialized) { 2721 | injectorState = new InjectorState(); 2722 | module.$$cleanup(); 2723 | } else { 2724 | injectorState.sharedError = null; 2725 | } 2726 | }); 2727 | }; 2728 | 2729 | module.$$beforeEach = function() { 2730 | if (injectorState.shared && currentSpec && currentSpec != this) { 2731 | var state = currentSpec; 2732 | currentSpec = this; 2733 | angular.forEach(["$injector","$modules","$providerInjector", "$injectorStrict"], function(k) { 2734 | currentSpec[k] = state[k]; 2735 | state[k] = null; 2736 | }); 2737 | } else { 2738 | currentSpec = this; 2739 | originalRootElement = null; 2740 | annotatedFunctions = []; 2741 | } 2742 | }; 2743 | 2744 | module.$$afterEach = function() { 2745 | if (injectorState.cleanupAfterEach()) { 2746 | module.$$cleanup(); 2747 | } 2748 | }; 2749 | 2750 | module.$$cleanup = function() { 2751 | var injector = currentSpec.$injector; 2752 | 2753 | annotatedFunctions.forEach(function(fn) { 2754 | delete fn.$inject; 2755 | }); 2756 | 2757 | angular.forEach(currentSpec.$modules, function(module) { 2758 | if (module && module.$$hashKey) { 2759 | module.$$hashKey = undefined; 2760 | } 2761 | }); 2762 | 2763 | currentSpec.$injector = null; 2764 | currentSpec.$modules = null; 2765 | currentSpec.$providerInjector = null; 2766 | currentSpec = null; 2767 | 2768 | if (injector) { 2769 | // Ensure `$rootElement` is instantiated, before checking `originalRootElement` 2770 | var $rootElement = injector.get('$rootElement'); 2771 | var rootNode = $rootElement && $rootElement[0]; 2772 | var cleanUpNodes = !originalRootElement ? [] : [originalRootElement[0]]; 2773 | if (rootNode && (!originalRootElement || rootNode !== originalRootElement[0])) { 2774 | cleanUpNodes.push(rootNode); 2775 | } 2776 | angular.element.cleanData(cleanUpNodes); 2777 | 2778 | // Ensure `$destroy()` is available, before calling it 2779 | // (a mocked `$rootScope` might not implement it (or not even be an object at all)) 2780 | var $rootScope = injector.get('$rootScope'); 2781 | if ($rootScope && $rootScope.$destroy) $rootScope.$destroy(); 2782 | } 2783 | 2784 | // clean up jquery's fragment cache 2785 | angular.forEach(angular.element.fragments, function(val, key) { 2786 | delete angular.element.fragments[key]; 2787 | }); 2788 | 2789 | MockXhr.$$lastInstance = null; 2790 | 2791 | angular.forEach(angular.callbacks, function(val, key) { 2792 | delete angular.callbacks[key]; 2793 | }); 2794 | angular.callbacks.counter = 0; 2795 | }; 2796 | 2797 | (window.beforeEach || window.setup)(module.$$beforeEach); 2798 | (window.afterEach || window.teardown)(module.$$afterEach); 2799 | 2800 | /** 2801 | * @ngdoc function 2802 | * @name angular.mock.inject 2803 | * @description 2804 | * 2805 | * *NOTE*: This function is also published on window for easy access.
2806 | * *NOTE*: This function is declared ONLY WHEN running tests with jasmine or mocha 2807 | * 2808 | * The inject function wraps a function into an injectable function. The inject() creates new 2809 | * instance of {@link auto.$injector $injector} per test, which is then used for 2810 | * resolving references. 2811 | * 2812 | * 2813 | * ## Resolving References (Underscore Wrapping) 2814 | * Often, we would like to inject a reference once, in a `beforeEach()` block and reuse this 2815 | * in multiple `it()` clauses. To be able to do this we must assign the reference to a variable 2816 | * that is declared in the scope of the `describe()` block. Since we would, most likely, want 2817 | * the variable to have the same name of the reference we have a problem, since the parameter 2818 | * to the `inject()` function would hide the outer variable. 2819 | * 2820 | * To help with this, the injected parameters can, optionally, be enclosed with underscores. 2821 | * These are ignored by the injector when the reference name is resolved. 2822 | * 2823 | * For example, the parameter `_myService_` would be resolved as the reference `myService`. 2824 | * Since it is available in the function body as _myService_, we can then assign it to a variable 2825 | * defined in an outer scope. 2826 | * 2827 | * ``` 2828 | * // Defined out reference variable outside 2829 | * var myService; 2830 | * 2831 | * // Wrap the parameter in underscores 2832 | * beforeEach( inject( function(_myService_){ 2833 | * myService = _myService_; 2834 | * })); 2835 | * 2836 | * // Use myService in a series of tests. 2837 | * it('makes use of myService', function() { 2838 | * myService.doStuff(); 2839 | * }); 2840 | * 2841 | * ``` 2842 | * 2843 | * See also {@link angular.mock.module angular.mock.module} 2844 | * 2845 | * ## Example 2846 | * Example of what a typical jasmine tests looks like with the inject method. 2847 | * ```js 2848 | * 2849 | * angular.module('myApplicationModule', []) 2850 | * .value('mode', 'app') 2851 | * .value('version', 'v1.0.1'); 2852 | * 2853 | * 2854 | * describe('MyApp', function() { 2855 | * 2856 | * // You need to load modules that you want to test, 2857 | * // it loads only the "ng" module by default. 2858 | * beforeEach(module('myApplicationModule')); 2859 | * 2860 | * 2861 | * // inject() is used to inject arguments of all given functions 2862 | * it('should provide a version', inject(function(mode, version) { 2863 | * expect(version).toEqual('v1.0.1'); 2864 | * expect(mode).toEqual('app'); 2865 | * })); 2866 | * 2867 | * 2868 | * // The inject and module method can also be used inside of the it or beforeEach 2869 | * it('should override a version and test the new version is injected', function() { 2870 | * // module() takes functions or strings (module aliases) 2871 | * module(function($provide) { 2872 | * $provide.value('version', 'overridden'); // override version here 2873 | * }); 2874 | * 2875 | * inject(function(version) { 2876 | * expect(version).toEqual('overridden'); 2877 | * }); 2878 | * }); 2879 | * }); 2880 | * 2881 | * ``` 2882 | * 2883 | * @param {...Function} fns any number of functions which will be injected using the injector. 2884 | */ 2885 | 2886 | 2887 | 2888 | var ErrorAddingDeclarationLocationStack = function(e, errorForStack) { 2889 | this.message = e.message; 2890 | this.name = e.name; 2891 | if (e.line) this.line = e.line; 2892 | if (e.sourceId) this.sourceId = e.sourceId; 2893 | if (e.stack && errorForStack) 2894 | this.stack = e.stack + '\n' + errorForStack.stack; 2895 | if (e.stackArray) this.stackArray = e.stackArray; 2896 | }; 2897 | ErrorAddingDeclarationLocationStack.prototype.toString = Error.prototype.toString; 2898 | 2899 | window.inject = angular.mock.inject = function() { 2900 | var blockFns = Array.prototype.slice.call(arguments, 0); 2901 | var errorForStack = new Error('Declaration Location'); 2902 | return wasInjectorCreated() ? workFn.call(currentSpec) : workFn; 2903 | ///////////////////// 2904 | function workFn() { 2905 | var modules = currentSpec.$modules || []; 2906 | var strictDi = !!currentSpec.$injectorStrict; 2907 | modules.unshift(['$injector', function($injector) { 2908 | currentSpec.$providerInjector = $injector; 2909 | }]); 2910 | modules.unshift('ngMock'); 2911 | modules.unshift('ng'); 2912 | var injector = currentSpec.$injector; 2913 | if (!injector) { 2914 | if (strictDi) { 2915 | // If strictDi is enabled, annotate the providerInjector blocks 2916 | angular.forEach(modules, function(moduleFn) { 2917 | if (typeof moduleFn === "function") { 2918 | angular.injector.$$annotate(moduleFn); 2919 | } 2920 | }); 2921 | } 2922 | injector = currentSpec.$injector = angular.injector(modules, strictDi); 2923 | currentSpec.$injectorStrict = strictDi; 2924 | } 2925 | for (var i = 0, ii = blockFns.length; i < ii; i++) { 2926 | if (currentSpec.$injectorStrict) { 2927 | // If the injector is strict / strictDi, and the spec wants to inject using automatic 2928 | // annotation, then annotate the function here. 2929 | injector.annotate(blockFns[i]); 2930 | } 2931 | try { 2932 | /* jshint -W040 *//* Jasmine explicitly provides a `this` object when calling functions */ 2933 | injector.invoke(blockFns[i] || angular.noop, this); 2934 | /* jshint +W040 */ 2935 | } catch (e) { 2936 | if (e.stack && errorForStack) { 2937 | throw new ErrorAddingDeclarationLocationStack(e, errorForStack); 2938 | } 2939 | throw e; 2940 | } finally { 2941 | errorForStack = null; 2942 | } 2943 | } 2944 | } 2945 | }; 2946 | 2947 | 2948 | angular.mock.inject.strictDi = function(value) { 2949 | value = arguments.length ? !!value : true; 2950 | return wasInjectorCreated() ? workFn() : workFn; 2951 | 2952 | function workFn() { 2953 | if (value !== currentSpec.$injectorStrict) { 2954 | if (currentSpec.$injector) { 2955 | throw new Error('Injector already created, can not modify strict annotations'); 2956 | } else { 2957 | currentSpec.$injectorStrict = value; 2958 | } 2959 | } 2960 | } 2961 | }; 2962 | 2963 | function InjectorState() { 2964 | this.shared = false; 2965 | this.sharedError = null; 2966 | 2967 | this.cleanupAfterEach = function() { 2968 | return !this.shared || this.sharedError; 2969 | }; 2970 | } 2971 | })(window.jasmine || window.mocha); 2972 | 2973 | 2974 | })(window, window.angular); 2975 | -------------------------------------------------------------------------------- /test/lib/de.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * numeral.js language configuration 3 | * language : German (de) – generally useful in Germany, Austria, Luxembourg, Belgium 4 | * author : Marco Krage : https://github.com/sinky 5 | */ 6 | (function () { 7 | var language = { 8 | delimiters: { 9 | thousands: ' ', 10 | decimal: ',' 11 | }, 12 | abbreviations: { 13 | thousand: 'k', 14 | million: 'm', 15 | billion: 'b', 16 | trillion: 't' 17 | }, 18 | ordinal: function (number) { 19 | return '.'; 20 | }, 21 | currency: { 22 | symbol: '€' 23 | } 24 | }; 25 | 26 | // Node 27 | if (typeof module !== 'undefined' && module.exports) { 28 | module.exports = language; 29 | } 30 | // Browser 31 | if (typeof window !== 'undefined' && this.numeral && this.numeral.language) { 32 | this.numeral.language('de', language); 33 | } 34 | }()); 35 | -------------------------------------------------------------------------------- /test/unit/filtersSpec.js: -------------------------------------------------------------------------------- 1 | /*global module, inject, beforeEach, expect, describe, it */ 2 | 'use strict'; 3 | 4 | describe('numeraljs filter', function () { 5 | 6 | var numeraljsFilter; 7 | 8 | beforeEach(module('ngNumeraljs')); 9 | 10 | describe('without configuration', function () { 11 | beforeEach(inject(function ($filter) { 12 | numeraljsFilter = $filter('numeraljs'); 13 | })); 14 | 15 | it('should return the default formatted value if format is missing', function () { 16 | /* Default numeral.js format is '0,0' */ 17 | expect(numeraljsFilter('1234567890')).toEqual('1,234,567,890'); 18 | expect(numeraljsFilter('12345', null)).toEqual('12,345'); 19 | }); 20 | 21 | it('should return value if value is null or undefined', function () { 22 | expect(numeraljsFilter(undefined, '0%')).toEqual(undefined); 23 | expect(numeraljsFilter(null)).toEqual(null); 24 | expect(numeraljsFilter(null, '0.0')).toEqual(null); 25 | }); 26 | 27 | it('should format strings as numbers', function () { 28 | expect(numeraljsFilter('1024.34', '0.0')).toEqual('1024.3'); 29 | expect(numeraljsFilter('1024.38', '0.0')).toEqual('1024.4'); 30 | expect(numeraljsFilter('34039.1', '0,0.00')).toEqual('34,039.10'); 31 | expect(numeraljsFilter('-0.23', '(.000)')).toEqual('(.230)'); 32 | expect(numeraljsFilter('1230974', '0.0a')).toEqual('1.2m'); 33 | }); 34 | 35 | it('should format zeros as numbers', function () { 36 | expect(numeraljsFilter(0, '0,0')).toEqual('0'); 37 | expect(numeraljsFilter(0, '0.0')).toEqual('0.0'); 38 | expect(numeraljsFilter(0, '$0.00')).toEqual('$0.00'); 39 | }); 40 | 41 | it('should format numbers as numbers', function () { 42 | expect(numeraljsFilter(1024.34, '0.0')).toEqual('1024.3'); 43 | expect(numeraljsFilter(1024.38, '0.0')).toEqual('1024.4'); 44 | expect(numeraljsFilter(34039.1, '0,0.00')).toEqual('34,039.10'); 45 | expect(numeraljsFilter(-0.23, '(.000)')).toEqual('(.230)'); 46 | expect(numeraljsFilter(1230974, '0.0a')).toEqual('1.2m'); 47 | }); 48 | 49 | it('should format strings as currency', function () { 50 | expect(numeraljsFilter('1024.34', '$0.0')).toEqual('$1024.3'); 51 | expect(numeraljsFilter('-1024.38', '($ 0.0)')).toEqual('($ 1024.4)'); 52 | }); 53 | 54 | it('should format strings as bytes', function () { 55 | expect(numeraljsFilter('2048', '0ib')).toEqual('2KiB'); 56 | expect(numeraljsFilter('3467479682787', '0.00ib')).toEqual('3.15TiB'); 57 | }); 58 | }); 59 | 60 | describe('with configuration', function () { 61 | describe('when setting format string', function () { 62 | beforeEach(module('ngNumeraljs', function ($numeraljsConfigProvider) { 63 | $numeraljsConfigProvider.namedFormat('currency', '$ 0,0.00'); 64 | $numeraljsConfigProvider.namedFormat('currencySuffix', '0,0.00 $'); 65 | })); 66 | 67 | beforeEach(inject(function ($filter) { 68 | numeraljsFilter = $filter('numeraljs'); 69 | })); 70 | 71 | it('should use configured format string', function () { 72 | expect(numeraljsFilter('1024.344', 'currency')).toEqual('$ 1,024.34'); 73 | expect(numeraljsFilter('1024.344', 'currencySuffix')).toEqual('1,024.34 $'); 74 | }); 75 | }); 76 | 77 | describe('when setting default format', function () { 78 | beforeEach(module('ngNumeraljs', function ($numeraljsConfigProvider) { 79 | $numeraljsConfigProvider.defaultFormat('0.0 $'); 80 | })); 81 | 82 | beforeEach(inject(function ($filter) { 83 | numeraljsFilter = $filter('numeraljs'); 84 | })); 85 | 86 | it('should use default format string', function () { 87 | expect(numeraljsFilter('1024.344')).toEqual('1024.3 $'); 88 | }); 89 | }); 90 | 91 | describe('when using configProvider', function () { 92 | var configureOnce = true; 93 | beforeEach(module('ngNumeraljs', function ($numeraljsConfigProvider) { 94 | if (configureOnce) { 95 | $numeraljsConfigProvider.defaultFormat('0,0'); 96 | $numeraljsConfigProvider.register('locale', 'de', { 97 | delimiters: { 98 | thousands: ' ', 99 | decimal: ',' 100 | }, 101 | abbreviations: { 102 | thousand: 'k', 103 | million: 'm', 104 | billion: 'b', 105 | trillion: 't' 106 | }, 107 | ordinal: function () { 108 | return '.'; 109 | }, 110 | currency: { 111 | symbol: '€' 112 | } 113 | }); 114 | configureOnce = false; 115 | } 116 | })); 117 | 118 | describe('with default language', function () { 119 | beforeEach(inject(function ($filter) { 120 | numeraljsFilter = $filter('numeraljs'); 121 | })); 122 | 123 | it('should use default format', function () { 124 | expect(numeraljsFilter(1234567)).toEqual('1,234,567'); 125 | expect(numeraljsFilter('1234567')).toEqual('1,234,567'); 126 | }); 127 | 128 | it('should use default (en) settings for currency', function () { 129 | expect(numeraljsFilter(1024.344, '$ 0,0.00')).toEqual('$ 1,024.34'); 130 | expect(numeraljsFilter('1024.344', '$ 0,0.00')).toEqual('$ 1,024.34'); 131 | }); 132 | }); 133 | 134 | describe('with switch to language', function () { 135 | beforeEach(module('ngNumeraljs', function ($numeraljsConfigProvider) { 136 | $numeraljsConfigProvider.locale('de'); 137 | })); 138 | 139 | beforeEach(inject(function ($filter) { 140 | numeraljsFilter = $filter('numeraljs'); 141 | })); 142 | 143 | it('should use default format', function () { 144 | expect(numeraljsFilter(1234567)).toEqual('1 234 567'); 145 | expect(numeraljsFilter('1234567')).toEqual('1 234 567'); 146 | }); 147 | 148 | it('should use set (de) settings for currency', function () { 149 | expect(numeraljsFilter(1024.344, '$ 0,0.00')).toEqual('€ 1 024,34'); 150 | expect(numeraljsFilter('1024,344', '$ 0,0.00')).toEqual('€ 1 024,34'); 151 | }); 152 | }); 153 | }); 154 | }); 155 | 156 | describe('with runtime configuration', function () { 157 | var $config; 158 | 159 | describe('when setting default format', function () { 160 | beforeEach(inject(function ($filter, $numeraljsConfig) { 161 | numeraljsFilter = $filter('numeraljs'); 162 | $config = $numeraljsConfig; 163 | })); 164 | 165 | it('should override the default format', function () { 166 | $config.defaultFormat('0.0 $'); 167 | $config.locale('en'); 168 | expect(numeraljsFilter('1024.344')).toEqual('1024.3 $'); 169 | }); 170 | }); 171 | }); 172 | }); 173 | --------------------------------------------------------------------------------