diff --git a/.gitignore b/.gitignore index fa7ccf614b..5e95d2224f 100644 --- a/.gitignore +++ b/.gitignore @@ -79,3 +79,4 @@ # asset cache /.sass-cache/ +/node_modules/ diff --git a/.jshintignore b/.jshintignore new file mode 100644 index 0000000000..55a546e48d --- /dev/null +++ b/.jshintignore @@ -0,0 +1,24 @@ +app/assets/javascripts/raphael.js +app/assets/javascripts/raphael-min.js +app/assets/javascripts/date-de-DE.js +app/assets/javascripts/date-en-US.js +app/assets/javascripts/jstoolbar/**/* +app/assets/javascripts/jquery_noconflict.js +app/assets/javascripts/pages/**/* +app/assets/javascripts/project/**/* +app/assets/javascripts/jquery.menu_expand.js +app/assets/javascripts/jquery-ui-i18n.js + +#we sould fix this ones +app/assets/javascripts/select_list_move.js +app/assets/javascripts/context_menu.js +app/assets/javascripts/breadcrumb.js +app/assets/javascripts/keyboard_shortcuts.js +app/assets/javascripts/openproject.js +app/assets/javascripts/top-shelf.js +app/assets/javascripts/top_menu.js +app/assets/javascripts/findDomElement.js +app/assets/javascripts/application.js +app/assets/javascripts/action_menu.js +app/assets/javascripts/accessibility.js +app/assets/javascripts/repository_navigation.js diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 0000000000..30307ca383 --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,100 @@ +module.exports = function(grunt) { + + var jsPath = "app/assets/javascripts/"; + var testSource = "mocha/index.html"; + // Project configuration. + grunt.initConfig({ + pkg: grunt.file.readJSON('package.json'), + jshint: { + all: { + files: { + src: [jsPath] + } + } + }, + mocha_phantomjs: { + all: [testSource], + small: { + src:[testSource], + options: { + reporter: "dot" + } + }, + cov: { + src:[testSource], + options: { + output: "coverage.json", + reporter: "json-cov" + } + }, + jenkins: { + src:[testSource], + options: { + reporter: "XUnit" + } + } + }, + jscoverage: { + options: { + inputDirectory: 'app/assets/javascripts/', + outputDirectory: 'app/assets/javascripts_cov/' + } + }, + watch: { + scripts: { + files: [jsPath] , + tasks: ['jshint', 'mocha_phantomjs:small'], + options: { + spawn: false, + }, + }, + }, + }); + + grunt.loadNpmTasks("grunt-jscoverage"); + grunt.loadNpmTasks('grunt-contrib-jshint'); + grunt.loadNpmTasks('grunt-contrib-watch'); + grunt.loadNpmTasks('grunt-mocha-phantomjs'); + + // Default task(s). + grunt.registerTask('default', ['jshint']); + + var tempPath = "app/assets/javascripts_temp/"; + var covPath = "app/assets/javascripts_cov/"; + + grunt.registerTask('moveFiles', 'switch directories for coverage-enabled files and normal files', function () { + var fs = require("fs"); + fs.renameSync(jsPath, tempPath); + fs.renameSync(covPath, jsPath); + + + fs.renameSync(tempPath + "date-en-US.js", jsPath + "date-en-US.js"); + }); + + grunt.registerTask('cleanUpCoverage', 'undo moveFiles', function () { + var done = this.async(); + + var fs = require("fs"); + if (fs.existsSync(tempPath)) { + fs.renameSync(jsPath, covPath); + fs.renameSync(tempPath, jsPath); + } + + if (fs.existsSync("coverage.json")) { + fs.unlinkSync("coverage.json"); + } + + rmdir = require("rimraf"); + rmdir(covPath, function (e) { + done(e); + }); + }); + + grunt.registerTask('jsonCov2Html', 'convert coverage to html', function () { + var exec = require("exec"); + exec("cat coverage.json | node_modules/json2htmlcov/bin/json2htmlcov > coverage.html"); + }); + + grunt.registerTask('coverage', ['jscoverage', 'moveFiles', 'mocha_phantomjs:cov', 'jsonCov2Html', 'cleanUpCoverage']); + +}; \ No newline at end of file diff --git a/app/assets/javascripts/members_select_boxes.js b/app/assets/javascripts/members_select_boxes.js index 41220a7f00..c079284dc1 100644 --- a/app/assets/javascripts/members_select_boxes.js +++ b/app/assets/javascripts/members_select_boxes.js @@ -48,11 +48,11 @@ jQuery(document).ready(function($) { markup.push(OpenProject.Helpers.markupEscape( item.name.substring(match + tl, item.name.length))); return markup.join(""); - } + }; formatItemSelection = function (item) { return OpenProject.Helpers.markupEscape(item.name); - } + }; $("#members_add_form select.select2-select").each(function (ix, elem){ if ($(elem).hasClass("remote") || $(elem).attr("data-ajaxURL") !== undefined) { @@ -60,12 +60,12 @@ jQuery(document).ready(function($) { if (!$.isEmptyObject(elem.siblings('div.select2-select.select2-container'))) { setTimeout (function () { var attributes, allowed, currentName, fakeInput; - attributes = {} + attributes = {}; allowed = ["title", "placeholder"]; for(var i = 0; i < $(elem).get(0).attributes.length; i++) { currentName = $(elem).get(0).attributes[i].name; - if(currentName.indexOf("data-") == 0 || $.inArray(currentName, allowed)); //only ones starting with data- + if(currentName.indexOf("data-") === 0 || $.inArray(currentName, allowed)); //only ones starting with data- attributes[currentName] = $(elem).attr(currentName); } fakeInput = $(elem).after("").siblings(":input:first"); @@ -88,7 +88,7 @@ jQuery(document).ready(function($) { }, results: function (data, page) { - active_items = [] + active_items = []; data.results.items.each(function (e) { e.name = $('
').text(e.name).html();
                         active_items.push(e);
