├── .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 | [](https://www.npmjs.com/package/angular-numeraljs) [](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 |
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 | *
Request expectations
Backend definitions
1044 | *
1045 | *
Syntax
1046 | *
.expect(...).respond(...)
1047 | *
.when(...).respond(...)
1048 | *
1049 | *
1050 | *
Typical usage
1051 | *
strict unit tests
1052 | *
loose (black-box) unit testing
1053 | *
1054 | *
1055 | *
Fulfills multiple requests
1056 | *
NO
1057 | *
YES
1058 | *
1059 | *
1060 | *
Order of requests matters
1061 | *
YES
1062 | *
NO
1063 | *
1064 | *
1065 | *
Request required
1066 | *
YES
1067 | *
NO
1068 | *
1069 | *
1070 | *
Response required
1071 | *
optional (see below)
1072 | *
YES
1073 | *
1074 | *
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 |
--------------------------------------------------------------------------------