├── .editorconfig ├── .gitignore ├── .travis.yml ├── Gruntfile.js ├── README.md ├── bower.json ├── package.json ├── promise-tracker-http-interceptor.js ├── promise-tracker.js └── test ├── karma.conf.js └── unit ├── interceptor.spec.js └── provider.spec.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | npm-debug.log 3 | .DS_Store 4 | demo 5 | bower_components 6 | node_modules 7 | dist 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: node_js 3 | node_js: 4 | - 0.10 5 | env: 6 | 7 | before_script: 8 | - export DISPLAY=:99.0 9 | - sh -e /etc/init.d/xvfb start 10 | - npm install -g grunt-cli bower 11 | - npm link 12 | - bower install 13 | 14 | script: 15 | - grunt 16 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | 3 | require('load-grunt-tasks')(grunt); 4 | 5 | grunt.initConfig({ 6 | dist: 'dist', 7 | pkgFile: 'bower.json', 8 | pkg: grunt.file.readJSON('bower.json'), 9 | 10 | watch: { 11 | scripts: { 12 | files: ['src/**/*.js', 'test/unit/**/*.js'], 13 | tasks: ['karma:watch:run'] 14 | }, 15 | gruntfile: { 16 | files: ['Gruntfile.js'], 17 | tasks: ['jshint'] 18 | } 19 | }, 20 | 21 | jshint: { 22 | all: ['Gruntfile.js', 'src/**/*.js'], 23 | options: { 24 | eqeqeq: true, 25 | globals: { 26 | angular: true 27 | } 28 | } 29 | }, 30 | 31 | clean: ['demo/**/*'], 32 | 33 | karma: { 34 | watch: { 35 | configFile: 'test/karma.conf.js', 36 | background: true, 37 | }, 38 | single: { 39 | configFile: 'test/karma.conf.js', 40 | singleRun: true, 41 | browsers: [process.env.TRAVIS ? 'Firefox' : 'Chrome'], 42 | } 43 | }, 44 | 45 | changelog: { 46 | options: { 47 | dest: 'CHANGELOG.md' 48 | } 49 | }, 50 | }); 51 | 52 | grunt.registerTask('dev', ['karma:watch', 'watch']); 53 | 54 | grunt.registerTask('default', ['jshint', 'test']); 55 | grunt.registerTask('test', ['karma:single']); 56 | }; 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | angular-promise-tracker 2 | ======================= 3 | 4 | > **Version**: 2.0 5 | 6 | (note to users using version 1.x: upgrading has *many* breaking changes, see [the CHANGELOG](https://github.com/ajoslin/angular-promise-tracker/tree/master/CHANGELOG.md).) 7 | 8 | [![Build Status](https://travis-ci.org/ajoslin/angular-promise-tracker.png)](https://travis-ci.org/ajoslin/angular-promise-tracker) 9 | 10 | Small, feature filled library used to easily add spinners or general promise/request tracking to your angular app. 11 | 12 | * [Quick Start](#quick-start) 13 | * [API Documentation](#api-documentation) 14 | * [Changes](https://github.com/ajoslin/angular-promise-tracker/tree/master/CHANGELOG.md) 15 | * [License](#license) 16 | 17 | ## Quick Start 18 | 19 | The basic idea: each time we add one or more promises to an instance of a `promiseTracker`, that instance's `active()` method will return true until all added promises are resolved. A common use case is showing some sort of loading spinner while some http requests are loading. 20 | 21 | [Play with this example on plunkr](http://plnkr.co/edit/PrO2ou9b1uANbeGoX6eB?p=preview) 22 | 23 | ```sh 24 | $ bower install angular-promise-tracker 25 | ``` 26 | ```html 27 | 28 |
29 | Loading... 30 |
31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | ``` 41 | ```js 42 | angular.module('myApp', ['ajoslin.promise-tracker']) 43 | .controller('MainCtrl', function($scope, $http, $timeout, promiseTracker) { 44 | //Create a new tracker 45 | $scope.loadingTracker = promiseTracker(); 46 | 47 | //use `addPromise` to add any old promise to our tracker 48 | $scope.delaySomething = function() { 49 | var promise = $timeout(function() { 50 | alert('Delayed something!'); 51 | }, 1000); 52 | $scope.loadingTracker.addPromise(promise); 53 | }; 54 | 55 | //use `tracker:` shortcut in $http config to link our http promise to a tracker 56 | //This shortcut is included in promise-tracker-http-interceptor.js 57 | $scope.fetchSomething = function(id) { 58 | return $http.get('/something', { 59 | tracker: $scope.loadingTracker 60 | }).then(function(response) { 61 | alert('Fetched something! ' + response.data); 62 | }); 63 | }; 64 | }); 65 | ``` 66 | 67 | ## API Documentation 68 | 69 | ### Service `promiseTracker` 70 | 71 | * **`tracker` promiseTracker([options])** 72 | 73 | Creates and returns a new promiseTracker. 74 | 75 | Options can be given as an object, with the following allowed values: 76 | 77 | - `activationDelay` `{Number}` - Number of milliseconds that an added promise needs to be pending before this tracker is active. 78 | * Usage example: You have some http calls that sometimes return too quickly for a loading spinner to look good. You only want to show the tracker if a promise is pending for over 500ms. You put `{activationDelay: 500}` in options. 79 | - `minDuration` `{Number}` - Minimum number of milliseconds that a tracker will stay active. 80 | * Usage example: You want a loading spinner to always show up for at least 750ms. You put `{minDuration: 750}` in options. 81 | 82 | Often you want a global promiseTracker (eg to show a loading screen); one easy way is to put the tracker on your $rootScope: 83 | 84 | ```js 85 | app.run(function($rootScope, promiseTracker) { 86 | $rootScope.loadingTracker = promiseTracker(); 87 | }); 88 | ``` 89 | 90 | ### Instantiated promiseTracker 91 | 92 | Example: `var myTracker = promiseTracker({ activationDelay: 500, minDuration: 750 });` 93 | 94 | * **`boolean` tracker.active()** 95 | 96 | Returns whether this tracker is currently active. That is, whether any of the promises added to/created by this tracker are still pending. Note: if the `activationDelay` has not elapsed yet, this will return false. 97 | 98 | * **`boolean` tracker.tracking()** 99 | 100 | Returns whether this tracker is currently tracking a request. That is, whether any of the promises added to/created by this tracker are still pending. This method has no regard for `activationDelay`. 101 | 102 | * **`number` tracker.trackingCount()** 103 | 104 | The count of promises currently being tracked. 105 | 106 | * **`promise` tracker.addPromise(promise)** 107 | 108 | Add any arbitrary promise to tracker. `tracker.active()` will be true until `promise` is resolved or rejected. 109 | 110 | - `promise` `{object}` - Promise to add 111 | 112 | Usage Example: 113 | 114 | ```js 115 | var promise = $timeout(doSomethingCool, 1000); 116 | myTracker.addPromise(promise); 117 | console.log(myTracker.active()); // => true 118 | //1000 milliseconds later... 119 | console.log(myTracker.active()); // => false 120 | ``` 121 | 122 | * **`promise` tracker.createPromise()** 123 | 124 | Creates and returns a new deferred object that is tracked by our promiseTracker. 125 | 126 | Usage Example: 127 | 128 | ```js 129 | var deferred = myTracker.createPromise() 130 | console.log(myTracker.active()); // => true 131 | deferred.resolve(); 132 | console.log(myTracker.active()); // => false 133 | ``` 134 | 135 | * **`void` tracker.cancel()** 136 | 137 | Causes a tracker to immediately become inactive and stop tracking all current promises. 138 | 139 | ### **`$http` Sugar** 140 | 141 | **Requires promise-tracker-http-interceptor.js** 142 | 143 | * **Any $http call's `config` parameter can have a `tracker` field. Examples:** 144 | 145 | ```js 146 | //Add $http promise to tracker with id 'myTracker' 147 | $http('/banana', { tracker: myPromiseTrackerInstance }) 148 | ``` 149 | ```js 150 | //Add $http promise to both 'tracker1' and 'tracker2' 151 | $http.post('/elephant', {some: 'data'}, { tracker: [myFirstTracker, mySecondTracker] }) 152 | ``` 153 | 154 | ## More Examples 155 | 156 | * Do something whenever the tracker's active state changes 157 | 158 | ```js 159 | angular.module('app', ['ajoslin.promise-tracker']) 160 | 161 | .factory('myTracker', function (promiseTracker) { 162 | return promiseTracker(); 163 | }) 164 | 165 | .controller('AppCtrl', function ($rootScope, myTracker) { 166 | $rootScope.$watch(myTracker.active, function (isActive) { 167 | //doSomething() 168 | }); 169 | }); 170 | ``` 171 | 172 | ## Development 173 | 174 | * Install karma & grunt with `npm install -g karma grunt-cli` to build & test 175 | * Install local dependencies with `bower install && npm install` 176 | * Run `grunt` to lint, test, build the code, and build the docs site 177 | * Run `grunt dev` to watch and re-test on changes 178 | 179 | #### New Versions 180 | 181 | ## License 182 | 183 | > Public Domain Mark angular-promise-tracker by Andy Joslin is free of known copyright restrictions. 184 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Andy Joslin", 3 | "name": "promise-tracker", 4 | "description": "Easily add spinners or general request tracking to your angular app.", 5 | "version": "2.1.0", 6 | "homepage": "http://github.com/ajoslin/angular-promise-tracker", 7 | "repository": { 8 | "type": "git", 9 | "url": "git://github.com/ajoslin/angular-promise-tracker" 10 | }, 11 | "license": "Public Domain", 12 | "main": "./promise-tracker.js", 13 | "ignore": [ 14 | "CHANGELOG.md", 15 | "Gruntfile.js", 16 | "bower.json", 17 | "bower_components", 18 | "dist", 19 | "node_modules", 20 | "package.json", 21 | "src", 22 | "test" 23 | ], 24 | "files": [ 25 | "promise-tracker.js", 26 | "promise-tracker-http-interceptor.js" 27 | ], 28 | "dependencies": { 29 | "angular": ">=1.3.0" 30 | }, 31 | "devDependencies": { 32 | "angular-mocks": ">=1.3.0", 33 | "angular-resource": ">=1.3.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-promise-tracker", 3 | "version": "2.2.2", 4 | "main": "promise-tracker.js", 5 | "author": { 6 | "name": "Andy Joslin" 7 | }, 8 | "repository": { 9 | "url": "git://github.com/ajoslin/angular-promise-tracker.git" 10 | }, 11 | "homepage": "https://github.com/ajoslin/angular-promise-tracker", 12 | "dependencies": { 13 | "angular": ">=1.3.0" 14 | }, 15 | "devDependencies": { 16 | "load-grunt-tasks": "~0.2.1", 17 | "semver": "~2.2.1", 18 | "grunt-contrib-clean": "~0.5.0", 19 | "grunt-contrib-jshint": "~0.7.2", 20 | "grunt": "~0.4.2", 21 | "grunt-contrib-watch": "~0.5.3", 22 | "grunt-contrib-uglify": "~0.2.7", 23 | "karma-script-launcher": "~0.1.0", 24 | "karma-chrome-launcher": "~0.1.2", 25 | "karma-html2js-preprocessor": "~0.1.0", 26 | "karma-firefox-launcher": "~0.1.2", 27 | "karma-jasmine": "~0.1.5", 28 | "requirejs": "~2.1.9", 29 | "karma-requirejs": "~0.2.1", 30 | "karma-coffee-preprocessor": "~0.1.1", 31 | "karma-phantomjs-launcher": "~0.1.1", 32 | "karma": "~0.10.8", 33 | "grunt-karma": "~0.6.2", 34 | "grunt-conventional-changelog": "~1.1.0" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /promise-tracker-http-interceptor.js: -------------------------------------------------------------------------------- 1 | /* 2 | * promise-tracker - v2.1.0 - 2014-11-15 3 | * http://github.com/ajoslin/angular-promise-tracker 4 | * Created by Andy Joslin; Licensed under Public Domain 5 | */ 6 | 7 | (function() { 8 | 9 | angular.module('ajoslin.promise-tracker') 10 | .config(['$httpProvider', function($httpProvider) { 11 | $httpProvider.interceptors.push(['$q', 'promiseTracker', function($q, promiseTracker) { 12 | return { 13 | request: function(config) { 14 | if (config.tracker) { 15 | if (!angular.isArray(config.tracker)) { 16 | config.tracker = [config.tracker]; 17 | } 18 | config.$promiseTrackerDeferred = config.$promiseTrackerDeferred || []; 19 | 20 | angular.forEach(config.tracker, function(tracker) { 21 | var deferred = tracker.createPromise(); 22 | config.$promiseTrackerDeferred.push(deferred); 23 | }); 24 | } 25 | return $q.when(config); 26 | }, 27 | response: function(response) { 28 | if (response.config && response.config.$promiseTrackerDeferred) { 29 | angular.forEach(response.config.$promiseTrackerDeferred, function(deferred) { 30 | deferred.resolve(response); 31 | }); 32 | } 33 | return $q.when(response); 34 | }, 35 | responseError: function(response) { 36 | if (response.config && response.config.$promiseTrackerDeferred) { 37 | angular.forEach(response.config.$promiseTrackerDeferred, function(deferred) { 38 | deferred.reject(response); 39 | }); 40 | } 41 | return $q.reject(response); 42 | } 43 | }; 44 | }]); 45 | }]); 46 | 47 | }()); -------------------------------------------------------------------------------- /promise-tracker.js: -------------------------------------------------------------------------------- 1 | angular.module('ajoslin.promise-tracker', []) 2 | 3 | .provider('promiseTracker', function() { 4 | var trackers = {}; 5 | 6 | this.$get = ['$q', '$timeout', function($q, $timeout) { 7 | function cancelTimeout(promise) { 8 | if (promise) { 9 | $timeout.cancel(promise); 10 | } 11 | } 12 | 13 | return function PromiseTracker(options) { 14 | options = options || {}; 15 | 16 | //Array of promises being tracked 17 | var tracked = []; 18 | var self = {}; 19 | 20 | //Allow an optional "minimum duration" that the tracker has to stay active for. 21 | var minDuration = options.minDuration; 22 | //Allow a delay that will stop the tracker from activating until that time is reached 23 | var activationDelay = options.activationDelay; 24 | 25 | var minDurationPromise; 26 | var activationDelayPromise; 27 | 28 | self.active = function() { 29 | //Even if we have a promise in our tracker, we aren't active until delay is elapsed 30 | if (activationDelayPromise) { 31 | return false; 32 | } 33 | return tracked.length > 0; 34 | }; 35 | 36 | self.tracking = function() { 37 | //Even if we aren't active, we could still have a promise in our tracker 38 | return tracked.length > 0; 39 | }; 40 | 41 | self.trackingCount = function() { 42 | return tracked.length; 43 | }; 44 | 45 | self.destroy = self.cancel = function() { 46 | minDurationPromise = cancelTimeout(minDurationPromise); 47 | activationDelayPromise = cancelTimeout(activationDelayPromise); 48 | for (var i=tracked.length-1; i>=0; i--) { 49 | tracked[i].resolve(); 50 | } 51 | tracked.length = 0; 52 | }; 53 | 54 | //Create a promise that will make our tracker active until it is resolved. 55 | // @return deferred - our deferred object that is being tracked 56 | self.createPromise = function() { 57 | var deferred = $q.defer(); 58 | tracked.push(deferred); 59 | 60 | //If the tracker was just inactive and this the first in the list of 61 | //promises, we reset our delay and minDuration 62 | //again. 63 | if (tracked.length === 1) { 64 | if (activationDelay) { 65 | activationDelayPromise = $timeout(function() { 66 | activationDelayPromise = cancelTimeout(activationDelayPromise); 67 | startMinDuration(); 68 | }, activationDelay); 69 | } else { 70 | startMinDuration(); 71 | } 72 | } 73 | 74 | deferred.promise.then(onDone(false), onDone(true)); 75 | 76 | return deferred; 77 | 78 | function startMinDuration() { 79 | if (minDuration) { 80 | minDurationPromise = $timeout(angular.noop, minDuration); 81 | } 82 | } 83 | 84 | //Create a callback for when this promise is done. It will remove our 85 | //tracked promise from the array if once minDuration is complete 86 | function onDone(isError) { 87 | return function(value) { 88 | (minDurationPromise || $q.when()).then(function() { 89 | var index = tracked.indexOf(deferred); 90 | tracked.splice(index, 1); 91 | 92 | //If this is the last promise, cleanup the timeouts 93 | //for activationDelay 94 | if (tracked.length === 0) { 95 | activationDelayPromise = cancelTimeout(activationDelayPromise); 96 | } 97 | }); 98 | }; 99 | } 100 | }; 101 | 102 | self.addPromise = function(promise) { 103 | if (Array.isArray(promise)) { 104 | return $q.all(promise.map(self.addPromise)); 105 | } 106 | 107 | promise = promise && (promise.$promise || promise) || {}; 108 | if (!promise.then) { 109 | throw new Error("promiseTracker#addPromise expects a promise object!"); 110 | } 111 | 112 | var deferred = self.createPromise(); 113 | 114 | //When given promise is done, resolve our created promise 115 | //Allow $then for angular-resource objects 116 | promise.then(function success(value) { 117 | deferred.resolve(value); 118 | return value; 119 | }, function error(value) { 120 | deferred.reject(value); 121 | return $q.reject(value); 122 | }); 123 | 124 | return deferred; 125 | }; 126 | 127 | return self; 128 | }; 129 | }]; 130 | }); 131 | -------------------------------------------------------------------------------- /test/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Mon Dec 23 2013 08:15:21 GMT-0500 (EST) 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | 7 | // base path, that will be used to resolve files and exclude 8 | basePath: '../', 9 | 10 | 11 | // frameworks to use 12 | frameworks: ['jasmine'], 13 | 14 | 15 | // list of files / patterns to load in the browser 16 | files: [ 17 | 'bower_components/angular/angular.js', 18 | 'bower_components/angular-mocks/angular-mocks.js', 19 | 'bower_components/angular-resource/angular-resource.js', 20 | 'promise-tracker.js', 21 | 'promise-tracker-http-interceptor.js', 22 | 'test/unit/**/*.js' 23 | ], 24 | 25 | 26 | // list of files to exclude 27 | exclude: [ 28 | 29 | ], 30 | 31 | 32 | // test results reporter to use 33 | // possible values: 'dots', 'progress', 'junit', 'growl', 'coverage' 34 | reporters: ['dots'], 35 | 36 | 37 | // web server port 38 | port: 9876, 39 | 40 | 41 | // enable / disable colors in the output (reporters and logs) 42 | colors: true, 43 | 44 | 45 | // level of logging 46 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 47 | logLevel: config.LOG_INFO, 48 | 49 | 50 | // enable / disable watching file and executing tests whenever any file changes 51 | autoWatch: true, 52 | 53 | 54 | // Start these browsers, currently available: 55 | // - Chrome 56 | // - ChromeCanary 57 | // - Firefox 58 | // - Opera (has to be installed with `npm install karma-opera-launcher`) 59 | // - Safari (only Mac; has to be installed with `npm install karma-safari-launcher`) 60 | // - PhantomJS 61 | // - IE (only Windows; has to be installed with `npm install karma-ie-launcher`) 62 | browsers: ['Chrome'], 63 | 64 | 65 | // If browser does not capture in given timeout [ms], kill it 66 | captureTimeout: 60000, 67 | 68 | 69 | // Continuous Integration mode 70 | // if true, it capture browsers, run tests and exit 71 | singleRun: false 72 | }); 73 | }; 74 | -------------------------------------------------------------------------------- /test/unit/interceptor.spec.js: -------------------------------------------------------------------------------- 1 | describe('http interceptor', function() { 2 | 3 | beforeEach(module('ajoslin.promise-tracker')); 4 | 5 | var http, promiseTracker, backend, q; 6 | beforeEach(inject(function($http, _promiseTracker_, $httpBackend, $q) { 7 | http = $http; 8 | promiseTracker = _promiseTracker_; 9 | $httpBackend.whenGET('/ok').respond(200); 10 | $httpBackend.whenGET('/error').respond(404); 11 | backend = $httpBackend; 12 | q = $q; 13 | })); 14 | 15 | function digest() { 16 | inject(function($rootScope) { $rootScope.$digest(); }); 17 | } 18 | 19 | it('should add a promise to tracking with http config option', function() { 20 | var tracker = promiseTracker(); 21 | var tracker2 = promiseTracker(); 22 | spyOn(tracker, 'createPromise').andCallThrough(); 23 | spyOn(tracker2, 'createPromise').andCallThrough(); 24 | 25 | http.get('/ok', { tracker: tracker }); 26 | digest(); 27 | expect(tracker.createPromise).toHaveBeenCalled(); 28 | 29 | tracker.createPromise.reset(); 30 | http.get('/ok', { tracker: [tracker,tracker2] }); 31 | digest(); 32 | expect(tracker.createPromise).toHaveBeenCalled(); 33 | expect(tracker2.createPromise).toHaveBeenCalled(); 34 | }); 35 | 36 | it('should resolve on good response', function(){ 37 | var tracker = promiseTracker(); 38 | var deferred = q.defer(); 39 | spyOn(tracker, 'createPromise').andCallFake(function() { 40 | return deferred; 41 | }); 42 | spyOn(deferred, 'resolve'); 43 | 44 | http.get('/ok', { tracker: tracker }); 45 | digest(); 46 | backend.flush(); 47 | expect(deferred.resolve).toHaveBeenCalled(); 48 | expect(deferred.resolve.mostRecentCall.args[0].status).toBe(200); 49 | }); 50 | 51 | it('should reject on error response', function(){ 52 | var tracker = promiseTracker(); 53 | var deferred = q.defer(); 54 | spyOn(tracker, 'createPromise').andCallFake(function() { 55 | return deferred; 56 | }); 57 | spyOn(deferred, 'reject'); 58 | 59 | http.get('/error', { tracker: tracker }); 60 | digest(); 61 | backend.flush(); 62 | expect(deferred.reject).toHaveBeenCalled(); 63 | expect(deferred.reject.mostRecentCall.args[0].status).toBe(404); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /test/unit/provider.spec.js: -------------------------------------------------------------------------------- 1 | describe('promiseTracker provider', function() { 2 | beforeEach(module('ajoslin.promise-tracker')); 3 | 4 | var promiseTracker, timeout, q; 5 | beforeEach(inject(function(_promiseTracker_, $timeout, $q) { 6 | promiseTracker = _promiseTracker_; 7 | timeout = $timeout; 8 | q = $q; 9 | })); 10 | 11 | function digest() { 12 | inject(function($rootScope) { $rootScope.$digest(); }); 13 | } 14 | 15 | it('should create a tracker with api', function() { 16 | var tracker = new promiseTracker(); 17 | expect(typeof tracker.addPromise).toBe('function'); 18 | expect(typeof tracker.createPromise).toBe('function'); 19 | expect(typeof tracker.destroy).toBe('function'); 20 | expect(typeof tracker.cancel).toBe('function'); 21 | }); 22 | 23 | it('should create a tracker even if no `new`', function() { 24 | var tracker = promiseTracker(); 25 | expect(typeof tracker.addPromise).toBe('function'); 26 | }); 27 | 28 | it('should not be active by default', function() { 29 | expect(promiseTracker().active()).toBe(false); 30 | }); 31 | 32 | it('should not be tracking by default', function() { 33 | expect(promiseTracker().tracking()).toBe(false); 34 | }); 35 | 36 | describe('addPromise', function() { 37 | 38 | it('should error with object', function() { 39 | expect(function() { promiseTracker().addPromise({}); }).toThrow(); 40 | }); 41 | 42 | it('should error with deferred', function() { 43 | expect(function() { promiseTracker().addPromise(q.defer()); }).toThrow(); 44 | }); 45 | 46 | it('should not error with then, $then, $promise.then', function() { 47 | promiseTracker().addPromise(q.defer().promise); 48 | promiseTracker().addPromise({ $promise: q.defer().promise } ); 49 | }); 50 | 51 | it('should return promise from createPromise', function() { 52 | var tracker = promiseTracker(); 53 | var promise = q.defer().promise; 54 | var created = q.defer(); 55 | spyOn(tracker, 'createPromise').andCallFake(function() { 56 | return created; 57 | }); 58 | var ret = tracker.addPromise(promise); 59 | expect(ret).toBe(created); 60 | }); 61 | 62 | it('should resolve returned promise when passed in promise is resolved', function() { 63 | var tracker = promiseTracker(); 64 | var deferred = q.defer(); 65 | var trackerPromise = tracker.addPromise(deferred.promise); 66 | spyOn(trackerPromise, 'resolve'); 67 | deferred.resolve(1); 68 | digest(); 69 | expect(trackerPromise.resolve).toHaveBeenCalledWith(1); 70 | }); 71 | 72 | it('should reject returned promise when passed in promise is rejected', function() { 73 | var tracker = promiseTracker(); 74 | var deferred = q.defer(); 75 | var trackerPromise = tracker.addPromise(deferred.promise); 76 | spyOn(trackerPromise, 'reject'); 77 | deferred.reject(2); 78 | digest(); 79 | expect(trackerPromise.reject).toHaveBeenCalledWith(2); 80 | }); 81 | 82 | it('should start tracking with then, $then, $promise.then', function() { 83 | var tracker = promiseTracker(); 84 | tracker.addPromise(q.defer().promise); 85 | expect(tracker.tracking()).toBe(true); 86 | 87 | tracker = promiseTracker(); 88 | tracker.addPromise(q.defer().promise); 89 | expect(tracker.tracking()).toBe(true); 90 | 91 | tracker = promiseTracker(); 92 | tracker.addPromise({ $promise: q.defer().promise }); 93 | expect(tracker.tracking()).toBe(true); 94 | }); 95 | 96 | }); 97 | 98 | describe('createPromise', function() { 99 | 100 | it('should return a deferred', function() { 101 | expect(promiseTracker().createPromise().promise.then).toBeTruthy(); 102 | }); 103 | 104 | it('should set active to true when promise is added', function() { 105 | var tracker = promiseTracker(); 106 | tracker.createPromise(); 107 | expect(tracker.active()).toBe(true); 108 | }); 109 | 110 | it('should set active to true when promises are added', function() { 111 | var tracker = promiseTracker(); 112 | tracker.createPromise(); 113 | tracker.createPromise(); 114 | expect(tracker.active()).toBe(true); 115 | }); 116 | 117 | it('should set active to false when promises are added and resolved/rejected', function() { 118 | var tracker = promiseTracker(); 119 | var p1 = tracker.createPromise(); 120 | var p2 = tracker.createPromise(); 121 | expect(tracker.active()).toBe(true); 122 | p1.resolve(); 123 | digest(); 124 | expect(tracker.active()).toBe(true); 125 | p2.reject(); 126 | digest(); 127 | expect(tracker.active()).toBe(false); 128 | }); 129 | 130 | it('should set tracking to true when promise is added', function() { 131 | var tracker = promiseTracker(); 132 | tracker.createPromise(); 133 | expect(tracker.tracking()).toBe(true); 134 | }); 135 | 136 | it('should set tracking to true when promises are added', function() { 137 | var tracker = promiseTracker(); 138 | tracker.createPromise(); 139 | tracker.createPromise(); 140 | expect(tracker.tracking()).toBe(true); 141 | }); 142 | 143 | it('should set tracking to false when promises are added and resolved/rejected', function() { 144 | var tracker = promiseTracker(); 145 | var p1 = tracker.createPromise(); 146 | var p2 = tracker.createPromise(); 147 | expect(tracker.tracking()).toBe(true); 148 | p1.resolve(); 149 | digest(); 150 | expect(tracker.tracking()).toBe(true); 151 | p2.reject(); 152 | digest(); 153 | expect(tracker.tracking()).toBe(false); 154 | }); 155 | 156 | }); 157 | 158 | it('cancel should deactivate and resolve all promises', function() { 159 | var tracker = promiseTracker(); 160 | var p1 = tracker.createPromise(); 161 | expect(tracker.active()).toBe(true); 162 | spyOn(p1, 'resolve'); 163 | tracker.cancel(); 164 | expect(p1.resolve).toHaveBeenCalled(); 165 | expect(tracker.active()).toBe(false); 166 | expect(tracker.tracking()).toBe(false); 167 | }); 168 | 169 | it('destroy should be cancel', function() { 170 | var tracker = promiseTracker(); 171 | expect(tracker.destroy).toBe(tracker.cancel); 172 | }); 173 | 174 | describe('activationDelay', function() { 175 | 176 | it('should not be active() until delay is over', function() { 177 | var tracker = promiseTracker({ activationDelay: 1000 }); 178 | tracker.createPromise(); 179 | 180 | //Should not be active due to delay 181 | expect(tracker.active()).toBe(false); 182 | tracker.createPromise(); 183 | expect(tracker.active()).toBe(false); 184 | 185 | //Flush, it should be active 186 | timeout.flush(); 187 | expect(tracker.active()).toBe(true); 188 | }); 189 | 190 | it('should be tracking irrespective of the activation delay', function() { 191 | var tracker = promiseTracker({ activationDelay: 1000 }); 192 | tracker.createPromise(); 193 | 194 | //Should be tracking 195 | expect(tracker.tracking()).toBe(true); 196 | tracker.createPromise(); 197 | expect(tracker.tracking()).toBe(true); 198 | 199 | //Flush, it should be tracking 200 | timeout.flush(); 201 | expect(tracker.tracking()).toBe(true); 202 | }); 203 | 204 | }); 205 | 206 | describe('minDuration', function() { 207 | 208 | it('should be active() for at least minDuration', function() { 209 | var tracker = promiseTracker({ minDuration: 1000 }); 210 | var p1 = tracker.createPromise(); 211 | expect(tracker.active()).toBe(true); 212 | p1.resolve(); 213 | digest(); 214 | //Should still be active until minDuration timeout elapses 215 | expect(tracker.active()).toBe(true); 216 | timeout.flush(); 217 | expect(tracker.active()).toBe(false); 218 | }); 219 | 220 | it('should not deactivate if there is still another promise active', function() { 221 | var tracker = promiseTracker({ minDuration: 1000 }); 222 | var p1 = tracker.createPromise(); 223 | expect(tracker.active()).toBe(true); 224 | p1.resolve(); 225 | digest(); 226 | //Should still be active until minDuration timeout elapses 227 | expect(tracker.active()).toBe(true); 228 | var p2 = tracker.createPromise(); 229 | timeout.flush(); 230 | expect(tracker.active()).toBe(true); 231 | p2.resolve(); 232 | digest(); 233 | expect(tracker.active()).toBe(false); 234 | }); 235 | 236 | it('should be tracking for at least minDuration', function() { 237 | var tracker = promiseTracker({ minDuration: 1000 }); 238 | var p1 = tracker.createPromise(); 239 | expect(tracker.tracking()).toBe(true); 240 | p1.resolve(); 241 | digest(); 242 | //Should still be tracking until minDuration timeout elapses 243 | expect(tracker.tracking()).toBe(true); 244 | timeout.flush(); 245 | expect(tracker.tracking()).toBe(false); 246 | }); 247 | 248 | it('should continue tracking if there is still another promise active', function() { 249 | var tracker = promiseTracker({ minDuration: 1000 }); 250 | var p1 = tracker.createPromise(); 251 | expect(tracker.tracking()).toBe(true); 252 | p1.resolve(); 253 | digest(); 254 | //Should still be tracking until minDuration timeout elapses 255 | expect(tracker.tracking()).toBe(true); 256 | var p2 = tracker.createPromise(); 257 | timeout.flush(); 258 | expect(tracker.tracking()).toBe(true); 259 | p2.resolve(); 260 | digest(); 261 | expect(tracker.tracking()).toBe(false); 262 | }); 263 | 264 | }); 265 | 266 | describe('minDuration + activationDelay', function() { 267 | 268 | it('should delay, be active, wait until duration, then be inactive', function() { 269 | var tracker = promiseTracker({ minDuration: 500, activationDelay: 250 }); 270 | var p1 = tracker.createPromise(); 271 | expect(tracker.active()).toBe(false); 272 | timeout.flush(); 273 | expect(tracker.active()).toBe(true); 274 | p1.resolve(); 275 | digest(); 276 | expect(tracker.active()).toBe(true); 277 | timeout.flush(); 278 | expect(tracker.active()).toBe(false); 279 | }); 280 | 281 | it('should delay, be tracking, wait until duration, then be not tracking', function() { 282 | var tracker = promiseTracker({ minDuration: 500, activationDelay: 250 }); 283 | expect(tracker.tracking()).toBe(false); 284 | var p1 = tracker.createPromise(); 285 | expect(tracker.tracking()).toBe(true); 286 | timeout.flush(); 287 | expect(tracker.tracking()).toBe(true); 288 | p1.resolve(); 289 | digest(); 290 | expect(tracker.tracking()).toBe(true); 291 | timeout.flush(); 292 | expect(tracker.tracking()).toBe(false); 293 | }); 294 | 295 | }); 296 | 297 | }); 298 | --------------------------------------------------------------------------------