Implement item comparison through rasterization and resemble.js diffing, directly integrated into QUnit.

And start using it for boolean operation unit tests.
This commit is contained in:
Jürg Lehni 2015-12-28 22:42:16 +01:00
parent 2596b81616
commit a1fcaabed6
5 changed files with 92 additions and 22 deletions

View file

@ -27,6 +27,7 @@
"straps": "~1.7.2",
"acorn": "~0.5.0",
"qunit": "~1.20.0",
"resemblejs": "~2.0.1",
"stats.js": "r14"
},
"keywords": [

View file

@ -1564,6 +1564,7 @@ var Item = Base.extend(Emitter, /** @lends Item# */{
* raster.scale(5);
*/
rasterize: function(resolution, insert) {
// TODO: Switch to options object for more descriptive call signature.
var bounds = this.getStrokeBounds(),
scale = (resolution || this.getView().getResolution()) / 72,
// Floor top-left corner and ceil bottom-right corner, to never

View file

@ -4,6 +4,7 @@
<title>Paper.js Tests</title>
<link rel="stylesheet" href="../bower_components/qunit/qunit/qunit.css">
<script type="text/javascript" src="../bower_components/qunit/qunit/qunit.js"></script>
<script type="text/javascript" src="../bower_components/resemblejs/resemble.js"></script>
<script type="text/javascript" src="js/helpers.js"></script>
<script type="text/javascript" src="../src/load.js"></script>
<script type="text/javascript" src="tests/load.js"></script>

View file

@ -21,6 +21,15 @@ QUnit.begin(function() {
QUnit.config.hidepassed = true;
document.getElementById('qunit-tests').className += ' hidepass';
}
resemble.outputSettings({
errorColor: {
red: 255,
green: 51,
blue: 0
},
errorType: 'flat',
transparency: 1
});
});
// Register a jsDump parser for Base.
@ -48,26 +57,71 @@ function compareProperties(actual, expected, properties, message, options) {
}
function compareItem(actual, expected, message, options, properties) {
if (options && options.cloned)
QUnit.notStrictEqual(actual.id, 'not ' + expected.id, message + '.id');
QUnit.strictEqual(actual.constructor, expected.constructor,
message + '.constructor');
equals(actual.name,
// When item was cloned and had a name, the name will be versioned
options && options.cloned && expected.name
? expected.name + ' 1' : expected.name,
message + '.name');
compareProperties(actual, expected, ['children', 'bounds', 'position',
'matrix', 'data', 'opacity', 'locked', 'visible', 'blendMode',
'selected', 'fullySelected', 'clipMask', 'guide'],
message, options);
if (properties)
compareProperties(actual, expected, properties, message, options);
// Style
compareProperties(actual.style, expected.style, ['fillColor', 'strokeColor',
'strokeCap', 'strokeJoin', 'dashArray', 'dashOffset', 'miterLimit',
'fontSize', 'font', 'leading', 'justification'],
message + '.style', options);
function getImageTag(raster) {
return '<img width="' + raster.with + '" height="' + raster.height
+ '" src="'+ raster.source + '">'
}
if (options && options.rasterize) {
var resolution = options.rasterize == true ? 72 : options.rasterize;
var raster1 = actual && actual.rasterize(resolution, false),
raster2 = expected && expected.rasterize(resolution, false);
if (!raster1 || !raster2) {
QUnit.pushFailure('Unable to compare rasterized items: ' +
(!raster1 ? 'actual' : 'expected') + ' item is null',
QUnit.stack(2));
} else {
// Use resemble.js to compare the two rasterized items.
var id = QUnit.config.current.testId,
result;
resemble(raster1.getImageData())
.compareTo(raster2.getImageData())
// When working with imageData, this call is synchronous:
.onComplete(function(data) { result = data; });
var identical = result ? 100 - result.misMatchPercentage : 0,
ok = identical == 100.0;
QUnit.push(ok, identical.toFixed(1) + '% identical',
'100.0% identical', message);
if (!ok && result) {
var output = document.getElementById('qunit-test-output-' + id),
bounds = result.diffBounds;
output.querySelector('.test-expected td').innerHTML =
getImageTag(raster1);
var el = output.querySelector('.test-actual td');
el.innerHTML = getImageTag(raster2) + '<br>' +
el.innerHTML.replace(/<\/?pre>|"/g, '');
output.querySelector('.test-diff td').innerHTML =
getImageTag({
source: result.getImageDataUrl(),
width: bounds.right - bounds.left,
height: bounds.bottom - bounds.top
});
}
}
} else {
if (options && options.cloned)
QUnit.notStrictEqual(actual.id, expected.id,
'not ' + message + '.id');
QUnit.strictEqual(actual.constructor, expected.constructor,
message + '.constructor');
// When item is cloned and has a name, the name will be versioned:
equals(actual.name,
options && options.cloned && expected.name
? expected.name + ' 1' : expected.name,
message + '.name');
compareProperties(actual, expected, ['children', 'bounds', 'position',
'matrix', 'data', 'opacity', 'locked', 'visible', 'blendMode',
'selected', 'fullySelected', 'clipMask', 'guide'],
message, options);
if (properties)
compareProperties(actual, expected, properties, message, options);
// Style
compareProperties(actual.style, expected.style, ['fillColor',
'strokeColor', 'strokeCap', 'strokeJoin', 'dashArray',
'dashOffset', 'miterLimit', 'fontSize', 'font', 'leading',
'justification'], message + '.style', options);
}
}
// A list of comparator functions, based on `expected` type. See equals() for

View file

@ -27,7 +27,14 @@ test('path.unite(); #609', function() {
path2.closePath();
var result = path1.unite(path2);
equals(result.pathData, 'M150,150c0,27.61424 -22.38576,50 -50,50c-27.61424,0 -50,-22.38576 -50,-50c0,-27.61424 22.38576,-50 50,-50c27.61424,0 50,22.38576 50,50z', 'result.pathData');
result.fillColor = 'blue';
var expected = new Path({
pathData: 'M150,150c0,27.61424 -22.38576,50 -50,50c-27.61424,0 -50,-22.38576 -50,-50c0,-27.61424 22.38576,-50 50,-50c27.61424,0 50,22.38576 50,50z',
fillColor: 'blue'
});
equals(result, expected, 'path1.unite(path2);', { rasterize: true });
});
test('ring.subtract(square); #610', function() {
@ -50,6 +57,12 @@ test('ring.subtract(square); #610', function() {
var ring = outer.subtract(inner);
var result = ring.subtract(square);
result.fillColor = 'blue';
equals(result.pathData, 'M-132,0c0,-69.53737 53.7698,-126.51614 122,-131.62689l0,32.12064c-50.53323,5.01724 -90,47.65277 -90,99.50625c0,51.85348 39.46677,94.489 90,99.50625l0,32.12064c-68.2302,-5.11075 -122,-62.08951 -122,-131.62689z');
var expected = new Path({
pathData: 'M-132,0c0,-69.53737 53.7698,-126.51614 122,-131.62689l0,32.12064c-50.53323,5.01724 -90,47.65277 -90,99.50625c0,51.85348 39.46677,94.489 90,99.50625l0,32.12064c-68.2302,-5.11075 -122,-62.08951 -122,-131.62689z',
fillColor: 'blue'
});
equals(result, expected, 'ring.subtract(square);', { rasterize: true });
});