From a1fcaabed625e4edd959993a006a8f1b796cc5f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrg=20Lehni?= Date: Mon, 28 Dec 2015 22:42:16 +0100 Subject: [PATCH] Implement item comparison through rasterization and resemble.js diffing, directly integrated into QUnit. And start using it for boolean operation unit tests. --- bower.json | 1 + src/item/Item.js | 1 + test/index.html | 1 + test/js/helpers.js | 94 ++++++++++++++++++++++++++++++-------- test/tests/Path_Boolean.js | 17 ++++++- 5 files changed, 92 insertions(+), 22 deletions(-) diff --git a/bower.json b/bower.json index a923f893..89baf391 100644 --- a/bower.json +++ b/bower.json @@ -27,6 +27,7 @@ "straps": "~1.7.2", "acorn": "~0.5.0", "qunit": "~1.20.0", + "resemblejs": "~2.0.1", "stats.js": "r14" }, "keywords": [ diff --git a/src/item/Item.js b/src/item/Item.js index b27de970..9c1b83c7 100644 --- a/src/item/Item.js +++ b/src/item/Item.js @@ -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 diff --git a/test/index.html b/test/index.html index 44e9102e..6c60d018 100644 --- a/test/index.html +++ b/test/index.html @@ -4,6 +4,7 @@ Paper.js Tests + diff --git a/test/js/helpers.js b/test/js/helpers.js index 87d14a5c..9981f272 100644 --- a/test/js/helpers.js +++ b/test/js/helpers.js @@ -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 '' + } + + 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) + '
' + + 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 diff --git a/test/tests/Path_Boolean.js b/test/tests/Path_Boolean.js index 1e70b865..dcaf5127 100644 --- a/test/tests/Path_Boolean.js +++ b/test/tests/Path_Boolean.js @@ -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 }); });