diff --git a/.travis.yml b/.travis.yml index dc90fb30..696c89c4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -67,6 +67,7 @@ script: - sh -c "if [ '$JSTESTS' != '1' -a '$CODECHECK' != '1' -a '$CODECHECK' != '2' ]; then php ocular.phar code-coverage:upload --access-token="$SCRUTINIZER_TOKEN" --format=php-clover coverage.clover; fi" #- cd ../ - sh -c "if [ '$JSTESTS' = '1' ]; then grunt jshint; fi" + - sh -c "if [ '$JSTESTS' = '1' ]; then grunt karma; fi" #after_failure: diff --git a/Gruntfile.js b/Gruntfile.js index f394e4da..ffec2205 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -78,6 +78,13 @@ module.exports = function (grunt) { } }, + karma: { + unit: { + configFile: './karma.conf.js', + background: false + } + }, + //@TODO JSHint watch: { scripts: { @@ -107,6 +114,7 @@ module.exports = function (grunt) { grunt.loadNpmTasks('grunt-html2js'); grunt.loadNpmTasks('grunt-contrib-watch'); grunt.loadNpmTasks('grunt-contrib-jshint'); + grunt.loadNpmTasks('grunt-karma'); // Default task(s). grunt.registerTask('default', ['html2js', 'sass']); grunt.registerTask('hint', ['jshint']); diff --git a/js/app/app.js b/js/app/app.js index 03bad227..ae8e53ef 100644 --- a/js/app/app.js +++ b/js/app/app.js @@ -125,7 +125,7 @@ $('#controls').css('min-width', controlsWidth + magic); } }; - $(window).resize(_.debounce(adjustControlsWidth, 400)); + $(window).resize(adjustControlsWidth, 400); setTimeout(function () { adjustControlsWidth(true); }, 200); diff --git a/js/app/controllers/credential.js b/js/app/controllers/credential.js index 579f3e91..68c65bfe 100644 --- a/js/app/controllers/credential.js +++ b/js/app/controllers/credential.js @@ -26,7 +26,6 @@ $scope.active_vault = _vault; //@TODO check if vault exists } - } $scope.show_spinner = true; diff --git a/js/app/controllers/menu.js b/js/app/controllers/menu.js index 57458244..495944c1 100644 --- a/js/app/controllers/menu.js +++ b/js/app/controllers/menu.js @@ -10,8 +10,8 @@ * Controller of the passmanApp */ angular.module('passmanApp') - .controller('MenuCtrl', ['$scope', 'VaultService', 'SettingsService', '$location', '$rootScope', 'TagService', - function ($scope, VaultService, SettingsService, $location, $rootScope, TagService) { + .controller('MenuCtrl', ['$scope', 'VaultService', '$location', '$rootScope', 'TagService','SettingsService', + function ($scope, VaultService, $location, $rootScope, TagService, SettingsService) { $rootScope.logout = function () { SettingsService.setSetting('defaultVaultPass', false); $rootScope.$broadcast('logout'); diff --git a/js/vendor/download.js b/js/vendor/download.js index 1a987a9d..3bf0c449 100644 --- a/js/vendor/download.js +++ b/js/vendor/download.js @@ -6,7 +6,7 @@ // v4.1 adds url download capability via solo URL argument (same domain/CORS only) // v4.2 adds semantic variable names, long (over 2MB) dataURL support, and hidden by default temp anchors // https://github.com/rndme/download - +/* (function (root, factory) { if (typeof define === 'function' && define.amd) { // AMD. Register as an anonymous module. @@ -158,5 +158,5 @@ reader.readAsDataURL(blob); } return true; - }; /* end download() */ -})); \ No newline at end of file + }; /* end download() +}));*/ \ No newline at end of file diff --git a/karma.conf.js b/karma.conf.js new file mode 100644 index 00000000..d7c56a17 --- /dev/null +++ b/karma.conf.js @@ -0,0 +1,78 @@ +// Karma configuration +// Generated on Mon Oct 17 2016 15:46:52 GMT+0200 (CEST) +var isTravis = (process.env.TRAVIS_BUILD_NUMBER) ? true : false; +var browsers = ['Firefox']; +if(!isTravis){ + browsers = ['Chrome']; +} +module.exports = function (config) { + config.set({ + + // base path that will be used to resolve all patterns (eg. files, exclude) + basePath: '.', + + + // frameworks to use + // available frameworks: https://npmjs.org/browse/keyword/karma-adapter + frameworks: ['jasmine'], + + + // list of files / patterns to load in the browser + files: [ + '../../core/vendor/jquery/dist/jquery.js', + '../../core/vendor/underscore/underscore.js', + 'js/vendor/angular/angular.min.js', + 'tests/unit/js/mocks/*.js', + 'js/vendor/angular-mocks/angular-mocks.js', + 'js/vendor/**/*.js', + 'js/app/**/*.js', + { pattern: 'tests/unit/js/app/**/*.js', included: true } + ], + + + // list of files to exclude + exclude: [ + 'js/vendor/angular-mocks/ngAnimateMock.js', + 'js/vendor/angular-mocks/ngMock.js', + 'js/vendor/angular-mocks/ngMockE2E.js' + ], + + + // preprocess matching files before serving them to the browser + // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor + preprocessors: {}, + + + // test results reporter to use + // possible values: 'dots', 'progress' + // available reporters: https://npmjs.org/browse/keyword/karma-reporter + reporters: ['verbose'], + + + // 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: false, + + + // start these browsers + // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher + browsers: browsers, + + + // Continuous Integration mode + // if true, Karma captures browsers, runs the tests and exits + singleRun: true + }); +}; diff --git a/package.json b/package.json index 3b670ca6..01027acd 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,9 @@ "name": "Passman", "version": "0.0.1", "devDependencies": { + "karma-verbose-reporter": "0.0.3" + }, + "dependencies": { "grunt": "~0.4.5", "grunt-contrib-jshint": "^0.12.0", "grunt-contrib-nodeunit": "~0.4.1", @@ -9,6 +12,14 @@ "grunt-contrib-uglify": "~0.5.0", "grunt-contrib-watch": "^1.0.0", "grunt-html2js": "^0.3.6", - "html-minifier": "^3.0.2" + "grunt-karma": "^2.0.0", + "html-minifier": "^3.0.2", + "jasmine-core": "^2.5.2", + "karma": "^1.3.0", + "karma-chrome-launcher": "^2.0.0", + "karma-firefox-launcher": "^1.0.0", + "karma-jasmine": "^1.0.2", + "karma-requirejs": "^1.1.0", + "requirejs": "^2.3.2" } } diff --git a/tests/unit/js/app/controllers/credentialTest.js b/tests/unit/js/app/controllers/credentialTest.js new file mode 100644 index 00000000..04e71a6b --- /dev/null +++ b/tests/unit/js/app/controllers/credentialTest.js @@ -0,0 +1,45 @@ +describe('CredentialCtrl', function() { + var ctrl, scope, rootScope; + beforeEach(module('passmanApp', function ($provide) { + $provide.value('$window', { + localStorage: localStorageMock() + }); + })); + beforeEach(module('LocalStorageModule')); + beforeEach(module('mock.credentialsService')); + + beforeEach(inject(function($controller, $rootScope, _CredentialService_, SettingsService) { // inject mocked service + scope = $rootScope.$new(); + rootScope = $rootScope; + ctrl = $controller('CredentialCtrl', { + $scope: scope, + CredentialService: _CredentialService_, + SettingService: SettingsService + }); + })); + + describe('Test events', function() { + it('[event] selected_tags_updated', function() { + rootScope.$broadcast('selected_tags_updated', [{text: 'hello'}]); + expect(scope.selectedtags).toEqual(['hello']); + }); + + it('[event] set_delete_time', function() { + rootScope.$broadcast('set_delete_time', 1337); + expect(scope.delete_time).toEqual(1337); + }); + + it('[event] logout', function() { + rootScope.$broadcast('logout'); + expect(scope.active_vault).toEqual(null); + expect(scope.credentials).toEqual([]); + }); + + it('[event] close selected credential', function() { + var _spy = spyOn(rootScope, '$emit'); + scope.closeSelected(); + expect(_spy).toHaveBeenCalled(); + expect(scope.selectedCredential).toEqual(false); + }); + }); +}); \ No newline at end of file diff --git a/tests/unit/js/app/controllers/menuTest.js b/tests/unit/js/app/controllers/menuTest.js new file mode 100644 index 00000000..27692e2a --- /dev/null +++ b/tests/unit/js/app/controllers/menuTest.js @@ -0,0 +1,53 @@ +describe('MenuCtrl', function() { + beforeEach(module('passmanApp', function ($provide) { + $provide.value('$window', { + localStorage: localStorageMock() + }); + })); + + beforeEach(module('LocalStorageModule')); + + var $controller; + var $scope; + beforeEach(inject(function(_$controller_){ + // The injector unwraps the underscores (_) from around the parameter names when matching + $controller = _$controller_; + })); + beforeEach(inject(function($rootScope) { + $scope = $rootScope.$new(); + })); + + + describe('$scope.selectedTags', function() { + it('should add a tag to selected tags', function() { + var controller = $controller('MenuCtrl', { $scope: $scope }); + $scope.tagClicked({text: 'hello'}); + expect($scope.selectedTags).toEqual([{text: 'hello'}]); + }); + }); + + describe('$scope.toggleDeleteTime', function() { + it('should toggle delete time', function() { + var controller = $controller('MenuCtrl', { $scope: $scope }); + $scope.delete_time = 0; + $scope.toggleDeleteTime(); + expect($scope.delete_time).toEqual(1); + $scope.toggleDeleteTime(); + expect($scope.delete_time).toEqual(0); + }); + }); + + describe('$scope.getTags()', function() { + var scope, ctrl, service; + beforeEach(inject(function(TagService) { + console.log('*** IN INJECT!!***: ', TagService); + service = TagService; + })); + + it('should return an empty array', function() { + var controller = $controller('MenuCtrl', { $scope: $scope }); + expect($scope.getTags()).toEqual([]); + + }); + }); +}); \ No newline at end of file diff --git a/tests/unit/js/mocks/CredentialService.js b/tests/unit/js/mocks/CredentialService.js new file mode 100644 index 00000000..1fac8d26 --- /dev/null +++ b/tests/unit/js/mocks/CredentialService.js @@ -0,0 +1,22 @@ +angular.module('mock.credentialsService', []). +service('CredentialService', function($q) { + var credentialService = {}; + + credentialService.getCredential = function() { + var _credential = { + id: 8888, + label: "test user" + }; + return $q.when(_credential); + }; + + credentialService.getRevisions = function() { + var mockRevision = [{ + id: 1234, + created: "" + }]; + return $q.when(mockRevision); + }; + + return credentialService; +}); \ No newline at end of file diff --git a/tests/unit/js/mocks/global.js b/tests/unit/js/mocks/global.js new file mode 100644 index 00000000..328e7547 --- /dev/null +++ b/tests/unit/js/mocks/global.js @@ -0,0 +1 @@ +var oc_requesttoken = null; \ No newline at end of file diff --git a/tests/unit/js/mocks/localStorageMock.js b/tests/unit/js/mocks/localStorageMock.js new file mode 100644 index 00000000..ea449f33 --- /dev/null +++ b/tests/unit/js/mocks/localStorageMock.js @@ -0,0 +1,42 @@ +'use strict'; +//Mock localStorage +function localStorageMock() { + var storage = {}; + Object.defineProperties(storage, { + setItem: { + value: function(key, value) { + storage[key] = value || ''; + }, + enumerable: false, + writable: true + }, + getItem: { + value: function(key) { + return storage[key] ? storage[key] : null; + }, + enumerable: false, + writable: true + }, + removeItem: { + value: function(key) { + delete storage[key]; + }, + enumerable: false, + writable: true + }, + length: { + get: function() { + return Object.keys(storage).length; + }, + enumerable: false + }, + key: { + value: function(i) { + var aKeys = Object.keys(storage); + return aKeys[i] || null; + }, + enumerable: false + } + }); + return storage; +} \ No newline at end of file