Merge pull request #55 from cmmcleod/karma-testing

Karma testing
This commit is contained in:
Colin McLeod
2015-06-11 21:12:52 -07:00
35 changed files with 677 additions and 579 deletions

View File

@@ -1 +0,0 @@
app/js/db.js

View File

@@ -1,5 +1,6 @@
angular.module('app', ['ui.router', 'ct.ui.router.extras.sticky', 'ui.sortable', 'shipyard', 'ngLodash', 'app.templates'])
.run(['$rootScope', '$location', '$window', '$document','$state','commonArray','shipPurpose','shipSize','hardPointClass','GroupMap', 'Persist', function ($rootScope, $location, $window, $doc, $state, CArr, shipPurpose, sz, hpc, GroupMap, Persist) {
.run(['$rootScope', '$location', '$window', '$document', '$state', 'commonArray', 'shipPurpose', 'shipSize', 'hardPointClass', 'GroupMap', 'Persist',
function($rootScope, $location, $window, $doc, $state, CArr, shipPurpose, sz, hpc, GroupMap, Persist) {
// App is running as a standalone web app on tablet/mobile
var isStandAlone = $window.navigator.standalone || ($window.external && $window.external.msIsSiteMode && $window.external.msIsSiteMode());
@@ -48,7 +49,7 @@ angular.module('app', ['ui.router', 'ct.ui.router.extras.sticky', 'ui.sortable',
$rootScope.fPct = d3.format('.2%');
$rootScope.f1Pct = d3.format('.1%');
$rootScope.fRPct = d3.format('%');
$rootScope.fTime = function(d) { return Math.floor(d/60) + ":" + ("00" + Math.floor(d%60)).substr(-2,2); };
$rootScope.fTime = function(d) { return Math.floor(d / 60) + ':' + ('00' + Math.floor(d % 60)).substr(-2, 2); };
if (isStandAlone) {
var state = Persist.getState();

View File

@@ -44,12 +44,12 @@ angular.module('app').config(['$provide','$stateProvider', '$urlRouterProvider',
.state('error', { params: { type: null, message: null, details: null }, templateUrl: 'views/page-error.html', controller: 'ErrorController', sticky: true })
// Modal States and views
.state('modal', { abstract: true, views:{ "modal": { templateUrl: "views/_modal.html", controller: 'ModalController' } } })
.state('modal.about', { views: { "modal-content": { templateUrl: "views/modal-about.html" } } })
.state('modal.export', { params: {title:null, data: null, promise: null}, views: { "modal-content": { templateUrl: "views/modal-export.html", controller: 'ExportController' } } })
.state('modal.import', { params: {obj:null}, views: { "modal-content": { templateUrl: "views/modal-import.html", controller: 'ImportController' } } })
.state('modal.link', { params: {url:null}, views: { "modal-content": { templateUrl: "views/modal-link.html", controller: 'LinkController' } } })
.state('modal.delete', { views: { "modal-content": { templateUrl: "views/modal-delete.html", controller: 'DeleteController' } } });
.state('modal', { abstract: true, views: { 'modal': { templateUrl: 'views/_modal.html', controller: 'ModalController' } } })
.state('modal.about', { views: { 'modal-content': { templateUrl: 'views/modal-about.html' } } })
.state('modal.export', { params: { title: null, data: null, promise: null }, views: { 'modal-content': { templateUrl: 'views/modal-export.html', controller: 'ExportController' } } })
.state('modal.import', { params: { obj: null }, views: { 'modal-content': { templateUrl: 'views/modal-import.html', controller: 'ImportController' } } })
.state('modal.link', { params: { url: null }, views: { 'modal-content': { templateUrl: 'views/modal-link.html', controller: 'LinkController' } } })
.state('modal.delete', { views: { 'modal-content': { templateUrl: 'views/modal-delete.html', controller: 'DeleteController' } } });
// Redirects

View File

@@ -10,39 +10,40 @@ angular.module('app').controller('ComparisonController', ['lodash', '$rootScope'
$scope.importObj = {}; // Used for importing comparison builds (from permalinked comparison)
var defaultFacets = [9, 6, 4, 1, 3, 2]; // Reverse order of Armour, Shields, Speed, Jump Range, Cargo Capacity, Cost
var facets = $scope.facets = angular.copy(ShipFacets);
var shipId, buildName, comparisonData;
/**
* Add an existing build to the comparison. The build must be saved locally.
* @param {string} shipId The unique ship key/id
* @param {string} buildName The build name
* @param {string} id The unique ship key/id
* @param {string} name The build name
*/
$scope.addBuild = function (shipId, buildName, code) {
var data = Ships[shipId]; // Get ship properties
code = code? code : Persist.builds[shipId][buildName]; // Retrieve build code if not passed
var b = new Ship(shipId, data.properties, data.slots); // Create a new Ship instance
$scope.addBuild = function(id, name, code) {
var data = Ships[id]; // Get ship properties
code = code ? code : Persist.builds[id][name]; // Retrieve build code if not passed
var b = new Ship(id, data.properties, data.slots); // Create a new Ship instance
Serializer.toShip(b, code); // Populate components from code
// Extend ship instance and add properties below
b.buildName = buildName;
b.buildName = name;
b.code = code;
b.pctRetracted = b.powerRetracted / b.powerAvailable;
b.pctDeployed = b.powerDeployed / b.powerAvailable;
$scope.builds.push(b); // Add ship build to comparison
$scope.builds = $filter('orderBy')($scope.builds, $scope.predicate, $scope.desc); // Resort
_.remove($scope.unusedBuilds, function (b) { // Remove from unused builds
return b.id == shipId && b.buildName == buildName;
_.remove($scope.unusedBuilds, function(o) { // Remove from unused builds
return o.id == id && o.buildName == name;
});
$scope.saved = false;
};
/**
* Removes a build from the comparison
* @param {string} shipId The unique ship key/id
* @param {string} buildName The build name
* @param {string} id The unique ship key/id
* @param {string} name The build name
*/
$scope.removeBuild = function (shipId, buildName) {
_.remove($scope.builds, function (b) {
if (b.id == shipId && b.buildName == buildName) {
$scope.unusedBuilds.push({id: shipId, buildName: buildName, name: b.name}); // Add build back to unused builds
$scope.removeBuild = function(id, name) {
_.remove($scope.builds, function(s) {
if (s.id == id && s.buildName == name) {
$scope.unusedBuilds.push({ id: id, buildName: name, name: s.name }); // Add build back to unused builds
return true;
}
return false;
@@ -68,8 +69,8 @@ angular.module('app').controller('ComparisonController', ['lodash', '$rootScope'
var elem = angular.element(e.target);
if (elem.attr('prop')) { // Get component ID
$scope.sort(elem.attr('prop'));
}
else if (elem.attr('del')) { // Delete index
} else if (elem.attr('del')) { // Delete index
$scope.removeBuild(elem.attr('del'));
}
};
@@ -79,7 +80,7 @@ angular.module('app').controller('ComparisonController', ['lodash', '$rootScope'
* @param {string} key Ship property
*/
$scope.sort = function(key) {
$scope.desc = ($scope.predicate == key)? !$scope.desc : $scope.desc;
$scope.desc = $scope.predicate == key ? !$scope.desc : $scope.desc;
$scope.predicate = key;
$scope.builds = $filter('orderBy')($scope.builds, $scope.predicate, $scope.desc);
};
@@ -150,8 +151,8 @@ angular.module('app').controller('ComparisonController', ['lodash', '$rootScope'
function(shortUrl) {
return Utils.comparisonBBCode(facets, $scope.builds, shortUrl);
},
function (e) {
return 'Error - ' + e.statusText;
function(err) {
return 'Error - ' + err.statusText;
}
);
$state.go('modal.export', { promise: promise, title: 'Forum BBCode' });
@@ -184,7 +185,6 @@ angular.module('app').controller('ComparisonController', ['lodash', '$rootScope'
});
/* Initialization */
var shipId, buildName, comparisonData;
if ($scope.compareMode) {
if ($scope.name == 'all') {
for (shipId in Persist.builds) {

View File

@@ -21,7 +21,7 @@ angular.module('app')
$scope.details = $p.details;
break;
default:
$scope.msgPre = "Uh, Jameson, we have a problem..";
$scope.msgPre = 'Uh, Jameson, we have a problem..';
$scope.errorMessage = $p.message;
$scope.details = $p.details;
}

View File

@@ -48,7 +48,7 @@ angular.module('app').controller('ImportController', ['$scope', '$stateParams',
}
}
} else {
$scope.errorMsg = '"' + shipId + '" is not a valid Ship Id!';
$scope.errorMsg = '"' + shipId + '"" is not a valid Ship Id!';
return;
}
$scope.builds = importObj.builds;

View File

@@ -166,12 +166,12 @@ angular.module('app').controller('OutfitController', ['$window','$rootScope','$s
* @return {[type]} [description]
*/
$scope.sortCost = function(key) {
$scope.costDesc = ($scope.costPredicate == key)? !$scope.costDesc : $scope.costDesc;
$scope.costDesc = $scope.costPredicate == key ? !$scope.costDesc : $scope.costDesc;
$scope.costPredicate = key;
};
$scope.sortPwr = function(key) {
$scope.pwrDesc = ($scope.pwrPredicate == key)? !$scope.pwrDesc : $scope.pwrDesc;
$scope.pwrDesc = $scope.pwrPredicate == key ? !$scope.pwrDesc : $scope.pwrDesc;
$scope.pwrPredicate = key;
};

View File

@@ -1,6 +1,4 @@
angular.module('app').directive('areaChart', ['$window', function($window) {
return {
restrict: 'A',
scope: {
@@ -18,57 +16,57 @@ angular.module('app').directive('areaChart', ['$window', function ($window) {
drag = d3.behavior.drag(),
dragging = false,
// Define Axes
xAxis = d3.svg.axis().outerTickSize(0).orient("bottom").tickFormat(d3.format('.2r')),
yAxis = d3.svg.axis().ticks(6).outerTickSize(0).orient("left").tickFormat(fmt),
xAxis = d3.svg.axis().outerTickSize(0).orient('bottom').tickFormat(d3.format('.2r')),
yAxis = d3.svg.axis().ticks(6).outerTickSize(0).orient('left').tickFormat(fmt),
x = d3.scale.linear(),
y = d3.scale.linear();
// Create chart
var svg = d3.select(element[0]).append("svg");
var vis = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var svg = d3.select(element[0]).append('svg');
var vis = svg.append('g').attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
// Define Area
var area = d3.svg.area();
var gradient = vis.append("defs")
.append("linearGradient")
.attr("id", "gradient")
.attr("x1", "0%").attr("y1", "0%")
.attr("x2", "100%").attr("y2", "100%")
.attr("spreadMethod", "pad");
gradient.append("stop")
.attr("offset", "0%")
.attr("stop-color", "#ff8c0d")
.attr("stop-opacity", 1);
gradient.append("stop")
.attr("offset", "100%")
.attr("stop-color", "#ff3b00")
.attr("stop-opacity", 1);
var gradient = vis.append('defs')
.append('linearGradient')
.attr('id', 'gradient')
.attr('x1', '0%').attr('y1', '0%')
.attr('x2', '100%').attr('y2', '100%')
.attr('spreadMethod', 'pad');
gradient.append('stop')
.attr('offset', '0%')
.attr('stop-color', '#ff8c0d')
.attr('stop-opacity', 1);
gradient.append('stop')
.attr('offset', '100%')
.attr('stop-color', '#ff3b00')
.attr('stop-opacity', 1);
// Create Y Axis SVG Elements
var yTxt = vis.append("g").attr("class", "y axis")
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", -50)
.attr("dy", ".1em")
.style("text-anchor", "middle")
var yTxt = vis.append('g').attr('class', 'y axis')
.append('text')
.attr('transform', 'rotate(-90)')
.attr('y', -50)
.attr('dy', '.1em')
.style('text-anchor', 'middle')
.text(labels.yAxis.title + ' (' + labels.yAxis.unit + ')');
// Create X Axis SVG Elements
var xLbl = vis.append("g").attr("class", "x axis");
var xTxt = xLbl.append("text")
.attr("y", 30)
.attr("dy", ".1em")
.style("text-anchor", "middle")
var xLbl = vis.append('g').attr('class', 'x axis');
var xTxt = xLbl.append('text')
.attr('y', 30)
.attr('dy', '.1em')
.style('text-anchor', 'middle')
.text(labels.xAxis.title + ' (' + labels.xAxis.unit + ')');
// Create and Add tooltip
var tip = vis.append("g").style("display", "none");
tip.append("rect").attr("width","4em").attr("height", "2em").attr("x", "0.5em").attr("y","-1em").attr("class","tip");
tip.append("circle")
.attr("class", "marker")
.attr("r", 4);
tip.append("text").attr("class", 'label x').attr("y", "-0.25em");
tip.append("text").attr("class", 'label y').attr("y", '0.85em');
var tip = vis.append('g').style('display', 'none');
tip.append('rect').attr('width', '4em').attr('height', '2em').attr('x', '0.5em').attr('y', '-1em').attr('class', 'tip');
tip.append('circle')
.attr('class', 'marker')
.attr('r', 4);
tip.append('text').attr('class', 'label x').attr('y', '-0.25em');
tip.append('text').attr('class', 'label y').attr('y', '0.85em');
/**
* Watch for changes in the series data (mass changes, etc)
@@ -89,33 +87,33 @@ angular.module('app').directive('areaChart', ['$window', function ($window) {
data.push([ series.xMin, yVal ]);
area.x(function(d, i) { return i * w; }).y0(h).y1(function(d) { return y(d[1]); });
} else {
for (var d = series.xMin; d <= series.xMax; d += 1) {
data.push([ d, func(d) ]);
for (var val = series.xMin; val <= series.xMax; val += 1) {
data.push([ val, func(val) ]);
}
area.x(function(d) { return x(d[0]); }).y0(h).y1(function(d) { return y(d[1]); });
}
// Update Chart Size
svg.attr("width", width).attr("height", height);
svg.attr('width', width).attr('height', height);
// Update domain and scale for axes;
x.range([0, w]).domain([series.xMin, series.xMax]).clamp(true);
xAxis.scale(x);
xLbl.attr("transform", "translate(0," + h + ")");
xTxt.attr("x", w/2);
xLbl.attr('transform', 'translate(0,' + h + ')');
xTxt.attr('x', w / 2);
y.range([h, 0]).domain([series.yMin, series.yMax]);
yAxis.scale(y);
yTxt.attr("x", -h/2);
vis.selectAll(".y.axis").call(yAxis);
vis.selectAll(".x.axis").call(xAxis);
yTxt.attr('x', -h / 2);
vis.selectAll('.y.axis').call(yAxis);
vis.selectAll('.x.axis').call(xAxis);
// Remove existing elements
vis.selectAll('path.area').remove();
vis.insert("path",':first-child') // Area/Path to appear behind everything else
vis.insert('path', ':first-child') // Area/Path to appear behind everything else
.datum(data)
.attr("class", "area")
.attr('class', 'area')
.attr('fill', 'url(#gradient)')
.attr("d", area)
.attr('d', area)
.on('mouseover', showTip)
.on('mouseout', hideTip)
.on('mousemove', moveTip)
@@ -127,7 +125,7 @@ angular.module('app').directive('areaChart', ['$window', function ($window) {
moveTip.call(this);
showTip();
})
.on("dragend", function() {
.on('dragend', function() {
dragging = false;
hideTip();
})
@@ -135,20 +133,20 @@ angular.module('app').directive('areaChart', ['$window', function ($window) {
}
function showTip() {
tip.style("display", null);
tip.style('display', null);
}
function hideTip() {
if (!dragging) {
tip.style("display", "none");
tip.style('display', 'none');
}
}
function moveTip() {
var xPos = d3.mouse(this)[0], x0 = x.invert(xPos), y0 = func(x0), flip = (x0 / x.domain()[1] > 0.75);
tip.attr("transform", "translate(" + x(x0) + "," + y(y0) + ")");
tip.selectAll('rect').attr("x", flip? '-4.5em' : "0.5em").style("text-anchor", flip? 'end' : 'start');
tip.selectAll('text.label').attr("x", flip? "-1em" : "1em").style("text-anchor", flip? 'end' : 'start');
tip.attr('transform', 'translate(' + x(x0) + ',' + y(y0) + ')');
tip.selectAll('rect').attr('x', flip ? '-4.5em' : '0.5em').style('text-anchor', flip ? 'end' : 'start');
tip.selectAll('text.label').attr('x', flip ? '-1em' : '1em').style('text-anchor', flip ? 'end' : 'start');
tip.select('text.label.x').text(fmtLong(x0) + ' ' + labels.xAxis.unit);
tip.select('text.label.y').text(fmtLong(y0) + ' ' + labels.yAxis.unit);
}

View File

@@ -4,7 +4,7 @@ angular.module('app').directive('barChart', ['$window', function ($window) {
return build.buildName + '\n' + build.name;
}
var insertLinebreaks = function (d) {
function insertLinebreaks(d) {
var el = d3.select(this);
var words = d.split('\n');
el.text('').attr('y', -6);
@@ -14,7 +14,7 @@ angular.module('app').directive('barChart', ['$window', function ($window) {
tspan.attr('x', -9).attr('dy', 12);
}
}
};
}
return {
restrict: 'A',

View File

@@ -25,11 +25,11 @@ angular.module('app').directive('powerBands', ['$window', function ($window) {
// Create Y Axis SVG Elements
vis.append('g').attr('class', 'watt axis');
vis.append('g').attr('class', 'pct axis');
vis.append("text").attr('x', -35).attr('y', 16).attr('class','primary').text('RET');
vis.append("text").attr('x', -35).attr('y', barHeight + 18).attr('class','primary').text('DEP');
vis.append('text').attr('x', -35).attr('y', 16).attr('class', 'primary').text('RET');
vis.append('text').attr('x', -35).attr('y', barHeight + 18).attr('class', 'primary').text('DEP');
var retLbl = vis.append("text").attr('y', 16);
var depLbl = vis.append("text").attr('y', barHeight + 18);
var retLbl = vis.append('text').attr('y', 16);
var depLbl = vis.append('text').attr('y', barHeight + 18);
// Watch for changes to data and events
scope.$watchCollection('available', render);
@@ -63,33 +63,34 @@ angular.module('app').directive('powerBands', ['$window', function ($window) {
.attr('x', w + 5 )
.attr('class', maxBand.retractedSum > available ? 'warning' : 'primary')
.text(wattFmt(Math.max(0, maxBand.retractedSum)) + ' (' + pctFmt(Math.max(0, maxBand.retractedSum / available)) + ')');
depLbl
.attr('x', w + 5 )
.attr('class', maxBand.deployedSum > available ? 'warning' : 'primary')
.text(wattFmt(Math.max(0, maxBand.deployedSum)) + ' (' + pctFmt(Math.max(0, maxBand.deployedSum / available)) + ')');
retracted.selectAll("rect").data(bands).enter().append("rect")
.attr("height", barHeight)
.attr("width", function(d) { return Math.max(wattScale(d.retracted) - 1, 0); })
.attr("x", function(d) { return wattScale(d.retractedSum) - wattScale(d.retracted); })
retracted.selectAll('rect').data(bands).enter().append('rect')
.attr('height', barHeight)
.attr('width', function(d) { return Math.max(wattScale(d.retracted) - 1, 0); })
.attr('x', function(d) { return wattScale(d.retractedSum) - wattScale(d.retracted); })
.attr('y', 1)
.attr('class', function(d) { return (d.retractedSum > available) ? 'warning' : 'primary'; });
retracted.selectAll("text").data(bands).enter().append("text")
retracted.selectAll('text').data(bands).enter().append('text')
.attr('x', function(d) { return wattScale(d.retractedSum) - (wattScale(d.retracted) / 2); })
.attr('y', 15)
.style('text-anchor', 'middle')
.attr('class', 'primary-bg')
.text(function(d, i) { return bandText(d.retracted, i); });
deployed.selectAll("rect").data(bands).enter().append("rect")
.attr("height", barHeight)
.attr("width", function(d) { return Math.max(wattScale(d.deployed + d.retracted) - 1, 0); })
.attr("x", function(d) { return wattScale(d.deployedSum) - wattScale(d.retracted) - wattScale(d.deployed); })
deployed.selectAll('rect').data(bands).enter().append('rect')
.attr('height', barHeight)
.attr('width', function(d) { return Math.max(wattScale(d.deployed + d.retracted) - 1, 0); })
.attr('x', function(d) { return wattScale(d.deployedSum) - wattScale(d.retracted) - wattScale(d.deployed); })
.attr('y', barHeight + 2)
.attr('class', function(d) { return (d.deployedSum > available) ? 'warning' : 'primary'; });
deployed.selectAll("text").data(bands).enter().append("text")
deployed.selectAll('text').data(bands).enter().append('text')
.attr('x', function(d) { return wattScale(d.deployedSum) - ((wattScale(d.retracted) + wattScale(d.deployed)) / 2); })
.attr('y', barHeight + 17)
.style('text-anchor', 'middle')

View File

@@ -15,19 +15,19 @@ angular.module('app').directive('slider', ['$window', function ($window) {
pct = d3.format('.1%'),
unit = scope.unit,
val = scope.max,
svg = d3.select(element[0]).append("svg"),
vis = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")"),
xAxis = vis.append("g").attr("class", "x slider-axis").attr("transform", "translate(0," + h / 2 + ")"),
svg = d3.select(element[0]).append('svg'),
vis = svg.append('g').attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'),
xAxis = vis.append('g').attr('class', 'x slider-axis').attr('transform', 'translate(0,' + h / 2 + ')'),
x = d3.scale.linear(),
slider = vis.append("g").attr("class", "slider"),
filled = slider.append('path').attr('class', 'filled').attr("transform", "translate(0," + h/2 + ")"),
brush = d3.svg.brush().x(x).extent([scope.max, scope.max]).on("brush", brushed),
handle = slider.append("circle").attr("class", "handle").attr("r", '0.6em'),
lbl = slider.append("g").append("text").attr("y", h/2);
slider = vis.append('g').attr('class', 'slider'),
filled = slider.append('path').attr('class', 'filled').attr('transform', 'translate(0,' + h / 2 + ')'),
brush = d3.svg.brush().x(x).extent([scope.max, scope.max]).on('brush', brushed),
handle = slider.append('circle').attr('class', 'handle').attr('r', '0.6em'),
lbl = slider.append('g').append('text').attr('y', h / 2);
slider.call(brush);
slider.select(".background").attr("height", h);
handle.attr("transform", "translate(0," + h / 2 + ")");
slider.select('.background').attr('height', h);
handle.attr('transform', 'translate(0,' + h / 2 + ')');
/**
* Watch for changes in the max, window size
@@ -42,21 +42,21 @@ angular.module('app').directive('slider', ['$window', function ($window) {
function render() {
var width = element[0].offsetWidth, w = width - margin.left - margin.right;
svg.attr("width", width).attr("height", height);
svg.attr('width', width).attr('height', height);
x.domain([0, scope.max]).range([0, w]).clamp(true);
handle.attr("cx", x(val));
handle.attr('cx', x(val));
xAxis
.call(d3.svg.axis()
.scale(x)
.orient("bottom")
.orient('bottom')
.tickFormat(function(d) { return d + unit; })
.tickValues([0, scope.max / 4, scope.max / 2, (3 * scope.max) / 4, scope.max])
.tickSize(0)
.tickPadding(12))
.select(".domain");
.select('.domain');
lbl.attr('x', w + 20);
slider.call(brush.extent([val, val])).call(brush.event);
slider.selectAll(".extent,.resize").remove();
slider.selectAll('.extent,.resize').remove();
}
function brushed() {
@@ -67,8 +67,8 @@ angular.module('app').directive('slider', ['$window', function ($window) {
}
lbl.text(fmt(val) + ' ' + unit + ' ' + pct(val / scope.max));
scope.change({ val: val });
handle.attr("cx", x(val));
filled.attr("d", "M0,0V0H" + x(val) + "V0");
handle.attr('cx', x(val));
filled.attr('d', 'M0,0V0H' + x(val) + 'V0');
}
scope.$on('$destroy', function() {

View File

@@ -4,7 +4,7 @@ angular.module('app').directive('slotHardpoint', ['$rootScope', function ($r) {
scope: {
hp: '=',
size: '=',
lbl: '=',
lbl: '='
},
templateUrl: 'views/_slot-hardpoint.html',
link: function(scope) {

View File

@@ -36,10 +36,7 @@ angular.module('app').service('Serializer', ['lodash', function (_) {
* @param {string} code The string to deserialize
*/
this.toShip = function(ship, dataString) {
var commonCount = ship.common.length,
hpCount = commonCount + ship.hardpoints.length,
totalCount = hpCount + ship.internal.length,
common = new Array(ship.common.length),
var common = new Array(ship.common.length),
hardpoints = new Array(ship.hardpoints.length),
internal = new Array(ship.internal.length),
parts = dataString.split('.'),
@@ -61,12 +58,16 @@ angular.module('app').service('Serializer', ['lodash', function (_) {
// - priorities
// - enabled/disabled
ship.buildWith({
ship.buildWith(
{
bulkheads: code.charAt(0) * 1,
common: common,
hardpoints: hardpoints,
internal: internal,
}, priorities, enabled);
internal: internal
},
priorities,
enabled
);
};
this.fromComparison = function(name, builds, facets, predicate, desc) {
@@ -101,11 +102,11 @@ angular.module('app').service('Serializer', ['lodash', function (_) {
this.enabled.push(slot.enabled ? 1 : 0);
this.priorities.push(slot.priority);
return (slot.id === null)? '-' : slot.id;
return slot.id === null ? '-' : slot.id;
}
function decodeToArray(code, arr, codePos) {
for (i = 0; i < arr.length; i++) {
for (var i = 0; i < arr.length; i++) {
if (code.charAt(codePos) == '-') {
arr[i] = 0;
codePos++;

View File

@@ -1,5 +1,11 @@
angular.module('shipyard').factory('ComponentSet', ['lodash', function(_) {
function filter(data, maxClass, minClass, mass) {
return _.filter(data, function(c) {
return c.class <= maxClass && c.class >= minClass && (c.maxmass === undefined || mass <= c.maxmass);
});
}
function ComponentSet(components, mass, maxCommonArr, maxInternal, maxHardPoint) {
this.mass = mass;
this.common = {};
@@ -57,12 +63,6 @@ angular.module('shipyard').factory('ComponentSet', ['lodash', function (_) {
return this.intClass[c];
};
function filter (data, maxClass, minClass, mass) {
return _.filter(data, function (c) {
return c.class <= maxClass && c.class >= minClass && (c.maxmass === undefined || mass <= c.maxmass);
});
}
return ComponentSet;
}]);

View File

@@ -64,13 +64,18 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength',
common = this.common,
hps = this.hardpoints,
bands = this.priorityBands,
cl = common.length, hl = hps.length, il = internal.length,
cl = common.length,
i, l;
this.useBulkhead(comps.bulkheads || 0, true);
this.cargoScoop.priority = priorities ? priorities[0] * 1 : 0;
this.cargoScoop.enabled = enabled ? enabled[0] * 1 : true;
for (i = 0, l = this.priorityBands.length; i < l; i++) {
this.priorityBands[i].deployed = 0;
this.priorityBands[i].retracted = 0;
}
if (this.cargoScoop.enabled) {
bands[this.cargoScoop.priority].retracted += this.cargoScoop.c.power;
}
@@ -79,34 +84,35 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength',
common[i].enabled = enabled ? enabled[i + 1] * 1 : true;
common[i].priority = priorities ? priorities[i + 1] * 1 : 0;
common[i].type = 'SYS';
common[i].c = common[i].id = null; // Resetting 'old' component if there was one
this.use(common[i], comps.common[i], Components.common(i, comps.common[i]), true);
}
common[1].type = 'ENG'; // Thrusters
common[2].type = 'ENG'; // FSD
cl++; // Increase accounting for Cargo Scoop
cl++; // Increase accounts for Cargo Scoop
for(i = 0, l = comps.hardpoints.length; i < l; i++) {
for (i = 0, l = hps.length; i < l; i++) {
hps[i].enabled = enabled ? enabled[cl + i] * 1 : true;
hps[i].priority = priorities ? priorities[cl + i] * 1 : 0;
hps[i].type = hps[i].maxClass ? 'WEP' : 'SYS';
hps[i].c = hps[i].id = null; // Resetting 'old' component if there was one
if (comps.hardpoints[i] !== 0) {
this.use(hps[i], comps.hardpoints[i], Components.hardpoints(comps.hardpoints[i]), true);
} else {
hps[i].c = hps[i].id = null;
}
}
for(i = 0, l = comps.internal.length; i < l; i++) {
internal[i].enabled = enabled? enabled[hl + cl + i] * 1 : true;
internal[i].priority = priorities? priorities[hl + cl + i] * 1 : 0;
cl += hps.length; // Increase accounts for hardpoints
for (i = 0, l = internal.length; i < l; i++) {
internal[i].enabled = enabled ? enabled[cl + i] * 1 : true;
internal[i].priority = priorities ? priorities[cl + i] * 1 : 0;
internal[i].type = 'SYS';
internal[i].id = internal[i].c = null; // Resetting 'old' component if there was one
if (comps.internal[i] !== 0) {
this.use(internal[i], comps.internal[i], Components.internal(comps.internal[i]), true);
} else {
internal[i].id = internal[i].c = null;
}
}
@@ -179,18 +185,24 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength',
});
};
/**
* Will change the priority of the specified slot if the new priority is valid
* @param {object} slot The slot to be updated
* @param {number} newPriority The new priority to be set
* @return {boolean} Returns true if the priority was changed (within range)
*/
Ship.prototype.changePriority = function(slot, newPriority) {
if (newPriority >= 0 && newPriority < this.priorityBands.length) {
var oldPriority = slot.priority;
slot.priority = newPriority;
if (slot.enabled) {
if (slot.enabled) { // Only update power if the slot is enabled
var usage = (slot.c.passive || this.hardpoints.indexOf(slot) == -1) ? 'retracted' : 'deployed';
this.priorityBands[oldPriority][usage] -= slot.c.power;
this.priorityBands[newPriority][usage] += slot.c.power;
this.updatePower();
return true;
}
return true;
}
return false;
};
@@ -214,14 +226,11 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength',
Ship.prototype.getSlotStatus = function(slot, deployed) {
if (!slot.c) { // Empty Slot
return 0; // No Status (Not possible)
}
else if (!slot.enabled) {
} else if (!slot.enabled) {
return 1; // Disabled
}
else if (deployed) {
} else if (deployed) {
return this.priorityBands[slot.priority].deployedSum > this.powerAvailable ? 2 : 3; // Offline : Online
}
else if (this.hardpoints.indexOf(slot) != -1 && !slot.c.passive) { // Active hardpoints have no retracted status
} else if (this.hardpoints.indexOf(slot) != -1 && !slot.c.passive) { // Active hardpoints have no retracted status
return 0; // No Status (Not possible)
}
return this.priorityBands[slot.priority].retractedSum > this.powerAvailable ? 2 : 3; // Offline : Online
@@ -254,7 +263,7 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength',
this.totalCost -= old.cost;
}
if(old.power) {
if (old.power && slot.enabled) {
this.priorityBands[slot.priority][(isHardPoint && !old.passive) ? 'deployed' : 'retracted'] -= old.power;
powerChange = true;
}
@@ -284,7 +293,7 @@ angular.module('shipyard').factory('Ship', ['Components', 'calcShieldStrength',
this.totalCost += n.cost;
}
if (n.power) {
if (n.power && slot.enabled) {
this.priorityBands[slot.priority][(isHardPoint && !n.passive) ? 'deployed' : 'retracted'] += n.power;
powerChange = true;
}

View File

@@ -47,23 +47,23 @@ angular.module('shipyard', ['ngLodash'])
cc: 'Collector Limpet Ctrl',
// Hard Points
bl: "Beam Laser",
ul: "Burst Laser",
c: "Cannon",
cs: "Cargo Scanner",
cm: "Countermeasure",
fc: "Fragment Cannon",
ws: "Frame Shift Wake Scanner",
kw: "Kill Warrant Scanner",
nl: "Mine Launcher",
ml: "Mining Laser",
mr: "Missile Rack",
pa: "Plasma Accelerator",
mc: "Multi-cannon",
pl: "Pulse Laser",
rg: "Rail Gun",
sb: "Shield Booster",
tp: "Torpedo Pylon"
bl: 'Beam Laser',
ul: 'Burst Laser',
c: 'Cannon',
cs: 'Cargo Scanner',
cm: 'Countermeasure',
fc: 'Fragment Cannon',
ws: 'Frame Shift Wake Scanner',
kw: 'Kill Warrant Scanner',
nl: 'Mine Launcher',
ml: 'Mining Laser',
mr: 'Missile Rack',
pa: 'Plasma Accelerator',
mc: 'Multi-cannon',
pl: 'Pulse Laser',
rg: 'Rail Gun',
sb: 'Shield Booster',
tp: 'Torpedo Pylon'
})
.value('shipPurpose', {
mp: 'Multi Purpose',
@@ -77,7 +77,7 @@ angular.module('shipyard', ['ngLodash'])
'Small',
'Medium',
'Large',
'Capital',
'Capital'
])
.value('hardPointClass', [
'Utility',
@@ -163,7 +163,7 @@ angular.module('shipyard', ['ngLodash'])
lbls: ['Unladen', 'Laden'],
unit: 'LY',
fmt: 'fRound'
},
}
])
/**
* Calculate the maximum single jump range based on mass and a specific FSD

View File

@@ -212,7 +212,7 @@
<td><u>SYS</u></td>
<td>1</td>
<td class="ri">{{fPwr(pp.c.pGen)}}</td>
<td><u>100%</u></td>
<td class="ri"><u>100%</u></td>
<td></td>
<td></td>
</tr>

View File

@@ -1,25 +1,28 @@
// Build / Built-in dependencies
var gulp = require('gulp'),
less = require('gulp-less'),
jshint = require('gulp-jshint'),
minifyCSS = require('gulp-minify-css'),
concat = require('gulp-concat'),
uglify = require('gulp-uglify'),
sourcemaps = require('gulp-sourcemaps'),
templateCache = require('gulp-angular-templatecache'),
htmlmin = require('gulp-htmlmin'),
template = require('gulp-template'),
mainBowerFiles = require('main-bower-files'),
del = require('del'),
runSequence = require('run-sequence'),
exec = require('child_process').exec,
RevAll = require('gulp-rev-all'),
pkg = require('./package.json');
// Package.json / Gulp Dependencies
var appCache = require("gulp-manifest"),
concat = require('gulp-concat'),
del = require('del'),
eslint = require('gulp-eslint');
gutil = require('gulp-util'),
htmlmin = require('gulp-htmlmin'),
jsonlint = require("gulp-jsonlint"),
karma = require('karma').server,
less = require('gulp-less'),
mainBowerFiles = require('main-bower-files'),
minifyCSS = require('gulp-minify-css'),
revAll = require('gulp-rev-all'),
runSequence = require('run-sequence'),
sourcemaps = require('gulp-sourcemaps'),
svgstore = require('gulp-svgstore'),
svgmin = require('gulp-svgmin'),
jsonlint = require("gulp-jsonlint"),
appCache = require("gulp-manifest"),
jasmine = require('gulp-jasmine'),
pkg = require('./package.json');
template = require('gulp-template'),
templateCache = require('gulp-angular-templatecache'),
uglify = require('gulp-uglify');
var cdnHostStr = '';
@@ -36,20 +39,31 @@ gulp.task('less', function() {
});
gulp.task('js-lint', function() {
return gulp.src('app/js/**/*.js')
.pipe(jshint({
undef: true,
unused: true,
curly: true,
predef: [ 'angular','DB','d3', 'ga', 'GAPI_KEY', 'document' , 'LZString' ]
return gulp.src(['app/js/**/*.js', '!app/js/template_cache.js', '!app/js/db.js'])
.pipe(eslint({
globals: { angular:1, DB:1, d3:1, ga:1, GAPI_KEY:1, LZString: 1 },
rules: {
quotes: [2, 'single'],
strict: 'global',
eqeqeq: 'smart',
'space-after-keywords': [2, 'always'],
'no-use-before-define': 'no-func',
'space-before-function-paren': [2, 'never'],
'space-before-blocks': [2, 'always'],
'object-curly-spacing': [2, "always"],
'brace-style': [2, '1tbs', { allowSingleLine: true }]
},
envs: ['browser']
}))
.pipe(jshint.reporter('default'));
.pipe(eslint.format())
.pipe(eslint.failAfterError());
});
gulp.task('json-lint', function() {
return gulp.src('data/**/*.json')
.pipe(jsonlint())
.pipe(jsonlint.reporter());
.pipe(jsonlint.reporter())
.pipe(jsonlint.failAfterError());
});
gulp.task('bower', function(){
@@ -187,11 +201,11 @@ gulp.task('watch', function() {
});
gulp.task('cache-bust', function(done) {
var revAll = new RevAll({ prefix: cdnHostStr, dontRenameFile: ['.html','db.json'] });
var rev_all = new revAll({ prefix: cdnHostStr, dontRenameFile: ['.html','db.json'] });
var stream = gulp.src('build/**')
.pipe(revAll.revision())
.pipe(rev_all.revision())
.pipe(gulp.dest('build'))
.pipe(revAll.manifestFile())
.pipe(rev_all.manifestFile())
.pipe(gulp.dest('build'));
stream.on('end', function() {
@@ -238,9 +252,13 @@ gulp.task('upload', function(done) {
);
});
gulp.task('test', function () {
return gulp.src('tests/test-*.js')
.pipe(jasmine());
gulp.task('test', function (done) {
karma.start({
configFile: __dirname + '/test/karma.conf.js',
singleRun: true
}, function(exitStatus) {
done(exitStatus ? new gutil.PluginError('karma', { message: 'Unit tests failed!' }) : undefined);
});
});
gulp.task('lint', ['js-lint', 'json-lint']);

View File

@@ -9,14 +9,15 @@
"engine": "node >= 0.12.2",
"dependencies": {},
"devDependencies": {
"angular-mocks": "^1.3.16",
"async": "^0.9.0",
"del": "^1.1.1",
"gulp": "^3.8.11",
"gulp-angular-templatecache": "^1.6.0",
"gulp-concat": "^2.5.2",
"gulp-eslint": "^0.13.2",
"gulp-htmlmin": "^1.1.1",
"gulp-jasmine": "^2.0.1",
"gulp-jshint": "^1.10.0",
"gulp-jsonlint": "^1.0.2",
"gulp-less": "^3.0.2",
"gulp-manifest": "0.0.6",
@@ -28,7 +29,11 @@
"gulp-template": "^3.0.0",
"gulp-uglify": "^1.2.0",
"gulp-util": "^3.0.4",
"jasmine-core": "^2.3.4",
"json-concat": "0.0.0",
"karma": "^0.12.36",
"karma-chrome-launcher": "^0.1.12",
"karma-jasmine": "^0.3.5",
"main-bower-files": "^2.6.2",
"run-sequence": "^1.0.2",
"uglify-js": "^2.4.19"

34
test/karma.conf.js Normal file
View File

@@ -0,0 +1,34 @@
// Karma configuration
// Generated on Thu Jun 11 2015 19:39:40 GMT-0700 (PDT)
module.exports = function(config) {
config.set({
basePath: '',
// frameworks to use
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
frameworks: ['jasmine'],
// list of files / patterns to load in the browser
files: [
'../build/lib*.js',
'../node_modules/angular-mocks/angular-mocks.js',
'../build/app*.js',
'tests/**/*.js'
],
// list of files to exclude
exclude: [],
// preprocess matching files before serving them to the browser
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
preprocessors: {},
// test results reporter to use
// possible values: 'dots', 'progress'
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
reporters: ['progress'],
port: 9876,
colors: true,
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
logLevel: config.LOG_INFO,
autoWatch: false,
browsers: ['Chrome'],
singleRun: false
});
};

66
test/tests/test-data.js Normal file
View File

@@ -0,0 +1,66 @@
describe("Database", function() {
var shipProperties = ["grp", "name", "manufacturer", "class", "cost", "speed", "boost", "agility", "shields", "armour", "fuelcost", "mass"];
it("has ships and components", function() {
expect(DB.ships).toBeDefined()
expect(DB.components.common).toBeDefined();
expect(DB.components.hardpoints).toBeDefined();
expect(DB.components.internal).toBeDefined();
expect(DB.components.bulkheads).toBeDefined();
});
it("has unique IDs for every hardpoint", function() {
var ids = {};
var groups = DB.components.hardpoints;
for (var g in groups) {
var group = groups[g];
for (var i = 0; i < group.length; i++) {
var id = group[i].id;
expect(ids[id]).toBeFalsy('ID already exists: ' + id);
expect(group[i].grp).toBeDefined('Hardpoint has no group defined, ID:' + id);
ids[id] = true;
}
}
});
it("has valid internal components", function() {
var ids = {};
var groups = DB.components.internal;
for (var g in groups) {
var group = groups[g];
for (var i = 0; i < group.length; i++) {
var id = group[i].id;
expect(ids[id]).toBeFalsy('ID already exists: ' + id);
expect(group[i].grp).toBeDefined('Internal component has no group defined, ID:' + id);
ids[id] = true;
}
}
});
it("has data for every ship", function() {
for (var s in DB.ships) {
for (var p = 0; p < shipProperties.length; p++) {
expect(DB.ships[s].properties[shipProperties[p]]).toBeDefined(shipProperties[p] + ' is missing for ' + s);
}
expect(DB.ships[s].slots.common.length).toEqual(7, s + ' is missing common slots');
expect(DB.ships[s].defaults.common.length).toEqual(7, s + ' is missing common defaults');
expect(DB.ships[s].slots.hardpoints.length).toEqual(DB.ships[s].defaults.hardpoints.length, s + ' hardpoint slots and defaults dont match');
expect(DB.ships[s].slots.internal.length).toEqual(DB.ships[s].defaults.internal.length, s + ' hardpoint slots and defaults dont match');
expect(DB.ships[s].retailCost).toBeGreaterThan(DB.ships[s].properties.cost, s + ' has invalid retail cost');
expect(DB.components.bulkheads[s]).toBeDefined(s + ' is missing bulkheads');
}
});
it("has components with a group defined", function() {
for (var i = 0; i < DB.components.common.length; i++) {
var group = DB.components.common[i];
for (var c in group) {
expect(group[c].grp).toBeDefined('Common component has no group defined, Type: ' + i + ', ID: ' + c);
}
}
});
});

View File

@@ -0,0 +1,34 @@
describe("Ship Factory", function() {
var Ship;
beforeEach(module('shipyard'));
beforeEach(inject(['Ship', function (_Ship_) {
Ship = _Ship_;
}]));
it("can build all ships", function() {
for (var s in DB.ships) {
var shipData = DB.ships[s];
var ship = new Ship(s, shipData.properties, shipData.slots);
for (p in shipData.properties) {
expect(ship[p]).toEqual(shipData.properties[p], s + ' property [' + p + '] does not match when built');
}
ship.buildWith(shipData.defaults);
expect(ship.totalCost).toEqual(shipData.retailCost, s + ' retail cost does not match default build cost');
expect(ship.priorityBands[0].retracted).toBeGreaterThan(0, s + ' cargo');
expect(ship.powerAvailable).toBeGreaterThan(0, s + ' powerAvailable');
expect(ship.unladenRange).toBeGreaterThan(0, s + ' unladenRange');
expect(ship.ladenRange).toBeGreaterThan(0, s + ' ladenRange');
expect(ship.fuelCapacity).toBeGreaterThan(0, s + ' fuelCapacity');
expect(ship.unladenTotalRange).toBeGreaterThan(0, s + ' unladenTotalRange');
expect(ship.ladenTotalRange).toBeGreaterThan(0, s + ' ladenTotalRange');
expect(ship.shieldStrength).toBeGreaterThan(0, s + ' shieldStrength');
expect(ship.armourTotal).toBeGreaterThan(0, s + ' armourTotal');
}
});
});

View File

@@ -1,68 +0,0 @@
var data = require('../app/db.json');
var shipProperties = ["grp", "name", "manufacturer", "class", "cost", "speed", "boost", "agility", "shields", "armour", "fuelcost", "mass"];
describe("Database", function() {
it("has ships and components", function() {
expect(data.ships).toBeDefined()
expect(data.components.common).toBeDefined();
expect(data.components.hardpoints).toBeDefined();
expect(data.components.internal).toBeDefined();
expect(data.components.bulkheads).toBeDefined();
});
it("has unique IDs for every hardpoint", function() {
var ids = {};
var groups = data.components.hardpoints;
for (var g in groups) {
var group = groups[g];
for (var i = 0; i < group.length; i++) {
var id = group[i].id;
expect(ids[id]).toBeFalsy('ID already exists: ' + id);
expect(group[i].grp).toBeDefined('Hardpoint has no group defined, ID:' + id);
ids[id] = true;
}
}
});
it("has valid internal components", function() {
var ids = {};
var groups = data.components.internal;
for (var g in groups) {
var group = groups[g];
for (var i = 0; i < group.length; i++) {
var id = group[i].id;
expect(ids[id]).toBeFalsy('ID already exists: ' + id);
expect(group[i].grp).toBeDefined('Internal component has no group defined, ID:' + id);
ids[id] = true;
}
}
});
it("has data for every ship", function() {
for (var s in data.ships) {
for (var p = 0; p < shipProperties.length; p++) {
expect(data.ships[s].properties[shipProperties[p]]).toBeDefined(shipProperties[p] + ' is missing for ' + s);
}
expect(data.ships[s].slots.common.length).toEqual(7, s + ' is missing common slots');
expect(data.ships[s].defaults.common.length).toEqual(7, s + ' is missing common defaults');
expect(data.ships[s].slots.hardpoints.length).toEqual(data.ships[s].defaults.hardpoints.length, s + ' hardpoint slots and defaults dont match');
expect(data.ships[s].slots.internal.length).toEqual(data.ships[s].defaults.internal.length, s + ' hardpoint slots and defaults dont match');
expect(data.ships[s].retailCost).toBeGreaterThan(data.ships[s].properties.cost, s + ' has invalid retail cost');
expect(data.components.bulkheads[s]).toBeDefined(s + ' is missing bulkheads');
}
});
it("has components with a group defined", function() {
for (var i = 0; i < data.components.common.length; i++) {
var group = data.components.common[i];
for (var c in group) {
expect(group[c].grp).toBeDefined('Common component has no group defined, Type: ' + i + ', ID: ' + c);
}
}
});
});