2017-03-04 16:47:53 +01:00
|
|
|
'use strict';
|
|
|
|
|
2020-01-18 01:13:38 +01:00
|
|
|
import pixelmatch from 'pixelmatch';
|
|
|
|
import utils from './utils';
|
2017-03-05 17:49:12 +01:00
|
|
|
|
|
|
|
function toPercent(value) {
|
|
|
|
return Math.round(value * 10000) / 100;
|
|
|
|
}
|
|
|
|
|
|
|
|
function createImageData(w, h) {
|
|
|
|
var canvas = utils.createCanvas(w, h);
|
|
|
|
var context = canvas.getContext('2d');
|
|
|
|
return context.getImageData(0, 0, w, h);
|
|
|
|
}
|
|
|
|
|
|
|
|
function canvasFromImageData(data) {
|
|
|
|
var canvas = utils.createCanvas(data.width, data.height);
|
|
|
|
var context = canvas.getContext('2d');
|
|
|
|
context.putImageData(data, 0, 0);
|
|
|
|
return canvas;
|
|
|
|
}
|
|
|
|
|
2020-07-13 19:20:05 +02:00
|
|
|
function buildPixelMatchPreview(actual, expected, diff, threshold, tolerance, count, description) {
|
2017-03-05 17:49:12 +01:00
|
|
|
var ratio = count / (actual.width * actual.height);
|
|
|
|
var wrapper = document.createElement('div');
|
2020-07-13 19:20:05 +02:00
|
|
|
wrapper.appendChild(document.createTextNode(description));
|
2017-03-05 17:49:12 +01:00
|
|
|
|
|
|
|
wrapper.style.cssText = 'display: flex; overflow-y: auto';
|
|
|
|
|
|
|
|
[
|
|
|
|
{data: actual, label: 'Actual'},
|
|
|
|
{data: expected, label: 'Expected'},
|
|
|
|
{data: diff, label:
|
|
|
|
'diff: ' + count + 'px ' +
|
|
|
|
'(' + toPercent(ratio) + '%)<br/>' +
|
|
|
|
'thr: ' + toPercent(threshold) + '%, ' +
|
2017-07-22 14:13:09 +02:00
|
|
|
'tol: ' + toPercent(tolerance) + '%'
|
2017-03-05 17:49:12 +01:00
|
|
|
}
|
|
|
|
].forEach(function(values) {
|
|
|
|
var item = document.createElement('div');
|
|
|
|
item.style.cssText = 'text-align: center; font: 12px monospace; line-height: 1.4; margin: 8px';
|
|
|
|
item.innerHTML = '<div style="margin: 8px; height: 32px">' + values.label + '</div>';
|
|
|
|
item.appendChild(canvasFromImageData(values.data));
|
|
|
|
wrapper.appendChild(item);
|
|
|
|
});
|
|
|
|
|
|
|
|
// WORKAROUND: https://github.com/karma-runner/karma-jasmine/issues/139
|
|
|
|
wrapper.indexOf = function() {
|
|
|
|
return -1;
|
|
|
|
};
|
|
|
|
|
|
|
|
return wrapper;
|
|
|
|
}
|
|
|
|
|
2017-03-04 16:47:53 +01:00
|
|
|
function toBeCloseToPixel() {
|
|
|
|
return {
|
|
|
|
compare: function(actual, expected) {
|
|
|
|
var result = false;
|
|
|
|
|
|
|
|
if (!isNaN(actual) && !isNaN(expected)) {
|
|
|
|
var diff = Math.abs(actual - expected);
|
|
|
|
var A = Math.abs(actual);
|
|
|
|
var B = Math.abs(expected);
|
|
|
|
var percentDiff = 0.005; // 0.5% diff
|
|
|
|
result = (diff <= (A > B ? A : B) * percentDiff) || diff < 2; // 2 pixels is fine
|
|
|
|
}
|
|
|
|
|
|
|
|
return {pass: result};
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2020-02-28 01:37:11 +01:00
|
|
|
function toBeCloseToPoint() {
|
|
|
|
function rnd(v) {
|
|
|
|
return Math.round(v * 100) / 100;
|
|
|
|
}
|
|
|
|
return {
|
|
|
|
compare: function(actual, expected) {
|
|
|
|
return {
|
|
|
|
pass: rnd(actual.x) === rnd(expected.x) && rnd(actual.y) === rnd(expected.y)
|
|
|
|
};
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2017-03-04 16:47:53 +01:00
|
|
|
function toEqualOneOf() {
|
|
|
|
return {
|
|
|
|
compare: function(actual, expecteds) {
|
|
|
|
var result = false;
|
|
|
|
for (var i = 0, l = expecteds.length; i < l; i++) {
|
|
|
|
if (actual === expecteds[i]) {
|
|
|
|
result = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return {
|
|
|
|
pass: result
|
|
|
|
};
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function toBeValidChart() {
|
|
|
|
return {
|
|
|
|
compare: function(actual) {
|
|
|
|
var message = null;
|
|
|
|
|
|
|
|
if (!(actual instanceof Chart)) {
|
|
|
|
message = 'Expected ' + actual + ' to be an instance of Chart';
|
2017-04-22 09:49:10 +02:00
|
|
|
} else if (Object.prototype.toString.call(actual.canvas) !== '[object HTMLCanvasElement]') {
|
2017-03-04 16:47:53 +01:00
|
|
|
message = 'Expected canvas to be an instance of HTMLCanvasElement';
|
2017-04-22 09:49:10 +02:00
|
|
|
} else if (Object.prototype.toString.call(actual.ctx) !== '[object CanvasRenderingContext2D]') {
|
2017-03-04 16:47:53 +01:00
|
|
|
message = 'Expected context to be an instance of CanvasRenderingContext2D';
|
|
|
|
} else if (typeof actual.height !== 'number' || !isFinite(actual.height)) {
|
|
|
|
message = 'Expected height to be a strict finite number';
|
|
|
|
} else if (typeof actual.width !== 'number' || !isFinite(actual.width)) {
|
|
|
|
message = 'Expected width to be a strict finite number';
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
2017-07-22 14:13:09 +02:00
|
|
|
message: message ? message : 'Expected ' + actual + ' to be valid chart',
|
2017-03-04 16:47:53 +01:00
|
|
|
pass: !message
|
|
|
|
};
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function toBeChartOfSize() {
|
|
|
|
return {
|
|
|
|
compare: function(actual, expected) {
|
|
|
|
var res = toBeValidChart().compare(actual);
|
|
|
|
if (!res.pass) {
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
var message = null;
|
|
|
|
var canvas = actual.ctx.canvas;
|
|
|
|
var style = getComputedStyle(canvas);
|
2017-06-04 18:57:55 +02:00
|
|
|
var pixelRatio = actual.options.devicePixelRatio || window.devicePixelRatio;
|
2017-08-01 14:28:45 +02:00
|
|
|
var dh = parseInt(style.height, 10) || 0;
|
|
|
|
var dw = parseInt(style.width, 10) || 0;
|
2017-03-04 16:47:53 +01:00
|
|
|
var rh = canvas.height;
|
|
|
|
var rw = canvas.width;
|
|
|
|
var orh = rh / pixelRatio;
|
|
|
|
var orw = rw / pixelRatio;
|
|
|
|
|
|
|
|
// sanity checks
|
|
|
|
if (actual.height !== orh) {
|
|
|
|
message = 'Expected chart height ' + actual.height + ' to be equal to original render height ' + orh;
|
|
|
|
} else if (actual.width !== orw) {
|
|
|
|
message = 'Expected chart width ' + actual.width + ' to be equal to original render width ' + orw;
|
|
|
|
}
|
|
|
|
|
|
|
|
// validity checks
|
|
|
|
if (dh !== expected.dh) {
|
|
|
|
message = 'Expected display height ' + dh + ' to be equal to ' + expected.dh;
|
|
|
|
} else if (dw !== expected.dw) {
|
|
|
|
message = 'Expected display width ' + dw + ' to be equal to ' + expected.dw;
|
|
|
|
} else if (rh !== expected.rh) {
|
|
|
|
message = 'Expected render height ' + rh + ' to be equal to ' + expected.rh;
|
|
|
|
} else if (rw !== expected.rw) {
|
|
|
|
message = 'Expected render width ' + rw + ' to be equal to ' + expected.rw;
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
2017-07-22 14:13:09 +02:00
|
|
|
message: message ? message : 'Expected ' + actual + ' to be a chart of size ' + expected,
|
2017-03-04 16:47:53 +01:00
|
|
|
pass: !message
|
|
|
|
};
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2017-03-05 17:49:12 +01:00
|
|
|
function toEqualImageData() {
|
|
|
|
return {
|
|
|
|
compare: function(actual, expected, opts) {
|
|
|
|
var message = null;
|
|
|
|
var debug = opts.debug || false;
|
2017-07-22 14:13:09 +02:00
|
|
|
var tolerance = opts.tolerance === undefined ? 0.001 : opts.tolerance;
|
|
|
|
var threshold = opts.threshold === undefined ? 0.1 : opts.threshold;
|
2017-03-05 17:49:12 +01:00
|
|
|
var ctx, idata, ddata, w, h, count, ratio;
|
|
|
|
|
|
|
|
if (actual instanceof Chart) {
|
|
|
|
ctx = actual.ctx;
|
|
|
|
} else if (actual instanceof HTMLCanvasElement) {
|
|
|
|
ctx = actual.getContext('2d');
|
|
|
|
} else if (actual instanceof CanvasRenderingContext2D) {
|
|
|
|
ctx = actual;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ctx) {
|
|
|
|
h = expected.height;
|
|
|
|
w = expected.width;
|
|
|
|
idata = ctx.getImageData(0, 0, w, h);
|
|
|
|
ddata = createImageData(w, h);
|
|
|
|
count = pixelmatch(idata.data, expected.data, ddata.data, w, h, {threshold: threshold});
|
|
|
|
ratio = count / (w * h);
|
|
|
|
|
|
|
|
if ((ratio > tolerance) || debug) {
|
2020-07-13 19:20:05 +02:00
|
|
|
message = buildPixelMatchPreview(idata, expected, ddata, threshold, tolerance, count, opts.description);
|
2017-03-05 17:49:12 +01:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
message = 'Input value is not a valid image source.';
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
message: message,
|
|
|
|
pass: !message
|
|
|
|
};
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2020-01-18 01:13:38 +01:00
|
|
|
export default {
|
2020-02-28 01:37:11 +01:00
|
|
|
toBeCloseToPixel,
|
|
|
|
toBeCloseToPoint,
|
|
|
|
toEqualOneOf,
|
|
|
|
toBeValidChart,
|
|
|
|
toBeChartOfSize,
|
|
|
|
toEqualImageData
|
2017-03-04 16:47:53 +01:00
|
|
|
};
|