mirror of
https://github.com/scratchfoundation/paper.js.git
synced 2024-12-29 09:22:22 -05:00
Implement unit tests for SVG Importing, based on visual comparison.
For now, one test for #932
This commit is contained in:
parent
8e25327b09
commit
a12e99e387
5 changed files with 189 additions and 39 deletions
|
@ -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>
|
||||
|
|
|
@ -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
91
test/assets/clipping.svg
Normal 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 |
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue