Extract library for tests

This commit is contained in:
Joe Fiorini 2015-03-20 17:18:29 -04:00
parent 0a2a297dcb
commit 529fa935c9
8 changed files with 547 additions and 300 deletions

View File

@ -481,4 +481,4 @@ export function UsersEdit($scope, $rootScope, $compile, $location, $log, $routeP
UsersEdit.$inject = ['$scope', '$rootScope', '$compile', '$location', '$log', '$routeParams', 'UserForm', 'GenerateForm',
'Rest', 'Alert', 'ProcessErrors', 'LoadBreadCrumbs', 'RelatedSearchInit', 'RelatedPaginateInit', 'ReturnToCaller', 'ClearScope',
'GetBasePath', 'Prompt', 'CheckAccess', 'ResetForm', 'Wait', 'Stream'
];
];

View File

@ -1,24 +1,48 @@
// 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'],
frameworks:
[ 'mocha',
'chai',
'sinon-chai',
'chai-as-promised'
],
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'
'../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: [

View File

@ -0,0 +1,28 @@
// 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

@ -0,0 +1,274 @@
import RestStub from 'tests/unit/rest-stub';
var $provide;
function wrapInjected(dslFn) {
// wrapInjected(before(inject(..., function() {
// }));
return function(fn) {
dslFn.apply(this,
[inject(
[ '$injector',
function($injector) {
var $compile = $injector.get('$compile');
var $httpBackend = $injector.get('$httpBackend');
var $rootScope = $injector.get('$rootScope');
return fn.apply(this, [$httpBackend, $compile, $rootScope]);
}.bind(this)
])]);
};
};
function TestModule(name, deps) {
window.localStorage.setItem('zones', []);
return {
mockedProviders: {},
registerPreHooks: function() {
var self = this;
beforeEach("tower module", module('Tower'));
beforeEach(name + " module", module(name));
beforeEach("templates module", module('templates'));
beforeEach("mock app setup", module(['$provide', function(_provide_) {
var getBasePath = function(path) {
return '/' + path + '/';
}
$provide = _provide_;
$provide.value('LoadBasePaths', angular.noop);
$provide.value('GetBasePath', getBasePath);
for (var name in self.mockedProviders) {
$provide.value(name, self.mockedProviders[name]);
}
}]));
wrapInjected(beforeEach)(function($httpBackend) {
$httpBackend
.expectGET('/static/js/local_config.js')
.respond({});
});
},
mockProvider: function(name, value) {
this.mockedProviders[name] = value;
},
describe: function(name, describeFn) {
describe(name, function() {
describeFn.apply(this);
});
},
registerPostHooks: function() {
afterEach(inject(['$httpBackend', function($httpBackend) {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
}]));
}
};
};
function TestService(name) {
var restStub = new RestStub();
afterEach(function() {
restStub.reset();
});
return {
withService: function(fn) {
beforeEach(name + " service", inject([name, function() {
var service = arguments[0];
fn(service);
}]));
},
restStub: restStub,
};
};
// Note: if you need a compile step for your directive you
// must either:
//
// 1. Use a before/after compile hook, which also allows
// you to modify the scope before compiling
// 2. If you don't use a hook, call `registerCompile`
// prior to the first `it` in your tests
function TestDirective(name, deps) {
return { name: name,
// Hooks that need to run after any hooks registered
// by the test
withScope: function(fn) {
var self = this;
beforeEach("capture outer $scope", inject(['$rootScope', function($rootScope) {
var $scope = self.$scope = self.$scope || $rootScope.$new();
// `this` refers to mocha test suite
fn.apply(this, [$scope]);
}]));
},
withIsolateScope: function(fn) {
var self = this;
beforeEach("capture isolate scope", inject(['$rootScope', function($rootScope) {
// `this` refers to mocha test suite
fn.apply(this, [self.$element.isolateScope()]);
}]));
},
beforeCompile: function(fn) {
var self = this;
// Run before compile step by passing in the
// outer scope, allowing for modifications
// prior to compiling
self.withScope(fn);
this.registerCompile();
},
afterCompile: function(fn) {
var self = this;
// Make sure compile step gets setup first
if (!this._compileRegistered) {
this.registerCompile();
}
// Then pre-apply the function with the outer scope
self.withScope(function($scope) {
// `this` refers to mocha test suite
fn = fn.bind(this, $scope);
});
// Finally, have it called by the isolate scope
// hook, which will pass in both the outer
// scope (since it was pre-applied) and the
// isolate scope (if one exists)
//
self.withIsolateScope(function($scope) {
// `this` refers to mocha test suite
fn.apply(this, [$scope]);
});
},
registerCompile: function(deps) {
var self = this;
// Only setup compile step once
if (this._compileRegistered) {
return;
}
beforeEach("compile directive element",
inject(['$compile', '$httpBackend', function($compile, $httpBackend) {
self.$element = $compile(self.element)(self.$scope);
$(self.$element).appendTo('body');
self.$scope.$digest();
$httpBackend.flush();
}]));
afterEach("cleanup directive element", function() {
self.$element.trigger('$destroy');
self.$element.remove();
});
this._compileRegistered = true;
},
withController: function(fn) {
var self = this;
beforeEach(function() {
self._ensureCompiled();
fn(self.$element.controller(self.name));
});
},
use: function(elem) {
this.element = angular.element(elem);
},
provideTemplate: function(url, template) {
var $scope = this.$scope;
beforeEach("mock template endpoint", inject(['$httpBackend', function($httpBackend) {
$httpBackend
.whenGET(url)
.respond(template);
}]));
}
};
}
function ModuleDescriptor(name, deps) {
var moduleTests = [];
var testModule =
Object.create(TestModule(name, deps));
var proto =
{ mockProvider: function(name, value) {
testModule.mockProvider(name, value);
return this;
},
testService: function(name, test) {
testModule.describe(name, function() {
var testService = Object.create(TestService(name));
testModule.mockProvider('Rest', testService.restStub);
testModule.mockProvider('$cookieStore', { get: angular.noop });
testModule.registerPreHooks();
beforeEach("$q", inject(['$q', function($q) {
testService.restStub.$q = $q;
}]));
test.apply(null, [testService, testService.restStub]);
});
},
testDirective: function(name, test) {
testModule.describe(name, function(deps) {
var directiveDeps = _.clone(deps);
var testDirective =
Object.create(TestDirective(name));
// Hand in testDirective object & injected
// dependencies to the test as separate arguments
//
var args = [testDirective].concat(_.values(directiveDeps));
var testObj =
// Using Function#bind to create a new function
// with the arguments pre-applied (go search
// the web for "partial application" to know more)
//
{ run: test.bind(null, testDirective, args),
name: name
};
testModule.registerPreHooks();
// testDirective.registerCompile();
testObj.run();
// testDirective.registerPostHooks();
});
}
};
return proto;
}
export function describeModule(name) {
var descriptor = null
descriptor = Object.create(ModuleDescriptor(name));
return descriptor;
};

