From 74cda29f8d4d9619f06cb9f350de97304e578000 Mon Sep 17 00:00:00 2001 From: Matias Pando Date: Tue, 10 Feb 2015 17:33:16 -0300 Subject: [PATCH 1/5] Setting up karma coverage --- karma.conf.js | 7 ++++++- test/unit/controllers/controllersSpec.js | 26 +++++++++++++++++++----- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/karma.conf.js b/karma.conf.js index a194c5a3a..0390efd96 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -71,14 +71,19 @@ module.exports = function(config) { // preprocess matching files before serving them to the browser // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor preprocessors: { + 'js/controllers/*.js': ['coverage'] + }, + coverageReporter: { + type: 'html', + dir: 'coverage/' }, // test results reporter to use // possible values: 'dots', 'progress' // available reporters: https://npmjs.org/browse/keyword/karma-reporter - reporters: ['progress'], + reporters: ['progress', 'coverage'], // web server port diff --git a/test/unit/controllers/controllersSpec.js b/test/unit/controllers/controllersSpec.js index ca9436c26..f544910c3 100644 --- a/test/unit/controllers/controllersSpec.js +++ b/test/unit/controllers/controllersSpec.js @@ -112,6 +112,8 @@ describe("Unit: Controllers", function() { iden.getWalletById = sinon.stub().returns(w); iden.getName = sinon.stub().returns('name'); iden.deleteWallet = sinon.stub(); + iden.close = sinon.stub().returns(null); + $rootScope.wallet = w; $rootScope.iden = iden; @@ -479,7 +481,7 @@ describe("Unit: Controllers", function() { }); - describe.skip("Unit: Sidebar Controller", function() { + describe("Unit: Sidebar Controller", function() { beforeEach(inject(function($controller, $rootScope) { rootScope = $rootScope; scope = $rootScope.$new(); @@ -488,10 +490,10 @@ describe("Unit: Controllers", function() { }); })); - it('should return an array of n undefined elements', function() { - var n = 5; - var array = scope.getNumber(n); - expect(array.length).equal(n); + it.only('should call sign out', function() { + + scope.signout(); + rootScope.iden.close.calledOnce.should.be.true; }); }); @@ -568,6 +570,20 @@ describe("Unit: Controllers", function() { }); }); + describe.only('SignOut Controller', function() { + var what; + beforeEach(inject(function($controller, $rootScope) { + scope = $rootScope.$new(); + what = $controller('signOutController', { + $scope: scope, + }); + })); + + it('should exist', function() { + should.exist(what); + }); + }); + describe('Settings Controller', function() { var what; beforeEach(inject(function($controller, $rootScope) { From 312d70714592875e33c5d47df99fc4b57f219998 Mon Sep 17 00:00:00 2001 From: Matias Pando Date: Thu, 12 Feb 2015 10:20:14 -0300 Subject: [PATCH 2/5] Added test on controller more.js --- js/controllers/send.js | 2 +- test/unit/controllers/controllersSpec.js | 108 +++++++++++++++++++++-- 2 files changed, 102 insertions(+), 8 deletions(-) diff --git a/js/controllers/send.js b/js/controllers/send.js index 7989d1ac7..ad3e861b5 100644 --- a/js/controllers/send.js +++ b/js/controllers/send.js @@ -492,7 +492,7 @@ angular.module('copayApp.controllers').controller('SendController', return $scope.setFromPayPro(parsed.data.merchant); var amount = (parsed.data && parsed.data.amount) ? - ((parsed.data.amount * 100000000).toFixed(0) * satToUnit).toFixed(w.settings.unitDecimals): 0; + ((parsed.data.amount * 100000000).toFixed(0) * satToUnit).toFixed(w.settings.unitDecimals) : 0; $scope.setForm(addr, amount, parsed.data.message, true); return addr; diff --git a/test/unit/controllers/controllersSpec.js b/test/unit/controllers/controllersSpec.js index f544910c3..441b7b9a2 100644 --- a/test/unit/controllers/controllersSpec.js +++ b/test/unit/controllers/controllersSpec.js @@ -106,6 +106,7 @@ describe("Unit: Controllers", function() { isChange: false }]); + var iden = {}; iden.getLastFocusedWallet = sinon.stub().returns(null); iden.getWallets = sinon.stub().returns([w]); @@ -140,12 +141,37 @@ describe("Unit: Controllers", function() { scope.create(invalidForm); }); }); + }); + + + describe('Create Profile Controller', function() { + var c; + beforeEach(inject(function($controller, $rootScope) { + scope = $rootScope.$new(); + c = $controller('CreateProfileController', { + $scope: scope, + }); + })); + + it('should exist', function() { + should.exist(c); + }); + + it('#init', function() { + scope.init(); + }); + + it('#clear', function() { + scope.clear(); + }); }); describe('Receive Controller', function() { var c; + var rootScope; beforeEach(inject(function($controller, $rootScope) { + rootScope = $rootScope; scope = $rootScope.$new(); c = $controller('ReceiveController', { $scope: scope, @@ -204,6 +230,8 @@ describe("Unit: Controllers", function() { c.networkName = walletConfig.networkName; c.version = '0.0.1'; + c.generateAddress = sinon.stub().returns({}); + c.balanceInfo = {}; return new Wallet(c); @@ -217,6 +245,11 @@ describe("Unit: Controllers", function() { should.exist(c); }); + it('#init', function() { + scope.init(); + rootScope.title.should.be.equal('Receive'); + }); + it('should call setAddressList', function() { scope.setAddressList(); expect(scope.addresses).to.be.empty; @@ -224,6 +257,12 @@ describe("Unit: Controllers", function() { scope.setAddressList(); expect(scope.addresses).to.be.empty; }); + + it('#newAddr', function() { + rootScope.wallet.generateAddress = sinon.stub().returns({}); + scope.newAddr(); + rootScope.wallet.generateAddress.calledOnce.should.be.true; + }); }); describe('History Controller', function() { @@ -254,9 +293,10 @@ describe("Unit: Controllers", function() { }); describe('Send Controller', function() { - var scope, form, sendForm, sendCtrl; + var scope, form, sendForm, sendCtrl, rootScope; beforeEach(angular.mock.inject(function($compile, $rootScope, $controller, rateService, notification) { scope = $rootScope.$new(); + rootScope = $rootScope; scope.rateService = rateService; var element = angular.element( '
' + @@ -300,6 +340,21 @@ describe("Unit: Controllers", function() { expect(scope.title); }); + it('#setError', function() { + scope.setError('my error'); + expect(scope.error); + }); + + it('#setFromPayPro', function() { + var old = rootScope.wallet.fetchPaymentRequest + rootScope.wallet.fetchPaymentRequest = sinon.stub().returns(null); + scope.setFromPayPro('newURL'); + rootScope.wallet.fetchPaymentRequest.calledOnce.should.be.true; + rootScope.wallet.fetchPaymentRequest = old; + }); + + + it('should validate address with network', function() { form.newaddress.$setViewValue('mkfTyEk7tfgV611Z4ESwDDSZwhsZdbMpVy'); expect(form.newaddress.$invalid).to.equal(false); @@ -478,7 +533,6 @@ describe("Unit: Controllers", function() { expect(rootScope.insightError).equal(1); scope.$apply(); }); - }); describe("Unit: Sidebar Controller", function() { @@ -490,8 +544,7 @@ describe("Unit: Controllers", function() { }); })); - it.only('should call sign out', function() { - + it('should call sign out', function() { scope.signout(); rootScope.iden.close.calledOnce.should.be.true; }); @@ -570,7 +623,7 @@ describe("Unit: Controllers", function() { }); }); - describe.only('SignOut Controller', function() { + describe('SignOut Controller', function() { var what; beforeEach(inject(function($controller, $rootScope) { scope = $rootScope.$new(); @@ -613,7 +666,6 @@ describe("Unit: Controllers", function() { it('should exist', function() { should.exist(ctrl); }); - }); describe('Join Controller', function() { @@ -683,9 +735,10 @@ describe("Unit: Controllers", function() { }); describe('More Controller', function() { - var ctrl, modalCtrl; + var ctrl, modalCtrl, rootScope; beforeEach(inject(function($controller, $rootScope) { scope = $rootScope.$new(); + rootScope = $rootScope; ctrl = $controller('MoreController', { $scope: scope }); @@ -726,6 +779,47 @@ describe("Unit: Controllers", function() { scope.iden.deleteWallet.getCall(0).args[0].should.equal(w.getId()); }); + it('#save', function() { + var old = rootScope.wallet.changeSettings; + rootScope.wallet.changeSettings = sinon.stub().returns(null); + scope.selectedUnit = {}; + scope.save(); + rootScope.wallet.changeSettings.calledOnce.should.equal.true; + rootScope.wallet.changeSettings = old; + }); + + it('#purge checking balance', function() { + var old = rootScope.wallet.purgeTxProposals; + rootScope.wallet.purgeTxProposals = sinon.stub().returns(true); + scope.purge(); + rootScope.wallet.purgeTxProposals.calledOnce.should.equal.true; + rootScope.wallet.purgeTxProposals = old; + }); + + it('#purge without checking balance', function() { + var old = rootScope.wallet.purgeTxProposals; + rootScope.wallet.purgeTxProposals = sinon.stub().returns(false); + scope.purge(); + rootScope.wallet.purgeTxProposals.calledOnce.should.equal.true; + rootScope.wallet.purgeTxProposals = old; + }); + + it('#updateIndexes', function() { + var old = rootScope.wallet.purgeTxProposals; + rootScope.wallet.updateIndexes = sinon.stub().yields(); + scope.updateIndexes(); + rootScope.wallet.updateIndexes.calledOnce.should.equal.true; + rootScope.wallet.updateIndexes = old; + }); + + it('#updateIndexes return error', function() { + var old = rootScope.wallet.purgeTxProposals; + rootScope.wallet.updateIndexes = sinon.stub().yields('error'); + scope.updateIndexes(); + rootScope.wallet.updateIndexes.calledOnce.should.equal.true; + rootScope.wallet.updateIndexes = old; + }); + }); }); From 3696b383d4004217eb0c062e8220878ab241ddc0 Mon Sep 17 00:00:00 2001 From: Matias Pando Date: Thu, 12 Feb 2015 15:08:39 -0300 Subject: [PATCH 3/5] Added test on controller copayer --- test/unit/controllers/controllersSpec.js | 183 ++++++++++++++++++++++- 1 file changed, 175 insertions(+), 8 deletions(-) diff --git a/test/unit/controllers/controllersSpec.js b/test/unit/controllers/controllersSpec.js index 441b7b9a2..b5ae8a39f 100644 --- a/test/unit/controllers/controllersSpec.js +++ b/test/unit/controllers/controllersSpec.js @@ -145,9 +145,11 @@ describe("Unit: Controllers", function() { describe('Create Profile Controller', function() { - var c; - beforeEach(inject(function($controller, $rootScope) { + var c, confService, idenService; + beforeEach(inject(function($controller, $rootScope, configService, identityService) { scope = $rootScope.$new(); + confService = configService; + idenService = identityService; c = $controller('CreateProfileController', { $scope: scope, }); @@ -165,6 +167,40 @@ describe("Unit: Controllers", function() { scope.clear(); }); + it('#saveSettings', function() { + var old = confService.set; + confService.set = sinon.stub().returns(null); + scope.saveSettings(); + confService.set.calledOnce.should.be.true; + confService.set = old; + }); + + it('#createProfile', function() { + var old = scope.saveSettings; + scope.saveSettings = sinon.stub().returns(null); + scope.createProfile(); + scope.saveSettings.calledOnce.should.be.true; + scope.saveSettings = old; + }); + + it('#_doCreateProfile', function() { + var old = idenService.create; + idenService.create = sinon.stub().returns(null); + scope._doCreateProfile('myemail@domain.com', 'password'); + idenService.create.calledOnce.should.be.true; + idenService.create = old; + }); + + it('#createDefaultWallet', function() { + var old = idenService.createDefaultWallet; + idenService.createDefaultWallet = sinon.stub().returns(null); + scope.createDefaultWallet(); + idenService.createDefaultWallet.calledOnce.should.be.true; + idenService.createDefaultWallet = old; + }); + + + }); describe('Receive Controller', function() { @@ -550,6 +586,41 @@ describe("Unit: Controllers", function() { }); }); + describe("Head Controller", function() { + var scope, ctrl, rootScope, idenService, balService; + beforeEach(inject(function($controller, $rootScope, identityService, balanceService) { + rootScope = $rootScope; + idenService = identityService; + balService = balanceService; + scope = $rootScope.$new(); + ctrl = $controller('HeadController', { + $scope: scope, + }); + })); + + it('should exist', function() { + should.exist(ctrl); + }); + + it('should call sign out', function() { + var old = idenService.signout; + idenService.signout = sinon.stub().returns(null); + scope.signout(); + idenService.signout.calledOnce.should.be.true; + idenService.signout = old; + }); + + it('should call refresh', function() { + var old = rootScope.wallet.sendWalletReady; + rootScope.wallet.sendWalletReady = sinon.stub().returns(null); + balService.clearBalanceCache = sinon.stub().returns(null); + scope.refresh(); + rootScope.wallet.sendWalletReady.calledOnce.should.be.true; + rootScope.wallet.sendWalletReady = old; + }); + + }); + describe('Send Controller', function() { var sendCtrl, form; beforeEach(inject(function($compile, $rootScope, $controller) { @@ -666,6 +737,15 @@ describe("Unit: Controllers", function() { it('should exist', function() { should.exist(ctrl); }); + + it('#init', function() { + var old = scope.updateList; + scope.updateList = sinon.stub().returns(null); + scope.init(); + scope.updateList.callCount.should.be.equal(3); //why 3 ?????? + scope.updateList = old; + }); + }); describe('Join Controller', function() { @@ -721,24 +801,36 @@ describe("Unit: Controllers", function() { }); describe('Warning Controller', function() { - var what; - beforeEach(inject(function($controller, $rootScope) { + var ctrl, idenService; + beforeEach(inject(function($controller, $rootScope, identityService) { scope = $rootScope.$new(); - what = $controller('WarningController', { + idenService = identityService; + ctrl = $controller('WarningController', { $scope: scope, }); })); it('should exist', function() { - should.exist(what); + should.exist(ctrl); }); + + it('#signout', function() { + var old = idenService.signout; + idenService.signout = sinon.stub().returns(null); + scope.signout(); + idenService.signout.calledOnce.should.be.true; + idenService.signout = old; + }); + }); describe('More Controller', function() { - var ctrl, modalCtrl, rootScope; - beforeEach(inject(function($controller, $rootScope) { + var ctrl, modalCtrl, rootScope, idenService, bkpService; + beforeEach(inject(function($controller, $rootScope, backupService, identityService) { scope = $rootScope.$new(); rootScope = $rootScope; + idenService = identityService; + bkpService = backupService; ctrl = $controller('MoreController', { $scope: scope }); @@ -820,6 +912,81 @@ describe("Unit: Controllers", function() { rootScope.wallet.updateIndexes = old; }); + it('#deleteWallet', function() { + var old = idenService.deleteWallet; + idenService.deleteWallet = sinon.stub().yields(null); + scope.deleteWallet(); + idenService.deleteWallet.calledOnce.should.equal.true; + scope.loading.should.be.false; + idenService.deleteWallet = old; + }); + + it('#deleteWallet with error', function() { + var old = idenService.deleteWallet; + idenService.deleteWallet = sinon.stub().yields('error'); + scope.deleteWallet(); + idenService.deleteWallet.calledOnce.should.equal.true; + scope.error.should.be.equal('error'); + idenService.deleteWallet = old; + }); + + it('#viewWalletBackup', function() { + var old = bkpService.walletEncrypted; + bkpService.walletEncrypted = sinon.stub().returns('backup0001'); + scope.viewWalletBackup(); + bkpService.walletEncrypted.calledOnce.should.equal.true; + bkpService.walletEncrypted = old; + }); + + it('#copyWalletBackup', function() { + var old = bkpService.walletEncrypted; + bkpService.walletEncrypted = sinon.stub().returns('backup0001'); + window.cordova = { + plugins: { + clipboard: { + copy: function(e) { + return e; + } + } + } + }; + + window.plugins = { + toast: { + showShortCenter: function(e) { + return e; + } + } + }; + scope.copyWalletBackup(); + bkpService.walletEncrypted.calledOnce.should.equal.true; + bkpService.walletEncrypted = old; + }); + + it('#sendWalletBackup', function() { + var old = bkpService.walletEncrypted; + bkpService.walletEncrypted = sinon.stub().returns('backup0001'); + + window.plugins = { + toast: { + showShortCenter: function(e) { + return e; + } + } + }; + + window.plugin = { + email: { + open: function(e) { + return e; + } + } + }; + scope.sendWalletBackup(); + bkpService.walletEncrypted.calledOnce.should.equal.true; + bkpService.walletEncrypted = old; + }); + }); }); From b50b4e1aee704d32cfdad0303336a0440d61de81 Mon Sep 17 00:00:00 2001 From: Matias Pando Date: Thu, 12 Feb 2015 17:26:30 -0300 Subject: [PATCH 4/5] Added test on controllers --- js/controllers/copayers.js | 7 +- test/unit/controllers/controllersSpec.js | 146 ++++++++++++++++++++--- 2 files changed, 137 insertions(+), 16 deletions(-) diff --git a/js/controllers/copayers.js b/js/controllers/copayers.js index 91d3ecd14..9fc338ad0 100644 --- a/js/controllers/copayers.js +++ b/js/controllers/copayers.js @@ -3,6 +3,8 @@ angular.module('copayApp.controllers').controller('CopayersController', function($scope, $rootScope, $timeout, go, identityService, notification, isCordova) { var w = $rootScope.wallet; + + $scope.init = function() { $rootScope.title = 'Share this secret with your copayers'; $scope.loading = false; @@ -11,6 +13,7 @@ angular.module('copayApp.controllers').controller('CopayersController', w.on('publicKeyRingUpdated', $scope.updateList); w.on('ready', $scope.updateList); + $scope.updateList(); }; @@ -36,7 +39,9 @@ angular.module('copayApp.controllers').controller('CopayersController', $scope.loading = null; $scope.error = err.message || err; copay.logger.warn(err); - $timeout(function () { $scope.$digest(); }); + $timeout(function() { + $scope.$digest(); + }); } else { $scope.loading = false; if ($rootScope.wallet) { diff --git a/test/unit/controllers/controllersSpec.js b/test/unit/controllers/controllersSpec.js index b5ae8a39f..93a3c005e 100644 --- a/test/unit/controllers/controllersSpec.js +++ b/test/unit/controllers/controllersSpec.js @@ -328,6 +328,104 @@ describe("Unit: Controllers", function() { }); }); + + + describe('Profile Controller', function() { + var ctrl, bkpService, idenService; + beforeEach(inject(function($controller, $rootScope, backupService, identityService) { + scope = $rootScope.$new(); + bkpService = backupService; + idenService = identityService; + ctrl = $controller('ProfileController', { + $scope: scope, + }); + })); + + it('should exist', function() { + should.exist(ctrl); + }); + + it('#downloadProfileBackup', function() { + var old = bkpService.profileDownload; + bkpService.profileDownload = sinon.stub().returns(null); + scope.downloadProfileBackup(); + bkpService.profileDownload.calledOnce.should.be.true; + bkpService.profileDownload = old; + }); + + it('#viewProfileBackup', function() { + var old = bkpService.profileEncrypted; + bkpService.profileEncrypted = sinon.stub().returns(null); + scope.viewProfileBackup(); + //bkpService.profileEncrypted.calledOnce.should.be.true; + bkpService.profileEncrypted = old; + }); + + it('#copyProfileBackup', function() { + var old = bkpService.profileEncrypted; + bkpService.profileEncrypted = sinon.stub().returns(null); + + window.cordova = { + plugins: { + clipboard: { + copy: function(e) { + return e; + } + } + } + }; + + window.plugins = { + toast: { + showShortCenter: function(e) { + return e; + } + } + }; + + scope.copyProfileBackup(); + bkpService.profileEncrypted.calledOnce.should.be.true; + bkpService.profileEncrypted = old; + }); + + it('#sendProfileBackup', function() { + var old = bkpService.profileEncrypted; + bkpService.profileEncrypted = sinon.stub().returns(null); + + window.plugin = { + email: { + open: function(e) { + return e; + } + } + }; + + window.plugins = { + toast: { + showShortCenter: function(e) { + return e; + } + } + }; + + scope.sendProfileBackup(); + bkpService.profileEncrypted.calledOnce.should.be.true; + bkpService.profileEncrypted = old; + }); + + it('#deleteProfile', function() { + var old = idenService.deleteProfile; + idenService.deleteProfile = sinon.stub().returns(null); + scope.deleteProfile(); + idenService.deleteProfile.calledOnce.should.be.true; + idenService.deleteProfile = old; + }); + + + }); + + + describe('Send Controller', function() { var scope, form, sendForm, sendCtrl, rootScope; beforeEach(angular.mock.inject(function($compile, $rootScope, $controller, rateService, notification) { @@ -658,16 +756,16 @@ describe("Unit: Controllers", function() { }); describe('Import Controller', function() { - var what; + var ctrl; beforeEach(inject(function($controller, $rootScope) { scope = $rootScope.$new(); - what = $controller('ImportController', { + ctrl = $controller('ImportController', { $scope: scope, }); })); it('should exist', function() { - should.exist(what); + should.exist(ctrl); }); it('import status', function() { expect(scope.importStatus).equal('Importing wallet - Reading backup...'); @@ -676,16 +774,16 @@ describe("Unit: Controllers", function() { // TODO: fix this test describe.skip('Home Controller', function() { - var what; + var ctrl; beforeEach(inject(function($controller, $rootScope) { scope = $rootScope.$new(); - what = $controller('HomeController', { + ctrl = $controller('HomeController', { $scope: scope, }); })); it('should exist', function() { - should.exist(what); + should.exist(ctrl); }); describe('#open', function() { it('should work with invalid form', function() { @@ -695,16 +793,16 @@ describe("Unit: Controllers", function() { }); describe('SignOut Controller', function() { - var what; + var ctrl; beforeEach(inject(function($controller, $rootScope) { scope = $rootScope.$new(); - what = $controller('signOutController', { + ctrl = $controller('signOutController', { $scope: scope, }); })); it('should exist', function() { - should.exist(what); + should.exist(ctrl); }); }); @@ -724,10 +822,11 @@ describe("Unit: Controllers", function() { describe('Copayers Controller', function() { var saveDownload = null; - var ctrl; - beforeEach(inject(function($controller, $rootScope) { + var ctrl, rootScope, idenService; + beforeEach(inject(function($controller, $rootScope, identityService) { scope = $rootScope.$new(); - + rootScope = $rootScope; + idenService = identityService; ctrl = $controller('CopayersController', { $scope: scope, $modal: {}, @@ -746,19 +845,36 @@ describe("Unit: Controllers", function() { scope.updateList = old; }); + it('#updateList', function() { + var old = rootScope.wallet.getRegisteredPeerIds; + rootScope.wallet.getRegisteredPeerIds = sinon.stub().returns(null); + rootScope.wallet.removeListener = sinon.stub().returns(null); + scope.updateList(); + rootScope.wallet.getRegisteredPeerIds.callCount.should.be.equal(1); + rootScope.wallet.getRegisteredPeerIds = old; + }); + + it('#deleteWallet', function() { + var old = idenService.deleteWallet; + idenService.deleteWallet = sinon.stub().returns(null); + scope.deleteWallet(); + idenService.deleteWallet.callCount.should.be.equal(1); + idenService.deleteWallet = old; + }); + }); describe('Join Controller', function() { - var what; + var ctrl; beforeEach(inject(function($controller, $rootScope) { scope = $rootScope.$new(); - what = $controller('JoinController', { + ctrl = $controller('JoinController', { $scope: scope, }); })); it('should exist', function() { - should.exist(what); + should.exist(ctrl); }); describe('#join', function() { it('should work with invalid form', function() { From c0499089b1dbc081c85c6921ab3355dac3219027 Mon Sep 17 00:00:00 2001 From: Gustavo Maximiliano Cortez Date: Wed, 18 Feb 2015 14:51:51 -0300 Subject: [PATCH 5/5] Added karma-coverage to package.json --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index e5641ef84..2b07cdfa0 100644 --- a/package.json +++ b/package.json @@ -63,15 +63,16 @@ "grunt-contrib-cssmin": "^0.10.0", "grunt-contrib-uglify": "^0.5.1", "grunt-contrib-watch": "^0.5.3", + "grunt-exec": "*", "grunt-jsdoc": "^0.5.7", "grunt-markdown": "^0.5.0", "grunt-mocha-test": "^0.8.2", "grunt-release": "^0.7.0", - "grunt-exec": "*", "istanbul": "^0.2.10", "karma": "^0.12.9", "karma-chai": "^0.1.0", "karma-chrome-launcher": "^0.1.3", + "karma-coverage": "^0.2.7", "karma-firefox-launcher": "^0.1", "karma-mocha": "^0.1.9", "karma-phantomjs-launcher": "^0.1.4",