From: Evert Timberg Date: Sat, 29 Aug 2015 02:32:56 +0000 (-0400) Subject: Start writing tests for core.helpers. Fix a number of small bugs found during testing X-Git-Tag: 2.0.0-alpha4~36^2~5 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=371bc8913f5abac58517b42a6c86a6400d09146e;p=thirdparty%2FChart.js.git Start writing tests for core.helpers. Fix a number of small bugs found during testing --- diff --git a/src/core/core.helpers.js b/src/core/core.helpers.js index 2fbb644a9..46ad752d8 100644 --- a/src/core/core.helpers.js +++ b/src/core/core.helpers.js @@ -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 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); - } - - } + return index <= 0 ? collection[collection.length - 1] : collection[index - 1]; } - - 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/ diff --git a/test/core.helpers.tests.js b/test/core.helpers.tests.js index e69de29bb..730222b5b 100644 --- a/test/core.helpers.tests.js +++ b/test/core.helpers.tests.js @@ -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); + + }); +}); \ No newline at end of file