@@ -107,10 +107,10 @@ jQuery(document).ready(function($) {
         $(elem).select2();
       }
     });
-  }
+  };
 
   memberstab = $('#tab-members').first();
-  if ((memberstab != null) && (memberstab.hasClass("selected"))) {
+  if ((memberstab !== null) && (memberstab.hasClass("selected"))) {
     init_members_cb();
   } else {
     memberstab.click(init_members_cb);
diff --git a/app/assets/javascripts/timelines.js b/app/assets/javascripts/timelines.js
index 78bd206d19..cbdd857ff7 100644
--- a/app/assets/javascripts/timelines.js
+++ b/app/assets/javascripts/timelines.js
@@ -73,7 +73,7 @@
 
 // environment and other global vars
 /*jshint browser:true, devel:true*/
-/*global jQuery:false, Raphael:false, Timeline:true*/
+/*global jQuery:false, Raphael:false, Timeline:true, modalHelperInstance: true, I18n: true*/
 
 if (typeof Timeline === "undefined") {
   Timeline = {};
diff --git a/app/assets/javascripts/timelines/model/Color.js b/app/assets/javascripts/timelines/model/Color.js
index a2f1cf489d..f76e33196f 100644
--- a/app/assets/javascripts/timelines/model/Color.js
+++ b/app/assets/javascripts/timelines/model/Color.js
@@ -1,4 +1,4 @@
-7//-- copyright
+//-- copyright
 // OpenProject is a project management system.
 // Copyright (C) 2012-2013 the OpenProject Foundation (OPF)
 //
diff --git a/app/assets/javascripts/timelines/model/PlanningElement.js b/app/assets/javascripts/timelines/model/PlanningElement.js
index 8a6848fac3..e1135cf1c0 100644
--- a/app/assets/javascripts/timelines/model/PlanningElement.js
+++ b/app/assets/javascripts/timelines/model/PlanningElement.js
@@ -672,7 +672,7 @@ Timeline.PlanningElement = {
           textColor = timeline.getLimunanceFor(color) > Timeline.PE_LUMINANCE_THRESHOLD ?
                       Timeline.PE_DARK_TEXT_COLOR : Timeline.PE_LIGHT_TEXT_COLOR;
 
-          var text = this.subject;
+          text = this.subject;
           label = timeline.paper.text(0, 0, text);
           label.attr({
             'font-size': 12,
diff --git a/doc/CHANGELOG.md b/doc/CHANGELOG.md
index 840a535ceb..330a7b66a0 100644
--- a/doc/CHANGELOG.md
+++ b/doc/CHANGELOG.md
@@ -49,6 +49,7 @@ See doc/COPYRIGHT.rdoc for more details.
 * `#3854` Move function and Query filters allows to select groups as responsible
 * `#3974` [Timelines] Typo at creating timelines
 * `#4023` [Accessibility] Keep keyboard focus within modal while it's open
+* Add Gruntfile for easier JavaScript testing.
 
 ## 3.0.0pre43
 
diff --git a/package.json b/package.json
new file mode 100644
index 0000000000..9b6cc6a1cd
--- /dev/null
+++ b/package.json
@@ -0,0 +1,16 @@
+{
+  "name": "OpenProject",
+  "version": "0.1.0",
+  "devDependencies": {
+    "grunt": "~0.4.2",
+    "grunt-contrib-jshint": "~0.8.0",
+    "mocha-phantomjs": "~3.1.6",
+    "phantomjs": "~1.9.2",
+    "grunt-contrib-watch": "~0.5.3",
+    "grunt-mocha-phantomjs": "~0.3.2",
+    "grunt-jscoverage": "0.0.3",
+    "rimraf": "~2.2.5",
+    "json2htmlcov": "~0.1.1",
+    "exec": "0.0.6"
+  }
+}