mirror of
https://github.com/chartjs/Chart.js.git
synced 2025-12-08 20:36:08 +00:00
Start writing tests for core.helpers. Fix a number of small bugs found during testing
This commit is contained in:
parent
7fed38f1ab
commit
371bc8913f
@ -57,12 +57,6 @@
|
||||
});
|
||||
return base;
|
||||
},
|
||||
merge = helpers.merge = function(base, master) {
|
||||
//Merge properties in left object over to a shallow clone of object right.
|
||||
var args = Array.prototype.slice.call(arguments, 0);
|
||||
args.unshift({});
|
||||
return extend.apply(null, args);
|
||||
},
|
||||
// Need a special merge function to chart configs since they are now grouped
|
||||
configMerge = helpers.configMerge = function(_base) {
|
||||
var base = clone(_base);
|
||||
@ -84,7 +78,13 @@
|
||||
helpers.each(value, function(valueObj, index) {
|
||||
|
||||
if (index < baseArray.length) {
|
||||
baseArray[index] = helpers.configMerge(baseArray[index], valueObj);
|
||||
if (typeof baseArray[index] == 'object' && baseArray[index] !== null && typeof valueObj == 'object' && valueObj !== null) {
|
||||
// Two objects are coming together. Do a merge of them.
|
||||
baseArray[index] = helpers.configMerge(baseArray[index], valueObj);
|
||||
} else {
|
||||
// Just overwrite in this case since there is nothing to merge
|
||||
baseArray[index] = valueObj;
|
||||
}
|
||||
} else {
|
||||
baseArray.push(valueObj); // nothing to merge
|
||||
}
|
||||
@ -143,12 +143,12 @@
|
||||
return base;
|
||||
},
|
||||
getValueAtIndexOrDefault = helpers.getValueAtIndexOrDefault = function(value, index, defaultValue) {
|
||||
if (!value) {
|
||||
if (value === undefined || value === null) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
if (helpers.isArray(value) && index < value.length) {
|
||||
return value[index];
|
||||
if (helpers.isArray(value)) {
|
||||
return index < value.length ? value[index] : defaultValue;
|
||||
}
|
||||
|
||||
return value;
|
||||
@ -176,7 +176,7 @@
|
||||
},
|
||||
findNextWhere = helpers.findNextWhere = function(arrayToSearch, filterCallback, startIndex) {
|
||||
// Default to start of the array
|
||||
if (!startIndex) {
|
||||
if (startIndex === undefined || startIndex === null) {
|
||||
startIndex = -1;
|
||||
}
|
||||
for (var i = startIndex + 1; i < arrayToSearch.length; i++) {
|
||||
@ -188,7 +188,7 @@
|
||||
},
|
||||
findPreviousWhere = helpers.findPreviousWhere = function(arrayToSearch, filterCallback, startIndex) {
|
||||
// Default to end of the array
|
||||
if (!startIndex) {
|
||||
if (startIndex === undefined || startIndex === null) {
|
||||
startIndex = arrayToSearch.length;
|
||||
}
|
||||
for (var i = startIndex - 1; i >= 0; i--) {
|
||||
@ -259,18 +259,6 @@
|
||||
return Math.log(x) / Math.LN10;
|
||||
}
|
||||
},
|
||||
cap = helpers.cap = function(valueToCap, maxValue, minValue) {
|
||||
if (isNumber(maxValue)) {
|
||||
if (valueToCap > maxValue) {
|
||||
return maxValue;
|
||||
}
|
||||
} else if (isNumber(minValue)) {
|
||||
if (valueToCap < minValue) {
|
||||
return minValue;
|
||||
}
|
||||
}
|
||||
return valueToCap;
|
||||
},
|
||||
getDecimalPlaces = helpers.getDecimalPlaces = function(num) {
|
||||
if (num % 1 !== 0 && isNumber(num)) {
|
||||
var s = num.toString();
|
||||
@ -335,94 +323,16 @@
|
||||
},
|
||||
nextItem = helpers.nextItem = function(collection, index, loop) {
|
||||
if (loop) {
|
||||
return collection[index + 1] || collection[0];
|
||||
return index >= collection.length - 1 ? collection[0] : collection[index + 1];
|
||||
}
|
||||
return collection[index + 1] || collection[collection.length - 1];
|
||||
|
||||
return index >= collection.length - 1 ? collection[collection.length - 1] : collection[index + 1];
|
||||
},
|
||||
previousItem = helpers.previousItem = function(collection, index, loop) {
|
||||
if (loop) {
|
||||
return collection[index - 1] || collection[collection.length - 1];
|
||||
return index <= 0 ? collection[collection.length - 1] : collection[index - 1];
|
||||
}
|
||||
return collection[index - 1] || collection[0];
|
||||
},
|
||||
calculateOrderOfMagnitude = helpers.calculateOrderOfMagnitude = function(val) {
|
||||
return Math.floor(Math.log(val) / Math.LN10);
|
||||
},
|
||||
calculateScaleRange = helpers.calculateScaleRange = function(valuesArray, drawingSize, textSize, startFromZero, integersOnly) {
|
||||
|
||||
//Set a minimum step of two - a point at the top of the graph, and a point at the base
|
||||
var minSteps = 2,
|
||||
maxSteps = Math.floor(drawingSize / (textSize * 1.5)),
|
||||
skipFitting = (minSteps >= maxSteps);
|
||||
|
||||
var maxValue = max(valuesArray),
|
||||
minValue = min(valuesArray);
|
||||
|
||||
// We need some degree of seperation here to calculate the scales if all the values are the same
|
||||
// Adding/minusing 0.5 will give us a range of 1.
|
||||
if (maxValue === minValue) {
|
||||
maxValue += 0.5;
|
||||
// So we don't end up with a graph with a negative start value if we've said always start from zero
|
||||
if (minValue >= 0.5 && !startFromZero) {
|
||||
minValue -= 0.5;
|
||||
} else {
|
||||
// Make up a whole number above the values
|
||||
maxValue += 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
var valueRange = Math.abs(maxValue - minValue),
|
||||
rangeOrderOfMagnitude = calculateOrderOfMagnitude(valueRange),
|
||||
graphMax = Math.ceil(maxValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude),
|
||||
graphMin = (startFromZero) ? 0 : Math.floor(minValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude),
|
||||
graphRange = graphMax - graphMin,
|
||||
stepValue = Math.pow(10, rangeOrderOfMagnitude),
|
||||
numberOfSteps = Math.round(graphRange / stepValue);
|
||||
|
||||
//If we have more space on the graph we'll use it to give more definition to the data
|
||||
while ((numberOfSteps > maxSteps || (numberOfSteps * 2) < maxSteps) && !skipFitting) {
|
||||
if (numberOfSteps > maxSteps) {
|
||||
stepValue *= 2;
|
||||
numberOfSteps = Math.round(graphRange / stepValue);
|
||||
// Don't ever deal with a decimal number of steps - cancel fitting and just use the minimum number of steps.
|
||||
if (numberOfSteps % 1 !== 0) {
|
||||
skipFitting = true;
|
||||
}
|
||||
}
|
||||
//We can fit in double the amount of scale points on the scale
|
||||
else {
|
||||
//If user has declared ints only, and the step value isn't a decimal
|
||||
if (integersOnly && rangeOrderOfMagnitude >= 0) {
|
||||
//If the user has said integers only, we need to check that making the scale more granular wouldn't make it a float
|
||||
if (stepValue / 2 % 1 === 0) {
|
||||
stepValue /= 2;
|
||||
numberOfSteps = Math.round(graphRange / stepValue);
|
||||
}
|
||||
//If it would make it a float break out of the loop
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
//If the scale doesn't have to be an int, make the scale more granular anyway.
|
||||
else {
|
||||
stepValue /= 2;
|
||||
numberOfSteps = Math.round(graphRange / stepValue);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (skipFitting) {
|
||||
numberOfSteps = minSteps;
|
||||
stepValue = graphRange / numberOfSteps;
|
||||
}
|
||||
return {
|
||||
steps: numberOfSteps,
|
||||
stepValue: stepValue,
|
||||
min: graphMin,
|
||||
max: graphMin + (numberOfSteps * stepValue)
|
||||
};
|
||||
|
||||
return index <= 0 ? collection[0] : collection[index - 1];
|
||||
},
|
||||
// Implementation of the nice number algorithm used in determining where axis labels will go
|
||||
niceNum = helpers.niceNum = function(range, round) {
|
||||
@ -505,17 +415,6 @@
|
||||
return tmpl(templateString, valuesObject);
|
||||
},
|
||||
/* jshint ignore:end */
|
||||
generateLabels = helpers.generateLabels = function(templateString, numberOfSteps, graphMin, stepValue) {
|
||||
var labelsArray = new Array(numberOfSteps);
|
||||
if (templateString) {
|
||||
each(labelsArray, function(val, index) {
|
||||
labelsArray[index] = template(templateString, {
|
||||
value: (graphMin + (stepValue * (index + 1)))
|
||||
});
|
||||
});
|
||||
}
|
||||
return labelsArray;
|
||||
},
|
||||
//--Animation methods
|
||||
//Easing functions adapted from Robert Penner's easing equations
|
||||
//http://www.robertpenner.com/easing/
|
||||
|
||||
@ -0,0 +1,385 @@
|
||||
describe('Core helper tests', function() {
|
||||
|
||||
var helpers;
|
||||
|
||||
beforeAll(function() {
|
||||
helpers = window.Chart.helpers;
|
||||
});
|
||||
|
||||
it('Should iterate over an array and pass the extra data to that function', function() {
|
||||
var testData = [0, 9, "abc"];
|
||||
var scope = {}; // fake out the scope and ensure that 'this' is the correct thing
|
||||
|
||||
helpers.each(testData, function(item, index) {
|
||||
expect(item).not.toBe(undefined);
|
||||
expect(index).not.toBe(undefined);
|
||||
|
||||
expect(testData[index]).toBe(item);
|
||||
expect(this).toBe(scope);
|
||||
}, scope);
|
||||
|
||||
// Reverse iteration
|
||||
var iterated = [];
|
||||
helpers.each(testData, function(item, index) {
|
||||
expect(item).not.toBe(undefined);
|
||||
expect(index).not.toBe(undefined);
|
||||
|
||||
expect(testData[index]).toBe(item);
|
||||
expect(this).toBe(scope);
|
||||
|
||||
iterated.push(item);
|
||||
}, scope, true);
|
||||
|
||||
expect(iterated.slice().reverse()).toEqual(testData);
|
||||
});
|
||||
|
||||
it('Should iterate over properties in an object', function() {
|
||||
var testData = {
|
||||
myProp1: 'abc',
|
||||
myProp2: 276,
|
||||
myProp3: ['a', 'b']
|
||||
};
|
||||
|
||||
helpers.each(testData, function(value, key) {
|
||||
if (key === 'myProp1') {
|
||||
expect(value).toBe('abc');
|
||||
} else if (key === 'myProp2') {
|
||||
expect(value).toBe(276);
|
||||
} else if (key === 'myProp3') {
|
||||
expect(value).toEqual(['a', 'b']);
|
||||
} else {
|
||||
expect(false).toBe(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should not error when iterating over a null object', function() {
|
||||
expect(function() {
|
||||
helpers.each(undefined);
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it('Should clone an object', function() {
|
||||
var testData = {
|
||||
myProp1: 'abc',
|
||||
myProp2: ['a', 'b'],
|
||||
myProp3: {
|
||||
myProp4: 5,
|
||||
myProp5: [1, 2]
|
||||
}
|
||||
};
|
||||
|
||||
var clone = helpers.clone(testData);
|
||||
expect(clone).toEqual(testData);
|
||||
expect(clone).not.toBe(testData);
|
||||
|
||||
expect(clone.myProp2).not.toBe(testData.myProp2);
|
||||
expect(clone.myProp3).not.toBe(testData.myProp3);
|
||||
expect(clone.myProp3.myProp5).not.toBe(testData.myProp3.myProp5);
|
||||
});
|
||||
|
||||
it('should extend an object', function() {
|
||||
var original = {
|
||||
myProp1: 'abc',
|
||||
myProp2: 56
|
||||
};
|
||||
|
||||
var extension = {
|
||||
myProp3: [2, 5, 6],
|
||||
myProp2: 0
|
||||
};
|
||||
|
||||
helpers.extend(original, extension);
|
||||
|
||||
expect(original).toEqual({
|
||||
myProp1: 'abc',
|
||||
myProp2: 0,
|
||||
myProp3: [2, 5, 6],
|
||||
});
|
||||
});
|
||||
|
||||
it('Should merge a normal config without scales', function() {
|
||||
var baseConfig = {
|
||||
valueProp: 5,
|
||||
arrayProp: [1, 2, 3, 4, 5, 6],
|
||||
objectProp: {
|
||||
prop1: 'abc',
|
||||
prop2: 56
|
||||
}
|
||||
};
|
||||
|
||||
var toMerge = {
|
||||
valueProp2: null,
|
||||
arrayProp: ['a', 'c'],
|
||||
objectProp: {
|
||||
prop1: 'c',
|
||||
prop3: 'prop3'
|
||||
}
|
||||
};
|
||||
|
||||
var merged = helpers.configMerge(baseConfig, toMerge);
|
||||
expect(merged).toEqual({
|
||||
valueProp: 5,
|
||||
valueProp2: null,
|
||||
arrayProp: ['a', 'c', 3, 4, 5, 6],
|
||||
objectProp: {
|
||||
prop1: 'c',
|
||||
prop2: 56,
|
||||
prop3: 'prop3'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should merge arrays containing objects', function() {
|
||||
var baseConfig = {
|
||||
arrayProp: [{
|
||||
prop1: 'abc',
|
||||
prop2: 56
|
||||
}],
|
||||
};
|
||||
|
||||
var toMerge = {
|
||||
arrayProp: [{
|
||||
prop1: 'myProp1',
|
||||
prop3: 'prop3'
|
||||
}, 2, {
|
||||
prop1: 'myProp1'
|
||||
}],
|
||||
};
|
||||
|
||||
var merged = helpers.configMerge(baseConfig, toMerge);
|
||||
expect(merged).toEqual({
|
||||
arrayProp: [{
|
||||
prop1: 'myProp1',
|
||||
prop2: 56,
|
||||
prop3: 'prop3'
|
||||
},
|
||||
2, {
|
||||
prop1: 'myProp1'
|
||||
}],
|
||||
});
|
||||
});
|
||||
|
||||
it ('Should merge scale configs', function() {
|
||||
var baseConfig = {
|
||||
scales: {
|
||||
prop1: {
|
||||
abc: 123,
|
||||
def: '456'
|
||||
},
|
||||
prop2: 777,
|
||||
yAxes: [{
|
||||
type: 'linear',
|
||||
}, {
|
||||
type: 'log'
|
||||
}]
|
||||
}
|
||||
};
|
||||
|
||||
var toMerge = {
|
||||
scales: {
|
||||
prop1: {
|
||||
def: 'bbb',
|
||||
ghi: 78
|
||||
},
|
||||
prop2: null,
|
||||
yAxes: [{
|
||||
type: 'linear',
|
||||
axisProp: 456
|
||||
}, {
|
||||
// pulls in linear default config since axis type changes
|
||||
type: 'linear',
|
||||
position: 'right'
|
||||
}, {
|
||||
// Pulls in linear default config since axis not in base
|
||||
type: 'linear'
|
||||
}]
|
||||
}
|
||||
};
|
||||
|
||||
var merged = helpers.configMerge(baseConfig, toMerge);
|
||||
expect(merged).toEqual({
|
||||
scales: {
|
||||
prop1: {
|
||||
abc: 123,
|
||||
def: 'bbb',
|
||||
ghi: 78
|
||||
},
|
||||
prop2: null,
|
||||
yAxes: [{
|
||||
type: 'linear',
|
||||
axisProp: 456
|
||||
}, {
|
||||
type: 'linear',
|
||||
display: true,
|
||||
position: "right",
|
||||
gridLines: {
|
||||
show: true,
|
||||
color: "rgba(0, 0, 0, 0.1)",
|
||||
lineWidth: 1,
|
||||
drawOnChartArea: true,
|
||||
drawTicks: true,
|
||||
zeroLineWidth: 1,
|
||||
zeroLineColor: "rgba(0,0,0,0.25)",
|
||||
},
|
||||
reverse: false,
|
||||
beginAtZero: false,
|
||||
override: null,
|
||||
labels: {
|
||||
show: true,
|
||||
mirror: false,
|
||||
padding: 10,
|
||||
template: "<%=value.toLocaleString()%>",
|
||||
fontSize: 12,
|
||||
fontStyle: "normal",
|
||||
fontColor: "#666",
|
||||
fontFamily: "Helvetica Neue"
|
||||
}
|
||||
}, {
|
||||
type: 'linear',
|
||||
display: true,
|
||||
position: "left",
|
||||
gridLines: {
|
||||
show: true,
|
||||
color: "rgba(0, 0, 0, 0.1)",
|
||||
lineWidth: 1,
|
||||
drawOnChartArea: true,
|
||||
drawTicks: true,
|
||||
zeroLineWidth: 1,
|
||||
zeroLineColor: "rgba(0,0,0,0.25)",
|
||||
},
|
||||
reverse: false,
|
||||
beginAtZero: false,
|
||||
override: null,
|
||||
labels: {
|
||||
show: true,
|
||||
mirror: false,
|
||||
padding: 10,
|
||||
template: "<%=value.toLocaleString()%>",
|
||||
fontSize: 12,
|
||||
fontStyle: "normal",
|
||||
fontColor: "#666",
|
||||
fontFamily: "Helvetica Neue"
|
||||
}
|
||||
}]
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it ('should get value or default', function() {
|
||||
expect(helpers.getValueAtIndexOrDefault(98, 0, 56)).toBe(98);
|
||||
expect(helpers.getValueAtIndexOrDefault(0, 0, 56)).toBe(0);
|
||||
expect(helpers.getValueAtIndexOrDefault(undefined, undefined, 56)).toBe(56);
|
||||
expect(helpers.getValueAtIndexOrDefault([1, 2, 3], 1, 100)).toBe(2);
|
||||
expect(helpers.getValueAtIndexOrDefault([1, 2, 3], 3, 100)).toBe(100);
|
||||
});
|
||||
|
||||
it ('should filter an array', function() {
|
||||
var data = [-10, 0, 6, 0, 7];
|
||||
var callback = function(item) { return item > 2};
|
||||
expect(helpers.where(data, callback)).toEqual([6, 7]);
|
||||
expect(helpers.findNextWhere(data, callback)).toEqual(6);
|
||||
expect(helpers.findNextWhere(data, callback, 2)).toBe(7);
|
||||
expect(helpers.findNextWhere(data, callback, 4)).toBe(undefined);
|
||||
expect(helpers.findPreviousWhere(data, callback)).toBe(7);
|
||||
expect(helpers.findPreviousWhere(data, callback, 3)).toBe(6);
|
||||
expect(helpers.findPreviousWhere(data, callback, 0)).toBe(undefined);
|
||||
});
|
||||
|
||||
it ('Should get the correct sign', function() {
|
||||
expect(helpers.sign(0)).toBe(0);
|
||||
expect(helpers.sign(10)).toBe(1);
|
||||
expect(helpers.sign(-5)).toBe(-1);
|
||||
});
|
||||
|
||||
it ('should do a log10 operation', function() {
|
||||
expect(helpers.log10(0)).toBe(-Infinity);
|
||||
expect(helpers.log10(1)).toBe(0);
|
||||
expect(helpers.log10(1000)).toBe(3);
|
||||
});
|
||||
|
||||
it ('Should generate ids', function() {
|
||||
expect(helpers.uid()).toBe('chart-0');
|
||||
expect(helpers.uid()).toBe('chart-1');
|
||||
expect(helpers.uid()).toBe('chart-2');
|
||||
expect(helpers.uid()).toBe('chart-3');
|
||||
});
|
||||
|
||||
it ('should detect a number', function() {
|
||||
expect(helpers.isNumber(123)).toBe(true);
|
||||
expect(helpers.isNumber('123')).toBe(true);
|
||||
expect(helpers.isNumber(null)).toBe(false);
|
||||
expect(helpers.isNumber(NaN)).toBe(false);
|
||||
expect(helpers.isNumber(undefined)).toBe(false);
|
||||
expect(helpers.isNumber('cbc')).toBe(false);
|
||||
});
|
||||
|
||||
it ('should convert between radians and degrees', function() {
|
||||
expect(helpers.toRadians(180)).toBe(Math.PI);
|
||||
expect(helpers.toRadians(90)).toBe(0.5 * Math.PI);
|
||||
expect(helpers.toDegrees(Math.PI)).toBe(180);
|
||||
expect(helpers.toDegrees(Math.PI * 3 /2)).toBe(270);
|
||||
});
|
||||
|
||||
it ('should get an angle from a point', function() {
|
||||
var center = {
|
||||
x: 0,
|
||||
y: 0
|
||||
};
|
||||
|
||||
expect(helpers.getAngleFromPoint(center, {x: 0, y: 10})).toEqual({
|
||||
angle: Math.PI / 2,
|
||||
distance: 10,
|
||||
});
|
||||
|
||||
expect(helpers.getAngleFromPoint(center, {x: Math.sqrt(2), y: Math.sqrt(2)})).toEqual({
|
||||
angle: Math.PI / 4,
|
||||
distance: 2
|
||||
});
|
||||
|
||||
expect(helpers.getAngleFromPoint(center, {x: -1.0 * Math.sqrt(2), y: -1.0 * Math.sqrt(2)})).toEqual({
|
||||
angle: Math.PI * 1.25,
|
||||
distance: 2
|
||||
});
|
||||
});
|
||||
|
||||
it ('should spline curves', function() {
|
||||
expect(helpers.splineCurve({x: 0, y: 0}, {x: 1, y: 1}, { x: 2, y: 0}, 0)).toEqual({
|
||||
previous: {
|
||||
x: 1,
|
||||
y: 1,
|
||||
},
|
||||
next: {
|
||||
x: 1,
|
||||
y: 1,
|
||||
}
|
||||
});
|
||||
|
||||
expect(helpers.splineCurve({x: 0, y: 0}, {x: 1, y: 1}, { x: 2, y: 0}, 1)).toEqual({
|
||||
previous: {
|
||||
x: 0,
|
||||
y: 1,
|
||||
},
|
||||
next: {
|
||||
x: 2,
|
||||
y: 1,
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it ('should get the next or previous item in an array', function() {
|
||||
var testData = [0, 1, 2];
|
||||
|
||||
expect(helpers.nextItem(testData, 0, false)).toEqual(1);
|
||||
expect(helpers.nextItem(testData, 2, false)).toEqual(2);
|
||||
expect(helpers.nextItem(testData, 2, true)).toEqual(0);
|
||||
expect(helpers.nextItem(testData, 1, true)).toEqual(2);
|
||||
expect(helpers.nextItem(testData, -1, false)).toEqual(0);
|
||||
|
||||
expect(helpers.previousItem(testData, 0, false)).toEqual(0);
|
||||
expect(helpers.previousItem(testData, 0, true)).toEqual(2);
|
||||
expect(helpers.previousItem(testData, 2, false)).toEqual(1);
|
||||
expect(helpers.previousItem(testData, 1, true)).toEqual(0);
|
||||
|
||||
});
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user