Cleanup and reorganize core and canvas helpers

Move some of the "core" and "canvas" utils in `helpers.core.js` and `helpers.canvas.js` and introduce the new `isNullOrUndef` and `isObject` helpers. Deprecate `indexOf` and rename `drawRoundedRectangle` to `roundedRect` which now creates a simple `rect` path if radius is 0. Write missing unit tests for the moved helpers.
This commit is contained in:
Simon Brunel 2017-06-24 11:46:06 +02:00 committed by Evert Timberg
parent 6c82c93853
commit 5196e05062
18 changed files with 630 additions and 275 deletions

1
.gitignore vendored
View File

@ -4,6 +4,7 @@
/dist
/docs/index.md
/gh-pages
/jsdoc
/node_modules
.DS_Store
.idea

View File

@ -3,11 +3,12 @@
*/
var Chart = require('./core/core.js')();
require('./helpers/helpers.core')(Chart);
require('./core/core.helpers')(Chart);
require('./helpers/helpers.time')(Chart);
require('./helpers/helpers.canvas')(Chart);
require('./platforms/platform.js')(Chart);
require('./core/core.canvasHelpers')(Chart);
require('./core/core.element')(Chart);
require('./core/core.plugin.js')(Chart);
require('./core/core.animation')(Chart);

View File

