Implement unit tests for SVG Importing, based on visual comparison.

For now, one test for #932
This commit is contained in:
Jürg Lehni 2016-02-01 20:15:37 +01:00
parent 8e25327b09
commit a12e99e387
5 changed files with 189 additions and 39 deletions

View file

@ -76,9 +76,6 @@
</g>
<text transform="matrix(1 0 0 1 83.5002 941.5)" font-family="'Helvetica'" font-size="12">Clipping a group with a path:</text>
<g>
<!--
<rect x="137" y="970" fill="none" stroke="#CECECE" stroke-miterlimit="10" width="113" height="113"/>
-->
<defs>
<rect id="SVGID_7_" x="153" y="970" width="113" height="113"/>
</defs>

View file

@ -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);
},
/**

91
test/assets/clipping.svg Normal file
View file

@ -0,0 +1,91 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
x="0" y="0" width="274.5" height="1040.3" viewBox="81 55.33 274.5 1040.3" xml:space="preserve">
<text transform="matrix(1 0 0 1 83.5002 68.5)" font-family="Helvetica" font-size="12">Clipping a path with a path:</text>
<g>
<g>
<path fill="none" stroke="#CECECE" stroke-miterlimit="10" d="M353,198c0,56.885-46.114,103-103,103c-56.885,0-103-46.115-103-103 S193.115,95,250,95C306.886,95,353,141.115,353,198z"
/>
<rect x="137" y="85" fill="none" stroke="#CECECE" stroke-miterlimit="10"
width="113" height="113" />
</g>
<g>
<defs>
<rect id="SVGID_1_" x="137" y="85" width="113" height="113" />
</defs>
<clipPath id="SVGID_2_">
<use xlink:href="#SVGID_1_" overflow="visible" />
</clipPath>
<circle clip-path="url(#SVGID_2_)" fill="#00FF1E" stroke="#FF0000" stroke-width="10"
stroke-miterlimit="10" cx="250" cy="198" r="103" />
</g>
</g>
<text transform="matrix(1 0 0 1 83.5002 355.5)" font-family="Helvetica" font-size="12">Clipping a compound path with a path:</text>
<g>
<g>
<path fill="none" stroke="#CECECE" stroke-miterlimit="10" d="M353,491.911c0,56.885-46.114,103-103,103 c-56.885,0-103-46.115-103-103s46.115-103,103-103C306.886,388.911,353,435.026,353,491.911z M250,409.512 c-45.508,0-82.4,36.892-82.4,82.399s36.893,82.4,82.4,82.4c45.509,0,82.399-36.894,82.399-82.4 C332.4,446.403,295.509,409.512,250,409.512z"
/>
<rect x="137" y="378.911" fill="none" stroke="#CECECE" stroke-miterlimit="10"
width="113" height="113" />
</g>
<g>
<defs>
<rect id="SVGID_3_" x="137" y="378.911" width="113" height="113" />
</defs>
<clipPath id="SVGID_4_">
<use xlink:href="#SVGID_3_" overflow="visible" />
</clipPath>
<path clip-path="url(#SVGID_4_)" fill="#00FF1E" stroke="#FF0000" stroke-width="10"
stroke-miterlimit="10" d="M353,491.911 c0,56.885-46.114,103-103,103c-56.885,0-103-46.115-103-103s46.115-103,103-103C306.886,388.911,353,435.026,353,491.911z M250,409.512c-45.508,0-82.4,36.892-82.4,82.399s36.893,82.4,82.4,82.4c45.509,0,82.399-36.894,82.399-82.4 C332.4,446.403,295.509,409.512,250,409.512z"
/>
</g>
</g>
<text transform="matrix(1 0 0 1 83.5002 650.5)" font-family="Helvetica" font-size="12">Clipping a path with a compound path:</text>
<g>
<g>
<path fill="none" stroke="#CECECE" stroke-miterlimit="10" d="M353,785.823c0,56.885-46.114,103-103,103 c-56.885,0-103-46.115-103-103c0-56.886,46.115-103,103-103C306.886,682.823,353,728.938,353,785.823z M250,703.423 c-45.508,0-82.4,36.892-82.4,82.4c0,45.508,36.893,82.399,82.4,82.399c45.509,0,82.399-36.893,82.399-82.399 C332.4,740.314,295.509,703.423,250,703.423z"
/>
<rect x="137" y="672.822" fill="none" stroke="#CECECE" stroke-miterlimit="10"
width="113" height="113" />
</g>
<g>
<defs>
<path id="SVGID_5_" d="M353,785.823c0,56.885-46.114,103-103,103c-56.885,0-103-46.115-103-103c0-56.886,46.115-103,103-103 C306.886,682.823,353,728.938,353,785.823z M250,703.423c-45.508,0-82.4,36.892-82.4,82.4c0,45.508,36.893,82.399,82.4,82.399 c45.509,0,82.399-36.893,82.399-82.399C332.4,740.314,295.509,703.423,250,703.423z"
/>
</defs>
<clipPath id="SVGID_6_">
<use xlink:href="#SVGID_5_" overflow="visible" />
</clipPath>
<rect x="137" y="672.822" clip-path="url(#SVGID_6_)" fill="#00FF1E" stroke="#FF0000"
stroke-width="10" stroke-miterlimit="10" width="113" height="113" />
</g>
</g>
<text transform="matrix(1 0 0 1 83.5002 941.5)" font-family="Helvetica" font-size="12">Clipping a group with a path:</text>
<g>
<defs>
<rect id="SVGID_7_" x="153" y="970" width="113" height="113" />
</defs>
<clipPath id="SVGID_8_">
<use xlink:href="#SVGID_7_" overflow="visible" />
</clipPath>
<g clip-path="url(#SVGID_8_)">
<circle fill="#00FF1E" stroke="#FF0000" stroke-width="10" stroke-miterlimit="10"
cx="156.5" cy="966" r="21.5" />
<circle fill="#00FF1E" stroke="#FF0000" stroke-width="10" stroke-miterlimit="10"
cx="209.5" cy="966" r="21.5" />
<circle fill="#00FF1E" stroke="#FF0000" stroke-width="10" stroke-miterlimit="10"
cx="262.5" cy="966" r="21.5" />
<circle fill="#00FF1E" stroke="#FF0000" stroke-width="10" stroke-miterlimit="10"
cx="156.5" cy="1019" r="21.5" />
<circle fill="#00FF1E" stroke="#FF0000" stroke-width="10" stroke-miterlimit="10"
cx="209.5" cy="1019" r="21.5" />
<circle fill="#00FF1E" stroke="#FF0000" stroke-width="10" stroke-miterlimit="10"
cx="262.5" cy="1019" r="21.5" />
<circle fill="#00FF1E" stroke="#FF0000" stroke-width="10" stroke-miterlimit="10"
cx="156.5" cy="1072" r="21.5" />
<circle fill="#00FF1E" stroke="#FF0000" stroke-width="10" stroke-miterlimit="10"
cx="209.5" cy="1072" r="21.5" />
<circle fill="#00FF1E" stroke="#FF0000" stroke-width="10" stroke-miterlimit="10"
cx="262.5" cy="1072" r="21.5" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.5 KiB

View file

@ -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) {

View file

@ -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();
};
}