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 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/ diff --git a/src/elements/element.arc.js b/src/elements/element.arc.js index e09f0cf57..209c505d9 100644 --- a/src/elements/element.arc.js +++ b/src/elements/element.arc.js @@ -37,21 +37,24 @@ var vm = this._view; - var pointRelativePosition = helpers.getAngleFromPoint(vm, { - x: chartX, - y: chartY - }); + if (vm) { + var pointRelativePosition = helpers.getAngleFromPoint(vm, { + x: chartX, + y: chartY + }); - // Put into the range of (-PI/2, 3PI/2] - var startAngle = vm.startAngle < (-0.5 * Math.PI) ? vm.startAngle + (2.0 * Math.PI) : vm.startAngle > (1.5 * Math.PI) ? vm.startAngle - (2.0 * Math.PI) : vm.startAngle; - var endAngle = vm.endAngle < (-0.5 * Math.PI) ? vm.endAngle + (2.0 * Math.PI) : vm.endAngle > (1.5 * Math.PI) ? vm.endAngle - (2.0 * Math.PI) : vm.endAngle + // Put into the range of (-PI/2, 3PI/2] + var startAngle = vm.startAngle < (-0.5 * Math.PI) ? vm.startAngle + (2.0 * Math.PI) : vm.startAngle > (1.5 * Math.PI) ? vm.startAngle - (2.0 * Math.PI) : vm.startAngle; + var endAngle = vm.endAngle < (-0.5 * Math.PI) ? vm.endAngle + (2.0 * Math.PI) : vm.endAngle > (1.5 * Math.PI) ? vm.endAngle - (2.0 * Math.PI) : vm.endAngle - //Check if within the range of the open/close angle - var betweenAngles = (pointRelativePosition.angle >= startAngle && pointRelativePosition.angle <= endAngle), - withinRadius = (pointRelativePosition.distance >= vm.innerRadius && pointRelativePosition.distance <= vm.outerRadius); + //Check if within the range of the open/close angle + var betweenAngles = (pointRelativePosition.angle >= startAngle && pointRelativePosition.angle <= endAngle), + withinRadius = (pointRelativePosition.distance >= vm.innerRadius && pointRelativePosition.distance <= vm.outerRadius); - return (betweenAngles && withinRadius); - //Ensure within the outside of the arc centre, but inside arc outer + return (betweenAngles && withinRadius); + } else { + return false; + } }, tooltipPosition: function() { var vm = this._view; diff --git a/src/elements/element.line.js b/src/elements/element.line.js index 62c316c95..88c213699 100644 --- a/src/elements/element.line.js +++ b/src/elements/element.line.js @@ -114,7 +114,7 @@ // Now draw the line between all the points with any borders ctx.lineCap = vm.borderCapStyle || Chart.defaults.global.elements.line.borderCapStyle; - + // IE 9 and 10 do not support line dash if (ctx.setLineDash) { ctx.setLineDash(vm.borderDash || Chart.defaults.global.elements.line.borderDash); @@ -122,7 +122,7 @@ ctx.lineDashOffset = vm.borderDashOffset || Chart.defaults.global.elements.line.borderDashOffset; ctx.lineJoin = vm.borderJoinStyle || Chart.defaults.global.elements.line.borderJoinStyle; - ctx.lineWidth = vm.borderWidth || Chart.defaults.global.defaultColor; + ctx.lineWidth = vm.borderWidth || Chart.defaults.global.elements.line.borderWidth; ctx.strokeStyle = vm.borderColor || Chart.defaults.global.defaultColor; ctx.beginPath(); @@ -184,10 +184,9 @@ } } - ctx.stroke(); ctx.restore(); }, }); -}).call(this); +}).call(this); \ No newline at end of file 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/src/elements/element.rectangle.js b/src/elements/element.rectangle.js index 10682b1c0..3d9539eb9 100644 --- a/src/elements/element.rectangle.js +++ b/src/elements/element.rectangle.js @@ -55,15 +55,26 @@ }, inRange: function(mouseX, mouseY) { var vm = this._view; - if (vm.y < vm.base) { - return (mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2) && (mouseY >= vm.y && mouseY <= vm.base); - } else { - return (mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2) && (mouseY >= vm.base && mouseY <= vm.y); - } + var inRange = false; + + if (vm) { + if (vm.y < vm.base) { + inRange = (mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2) && (mouseY >= vm.y && mouseY <= vm.base); + } else { + inRange = (mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2) && (mouseY >= vm.base && mouseY <= vm.y); + } + } + + return inRange; }, inLabelRange: function(mouseX) { var vm = this._view; - return (mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2); + + if (vm) { + return (mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2); + } else { + return false; + } }, tooltipPosition: function() { var vm = this._view; 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 diff --git a/test/element.arc.tests.js b/test/element.arc.tests.js new file mode 100644 index 000000000..4ba854575 --- /dev/null +++ b/test/element.arc.tests.js @@ -0,0 +1,176 @@ +// Test the rectangle element + +describe('Arc element tests', function() { + it ('Should be constructed', function() { + var arc = new Chart.elements.Arc({ + _datasetIndex: 2, + _index: 1 + }); + + expect(arc).not.toBe(undefined); + expect(arc._datasetIndex).toBe(2); + expect(arc._index).toBe(1); + }); + + it ('should determine if in range', function() { + var arc = new Chart.elements.Arc({ + _datasetIndex: 2, + _index: 1 + }); + + // Make sure we can run these before the view is added + expect(arc.inRange(2, 2)).toBe(false); + expect(arc.inLabelRange(2)).toBe(false); + + // Mock out the view as if the controller put it there + arc._view = { + startAngle: 0, + endAngle: Math.PI / 2, + x: 0, + y: 0, + innerRadius: 5, + outerRadius: 10, + }; + + expect(arc.inRange(2, 2)).toBe(false); + expect(arc.inRange(7, 0)).toBe(true); + expect(arc.inRange(0, 11)).toBe(false); + expect(arc.inRange(Math.sqrt(32), Math.sqrt(32))).toBe(true); + expect(arc.inRange(-1.0 * Math.sqrt(7), Math.sqrt(7))).toBe(false); + }); + + it ('should get the tooltip position', function() { + var arc = new Chart.elements.Arc({ + _datasetIndex: 2, + _index: 1 + }); + + // Mock out the view as if the controller put it there + arc._view = { + startAngle: 0, + endAngle: Math.PI / 2, + x: 0, + y: 0, + innerRadius: 0, + outerRadius: Math.sqrt(2), + }; + + var pos = arc.tooltipPosition(); + expect(pos.x).toBeCloseTo(0.5); + expect(pos.y).toBeCloseTo(0.5); + }); + + it ('should draw correctly with no border', function() { + var mockContext = window.createMockContext(); + var arc = new Chart.elements.Arc({ + _datasetIndex: 2, + _index: 1, + _chart: { + ctx: mockContext, + } + }); + + // Mock out the view as if the controller put it there + arc._view = { + startAngle: 0, + endAngle: Math.PI / 2, + x: 10, + y: 5, + innerRadius: 1, + outerRadius: 3, + + backgroundColor: 'rgb(0, 0, 255)', + borderColor: 'rgb(255, 0, 0)', + }; + + arc.draw(); + + expect(mockContext.getCalls()).toEqual([{ + name: 'beginPath', + args: [] + }, { + name: 'arc', + args: [10, 5, 3, 0, Math.PI / 2] + }, { + name: 'arc', + args: [10, 5, 1, Math.PI / 2, 0, true] + }, { + name: 'closePath', + args: [] + }, { + name: 'setStrokeStyle', + args: ['rgb(255, 0, 0)'] + }, { + name: 'setLineWidth', + args: [undefined] + }, { + name: 'setFillStyle', + args: ['rgb(0, 0, 255)'] + }, { + name: 'fill', + args: [] + }, { + name: 'setLineJoin', + args: ['bevel'] + }]); + }); + + it ('should draw correctly with a border', function() { + var mockContext = window.createMockContext(); + var arc = new Chart.elements.Arc({ + _datasetIndex: 2, + _index: 1, + _chart: { + ctx: mockContext, + } + }); + + // Mock out the view as if the controller put it there + arc._view = { + startAngle: 0, + endAngle: Math.PI / 2, + x: 10, + y: 5, + innerRadius: 1, + outerRadius: 3, + + backgroundColor: 'rgb(0, 0, 255)', + borderColor: 'rgb(255, 0, 0)', + borderWidth: 5 + }; + + arc.draw(); + + expect(mockContext.getCalls()).toEqual([{ + name: 'beginPath', + args: [] + }, { + name: 'arc', + args: [10, 5, 3, 0, Math.PI / 2] + }, { + name: 'arc', + args: [10, 5, 1, Math.PI / 2, 0, true] + }, { + name: 'closePath', + args: [] + }, { + name: 'setStrokeStyle', + args: ['rgb(255, 0, 0)'] + }, { + name: 'setLineWidth', + args: [5] + }, { + name: 'setFillStyle', + args: ['rgb(0, 0, 255)'] + }, { + name: 'fill', + args: [] + }, { + name: 'setLineJoin', + args: ['bevel'] + }, { + name: 'stroke', + args: [] + }]); + }); +}); \ No newline at end of file diff --git a/test/element.line.tests.js b/test/element.line.tests.js new file mode 100644 index 000000000..5e41d7ff2 --- /dev/null +++ b/test/element.line.tests.js @@ -0,0 +1,526 @@ +// Tests for the line element +describe('Line element tests', function() { + it ('should be constructed', function() { + var line = new Chart.elements.Line({ + _datasetindex: 2, + _points: [1, 2, 3, 4] + }); + + expect(line).not.toBe(undefined); + expect(line._datasetindex).toBe(2); + expect(line._points).toEqual([1, 2, 3, 4]); + }); + + it ('should draw with default settings', function() { + var mockContext = window.createMockContext(); + + // Create our points + var points = []; + points.push(new Chart.elements.Point({ + _datasetindex: 2, + _index: 0, + _view: { + x: 0, + y: 10 + } + })); + points.push(new Chart.elements.Point({ + _datasetindex: 2, + _index: 1, + _view: { + x: 5, + y: 0 + } + })); + points.push(new Chart.elements.Point({ + _datasetindex: 2, + _index: 2, + _view: { + x: 15, + y: -10 + } + })); + points.push(new Chart.elements.Point({ + _datasetindex: 2, + _index: 3, + _view: { + x: 19, + y: -5 + } + })); + + var line = new Chart.elements.Line({ + _datasetindex: 2, + _chart: { + ctx: mockContext, + }, + _children: points, + // Need to provide some settings + _view: { + fill: false, // don't want to fill + tension: 0.0, // no bezier curve for now + } + }) + + line.draw(); + + expect(mockContext.getCalls()).toEqual([{ + name: 'save', + args: [], + }, { + name: 'moveTo', + args: [0, 10] + }, { + name: 'lineTo', + args: [5, 0] + }, { + name: 'lineTo', + args: [15, -10] + }, { + name: 'lineTo', + args: [19, -5] + }, { + name: 'setLineCap', + args: ['butt'] + }, { + name: 'setLineDash', + args: [[]] + }, { + name: 'setLineDashOffset', + args: [0.0] + }, { + name: 'setLineJoin', + args: ['miter'] + }, { + name: 'setLineWidth', + args: [3] + }, { + name: 'setStrokeStyle', + args: ['rgba(0,0,0,0.1)'] + }, { + name: 'beginPath', + args: [] + }, { + name: 'moveTo', + args: [0, 10] + }, { + name: 'lineTo', + args: [5, 0] + }, { + name: 'lineTo', + args: [15, -10] + }, { + name: 'lineTo', + args: [19, -5] + }, { + name: 'stroke', + args: [], + }, { + name: 'restore', + args: [] + }]) + }); + + it ('should draw with custom settings', function() { + var mockContext = window.createMockContext(); + + // Create our points + var points = []; + points.push(new Chart.elements.Point({ + _datasetindex: 2, + _index: 0, + _view: { + x: 0, + y: 10 + } + })); + points.push(new Chart.elements.Point({ + _datasetindex: 2, + _index: 1, + _view: { + x: 5, + y: 0 + } + })); + points.push(new Chart.elements.Point({ + _datasetindex: 2, + _index: 2, + _view: { + x: 15, + y: -10 + } + })); + points.push(new Chart.elements.Point({ + _datasetindex: 2, + _index: 3, + _view: { + x: 19, + y: -5 + } + })); + + var line = new Chart.elements.Line({ + _datasetindex: 2, + _chart: { + ctx: mockContext, + }, + _children: points, + // Need to provide some settings + _view: { + fill: true, + scaleZero: 2, // for filling lines + tension: 0.0, // no bezier curve for now + + borderCapStyle: 'round', + borderColor: 'rgb(255, 255, 0)', + borderDash: [2, 2], + borderDashOffset: 1.5, + borderJoinStyle: 'bevel', + borderWidth: 4, + backgroundColor: 'rgb(0, 0, 0)' + } + }) + + line.draw(); + + var expected = [{ + name: 'save', + args: [], + }, { + name: 'moveTo', + args: [0, 10] + }, { + name: 'lineTo', + args: [5, 0] + }, { + name: 'lineTo', + args: [15, -10] + }, { + name: 'lineTo', + args: [19, -5] + }, { + name: 'lineTo', + args: [19, 2] + }, { + name: 'lineTo', + args: [0, 2] + }, { + name: 'setFillStyle', + args: ['rgb(0, 0, 0)'] + }, { + name: 'closePath', + args: [] + }, { + name: 'fill', + args: [] + }, { + name: 'setLineCap', + args: ['round'] + }, { + name: 'setLineDash', + args: [[2, 2]] + }, { + name: 'setLineDashOffset', + args: [1.5] + }, { + name: 'setLineJoin', + args: ['bevel'] + }, { + name: 'setLineWidth', + args: [4] + }, { + name: 'setStrokeStyle', + args: ['rgb(255, 255, 0)'] + }, { + name: 'beginPath', + args: [] + }, { + name: 'moveTo', + args: [0, 10] + }, { + name: 'lineTo', + args: [5, 0] + }, { + name: 'lineTo', + args: [15, -10] + }, { + name: 'lineTo', + args: [19, -5] + }, { + name: 'stroke', + args: [], + }, { + name: 'restore', + args: [] + }]; + expect(mockContext.getCalls()).toEqual(expected) + }); + + it ('should be able to draw with a loop back to the beginning point', function() { + var mockContext = window.createMockContext(); + + // Create our points + var points = []; + points.push(new Chart.elements.Point({ + _datasetindex: 2, + _index: 0, + _view: { + x: 0, + y: 10 + } + })); + points.push(new Chart.elements.Point({ + _datasetindex: 2, + _index: 1, + _view: { + x: 5, + y: 0 + } + })); + points.push(new Chart.elements.Point({ + _datasetindex: 2, + _index: 2, + _view: { + x: 15, + y: -10 + } + })); + points.push(new Chart.elements.Point({ + _datasetindex: 2, + _index: 3, + _view: { + x: 19, + y: -5 + } + })); + + var line = new Chart.elements.Line({ + _datasetindex: 2, + _chart: { + ctx: mockContext, + }, + _children: points, + _loop: true, // want the line to loop back to the first point + // Need to provide some settings + _view: { + fill: false, // don't want to fill + tension: 0.0, // no bezier curve for now + } + }) + + line.draw(); + + expect(mockContext.getCalls()).toEqual([{ + name: 'save', + args: [], + }, { + name: 'moveTo', + args: [0, 10] + }, { + name: 'lineTo', + args: [5, 0] + }, { + name: 'lineTo', + args: [15, -10] + }, { + name: 'lineTo', + args: [19, -5] + }, { + name: 'lineTo', + args: [0, 10] + }, { + name: 'setLineCap', + args: ['butt'] + }, { + name: 'setLineDash', + args: [[]] + }, { + name: 'setLineDashOffset', + args: [0.0] + }, { + name: 'setLineJoin', + args: ['miter'] + }, { + name: 'setLineWidth', + args: [3] + }, { + name: 'setStrokeStyle', + args: ['rgba(0,0,0,0.1)'] + }, { + name: 'beginPath', + args: [] + }, { + name: 'moveTo', + args: [0, 10] + }, { + name: 'lineTo', + args: [5, 0] + }, { + name: 'lineTo', + args: [15, -10] + }, { + name: 'lineTo', + args: [19, -5] + }, { + name: 'lineTo', + args: [0, 10] + }, { + name: 'stroke', + args: [], + }, { + name: 'restore', + args: [] + }]) + }); + + it ('should draw with bezier curves if tension > 0', function() { + var mockContext = window.createMockContext(); + + // Create our points + var points = []; + points.push(new Chart.elements.Point({ + _datasetindex: 2, + _index: 0, + _view: { + x: 0, + y: 10, + controlPointNextX: 1, + controlPointNextY: 1, + controlPointPreviousX: 0, + controlPointPreviousY: 0, + } + })); + points.push(new Chart.elements.Point({ + _datasetindex: 2, + _index: 1, + _view: { + x: 5, + y: 0, + controlPointNextX: 6, + controlPointNextY: 7, + controlPointPreviousX: 4, + controlPointPreviousY: 3, + } + })); + points.push(new Chart.elements.Point({ + _datasetindex: 2, + _index: 2, + _view: { + x: 15, + y: -10, + controlPointNextX: 16, + controlPointNextY: 17, + controlPointPreviousX: 14, + controlPointPreviousY: 13, + } + })); + points.push(new Chart.elements.Point({ + _datasetindex: 2, + _index: 3, + _view: { + x: 19, + y: -5, + controlPointNextX: 20, + controlPointNextY: 21, + controlPointPreviousX: 18, + controlPointPreviousY: 17, + } + })); + + var line = new Chart.elements.Line({ + _datasetindex: 2, + _chart: { + ctx: mockContext, + }, + _children: points, + // Need to provide some settings + _view: { + fill: true, + scaleZero: 2, // for filling lines + tension: 0.5, // have bezier curves + + borderCapStyle: 'round', + borderColor: 'rgb(255, 255, 0)', + borderDash: [2, 2], + borderDashOffset: 1.5, + borderJoinStyle: 'bevel', + borderWidth: 4, + backgroundColor: 'rgb(0, 0, 0)' + } + }) + + line.draw(); + + var expected = [{ + name: 'save', + args: [], + }, { + name: 'moveTo', + args: [0, 10] + }, { + name: 'bezierCurveTo', + args: [1, 1, 4, 3, 5, 0] + }, { + name: 'bezierCurveTo', + args: [6, 7, 14, 13, 15, -10] + }, { + name: 'bezierCurveTo', + args: [16, 17, 18, 17, 19, -5] + }, { + name: 'lineTo', + args: [19, 2] + }, { + name: 'lineTo', + args: [0, 2] + }, { + name: 'setFillStyle', + args: ['rgb(0, 0, 0)'] + }, { + name: 'closePath', + args: [] + }, { + name: 'fill', + args: [] + }, { + name: 'setLineCap', + args: ['round'] + }, { + name: 'setLineDash', + args: [[2, 2]] + }, { + name: 'setLineDashOffset', + args: [1.5] + }, { + name: 'setLineJoin', + args: ['bevel'] + }, { + name: 'setLineWidth', + args: [4] + }, { + name: 'setStrokeStyle', + args: ['rgb(255, 255, 0)'] + }, { + name: 'beginPath', + args: [] + }, { + name: 'moveTo', + args: [0, 10] + }, { + name: 'bezierCurveTo', + args: [1, 1, 4, 3, 5, 0] + }, { + name: 'bezierCurveTo', + args: [6, 7, 14, 13, 15, -10] + }, { + name: 'bezierCurveTo', + args: [16, 17, 18, 17, 19, -5] + }, { + name: 'stroke', + args: [], + }, { + name: 'restore', + args: [] + }]; + expect(mockContext.getCalls()).toEqual(expected) + }); +}); \ No newline at end of file diff --git a/test/element.point.tests.js b/test/element.point.tests.js new file mode 100644 index 000000000..8a4052ca7 --- /dev/null +++ b/test/element.point.tests.js @@ -0,0 +1,190 @@ +// 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: 'setStrokeStyle', + args: ['rgba(1, 2, 3, 1)'] + }, { + name: 'setLineWidth', + args: [6] + }, { + name: 'setFillStyle', + args: ['rgba(0, 255, 0)'] + }, { + name: 'fill', + args: [], + }, { + name: 'stroke', + args: [] + }]); + }); + + 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: 'setStrokeStyle', + args: ['rgba(0,0,0,0.1)'] + }, { + name: 'setLineWidth', + args: [1] + }, { + name: 'setFillStyle', + args: ['rgba(0,0,0,0.1)'] + }, { + name: 'fill', + args: [], + }, { + name: 'stroke', + args: [] + }]); + }); + + 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/element.rectangle.tests.js b/test/element.rectangle.tests.js new file mode 100644 index 000000000..f8bd51efd --- /dev/null +++ b/test/element.rectangle.tests.js @@ -0,0 +1,246 @@ +// Test the rectangle element + +describe('Rectangle element tests', function() { + it ('Should be constructed', function() { + var rectangle = new Chart.elements.Rectangle({ + _datasetIndex: 2, + _index: 1 + }); + + expect(rectangle).not.toBe(undefined); + expect(rectangle._datasetIndex).toBe(2); + expect(rectangle._index).toBe(1); + }); + + it ('Should correctly identify as in range', function() { + var rectangle = new Chart.elements.Rectangle({ + _datasetIndex: 2, + _index: 1 + }); + + // Safely handles if these are called before the viewmodel is instantiated + expect(rectangle.inRange(5)).toBe(false); + expect(rectangle.inLabelRange(5)).toBe(false); + + // Attach a view object as if we were the controller + rectangle._view = { + base: 0, + width: 4, + x: 10, + y: 15 + }; + + expect(rectangle.inRange(10, 15)).toBe(true); + expect(rectangle.inRange(10, 10)).toBe(true); + expect(rectangle.inRange(10, 16)).toBe(false); + expect(rectangle.inRange(5, 5)).toBe(false); + + expect(rectangle.inLabelRange(5)).toBe(false); + expect(rectangle.inLabelRange(7)).toBe(false); + expect(rectangle.inLabelRange(10)).toBe(true); + expect(rectangle.inLabelRange(12)).toBe(true); + expect(rectangle.inLabelRange(15)).toBe(false); + expect(rectangle.inLabelRange(20)).toBe(false); + + // Test when the y is below the base (negative bar) + var negativeRectangle = new Chart.elements.Rectangle({ + _datasetIndex: 2, + _index: 1 + }); + + // Attach a view object as if we were the controller + negativeRectangle._view = { + base: 0, + width: 4, + x: 10, + y: -15 + }; + + expect(negativeRectangle.inRange(10, -16)).toBe(false); + expect(negativeRectangle.inRange(10, 1)).toBe(false); + expect(negativeRectangle.inRange(10, -5)).toBe(true); + }); + + it ('should get the correct height', function() { + var rectangle = new Chart.elements.Rectangle({ + _datasetIndex: 2, + _index: 1 + }); + + // Attach a view object as if we were the controller + rectangle._view = { + base: 0, + width: 4, + x: 10, + y: 15 + }; + + expect(rectangle.height()).toBe(-15); + + // Test when the y is below the base (negative bar) + var negativeRectangle = new Chart.elements.Rectangle({ + _datasetIndex: 2, + _index: 1 + }); + + // Attach a view object as if we were the controller + negativeRectangle._view = { + base: -10, + width: 4, + x: 10, + y: -15 + }; + expect(negativeRectangle.height()).toBe(5); + }); + + it ('should get the correct tooltip position', function() { + var rectangle = new Chart.elements.Rectangle({ + _datasetIndex: 2, + _index: 1 + }); + + // Attach a view object as if we were the controller + rectangle._view = { + base: 0, + width: 4, + x: 10, + y: 15 + }; + + expect(rectangle.tooltipPosition()).toEqual({ + x: 10, + y: 0, + }); + + // Test when the y is below the base (negative bar) + var negativeRectangle = new Chart.elements.Rectangle({ + _datasetIndex: 2, + _index: 1 + }); + + // Attach a view object as if we were the controller + negativeRectangle._view = { + base: -10, + width: 4, + x: 10, + y: -15 + }; + + expect(negativeRectangle.tooltipPosition()).toEqual({ + x: 10, + y: -15, + }); + }); + + it ('should draw correctly', function() { + var mockContext = window.createMockContext(); + var rectangle = new Chart.elements.Rectangle({ + _datasetIndex: 2, + _index: 1, + _chart: { + ctx: mockContext, + } + }); + + // Attach a view object as if we were the controller + rectangle._view = { + backgroundColor: 'rgb(255, 0, 0)', + base: 0, + borderColor: 'rgb(0, 0, 255)', + borderWidth: 1, + ctx: mockContext, + width: 4, + x: 10, + y: 15, + }; + + rectangle.draw(); + + expect(mockContext.getCalls()).toEqual([{ + name: 'beginPath', + args: [], + }, { + name: 'setFillStyle', + args: ['rgb(255, 0, 0)'] + }, { + name: 'setStrokeStyle', + args: ['rgb(0, 0, 255)'], + }, { + name: 'setLineWidth', + args: [1] + }, { + name: 'moveTo', + args: [8.5, 0] + }, { + name: 'lineTo', + args: [8.5, 15.5] + }, { + name: 'lineTo', + args: [11.5, 15.5] + }, { + name: 'lineTo', + args: [11.5, 0] + }, { + name: 'fill', + args: [], + }, { + name: 'stroke', + args: [] + }]); + }); + + it ('should draw correctly with no stroke', function() { + var mockContext = window.createMockContext(); + var rectangle = new Chart.elements.Rectangle({ + _datasetIndex: 2, + _index: 1, + _chart: { + ctx: mockContext, + } + }); + + // Attach a view object as if we were the controller + rectangle._view = { + backgroundColor: 'rgb(255, 0, 0)', + base: 0, + borderColor: 'rgb(0, 0, 255)', + ctx: mockContext, + width: 4, + x: 10, + y: 15, + }; + + rectangle.draw(); + + expect(mockContext.getCalls()).toEqual([{ + name: 'beginPath', + args: [], + }, { + name: 'setFillStyle', + args: ['rgb(255, 0, 0)'] + }, { + name: 'setStrokeStyle', + args: ['rgb(0, 0, 255)'], + }, { + name: 'setLineWidth', + args: [undefined] + }, { + name: 'moveTo', + args: [8, 0] + }, { + name: 'lineTo', + args: [8, 15] + }, { + name: 'lineTo', + args: [12, 15] + }, { + name: 'lineTo', + args: [12, 0] + }, { + name: 'fill', + args: [], + }]); + }); + + +}); \ No newline at end of file diff --git a/test/mockContext.js b/test/mockContext.js new file mode 100644 index 000000000..239a0a3e4 --- /dev/null +++ b/test/mockContext.js @@ -0,0 +1,108 @@ +(function() { + // Code from http://stackoverflow.com/questions/4406864/html-canvas-unit-testing + var Context = function() { + this._calls = []; // names/args of recorded calls + this._initMethods(); + + this._fillStyle = null; + this._lineCap = null; + this._lineDashOffset = null; + this._lineJoin = null; + this._lineWidth = null; + this._strokeStyle = null; + + // Define properties here so that we can record each time they are set + Object.defineProperties(this, { + "fillStyle": { + 'get': function() { return this._fillStyle; }, + 'set': function(style) { + this._fillStyle = style; + this.record('setFillStyle', [style]); + } + }, + 'lineCap': { + 'get': function() { return this._lineCap; }, + 'set': function(cap) { + this._lineCap = cap; + this.record('setLineCap', [cap]); + } + }, + 'lineDashOffset': { + 'get': function() { return this._lineDashOffset; }, + 'set': function(offset) { + this._lineDashOffset = offset; + this.record('setLineDashOffset', [offset]); + } + }, + 'lineJoin': { + 'get': function() { return this._lineJoin; }, + 'set': function(join) { + this._lineJoin = join; + this.record('setLineJoin', [join]); + } + }, + 'lineWidth': { + 'get': function() { return this._lineWidth; }, + 'set': function (width) { + this._lineWidth = width; + this.record('setLineWidth', [width]); + } + }, + 'strokeStyle': { + 'get': function() { return this._strokeStyle; }, + 'set': function(style) { + this._strokeStyle = style; + this.record('setStrokeStyle', [style]); + } + }, + }); + }; + + 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() {}, + bezierCurveTo: function() {}, + closePath: function() {}, + fill: function() {}, + lineTo: function(x, y) {}, + moveTo: function(x, y) {}, + restore: function() {}, + save: function() {}, + setLineDash: function() {}, + 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