From 4264fc3858d23f7de896d4aa9b25d1d3a768c3f8 Mon Sep 17 00:00:00 2001
From: Shu Muto <shu-mutou@rf.jp.nec.com>
Date: Mon, 12 Sep 2016 16:09:00 +0900
Subject: [PATCH] Setup JavaScript test environment

This patch setups JavaScript test environment.
- eslint
  test at local: `tox -e=eslint`
- karma
  test at local: `tox -e=karma`

Change-Id: I8b34cef1e0b7395a29af77939e534c4a31c76073
Implements: blueprint js-test-env
---
 .eslintrc              |  60 ++++++++++++++++
 .gitignore             |   5 ++
 package.json           |  35 ++++++++++
 test-shim.js           |  97 ++++++++++++++++++++++++++
 tox.ini                |  16 ++++-
 zaqar_ui/karma.conf.js | 155 +++++++++++++++++++++++++++++++++++++++++
 6 files changed, 367 insertions(+), 1 deletion(-)
 create mode 100644 .eslintrc
 create mode 100644 package.json
 create mode 100644 test-shim.js
 create mode 100644 zaqar_ui/karma.conf.js

diff --git a/.eslintrc b/.eslintrc
new file mode 100644
index 0000000..7f915c8
--- /dev/null
+++ b/.eslintrc
@@ -0,0 +1,60 @@
+# Set up globals
+globals:
+  angular: false
+
+extends: openstack
+
+# Most environment options are not explicitly enabled or disabled, only
+# included here for completeness' sake. They are commented out, because the
+# global updates.py script would otherwise override them during a global
+# requirements synchronization.
+#
+# Individual projects should choose which platforms they deploy to.
+
+env:
+  # browser global variables.
+  browser: true
+
+  # Adds all of the Jasmine testing global variables for version 1.3 and 2.0.
+  jasmine: true
+
+# Enable eslint-plugin-angular
+plugins:
+  - angular
+
+# Below we adjust rules specific to horizon's usage of openstack's linting
+# rules, and its own plugin inclusions.
+rules:
+  #############################################################################
+  # Disabled Rules from eslint-config-openstack
+  #############################################################################
+  valid-jsdoc: [1, {
+    requireParamDescription: false
+  }]
+  brace-style: 1
+  block-scoped-var: 1
+  callback-return: 1
+  consistent-return: 1
+  guard-for-in: 1
+  no-extra-parens: 1
+  no-new: 1
+  no-redeclare: 1
+  no-undefined: 1
+  no-unneeded-ternary: 1
+  no-use-before-define: 1
+  quote-props: 0
+  semi-spacing: 1
+  space-in-parens: 1
+
+  #############################################################################
+  # Angular Plugin Customization
+  #############################################################################
+
+  angular/controller-as-vm:
+    - 1
+    - "ctrl"
+
+  # Remove after migrating to angular 1.4 or later.
+  angular/no-cookiestore:
+    - 1
+
diff --git a/.gitignore b/.gitignore
index 95e9d22..6c7bc47 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,11 +1,16 @@
 AUTHORS
 ChangeLog
 build
+cover
 dist
 doc/source/sourcecode
 zaqar_ui/test/.secret_key_store
+node_modules
+npm-debug.log
 .venv
 .tox
