Get unit tests working with Karma
Establish features for running Karma tests. Separate karma configurations are used due to current overlap of template file structure. You can run the karma tests via: ./run_tests.sh --karma The Karma framework allows for many features like unit test coverage and provides a common configuration for different test runners. Change-Id: I79680ef6369383c148da68e6677945886a48df81 Implements: blueprint karma
This commit is contained in:
parent
8ae838c836
commit
241c2cf432
2
.gitignore
vendored
2
.gitignore
vendored
@ -24,6 +24,8 @@ doc/source/sourcecode
|
||||
/static/
|
||||
.venv
|
||||
.tox
|
||||
node_modules
|
||||
npm-debug.log
|
||||
build
|
||||
dist
|
||||
AUTHORS
|
||||
|
@ -134,6 +134,21 @@ Available options:
|
||||
the dashboard module's directory structure. Default: A new directory within
|
||||
the current directory.
|
||||
|
||||
JavaScript
|
||||
----------
|
||||
|
||||
You can also run JavaScript unit tests using Karma. Karma is a test
|
||||
environment that allows for multiple test runners and reporters, including
|
||||
such features as code coverage. Karma allows developer to run tests live,
|
||||
as it can watch source and test files for changes.
|
||||
|
||||
To run the Karma tests for Horizon and Dashboard::
|
||||
|
||||
./run_tests.sh --karma
|
||||
|
||||
The default configuration also performs coverage reports, which are saved
|
||||
to ``horizon/.coverage-karma/`` and ``openstack_dashboard/.coverage-karma/``.
|
||||
|
||||
Give me metrics!
|
||||
================
|
||||
|
||||
|
99
horizon/karma.conf.js
Normal file
99
horizon/karma.conf.js
Normal file
@ -0,0 +1,99 @@
|
||||
module.exports = function(config){
|
||||
|
||||
// Path to xstatic pkg path.
|
||||
var xstaticPath = '../../.venv/lib/python2.7/site-packages/xstatic/pkg/';
|
||||
|
||||
config.set({
|
||||
|
||||
preprocessors: {
|
||||
// Used to collect templates for preprocessing.
|
||||
// NOTE: the templates must also be listed in the files section below.
|
||||
'./**/*.html': ['ng-html2js'],
|
||||
// Used to indicate files requiring coverage reports.
|
||||
'./**/!(*spec).js': ['coverage']
|
||||
},
|
||||
|
||||
// Sets up module to process templates.
|
||||
ngHtml2JsPreprocessor: {
|
||||
prependPrefix: '/static/',
|
||||
moduleName: 'templates'
|
||||
},
|
||||
|
||||
// Assumes you're in the top-level horizon directory.
|
||||
basePath : './static/',
|
||||
|
||||
// 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
|
||||
xstaticPath + 'jquery/data/jquery.js',
|
||||
xstaticPath + 'angular/data/angular.js',
|
||||
xstaticPath + 'angular/data/angular-mocks.js',
|
||||
xstaticPath + 'angular/data/angular-cookies.js',
|
||||
xstaticPath + 'angular_bootstrap/data/angular-bootstrap.js',
|
||||
xstaticPath + 'angular/data/angular-sanitize.js',
|
||||
xstaticPath + 'd3/data/d3.js',
|
||||
xstaticPath + 'rickshaw/data/rickshaw.js',
|
||||
xstaticPath + 'angular_smart_table/data/smart-table.js',
|
||||
xstaticPath + 'angular_lrdragndrop/data/lrdragndrop.js',
|
||||
xstaticPath + 'spin/data/spin.js',
|
||||
xstaticPath + 'spin/data/spin.jquery.js',
|
||||
|
||||
|
||||
// from jasmine_tests.py; only those that are deps for others
|
||||
'horizon/js/horizon.js',
|
||||
'horizon/js/angular/hz.api.module.js',
|
||||
'horizon/js/angular/services/**/*.js',
|
||||
'horizon/js/angular/hz.api.module.js',
|
||||
'dashboard-app/dashboard-app.module.js',
|
||||
'dashboard-app/utils/utils.module.js',
|
||||
'dashboard-app/**/*.js',
|
||||
'framework/framework.module.js',
|
||||
'framework/widgets/charts/charts.js',
|
||||
'framework/widgets/metadata-tree/metadata-tree.js',
|
||||
'framework/widgets/table/table.js',
|
||||
|
||||
// Catch-all for stuff that isn't required explicitly by others.
|
||||
'framework/**/!(*spec).js',
|
||||
|
||||
// Templates.
|
||||
'./**/*.html',
|
||||
|
||||
// TESTS
|
||||
'**/*.spec.js',
|
||||
],
|
||||
|
||||
autoWatch : true,
|
||||
|
||||
frameworks: ['jasmine'],
|
||||
|
||||
browsers : ['PhantomJS'],
|
||||
|
||||
phantomjsLauncher: {
|
||||
// Have phantomjs exit if a ResourceError is encountered
|
||||
// (useful if karma exits without killing phantom)
|
||||
exitOnResourceError: true
|
||||
},
|
||||
|
||||
reporters : [ 'progress', 'coverage' ],
|
||||
|
||||
plugins : [
|
||||
'karma-phantomjs-launcher',
|
||||
'karma-jasmine',
|
||||
'karma-ng-html2js-preprocessor',
|
||||
'karma-coverage'
|
||||
],
|
||||
|
||||
coverageReporter: {
|
||||
type : 'html',
|
||||
dir : '../.coverage-karma/'
|
||||
}
|
||||
|
||||
});
|
||||
};
|
||||
|
85
openstack_dashboard/karma.conf.js
Normal file
85
openstack_dashboard/karma.conf.js
Normal file
@ -0,0 +1,85 @@
|
||||
module.exports = function(config){
|
||||
|
||||
// Path to xstatic pkg path.
|
||||
var xstaticPath = '../../.venv/lib/python2.7/site-packages/xstatic/pkg/';
|
||||
|
||||
config.set({
|
||||
|
||||
preprocessors: {
|
||||
// Used to collect templates for preprocessing.
|
||||
// NOTE: the templates must also be listed in the files section below.
|
||||
'./**/*.html': ['ng-html2js'],
|
||||
// Used to indicate files requiring coverage reports.
|
||||
'./**/!(*spec).js': ['coverage']
|
||||
},
|
||||
|
||||
// Sets up module to process templates.
|
||||
ngHtml2JsPreprocessor: {
|
||||
prependPrefix: '/static/',
|
||||
moduleName: 'templates'
|
||||
},
|
||||
|
||||
// Assumes you're in the top-level horizon directory.
|
||||
basePath : './static/',
|
||||
|
||||
// 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
|
||||
xstaticPath + 'jquery/data/jquery.js',
|
||||
xstaticPath + 'angular/data/angular.js',
|
||||
xstaticPath + 'angular/data/angular-mocks.js',
|
||||
xstaticPath + 'angular/data/angular-cookies.js',
|
||||
xstaticPath + 'angular_bootstrap/data/angular-bootstrap.js',
|
||||
xstaticPath + 'angular/data/angular-sanitize.js',
|
||||
xstaticPath + 'd3/data/d3.js',
|
||||
xstaticPath + 'rickshaw/data/rickshaw.js',
|
||||
xstaticPath + 'angular_lrdragndrop/data/lrdragndrop.js',
|
||||
// Needed by modal spinner
|
||||
xstaticPath + 'spin/data/spin.js',
|
||||
xstaticPath + 'spin/data/spin.jquery.js',
|
||||
|
||||
// This one seems to have to come first.
|
||||
"dashboard/dashboard.module.js",
|
||||
"dashboard/workflow/workflow.js",
|
||||
"dashboard/launch-instance/launch-instance.js",
|
||||
"dashboard/**/*.js",
|
||||
|
||||
// Templates.
|
||||
'./**/*.html',
|
||||
],
|
||||
|
||||
autoWatch : true,
|
||||
|
||||
frameworks: ['jasmine'],
|
||||
|
||||
browsers : ['PhantomJS'],
|
||||
|
||||
phantomjsLauncher: {
|
||||
// Have phantomjs exit if a ResourceError is encountered
|
||||
// (useful if karma exits without killing phantom)
|
||||
exitOnResourceError: true
|
||||
},
|
||||
|
||||
reporters : [ 'progress', 'coverage' ],
|
||||
|
||||
plugins : [
|
||||
'karma-phantomjs-launcher',
|
||||
'karma-jasmine',
|
||||
'karma-ng-html2js-preprocessor',
|
||||
'karma-coverage'
|
||||
],
|
||||
|
||||
coverageReporter: {
|
||||
type : 'html',
|
||||
dir : '../.coverage-karma/'
|
||||
}
|
||||
|
||||
});
|
||||
};
|
||||
|
22
package.json
Normal file
22
package.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"name": "horizon",
|
||||
"description": "OpenStack Horizon - Angular",
|
||||
"repository": "none",
|
||||
"license": "Apache 2.0",
|
||||
"devDependencies": {
|
||||
"jasmine-core": "2.2.0",
|
||||
"karma": "0.12.31",
|
||||
"karma-chrome-launcher": "0.1.8",
|
||||
"karma-coverage": "0.3.1",
|
||||
"karma-jasmine": "0.3.5",
|
||||
"karma-ng-html2js-preprocessor": "0.1.2",
|
||||
"karma-phantomjs-launcher": "0.2.0",
|
||||
"phantomjs": "^1.9.17"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "node node_modules/karma/bin/karma start horizon/karma.conf.js --single-run && node node_modules/karma/bin/karma start openstack_dashboard/karma.conf.js --single-run"
|
||||
},
|
||||
"dependencies": {}
|
||||
}
|
15
run_tests.sh
15
run_tests.sh
@ -28,6 +28,7 @@ function usage {
|
||||
echo " -y, --pylint Just run pylint"
|
||||
echo " -j, --jshint Just run jshint"
|
||||
echo " -s, --jscs Just run jscs"
|
||||
echo " -k, --karma Just run karma"
|
||||
echo " -q, --quiet Run non-interactively. (Relatively) quiet."
|
||||
echo " Implies -V if -N is not set."
|
||||
echo " --only-selenium Run only the Selenium unit tests"
|
||||
@ -72,6 +73,7 @@ just_docs=0
|
||||
just_tabs=0
|
||||
just_jscs=0
|
||||
just_jshint=0
|
||||
just_karma=0
|
||||
never_venv=0
|
||||
quiet=0
|
||||
restore_env=0
|
||||
@ -109,6 +111,7 @@ function process_option {
|
||||
-y|--pylint) just_pylint=1;;
|
||||
-j|--jshint) just_jshint=1;;
|
||||
-s|--jscs) just_jscs=1;;
|
||||
-k|--karma) just_karma=1;;
|
||||
-f|--force) force=1;;
|
||||
-t|--tabs) just_tabs=1;;
|
||||
-q|--quiet) quiet=1;;
|
||||
@ -174,6 +177,12 @@ function run_jscs {
|
||||
fi
|
||||
}
|
||||
|
||||
function run_karma {
|
||||
echo "Running karma ..."
|
||||
npm install
|
||||
npm run test
|
||||
}
|
||||
|
||||
function warn_on_flake8_without_venv {
|
||||
set +o errexit
|
||||
${command_wrapper} python -c "import hacking" 2>/dev/null
|
||||
@ -578,6 +587,12 @@ if [ $just_jscs -eq 1 ]; then
|
||||
exit $?
|
||||
fi
|
||||
|
||||
# Karma
|
||||
if [ $just_karma -eq 1 ]; then
|
||||
run_karma
|
||||
exit $?
|
||||
fi
|
||||
|
||||
# Tab checker
|
||||
if [ $just_tabs -eq 1 ]; then
|
||||
tab_check
|
||||
|
98
test-shim.js
Normal file
98
test-shim.js
Normal file
@ -0,0 +1,98 @@
|
||||
/*
|
||||
* 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 angularModuleExtension = [];
|
||||
|
||||
|
||||
(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;
|
||||
|
||||
}(this));
|
||||
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user