Chart.js/test/jasmine.utils.js
Simon Brunel 1ca0ffb5d5 Introduce unit test based on image comparison (#3988)
Attempt to make easier the creation of unit tests that check the drawing output. Until now, this was done by checking calls on a 'fake' context, which is hard to maintain (need to update pixel values by hands) and also not reliable when optimizing code (i.e. different calls sequence but same result).

As of now, it's possible to define 'auto' tests based on JSON/PNG fixtures: chart is generated from the JSON file and compared to the associated PNG image. The image diff is done using `pixelmatch`. As an example (and in preparation of the `filler` plugin), add auto tests for the line element `fill` options.
2017-03-05 09:49:12 -07:00

159 lines
4.1 KiB
JavaScript

/* global __karma__ */
function loadJSON(url, callback) {
var request = new XMLHttpRequest();
request.onreadystatechange = function() {
if (request.readyState === 4) {
return callback(JSON.parse(request.responseText));
}
};
request.overrideMimeType('application/json');
request.open('GET', url, true);
request.send(null);
}
function createCanvas(w, h) {
var canvas = document.createElement('canvas');
canvas.width = w;
canvas.height = h;
return canvas;
}
function readImageData(url, callback) {
var image = new Image();
image.onload = function() {
var h = image.height;
var w = image.width;
var canvas = createCanvas(w, h);
var ctx = canvas.getContext('2d');
ctx.drawImage(image, 0, 0, w, h);
callback(ctx.getImageData(0, 0, w, h));
};
image.src = url;
}
/**
* Injects a new canvas (and div wrapper) and creates teh associated Chart instance
* using the given config. Additional options allow tweaking elements generation.
* @param {object} config - Chart config.
* @param {object} options - Chart acquisition options.
* @param {object} options.canvas - Canvas attributes.
* @param {object} options.wrapper - Canvas wrapper attributes.
* @param {boolean} options.persistent - If true, the chart will not be released after the spec.
*/
function acquireChart(config, options) {
var wrapper = document.createElement('div');
var canvas = document.createElement('canvas');
var chart, key;
config = config || {};
options = options || {};
options.canvas = options.canvas || {height: 512, width: 512};
options.wrapper = options.wrapper || {class: 'chartjs-wrapper'};
for (key in options.canvas) {
if (options.canvas.hasOwnProperty(key)) {
canvas.setAttribute(key, options.canvas[key]);
}
}
for (key in options.wrapper) {
if (options.wrapper.hasOwnProperty(key)) {
wrapper.setAttribute(key, options.wrapper[key]);
}
}
// by default, remove chart animation and auto resize
config.options = config.options || {};
config.options.animation = config.options.animation === undefined? false : config.options.animation;
config.options.responsive = config.options.responsive === undefined? false : config.options.responsive;
config.options.defaultFontFamily = config.options.defaultFontFamily || 'Arial';
wrapper.appendChild(canvas);
window.document.body.appendChild(wrapper);
chart = new Chart(canvas.getContext('2d'), config);
chart.$test = {
persistent: options.persistent,
wrapper: wrapper
};
return chart;
}
function releaseChart(chart) {
chart.destroy();
var wrapper = (chart.$test || {}).wrapper;
if (wrapper && wrapper.parentNode) {
wrapper.parentNode.removeChild(wrapper);
}
}
function injectCSS(css) {
// http://stackoverflow.com/q/3922139
var head = document.getElementsByTagName('head')[0];
var style = document.createElement('style');
style.setAttribute('type', 'text/css');
if (style.styleSheet) { // IE
style.styleSheet.cssText = css;
} else {
style.appendChild(document.createTextNode(css));
}
head.appendChild(style);
}
function specFromFixture(description, inputs) {
it(inputs.json, function(done) {
loadJSON(inputs.json, function(json) {
var chart = acquireChart(json.config, json.options);
if (!inputs.png) {
fail('Missing PNG comparison file for ' + inputs.json);
if (!json.debug) {
releaseChart(chart);
}
done();
}
readImageData(inputs.png, function(expected) {
expect(chart).toEqualImageData(expected, json);
releaseChart(chart);
done();
});
});
});
}
function specsFromFixtures(path) {
var regex = new RegExp('(^/base/test/fixtures/' + path + '.+)\\.(png|json)');
var inputs = {};
Object.keys(__karma__.files || {}).forEach(function(file) {
var matches = file.match(regex);
var name = matches && matches[1];
var type = matches && matches[2];
if (name && type) {
inputs[name] = inputs[name] || {};
inputs[name][type] = file;
}
});
return function() {
Object.keys(inputs).forEach(function(key) {
specFromFixture(key, inputs[key]);
});
};
}
module.exports = {
injectCSS: injectCSS,
createCanvas: createCanvas,
acquireChart: acquireChart,
releaseChart: releaseChart,
specsFromFixtures: specsFromFixtures
};