+.project
+.pydevproject
 *.pyc
 *.lock
 *.egg*
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..6682754
--- /dev/null
+++ b/package.json
@@ -0,0 +1,35 @@
+{
+  "name": "zaqar-ui",
+  "description": "Zaqar UI JavaScript tests",
+  "repository": {
+    "type": "git",
+    "url": "git://git.openstack.org/openstack/zaqar-ui"
+  },
+  "version": "0.0.0",
+  "private": true,
+  "license": "Apache 2.0",
+  "author": "Openstack <openstack-dev@lists.openstack.org>",
+  "devDependencies": {
+    "eslint": "^1.10.3",
+    "eslint-config-openstack": "^1.2.4",
+    "eslint-plugin-angular": "1.0.1",
+    "jasmine-core": "2.4.1",
+    "karma": "1.1.2",
+    "karma-chrome-launcher": "1.0.1",
+    "karma-cli": "1.0.1",
+    "karma-coverage": "1.1.1",
+    "karma-jasmine": "1.0.2",
+    "karma-ng-html2js-preprocessor": "1.0.0",
+    "karma-phantomjs-launcher": "0.2.0",
+    "karma-threshold-reporter": "0.1.15",
+    "phantomjs": "1.9.17"
+  },
+  "dependencies": {},
+  "scripts": {
+    "postinstall": "if [ ! -d .tox ] || [ ! -d .tox/py27 ]; then tox -epy27 --notest; fi",
+    "lint": "eslint --no-color zaqar_ui/static",
+    "lintq": "eslint --quiet zaqar_ui/static",
+    "test": "karma start zaqar_ui/karma.conf.js --single-run"
+  }
+}
+
diff --git a/test-shim.js b/test-shim.js
new file mode 100644
index 0000000..918ff0d
--- /dev/null
+++ b/test-shim.js
@@ -0,0 +1,97 @@
+/*
+ *  Shim for Javascript unit tests; supplying expected global features.
+ *  This should be removed from the codebase once i18n services are provided.
+ *  Taken from default i18n file provided by Django.
+ */
+
+var horizonPlugInModules = [];
+
+
+(function (globals) {
+
+  var django = globals.django || (globals.django = {});
+
+
+  django.pluralidx = function (count) { return (count == 1) ? 0 : 1; };
+
+  /* gettext identity library */
+
+  django.gettext = function (msgid) { return msgid; };
+  django.ngettext = function (singular, plural, count) { return (count == 1) ? singular : plural; };
+  django.gettext_noop = function (msgid) { return msgid; };
+  django.pgettext = function (context, msgid) { return msgid; };
+  django.npgettext = function (context, singular, plural, count) { return (count == 1) ? singular : plural; };
+
+
+  django.interpolate = function (fmt, obj, named) {
+    if (named) {
+      return fmt.replace(/%\(\w+\)s/g, function(match){return String(obj[match.slice(2,-2)])});
+    } else {
+      return fmt.replace(/%s/g, function(match){return String(obj.shift())});
+    }
+  };
+
+
+  /* formatting library */
+
+  django.formats = {
+    "DATETIME_FORMAT": "N j, Y, P",
+    "DATETIME_INPUT_FORMATS": [
+      "%Y-%m-%d %H:%M:%S",
+      "%Y-%m-%d %H:%M:%S.%f",
+      "%Y-%m-%d %H:%M",
+      "%Y-%m-%d",
+      "%m/%d/%Y %H:%M:%S",
+      "%m/%d/%Y %H:%M:%S.%f",
+      "%m/%d/%Y %H:%M",
+      "%m/%d/%Y",
+      "%m/%d/%y %H:%M:%S",
+      "%m/%d/%y %H:%M:%S.%f",
+      "%m/%d/%y %H:%M",
+      "%m/%d/%y"
+    ],
+    "DATE_FORMAT": "N j, Y",
+    "DATE_INPUT_FORMATS": [
+      "%Y-%m-%d",
+      "%m/%d/%Y",
+      "%m/%d/%y"
+    ],
+    "DECIMAL_SEPARATOR": ".",
+    "FIRST_DAY_OF_WEEK": "0",
+    "MONTH_DAY_FORMAT": "F j",
+    "NUMBER_GROUPING": "3",
+    "SHORT_DATETIME_FORMAT": "m/d/Y P",
+    "SHORT_DATE_FORMAT": "m/d/Y",
+    "THOUSAND_SEPARATOR": ",",
+    "TIME_FORMAT": "P",
+    "TIME_INPUT_FORMATS": [
+      "%H:%M:%S",
+      "%H:%M:%S.%f",
+      "%H:%M"
+    ],
+    "YEAR_MONTH_FORMAT": "F Y"
+  };
+
+  django.get_format = function (format_type) {
+    var value = django.formats[format_type];
+    if (typeof(value) == 'undefined') {
+      return format_type;
+    } else {
+      return value;
+    }
+  };
+
+  /* add to global namespace */
+  globals.pluralidx = django.pluralidx;
+  globals.gettext = django.gettext;
+  globals.ngettext = django.ngettext;
+  globals.gettext_noop = django.gettext_noop;
+  globals.pgettext = django.pgettext;
+  globals.npgettext = django.npgettext;
+  globals.interpolate = django.interpolate;
+  globals.get_format = django.get_format;
+  globals.STATIC_URL = '/static/';
+  globals.WEBROOT = '/';
+
+}(this));
+
diff --git a/tox.ini b/tox.ini
index ef9851e..88d4fbc 100644
--- a/tox.ini
+++ b/tox.ini
@@ -32,11 +32,25 @@ commands =
     pip install django>=1.8,<1.9
     python manage.py test {posargs}
 
+[testenv:eslint]
+whitelist_externals = npm
+commands =
+  npm install
+  npm run {posargs:postinstall}
+  npm run {posargs:lint}
+
+[testenv:karma]
+whitelist_externals = npm
+commands =
+  npm install
+  npm run {posargs:postinstall}
+  npm run {posargs:test}
+
 [testenv:docs]
 commands = python setup.py build_sphinx
 
 [flake8]
