Repository: ajoslin/angular-promise-tracker Branch: master Commit: 99e0064952a9 Files: 12 Total size: 28.3 KB Directory structure: gitextract_3ebtioif/ ├── .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 ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ root = true [*] indent_style = space indent_size = 2 end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true ================================================ FILE: .gitignore ================================================ *.swp npm-debug.log .DS_Store demo bower_components node_modules dist ================================================ FILE: .travis.yml ================================================ --- language: node_js node_js: - 0.10 env: before_script: - export DISPLAY=:99.0 - sh -e /etc/init.d/xvfb start - npm install -g grunt-cli bower - npm link - bower install script: - grunt ================================================ FILE: Gruntfile.js ================================================ module.exports = function (grunt) { require('load-grunt-tasks')(grunt); grunt.initConfig({ dist: 'dist', pkgFile: 'bower.json', pkg: grunt.file.readJSON('bower.json'), watch: { scripts: { files: ['src/**/*.js', 'test/unit/**/*.js'], tasks: ['karma:watch:run'] }, gruntfile: { files: ['Gruntfile.js'], tasks: ['jshint'] } }, jshint: { all: ['Gruntfile.js', 'src/**/*.js'], options: { eqeqeq: true, globals: { angular: true } } }, clean: ['demo/**/*'], karma: { watch: { configFile: 'test/karma.conf.js', background: true, }, single: { configFile: 'test/karma.conf.js', singleRun: true, browsers: [process.env.TRAVIS ? 'Firefox' : 'Chrome'], } }, changelog: { options: { dest: 'CHANGELOG.md' } }, }); grunt.registerTask('dev', ['karma:watch', 'watch']); grunt.registerTask('default', ['jshint', 'test']); grunt.registerTask('test', ['karma:single']); }; ================================================ FILE: README.md ================================================ angular-promise-tracker ======================= > **Version**: 2.0 (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).) [![Build Status](https://travis-ci.org/ajoslin/angular-promise-tracker.png)](https://travis-ci.org/ajoslin/angular-promise-tracker) Small, feature filled library used to easily add spinners or general promise/request tracking to your angular app. * [Quick Start](#quick-start) * [API Documentation](#api-documentation) * [Changes](https://github.com/ajoslin/angular-promise-tracker/tree/master/CHANGELOG.md) * [License](#license) ## Quick Start 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. [Play with this example on plunkr](http://plnkr.co/edit/PrO2ou9b1uANbeGoX6eB?p=preview) ```sh $ bower install angular-promise-tracker ``` ```html
Loading...
``` ```js angular.module('myApp', ['ajoslin.promise-tracker']) .controller('MainCtrl', function($scope, $http, $timeout, promiseTracker) { //Create a new tracker $scope.loadingTracker = promiseTracker(); //use `addPromise` to add any old promise to our tracker $scope.delaySomething = function() { var promise = $timeout(function() { alert('Delayed something!'); }, 1000); $scope.loadingTracker.addPromise(promise); }; //use `tracker:` shortcut in $http config to link our http promise to a tracker //This shortcut is included in promise-tracker-http-interceptor.js $scope.fetchSomething = function(id) { return $http.get('/something', { tracker: $scope.loadingTracker }).then(function(response) { alert('Fetched something! ' + response.data); }); }; }); ``` ## API Documentation ### Service `promiseTracker` * **`tracker` promiseTracker([options])** Creates and returns a new promiseTracker. Options can be given as an object, with the following allowed values: - `activationDelay` `{Number}` - Number of milliseconds that an added promise needs to be pending before this tracker is active. * 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. - `minDuration` `{Number}` - Minimum number of milliseconds that a tracker will stay active. * Usage example: You want a loading spinner to always show up for at least 750ms. You put `{minDuration: 750}` in options. Often you want a global promiseTracker (eg to show a loading screen); one easy way is to put the tracker on your $rootScope: ```js app.run(function($rootScope, promiseTracker) { $rootScope.loadingTracker = promiseTracker(); }); ``` ### Instantiated promiseTracker Example: `var myTracker = promiseTracker({ activationDelay: 500, minDuration: 750 });` * **`boolean` tracker.active()** 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. * **`boolean` tracker.tracking()** 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`. * **`number` tracker.trackingCount()** The count of promises currently being tracked. * **`promise` tracker.addPromise(promise)** Add any arbitrary promise to tracker. `tracker.active()` will be true until `promise` is resolved or rejected. - `promise` `{object}` - Promise to add Usage Example: ```js var promise = $timeout(doSomethingCool, 1000); myTracker.addPromise(promise); console.log(myTracker.active()); // => true //1000 milliseconds later... console.log(myTracker.active()); // => false ``` * **`promise` tracker.createPromise()** Creates and returns a new deferred object that is tracked by our promiseTracker. Usage Example: ```js var deferred = myTracker.createPromise() console.log(myTracker.active()); // => true deferred.resolve(); console.log(myTracker.active()); // => false ``` * **`void` tracker.cancel()** Causes a tracker to immediately become inactive and stop tracking all current promises. ### **`$http` Sugar** **Requires promise-tracker-http-interceptor.js** * **Any $http call's `config` parameter can have a `tracker` field. Examples:** ```js //Add $http promise to tracker with id 'myTracker' $http('/banana', { tracker: myPromiseTrackerInstance }) ``` ```js //Add $http promise to both 'tracker1' and 'tracker2' $http.post('/elephant', {some: 'data'}, { tracker: [myFirstTracker, mySecondTracker] }) ``` ## More Examples * Do something whenever the tracker's active state changes ```js angular.module('app', ['ajoslin.promise-tracker']) .factory('myTracker', function (promiseTracker) { return promiseTracker(); }) .controller('AppCtrl', function ($rootScope, myTracker) { $rootScope.$watch(myTracker.active, function (isActive) { //doSomething() }); }); ``` ## Development * Install karma & grunt with `npm install -g karma grunt-cli` to build & test * Install local dependencies with `bower install && npm install` * Run `grunt` to lint, test, build the code, and build the docs site * Run `grunt dev` to watch and re-test on changes #### New Versions ## License > Public Domain Mark angular-promise-tracker by Andy Joslin is free of known copyright restrictions. ================================================ FILE: bower.json ================================================ { "author": "Andy Joslin", "name": "promise-tracker", "description": "Easily add spinners or general request tracking to your angular app.", "version": "2.1.0", "homepage": "http://github.com/ajoslin/angular-promise-tracker", "repository": { "type": "git", "url": "git://github.com/ajoslin/angular-promise-tracker" }, "license": "Public Domain", "main": "./promise-tracker.js", "ignore": [ "CHANGELOG.md", "Gruntfile.js", "bower.json", "bower_components", "dist", "node_modules", "package.json", "src", "test" ], "files": [ "promise-tracker.js", "promise-tracker-http-interceptor.js" ], "dependencies": { "angular": ">=1.3.0" }, "devDependencies": { "angular-mocks": ">=1.3.0", "angular-resource": ">=1.3.0" } } ================================================ FILE: package.json ================================================ { "name": "angular-promise-tracker", "version": "2.2.2", "main": "promise-tracker.js", "author": { "name": "Andy Joslin" }, "repository": { "url": "git://github.com/ajoslin/angular-promise-tracker.git" }, "homepage": "https://github.com/ajoslin/angular-promise-tracker", "dependencies": { "angular": ">=1.3.0" }, "devDependencies": { "load-grunt-tasks": "~0.2.1", "semver": "~2.2.1", "grunt-contrib-clean": "~0.5.0", "grunt-contrib-jshint": "~0.7.2", "grunt": "~0.4.2", "grunt-contrib-watch": "~0.5.3", "grunt-contrib-uglify": "~0.2.7", "karma-script-launcher": "~0.1.0", "karma-chrome-launcher": "~0.1.2", "karma-html2js-preprocessor": "~0.1.0", "karma-firefox-launcher": "~0.1.2", "karma-jasmine": "~0.1.5", "requirejs": "~2.1.9", "karma-requirejs": "~0.2.1", "karma-coffee-preprocessor": "~0.1.1", "karma-phantomjs-launcher": "~0.1.1", "karma": "~0.10.8", "grunt-karma": "~0.6.2", "grunt-conventional-changelog": "~1.1.0" } } ================================================ FILE: promise-tracker-http-interceptor.js ================================================ /* * promise-tracker - v2.1.0 - 2014-11-15 * http://github.com/ajoslin/angular-promise-tracker * Created by Andy Joslin; Licensed under Public Domain */ (function() { angular.module('ajoslin.promise-tracker') .config(['$httpProvider', function($httpProvider) { $httpProvider.interceptors.push(['$q', 'promiseTracker', function($q, promiseTracker) { return { request: function(config) { if (config.tracker) { if (!angular.isArray(config.tracker)) { config.tracker = [config.tracker]; } config.$promiseTrackerDeferred = config.$promiseTrackerDeferred || []; angular.forEach(config.tracker, function(tracker) { var deferred = tracker.createPromise(); config.$promiseTrackerDeferred.push(deferred); }); } return $q.when(config); }, response: function(response) { if (response.config && response.config.$promiseTrackerDeferred) { angular.forEach(response.config.$promiseTrackerDeferred, function(deferred) { deferred.resolve(response); }); } return $q.when(response); }, responseError: function(response) { if (response.config && response.config.$promiseTrackerDeferred) { angular.forEach(response.config.$promiseTrackerDeferred, function(deferred) { deferred.reject(response); }); } return $q.reject(response); } }; }]); }]); }()); ================================================ FILE: promise-tracker.js ================================================ angular.module('ajoslin.promise-tracker', []) .provider('promiseTracker', function() { var trackers = {}; this.$get = ['$q', '$timeout', function($q, $timeout) { function cancelTimeout(promise) { if (promise) { $timeout.cancel(promise); } } return function PromiseTracker(options) { options = options || {}; //Array of promises being tracked var tracked = []; var self = {}; //Allow an optional "minimum duration" that the tracker has to stay active for. var minDuration = options.minDuration; //Allow a delay that will stop the tracker from activating until that time is reached var activationDelay = options.activationDelay; var minDurationPromise; var activationDelayPromise; self.active = function() { //Even if we have a promise in our tracker, we aren't active until delay is elapsed if (activationDelayPromise) { return false; } return tracked.length > 0; }; self.tracking = function() { //Even if we aren't active, we could still have a promise in our tracker return tracked.length > 0; }; self.trackingCount = function() { return tracked.length; }; self.destroy = self.cancel = function() { minDurationPromise = cancelTimeout(minDurationPromise); activationDelayPromise = cancelTimeout(activationDelayPromise); for (var i=tracked.length-1; i>=0; i--) { tracked[i].resolve(); } tracked.length = 0; }; //Create a promise that will make our tracker active until it is resolved. // @return deferred - our deferred object that is being tracked self.createPromise = function() { var deferred = $q.defer(); tracked.push(deferred); //If the tracker was just inactive and this the first in the list of //promises, we reset our delay and minDuration //again. if (tracked.length === 1) { if (activationDelay) { activationDelayPromise = $timeout(function() { activationDelayPromise = cancelTimeout(activationDelayPromise); startMinDuration(); }, activationDelay); } else { startMinDuration(); } } deferred.promise.then(onDone(false), onDone(true)); return deferred; function startMinDuration() { if (minDuration) { minDurationPromise = $timeout(angular.noop, minDuration); } } //Create a callback for when this promise is done. It will remove our //tracked promise from the array if once minDuration is complete function onDone(isError) { return function(value) { (minDurationPromise || $q.when()).then(function() { var index = tracked.indexOf(deferred); tracked.splice(index, 1); //If this is the last promise, cleanup the timeouts //for activationDelay if (tracked.length === 0) { activationDelayPromise = cancelTimeout(activationDelayPromise); } }); }; } }; self.addPromise = function(promise) { if (Array.isArray(promise)) { return $q.all(promise.map(self.addPromise)); } promise = promise && (promise.$promise || promise) || {}; if (!promise.then) { throw new Error("promiseTracker#addPromise expects a promise object!"); } var deferred = self.createPromise(); //When given promise is done, resolve our created promise //Allow $then for angular-resource objects promise.then(function success(value) { deferred.resolve(value); return value; }, function error(value) { deferred.reject(value); return $q.reject(value); }); return deferred; }; return self; }; }]; }); ================================================ FILE: test/karma.conf.js ================================================ // Karma configuration // Generated on Mon Dec 23 2013 08:15:21 GMT-0500 (EST) module.exports = function(config) { config.set({ // base path, that will be used to resolve files and exclude basePath: '../', // frameworks to use frameworks: ['jasmine'], // list of files / patterns to load in the browser files: [ 'bower_components/angular/angular.js', 'bower_components/angular-mocks/angular-mocks.js', 'bower_components/angular-resource/angular-resource.js', 'promise-tracker.js', 'promise-tracker-http-interceptor.js', 'test/unit/**/*.js' ], // list of files to exclude exclude: [ ], // test results reporter to use // possible values: 'dots', 'progress', 'junit', 'growl', 'coverage' reporters: ['dots'], // web server port port: 9876, // enable / disable colors in the output (reporters and logs) colors: true, // level of logging // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG logLevel: config.LOG_INFO, // enable / disable watching file and executing tests whenever any file changes autoWatch: true, // Start these browsers, currently available: // - Chrome // - ChromeCanary // - Firefox // - Opera (has to be installed with `npm install karma-opera-launcher`) // - Safari (only Mac; has to be installed with `npm install karma-safari-launcher`) // - PhantomJS // - IE (only Windows; has to be installed with `npm install karma-ie-launcher`) browsers: ['Chrome'], // If browser does not capture in given timeout [ms], kill it captureTimeout: 60000, // Continuous Integration mode // if true, it capture browsers, run tests and exit singleRun: false }); }; ================================================ FILE: test/unit/interceptor.spec.js ================================================ describe('http interceptor', function() { beforeEach(module('ajoslin.promise-tracker')); var http, promiseTracker, backend, q; beforeEach(inject(function($http, _promiseTracker_, $httpBackend, $q) { http = $http; promiseTracker = _promiseTracker_; $httpBackend.whenGET('/ok').respond(200); $httpBackend.whenGET('/error').respond(404); backend = $httpBackend; q = $q; })); function digest() { inject(function($rootScope) { $rootScope.$digest(); }); } it('should add a promise to tracking with http config option', function() { var tracker = promiseTracker(); var tracker2 = promiseTracker(); spyOn(tracker, 'createPromise').andCallThrough(); spyOn(tracker2, 'createPromise').andCallThrough(); http.get('/ok', { tracker: tracker }); digest(); expect(tracker.createPromise).toHaveBeenCalled(); tracker.createPromise.reset(); http.get('/ok', { tracker: [tracker,tracker2] }); digest(); expect(tracker.createPromise).toHaveBeenCalled(); expect(tracker2.createPromise).toHaveBeenCalled(); }); it('should resolve on good response', function(){ var tracker = promiseTracker(); var deferred = q.defer(); spyOn(tracker, 'createPromise').andCallFake(function() { return deferred; }); spyOn(deferred, 'resolve'); http.get('/ok', { tracker: tracker }); digest(); backend.flush(); expect(deferred.resolve).toHaveBeenCalled(); expect(deferred.resolve.mostRecentCall.args[0].status).toBe(200); }); it('should reject on error response', function(){ var tracker = promiseTracker(); var deferred = q.defer(); spyOn(tracker, 'createPromise').andCallFake(function() { return deferred; }); spyOn(deferred, 'reject'); http.get('/error', { tracker: tracker }); digest(); backend.flush(); expect(deferred.reject).toHaveBeenCalled(); expect(deferred.reject.mostRecentCall.args[0].status).toBe(404); }); }); ================================================ FILE: test/unit/provider.spec.js ================================================ describe('promiseTracker provider', function() { beforeEach(module('ajoslin.promise-tracker')); var promiseTracker, timeout, q; beforeEach(inject(function(_promiseTracker_, $timeout, $q) { promiseTracker = _promiseTracker_; timeout = $timeout; q = $q; })); function digest() { inject(function($rootScope) { $rootScope.$digest(); }); } it('should create a tracker with api', function() { var tracker = new promiseTracker(); expect(typeof tracker.addPromise).toBe('function'); expect(typeof tracker.createPromise).toBe('function'); expect(typeof tracker.destroy).toBe('function'); expect(typeof tracker.cancel).toBe('function'); }); it('should create a tracker even if no `new`', function() { var tracker = promiseTracker(); expect(typeof tracker.addPromise).toBe('function'); }); it('should not be active by default', function() { expect(promiseTracker().active()).toBe(false); }); it('should not be tracking by default', function() { expect(promiseTracker().tracking()).toBe(false); }); describe('addPromise', function() { it('should error with object', function() { expect(function() { promiseTracker().addPromise({}); }).toThrow(); }); it('should error with deferred', function() { expect(function() { promiseTracker().addPromise(q.defer()); }).toThrow(); }); it('should not error with then, $then, $promise.then', function() { promiseTracker().addPromise(q.defer().promise); promiseTracker().addPromise({ $promise: q.defer().promise } ); }); it('should return promise from createPromise', function() { var tracker = promiseTracker(); var promise = q.defer().promise; var created = q.defer(); spyOn(tracker, 'createPromise').andCallFake(function() { return created; }); var ret = tracker.addPromise(promise); expect(ret).toBe(created); }); it('should resolve returned promise when passed in promise is resolved', function() { var tracker = promiseTracker(); var deferred = q.defer(); var trackerPromise = tracker.addPromise(deferred.promise); spyOn(trackerPromise, 'resolve'); deferred.resolve(1); digest(); expect(trackerPromise.resolve).toHaveBeenCalledWith(1); }); it('should reject returned promise when passed in promise is rejected', function() { var tracker = promiseTracker(); var deferred = q.defer(); var trackerPromise = tracker.addPromise(deferred.promise); spyOn(trackerPromise, 'reject'); deferred.reject(2); digest(); expect(trackerPromise.reject).toHaveBeenCalledWith(2); }); it('should start tracking with then, $then, $promise.then', function() { var tracker = promiseTracker(); tracker.addPromise(q.defer().promise); expect(tracker.tracking()).toBe(true); tracker = promiseTracker(); tracker.addPromise(q.defer().promise); expect(tracker.tracking()).toBe(true); tracker = promiseTracker(); tracker.addPromise({ $promise: q.defer().promise }); expect(tracker.tracking()).toBe(true); }); }); describe('createPromise', function() { it('should return a deferred', function() { expect(promiseTracker().createPromise().promise.then).toBeTruthy(); }); it('should set active to true when promise is added', function() { var tracker = promiseTracker(); tracker.createPromise(); expect(tracker.active()).toBe(true); }); it('should set active to true when promises are added', function() { var tracker = promiseTracker(); tracker.createPromise(); tracker.createPromise(); expect(tracker.active()).toBe(true); }); it('should set active to false when promises are added and resolved/rejected', function() { var tracker = promiseTracker(); var p1 = tracker.createPromise(); var p2 = tracker.createPromise(); expect(tracker.active()).toBe(true); p1.resolve(); digest(); expect(tracker.active()).toBe(true); p2.reject(); digest(); expect(tracker.active()).toBe(false); }); it('should set tracking to true when promise is added', function() { var tracker = promiseTracker(); tracker.createPromise(); expect(tracker.tracking()).toBe(true); }); it('should set tracking to true when promises are added', function() { var tracker = promiseTracker(); tracker.createPromise(); tracker.createPromise(); expect(tracker.tracking()).toBe(true); }); it('should set tracking to false when promises are added and resolved/rejected', function() { var tracker = promiseTracker(); var p1 = tracker.createPromise(); var p2 = tracker.createPromise(); expect(tracker.tracking()).toBe(true); p1.resolve(); digest(); expect(tracker.tracking()).toBe(true); p2.reject(); digest(); expect(tracker.tracking()).toBe(false); }); }); it('cancel should deactivate and resolve all promises', function() { var tracker = promiseTracker(); var p1 = tracker.createPromise(); expect(tracker.active()).toBe(true); spyOn(p1, 'resolve'); tracker.cancel(); expect(p1.resolve).toHaveBeenCalled(); expect(tracker.active()).toBe(false); expect(tracker.tracking()).toBe(false); }); it('destroy should be cancel', function() { var tracker = promiseTracker(); expect(tracker.destroy).toBe(tracker.cancel); }); describe('activationDelay', function() { it('should not be active() until delay is over', function() { var tracker = promiseTracker({ activationDelay: 1000 }); tracker.createPromise(); //Should not be active due to delay expect(tracker.active()).toBe(false); tracker.createPromise(); expect(tracker.active()).toBe(false); //Flush, it should be active timeout.flush(); expect(tracker.active()).toBe(true); }); it('should be tracking irrespective of the activation delay', function() { var tracker = promiseTracker({ activationDelay: 1000 }); tracker.createPromise(); //Should be tracking expect(tracker.tracking()).toBe(true); tracker.createPromise(); expect(tracker.tracking()).toBe(true); //Flush, it should be tracking timeout.flush(); expect(tracker.tracking()).toBe(true); }); }); describe('minDuration', function() { it('should be active() for at least minDuration', function() { var tracker = promiseTracker({ minDuration: 1000 }); var p1 = tracker.createPromise(); expect(tracker.active()).toBe(true); p1.resolve(); digest(); //Should still be active until minDuration timeout elapses expect(tracker.active()).toBe(true); timeout.flush(); expect(tracker.active()).toBe(false); }); it('should not deactivate if there is still another promise active', function() { var tracker = promiseTracker({ minDuration: 1000 }); var p1 = tracker.createPromise(); expect(tracker.active()).toBe(true); p1.resolve(); digest(); //Should still be active until minDuration timeout elapses expect(tracker.active()).toBe(true); var p2 = tracker.createPromise(); timeout.flush(); expect(tracker.active()).toBe(true); p2.resolve(); digest(); expect(tracker.active()).toBe(false); }); it('should be tracking for at least minDuration', function() { var tracker = promiseTracker({ minDuration: 1000 }); var p1 = tracker.createPromise(); expect(tracker.tracking()).toBe(true); p1.resolve(); digest(); //Should still be tracking until minDuration timeout elapses expect(tracker.tracking()).toBe(true); timeout.flush(); expect(tracker.tracking()).toBe(false); }); it('should continue tracking if there is still another promise active', function() { var tracker = promiseTracker({ minDuration: 1000 }); var p1 = tracker.createPromise(); expect(tracker.tracking()).toBe(true); p1.resolve(); digest(); //Should still be tracking until minDuration timeout elapses expect(tracker.tracking()).toBe(true); var p2 = tracker.createPromise(); timeout.flush(); expect(tracker.tracking()).toBe(true); p2.resolve(); digest(); expect(tracker.tracking()).toBe(false); }); }); describe('minDuration + activationDelay', function() { it('should delay, be active, wait until duration, then be inactive', function() { var tracker = promiseTracker({ minDuration: 500, activationDelay: 250 }); var p1 = tracker.createPromise(); expect(tracker.active()).toBe(false); timeout.flush(); expect(tracker.active()).toBe(true); p1.resolve(); digest(); expect(tracker.active()).toBe(true); timeout.flush(); expect(tracker.active()).toBe(false); }); it('should delay, be tracking, wait until duration, then be not tracking', function() { var tracker = promiseTracker({ minDuration: 500, activationDelay: 250 }); expect(tracker.tracking()).toBe(false); var p1 = tracker.createPromise(); expect(tracker.tracking()).toBe(true); timeout.flush(); expect(tracker.tracking()).toBe(true); p1.resolve(); digest(); expect(tracker.tracking()).toBe(true); timeout.flush(); expect(tracker.tracking()).toBe(false); }); }); });