@ -285,13 +285,13 @@ module.exports = function(Chart) {
var ilen = points.length;
var i = 0;
Chart.canvasHelpers.clipArea(chart.ctx, area);
Chart.helpers.canvas.clipArea(chart.ctx, area);
if (lineEnabled(me.getDataset(), chart.options)) {
meta.dataset.draw();
}
Chart.canvasHelpers.unclipArea(chart.ctx);
Chart.helpers.canvas.unclipArea(chart.ctx);
// Draw the points
for (; i<ilen; ++i) {

View File

@ -151,7 +151,7 @@ module.exports = function(Chart) {
},
clear: function() {
helpers.clear(this);
helpers.canvas.clear(this);
return this;
},
@ -511,7 +511,7 @@ module.exports = function(Chart) {
me.clear();
if (easingValue === undefined || easingValue === null) {
if (helpers.isNullOrUndef(easingValue)) {
easingValue = 1;
}
@ -688,7 +688,7 @@ module.exports = function(Chart) {
if (canvas) {
me.unbindEvents();
helpers.clear(me);
helpers.canvas.clear(me);
platform.releaseContext(me.ctx);
me.canvas = null;
me.ctx = null;

View File

@ -5,32 +5,9 @@
var color = require('chartjs-color');
module.exports = function(Chart) {
// Global Chart helpers object for utility methods and classes
var helpers = Chart.helpers = {};
var helpers = Chart.helpers;
// -- Basic js utility methods
helpers.each = function(loopable, callback, self, reverse) {
// Check to see if null or undefined firstly.
var i, len;
if (helpers.isArray(loopable)) {
len = loopable.length;
if (reverse) {
for (i = len - 1; i >= 0; i--) {
callback.call(self, loopable[i], i);
}
} else {
for (i = 0; i < len; i++) {
callback.call(self, loopable[i], i);
}
}
} else if (typeof loopable === 'object') {
var keys = Object.keys(loopable);
len = keys.length;
for (i = 0; i < len; i++) {
callback.call(self, loopable[keys[i]], keys[i]);
}
}
};
helpers.clone = function(obj) {
var objClone = {};
helpers.each(obj, function(value, key) {
@ -123,32 +100,7 @@ module.exports = function(Chart) {
return base;
};
helpers.getValueAtIndexOrDefault = function(value, index, defaultValue) {
if (value === undefined || value === null) {
return defaultValue;
}
if (helpers.isArray(value)) {
return index < value.length ? value[index] : defaultValue;
}
return value;
};
helpers.getValueOrDefault = function(value, defaultValue) {
return value === undefined ? defaultValue : value;
};
helpers.indexOf = Array.prototype.indexOf?
function(array, item) {
return array.indexOf(item);
}:
function(array, item) {
for (var i = 0, ilen = array.length; i < ilen; ++i) {
if (array[i] === item) {
return i;
}
}
return -1;
};
helpers.where = function(collection, filterCallback) {
if (helpers.isArray(collection) && Array.prototype.filter) {
return collection.filter(filterCallback);
@ -178,7 +130,7 @@ module.exports = function(Chart) {
};
helpers.findNextWhere = function(arrayToSearch, filterCallback, startIndex) {
// Default to start of the array
if (startIndex === undefined || startIndex === null) {
if (helpers.isNullOrUndef(startIndex)) {
startIndex = -1;
}
for (var i = startIndex + 1; i < arrayToSearch.length; i++) {
@ -190,7 +142,7 @@ module.exports = function(Chart) {
};
helpers.findPreviousWhere = function(arrayToSearch, filterCallback, startIndex) {
// Default to end of the array
if (startIndex === undefined || startIndex === null) {
if (helpers.isNullOrUndef(startIndex)) {
startIndex = arrayToSearch.length;
}
for (var i = startIndex - 1; i >= 0; i--) {
@ -223,13 +175,6 @@ module.exports = function(Chart) {
return ChartElement;
};
helpers.noop = function() {};
helpers.uid = (function() {
var id = 0;
return function() {
return id++;
};
}());
// -- Math methods
helpers.isNumber = function(n) {
return !isNaN(parseFloat(n)) && isFinite(n);
@ -837,9 +782,6 @@ module.exports = function(Chart) {
canvas.style.width = width + 'px';
};
// -- Canvas methods
helpers.clear = function(chart) {
chart.ctx.clearRect(0, 0, chart.width, chart.height);
};
helpers.fontString = function(pixelSize, fontStyle, fontFamily) {
return fontStyle + ' ' + pixelSize + 'px ' + fontFamily;
};
@ -903,19 +845,6 @@ module.exports = function(Chart) {
});
return numberOfLines;
};
helpers.drawRoundedRectangle = function(ctx, x, y, width, height, radius) {
ctx.beginPath();
ctx.moveTo(x + radius, y);
ctx.lineTo(x + width - radius, y);
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
ctx.lineTo(x + width, y + height - radius);
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
ctx.lineTo(x + radius, y + height);
ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
ctx.lineTo(x, y + radius);
ctx.quadraticCurveTo(x, y, x + radius, y);
ctx.closePath();
};
helpers.color = !color?
function(value) {
@ -931,54 +860,10 @@ module.exports = function(Chart) {
return color(value);
};
helpers.isArray = Array.isArray?
function(obj) {
return Array.isArray(obj);
} :
function(obj) {
return Object.prototype.toString.call(obj) === '[object Array]';
};
// ! @see http://stackoverflow.com/a/14853974
helpers.arrayEquals = function(a0, a1) {
var i, ilen, v0, v1;
if (!a0 || !a1 || a0.length !== a1.length) {
return false;
}
for (i = 0, ilen=a0.length; i < ilen; ++i) {
v0 = a0[i];
v1 = a1[i];
if (v0 instanceof Array && v1 instanceof Array) {
if (!helpers.arrayEquals(v0, v1)) {
return false;
}
} else if (v0 !== v1) {
// NOTE: two different object instances will never be equal: {x:20} != {x:20}
return false;
}
}
return true;
};
helpers.callback = function(fn, args, thisArg) {
if (fn && typeof fn.call === 'function') {
return fn.apply(thisArg, args);
}
};
helpers.getHoverColor = function(colorValue) {
/* global CanvasPattern */
return (colorValue instanceof CanvasPattern) ?
colorValue :
helpers.color(colorValue).saturate(0.5).darken(0.1).rgbString();
};
/**
* Provided for backward compatibility, use Chart.helpers#callback instead.
* @function Chart.helpers#callCallback
* @deprecated since version 2.6.0
* @todo remove at version 3
*/
helpers.callCallback = helpers.callback;
};

View File

@ -422,7 +422,7 @@ module.exports = function(Chart) {
// Get the correct value. NaN bad inputs, If the value type is object get the x or y based on whether we are horizontal or not
getRightValue: function(rawValue) {
// Null and undefined values first
if (rawValue === null || typeof(rawValue) === 'undefined') {
if (helpers.isNullOrUndef(rawValue)) {
return NaN;
}
// isNaN(object) returns true, so make sure NaN is checking for a number; Discard Infinite values
@ -575,7 +575,7 @@ module.exports = function(Chart) {
helpers.each(me.ticks, function(tick, index) {
var label = (tick && tick.value) || tick;
// If the callback returned a null or undefined value, do not draw this line
if (label === undefined || label === null) {
if (helpers.isNullOrUndef(label)) {
return;
}
@ -583,7 +583,7 @@ module.exports = function(Chart) {
// Since we always show the last tick,we need may need to hide the last shown one before
var shouldSkip = (skipRatio > 1 && index % skipRatio > 0) || (index % skipRatio === 0 && index + skipRatio >= me.ticks.length);
if (shouldSkip && !isLastTick || (label === undefined || label === null)) {
if (shouldSkip && !isLastTick || helpers.isNullOrUndef(label)) {
return;
}

View File

@ -94,7 +94,7 @@ module.exports = function(Chart) {
ctx.fillStyle = color(ctx.fillStyle).alpha(ratio).rgbString();
}
Chart.canvasHelpers.drawPoint(ctx, pointStyle, radius, x, y);
Chart.helpers.canvas.drawPoint(ctx, pointStyle, radius, x, y);
}
});
};

View File

@ -1,10 +1,50 @@
'use strict';
module.exports = function(Chart) {
// Global Chart canvas helpers object for drawing items to canvas
var helpers = Chart.canvasHelpers = {};
var helpers = Chart.helpers;
helpers.drawPoint = function(ctx, pointStyle, radius, x, y) {
/**
* @namespace Chart.helpers.canvas
*/
helpers.canvas = {
/**
* Clears the entire canvas associated to the given `chart`.
* @param {Chart} chart - The chart for which to clear the canvas.
*/
clear: function(chart) {
chart.ctx.clearRect(0, 0, chart.width, chart.height);
},
/**
* Creates a "path" for a rectangle with rounded corners at position (x, y) with a
* given size (width, height) and the same `radius` for all corners.
* @param {CanvasRenderingContext2D} ctx - The canvas 2D Context.
* @param {Number} x - The x axis of the coordinate for the rectangle starting point.
* @param {Number} y - The y axis of the coordinate for the rectangle starting point.
* @param {Number} width - The rectangle's width.
* @param {Number} height - The rectangle's height.
* @param {Number} radius - The rounded amount (in pixels) for the four corners.
* @todo handler `radius` as top-left, top-right, bottom-right, bottom-left array/object?
* @todo clamp `radius` to the maximum "correct" value.
*/
roundedRect: function(ctx, x, y, width, height, radius) {
if (radius) {
ctx.moveTo(x + radius, y);
ctx.lineTo(x + width - radius, y);
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
ctx.lineTo(x + width, y + height - radius);
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
ctx.lineTo(x + radius, y + height);
ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
ctx.lineTo(x, y + radius);
ctx.quadraticCurveTo(x, y, x + radius, y);
} else {
ctx.rect(x, y, width, height);
}
}
};
helpers.canvas.drawPoint = function(ctx, pointStyle, radius, x, y) {
var type, edgeLength, xOffset, yOffset, height, size;
if (typeof pointStyle === 'object') {
@ -48,7 +88,9 @@ module.exports = function(Chart) {
var leftX = x - offset;
var topY = y - offset;
var sideSize = Math.SQRT2 * radius;
Chart.helpers.drawRoundedRectangle(ctx, leftX, topY, sideSize, sideSize, radius / 2);
ctx.beginPath();
this.roundedRect(ctx, leftX, topY, sideSize, sideSize, radius / 2);
ctx.closePath();
ctx.fill();
break;
case 'rectRot':
@ -110,18 +152,18 @@ module.exports = function(Chart) {
ctx.stroke();
};
helpers.clipArea = function(ctx, clipArea) {
helpers.canvas.clipArea = function(ctx, clipArea) {
ctx.save();
ctx.beginPath();
ctx.rect(clipArea.left, clipArea.top, clipArea.right - clipArea.left, clipArea.bottom - clipArea.top);
ctx.clip();
};
helpers.unclipArea = function(ctx) {
helpers.canvas.unclipArea = function(ctx) {
ctx.restore();
};
helpers.lineTo = function(ctx, previous, target, flip) {
helpers.canvas.lineTo = function(ctx, previous, target, flip) {
if (target.steppedLine) {
if (target.steppedLine === 'after') {
ctx.lineTo(previous.x, target.y);
@ -146,5 +188,34 @@ module.exports = function(Chart) {
target.y);
};
Chart.helpers.canvas = helpers;
/**
* Provided for backward compatibility, use Chart.helpers.canvas instead.
* @namespace Chart.canvasHelpers
* @deprecated since version 2.6.0
* @todo remove at version 3
* @private
*/
Chart.canvasHelpers = helpers.canvas;
/**
* Provided for backward compatibility, use Chart.helpers.canvas.clear instead.
* @namespace Chart.helpers.clear
* @deprecated since version 2.7.0
* @todo remove at version 3
* @private
*/
helpers.clear = helpers.canvas.clear;
/**
* Provided for backward compatibility, use Chart.helpers.canvas.roundedRect instead.
* @namespace Chart.helpers.drawRoundedRectangle
* @deprecated since version 2.7.0
* @todo remove at version 3
* @private
*/
helpers.drawRoundedRectangle = function(ctx) {
ctx.beginPath();
helpers.canvas.roundedRect.apply(this, arguments);
ctx.closePath();
};
};

182
src/helpers/helpers.core.js Normal file
View File

@ -0,0 +1,182 @@
'use strict';
module.exports = function(Chart) {
/**
* @namespace Chart.helpers
*/
var helpers = Chart.helpers = {
/**
* An empty function that can be used, for example, for optional callback.
*/
noop: function() {},
/**
* Returns a unique id, sequentially generated from a global variable.
* @returns {Number}
* @function
*/
uid: (function() {
var id = 0;
return function() {
return id++;
};
}()),
/**
* Returns true if `value` is neither null nor undefined, else returns false.
* @param {*} value - The value to test.
* @returns {Boolean}
* @since 2.7.0
*/
isNullOrUndef: function(value) {
return value === null || typeof value === 'undefined';
},
/**
* Returns true if `value` is an array, else returns false.
* @param {*} value - The value to test.
* @returns {Boolean}
* @function
*/
isArray: Array.isArray? Array.isArray : function(value) {
return Object.prototype.toString.call(value) === '[object Array]';
},
/**
* Returns true if `value` is an object (excluding null), else returns false.
* @param {*} value - The value to test.
* @returns {Boolean}
* @since 2.7.0
*/
isObject: function(value) {
return value !== null && Object.prototype.toString.call(value) === '[object Object]';
},
/**
* Returns `value` if defined, else returns `defaultValue`.
* @param {*} value - The value to return if defined.
* @param {*} defaultValue - The value to return if `value` is undefined.
* @returns {*}
*/
getValueOrDefault: function(value, defaultValue) {
return typeof value === 'undefined'? defaultValue : value;
},
/**
* Returns value at the given `index` in array if defined, else returns `defaultValue`.
* @param {Array} value - The array to lookup for value at `index`.
* @param {Number} index - The index in `value` to lookup for value.
* @param {*} defaultValue - The value to return if `value[index]` is undefined.
* @returns {*}
*/
getValueAtIndexOrDefault: function(value, index, defaultValue) {
if (helpers.isNullOrUndef(value)) {
return defaultValue;
}
if (helpers.isArray(value)) {
value = value[index];
return typeof value === 'undefined'? defaultValue : value;
}
return value;
},
/**
* Calls `fn` with the given `args` in the scope defined by `thisArg` and returns the
* value returned by `fn`. If `fn` is not a function, this method returns undefined.
* @param {Function} fn - The function to call.
* @param {Array|undefined|null} args - The arguments with which `fn` should be called.
* @param {Object} [thisArg] - The value of `this` provided for the call to `fn`.
* @returns {*}
*/
callback: function(fn, args, thisArg) {
if (fn && typeof fn.call === 'function') {
return fn.apply(thisArg, args);
}
},
/**
* Note(SB) for performance sake, this method should only be used when loopable type
* is unknown or in none intensive code (not called often and small loopable). Else
* it's preferable to use a regular for() loop and save extra function calls.
* @param {Object|Array} loopable - The object or array to be iterated.
* @param {Function} fn - The function to call for each item.
* @param {Object} [thisArg] - The value of `this` provided for the call to `fn`.
* @param {Boolean} [reverse] - If true, iterates backward on the loopable.
*/
each: function(loopable, fn, thisArg, reverse) {
var i, len, keys;
if (helpers.isArray(loopable)) {
len = loopable.length;
if (reverse) {
for (i = len - 1; i >= 0; i--) {
fn.call(thisArg, loopable[i], i);
}
} else {
for (i = 0; i < len; i++) {
fn.call(thisArg, loopable[i], i);
}
}
} else if (helpers.isObject(loopable)) {
keys = Object.keys(loopable);
len = keys.length;
for (i = 0; i < len; i++) {
fn.call(thisArg, loopable[keys[i]], keys[i]);
}
}
},
/**
* Returns true if the `a0` and `a1` arrays have the same content, else returns false.
* @see http://stackoverflow.com/a/14853974
* @param {Array} a0 - The array to compare
* @param {Array} a1 - The array to compare
* @returns {Boolean}
*/
arrayEquals: function(a0, a1) {
var i, ilen, v0, v1;
if (!a0 || !a1 || a0.length !== a1.length) {
return false;
}
for (i = 0, ilen=a0.length; i < ilen; ++i) {
v0 = a0[i];
v1 = a1[i];
if (v0 instanceof Array && v1 instanceof Array) {
if (!helpers.arrayEquals(v0, v1)) {
return false;
}
} else if (v0 !== v1) {
// NOTE: two different object instances will never be equal: {x:20} != {x:20}
return false;
}
}
return true;
}
};
/**
* Provided for backward compatibility, use Chart.helpers.callback instead.
* @function Chart.helpers.callCallback
* @deprecated since version 2.6.0
* @todo remove at version 3
* @private
*/
helpers.callCallback = helpers.callback;
/**
* Provided for backward compatibility, use Array.prototype.indexOf instead.
* Array.prototype.indexOf compatibility: Chrome, Opera, Safari, FF1.5+, IE9+
* @function Chart.helpers.indexOf
* @deprecated since version 2.7.0
* @todo remove at version 3
* @private
*/
helpers.indexOf = function(array, item, fromIndex) {
return Array.prototype.indexOf.call(array, item, fromIndex);
};
};

View File

@ -229,7 +229,7 @@ module.exports = function(Chart) {
var initial = canvas._chartjs.initial;
['height', 'width'].forEach(function(prop) {
var value = initial[prop];
if (value === undefined || value === null) {
if (helpers.isNullOrUndef(value)) {
canvas.removeAttribute(prop);
} else {
canvas.setAttribute(prop, value);

View File

@ -360,7 +360,7 @@ module.exports = function(Chart) {
var centerY = y + offSet;
// Draw pointStyle as legend symbol
Chart.canvasHelpers.drawPoint(ctx, legendItem.pointStyle, radius, centerX, centerY);
Chart.helpers.canvas.drawPoint(ctx, legendItem.pointStyle, radius, centerX, centerY);
} else {
// Draw box as legend symbol
if (!isLineWidthZero) {

View File

@ -2,7 +2,6 @@
module.exports = function(Chart) {
var helpers = Chart.helpers;
// Default config for a category scale
var defaultConfig = {
position: 'bottom'
@ -28,13 +27,13 @@ module.exports = function(Chart) {
if (me.options.ticks.min !== undefined) {
// user specified min value
findIndex = helpers.indexOf(labels, me.options.ticks.min);
findIndex = labels.indexOf(me.options.ticks.min);
me.minIndex = findIndex !== -1 ? findIndex : me.minIndex;
}
if (me.options.ticks.max !== undefined) {
// user specified max value
findIndex = helpers.indexOf(labels, me.options.ticks.max);
findIndex = labels.indexOf(me.options.ticks.max);
me.maxIndex = findIndex !== -1 ? findIndex : me.maxIndex;
}

View File

@ -89,6 +89,7 @@ Context.prototype._initMethods = function() {
},
moveTo: function() {},
quadraticCurveTo: function() {},
rect: function() {},
restore: function() {},
rotate: function() {},
save: function() {},

View File

@ -6,59 +6,6 @@ describe('Core helper tests', 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',
@ -268,14 +215,6 @@ describe('Core helper tests', function() {
expect(merged.scales.yAxes[2].ticks.callback).toEqual(jasmine.any(Function));
});
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) {
@ -588,20 +527,6 @@ describe('Core helper tests', function() {
expect(helpers.previousItem(testData, 1, true)).toEqual(0);
});
it('should clear a canvas', function() {
var context = window.createMockContext();
helpers.clear({
width: 100,
height: 150,
ctx: context
});
expect(context.getCalls()).toEqual([{
name: 'clearRect',
args: [0, 0, 100, 150]
}]);
});
it('should return the width of the longest text in an Array and 2D Array', function() {
var context = window.createMockContext();
var font = "normal 12px 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif";
@ -664,46 +589,6 @@ describe('Core helper tests', function() {
expect(helpers.numberOfLabelLines(arrayOfThings3)).toEqual(3);
});
it('should draw a rounded rectangle', function() {
var context = window.createMockContext();
helpers.drawRoundedRectangle(context, 10, 20, 30, 40, 5);
expect(context.getCalls()).toEqual([{
name: 'beginPath',
args: []
}, {
name: 'moveTo',
args: [15, 20]
}, {
name: 'lineTo',
args: [35, 20]
}, {
name: 'quadraticCurveTo',
args: [40, 20, 40, 25]
}, {
name: 'lineTo',
args: [40, 55]
}, {
name: 'quadraticCurveTo',
args: [40, 60, 35, 60]
}, {
name: 'lineTo',
args: [15, 60]
}, {
name: 'quadraticCurveTo',
args: [10, 60, 10, 55]
}, {
name: 'lineTo',
args: [10, 25]
}, {
name: 'quadraticCurveTo',
args: [10, 20, 15, 20]
}, {
name: 'closePath',
args: []
}]);
});
it ('should get the maximum width and height for a node', function() {
// Create div with fixed size as a test bed
var div = document.createElement('div');

View File

@ -209,9 +209,9 @@ describe('Point element tests', function() {
}]);
var drawRoundedRectangleSpy = jasmine.createSpy('drawRoundedRectangle');
var drawRoundedRectangle = Chart.helpers.drawRoundedRectangle;
var drawRoundedRectangle = Chart.helpers.canvas.roundedRect;
var offset = point._view.radius / Math.SQRT2;
Chart.helpers.drawRoundedRectangle = drawRoundedRectangleSpy;
Chart.helpers.canvas.roundedRect = drawRoundedRectangleSpy;
mockContext.resetCalls();
point._view.pointStyle = 'rectRounded';
point.draw();
@ -231,7 +231,7 @@ describe('Point element tests', function() {
})
);
Chart.helpers.drawRoundedRectangle = drawRoundedRectangle;
Chart.helpers.canvas.roundedRect = drawRoundedRectangle;
mockContext.resetCalls();
point._view.pointStyle = 'rectRot';
point.draw();

View File

@ -1,8 +1,8 @@
describe('Deprecations', function() {
describe('Version 2.7.0', function() {
describe('Chart.Controller.update(duration, lazy)', function() {
beforeEach(function() {
this.chart = acquireChart({
it('should add an animation with the provided options', function() {
var chart = acquireChart({
type: 'doughnut',
options: {
animation: {
@ -12,14 +12,12 @@ describe('Deprecations', function() {
}
});
this.addAnimationSpy = spyOn(Chart.animationService, 'addAnimation');
});
spyOn(Chart.animationService, 'addAnimation');
it('should add an animation with the provided options', function() {
this.chart.update(800, false);
chart.update(800, false);
expect(this.addAnimationSpy).toHaveBeenCalledWith(
this.chart,
expect(Chart.animationService.addAnimation).toHaveBeenCalledWith(
chart,
jasmine.objectContaining({easing: 'linear'}),
800,
false
@ -28,8 +26,8 @@ describe('Deprecations', function() {
});
describe('Chart.Controller.render(duration, lazy)', function() {
beforeEach(function() {
this.chart = acquireChart({
it('should add an animation with the provided options', function() {
var chart = acquireChart({
type: 'doughnut',
options: {
animation: {
@ -39,20 +37,56 @@ describe('Deprecations', function() {
}
});
this.addAnimationSpy = spyOn(Chart.animationService, 'addAnimation');
});
spyOn(Chart.animationService, 'addAnimation');
it('should add an animation with the provided options', function() {
this.chart.render(800, true);
chart.render(800, true);
expect(this.addAnimationSpy).toHaveBeenCalledWith(
this.chart,
expect(Chart.animationService.addAnimation).toHaveBeenCalledWith(
chart,
jasmine.objectContaining({easing: 'linear'}),
800,
true
);
});
});
describe('Chart.helpers.indexOf', function() {
it('should be defined and a function', function() {
expect(Chart.helpers.indexOf).toBeDefined();
expect(typeof Chart.helpers.indexOf).toBe('function');
});
it('should returns the correct index', function() {
expect(Chart.helpers.indexOf([1, 2, 42], 42)).toBe(2);
expect(Chart.helpers.indexOf([1, 2, 42], 3)).toBe(-1);
expect(Chart.helpers.indexOf([1, 42, 2, 42], 42, 2)).toBe(3);
expect(Chart.helpers.indexOf([1, 42, 2, 42], 3, 2)).toBe(-1);
});
});
describe('Chart.helpers.clear', function() {
it('should be defined and an alias of Chart.helpers.canvas.clear', function() {
expect(Chart.helpers.clear).toBeDefined();
expect(Chart.helpers.clear).toBe(Chart.helpers.canvas.clear);
});
});
describe('Chart.helpers.drawRoundedRectangle', function() {
it('should be defined and a function', function() {
expect(Chart.helpers.drawRoundedRectangle).toBeDefined();
expect(typeof Chart.helpers.drawRoundedRectangle).toBe('function');
});
it('should call Chart.helpers.canvas.roundedRect', function() {
var ctx = window.createMockContext();
spyOn(Chart.helpers.canvas, 'roundedRect');
Chart.helpers.drawRoundedRectangle(ctx, 10, 20, 30, 40, 5);
var calls = ctx.getCalls();
expect(calls[0]).toEqual({name: 'beginPath', args: []});
expect(calls[calls.length-1]).toEqual({name: 'closePath', args: []});
expect(Chart.helpers.canvas.roundedRect).toHaveBeenCalledWith(ctx, 10, 20, 30, 40, 5);
});
});
});
describe('Version 2.6.0', function() {
@ -163,6 +197,13 @@ describe('Deprecations', function() {
});
});
});
describe('Chart.helpers.callCallback', function() {
it('should be defined and an alias of Chart.helpers.callback', function() {
expect(Chart.helpers.callCallback).toBeDefined();
expect(Chart.helpers.callCallback).toBe(Chart.helpers.callback);
});
});
});
describe('Version 2.5.0', function() {

View File

@ -0,0 +1,50 @@
'use strict';
describe('Chart.helpers.canvas', function() {
var helpers = Chart.helpers;
describe('clear', function() {
it('should clear the chart canvas', function() {
var chart = acquireChart({}, {
canvas: {
style: 'width: 150px; height: 245px'
}
});
spyOn(chart.ctx, 'clearRect');
helpers.canvas.clear(chart);
expect(chart.ctx.clearRect.calls.count()).toBe(1);
expect(chart.ctx.clearRect.calls.first().object).toBe(chart.ctx);
expect(chart.ctx.clearRect.calls.first().args).toEqual([0, 0, 150, 245]);
});
});
describe('roundedRect', function() {
it('should create a rounded rectangle path', function() {
var context = window.createMockContext();
helpers.canvas.roundedRect(context, 10, 20, 30, 40, 5);
expect(context.getCalls()).toEqual([
{name: 'moveTo', args: [15, 20]},
{name: 'lineTo', args: [35, 20]},
{name: 'quadraticCurveTo', args: [40, 20, 40, 25]},
{name: 'lineTo', args: [40, 55]},
{name: 'quadraticCurveTo', args: [40, 60, 35, 60]},
{name: 'lineTo', args: [15, 60]},
{name: 'quadraticCurveTo', args: [10, 60, 10, 55]},
{name: 'lineTo', args: [10, 25]},
{name: 'quadraticCurveTo', args: [10, 20, 15, 20]}
]);
});
it('should optimize path if radius is 0', function() {
var context = window.createMockContext();
helpers.canvas.roundedRect(context, 10, 20, 30, 40, 0);
expect(context.getCalls()).toEqual([{name: 'rect', args: [10, 20, 30, 40]}]);
});
});
});

View File

@ -0,0 +1,239 @@
'use strict';
describe('Chart.helpers.core', function() {
var helpers = Chart.helpers;
describe('noop', function() {
it('should be callable', function() {
expect(helpers.noop).toBeDefined();
expect(typeof helpers.noop).toBe('function');
expect(typeof helpers.noop.call).toBe('function');
});
it('should returns "undefined"', function() {
expect(helpers.noop(42)).not.toBeDefined();
expect(helpers.noop.call(this, 42)).not.toBeDefined();
});
});
describe('isArray', function() {
it('should return true if value is an array', function() {
expect(helpers.isArray([])).toBeTruthy();
expect(helpers.isArray([42])).toBeTruthy();
expect(helpers.isArray(new Array())).toBeTruthy();
expect(helpers.isArray(Array.prototype)).toBeTruthy();
});
it('should return false if value is not an array', function() {
expect(helpers.isArray()).toBeFalsy();
expect(helpers.isArray({})).toBeFalsy();
expect(helpers.isArray(undefined)).toBeFalsy();
expect(helpers.isArray(null)).toBeFalsy();
expect(helpers.isArray(true)).toBeFalsy();
expect(helpers.isArray(false)).toBeFalsy();
expect(helpers.isArray(42)).toBeFalsy();
expect(helpers.isArray('Array')).toBeFalsy();
expect(helpers.isArray({__proto__: Array.prototype})).toBeFalsy();
});
});
describe('isObject', function() {
it('should return true if value is an object', function() {
expect(helpers.isObject({})).toBeTruthy();
expect(helpers.isObject({a: 42})).toBeTruthy();
expect(helpers.isObject(new Object())).toBeTruthy();
});
it('should return false if value is not an object', function() {
expect(helpers.isObject()).toBeFalsy();
expect(helpers.isObject(undefined)).toBeFalsy();
expect(helpers.isObject(null)).toBeFalsy();
expect(helpers.isObject(true)).toBeFalsy();
expect(helpers.isObject(false)).toBeFalsy();
expect(helpers.isObject(42)).toBeFalsy();
expect(helpers.isObject('Object')).toBeFalsy();
expect(helpers.isObject([])).toBeFalsy();
expect(helpers.isObject([42])).toBeFalsy();
expect(helpers.isObject(new Array())).toBeFalsy();
expect(helpers.isObject(new Date())).toBeFalsy();
});
});
describe('isNullOrUndef', function() {
it('should return true if value is null/undefined', function() {
expect(helpers.isNullOrUndef(null)).toBeTruthy();
expect(helpers.isNullOrUndef(undefined)).toBeTruthy();
});
it('should return false if value is not null/undefined', function() {
expect(helpers.isNullOrUndef(true)).toBeFalsy();
expect(helpers.isNullOrUndef(false)).toBeFalsy();
expect(helpers.isNullOrUndef('')).toBeFalsy();
expect(helpers.isNullOrUndef('String')).toBeFalsy();
expect(helpers.isNullOrUndef(0)).toBeFalsy();
expect(helpers.isNullOrUndef([])).toBeFalsy();
expect(helpers.isNullOrUndef({})).toBeFalsy();
expect(helpers.isNullOrUndef([42])).toBeFalsy();
expect(helpers.isNullOrUndef(new Date())).toBeFalsy();
});
});
describe('getValueOrDefault', function() {
it('should return value if defined', function() {
var object = {};
var array = [];
expect(helpers.getValueOrDefault(null, 42)).toBe(null);
expect(helpers.getValueOrDefault(false, 42)).toBe(false);
expect(helpers.getValueOrDefault(object, 42)).toBe(object);
expect(helpers.getValueOrDefault(array, 42)).toBe(array);
expect(helpers.getValueOrDefault('', 42)).toBe('');
expect(helpers.getValueOrDefault(0, 42)).toBe(0);
});
it('should return default if undefined', function() {
expect(helpers.getValueOrDefault(undefined, 42)).toBe(42);
expect(helpers.getValueOrDefault({}.foo, 42)).toBe(42);
});
});
describe('getValueAtIndexOrDefault', function() {
it('should return the passed value if not an array', function() {
expect(helpers.getValueAtIndexOrDefault(0, 0, 42)).toBe(0);
expect(helpers.getValueAtIndexOrDefault('', 0, 42)).toBe('');
expect(helpers.getValueAtIndexOrDefault(false, 0, 42)).toBe(false);
expect(helpers.getValueAtIndexOrDefault(98, 0, 42)).toBe(98);
});
it('should return the default value if the passed value is null or undefined', function() {
expect(helpers.getValueAtIndexOrDefault(null, 0, 42)).toBe(42);
expect(helpers.getValueAtIndexOrDefault(undefined, 0, 42)).toBe(42);
});
it('should return the value at index if defined', function() {
expect(helpers.getValueAtIndexOrDefault([1, false, 'foo'], 1, 42)).toBe(false);
expect(helpers.getValueAtIndexOrDefault([1, false, 'foo'], 2, 42)).toBe('foo');
});
it('should return the default value if value at index is undefined', function() {
expect(helpers.getValueAtIndexOrDefault([1, false, 'foo'], 3, 42)).toBe(42);
expect(helpers.getValueAtIndexOrDefault([1, undefined, 'foo'], 1, 42)).toBe(42);
});
});
describe('callback', function() {
it('should return undefined if fn is not a function', function() {
expect(helpers.callback()).not.toBeDefined();
expect(helpers.callback(null)).not.toBeDefined();
expect(helpers.callback(42)).not.toBeDefined();
expect(helpers.callback([])).not.toBeDefined();
expect(helpers.callback({})).not.toBeDefined();
});
it('should call fn with the given args', function() {
var spy = jasmine.createSpy('spy');
helpers.callback(spy);
helpers.callback(spy, []);
helpers.callback(spy, ['foo']);
helpers.callback(spy, [42, 'bar']);
expect(spy.calls.argsFor(0)).toEqual([]);
expect(spy.calls.argsFor(1)).toEqual([]);
expect(spy.calls.argsFor(2)).toEqual(['foo']);
expect(spy.calls.argsFor(3)).toEqual([42, 'bar']);
});
it('should call fn with the given scope', function() {
var spy = jasmine.createSpy('spy');
var scope = {};
helpers.callback(spy);
helpers.callback(spy, [], null);
helpers.callback(spy, [], undefined);
helpers.callback(spy, [], scope);
expect(spy.calls.all()[0].object).toBe(window);
expect(spy.calls.all()[1].object).toBe(window);
expect(spy.calls.all()[2].object).toBe(window);
expect(spy.calls.all()[3].object).toBe(scope);
});
it('should return the value returned by fn', function() {
expect(helpers.callback(helpers.noop, [41])).toBe(undefined);
expect(helpers.callback(function(i) {
return i+1;
}, [41])).toBe(42);
});
});
describe('each', function() {
it('should iterate over an array forward if reverse === false', function() {
var scope = {};
var scopes = [];
var items = [];
var keys = [];
helpers.each(['foo', 'bar', 42], function(item, key) {
scopes.push(this);
items.push(item);
keys.push(key);
}, scope);
expect(scopes).toEqual([scope, scope, scope]);
expect(items).toEqual(['foo', 'bar', 42]);
expect(keys).toEqual([0, 1, 2]);
});
it('should iterate over an array backward if reverse === true', function() {
var scope = {};
var scopes = [];
var items = [];
var keys = [];
helpers.each(['foo', 'bar', 42], function(item, key) {
scopes.push(this);
items.push(item);
keys.push(key);
}, scope, true);
expect(scopes).toEqual([scope, scope, scope]);
expect(items).toEqual([42, 'bar', 'foo']);
expect(keys).toEqual([2, 1, 0]);
});
it('should iterate over object properties', function() {
var scope = {};
var scopes = [];
var items = [];
helpers.each({a: 'foo', b: 'bar', c: 42}, function(item, key) {
scopes.push(this);
items[key] = item;
}, scope);
expect(scopes).toEqual([scope, scope, scope]);
expect(items).toEqual(jasmine.objectContaining({a: 'foo', b: 'bar', c: 42}));
});
it('should not throw when called with a non iterable object', function() {
expect(function() {
helpers.each(undefined);
}).not.toThrow();
expect(function() {
helpers.each(null);
}).not.toThrow();
expect(function() {
helpers.each(42);
}).not.toThrow();
});
});
describe('arrayEquals', function() {
it('should return false if arrays are not the same', function() {
expect(helpers.arrayEquals([], [42])).toBeFalsy();
expect(helpers.arrayEquals([42], ['42'])).toBeFalsy();
expect(helpers.arrayEquals([1, 2, 3], [1, 2, 3, 4])).toBeFalsy();
expect(helpers.arrayEquals(['foo', 'bar'], ['bar', 'foo'])).toBeFalsy();
expect(helpers.arrayEquals([1, 2, 3], [1, 2, 'foo'])).toBeFalsy();
expect(helpers.arrayEquals([1, 2, [3, 4]], [1, 2, [3, 'foo']])).toBeFalsy();
expect(helpers.arrayEquals([{a: 42}], [{a: 42}])).toBeFalsy();
});
it('should return false if arrays are not the same', function() {
var o0 = {};
var o1 = {};
var o2 = {};
expect(helpers.arrayEquals([], [])).toBeTruthy();
expect(helpers.arrayEquals([1, 2, 3], [1, 2, 3])).toBeTruthy();
expect(helpers.arrayEquals(['foo', 'bar'], ['foo', 'bar'])).toBeTruthy();
expect(helpers.arrayEquals([true, false, true], [true, false, true])).toBeTruthy();
expect(helpers.arrayEquals([o0, o1, o2], [o0, o1, o2])).toBeTruthy();
});
});
});