From: Evert Timberg Date: Sat, 28 May 2016 19:26:46 +0000 (-0400) Subject: Initial tooltip tests + fix a bug when the tooltip beforeLabel and afterLabel callbac... X-Git-Tag: v2.1.5~28^2~4 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=8d20379e29e66d453c24fc19a646115430db7c01;p=thirdparty%2FChart.js.git Initial tooltip tests + fix a bug when the tooltip beforeLabel and afterLabel callbacks returned strings --- diff --git a/samples/tooltip-hooks.html b/samples/tooltip-hooks.html index 1f74df0b4..88a660515 100644 --- a/samples/tooltip-hooks.html +++ b/samples/tooltip-hooks.html @@ -73,6 +73,12 @@ afterBody: function() { return '...afterBody'; }, + beforeLabel: function() { + return '...beforeLabel'; + }, + afterLabel: function() { + return '...afterLabel'; + }, beforeFooter: function() { return '...beforeFooter'; }, diff --git a/src/core/core.tooltip.js b/src/core/core.tooltip.js index cd28499bf..3b594c38c 100644 --- a/src/core/core.tooltip.js +++ b/src/core/core.tooltip.js @@ -163,15 +163,22 @@ module.exports = function(Chart) { // Args are: (tooltipItem, data) getBody: function(tooltipItems, data) { - var lines = []; + var bodyItems = []; + + helpers.each(tooltipItems, function(tooltipItem) { + var bodyItem = { + before: [], + lines: [], + after: [] + }; + helpers.pushAllIfDefined(this._options.callbacks.beforeLabel.call(this, tooltipItem, data), bodyItem.before); + helpers.pushAllIfDefined(this._options.callbacks.label.call(this, tooltipItem, data), bodyItem.lines); + helpers.pushAllIfDefined(this._options.callbacks.afterLabel.call(this, tooltipItem, data), bodyItem.after); - helpers.each(tooltipItems, function(bodyItem) { - helpers.pushAllIfDefined(this._options.callbacks.beforeLabel.call(this, bodyItem, data), lines); - helpers.pushAllIfDefined(this._options.callbacks.label.call(this, bodyItem, data), lines); - helpers.pushAllIfDefined(this._options.callbacks.afterLabel.call(this, bodyItem, data), lines); + bodyItems.push(bodyItem); }, this); - return lines; + return bodyItems; }, // Args are: (tooltipItem, data) @@ -307,7 +314,13 @@ module.exports = function(Chart) { height: vm.yPadding * 2, // Tooltip Padding width: 0 }; - var combinedBodyLength = vm.body.length + vm.beforeBody.length + vm.afterBody.length; + + + var combinedBodyLength = vm.body.reduce(function(count, bodyItem) { + return count + bodyItem.before.length + bodyItem.lines.length + bodyItem.after.length; + }, 0); + // Count in before and after body sections + combinedBodyLength += vm.beforeBody.length + vm.afterBody.length; size.height += vm.title.length * vm.titleFontSize; // Title Lines size.height += (vm.title.length - 1) * vm.titleSpacing; // Title Line Spacing @@ -328,9 +341,16 @@ module.exports = function(Chart) { helpers.each(vm.beforeBody.concat(vm.afterBody), function(line) { size.width = Math.max(size.width, ctx.measureText(line).width); }); - helpers.each(vm.body, function(line) { - size.width = Math.max(size.width, ctx.measureText(line).width + (this._options.mode !== 'single' ? (vm.bodyFontSize + 2) : 0)); - }, this); + + var _this = this; + var maxBodyWidth = function(line) { + size.width = Math.max(size.width, ctx.measureText(line).width + (_this._options.mode !== 'single' ? (vm.bodyFontSize + 2) : 0)); + }; + helpers.each(vm.body, function(bodyItem) { + helpers.each(bodyItem.before, maxBodyWidth); + helpers.each(bodyItem.lines, maxBodyWidth); + helpers.each(bodyItem.after, maxBodyWidth); + }); ctx.font = helpers.fontString(vm.footerFontSize, vm._footerFontStyle, vm._footerFontFamily); helpers.each(vm.footer, function(line) { @@ -524,28 +544,38 @@ module.exports = function(Chart) { pt.y += vm.bodyFontSize + vm.bodySpacing; }); - helpers.each(vm.body, function(body, i) { - // Draw Legend-like boxes if needed - if (this._options.mode !== 'single') { - // Fill a white rect so that colours merge nicely if the opacity is < 1 - ctx.fillStyle = helpers.color(vm.legendColorBackground).alpha(opacity).rgbaString(); - ctx.fillRect(pt.x, pt.y, vm.bodyFontSize, vm.bodyFontSize); + helpers.each(vm.body, function(bodyItem, i) { + var _this = this; + var fillLine = function(line) { + // Body Line + ctx.fillText(line, pt.x + (_this._options.mode !== 'single' ? (vm.bodyFontSize + 2) : 0), pt.y); + pt.y += vm.bodyFontSize + vm.bodySpacing; + }; + + helpers.each(bodyItem.before, fillLine); - // Border - ctx.strokeStyle = helpers.color(vm.labelColors[i].borderColor).alpha(opacity).rgbaString(); - ctx.strokeRect(pt.x, pt.y, vm.bodyFontSize, vm.bodyFontSize); + helpers.each(bodyItem.lines, function(line) { + // Draw Legend-like boxes if needed + if (this._options.mode !== 'single') { + // Fill a white rect so that colours merge nicely if the opacity is < 1 + ctx.fillStyle = helpers.color(vm.legendColorBackground).alpha(opacity).rgbaString(); + ctx.fillRect(pt.x, pt.y, vm.bodyFontSize, vm.bodyFontSize); - // Inner square - ctx.fillStyle = helpers.color(vm.labelColors[i].backgroundColor).alpha(opacity).rgbaString(); - ctx.fillRect(pt.x + 1, pt.y + 1, vm.bodyFontSize - 2, vm.bodyFontSize - 2); + // Border + ctx.strokeStyle = helpers.color(vm.labelColors[i].borderColor).alpha(opacity).rgbaString(); + ctx.strokeRect(pt.x, pt.y, vm.bodyFontSize, vm.bodyFontSize); - ctx.fillStyle = helpers.color(vm.bodyColor).alpha(opacity).rgbaString(); // Return fill style for text - } + // Inner square + ctx.fillStyle = helpers.color(vm.labelColors[i].backgroundColor).alpha(opacity).rgbaString(); + ctx.fillRect(pt.x + 1, pt.y + 1, vm.bodyFontSize - 2, vm.bodyFontSize - 2); - // Body Line - ctx.fillText(body, pt.x + (this._options.mode !== 'single' ? (vm.bodyFontSize + 2) : 0), pt.y); + ctx.fillStyle = helpers.color(vm.bodyColor).alpha(opacity).rgbaString(); // Return fill style for text + } - pt.y += vm.bodyFontSize + vm.bodySpacing; + fillLine(line); + }, this); + + helpers.each(bodyItem.after, fillLine); }, this); // After Body diff --git a/test/core.tooltip.tests.js b/test/core.tooltip.tests.js new file mode 100644 index 000000000..2141973af --- /dev/null +++ b/test/core.tooltip.tests.js @@ -0,0 +1,375 @@ +// Test the rectangle element +describe('tooltip tests', function() { + + beforeEach(function() { + window.addDefaultMatchers(jasmine); + }); + + afterEach(function() { + window.releaseAllCharts(); + }); + + it('Should display in label mode', function() { + var chartInstance = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + label: 'Dataset 1', + data: [10, 20, 30], + pointHoverBorderColor: 'rgb(255, 0, 0)', + pointHoverBackgroundColor: 'rgb(0, 255, 0)' + }, { + label: 'Dataset 2', + data: [40, 40, 40], + pointHoverBorderColor: 'rgb(0, 0, 255)', + pointHoverBackgroundColor: 'rgb(0, 255, 255)' + }], + labels: ['Point 1', 'Point 2', 'Point 3'] + }, + options: { + tooltips: { + mode: 'label' + } + } + }); + + // Trigger an event over top of the + var meta = chartInstance.getDatasetMeta(0); + var point = meta.data[1]; + + var node = chartInstance.chart.canvas; + var rect = node.getBoundingClientRect(); + + var evt = new MouseEvent('mousemove', { + view: window, + bubbles: true, + cancelable: true, + clientX: rect.left + point._model.x, + clientY: rect.top + point._model.y + }); + + // Manully trigger rather than having an async test + node.dispatchEvent(evt); + + // Check and see if tooltip was displayed + var tooltip = chartInstance.tooltip; + var globalDefaults = Chart.defaults.global; + + expect(tooltip._view).toEqual(jasmine.objectContaining({ + // Positioning + xPadding: 6, + yPadding: 6, + xAlign: 'left', + yAlign: 'center', + + // Body + bodyColor: '#fff', + _bodyFontFamily: globalDefaults.defaultFontFamily, + _bodyFontStyle: globalDefaults.defaultFontStyle, + _bodyAlign: 'left', + bodyFontSize: globalDefaults.defaultFontSize, + bodySpacing: 2, + + // Title + titleColor: '#fff', + _titleFontFamily: globalDefaults.defaultFontFamily, + _titleFontStyle: 'bold', + titleFontSize: globalDefaults.defaultFontSize, + _titleAlign: 'left', + titleSpacing: 2, + titleMarginBottom: 6, + + // Footer + footerColor: '#fff', + _footerFontFamily: globalDefaults.defaultFontFamily, + _footerFontStyle: 'bold', + footerFontSize: globalDefaults.defaultFontSize, + _footerAlign: 'left', + footerSpacing: 2, + footerMarginTop: 6, + + // Appearance + caretSize: 5, + cornerRadius: 6, + backgroundColor: 'rgba(0,0,0,0.8)', + opacity: 1, + legendColorBackground: '#fff', + + // Text + title: ['Point 2'], + beforeBody: [], + body: [{ + before: [], + lines: ['Dataset 1: 20'], + after: [] + }, { + before: [], + lines: ['Dataset 2: 40'], + after: [] + }], + afterBody: [], + footer: [], + x: 269, + y: 155, + caretPadding: 2, + labelColors: [{ + borderColor: 'rgb(255, 0, 0)', + backgroundColor: 'rgb(0, 255, 0)' + }, { + borderColor: 'rgb(0, 0, 255)', + backgroundColor: 'rgb(0, 255, 255)' + }] + })); + }); + + it('Should display in single mode', function() { + var chartInstance = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + label: 'Dataset 1', + data: [10, 20, 30], + pointHoverBorderColor: 'rgb(255, 0, 0)', + pointHoverBackgroundColor: 'rgb(0, 255, 0)' + }, { + label: 'Dataset 2', + data: [40, 40, 40], + pointHoverBorderColor: 'rgb(0, 0, 255)', + pointHoverBackgroundColor: 'rgb(0, 255, 255)' + }], + labels: ['Point 1', 'Point 2', 'Point 3'] + }, + options: { + tooltips: { + mode: 'single' + } + } + }); + + // Trigger an event over top of the + var meta = chartInstance.getDatasetMeta(0); + var point = meta.data[1]; + + var node = chartInstance.chart.canvas; + var rect = node.getBoundingClientRect(); + + var evt = new MouseEvent('mousemove', { + view: window, + bubbles: true, + cancelable: true, + clientX: rect.left + point._model.x, + clientY: rect.top + point._model.y + }); + + // Manully trigger rather than having an async test + node.dispatchEvent(evt); + + // Check and see if tooltip was displayed + var tooltip = chartInstance.tooltip; + var globalDefaults = Chart.defaults.global; + + expect(tooltip._view).toEqual(jasmine.objectContaining({ + // Positioning + xPadding: 6, + yPadding: 6, + xAlign: 'left', + yAlign: 'center', + + // Body + bodyColor: '#fff', + _bodyFontFamily: globalDefaults.defaultFontFamily, + _bodyFontStyle: globalDefaults.defaultFontStyle, + _bodyAlign: 'left', + bodyFontSize: globalDefaults.defaultFontSize, + bodySpacing: 2, + + // Title + titleColor: '#fff', + _titleFontFamily: globalDefaults.defaultFontFamily, + _titleFontStyle: 'bold', + titleFontSize: globalDefaults.defaultFontSize, + _titleAlign: 'left', + titleSpacing: 2, + titleMarginBottom: 6, + + // Footer + footerColor: '#fff', + _footerFontFamily: globalDefaults.defaultFontFamily, + _footerFontStyle: 'bold', + footerFontSize: globalDefaults.defaultFontSize, + _footerAlign: 'left', + footerSpacing: 2, + footerMarginTop: 6, + + // Appearance + caretSize: 5, + cornerRadius: 6, + backgroundColor: 'rgba(0,0,0,0.8)', + opacity: 1, + legendColorBackground: '#fff', + + // Text + title: ['Point 2'], + beforeBody: [], + body: [{ + before: [], + lines: ['Dataset 1: 20'], + after: [] + }], + afterBody: [], + footer: [], + x: 269, + y: 312, + caretPadding: 2, + labelColors: [] + })); + }); + + it('Should display information from user callbacks', function() { + var chartInstance = window.acquireChart({ + type: 'line', + data: { + datasets: [{ + label: 'Dataset 1', + data: [10, 20, 30], + pointHoverBorderColor: 'rgb(255, 0, 0)', + pointHoverBackgroundColor: 'rgb(0, 255, 0)' + }, { + label: 'Dataset 2', + data: [40, 40, 40], + pointHoverBorderColor: 'rgb(0, 0, 255)', + pointHoverBackgroundColor: 'rgb(0, 255, 255)' + }], + labels: ['Point 1', 'Point 2', 'Point 3'] + }, + options: { + tooltips: { + mode: 'label', + callbacks: { + beforeTitle: function() { + return 'beforeTitle'; + }, + title: function() { + return 'title'; + }, + afterTitle: function() { + return 'afterTitle' + }, + beforeBody: function() { + return 'beforeBody'; + }, + beforeLabel: function() { + return 'beforeLabel'; + }, + label: function() { + return 'label'; + }, + afterLabel: function() { + return 'afterLabel'; + }, + afterBody: function() { + return 'afterBody'; + }, + beforeFooter: function() { + return 'beforeFooter'; + }, + footer: function() { + return 'footer'; + }, + afterFooter: function() { + return 'afterFooter' + } + } + } + } + }); + + // Trigger an event over top of the + var meta = chartInstance.getDatasetMeta(0); + var point = meta.data[1]; + + var node = chartInstance.chart.canvas; + var rect = node.getBoundingClientRect(); + + var evt = new MouseEvent('mousemove', { + view: window, + bubbles: true, + cancelable: true, + clientX: rect.left + point._model.x, + clientY: rect.top + point._model.y + }); + + // Manully trigger rather than having an async test + node.dispatchEvent(evt); + + // Check and see if tooltip was displayed + var tooltip = chartInstance.tooltip; + var globalDefaults = Chart.defaults.global; + + expect(tooltip._view).toEqual(jasmine.objectContaining({ + // Positioning + xPadding: 6, + yPadding: 6, + xAlign: 'center', + yAlign: 'top', + + // Body + bodyColor: '#fff', + _bodyFontFamily: globalDefaults.defaultFontFamily, + _bodyFontStyle: globalDefaults.defaultFontStyle, + _bodyAlign: 'left', + bodyFontSize: globalDefaults.defaultFontSize, + bodySpacing: 2, + + // Title + titleColor: '#fff', + _titleFontFamily: globalDefaults.defaultFontFamily, + _titleFontStyle: 'bold', + titleFontSize: globalDefaults.defaultFontSize, + _titleAlign: 'left', + titleSpacing: 2, + titleMarginBottom: 6, + + // Footer + footerColor: '#fff', + _footerFontFamily: globalDefaults.defaultFontFamily, + _footerFontStyle: 'bold', + footerFontSize: globalDefaults.defaultFontSize, + _footerAlign: 'left', + footerSpacing: 2, + footerMarginTop: 6, + + // Appearance + caretSize: 5, + cornerRadius: 6, + backgroundColor: 'rgba(0,0,0,0.8)', + opacity: 1, + legendColorBackground: '#fff', + + // Text + title: ['beforeTitle', 'title', 'afterTitle'], + beforeBody: ['beforeBody'], + body: [{ + before: ['beforeLabel'], + lines: ['label'], + after: ['afterLabel'] + }, { + before: ['beforeLabel'], + lines: ['label'], + after: ['afterLabel'] + }], + afterBody: ['afterBody'], + footer: ['beforeFooter', 'footer', 'afterFooter'], + x: 216, + y: 190, + caretPadding: 2, + labelColors: [{ + borderColor: 'rgb(255, 0, 0)', + backgroundColor: 'rgb(0, 255, 0)' + }, { + borderColor: 'rgb(0, 0, 255)', + backgroundColor: 'rgb(0, 255, 255)' + }] + })); + }); +});