Merge pull request #305 from joefiorini/post-2.2--nodeTests

Post 2.2 - improve JS testing workflow
This commit is contained in:
Joe Fiorini
2015-07-21 12:16:43 -04:00
65 changed files with 511 additions and 477 deletions

2
.gitignore vendored
View File

@@ -49,6 +49,8 @@ npm-debug.log
coverage.xml coverage.xml
htmlcov htmlcov
pep8.txt pep8.txt
scratch
testem.log
# Mac OS X # Mac OS X
*.DS_Store *.DS_Store

View File

@@ -3,6 +3,7 @@ SITELIB=$(shell $(PYTHON) -c "from distutils.sysconfig import get_python_lib; pr
OFFICIAL ?= no OFFICIAL ?= no
PACKER ?= packer PACKER ?= packer
GRUNT ?= $(shell [ -t 0 ] && echo "grunt" || echo "grunt --no-color") GRUNT ?= $(shell [ -t 0 ] && echo "grunt" || echo "grunt --no-color")
TESTEM ?= ./node_modules/.bin/testem
BROCCOLI ?= ./node_modules/.bin/broccoli BROCCOLI ?= ./node_modules/.bin/broccoli
NODE ?= node NODE ?= node
@@ -293,8 +294,8 @@ reports/ui_code: node_modules clean-ui Brocfile.js bower.json Gruntfile.js
$(BROCCOLI) build reports/ui_code -- --no-concat --no-tests --no-styles --no-sourcemaps $(BROCCOLI) build reports/ui_code -- --no-concat --no-tests --no-styles --no-sourcemaps
# Run UI unit tests # Run UI unit tests
test_ui: node_modules minjs_ci Gruntfile.js test_ui: node_modules minjs_ci
$(GRUNT) karma:ci $(TESTEM) ci --file testem.yml -R xunit
# Run API unit tests across multiple Python/Django versions with Tox. # Run API unit tests across multiple Python/Django versions with Tox.
test_tox: test_tox:

View File

@@ -38,6 +38,7 @@ import mainMenu from 'tower/main-menu/main';
import browserData from 'tower/browser-data/main'; import browserData from 'tower/browser-data/main';
import dashboard from 'tower/dashboard/main'; import dashboard from 'tower/dashboard/main';
import moment from 'tower/shared/moment/main'; import moment from 'tower/shared/moment/main';
import templateUrl from 'tower/shared/template-url/main';
import {JobDetailController} from 'tower/controllers/JobDetail'; import {JobDetailController} from 'tower/controllers/JobDetail';
import {JobStdoutController} from 'tower/controllers/JobStdout'; import {JobStdoutController} from 'tower/controllers/JobStdout';
@@ -84,6 +85,7 @@ var tower = angular.module('Tower', [
mainMenu.name, mainMenu.name,
dashboard.name, dashboard.name,
moment.name, moment.name,
templateUrl.name,
'AuthService', 'AuthService',
'Utilities', 'Utilities',
'LicenseHelper', 'LicenseHelper',

View File

@@ -1,14 +1,14 @@
/* jshint unused: vars */ /* jshint unused: vars */
export default export default
[ '$rootScope', [ 'templateUrl',
function() { function(templateUrl) {
return { return {
restrict: 'E', restrict: 'E',
scope: { scope: {
data: '=' data: '='
}, },
replace: false, replace: false,
templateUrl: '/static/js/dashboard/counts/dashboard-counts.partial.html', templateUrl: templateUrl('dashboard/counts/dashboard-counts'),
link: function(scope, element, attrs) { link: function(scope, element, attrs) {
scope.$watch("data", function(data) { scope.$watch("data", function(data) {
if (data && data.hosts) { if (data && data.hosts) {

View File

@@ -1,11 +1,11 @@
/* jshint unused: vars */ /* jshint unused: vars */
export default export default
[ '$rootScope', [ 'templateUrl',
function() { function(templateUrl) {
return { return {
restrict: 'E', restrict: 'E',
scope: true, scope: true,
templateUrl: '/static/js/dashboard/dashboard.partial.html', templateUrl: templateUrl('dashboard/dashboard'),
link: function(scope, element, attrs) { link: function(scope, element, attrs) {
} }
}; };

View File

@@ -1,11 +1,11 @@
/* jshint unused: vars */ /* jshint unused: vars */
export default export default
[ '$rootScope', [ 'templateUrl',
function() { function(templateUrl) {
return { return {
restrict: 'E', restrict: 'E',
scope: true, scope: true,
templateUrl: '/static/js/dashboard/graphs/dashboard-graphs.partial.html', templateUrl: templateUrl('dashboard/graphs/dashboard-graphs'),
link: function(scope, element, attrs) { link: function(scope, element, attrs) {
function clearGraphs() { function clearGraphs() {
scope.jobStatusSelected = false; scope.jobStatusSelected = false;

View File

@@ -8,14 +8,15 @@
[ '$compile', [ '$compile',
'$window', '$window',
'adjustGraphSize', 'adjustGraphSize',
'templateUrl',
HostStatusGraph, HostStatusGraph,
]; ];
function HostStatusGraph($compile, $window, adjustGraphSize) { function HostStatusGraph($compile, $window, adjustGraphSize, templateUrl) {
return { return {
restrict: 'E', restrict: 'E',
link: link, link: link,
templateUrl: '/static/js/dashboard/graphs/host-status/host_status_graph.partial.html' templateUrl: templateUrl('dashboard/graphs/host-status/host_status_graph')
}; };
function link(scope, element, attr) { function link(scope, element, attr) {

View File

@@ -12,16 +12,17 @@
'Wait', 'Wait',
'adjustGraphSize', 'adjustGraphSize',
'jobStatusGraphData', 'jobStatusGraphData',
'templateUrl',
JobStatusGraph JobStatusGraph
]; ];
function JobStatusGraph($rootScope, $compile , $location, $window, Wait, adjustGraphSize, graphDataService) { function JobStatusGraph($rootScope, $compile , $location, $window, Wait, adjustGraphSize, graphDataService, templateUrl) {
return { return {
restrict: 'E', restrict: 'E',
scope: { scope: {
data: '=' data: '='
}, },
templateUrl: '/static/js/dashboard/graphs/job-status/job_status_graph.partial.html', templateUrl: templateUrl('dashboard/graphs/job-status/job_status_graph'),
link: link link: link
}; };

View File

@@ -1,8 +1,8 @@
import JobStatusGraphDirective from 'tower/dashboard/graphs/job-status/job-status-graph.directive'; import JobStatusGraphDirective from 'tower/dashboard/graphs/job-status/job-status-graph.directive';
import JobStatusGraphService from 'tower/dashboard/graphs/job-status/job-status-graph.service'; import JobStatusGraphService from 'tower/dashboard/graphs/job-status/job-status-graph.service';
import DashboardGraphHelpers from 'tower/dashboard/graphs/graph-helpers/main'; import DashboardGraphHelpers from 'tower/dashboard/graphs/graph-helpers/main';
import ApiLoader from 'tower/shared/api-loader'; import templateUrl from 'tower/shared/template-url/main';
export default angular.module('JobStatusGraph', [DashboardGraphHelpers.name, ApiLoader.name]) export default angular.module('JobStatusGraph', [DashboardGraphHelpers.name, templateUrl.name])
.directive('jobStatusGraph', JobStatusGraphDirective) .directive('jobStatusGraph', JobStatusGraphDirective)
.service('jobStatusGraphData', JobStatusGraphService); .service('jobStatusGraphData', JobStatusGraphService);

View File

@@ -1,14 +1,15 @@
/* jshint unused: vars */ /* jshint unused: vars */
export default export default
[ "PlaybookRun", [ "PlaybookRun",
function JobTemplatesList(PlaybookRun) { 'templateUrl',
function JobTemplatesList(PlaybookRun, templateUrl) {
return { return {
restrict: 'E', restrict: 'E',
link: link, link: link,
scope: { scope: {
data: '=' data: '='
}, },
templateUrl: '/static/js/dashboard/lists/job-templates/job-templates-list.partial.html' templateUrl: templateUrl('dashboard/lists/job-templates/job-templates-list')
}; };
function link(scope, element, attr) { function link(scope, element, attr) {

View File

@@ -1,43 +1,44 @@
/* jshint unused: vars */ /* jshint unused: vars */
export default export default
['moment', [ 'moment',
function JobsList(moment) { 'templateUrl',
return { function JobsList(moment, templateUrl) {
restrict: 'E', return {
link: link, restrict: 'E',
scope: { link: link,
data: '=' scope: {
}, data: '='
templateUrl: '/static/js/dashboard/lists/jobs/jobs-list.partial.html' },
}; templateUrl: templateUrl('dashboard/lists/jobs/jobs-list')
function link(scope, element, attr) {
scope.$watch("data", function(data) {
if (data) {
if (data.length > 0) {
createList(data);
scope.noJobs = false;
} else {
scope.noJobs = true;
}
}
});
function createList(list) {
// detailsUrl, status, name, time
scope.jobs = _.map(list, function(job){
return {
detailsUrl: job.url.replace("api/v1", "#"),
status: job.status,
name: job.name,
time: moment(job.finished).fromNow()
}; });
scope.snapRows = (list.length < 4);
}
scope.isSuccessful = function (status) {
return (status === "successful");
}; };
}
function link(scope, element, attr) {
scope.$watch("data", function(data) {
if (data) {
if (data.length > 0) {
createList(data);
scope.noJobs = false;
} else {
scope.noJobs = true;
}
}
});
function createList(list) {
// detailsUrl, status, name, time
scope.jobs = _.map(list, function(job){
return {
detailsUrl: job.url.replace("api/v1", "#"),
status: job.status,
name: job.name,
time: moment(job.finished).fromNow()
}; });
scope.snapRows = (list.length < 4);
}
scope.isSuccessful = function (status) {
return (status === "successful");
};
}
}]; }];

View File

@@ -1,15 +1,18 @@
export default function() { export default
return { [ 'templateUrl',
restrict: 'E', function(templateUrl) {
templateUrl: '/static/js/main-menu/menu-default.partial.html', return {
link: function(scope, element) { restrict: 'E',
var contents = element.contents(); templateUrl: templateUrl('main-menu/menu-default'),
contents.unwrap(); link: function(scope, element) {
var contents = element.contents();
contents.unwrap();
scope.$on('$destroy', function() { scope.$on('$destroy', function() {
contents.remove(); contents.remove();
$(".MenuItem--socketStatus").remove(); $(".MenuItem--socketStatus").remove();
}); });
}
};
} }
}; ];
}

View File

@@ -1,45 +1,48 @@
/* jshint unused: vars */ /* jshint unused: vars */
export default function() { export default
return { [ 'templateUrl',
restrict: 'E', function(templateUrl) {
controllerAs: 'mainMenu', return {
templateUrl: '/static/js/main-menu/main-menu.partial.html', restrict: 'E',
controller: ['$scope', function($scope) { controllerAs: 'mainMenu',
this.open = function() { templateUrl: templateUrl('main-menu/main-menu'),
$scope.isOpen = true; controller: ['$scope', function($scope) {
}; this.open = function() {
$scope.isOpen = true;
};
this.close = function() { this.close = function() {
$scope.isOpen = false; $scope.isOpen = false;
}; };
this.toggle = function() { this.toggle = function() {
$scope.isOpen = !$scope.isOpen; $scope.isOpen = !$scope.isOpen;
}; };
$scope.isOpen = false; $scope.isOpen = false;
}], }],
scope: { scope: {
menuStyle: '&menuStyle', menuStyle: '&menuStyle',
currentUser: '=' currentUser: '='
}, },
link: function(scope, element, attrs) { link: function(scope, element, attrs) {
scope.menuStyleClassName = 'blah'; scope.menuStyleClassName = 'blah';
scope.$watch(function() { scope.$watch(function() {
return scope.$eval(scope.menuStyle); return scope.$eval(scope.menuStyle);
}, function(newValue) { }, function(newValue) {
scope.menuStyleClassName = 'MainMenu--' + newValue; scope.menuStyleClassName = 'MainMenu--' + newValue;
}); });
scope.$watch('isOpen', function(isOpen) { scope.$watch('isOpen', function(isOpen) {
if (isOpen) { if (isOpen) {
element.find('.MainMenu').addClass("Menu--open"); element.find('.MainMenu').addClass("Menu--open");
element.find('menu-toggle-button').addClass("MenuToggle--open"); element.find('menu-toggle-button').addClass("MenuToggle--open");
} else { } else {
element.find('.MainMenu').removeClass("Menu--open"); element.find('.MainMenu').removeClass("Menu--open");
element.find('menu-toggle-button').removeClass("MenuToggle--open"); element.find('menu-toggle-button').removeClass("MenuToggle--open");
}
});
} }
}); };
} }
}; ];
}

View File

@@ -1,27 +1,30 @@
/* jshint unused: vars */ /* jshint unused: vars */
export default function() { export default
return { [ 'templateUrl',
templateUrl: '/static/js/main-menu/menu-toggle.partial.html', function(templateUrl) {
restrict: 'E', return {
require: '^^mainMenu', templateUrl: templateUrl('main-menu/menu-toggle'),
scope: { restrict: 'E',
width: '@', require: '^^mainMenu',
height: '@', scope: {
barHeight: '@' width: '@',
}, height: '@',
link: function(scope, element, attrs, mainMenuController) { barHeight: '@'
scope.$on('$destroy', function() { },
element.off('click'); link: function(scope, element, attrs, mainMenuController) {
}); scope.$on('$destroy', function() {
element.off('click');
});
element.on("click", function(e) { element.on("click", function(e) {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
scope.$apply(function() { scope.$apply(function() {
mainMenuController.toggle(); mainMenuController.toggle();
}); });
}); });
}
};
} }
}; ];
}

View File

@@ -1,15 +1,18 @@
export default function() { export default
return { [ 'templateUrl',
restrict: 'E', function(templateUrl) {
templateUrl: '/static/js/main-menu/menu-portal.partial.html', return {
link: function(scope, element) { restrict: 'E',
var contents = element.contents(); templateUrl: templateUrl('main-menu/menu-portal'),
contents.unwrap(); link: function(scope, element) {
var contents = element.contents();
contents.unwrap();
scope.$on('$destroy', function() { scope.$on('$destroy', function() {
contents.remove(); contents.remove();
$(".MenuItem--socketStatus").remove(); $(".MenuItem--socketStatus").remove();
}); });
}
};
} }
}; ];
}

View File

@@ -1,18 +1,22 @@
/* jshint unused: vars */ /* jshint unused: vars */
export default ['$rootScope', function($rootScope) { export default
return { [ '$rootScope',
restrict: 'E', 'templateUrl',
templateUrl: '/static/js/main-menu/web-socket-status.partial.html', function($rootScope, templateUrl) {
link: function(scope, element, attrs) { return {
scope.socketHelp = $rootScope.socketHelp; restrict: 'E',
scope.socketTip = $rootScope.socketTip; templateUrl: templateUrl('main-menu/web-socket-status'),
$rootScope.$watch('socketStatus', function(newStatus) { link: function(scope, element, attrs) {
scope.socketStatus = newStatus; scope.socketHelp = $rootScope.socketHelp;
}); scope.socketTip = $rootScope.socketTip;
$rootScope.$watch('socketTip', function(newTip) { $rootScope.$watch('socketStatus', function(newStatus) {
scope.socketTip = newTip; scope.socketStatus = newStatus;
}); });
$rootScope.$watch('socketTip', function(newTip) {
scope.socketTip = newTip;
});
}
};
} }
}; ];
}];

View File

@@ -9,30 +9,33 @@
import controller from './breadcrumbs.controller'; import controller from './breadcrumbs.controller';
import 'tower/shared/generator-helpers'; import 'tower/shared/generator-helpers';
export default function() { export default
[ 'templateUrl',
function(templateUrl) {
return { return {
restrict: 'E', restrict: 'E',
controller: controller, controller: controller,
transclude: true, transclude: true,
templateUrl: '/static/js/shared/breadcrumbs/breadcrumbs.partial.html', templateUrl: templateUrl('shared/breadcrumbs/breadcrumbs'),
scope: { scope: {
}, },
link: function(scope, element, attrs, controller) { link: function(scope, element, attrs, controller) {
// make breadcrumbs hidden until the current // make breadcrumbs hidden until the current
// breadcrumb has a title; this avoids // breadcrumb has a title; this avoids
// ugly rendering when an object's title // ugly rendering when an object's title
// is fetched via ajax // is fetched via ajax
// //
controller.setHidden(); controller.setHidden();
scope.$watch('isHidden', function(value) { scope.$watch('isHidden', function(value) {
if (value) { if (value) {
element.hide(); element.hide();
} else { } else {
element.show(); element.show();
}
});
} }
}); };
} }
}; ];
}

View File

@@ -1,29 +1,32 @@
export default function() { export default
return { [ 'templateUrl',
restrict: 'E', function(templateUrl) {
templateUrl: '/static/js/shared/icon/icon.partial.html', return {
scope: { restrict: 'E',
}, templateUrl: templateUrl('shared/icon/icon'),
link: function(scope, element, attrs) { scope: {
var svg = $('svg', element); },
var iconPath = '#' + attrs.name; link: function(scope, element, attrs) {
var svg = $('svg', element);
var iconPath = '#' + attrs.name;
// Make a copy of the <symbol> tag to insert its contents into this // Make a copy of the <symbol> tag to insert its contents into this
// element's svg tag // element's svg tag
var content = $(iconPath).clone(); var content = $(iconPath).clone();
// Copy classes & viewBox off the <symbol> so that we preserve any styling // Copy classes & viewBox off the <symbol> so that we preserve any styling
// when we copy the item inline // when we copy the item inline
var classes = $(iconPath).attr('class'); var classes = $(iconPath).attr('class');
// viewBox needs to be access via native // viewBox needs to be access via native
// javascript's setAttribute function // javascript's setAttribute function
var viewBox = $(iconPath)[0].getAttribute('viewBox'); var viewBox = $(iconPath)[0].getAttribute('viewBox');
svg[0].setAttribute('viewBox', viewBox); svg[0].setAttribute('viewBox', viewBox);
svg.attr('class', classes) svg.attr('class', classes)
.html(content.contents()); .html(content.contents());
}
};
} }
}; ];
}

View File

@@ -7,9 +7,9 @@ function moment() {
// lists the user's preferred languages, the first in the array // lists the user's preferred languages, the first in the array
// being the user's top choice. navigator.languages is currently // being the user's top choice. navigator.languages is currently
// comptabile with chrome>v32, ffox>32, but not IE/Safari // comptabile with chrome>v32, ffox>32, but not IE/Safari
var lang = navigator.languages ? var lang = window.navigator.languages ?
navigator.languages[0] : window.navigator.languages[0] :
(navigator.language || navigator.userLanguage); (window.navigator.language || window.navigator.userLanguage);
originalMoment.locale(lang); originalMoment.locale(lang);
return originalMoment.apply(this, arguments); return originalMoment.apply(this, arguments);

View File

@@ -7,9 +7,10 @@
import multiSelect from './multi-select-list.directive'; import multiSelect from './multi-select-list.directive';
import selectAll from './select-all.directive'; import selectAll from './select-all.directive';
import selectListItem from './select-list-item.directive'; import selectListItem from './select-list-item.directive';
import templateUrl from 'tower/shared/template-url/main';
export default export default
angular.module('multiSelectList', []) angular.module('multiSelectList', [templateUrl.name])
.directive('multiSelectList', multiSelect) .directive('multiSelectList', multiSelect)
.directive('selectAll', selectAll) .directive('selectAll', selectAll)
.directive('selectListItem', selectListItem); .directive('selectListItem', selectListItem);

View File

@@ -129,12 +129,10 @@
// // => // // =>
// '/static/js/shared/multi-select-list/select-all.html // '/static/js/shared/multi-select-list/select-all.html
// //
function template(base) {
return '/static/js/' + base + '.partial.html';
}
export default export default
[ function() { [ 'templateUrl',
function(templateUrl) {
return { return {
require: '^multiSelectList', require: '^multiSelectList',
restrict: 'E', restrict: 'E',
@@ -145,7 +143,7 @@ export default
extendedLabel: '&', extendedLabel: '&',
isSelectionEmpty: '=selectionsEmpty' isSelectionEmpty: '=selectionsEmpty'
}, },
templateUrl: template('shared/multi-select-list/select-all'), templateUrl: templateUrl('shared/multi-select-list/select-all'),
link: function(scope, element, attrs, controller) { link: function(scope, element, attrs, controller) {
scope.label = scope.label || 'All'; scope.label = scope.label || 'All';

View File

@@ -0,0 +1,6 @@
import templateUrl from './template-url.factory';
export default
angular.module('templateUrl', [])
.factory('templateUrl', templateUrl);

View File

@@ -0,0 +1,20 @@
function templateUrl($sce, path, isTrusted) {
isTrusted = isTrusted !== false; // defaults to true, can be passed in as false
var parts = ['', 'static', 'js'];
parts.push(path);
var url = parts.join('/') + '.partial.html';
if (isTrusted) {
url = $sce.trustAsResourceUrl(url);
}
return url;
}
export default
[ '$sce',
function($sce) {
return _.partial(templateUrl, $sce);
}
];

View File

@@ -2,9 +2,10 @@ import factScanDataService from './fact-scan-data.service';
import getDataForComparison from './get-data-for-comparison.factory'; import getDataForComparison from './get-data-for-comparison.factory';
import getModuleOptions from './get-module-options.factory'; import getModuleOptions from './get-module-options.factory';
import resolveEmptyVersions from './resolve-empty-versions.factory'; import resolveEmptyVersions from './resolve-empty-versions.factory';
import shared from 'tower/shared/main';
export default export default
angular.module('systemTracking.dataServices', []) angular.module('systemTracking.dataServices', [shared.name])
.factory('getModuleOptions', getModuleOptions) .factory('getModuleOptions', getModuleOptions)
.factory('getDataForComparison', getDataForComparison) .factory('getDataForComparison', getDataForComparison)
.factory('resolveEmptyVersions', resolveEmptyVersions) .factory('resolveEmptyVersions', resolveEmptyVersions)

View File

@@ -8,7 +8,8 @@
export default export default
[ 'moment', [ 'moment',
function(moment) { 'templateUrl',
function(moment, templateUrl) {
return { return {
restrict: 'E', restrict: 'E',
scope: { scope: {
@@ -17,7 +18,7 @@ export default
autoUpdate: '=?', autoUpdate: '=?',
inputClass: '&' inputClass: '&'
}, },
templateUrl: '/static/js/system-tracking/date-picker/date-picker.partial.html', templateUrl: templateUrl('system-tracking/date-picker/date-picker'),
link: function(scope, element, attrs) { link: function(scope, element, attrs) {
// We need to make sure this _never_ recurses, which sometimes happens // We need to make sure this _never_ recurses, which sometimes happens

View File

@@ -1,9 +1,10 @@
/* jshint unused: vars */ /* jshint unused: vars */
export default export default
[ function() { [ 'templateUrl',
function(templateUrl) {
return { restrict: 'E', return { restrict: 'E',
templateUrl: '/static/js/system-tracking/fact-data-table/fact-data-table.partial.html', templateUrl: templateUrl('system-tracking/fact-data-table/fact-data-table'),
scope: scope:
{ leftHostname: '=', { leftHostname: '=',
rightHostname: '=', rightHostname: '=',

View File

@@ -55,7 +55,11 @@ var define, requireModule, require, requirejs;
} }
if (!registry[name]) { if (!registry[name]) {
throw new Error('Could not find module ' + name); name = name + '/index';
if (!registry[name]) {
throw new Error('Could not find module ' + name);
}
} }
var mod = registry[name]; var mod = registry[name];

5
awx/ui/tests/.jshintrc Normal file
View File

@@ -0,0 +1,5 @@
{
"expr": true,
"esnext": true,
"node": true
}

View File

@@ -1,11 +1,20 @@
import Tower from 'tower/app'; /* jshint node: true */
import {describeModule} from 'tests/unit/describe-module';
import JobStatusGraph from 'tower/dashboard/graphs/job-status/main' import '../support/node';
import {describeModule} from '../support/describe-module';
import 'tower/shared/Utilities';
import 'tower/shared/RestServices';
import JobStatusGraph from 'tower/dashboard/graphs/job-status/main';
var resizeHandler = sinon.spy(); var resizeHandler = sinon.spy();
window.$.fn.removeResize = angular.noop;
describeModule(JobStatusGraph.name) describeModule(JobStatusGraph.name)
.mockProvider('adjustGraphSize', resizeHandler) .mockProvider('adjustGraphSize', resizeHandler)
.mockProvider('Wait', angular.noop)
.mockProvider('Rest', angular.noop)
.testDirective('jobStatusGraph', function(directive) { .testDirective('jobStatusGraph', function(directive) {

View File

@@ -1,30 +0,0 @@
/**********************************
* Copyright (c) 2015 Ansible, Inc.
*
* CheckLicense.js
*
* Tests the CheckLicense service- helpers/CheckLicense.js
*
*/
/* global describe, it, expect, by, browser, element, beforeEach */
describe('E2E:CheckLicense', function() {
beforeEach(function() {
browser.get('http://localhost:8013');
});
it('should present login dialog', function() {
var labels = element.all(by.css('#login-modal .modal-body label'));
expect(labels.get(0).getText()).toMatch(/Username/);
});
it('should login', function() {
element(by.model('login_username')).sendKeys('admin');
element(by.model('login_password')).sendKeys('password01!');
element(by.id('login-button')).click();
var user = element(by.css('#account-menu [ng-bind="current_user.username"]'));
expect(user.getText()).toMatch(/admin/);
});
});

View File

@@ -1,8 +1,10 @@
import '../support/node';
import featuresController from 'tower/shared/features/features.controller'; import featuresController from 'tower/shared/features/features.controller';
describe('featuresController', function() { describe('featuresController', function() {
it('checks if a feature is enabled', inject(['$rootScope', function($rootScope) { it('checks if a feature is enabled', window.inject(['$rootScope', function($rootScope) {
var actual; var actual;
$rootScope.features = { $rootScope.features = {

View File

@@ -1,5 +1,7 @@
import '../support/node';
import features from 'tower/shared/features/main'; import features from 'tower/shared/features/main';
import {describeModule} from '../describe-module'; import {describeModule} from '../support/describe-module';
//test that it returns features, as well as test that it is returned in rootScope //test that it returns features, as well as test that it is returned in rootScope
@@ -31,7 +33,7 @@ describeModule(features.name)
}); });
it('caches in rootScope', inject(['$rootScope', it('caches in rootScope', window.inject(['$rootScope',
function($rootScope){ function($rootScope){
var features = {}, var features = {},
result = { result = {

View File

@@ -1,5 +1,7 @@
import '../support/node';
import jobTemplates from 'tower/job-templates/main'; import jobTemplates from 'tower/job-templates/main';
import {describeModule} from '../describe-module'; import {describeModule} from '../support/describe-module';
describeModule(jobTemplates.name) describeModule(jobTemplates.name)
.testService('deleteJobTemplate', function(test, restStub) { .testService('deleteJobTemplate', function(test, restStub) {

View File

@@ -1,22 +0,0 @@
// Karma configuration
// Generated on Mon Aug 04 2014 21:17:04 GMT-0400 (EDT)
var sharedConfig = require('./karma-shared.conf');
module.exports = function(config) {
var conf = sharedConfig();
// list of files / patterns to load in the browser
conf.files = conf.files.concat([
'../static/lib/angular-mocks/angular-mocks.js',
'../../../node_modules/ng-midway-tester/src/ngMidwayTester.js',
'./unit/*',
'./unit/**/*'
]);
// level of logging
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
conf.logLevel = config.LOG_INFO,
config.set(conf);
};

View File

@@ -1,88 +0,0 @@
// Karma configuration
// Generated on Mon Aug 04 2014 21:17:04 GMT-0400 (EDT)
var path = require('path');
module.exports = function(config) {
config.set({
// base path that will be used to resolve all patterns (eg. files, exclude)
basePath: '',
autoWatchBatchDelay: 2000,
// frameworks to use
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
frameworks:
[ 'mocha',
'chai',
'sinon-chai',
'chai-as-promised',
'chai-things'
],
preprocessors:
{ '../dist/**/*.html': ['ng-html2js']
},
// list of files / patterns to load in the browser
files: [
'../tests/phantomjs-polyfill.js',
'../dist/tower.concat.js',
'../static/lib/angular-mocks/angular-mocks.js',
'../static/lib/ember-cli-test-loader/test-loader.js',
'../dist/tests/**/*.js',
'../tests/unit.js',
'../dist/partials/**/*.html',
'../dist/js/**/*.html'
],
ngHtml2JsPreprocessor: {
stripPrefix: path.join(process.cwd(), 'awx/ui/dist'),
prependPrefix: '/static',
moduleName: 'templates'
},
// list of files to exclude
exclude: [
'../static/js/awx.min.js'
],
// test results reporter to use
// possible values: 'dots', 'progress'
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
reporters: ['dots', 'progress'],
client: {
mocha: {
ui: 'bdd'
}
},
// web server port
port: 9876,
// enable / disable colors in the output (reporters and logs)
colors: true,
// enable / disable watching file and executing tests whenever any file changes
autoWatch: true,
// start these browsers
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
browsers: ['Chrome'],
// Continuous Integration mode
// if true, Karma captures browsers, runs the tests and exits
singleRun: false
});
};

View File

@@ -1,4 +1,6 @@
import {describeModule} from 'tests/unit/describe-module'; import '../support/node';
import {describeModule} from '../support/describe-module';
import mod from 'tower/shared/multi-select-list/main'; import mod from 'tower/shared/multi-select-list/main';
describeModule(mod.name) describeModule(mod.name)
@@ -18,9 +20,9 @@ describeModule(mod.name)
}); });
it('works as an attribute on elements', function() { it('works as an attribute on elements', function() {
inject(['$compile', function($compile) { window.inject(['$compile', function($compile) {
var node = $compile('<div multi-select-list></div>')($scope); var node = $compile('<div multi-select-list></div>')($scope);
var classes = Array.prototype.slice.apply(node[0].classList) var classes = Array.prototype.slice.apply(node.attr('class').split(' '));
expect(classes).to.contain('ng-scope'); expect(classes).to.contain('ng-scope');
}]); }]);
}); });

View File

@@ -1,4 +1,7 @@
import {describeModule} from 'tests/unit/describe-module'; import '../support/node';
import {describeModule} from '../support/describe-module';
import mod from 'tower/shared/multi-select-list/main';
var mockController = { var mockController = {
selectAll: sinon.spy(), selectAll: sinon.spy(),
@@ -7,7 +10,7 @@ var mockController = {
deselectAllExtended: sinon.spy() deselectAllExtended: sinon.spy()
}; };
describeModule('multiSelectList') describeModule(mod.name)
.testDirective('selectAll', function(directive) { .testDirective('selectAll', function(directive) {
var $scope; var $scope;
@@ -36,7 +39,7 @@ describeModule('multiSelectList')
}); });
it('works as an element tag', function() { it('works as an element tag', function() {
var classes = Array.prototype.slice.apply(directive.$element[0].classList); var classes = directive.$element.attr('class').split(' ');
expect(classes).to.contain('ng-scope'); expect(classes).to.contain('ng-scope');
}); });
@@ -91,4 +94,4 @@ describeModule('multiSelectList')
}); });
}); });

View File

@@ -1,28 +0,0 @@
// Phantom.js is missing the standard Function.prototype.bind
// function. See https://code.google.com/p/phantomjs/issues/detail?id=522
// for more details.
//
if (!Function.prototype.bind) {
Function.prototype.bind = function(oThis) {
if (typeof this !== 'function') {
// closest thing possible to the ECMAScript 5
// internal IsCallable function
throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
}
var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
fNOP = function() {},
fBound = function() {
return fToBind.apply(this instanceof fNOP
? this
: oThis,
aArgs.concat(Array.prototype.slice.call(arguments)));
};
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
};
}

View File

@@ -1,4 +0,0 @@
exports.config = {
seleniumAddress: 'http://localhost:4444/wd/hub',
specs: ['e2e/*.js']
}

View File

@@ -1,5 +1,7 @@
import {describeModule} from 'tests/unit/describe-module'; import '../support/node';
import JobStatusGraph from 'tower/dashboard/graphs/job-status/main'
import {describeModule} from '../support/describe-module';
import JobStatusGraph from 'tower/dashboard/graphs/job-status/main';
var processErrors = sinon.spy(); var processErrors = sinon.spy();

View File

@@ -1,3 +1,5 @@
import '../support/node';
import 'tower/shared/main'; import 'tower/shared/main';
describe('LodashAsPromised', function() { describe('LodashAsPromised', function() {
@@ -17,7 +19,7 @@ describe('LodashAsPromised', function() {
return memo + value; return memo + value;
} }
beforeEach(module('shared')); beforeEach(window.module('shared'));
beforeEach(inject(['lodashAsPromised', '$q', function(_lodash, _$q) { beforeEach(inject(['lodashAsPromised', '$q', function(_lodash, _$q) {
_ = _lodash; _ = _lodash;

View File

@@ -1,4 +1,4 @@
import RestStub from 'tests/unit/rest-stub'; import RestStub from './rest-stub';
var $provide; var $provide;
@@ -7,7 +7,7 @@ function wrapInjected(dslFn) {
// })); // }));
return function(fn) { return function(fn) {
dslFn.apply(this, dslFn.apply(this,
[inject( [window.inject(
[ '$injector', [ '$injector',
function($injector) { function($injector) {
var $compile = $injector.get('$compile'); var $compile = $injector.get('$compile');
@@ -28,18 +28,19 @@ function TestModule(name, deps) {
registerPreHooks: function() { registerPreHooks: function() {
var self = this; var self = this;
beforeEach("tower module", module('Tower')); // beforeEach("tower module", window.module('Tower'));
beforeEach(name + " module", module(name)); beforeEach(name + " module", window.module(name));
beforeEach("templates module", module('templates')); beforeEach("templates module", window.module('templates'));
beforeEach("mock app setup", module(['$provide', function(_provide_) { beforeEach("mock app setup", window.module(['$provide', function(_provide_) {
var getBasePath = function(path) { var getBasePath = function(path) {
return '/' + path + '/'; return '/' + path + '/';
} };
$provide = _provide_; $provide = _provide_;
$provide.value('LoadBasePaths', angular.noop); $provide.value('LoadBasePaths', angular.noop);
$provide.value('GetBasePath', getBasePath); $provide.value('GetBasePath', getBasePath);
$provide.value('ProcessErrors', angular.noop);
for (var name in self.mockedProviders) { for (var name in self.mockedProviders) {
$provide.value(name, self.mockedProviders[name]); $provide.value(name, self.mockedProviders[name]);
@@ -47,12 +48,12 @@ function TestModule(name, deps) {
}])); }]));
wrapInjected(beforeEach)(function($httpBackend) { // wrapInjected(beforeEach)(function($httpBackend) {
$httpBackend // $httpBackend
.expectGET('/static/js/local_config.js') // .expectGET('/static/js/local_config.js')
.respond({}); // .respond({});
}); // });
}, },
mockProvider: function(name, value) { mockProvider: function(name, value) {
this.mockedProviders[name] = value; this.mockedProviders[name] = value;
@@ -64,7 +65,7 @@ function TestModule(name, deps) {
}); });
}, },
registerPostHooks: function() { registerPostHooks: function() {
afterEach(inject(['$httpBackend', function($httpBackend) { afterEach(window.inject(['$httpBackend', function($httpBackend) {
$httpBackend.verifyNoOutstandingExpectation(); $httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest(); $httpBackend.verifyNoOutstandingRequest();
}])); }]));
@@ -81,7 +82,7 @@ function TestService(name) {
return { return {
withService: function(fn) { withService: function(fn) {
beforeEach(name + " service", inject([name, function() { beforeEach(name + " service", window.inject([name, function() {
var service = arguments[0]; var service = arguments[0];
fn(service); fn(service);
}])); }]));
@@ -104,7 +105,7 @@ function TestDirective(name, deps) {
// by the test // by the test
withScope: function(fn) { withScope: function(fn) {
var self = this; var self = this;
beforeEach("capture outer $scope", inject(['$rootScope', function($rootScope) { beforeEach("capture outer $scope", window.inject(['$rootScope', function($rootScope) {
var $scope = self.$scope = self.$scope || $rootScope.$new(); var $scope = self.$scope = self.$scope || $rootScope.$new();
// `this` refers to mocha test suite // `this` refers to mocha test suite
fn.apply(this, [$scope]); fn.apply(this, [$scope]);
@@ -112,7 +113,7 @@ function TestDirective(name, deps) {
}, },
withIsolateScope: function(fn) { withIsolateScope: function(fn) {
var self = this; var self = this;
beforeEach("capture isolate scope", inject(['$rootScope', function($rootScope) { beforeEach("capture isolate scope", window.inject(['$rootScope', function($rootScope) {
// `this` refers to mocha test suite // `this` refers to mocha test suite
fn.apply(this, [self.$element.isolateScope()]); fn.apply(this, [self.$element.isolateScope()]);
}])); }]));
@@ -165,7 +166,7 @@ function TestDirective(name, deps) {
} }
beforeEach("compile directive element", beforeEach("compile directive element",
inject(['$compile', '$httpBackend', '$rootScope', function($compile, $httpBackend, $rootScope) { window.inject(['$compile', '$httpBackend', '$rootScope', function($compile, $httpBackend, $rootScope) {
if (!self.$scope) { if (!self.$scope) {
self.$scope = $rootScope.$new(); self.$scope = $rootScope.$new();
@@ -176,13 +177,14 @@ function TestDirective(name, deps) {
self.$scope.$digest(); self.$scope.$digest();
$httpBackend.flush(); // $httpBackend.flush();
}])); }]));
afterEach("cleanup directive element", function() { afterEach("cleanup directive element", function() {
self.$element.trigger('$destroy'); $(self.$element).trigger('$destroy');
self.$element.remove(); self.$element.remove();
delete self.$scope;
}); });
this._compileRegistered = true; this._compileRegistered = true;
@@ -200,7 +202,7 @@ function TestDirective(name, deps) {
}, },
provideTemplate: function(url, template) { provideTemplate: function(url, template) {
var $scope = this.$scope; var $scope = this.$scope;
beforeEach("mock template endpoint", inject(['$httpBackend', function($httpBackend) { beforeEach("mock template endpoint", window.inject(['$httpBackend', function($httpBackend) {
$httpBackend $httpBackend
.whenGET(url) .whenGET(url)
.respond(template); .respond(template);
@@ -233,7 +235,7 @@ function ModuleDescriptor(name, deps) {
testModule.mockProvider('$cookieStore', { get: angular.noop }); testModule.mockProvider('$cookieStore', { get: angular.noop });
testModule.registerPreHooks(); testModule.registerPreHooks();
beforeEach("$q", inject(['$q', function($q) { beforeEach("$q", window.inject(['$q', function($q) {
testService.restStub.$q = $q; testService.restStub.$q = $q;
}])); }]));

View File

@@ -0,0 +1,4 @@
module.exports =
function exportGlobal(varName, value) {
global[varName] = global.window[varName] = value;
};

View File

@@ -0,0 +1,26 @@
/* jshint node: true */
(function() {
var isNode = typeof window === 'undefined';
if (!isNode) {
window.expect = chai.expect;
return;
}
require('./setup/jsdom');
require('./setup/mocha');
require('./setup/jquery');
require('./setup/angular');
require('./setup/angular-mocks');
require('./setup/angular-templates');
require('./setup/sinon');
require('./setup/chai');
require('./setup/chai-plugins');
require('./setup/d3');
require('./setup/nv');
require('./setup/lodash');
require('./setup/local-storage');
require('./setup/moment');
})();

View File

@@ -0,0 +1,5 @@
var exportGlobal = require('../export-global');
require('angular-mocks/angular-mocks');
exportGlobal('inject', window.inject);

View File

@@ -0,0 +1,2 @@
angular.module('templates', []);
require('../../../../templates');

View File

@@ -0,0 +1,5 @@
var exportGlobal = require('../export-global');
require('angular/angular');
exportGlobal('angular', window.angular);

View File

@@ -0,0 +1,8 @@
var sinonChai = require('sinon-chai');
var chaiAsPromised = require('chai-as-promised');
var chaiThings = require('chai-things');
chai.use(sinonChai);
chai.use(chaiAsPromised);
chai.use(chaiThings);

View File

@@ -0,0 +1,5 @@
var exportGlobal = require('../export-global');
var chai = require('chai');
exportGlobal('chai', chai);
exportGlobal('expect', chai.expect);

6
awx/ui/tests/support/node/setup/d3.js vendored Normal file
View File

@@ -0,0 +1,6 @@
var exportGlobal = require('../export-global');
var d3 = require('d3');
exportGlobal('d3', d3);

View File

@@ -0,0 +1,7 @@
var exportGlobal = require('../export-global');
var jquery = require('jquery');
exportGlobal('$', jquery);
exportGlobal('jQuery', jquery);

View File

@@ -0,0 +1,6 @@
var jsdom = require('jsdom').jsdom;
var document = jsdom('tower');
var window = document.parentWindow;
global.document = document;
global.window = window;

View File

@@ -0,0 +1,7 @@
var exportGlobal = require('../export-global');
var LocalStorage = require('node-localstorage').LocalStorage;
exportGlobal('localStorage',
new LocalStorage('./scratch'));

View File

@@ -0,0 +1,4 @@
var exportGlobal = require('../export-global');
var lodash = require('lodash');
exportGlobal('_', lodash);

View File

@@ -0,0 +1,7 @@
var exportGlobal = require('../export-global');
var mocha = require('mocha');
exportGlobal('mocha', mocha);
exportGlobal('beforeEach', beforeEach);
exportGlobal('afterEach', afterEach);

View File

@@ -0,0 +1,5 @@
var exportGlobal = require('../export-global');
var moment = require('moment');
exportGlobal('moment', moment);

View File

@@ -0,0 +1,6 @@
var exportGlobal = require('../export-global');
var nv = require('nvd3');
exportGlobal('nv', nv);

View File

@@ -0,0 +1,4 @@
var exportGlobal = require('../export-global');
var sinon = require('sinon');
exportGlobal('sinon', sinon);

View File

@@ -63,7 +63,7 @@ RestStub.prototype =
inject(['$rootScope', function($rootScope) { inject(['$rootScope', function($rootScope) {
$rootScope.$apply(); $rootScope.$apply();
}]); }]);
}, 1000); }, 10);
} }
}; };

View File

@@ -1,33 +1,32 @@
import compareFacts from 'tower/system-tracking/compare-facts/flat';
/* jshint node: true */ /* jshint node: true */
/* globals -expect, -_ */
var _, expect; import '../../support/node';
import compareFacts from 'tower/system-tracking/compare-facts/flat';
// This makes this test runnable in node OR karma. The sheer // This makes this test runnable in node OR karma. The sheer
// number of times I had to run this test made the karma // number of times I had to run this test made the karma
// workflow just too dang slow for me. Maybe this can // workflow just too dang slow for me. Maybe this can
// be a pattern going forward? Not sure... // be a pattern going forward? Not sure...
// //
(function(global) { // (function(global) {
var chai = global.chai || require('chai'); // var chai = global.chai || require('chai');
if (typeof window === 'undefined') { // if (typeof window === 'undefined') {
var chaiThings = global.chaiThings || require('chai-things'); // var chaiThings = global.chaiThings || require('chai-things');
chai.use(chaiThings); // chai.use(chaiThings);
} // }
_ = global._ || require('lodash'); // _ = global._ || require('lodash');
expect = global.expect || chai.expect; // expect = global.expect || chai.expect;
global.expect = expect; // global.expect = expect;
global._ = _; // global._ = _;
})(typeof window === 'undefined' ? global : window); // })(typeof window === 'undefined' ? global : window);
describe('CompareFacts.Flat', function() { describe('CompareFacts.Flat', function() {

View File

@@ -1,34 +1,8 @@
import compareFacts from 'tower/system-tracking/compare-facts/nested';
/* jshint node: true */ /* jshint node: true */
/* globals -expect, -_ */
var _, expect; import '../../support/node';
// This makes this test runnable in node OR karma. The sheer
// number of times I had to run this test made the karma
// workflow just too dang slow for me. Maybe this can
// be a pattern going forward? Not sure...
//
(function(global) {
var chai = global.chai || require('chai');
if (typeof window === 'undefined') {
var chaiThings = global.chaiThings || require('chai-things');
chai.use(chaiThings);
}
_ = global._ || require('lodash');
expect = global.expect || chai.expect;
global.expect = expect;
global._ = _;
})(typeof window === 'undefined' ? global : window);
import compareFacts from 'tower/system-tracking/compare-facts/nested';
describe('CompareFacts.Nested', function() { describe('CompareFacts.Nested', function() {

View File

@@ -1,5 +1,7 @@
import systemTracking from 'tower/system-tracking/main'; import '../support/node';
import {describeModule} from '../describe-module';
import systemTracking from 'tower/system-tracking/data-services/main';
import {describeModule} from '../support/describe-module';
import moment from 'tower/shared/moment/moment'; import moment from 'tower/shared/moment/moment';
describeModule(systemTracking.name) describeModule(systemTracking.name)

30
testem.yml Normal file
View File

@@ -0,0 +1,30 @@
---
framework: mocha
cwd: awx/ui/
port: 7358
src_files:
- static/js/**/*.js
- static/lib/**/*.js
- tests/**/*.js
serve_files:
- dist/tower.concat.js
- dist/tests/**/*.js
- dist/tests/unit.js
unsafe_file_serving: true
test_page: tests.html
disable_watching: true
routes:
/awx/ui/dist: /static
/tests.html: ../../packaging/grunt/testem.mustache
/test-loader.js: static/lib/ember-cli-test-loader/test-loader.js
/vendor: ../../node_modules
/angular-mocks.js: dist/lib/angular-mocks/angular-mocks.js
/templates.js: dist/node-tests/templates.js
reporter: xunit
launch_in_dev:
- Mocha
launch_in_ci:
- PhantomJS
launchers:
Mocha:
command: npm test