├── .gitignore ├── jasmine-1.3 ├── .gitignore └── package.json ├── jasmine-2.2 ├── .gitignore └── package.json ├── jasmine-3.1 ├── .gitignore └── package.json ├── .editorconfig ├── bower.json ├── karma.conf.js ├── .jshintrc ├── package.json ├── LICENSE ├── Gruntfile.js ├── dist ├── jasmine-promise-matchers.min.js └── jasmine-promise-matchers.js ├── README.md ├── tests └── test.js └── source └── source.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .bower 3 | .tmp 4 | bower_components 5 | node_modules 6 | npm-debug.log 7 | tmp 8 | -------------------------------------------------------------------------------- /jasmine-1.3/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .bower 3 | .tmp 4 | bower_components 5 | node_modules 6 | npm-debug.log 7 | tmp 8 | karma.conf.js 9 | source 10 | tests 11 | -------------------------------------------------------------------------------- /jasmine-2.2/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .bower 3 | .tmp 4 | bower_components 5 | node_modules 6 | npm-debug.log 7 | tmp 8 | karma.conf.js 9 | source 10 | tests 11 | -------------------------------------------------------------------------------- /jasmine-3.1/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .bower 3 | .tmp 4 | bower_components 5 | node_modules 6 | npm-debug.log 7 | tmp 8 | karma.conf.js 9 | source 10 | tests 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | ; EditorConfig is awesome: http://EditorConfig.org 2 | ; these particular contents based on https://github.com/yeoman/generator-angular/blob/master/.editorconfig 3 | root = true 4 | 5 | [*] 6 | indent_style = space 7 | indent_size = 2 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | 13 | [*.md] 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jasmine-promise-matchers", 3 | "version": "2.4.0", 4 | "main": "dist/jasmine-promise-matchers.js", 5 | "license": "MIT", 6 | "ignore": [ 7 | "source", 8 | "spec", 9 | ".bowerrc", 10 | ".gitignore", 11 | ".jshintignore", 12 | ".jshintrc", 13 | "bower.json", 14 | "gruntfile.js", 15 | "package.json", 16 | "README.md" 17 | ], 18 | "dependencies": { 19 | }, 20 | "devDependencies": { 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function (config) { 2 | config.set({ 3 | basePath: '', 4 | frameworks: ['jasmine'], 5 | preprocessors: { 6 | }, 7 | files: [ 8 | 'node_modules/angular/angular.js', 9 | 'node_modules/angular-mocks/angular-mocks.js', 10 | 'source/**/*.js', 11 | 'tests/**/*.js' 12 | ], 13 | exclude: [], 14 | reporters: ['progress'], 15 | port: 8080, 16 | runnerPort: 9100, 17 | colors: true, 18 | logLevel: config.LOG_DEBUG, 19 | autoWatch: false, 20 | browsers: ['Chrome'], 21 | captureTimeout: 5000, 22 | singleRun: false 23 | }); 24 | }; 25 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "browser": true, 4 | "esnext": true, 5 | "bitwise": true, 6 | "curly": true, 7 | "eqeqeq": true, 8 | "es3": true, 9 | "immed": true, 10 | "indent": 2, 11 | "latedef": true, 12 | "newcap": true, 13 | "noarg": true, 14 | "quotmark": "single", 15 | "regexp": true, 16 | "undef": true, 17 | "trailing": true, 18 | "smarttabs": true, 19 | "debug": true, 20 | "globals": { 21 | "UAParser": false, 22 | "angular": false, 23 | "jQuery": false, 24 | "_": false, 25 | "$": false, 26 | "rsm": false, 27 | "moment": false, 28 | "google": false, 29 | "i18n": false, 30 | "CryptoJS": false, 31 | "expect": false, 32 | "describe": false, 33 | "afterEach": false, 34 | "beforeEach": false, 35 | "browser": false, 36 | "it": false, 37 | "inject": false, 38 | "element": false 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jasmine-promise-matchers", 3 | "main": "dist/jasmine-promise-matchers.js", 4 | "version": "2.6.0", 5 | "homepage": "https://github.com/bvaughn/jasmine-promise-matchers", 6 | "repository": { 7 | "type": "git", 8 | "url": "git://github.com/bvaughn/jasmine-promise-matchers.git" 9 | }, 10 | "license": "MIT", 11 | "files": [ 12 | "dist" 13 | ], 14 | "scripts": { 15 | "build": "grunt build", 16 | "prepublish": "npm build && npm test", 17 | "test": "grunt test" 18 | }, 19 | "dependencies": {}, 20 | "devDependencies": { 21 | "bower": "1.3.1", 22 | "grunt": "0.4.1", 23 | "grunt-concurrent": "0.3.0", 24 | "grunt-contrib-clean": "0.4.1", 25 | "grunt-contrib-concat": "0.3.0", 26 | "grunt-contrib-copy": "0.4.1", 27 | "grunt-contrib-jshint": "0.6.3", 28 | "grunt-contrib-uglify": "0.2.0", 29 | "grunt-contrib-watch": "0.4.0", 30 | "grunt-karma": "0.8.0", 31 | "grunt-replace": "0.4.4", 32 | "grunt-shell": "^1.1.2", 33 | "grunt-usemin": "2.0.2", 34 | "phantomjs-prebuilt": "^2.1.9" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Brian Vaughn 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /jasmine-2.2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jasmine-promise-matchers", 3 | "version": "0.0.5", 4 | "homepage": "https://github.com/bvaughn/jasmine-promise-matchers", 5 | "repository": { 6 | "type": "git", 7 | "url": "git://github.com/bvaughn/jasmine-promise-matchers.git" 8 | }, 9 | "dependencies": {}, 10 | "devDependencies": { 11 | "angular": "^1.3.15", 12 | "angular-mocks": "^1.3.15", 13 | "bower": "1.3.1", 14 | "grunt": "0.4.1", 15 | "grunt-concurrent": "0.3.0", 16 | "grunt-contrib-clean": "0.4.1", 17 | "grunt-contrib-concat": "0.3.0", 18 | "grunt-contrib-copy": "0.4.1", 19 | "grunt-contrib-jshint": "0.6.3", 20 | "grunt-contrib-uglify": "0.2.0", 21 | "grunt-contrib-watch": "0.4.0", 22 | "grunt-karma": "0.8.0", 23 | "grunt-lintspaces": "~0.8.0", 24 | "grunt-replace": "0.4.4", 25 | "grunt-usemin": "2.0.2", 26 | "jasmine-core": "^2.2.0", 27 | "karma": "^0.12.31", 28 | "karma-chrome-launcher": "^0.1.7", 29 | "karma-cli": "0.0.4", 30 | "karma-jasmine": "^0.3.5", 31 | "karma-phantomjs-launcher": "1.0.0", 32 | "phantomjs-prebuilt": "^2.1.8" 33 | }, 34 | "scripts": { 35 | "pretest": "cp ../karma.conf.js . && rm -rf source && cp -r ../source . && rm -rf tests && cp -r ../tests .", 36 | "test": "karma start karma.conf.js --single-run --browsers PhantomJS" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /jasmine-3.1/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jasmine-promise-matchers", 3 | "version": "0.0.5", 4 | "homepage": "https://github.com/bvaughn/jasmine-promise-matchers", 5 | "repository": { 6 | "type": "git", 7 | "url": "git://github.com/bvaughn/jasmine-promise-matchers.git" 8 | }, 9 | "dependencies": {}, 10 | "devDependencies": { 11 | "angular": "^1.3.15", 12 | "angular-mocks": "^1.3.15", 13 | "bower": "1.3.1", 14 | "grunt": "0.4.1", 15 | "grunt-concurrent": "0.3.0", 16 | "grunt-contrib-clean": "0.4.1", 17 | "grunt-contrib-concat": "0.3.0", 18 | "grunt-contrib-copy": "0.4.1", 19 | "grunt-contrib-jshint": "0.6.3", 20 | "grunt-contrib-uglify": "0.2.0", 21 | "grunt-contrib-watch": "0.4.0", 22 | "grunt-karma": "0.8.0", 23 | "grunt-lintspaces": "~0.8.0", 24 | "grunt-replace": "0.4.4", 25 | "grunt-usemin": "2.0.2", 26 | "jasmine-core": "^3.1.0", 27 | "karma": "^0.12.31", 28 | "karma-chrome-launcher": "^0.1.7", 29 | "karma-cli": "0.0.4", 30 | "karma-jasmine": "^0.3.5", 31 | "karma-phantomjs-launcher": "1.0.0", 32 | "phantomjs-prebuilt": "^2.1.8" 33 | }, 34 | "scripts": { 35 | "pretest": "cp ../karma.conf.js . && rm -rf source && cp -r ../source . && rm -rf tests && cp -r ../tests .", 36 | "test": "karma start karma.conf.js --single-run --browsers PhantomJS" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /jasmine-1.3/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jasmine-promise-matchers", 3 | "version": "0.0.5", 4 | "homepage": "https://github.com/bvaughn/jasmine-promise-matchers", 5 | "repository": { 6 | "type": "git", 7 | "url": "git://github.com/bvaughn/jasmine-promise-matchers.git" 8 | }, 9 | "dependencies": {}, 10 | "devDependencies": { 11 | "angular": "^1.3.15", 12 | "angular-mocks": "^1.3.15", 13 | "bower": "1.3.1", 14 | "grunt": "0.4.1", 15 | "grunt-concurrent": "0.3.0", 16 | "grunt-contrib-clean": "0.4.1", 17 | "grunt-contrib-concat": "0.3.0", 18 | "grunt-contrib-copy": "0.4.1", 19 | "grunt-contrib-jshint": "0.6.3", 20 | "grunt-contrib-uglify": "0.2.0", 21 | "grunt-contrib-watch": "0.4.0", 22 | "grunt-karma": "0.8.0", 23 | "grunt-lintspaces": "~0.8.0", 24 | "grunt-replace": "0.4.4", 25 | "grunt-usemin": "2.0.2", 26 | "jasmine": "^2.3.2", 27 | "jasmine-core": "^2.2.0", 28 | "karma": "^0.12.31", 29 | "karma-chrome-launcher": "^0.1.7", 30 | "karma-cli": "0.0.4", 31 | "karma-jasmine": "^0.3.6", 32 | "karma-phantomjs-launcher": "1.0.0", 33 | "phantomjs-prebuilt": "^2.1.8" 34 | }, 35 | "scripts": { 36 | "pretest": "cp ../karma.conf.js . && rm -rf source && cp -r ../source . && rm -rf tests && cp -r ../tests .", 37 | "test": "karma start karma.conf.js --single-run --browsers PhantomJS" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(grunt) { 4 | 5 | // Project configuration. 6 | grunt.initConfig({ 7 | pkg: grunt.file.readJSON('package.json'), 8 | jshint: { 9 | beforeconcat: ['src/**/*.js'] 10 | }, 11 | concat: { 12 | dist: { 13 | src: [ 14 | 'source/**/*.js' 15 | ], 16 | dest: 'dist/jasmine-promise-matchers.js' 17 | } 18 | }, 19 | uglify: { 20 | options: { 21 | mangle: { 22 | except: ['$injector'] 23 | } 24 | }, 25 | build: { 26 | src: 'dist/jasmine-promise-matchers.js', 27 | dest: 'dist/jasmine-promise-matchers.min.js' 28 | } 29 | }, 30 | shell: { 31 | jasmine1: { 32 | command: 'npm run test', 33 | options: { 34 | execOptions: { 35 | cwd: 'jasmine-1.3' 36 | } 37 | } 38 | }, 39 | jasmine2: { 40 | command: 'npm run test', 41 | options: { 42 | execOptions: { 43 | cwd: 'jasmine-2.2' 44 | } 45 | } 46 | }, 47 | jasmine3: { 48 | command: 'npm run test', 49 | options: { 50 | execOptions: { 51 | cwd: 'jasmine-3.1' 52 | } 53 | } 54 | } 55 | } 56 | }); 57 | 58 | grunt.loadNpmTasks('grunt-contrib-concat'); 59 | grunt.loadNpmTasks('grunt-contrib-jshint'); 60 | grunt.loadNpmTasks('grunt-contrib-uglify'); 61 | grunt.loadNpmTasks('grunt-shell'); 62 | 63 | grunt.registerTask('build', ['jshint','test', 'concat','uglify']); 64 | grunt.registerTask('test', ['shell']); 65 | grunt.registerTask('default', ['test']); 66 | }; 67 | -------------------------------------------------------------------------------- /dist/jasmine-promise-matchers.min.js: -------------------------------------------------------------------------------- 1 | var installPromiseMatchers;(function(){var t,e,a,n;installPromiseMatchers=function(s){s=s||{},angular.mock.inject(function($injector){t=$injector.get("$rootScope"),s.flushHttpBackend!==!1&&(e=$injector.get("$httpBackend")),s.flushTimeout!==!1&&(a=$injector.get("$timeout")),s.flushInterval!==!1&&(n=$injector.get("$interval"))})};var s={PENDING:"pending",REJECTED:"rejected",RESOLVED:"resolved"},c=function(c,r,u,o,i){var f={};if(c.then(function(t){f.actualData=t,f.actualState=s.RESOLVED},function(t){f.actualData=t,f.actualState=s.REJECTED}),t.$apply(),!f.actualState){if(e)try{e.flush()}catch(l){if("No pending request to flush !"!==l.message)throw l}if(a)try{a.flush()}catch(l){if("No deferred tasks to be flushed"!==l.message)throw l}if(n)try{n.flush(1e5)}catch(l){if("No deferred tasks to be flushed"!==l.message)throw l}}if(f.message="Expected "+f.actualState+" to be "+r,f.pass=f.actualState===r,void 0!==u&&f.pass){f.pass=o?u&&u.asymmetricMatch?o.equals(f.actualData,u,i):angular.equals(f.actualData,u):u instanceof jasmine.Matchers.Any||u instanceof jasmine.Matchers.ObjectContaining?u.jasmineMatches(f.actualData):angular.equals(f.actualData,u);var h=jasmine.pp(f.actualData),E=jasmine.pp(u);f.message="Expected "+h+" to be "+E}return f},r=function(t){return{message:"Expected "+t+" to be a Promise",pass:t&&t.then instanceof Function}},u={toBePromise:function(){return r(this.actual)},toBeRejected:function(){return c(this.actual,s.REJECTED).pass},toBeRejectedWith:function(t){return c(this.actual,s.REJECTED,t).pass},toBeResolved:function(){return c(this.actual,s.RESOLVED).pass},toBeResolvedWith:function(t){return c(this.actual,s.RESOLVED,t).pass}},o={toBePromise:function(){return{compare:function(t){return r(t)}}},toBeRejected:function(){return{compare:function(t){return c(t,s.REJECTED)}}},toBeRejectedWith:function(t,e){return{compare:function(a,n){return c(a,s.REJECTED,n,t,e)}}},toBeResolved:function(){return{compare:function(t){return c(t,s.RESOLVED)}}},toBeResolvedWith:function(t,e){return{compare:function(a,n){return c(a,s.RESOLVED,n,t,e)}}}},i=/^1/.test(jasmine.version);beforeEach(function(){i?this.addMatchers(u):jasmine.addMatchers(o)})})(); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Jasmine Promise Matchers 2 | ================ 3 | 4 | Custom matchers for **[Angular promises](http://docs.angularjs.org/api/ng/service/$q)** and **[Jasmine 1.3 - 3.x](https://jasmine.github.io/)**. 5 | 6 | # Overview 7 | 8 | Tests often require simple assertions about Promise resolution/rejection. This could be accomplished by spying on the Promise object *or* by chaining on another Promise (complete with *expects* statements)- but this is a lot of work. The following matchers allow basic assertions to be made about Promises via a brief expectation. 9 | 10 | (Note that each of the below matchers triggers a `$rootScope` digest so that their resolve/reject methods will be triggered. You do not need to trigger this digest yourself but should be aware of it in case it impacts other asynchronous portions of your test.) 11 | 12 | 13 | # Installation 14 | 15 | First install the library using NPM or Bower like so: 16 | 17 | ``` 18 | bower install jasmine-promise-matchers --save-dev 19 | npm install jasmine-promise-matchers --save-dev 20 | ``` 21 | 22 | Then modify your `karma.conf.js` config file to load the library like so: 23 | 24 | ```javascript 25 | module.exports = function (config) { 26 | config.set({ 27 | frameworks: ['jasmine'], 28 | files: [ 29 | "node_modules/jasmine-promise-matchers/dist/jasmine-promise-matchers.js" 30 | // Your source (e.g. source/**/*.js) 31 | // Your tests (e.g. tests/**/*.js) 32 | ] 33 | // Other configuration 34 | }); 35 | }; 36 | 37 | ``` 38 | 39 | Lastly be sure to load the custom Jasmine matchers before your tests run like so: 40 | 41 | ```javascript 42 | beforeEach(function() { 43 | angular.mock.module('your-module'); 44 | 45 | installPromiseMatchers(); 46 | 47 | inject(function() { 48 | // Your injected services 49 | }); 50 | }); 51 | ``` 52 | 53 | By default, this matcher flushes `$httpBackend`, `$interval`, and `$timeout` automatically. This can be overridden when installing the matcher like so: 54 | 55 | ```javascript 56 | beforeEach(function() { 57 | installPromiseMatchers({ 58 | flushHttpBackend: false, 59 | flushInterval: false, 60 | flushTimeout: false 61 | }); 62 | }); 63 | ``` 64 | 65 | 66 | Be sure to call [`angular.mock.module`](https://docs.angularjs.org/api/ngMock/function/angular.mock.module) before installing the promise matcher library (because the promise matcher installer uses the `injector`). 67 | 68 | 69 | # Sublime Plugin 70 | 71 | [@Hyzual](https://github.com/Hyzual) has created a Sublime plugin for this library. Find our more info about that plugin [here](https://packagecontrol.io/packages/Jasmine%20Promise%20Matchers). 72 | 73 | 74 | # Matchers 75 | 76 | ### toBePromise 77 | Verifies that a value is a $q Promise. 78 | ```js 79 | expect(promise).toBePromise(); 80 | ``` 81 | 82 | ### toBeRejected 83 | Verifies that a Promise is (or has been) rejected. 84 | ```js 85 | expect(promise).toBeRejected(); 86 | ``` 87 | 88 | ### toBeRejectedWith 89 | Verifies that a Promise is (or has been) rejected with the specified parameter. 90 | ```js 91 | expect(promise).toBeRejectedWith('something'); 92 | 93 | // Asymmetric matching is also supported for objects: 94 | expect(promise).toBeRejectedWith(jasmine.objectContaining({partial: 'match'})); 95 | ``` 96 | 97 | ### toBeResolved 98 | Verifies that a Promise is (or has been) resolved. 99 | ```js 100 | expect(promise).toBeResolved(); 101 | ``` 102 | 103 | ### toBeResolvedWith 104 | Verifies that a Promise is (or has been) resolved with the specified parameter. 105 | ```js 106 | expect(promise).toBeResolvedWith('something'); 107 | 108 | // Asymmetric matching is also supported for objects: 109 | expect(promise).toBeResolvedWith(jasmine.objectContaining({partial: 'match'})); 110 | ``` 111 | 112 | # Development 113 | 114 | If you'd like to contribute to this project you'll need to initialize it like so: 115 | ``` 116 | npm i -g karma 117 | cd 118 | npm i 119 | cd jasmine-1.3 120 | npm i 121 | cd ../jasmine-2.2 122 | npm i 123 | cd ../jasmine-3.1 124 | npm i 125 | ``` 126 | 127 | At this point you should be able to build via `grunt build` and run unit tests via `grunt test`. Tests will be run against both Jasmine 1.3 and 2.2 flavors. 128 | -------------------------------------------------------------------------------- /tests/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Allows for testing with angular.mock.module 4 | angular.module('foobar', []); 5 | 6 | describe('Promise Matcher tests', function () { 7 | var deferred, 8 | $http, 9 | $httpBackend, 10 | $timeout, 11 | $interval; 12 | 13 | beforeEach(function() { 14 | angular.mock.module('foobar'); 15 | 16 | installPromiseMatchers(); 17 | 18 | inject(function($q, 19 | _$http_, 20 | _$httpBackend_, 21 | _$timeout_, 22 | _$interval_) { 23 | deferred = $q.defer(); 24 | $http = _$http_; 25 | $httpBackend = _$httpBackend_; 26 | $timeout = _$timeout_; 27 | $interval = _$interval_; 28 | }); 29 | }); 30 | 31 | it('should recognized already-resolved promises as being resolved', function() { 32 | deferred.resolve(); 33 | 34 | expect(deferred.promise).toBeResolved(); 35 | }); 36 | 37 | it('should recognized already-resolved promises as being resolved with expected arguments', function() { 38 | deferred.resolve('foobar'); 39 | 40 | expect(deferred.promise).toBeResolvedWith('foobar'); 41 | }); 42 | 43 | it('should not accept pending promises as resolved', function() { 44 | expect(deferred.promise).not.toBeResolved(); 45 | }); 46 | 47 | it('should not accept rejected promises as resolved', function() { 48 | deferred.reject(); 49 | 50 | expect(deferred.promise).not.toBeResolved(); 51 | }); 52 | 53 | it('should not accept promises resolved with unexpected arguments', function() { 54 | deferred.resolve('foo'); 55 | 56 | expect(deferred.promise).not.toBeResolvedWith('bar'); 57 | }); 58 | 59 | it('should allow usage of objectContaining matchers in expectations in already-resolved', function() { 60 | deferred.resolve({someProperty: 'someValue', somethingElse: 'dontCare'}); 61 | 62 | expect(deferred.promise).toBeResolvedWith(jasmine.objectContaining({someProperty: 'someValue'})); 63 | }); 64 | 65 | it('should allow usage of any matchers in expectations in already-resolved', function() { 66 | deferred.resolve('foobar'); 67 | 68 | expect(deferred.promise).toBeResolvedWith(jasmine.any(String)); 69 | }); 70 | 71 | it('should recognized already-rejected promises as being rejected', function() { 72 | deferred.reject(); 73 | 74 | expect(deferred.promise).toBeRejected(); 75 | }); 76 | 77 | it('should recognized already-rejected promises as being rejected with expected arguments', function() { 78 | deferred.reject('foobar'); 79 | 80 | expect(deferred.promise).toBeRejectedWith('foobar'); 81 | }); 82 | 83 | it('should allow usage of objectContaining matchers in expectations in already-rejected', function() { 84 | deferred.reject({someProperty: 'someValue', somethingElse: 'dontCare'}); 85 | 86 | expect(deferred.promise).toBeRejectedWith(jasmine.objectContaining({someProperty: 'someValue'})); 87 | }); 88 | 89 | it('should allow usage of any matchers in expectations in already-rejected', function() { 90 | deferred.reject('foobar'); 91 | 92 | expect(deferred.promise).toBeRejectedWith(jasmine.any(String)); 93 | }); 94 | 95 | it('should not accept pending promises as rejected', function() { 96 | expect(deferred.promise).not.toBeRejected(); 97 | }); 98 | 99 | it('should not accept resolved promises as rejected', function() { 100 | deferred.resolve(); 101 | 102 | expect(deferred.promise).not.toBeRejected(); 103 | }); 104 | 105 | it('should not accept promises rejected with unexpected arguments', function() { 106 | deferred.reject('foo'); 107 | 108 | expect(deferred.promise).not.toBeRejectedWith('bar'); 109 | }); 110 | 111 | it('should recognized $q promises as promises', function() { 112 | expect(deferred.promise).toBePromise(); 113 | }); 114 | 115 | it('should recognized things that are not $q promises as such', function() { 116 | expect(undefined).not.toBePromise(); 117 | expect(null).not.toBePromise(); 118 | expect('').not.toBePromise(); 119 | expect('foo').not.toBePromise(); 120 | expect(1).not.toBePromise(); 121 | expect({}).not.toBePromise(); 122 | }); 123 | 124 | it('should ignore functions when evaulating equality (unless a Jasmine asymmetric equality matcher has been used)', function() { 125 | var objectA = { 126 | foo: 'bar', 127 | baz: function() {} 128 | }; 129 | var objectB = { 130 | foo: 'bar', 131 | baz: function() {} 132 | }; 133 | 134 | deferred.resolve(objectA); 135 | 136 | expect(deferred.promise).toBeResolvedWith(objectB); 137 | }); 138 | 139 | it('should flush pending $timeouts', function() { 140 | $timeout(function() { 141 | deferred.resolve(); 142 | }, 1000); 143 | 144 | expect(deferred.promise).toBeResolved(); 145 | }); 146 | 147 | it('should flush pending $interval', function() { 148 | $interval(function() { 149 | deferred.resolve(); 150 | }, 1000, 100); 151 | 152 | expect(deferred.promise).toBeResolved(); 153 | }); 154 | 155 | it('should flush pending $http requests', function() { 156 | $httpBackend.expectGET('/test').respond(200); 157 | expect($http.get('/test')).toBeResolved(); 158 | }); 159 | 160 | it('should not call $httpBackend.flush() when the promise is already resolved', function () { 161 | spyOn($httpBackend, 'flush'); 162 | deferred.resolve(); 163 | expect(deferred.promise).toBeResolved(); 164 | expect($httpBackend.flush).not.toHaveBeenCalled(); 165 | }); 166 | 167 | it('should not call $timeout.flush() when the promise is already resolved', function () { 168 | spyOn($timeout, 'flush'); 169 | deferred.resolve(); 170 | expect(deferred.promise).toBeResolved(); 171 | expect($timeout.flush).not.toHaveBeenCalled(); 172 | }); 173 | 174 | it('should not call $interval.flush() when the promise is already resolved', function () { 175 | spyOn($interval, 'flush'); 176 | deferred.resolve(); 177 | expect(deferred.promise).toBeResolved(); 178 | expect($interval.flush).not.toHaveBeenCalled(); 179 | }); 180 | }); 181 | -------------------------------------------------------------------------------- /source/source.js: -------------------------------------------------------------------------------- 1 | var installPromiseMatchers; 2 | 3 | (function() { 4 | var $scope, 5 | $httpBackend, 6 | $timeout, 7 | $interval; 8 | 9 | installPromiseMatchers = function(options) { 10 | options = options || {}; 11 | angular.mock.inject(function($injector) { 12 | $scope = $injector.get('$rootScope'); 13 | if (options.flushHttpBackend !== false) { 14 | $httpBackend = $injector.get('$httpBackend'); 15 | } 16 | if (options.flushTimeout !== false) { 17 | $timeout = $injector.get('$timeout'); 18 | } 19 | if (options.flushInterval !== false) { 20 | $interval = $injector.get('$interval'); 21 | } 22 | }); 23 | }; 24 | 25 | var PROMISE_STATE = { 26 | PENDING: 'pending', 27 | REJECTED: 'rejected', 28 | RESOLVED: 'resolved', 29 | }; 30 | 31 | 32 | /** 33 | * Helper method to verify expectations and return a Jasmine-friendly info-object. 34 | * 35 | * The last 2 parameters are optional and only required for Jasmine 2. 36 | * For more info see http://jasmine.github.io/2.0/custom_matcher.html 37 | * 38 | * @param promise Promise to test 39 | * @param expectedState PROMISE_STATE enum 40 | * @param opt_expectedData Optional value promise was expected to reject/resolve with 41 | * @param opt_util Jasmine 2 utility providing its own equality method 42 | * @param opt_customEqualityTesters Required by opt_util equality method 43 | */ 44 | var getPromiseInfo = function(promise, expectedState, opt_expectedData, opt_util, opt_customEqualityTesters) { 45 | var info = {}; 46 | 47 | promise.then( 48 | function(data) { 49 | info.actualData = data; 50 | info.actualState = PROMISE_STATE.RESOLVED; 51 | }, 52 | function(data) { 53 | info.actualData = data; 54 | info.actualState = PROMISE_STATE.REJECTED; 55 | }); 56 | 57 | $scope.$apply(); // Trigger Promise resolution 58 | 59 | if (!info.actualState) { 60 | // Trigger $httpBackend flush if any requests are pending 61 | if ($httpBackend) { 62 | try { 63 | $httpBackend.flush(); 64 | } catch (err) { 65 | if (err.message !== 'No pending request to flush !') { 66 | throw err; 67 | } 68 | } 69 | } 70 | 71 | // Trigger $timeout flush if any deferred tasks are pending 72 | if ($timeout) { 73 | try { 74 | $timeout.flush(); 75 | } catch (err) { 76 | if (err.message !== 'No deferred tasks to be flushed') { 77 | throw err; 78 | } 79 | } 80 | } 81 | 82 | // Trigger $interval flush if any deferred tasks are pending 83 | if ($interval) { 84 | try { 85 | // Flushing $interval requires an amount of time, I believe this number should flush pretty much anything useful... 86 | $interval.flush(100000); 87 | } catch (err) { 88 | if (err.message !== 'No deferred tasks to be flushed') { 89 | throw err; 90 | } 91 | } 92 | } 93 | } 94 | 95 | info.message = 'Expected ' + info.actualState + ' to be ' + expectedState; 96 | info.pass = info.actualState === expectedState; 97 | 98 | // If resolve/reject expectations have been made, check the data.. 99 | if (opt_expectedData !== undefined && info.pass) { 100 | 101 | // Jasmine 2 102 | if (opt_util) { 103 | // Detect Jasmine's asymmetric equality matchers and use Jasmine's own equality test for them 104 | // Otherwise use Angular's equality check since it ignores properties that are functions 105 | if (opt_expectedData && opt_expectedData.asymmetricMatch) { 106 | info.pass = opt_util.equals(info.actualData, opt_expectedData, opt_customEqualityTesters); 107 | } else { 108 | info.pass = angular.equals(info.actualData, opt_expectedData); 109 | } 110 | 111 | // Jasmine 1.3 112 | } else { 113 | if (opt_expectedData instanceof jasmine.Matchers.Any || 114 | opt_expectedData instanceof jasmine.Matchers.ObjectContaining) { 115 | info.pass = opt_expectedData.jasmineMatches(info.actualData); 116 | } else { 117 | info.pass = angular.equals(info.actualData, opt_expectedData); 118 | } 119 | } 120 | 121 | var actual = jasmine.pp(info.actualData); 122 | var expected = jasmine.pp(opt_expectedData); 123 | 124 | info.message = 'Expected ' + actual + ' to be ' + expected; 125 | } 126 | 127 | return info; 128 | }; 129 | 130 | var isPromise = function(value) { 131 | return { 132 | message: 'Expected ' + value + ' to be a Promise', 133 | pass: value && value.then instanceof Function 134 | }; 135 | }; 136 | 137 | // Jasmine 1.x style matchers 138 | var jasmine1Matchers = { 139 | toBePromise: function() { 140 | return isPromise(this.actual); 141 | }, 142 | toBeRejected: function() { 143 | return getPromiseInfo(this.actual, PROMISE_STATE.REJECTED).pass; 144 | }, 145 | toBeRejectedWith: function(expectedData) { 146 | return getPromiseInfo(this.actual, PROMISE_STATE.REJECTED, expectedData).pass; 147 | }, 148 | toBeResolved: function() { 149 | return getPromiseInfo(this.actual, PROMISE_STATE.RESOLVED).pass; 150 | }, 151 | toBeResolvedWith: function(expectedData) { 152 | return getPromiseInfo(this.actual, PROMISE_STATE.RESOLVED, expectedData).pass; 153 | } 154 | }; 155 | 156 | // Jasmine 2.x style matchers 157 | var jasmine2Matchers = { 158 | toBePromise: function() { 159 | return { 160 | compare: function(promise) { 161 | return isPromise(promise); 162 | } 163 | }; 164 | }, 165 | toBeRejected: function() { 166 | return { 167 | compare: function(promise) { 168 | return getPromiseInfo(promise, PROMISE_STATE.REJECTED); 169 | } 170 | }; 171 | }, 172 | toBeRejectedWith: function(util, customEqualityTesters) { 173 | return { 174 | compare: function(promise, expectedData) { 175 | return getPromiseInfo(promise, PROMISE_STATE.REJECTED, expectedData, util, customEqualityTesters); 176 | } 177 | }; 178 | }, 179 | toBeResolved: function() { 180 | return { 181 | compare: function(promise) { 182 | return getPromiseInfo(promise, PROMISE_STATE.RESOLVED); 183 | } 184 | }; 185 | }, 186 | toBeResolvedWith: function(util, customEqualityTesters) { 187 | return { 188 | compare: function(promise, expectedData) { 189 | return getPromiseInfo(promise, PROMISE_STATE.RESOLVED, expectedData, util, customEqualityTesters); 190 | } 191 | }; 192 | } 193 | }; 194 | 195 | // Detect which version of Jasmine we are running under 196 | var isJasmine1 = /^1/.test(jasmine.version); 197 | 198 | // Install the appropriate set of matchers based on which Jasmine version we're running with 199 | beforeEach(function() { 200 | if (isJasmine1) { 201 | this.addMatchers(jasmine1Matchers); 202 | } else { 203 | jasmine.addMatchers(jasmine2Matchers); 204 | } 205 | }); 206 | })(); 207 | -------------------------------------------------------------------------------- /dist/jasmine-promise-matchers.js: -------------------------------------------------------------------------------- 1 | var installPromiseMatchers; 2 | 3 | (function() { 4 | var $scope, 5 | $httpBackend, 6 | $timeout, 7 | $interval; 8 | 9 | installPromiseMatchers = function(options) { 10 | options = options || {}; 11 | angular.mock.inject(function($injector) { 12 | $scope = $injector.get('$rootScope'); 13 | if (options.flushHttpBackend !== false) { 14 | $httpBackend = $injector.get('$httpBackend'); 15 | } 16 | if (options.flushTimeout !== false) { 17 | $timeout = $injector.get('$timeout'); 18 | } 19 | if (options.flushInterval !== false) { 20 | $interval = $injector.get('$interval'); 21 | } 22 | }); 23 | }; 24 | 25 | var PROMISE_STATE = { 26 | PENDING: 'pending', 27 | REJECTED: 'rejected', 28 | RESOLVED: 'resolved', 29 | }; 30 | 31 | 32 | /** 33 | * Helper method to verify expectations and return a Jasmine-friendly info-object. 34 | * 35 | * The last 2 parameters are optional and only required for Jasmine 2. 36 | * For more info see http://jasmine.github.io/2.0/custom_matcher.html 37 | * 38 | * @param promise Promise to test 39 | * @param expectedState PROMISE_STATE enum 40 | * @param opt_expectedData Optional value promise was expected to reject/resolve with 41 | * @param opt_util Jasmine 2 utility providing its own equality method 42 | * @param opt_customEqualityTesters Required by opt_util equality method 43 | */ 44 | var getPromiseInfo = function(promise, expectedState, opt_expectedData, opt_util, opt_customEqualityTesters) { 45 | var info = {}; 46 | 47 | promise.then( 48 | function(data) { 49 | info.actualData = data; 50 | info.actualState = PROMISE_STATE.RESOLVED; 51 | }, 52 | function(data) { 53 | info.actualData = data; 54 | info.actualState = PROMISE_STATE.REJECTED; 55 | }); 56 | 57 | $scope.$apply(); // Trigger Promise resolution 58 | 59 | if (!info.actualState) { 60 | // Trigger $httpBackend flush if any requests are pending 61 | if ($httpBackend) { 62 | try { 63 | $httpBackend.flush(); 64 | } catch (err) { 65 | if (err.message !== 'No pending request to flush !') { 66 | throw err; 67 | } 68 | } 69 | } 70 | 71 | // Trigger $timeout flush if any deferred tasks are pending 72 | if ($timeout) { 73 | try { 74 | $timeout.flush(); 75 | } catch (err) { 76 | if (err.message !== 'No deferred tasks to be flushed') { 77 | throw err; 78 | } 79 | } 80 | } 81 | 82 | // Trigger $interval flush if any deferred tasks are pending 83 | if ($interval) { 84 | try { 85 | // Flushing $interval requires an amount of time, I believe this number should flush pretty much anything useful... 86 | $interval.flush(100000); 87 | } catch (err) { 88 | if (err.message !== 'No deferred tasks to be flushed') { 89 | throw err; 90 | } 91 | } 92 | } 93 | } 94 | 95 | info.message = 'Expected ' + info.actualState + ' to be ' + expectedState; 96 | info.pass = info.actualState === expectedState; 97 | 98 | // If resolve/reject expectations have been made, check the data.. 99 | if (opt_expectedData !== undefined && info.pass) { 100 | 101 | // Jasmine 2 102 | if (opt_util) { 103 | // Detect Jasmine's asymmetric equality matchers and use Jasmine's own equality test for them 104 | // Otherwise use Angular's equality check since it ignores properties that are functions 105 | if (opt_expectedData && opt_expectedData.asymmetricMatch) { 106 | info.pass = opt_util.equals(info.actualData, opt_expectedData, opt_customEqualityTesters); 107 | } else { 108 | info.pass = angular.equals(info.actualData, opt_expectedData); 109 | } 110 | 111 | // Jasmine 1.3 112 | } else { 113 | if (opt_expectedData instanceof jasmine.Matchers.Any || 114 | opt_expectedData instanceof jasmine.Matchers.ObjectContaining) { 115 | info.pass = opt_expectedData.jasmineMatches(info.actualData); 116 | } else { 117 | info.pass = angular.equals(info.actualData, opt_expectedData); 118 | } 119 | } 120 | 121 | var actual = jasmine.pp(info.actualData); 122 | var expected = jasmine.pp(opt_expectedData); 123 | 124 | info.message = 'Expected ' + actual + ' to be ' + expected; 125 | } 126 | 127 | return info; 128 | }; 129 | 130 | var isPromise = function(value) { 131 | return { 132 | message: 'Expected ' + value + ' to be a Promise', 133 | pass: value && value.then instanceof Function 134 | }; 135 | }; 136 | 137 | // Jasmine 1.x style matchers 138 | var jasmine1Matchers = { 139 | toBePromise: function() { 140 | return isPromise(this.actual); 141 | }, 142 | toBeRejected: function() { 143 | return getPromiseInfo(this.actual, PROMISE_STATE.REJECTED).pass; 144 | }, 145 | toBeRejectedWith: function(expectedData) { 146 | return getPromiseInfo(this.actual, PROMISE_STATE.REJECTED, expectedData).pass; 147 | }, 148 | toBeResolved: function() { 149 | return getPromiseInfo(this.actual, PROMISE_STATE.RESOLVED).pass; 150 | }, 151 | toBeResolvedWith: function(expectedData) { 152 | return getPromiseInfo(this.actual, PROMISE_STATE.RESOLVED, expectedData).pass; 153 | } 154 | }; 155 | 156 | // Jasmine 2.x style matchers 157 | var jasmine2Matchers = { 158 | toBePromise: function() { 159 | return { 160 | compare: function(promise) { 161 | return isPromise(promise); 162 | } 163 | }; 164 | }, 165 | toBeRejected: function() { 166 | return { 167 | compare: function(promise) { 168 | return getPromiseInfo(promise, PROMISE_STATE.REJECTED); 169 | } 170 | }; 171 | }, 172 | toBeRejectedWith: function(util, customEqualityTesters) { 173 | return { 174 | compare: function(promise, expectedData) { 175 | return getPromiseInfo(promise, PROMISE_STATE.REJECTED, expectedData, util, customEqualityTesters); 176 | } 177 | }; 178 | }, 179 | toBeResolved: function() { 180 | return { 181 | compare: function(promise) { 182 | return getPromiseInfo(promise, PROMISE_STATE.RESOLVED); 183 | } 184 | }; 185 | }, 186 | toBeResolvedWith: function(util, customEqualityTesters) { 187 | return { 188 | compare: function(promise, expectedData) { 189 | return getPromiseInfo(promise, PROMISE_STATE.RESOLVED, expectedData, util, customEqualityTesters); 190 | } 191 | }; 192 | } 193 | }; 194 | 195 | // Detect which version of Jasmine we are running under 196 | var isJasmine1 = /^1/.test(jasmine.version); 197 | 198 | // Install the appropriate set of matchers based on which Jasmine version we're running with 199 | beforeEach(function() { 200 | if (isJasmine1) { 201 | this.addMatchers(jasmine1Matchers); 202 | } else { 203 | jasmine.addMatchers(jasmine2Matchers); 204 | } 205 | }); 206 | })(); 207 | --------------------------------------------------------------------------------