-exclude = .venv,.git,.tox,dist,*lib/python*,*egg,build,panel_template,dash_template,local_settings.py,*/local/*,*/test/test_plugins/*,.ropeproject
+exclude = .venv,.git,.tox,dist,*lib/python*,*egg,build,panel_template,dash_template,local_settings.py,*/local/*,*/test/test_plugins/*,.ropeproject,node_modules
 max-complexity = 20
 
 [hacking]
diff --git a/zaqar_ui/karma.conf.js b/zaqar_ui/karma.conf.js
new file mode 100644
index 0000000..19c7cf0
--- /dev/null
+++ b/zaqar_ui/karma.conf.js
@@ -0,0 +1,155 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the 'License');
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an 'AS IS' BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+var fs = require('fs');
+var path = require('path');
+
+module.exports = function (config) {
+  // This tox venv is setup in the post-install npm step
+  var toxPath = '../.tox/py27/lib/python2.7/site-packages/';
+
+  config.set({
+    preprocessors: {
+      // Used to collect templates for preprocessing.
+      // NOTE: the templates must also be listed in the files section below.
+      './static/**/*.html': ['ng-html2js'],
+      // Used to indicate files requiring coverage reports.
+      './static/**/!(*.spec).js': ['coverage'],
+    },
+
+    // Sets up module to process templates.
+    ngHtml2JsPreprocessor: {
+      prependPrefix: '/',
+      moduleName: 'templates'
+    },
+
+    basePath: './',
+
+    // Contains both source and test files.
+    files: [
+      /*
+       * shim, partly stolen from /i18n/js/horizon/
+       * Contains expected items not provided elsewhere (dynamically by
+       * Django or via jasmine template.
+       */
+      '../test-shim.js',
+
+      // from jasmine.html
+      toxPath + 'xstatic/pkg/jquery/data/jquery.js',
+      toxPath + 'xstatic/pkg/angular/data/angular.js',
+      toxPath + 'xstatic/pkg/angular/data/angular-route.js',
+      toxPath + 'xstatic/pkg/angular/data/angular-mocks.js',
+      toxPath + 'xstatic/pkg/angular/data/angular-cookies.js',
+      toxPath + 'xstatic/pkg/angular_bootstrap/data/angular-bootstrap.js',
+      toxPath + 'xstatic/pkg/angular_gettext/data/angular-gettext.js',
+      toxPath + 'xstatic/pkg/angular/data/angular-sanitize.js',
+      toxPath + 'xstatic/pkg/d3/data/d3.js',
+      toxPath + 'xstatic/pkg/rickshaw/data/rickshaw.js',
+      toxPath + 'xstatic/pkg/angular_smart_table/data/smart-table.js',
+      toxPath + 'xstatic/pkg/angular_lrdragndrop/data/lrdragndrop.js',
+      toxPath + 'xstatic/pkg/spin/data/spin.js',
+      toxPath + 'xstatic/pkg/spin/data/spin.jquery.js',
+      toxPath + 'xstatic/pkg/tv4/data/tv4.js',
+      toxPath + 'xstatic/pkg/objectpath/data/ObjectPath.js',
+      toxPath + 'xstatic/pkg/angular_schema_form/data/schema-form.js',
+
+      // TODO: These should be mocked.
+      toxPath + '/horizon/static/horizon/js/horizon.js',
+
+      /**
+       * Include framework source code from horizon that we need.
+       * Otherwise, karma will not be able to find them when testing.
+       * These files should be mocked in the foreseeable future.
+       */
+      toxPath + 'horizon/static/framework/**/*.module.js',
+      toxPath + 'horizon/static/framework/**/!(*.spec|*.mock).js',
+      toxPath + 'openstack_dashboard/static/**/*.module.js',
+      toxPath + 'openstack_dashboard/static/**/!(*.spec|*.mock).js',
+      toxPath + 'openstack_dashboard/dashboards/**/static/**/*.module.js',
+      toxPath + 'openstack_dashboard/dashboards/**/static/**/!(*.spec|*.mock).js',
+
+      /**
+       * First, list all the files that defines application's angular modules.
+       * Those files have extension of `.module.js`. The order among them is
+       * not significant.
+       */
+      './static/**/*.module.js',
+
+      /**
+       * Followed by other JavaScript files that defines angular providers
+       * on the modules defined in files listed above. And they are not mock
+       * files or spec files defined below. The order among them is not
+       * significant.
+       */
+      './static/**/!(*.spec|*.mock).js',
+
+      /**
+       * Then, list files for mocks with `mock.js` extension. The order
+       * among them should not be significant.
+       */
+      toxPath + 'openstack_dashboard/static/**/*.mock.js',
+
+      /**
+       * Finally, list files for spec with `spec.js` extension. The order
+       * among them should not be significant.
+       */
+      './static/**/*.spec.js',
+
+      /**
+       * Angular external templates
+       */
+      './static/**/*.html'
+    ],
+
+    autoWatch: true,
+
+    frameworks: ['jasmine'],
+
+    browsers: ['PhantomJS'],
+
+    browserNoActivityTimeout: 60000,
+
+    phantomjsLauncher: {
+      // Have phantomjs exit if a ResourceError is encountered
+      // (useful if karma exits without killing phantom)
+      exitOnResourceError: true
+    },
+
+    reporters: ['progress', 'coverage', 'threshold'],
+
+    plugins: [
+      'karma-phantomjs-launcher',
+      'karma-jasmine',
+      'karma-ng-html2js-preprocessor',
+      'karma-coverage',
+      'karma-threshold-reporter'
+    ],
+
+    // Places coverage report in HTML format in the subdirectory below.
+    coverageReporter: {
+      type: 'html',
+      dir: '../cover/karma/'
+    },
+
+    // Coverage threshold values.
+    thresholdReporter: {
+      statements: 10, // target 100
+      branches: 0, // target 100
+      functions: 10, // target 100
+      lines: 10 // target 100
+    }
+  });
+};