View File

@ -1,90 +1,73 @@
import Tower from 'tower/app';
import {describeModule} from 'tests/unit/describe-module';
describe('Job Status Graph Directive', function() {
var element, scope, httpBackend;
var resizeHandler = sinon.spy();
var resizeHandler = sinon.spy();
describeModule('DashboardGraphs')
.mockProvider('adjustGraphSize', resizeHandler)
.testDirective('jobStatusGraph', function(directive) {
beforeEach(module('Tower'));
beforeEach(module(['$provide', function($provide) {
$provide.value('LoadBasePaths', angular.noop);
$provide.value('adjustGraphSize', resizeHandler);
}]));
directive.provideTemplate(
'/static/partials/job_status_graph.html',
"<div class='m'></div><div class='n'></div><div class='job-status-graph'><svg></svg></div>");
directive.use('<job-status-graph class="job-status-graph" data="data" job-type="all" period="month"></job-status-graph>');
directive.beforeCompile(function($scope) {
// Takes jobs grouped by result (successful or failure
// Then looks at each array of arrays, where index 0 is the timestamp & index 1 is the count of jobs with that status
$scope.data =
{ jobs:
{ successful: [[1, 0], [2, 0], [3,0], [4,0], [5,0]],
failed: [[1,0],[2,0],[3,0],[4,0],[5,0]]
}
};
});
function filterDataSeries(key, data) {
return data.map(function(datum) {
return datum.values;
})[key];
}
it('uses successes & failures from scope', function() {
var chartContainer = d3.select(directive.$element.find('svg')[0]);
var lineData = chartContainer.datum();
var successfulSeries = filterDataSeries(0, lineData);
var failedSeries = filterDataSeries(1, lineData);
expect(successfulSeries).to.eql(
[ {x: 1, y: 0, series: 0},
{x: 2, y: 0, series: 0},
{x: 3, y: 0, series: 0},
{x: 4, y: 0, series: 0},
{x: 5, y: 0, series: 0}
]);
expect(failedSeries).to.eql(
[ {x: 1, y: 0, series: 1},
{x: 2, y: 0, series: 1},
{x: 3, y: 0, series: 1},
{x: 4, y: 0, series: 1},
{x: 5, y: 0, series: 1}
]);
});
it('cleans up external bindings', function() {
directive.$element.trigger('$destroy');
resizeHandler.reset();
inject(['$window', function($window) {
angular.element($window).trigger('resize');
}]);
expect(resizeHandler).not.to.have.been.called;
});
beforeEach(inject(['$rootScope', '$compile', '$httpBackend', function($rootScope, $compile, $httpBackend) {
httpBackend = $httpBackend;
$httpBackend.expectGET('/static/js/local_config.js').respond({
});
$httpBackend.whenGET('/static/partials/job_status_graph.html')
.respond("<div class='m'></div><div class='n'></div><div class='job-status-graph'><svg></svg></div>");
scope = $rootScope.$new();
element = '<job-status-graph class="job-status-graph" data="data" job-type="all" period="month"></job-status-graph>';
// Takes jobs grouped by result (successful or failure
// Then looks at each array of arrays, where index 0 is the timestamp & index 1 is the count of jobs with that status
scope.data =
{ jobs:
{ successful: [[1, 0], [2, 0], [3,0], [4,0], [5,0]],
failed: [[1,0],[2,0],[3,0],[4,0],[5,0]]
}
};
element = $compile(element)(scope);
scope.$digest();
$httpBackend.flush();
}]));
afterEach(function() {
element.trigger('$destroy');
httpBackend.verifyNoOutstandingExpectation();
httpBackend.verifyNoOutstandingRequest();
});
function filterDataSeries(key, data) {
return data.map(function(datum) {
return datum.values;
})[key];
}
it('uses successes & failures from scope', function() {
var chartContainer = d3.select(element.find('svg')[0]);
var lineData = chartContainer.datum();
var successfulSeries = filterDataSeries(0, lineData);
var failedSeries = filterDataSeries(1, lineData);
expect(successfulSeries).to.eql(
[ {x: 1, y: 0, series: 0},
{x: 2, y: 0, series: 0},
{x: 3, y: 0, series: 0},
{x: 4, y: 0, series: 0},
{x: 5, y: 0, series: 0}]);
expect(failedSeries).to.eql(
[ {x: 1, y: 0, series: 1},
{x: 2, y: 0, series: 1},
{x: 3, y: 0, series: 1},
{x: 4, y: 0, series: 1},
{x: 5, y: 0, series: 1}]);
});
it('cleans up external bindings', function() {
element.trigger('$destroy');
resizeHandler.reset();
inject(['$window', function($window) {
angular.element($window).trigger('resize');
}]);
expect(resizeHandler).not.to.have.been.called;
});
});

View File

@ -0,0 +1,62 @@
function assertUrlDeferred(url, obj) {
if (angular.isUndefined(obj[url]) ||
angular.isUndefined(obj[url].then) &&
angular.isUndefined(obj[url].promise.then)) {
var urls = [];
for (key in obj) {
if (/\//.test(key)) {
urls.push(key);
}
}
var registered = urls.map(function(url) {
return "\t\"" + url + "\"";
}).join("\n");
throw "Could not find a thenable registered for url \"" + url + "\". Registered URLs include:\n\n" + registered + "\n\nPerhaps you typo'd the URL?\n"
}
}
function RestStub() {
}
RestStub.prototype =
{ setUrl: function(url) {
this[url] = this.$q.defer();
this.currentUrl = url;
},
reset: function() {
delete this.deferred;
},
get: function() {
// allow a single deferred on this in case we don't need URL
this.deferred = this[this.currentUrl];
return this.deferred.promise;
},
succeedAt: function(url, value) {
assertUrlDeferred(url, this);
this[url].resolve(value);
},
succeed: function(value) {
this.deferred.resolve(value);
},
failAt: function(url, value) {
assertUrlDeferred(url, this);
this[url].reject(value);
},
fail: function(value) {
this.deferred.reject(value);
},
flush: function() {
window.setTimeout(function() {
inject(['$rootScope', function($rootScope) {
$rootScope.$apply();
}], 1000);
});
}
};
export default RestStub;

View File

@ -1,132 +1,56 @@
describe('Host Count Graph Data Service', function() {
import {describeModule} from 'tests/unit/describe-module';
var q;
var processErrors = sinon.spy();
var hostCountGraphData, httpBackend, rootScope, timeout;
describeModule('DashboardGraphs')
.mockProvider('ProcessErrors', processErrors)
.testService('hostCountGraphData', function(test, restStub) {
var processErrors = sinon.spy();
var q;
var service;
var getBasePath = function(path) {
return '/' + path + '/';
}
beforeEach(inject(['$q', function($q) {
q = $q;
}]));
function flushPromises() {
window.setTimeout(function() {
inject(['$rootScope', function($rootScope) {
$rootScope.$apply();
}], 1000);
test.withService(function(_service_) {
service = _service_;
});
}
function assertUrlDeferred(url, obj) {
if (angular.isUndefined(obj[url]) ||
angular.isUndefined(obj[url].then) &&
angular.isUndefined(obj[url].promise.then)) {
var urls = [];
it('returns a promise to be fulfilled when data comes in', function() {
var license = "license";
var hostData = "hosts";
for (key in obj) {
if (/\//.test(key)) {
urls.push(key);
var result = service.get();
restStub.succeedAt('/config/', { data: {
license_info: {
instance_count: license
}
}
}
});
var registered = urls.map(function(url) {
return "\t\"" + url + "\"";
}).join("\n");
restStub.succeedAt('/dashboard/graphs/inventory/', { data: hostData });
throw "Could not find a thenable registered for url \"" + url + "\". Registered URLs include:\n\n" + registered + "\n\nPerhaps you typo'd the URL?\n"
}
}
restStub.flush();
var restStub = {
setUrl: function(url) {
restStub[url] = q.defer();
restStub.currentUrl = url;
},
reset: function() {
delete restStub.deferred;
},
get: function() {
// allow a single deferred on restStub in case we don't need URL
restStub.deferred = restStub[restStub.currentUrl];
return restStub.deferred.promise;
},
succeedAt: function(url, value) {
assertUrlDeferred(url, restStub);
restStub[url].resolve(value);
},
succeed: function(value) {
restStub.deferred.resolve(value);
},
failAt: function(url, value) {
assertUrlDeferred(url, restStub);
restStub[url].reject(value);
},
fail: function(value) {
restStub.deferred.reject(value);
}
};
beforeEach(module("Tower"));
beforeEach(module(['$provide', function($provide) {
$provide.value("$cookieStore", { get: angular.noop });
$provide.value('ProcessErrors', processErrors);
$provide.value('Rest', restStub);
$provide.value('GetBasePath', getBasePath);
}]));
afterEach(function() {
restStub.reset();
});
beforeEach(inject(['hostCountGraphData', '$httpBackend', '$q', '$rootScope', '$timeout', function(_hostCountGraphData_, $httpBackend, $q, $rootScope, $timeout) {
hostCountGraphData = _hostCountGraphData_;
httpBackend = $httpBackend;
rootScope = $rootScope;
timeout = $timeout;
$httpBackend.expectGET('/static/js/local_config.js').respond({
});
q = $q;
}]));
it('returns a promise to be fulfilled when data comes in', function() {
var license = "license";
var hostData = "hosts";
var result = hostCountGraphData.get();
restStub.succeedAt('/config/', { data: {
license_info: {
instance_count: license
}
}
return expect(result).to.eventually.eql({ license: license, hosts: hostData });;
});
restStub.succeedAt('/dashboard/graphs/inventory/', { data: hostData });
it('processes errors through error handler', function() {
var expected = { data: "blah", status: "bad" };
var actual = service.get();
flushPromises();
restStub.failAt('/config/', expected);
return expect(result).to.eventually.eql({ license: license, hosts: hostData });;
});
restStub.flush();
it('processes errors through error handler', function() {
var expected = { data: "blah", status: "bad" };
var actual = hostCountGraphData.get();
return actual.catch(function() {
expect(processErrors).to
.have.been.calledWith(null, expected.data, expected.status);
});
restStub.failAt('/config/', expected);
flushPromises();
return actual.catch(function() {
expect(processErrors).to
.have.been.calledWith(null, expected.data, expected.status);
});
});
});
});

View File

@ -1,124 +1,76 @@
describe('Job Status Graph Data Service', function() {
import {describeModule} from 'tests/unit/describe-module';
var q;
var processErrors = sinon.spy();
var jobStatusGraphData, httpBackend, rootScope, timeout;
describeModule('DashboardGraphs')
.mockProvider('ProcessErrors', processErrors)
.testService('jobStatusGraphData', function(test, restStub) {
var q;
var service;
var jobStatusChange = {
$on: sinon.spy(),
};
var jobStatusChange = {
$on: sinon.spy(),
};
var processErrors = sinon.spy();
beforeEach(inject(['$q', function($q) {
q = $q;
}]));
var getBasePath = function(path) {
return '/' + path + '/';
}
test.withService(function(_service) {
service = _service;
});
function flushPromises() {
window.setTimeout(function() {
inject(['$rootScope', function($rootScope) {
$rootScope.$apply();
}]);
});
}
it('returns a promise to be fulfilled when data comes in', function() {
var firstResult = "result";
var restStub = {
setUrl: angular.noop,
reset: function() {
delete restStub.deferred;
},
get: function() {
if (angular.isUndefined(restStub.deferred)) {
restStub.deferred = q.defer();
}
var result = service.get('', '');
return restStub.deferred.promise;
},
succeed: function(value) {
restStub.deferred.resolve(value);
},
fail: function(value) {
restStub.deferred.reject(value);
}
};
restStub.succeed({ data: firstResult });
beforeEach(module("Tower"));
restStub.flush();
beforeEach(module(['$provide', function($provide) {
return expect(result).to.eventually.equal(firstResult);;
});
$provide.value("$cookieStore", { get: angular.noop });
it('processes errors through error handler', function() {
var expected = { data: "blah", status: "bad" };
var actual = service.get().catch(function() {
return processErrors;
});
$provide.value('ProcessErrors', processErrors);
$provide.value('Rest', restStub);
$provide.value('GetBasePath', getBasePath);
}]));
restStub.fail(expected);
afterEach(function() {
restStub.reset();
});
restStub.flush();
beforeEach(inject(['jobStatusGraphData', '$httpBackend', '$q', '$rootScope', '$timeout', function(_jobStatusGraphData_, $httpBackend, $q, $rootScope, $timeout) {
jobStatusGraphData = _jobStatusGraphData_;
httpBackend = $httpBackend;
rootScope = $rootScope;
timeout = $timeout;
$httpBackend.expectGET('/static/js/local_config.js').respond({
});
q = $q;
}]));
return actual.catch(function() {
expect(processErrors).to
.have.been.calledWith(null, expected.data, expected.status);
});
it('returns a promise to be fulfilled when data comes in', function() {
var firstResult = "result";
});
var result = jobStatusGraphData.get('', '');
it('broadcasts event when data is received', function() {
var expected = "value";
var result = q.defer();
service.setupWatcher();
restStub.succeed({ data: firstResult });
inject(['$rootScope', function($rootScope) {
$rootScope.$on('DataReceived:JobStatusGraph', function(e, data) {
result.resolve(data);
});
$rootScope.$emit('JobStatusChange');
restStub.succeed({ data: expected });
restStub.flush();
}]);
flushPromises();
return expect(result.promise).to.eventually.equal(expected);
});
return expect(result).to.eventually.equal(firstResult);;
});
it('requests data with given period and jobType', function() {
restStub.setUrl = sinon.spy();
it('processes errors through error handler', function() {
var expected = { data: "blah", status: "bad" };
var actual = jobStatusGraphData.get().catch(function() {
return processErrors;
});
restStub.fail(expected);
flushPromises();
return actual.catch(function() {
expect(processErrors).to
.have.been.calledWith(null, expected.data, expected.status);
});
});
it('broadcasts event when data is received', function() {
var expected = "value";
var result = q.defer();
jobStatusGraphData.setupWatcher();
inject(['$rootScope', function($rootScope) {
$rootScope.$on('DataReceived:JobStatusGraph', function(e, data) {
result.resolve(data);
});
$rootScope.$emit('JobStatusChange');
restStub.succeed({ data: expected });
flushPromises();
}]);
return expect(result.promise).to.eventually.equal(expected);
});
it('requests data with given period and jobType', function() {
restStub.setUrl = sinon.spy();
jobStatusGraphData.get('1', '2');
expect(restStub.setUrl).to.have.been.calledWith('/dashboard/graphs/jobs/?period=1&job_type=2');
});
service.get('1', '2');
expect(restStub.setUrl).to.have.been.calledWith('/dashboard/graphs/jobs/?period=1&job_type=2');
});
});