From a12e99e387c745cf93185453fb47480b8532d735 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrg=20Lehni?= Date: Mon, 1 Feb 2016 20:15:37 +0100 Subject: [PATCH] Implement unit tests for SVG Importing, based on visual comparison. For now, one test for #932 --- examples/SVG Import/Clipping.html | 3 - src/item/Raster.js | 2 +- test/assets/clipping.svg | 91 +++++++++++++++++++++++++++++++ test/helpers.js | 82 ++++++++++++++++------------ test/tests/SvgImport.js | 50 +++++++++++++++++ 5 files changed, 189 insertions(+), 39 deletions(-) create mode 100644 test/assets/clipping.svg diff --git a/examples/SVG Import/Clipping.html b/examples/SVG Import/Clipping.html index 9be31c01..814203e6 100644 --- a/examples/SVG Import/Clipping.html +++ b/examples/SVG Import/Clipping.html @@ -76,9 +76,6 @@ Clipping a group with a path: - diff --git a/src/item/Raster.js b/src/item/Raster.js index 4498d09c..55893ec2 100644 --- a/src/item/Raster.js +++ b/src/item/Raster.js @@ -373,10 +373,10 @@ var Raster = Item.extend(/** @lends Raster# */{ image = document.getElementById(src) || new window.Image(); if (crossOrigin) image.crossOrigin = crossOrigin; - this.setImage(image); // A new image created above? Set the source now. if (!image.src) image.src = src; + this.setImage(image); }, /** diff --git a/test/assets/clipping.svg b/test/assets/clipping.svg new file mode 100644 index 00000000..a9fa2bae --- /dev/null +++ b/test/assets/clipping.svg @@ -0,0 +1,91 @@ + + Clipping a path with a path: + + + + + + + + + + + + + + + + Clipping a compound path with a path: + + + + + + + + + + + + + + + + Clipping a path with a compound path: + + + + + + + + + + + + + + + + Clipping a group with a path: + + + + + + + + + + + + + + + + + + + + diff --git a/test/helpers.js b/test/helpers.js index 02a0d046..4ab7dff8 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -11,6 +11,8 @@ */ var isNode = typeof global === 'object', + isPhantom = !!window.callPhantom, + isBrowser = !isNode && !isPhantom, root; if (isNode) { @@ -135,8 +137,7 @@ var compareProperties = function(actual, expected, properties, message, options) } }; -var compareRasterized = function(actual, expected, message, options) { - +var comparePixels = function(actual, expected, message, options) { function rasterize(item, group, resolution) { var raster = null; if (group) { @@ -152,10 +153,11 @@ var compareRasterized = function(actual, expected, message, options) { + '" src="' + raster.source + '">'; } + options = options || {}; // In order to properly compare pixel by pixel, we need to put each item // into a group with a white background of the united dimensions of the // bounds of both items before rasterizing. - var resolution = options.rasterize === true ? 72 : options.rasterize, + var resolution = options.resolution || 72, actualBounds = actual.strokeBounds, expecedBounds = expected.strokeBounds, bounds = actualBounds.isEmpty() @@ -164,7 +166,7 @@ var compareRasterized = function(actual, expected, message, options) { ? actualBounds : actualBounds.unite(expecedBounds); if (bounds.isEmpty()) { - QUnit.push(true, 'empty', 'empty', message); + QUnit.equal('empty', 'empty', message); return; } var group = actual && expected && new Group({ @@ -179,7 +181,7 @@ var compareRasterized = function(actual, expected, message, options) { actual = rasterize(actual, group, resolution), expected = rasterize(expected, group, resolution); if (!actual || !expected) { - QUnit.pushFailure('Unable to compare rasterized items: ' + + QUnit.push(false, null, null, 'Unable to compare rasterized items: ' + (!actual ? 'actual' : 'expected') + ' item is null', QUnit.stack(2)); } else { @@ -197,16 +199,18 @@ var compareRasterized = function(actual, expected, message, options) { } resemble(actual.getImageData()) .compareTo(expected.getImageData()) + .ignoreAntialiasing() // When working with imageData, this call is synchronous: .onComplete(function(data) { result = data; }); - var tolerance = (options.tolerance || 1e-4) * 100, // percentages... + // Compare with tolerance in percentage... + var tolerance = (options.tolerance || 1e-4) * 100, fixed = ((1 / tolerance) + '').length - 1, identical = result ? 100 - result.misMatchPercentage : 0, reached = identical.toFixed(fixed), hundred = (100).toFixed(fixed), - ok = reached == hundred; - QUnit.push(ok, reached + '% identical', hundred + '% identical', - message); + ok = reached == hundred, + text = reached + '% identical'; + QUnit.push(ok, text, hundred + '% identical', message); if (!ok && result && !isNode) { // Get the right entry for this unit test and assertion, and // replace the results with images @@ -227,29 +231,30 @@ var compareRasterized = function(actual, expected, message, options) { var compareItem = function(actual, expected, message, options, properties) { options = options || {}; if (options.rasterize) { - return compareRasterized(actual, expected, message, options); + comparePixels(actual, expected, message, options); + } else { + if (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.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); } - if (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.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 @@ -373,10 +378,17 @@ var comparators = { }, Raster: function(actual, expected, message, options) { - compareItem(actual, expected, message, options, - ['size', 'width', 'height', 'ppi', 'source', 'image']); - equals(actual.toDataURL(), expected.toDataURL(), - message + '.toDataUrl()'); + var pixels = options && options.pixels, + properties = ['size', 'width', 'height', 'resolution']; + if (!pixels) + properties.push('source', 'image'); + compareItem(actual, expected, message, options, properties); + if (pixels) { + comparePixels(actual, expected, message, options); + } else { + equals(actual.toDataURL(), expected.toDataURL(), + message + '.toDataUrl()'); + } }, Shape: function(actual, expected, message, options) { diff --git a/test/tests/SvgImport.js b/test/tests/SvgImport.js index 5bb5b547..cbd05a7c 100644 --- a/test/tests/SvgImport.js +++ b/test/tests/SvgImport.js @@ -119,3 +119,53 @@ test('Import complex CompoundPath and clone', function() { var item = paper.project.importSVG(svg); equals(item.clone(), item, null, { cloned: true }); }); + +if (!isNode) { + test('Import SVG clipping', function(assert) { + importSVG(assert, 'assets/clipping.svg', + 'The imported SVG item should visually be the same as the rasterized original SVG data.'); + }); +} + +function importSVG(assert, url, message, options) { + var done = assert.async(); + project.importSVG(url, { + onLoad: function(item, svg) { + compareSVG(done, item, svg, message, options); + }, + onError: function(error) { + // TODO: Implement in SvgImport first! + pushFailure('Loading SVG from a valid URL should not give an error.'); + done(); + } + }); +} + +function compareSVG(done, actual, expected, message, options) { + function getItem(item) { + return item instanceof Item + ? item + : typeof item === 'string' + ? new Raster('data:image/svg+xml;base64,' + btoa(item)) + : null; + } + + actual = getItem(actual); + expected = getItem(expected); + actual.position = expected.position; + + if (typeof actual === 'function') { + if (!message) + message = getFunctionMessage(actual); + actual = actual(); + } + + expected.onLoad = function() { + comparePixels(actual, expected, message, Base.set({ + tolerance: 1e-2, + resolution: 72 + }, options)); + done(); + }; +} +