diff --git a/src/elements/element.point.js b/src/elements/element.point.js index 1a4f709cb..94565e3ca 100644 --- a/src/elements/element.point.js +++ b/src/elements/element.point.js @@ -32,8 +32,13 @@ Chart.elements.Point = Chart.Element.extend({ inRange: function(mouseX, mouseY) { var vm = this._view; - var hoverRange = vm.hitRadius + vm.radius; - return ((Math.pow(mouseX - vm.x, 2) + Math.pow(mouseY - vm.y, 2)) < Math.pow(hoverRange, 2)); + + if (vm) { + var hoverRange = vm.hitRadius + vm.radius; + return ((Math.pow(mouseX - vm.x, 2) + Math.pow(mouseY - vm.y, 2)) < Math.pow(hoverRange, 2)); + } else { + return false; + } }, inLabelRange: function(mouseX) { var vm = this._view; diff --git a/test/element.point.tests.js b/test/element.point.tests.js new file mode 100644 index 000000000..e20964422 --- /dev/null +++ b/test/element.point.tests.js @@ -0,0 +1,180 @@ +// Test the point element + +describe('Point element tests', function() { + it ('Should be constructed', function() { + var point = new Chart.elements.Point({ + _datasetIndex: 2, + _index: 1 + }); + + expect(point).not.toBe(undefined); + expect(point._datasetIndex).toBe(2); + expect(point._index).toBe(1); + }); + + it ('Should correctly identify as in range', function() { + var point = new Chart.elements.Point({ + _datasetIndex: 2, + _index: 1 + }); + + // Safely handles if these are called before the viewmodel is instantiated + expect(point.inRange(5)).toBe(false); + expect(point.inLabelRange(5)).toBe(false); + + // Attach a view object as if we were the controller + point._view = { + radius: 2, + hitRadius: 3, + x: 10, + y: 15 + }; + + expect(point.inRange(10, 15)).toBe(true); + expect(point.inRange(10, 10)).toBe(false); + expect(point.inRange(10, 5)).toBe(false); + expect(point.inRange(5, 5)).toBe(false); + + expect(point.inLabelRange(5)).toBe(false); + expect(point.inLabelRange(7)).toBe(true); + expect(point.inLabelRange(10)).toBe(true); + expect(point.inLabelRange(12)).toBe(true); + expect(point.inLabelRange(15)).toBe(false); + expect(point.inLabelRange(20)).toBe(false); + }); + + it ('should get the correct tooltip position', function() { + var point = new Chart.elements.Point({ + _datasetIndex: 2, + _index: 1 + }); + + // Attach a view object as if we were the controller + point._view = { + radius: 2, + borderWidth: 6, + x: 10, + y: 15 + }; + + expect(point.tooltipPosition()).toEqual({ + x: 10, + y: 15, + padding: 8 + }); + }); + + it ('should draw correctly', function() { + var mockContext = window.createMockContext(); + var point = new Chart.elements.Point({ + _datasetIndex: 2, + _index: 1, + _chart: { + ctx: mockContext, + } + }); + + // Attach a view object as if we were the controller + point._view = { + radius: 2, + hitRadius: 3, + borderColor: 'rgba(1, 2, 3, 1)', + borderWidth: 6, + backgroundColor: 'rgba(0, 255, 0)', + x: 10, + y: 15, + ctx: mockContext + }; + + point.draw(); + + expect(mockContext.getCalls()).toEqual([{ + name: 'beginPath', + args: [] + }, { + name: 'arc', + args: [10, 15, 2, 0, 2 * Math.PI] + }, { + name: 'closePath', + args: [], + }, { + name: 'fill', + args: [], + }, { + name: 'stroke', + args: [] + }]); + + expect(mockContext.lineWidth).toBe(6); + expect(mockContext.strokeStyle).toBe('rgba(1, 2, 3, 1)'); + expect(mockContext.fillStyle).toBe('rgba(0, 255, 0)'); + }); + + it ('should draw correctly with default settings if necessary', function() { + var mockContext = window.createMockContext(); + var point = new Chart.elements.Point({ + _datasetIndex: 2, + _index: 1, + _chart: { + ctx: mockContext, + } + }); + + // Attach a view object as if we were the controller + point._view = { + radius: 2, + hitRadius: 3, + x: 10, + y: 15, + ctx: mockContext + }; + + point.draw(); + + expect(mockContext.getCalls()).toEqual([{ + name: 'beginPath', + args: [] + }, { + name: 'arc', + args: [10, 15, 2, 0, 2 * Math.PI] + }, { + name: 'closePath', + args: [], + }, { + name: 'fill', + args: [], + }, { + name: 'stroke', + args: [] + }]); + + expect(mockContext.lineWidth).toBe(1); + expect(mockContext.strokeStyle).toBe('rgba(0,0,0,0.1)'); + expect(mockContext.fillStyle).toBe('rgba(0,0,0,0.1)'); + }); + + it ('should not draw if skipped', function() { + var mockContext = window.createMockContext(); + var point = new Chart.elements.Point({ + _datasetIndex: 2, + _index: 1, + _chart: { + ctx: mockContext, + } + }); + + // Attach a view object as if we were the controller + point._view = { + radius: 2, + hitRadius: 3, + x: 10, + y: 15, + ctx: mockContext, + skip: true + }; + + point.draw(); + + expect(mockContext.getCalls()).toEqual([]); + }); +}); \ No newline at end of file diff --git a/test/mockContext.js b/test/mockContext.js new file mode 100644 index 000000000..ee3ce7ea4 --- /dev/null +++ b/test/mockContext.js @@ -0,0 +1,51 @@ +(function() { + // Code from http://stackoverflow.com/questions/4406864/html-canvas-unit-testing + var Context = function() { + this._calls = []; // names/args of recorded calls + this._initMethods(); + }; + + Context.prototype._initMethods = function() { + // define methods to test here + // no way to introspect so we have to do some extra work :( + var methods = { + arc: function() {}, + beginPath: function() {}, + closePath: function() {}, + fill: function() {}, + lineTo: function(x, y) {}, + moveTo: function(x, y) {}, + stroke: function() {} + }; + + // attach methods to the class itself + var scope = this; + var addMethod = function(name, method) { + scope[methodName] = function() { + scope.record(name, arguments); + method.apply(scope, arguments); + }; + } + + for (var methodName in methods) { + var method = methods[methodName]; + + addMethod(methodName, method); + } + }; + + Context.prototype.record = function(methodName, args) { + this._calls.push({ + name: methodName, + args: Array.prototype.slice.call(args) + }); + }, + + Context.prototype.getCalls = function() { + return this._calls; + } + + window.createMockContext = function() { + return new Context(); + }; +})(); \ No newline at end of file