diff --git a/.gitignore b/.gitignore index 3c3629e..a088b6f 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ node_modules +bower_components diff --git a/Gruntfile.js b/Gruntfile.js index 8ba670e..efa3870 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -22,7 +22,7 @@ module.exports = function(grunt) { } }, jshint: { - files: ['Gruntfile.js', 'src/**/*.js', 'src/**/**/*.js'], + files: ['Gruntfile.js', 'src/js/**/*.js'], options: { // options here to override JSHint defaults globals: { @@ -40,6 +40,28 @@ module.exports = function(grunt) { {expand: false, src: ['src/lib/plupload/plupload.silverlight.xap'], dest: 'dist/plupload.silverlight.xap'}, ] } + }, + karma: { + once: { + configFile: 'karma.conf.js', + singleRun: true, + browsers: [ + //'Chrome', + //'Firefox', + 'PhantomJS' + ] + }, + watch: { + configFile: 'karma.conf.js', + background: false, + autoWatch: true, + singleRun: false, + browsers: [ + //'Chrome', + //'Firefox', + 'PhantomJS' + ] + } } }); @@ -47,9 +69,11 @@ module.exports = function(grunt) { grunt.loadNpmTasks('grunt-contrib-jshint'); grunt.loadNpmTasks('grunt-contrib-concat'); grunt.loadNpmTasks('grunt-contrib-copy'); + grunt.loadNpmTasks('grunt-karma'); + - grunt.registerTask('test', ['jshint']); + grunt.registerTask('test', ['jshint', 'karma:once']); - grunt.registerTask('build', ['jshint', 'concat', 'uglify','copy']); + grunt.registerTask('build', ['jshint', 'karma:once', 'concat', 'uglify','copy']); }; \ No newline at end of file diff --git a/bower.json b/bower.json index 88a5af1..029c015 100644 --- a/bower.json +++ b/bower.json @@ -24,5 +24,9 @@ "bower_components", "test", "tests" - ] + ], + "dependencies": { + "angular": "~1.3.12", + "angular-mocks" : "~1.3.12" + } } diff --git a/karma.conf.js b/karma.conf.js new file mode 100644 index 0000000..3e57d9a --- /dev/null +++ b/karma.conf.js @@ -0,0 +1,22 @@ +'use strict'; + +module.exports = function(config) { + config.set({ + basePath: '', + frameworks: ['jasmine'], + logLevel: config.LOG_INFO, + colors: true, + runnerPort: 9100, + port: 9018, + reporters: ['progress'], + preprocessors: {}, + files: [ + 'bower_components/angular/angular.js', + 'bower_components/angular-mocks/angular-mocks.js', + 'tests/mocks/plUploader.mock.js', + 'src/lib/plupload/plupload.full.js', + 'src/js/plupload-angular-directive.js', + 'tests/js/plupload-angular-directive.spec.js' + ] + }); +} \ No newline at end of file diff --git a/package.json b/package.json index a3ba193..05ba832 100644 --- a/package.json +++ b/package.json @@ -7,11 +7,20 @@ ], "dependencies": {}, "devDependencies": { + "coveralls": "^2.11.2", "grunt": "^0.4.5", "grunt-contrib-concat": "^0.4.0", "grunt-contrib-copy": "^0.5.0", "grunt-contrib-jshint": "^0.10.0", - "grunt-contrib-uglify": "^0.4.0" + "grunt-contrib-uglify": "^0.4.0", + "grunt-karma": "^0.9.0", + "jasmine-core": "^2.1.2", + "karma": "^0.12.26", + "karma-coverage": "^0.2.6", + "karma-chrome-launcher": "^0.1.5", + "karma-firefox-launcher": "^0.1.3", + "karma-jasmine": "^0.3.1", + "karma-phantomjs-launcher": "^0.1.4" }, "engine": "node >= 0.4.1" } diff --git a/src/js/plupload-angular-directive.js b/src/js/plupload-angular-directive.js index 5d2e80b..2e527c6 100755 --- a/src/js/plupload-angular-directive.js +++ b/src/js/plupload-angular-directive.js @@ -1,221 +1,224 @@ -'use strict'; - -angular.module('plupload.directive', []) - .provider('plUploadService', function() { - - var config = { - flashPath: 'bower_components/plupload-angular-directive/dist/plupload.flash.swf', - silverLightPath: 'bower_components/plupload-angular-directive/dist/plupload.silverlight.xap', - uploadPath: 'upload.php' - }; - - this.setConfig = function(key, val) { - config[key] = val; - }; - - this.getConfig = function(key) { - return config[key]; - }; - - var that = this; - - this.$get = [function(){ - - return { - getConfig: that.getConfig, - setConfig: that.setConfig - }; - - }]; - - }) - .directive('plUpload', ['$parse', '$log', 'plUploadService', function ($parse, $log, plUploadService) { - return { - restrict: 'A', - scope: { - 'plProgressModel': '=', - 'plFilesModel': '=', - 'plFiltersModel': '=', - 'plMultiParamsModel':'=', - 'plInstance': '=' - }, - link: function (scope, iElement, iAttrs) { - - scope.randomString = function(len, charSet) { - charSet = charSet || 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - var randomString = ''; - for (var i = 0; i < len; i++) { - var randomPoz = Math.floor(Math.random() * charSet.length); - randomString += charSet.substring(randomPoz,randomPoz+1); - } - return randomString; - } - - if(!iAttrs.id){ - var randomValue = scope.randomString(5); - iAttrs.$set('id',randomValue); - } - if(!iAttrs.plAutoUpload){ - iAttrs.$set('plAutoUpload','true'); - } - if(!iAttrs.plMaxFileSize){ - iAttrs.$set('plMaxFileSize','10mb'); - } - if(!iAttrs.plUrl){ - iAttrs.$set('plUrl', plUploadService.getConfig('uploadPath')); - } - if(!iAttrs.plFlashSwfUrl){ - iAttrs.$set('plFlashSwfUrl', plUploadService.getConfig('flashPath')); - } - if(!iAttrs.plSilverlightXapUrl){ - iAttrs.$set('plSilverlightXapUrl', plUploadService.getConfig('silverLightPath')); - } - if(typeof scope.plFiltersModel=="undefined"){ - scope.filters = [{title : "Image files", extensions : "jpg,jpeg,gif,png,tiff,pdf"}]; - //alert('sf'); - } else{ - scope.filters = scope.plFiltersModel; - } - - - var options = { - runtimes : 'html5,flash,silverlight', - browse_button : iAttrs.id, - multi_selection: true, - // container : 'abc', - max_file_size : iAttrs.plMaxFileSize, - url : iAttrs.plUrl, - flash_swf_url : iAttrs.plFlashSwfUrl, - silverlight_xap_url : iAttrs.plSilverlightXapUrl, - filters : scope.filters, - drop_element: iAttrs.plDropElement - } - - - if(scope.plMultiParamsModel){ - options.multipart_params = scope.plMultiParamsModel; - } - - - var uploader = new plupload.Uploader(options); - - uploader.settings.headers = plUploadService.getConfig('headers'); - - uploader.init(); - - uploader.bind('Error', function(up, err) { - if(iAttrs.onFileError){ - scope.$parent.$apply(iAttrs.onFileError); - } - - $log.error("Cannot upload, error: " + err.message + (err.file ? ", File: " + err.file.name : "") + ""); - - up.refresh(); // Reposition Flash/Silverlight - }); - - uploader.bind('FilesAdded', function(up,files) { - //uploader.start(); - scope.$apply(function() { - if(iAttrs.plFilesModel) { - angular.forEach(files, function(file,key) { - if (!scope.plFilesModel) scope.plFilesModel=[]; - scope.plFilesModel.push(file); - }); - } - - if(iAttrs.onFileAdded){ - scope.$parent.$eval(iAttrs.onFileAdded); - } - }); - - if(iAttrs.plAutoUpload=="true"){ - uploader.start(); - } - }); - - uploader.bind('BeforeUpload', function(up, file) { - if(iAttrs.onBeforeUpload){ - var fn = $parse(iAttrs.onBeforeUpload); - fn(scope.$parent, {$file:file}); +(function() { + 'use strict'; + + angular.module('plupload.directive', []) + .constant('plupload', plupload) //Needed for use as dependency for the directive plUpload + .provider('plUploadService', function() { + + var config = { + flashPath: 'bower_components/plupload-angular-directive/dist/plupload.flash.swf', + silverLightPath: 'bower_components/plupload-angular-directive/dist/plupload.silverlight.xap', + uploadPath: 'upload.php' + }; + + this.setConfig = function(key, val) { + config[key] = val; + }; + + this.getConfig = function(key) { + return config[key]; + }; + + var that = this; + + this.$get = [function() { + + return { + getConfig: that.getConfig, + setConfig: that.setConfig + }; + + }]; + + }) + .directive('plUpload', ['$parse', '$log', 'plupload', 'plUploadService', function($parse, $log, plupload, plUploadService) { + return { + restrict: 'A', + scope: { + 'plProgressModel': '=', + 'plFilesModel': '=', + 'plFiltersModel': '=', + 'plMultiParamsModel': '=', + 'plInstance': '=' + }, + link: function(scope, iElement, iAttrs) { + + scope.randomString = function(len, charSet) { + charSet = charSet || 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + var randomString = ''; + for(var i = 0; i < len; i++) { + var randomPoz = Math.floor(Math.random() * charSet.length); + randomString += charSet.substring(randomPoz, randomPoz + 1); + } + return randomString; + }; + + if(!iAttrs.id) { + var randomValue = scope.randomString(5); + iAttrs.$set('id', randomValue); + } + if(!iAttrs.plAutoUpload) { + iAttrs.$set('plAutoUpload', 'true'); + } + if(!iAttrs.plMaxFileSize) { + iAttrs.$set('plMaxFileSize', '10mb'); + } + if(!iAttrs.plUrl) { + iAttrs.$set('plUrl', plUploadService.getConfig('uploadPath')); + } + if(!iAttrs.plFlashSwfUrl) { + iAttrs.$set('plFlashSwfUrl', plUploadService.getConfig('flashPath')); + } + if(!iAttrs.plSilverlightXapUrl) { + iAttrs.$set('plSilverlightXapUrl', plUploadService.getConfig('silverLightPath')); + } + if(typeof scope.plFiltersModel == "undefined") { + scope.filters = [{title: "Image files", extensions: "jpg,jpeg,gif,png,tiff,pdf"}]; + //alert('sf'); + } else { + scope.filters = scope.plFiltersModel; + } + + + var options = { + runtimes: 'html5,flash,silverlight', + browse_button: iAttrs.id, + multi_selection: true, + // container : 'abc', + max_file_size: iAttrs.plMaxFileSize, + url: iAttrs.plUrl, + flash_swf_url: iAttrs.plFlashSwfUrl, + silverlight_xap_url: iAttrs.plSilverlightXapUrl, + filters: scope.filters, + drop_element: iAttrs.plDropElement + }; + + + if(scope.plMultiParamsModel) { + options.multipart_params = scope.plMultiParamsModel; + } + + + var uploader = new plupload.Uploader(options); + + uploader.settings.headers = plUploadService.getConfig('headers'); + + uploader.init(); + + uploader.bind('Error', function(up, err) { + if(iAttrs.onFileError) { + scope.$parent.$apply(iAttrs.onFileError); + } + + $log.error("Cannot upload, error: " + err.message + (err.file ? ", File: " + err.file.name : "") + ""); + + up.refresh(); // Reposition Flash/Silverlight + }); + + uploader.bind('FilesAdded', function(up, files) { + //uploader.start(); + scope.$apply(function() { + if(iAttrs.plFilesModel) { + angular.forEach(files, function(file, key) { + if(!scope.plFilesModel) scope.plFilesModel = []; + scope.plFilesModel.push(file); + }); + } + + if(iAttrs.onFileAdded) { + scope.$parent.$eval(iAttrs.onFileAdded); + } + }); + + if(iAttrs.plAutoUpload == "true") { + uploader.start(); + } + }); + + uploader.bind('BeforeUpload', function(up, file) { + if(iAttrs.onBeforeUpload) { + var fn = $parse(iAttrs.onBeforeUpload); + fn(scope.$parent, {$file: file}); + } + }); + + uploader.bind('FileUploaded', function(up, file, res) { + //We are going to make some refactor here. + //The idea behind is always update files with the server response value + //And also launch the eventi if neeed + + //If we have the model... + if(iAttrs.plFilesModel) { + //Apply on scope... + scope.$apply(function() { + + //All files are uploaded? + scope.allUploaded = false; + + angular.forEach(scope.plFilesModel, function($file, key) { + + //Bug FIX, this logic will set allUploaded right + if(file.percent != 100) { + scope.allUploaded = false; + } else if(file.id == $file.id) { //If the file is the same that we are reciving... + //Set response on the file + $file.response = JSON.parse(res.response); + + //Need throw event? throw it + if(iAttrs.onFileUploaded) { + var fn = $parse(iAttrs.onFileUploaded); + fn(scope.$parent, {$response: res}); } + } + }); + }); + } + //We doesn't have model but we have the event + else if(!iAttrs.plFilesModel && iAttrs.onFileUploaded) { + var fn = $parse(iAttrs.onFileUploaded); + scope.$apply(function() { + fn(scope.$parent, {$response: res}); + }); + } + }); + + uploader.bind('UploadProgress', function(up, file) { + if(!iAttrs.plProgressModel) { + return; + } + + if(iAttrs.plFilesModel) { + scope.$apply(function() { + scope.sum = 0; + + angular.forEach(scope.plFilesModel, function(file, key) { + scope.sum = scope.sum + file.percent; + }); + + scope.plProgressModel = scope.sum / scope.plFilesModel.length; + }); + } else { + scope.$apply(function() { + scope.plProgressModel = file.percent; + }); + } + + + if(iAttrs.onFileProgress) { + scope.$parent.$eval(iAttrs.onFileProgress); + } + }); + + if(iAttrs.plInstance) { + scope.plInstance = uploader; + } + + scope.$on("$destroy", function() { + uploader.destroy(); + }); - uploader.bind('FileUploaded', function(up, file, res) { - //We are going to make some refactor here. - //The idea behind is always update files with the server response value - //And also launch the eventi if neeed - - //If we have the model... - if(iAttrs.plFilesModel) { - //Apply on scope... - scope.$apply(function() { - - //All files are uploaded? - scope.allUploaded = false; - - angular.forEach(scope.plFilesModel, function($file, key) { - - //Bug FIX, this logic will set allUploaded right - if(file.percent != 100) { - scope.allUploaded = false; - } else if(file.id == $file.id) { //If the file is the same that we are reciving... - //Set response on the file - $file.response = JSON.parse(res.response); - - //Need throw event? throw it - if(iAttrs.onFileUploaded) { - var fn = $parse(iAttrs.onFileUploaded); - fn(scope.$parent, {$response:res}); - } - } - - }); - }); - } - //We doesn't have model but we have the event - else if(!iAttrs.plFilesModel && iAttrs.onFileUploaded) { - var fn = $parse(iAttrs.onFileUploaded); - scope.$apply(function(){ - fn(scope.$parent, {$response:res}); - }); - } - }); - - uploader.bind('UploadProgress',function(up,file){ - if(!iAttrs.plProgressModel){ - return; - } - - if(iAttrs.plFilesModel){ - scope.$apply(function() { - scope.sum = 0; - - angular.forEach(scope.plFilesModel, function(file,key) { - scope.sum = scope.sum + file.percent; - }); - - scope.plProgressModel = scope.sum/scope.plFilesModel.length; - }); - } else { - scope.$apply(function() { - scope.plProgressModel = file.percent; - }); - } - - - if(iAttrs.onFileProgress){ - scope.$parent.$eval(iAttrs.onFileProgress); - } - }); - - if(iAttrs.plInstance){ - scope.plInstance = uploader; - } - - scope.$on("$destroy", function(){ - uploader.destroy(); - }); - - } - }; - }]) + } + }; + }]); +})(); \ No newline at end of file diff --git a/tests/js/plupload-angular-directive.spec.js b/tests/js/plupload-angular-directive.spec.js new file mode 100644 index 0000000..1fca632 --- /dev/null +++ b/tests/js/plupload-angular-directive.spec.js @@ -0,0 +1,406 @@ +describe('plupload-angular-directive', function(){ + + /* + * plUploadService tests + */ + describe('service', function(){ + + beforeEach(module('plupload.directive', function(plUploadServiceProvider){ + plUploadServiceProvider.setConfig('flashPath', 'plupload/flash.swf'); + plUploadServiceProvider.setConfig('silverLightPath', 'plupload/silverlight.xap'); + plUploadServiceProvider.setConfig('uploadPath', 'upload.php'); + })); + + var plUploadService; + beforeEach(inject(function(_plUploadService_){ + plUploadService = _plUploadService_; + })); + + describe('setConfig', function(){ + + it('can set values', function(){ + plUploadService.setConfig('flashPath', 'anotherValue'); + expect(plUploadService.getConfig('flashPath')).not.toBe('plupload/flash.swf'); + }); + }); + + describe('getConfig', function(){ + + it('can retrieve the config', function(){ + expect(plUploadService.getConfig('flashPath')).toBe('plupload/flash.swf'); + expect(plUploadService.getConfig('silverLightPath')).toBe('plupload/silverlight.xap'); + expect(plUploadService.getConfig('uploadPath')).toBe('upload.php'); + }); + + }); + + }); + + describe('directive', function(){ + + var $scope, $compile, $rootScope; + + beforeEach(module('plupload.directive', function(plUploadServiceProvider, $provide){ + plUploadServiceProvider.setConfig('flashPath', 'plupload/flash.swf'); + plUploadServiceProvider.setConfig('silverLightPath', 'plupload/silverlight.xap'); + plUploadServiceProvider.setConfig('uploadPath', 'upload.php'); + + //Mock the plupload + $provide.constant('plupload', PlUploadMock); + })); + + beforeEach(inject(function(_$rootScope_, _$compile_){ + $scope = _$rootScope_.$new(); + $compile = _$compile_; + $rootScope = _$rootScope_; + })); + + /** + * Fn that will help to create the element and compile into the scope + * @param attributes + */ + var createAndCompile = function(attributes) { + + var defaultAttributes = { + 'pl-upload' : '' + }; + + attributes = angular.extend(defaultAttributes, attributes); + + var element = angular.element('