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>
|
</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>
|
<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>
|
<g>
|
||||||
<!--
|
|
||||||
<rect x="137" y="970" fill="none" stroke="#CECECE" stroke-miterlimit="10" width="113" height="113"/>
|
|
||||||
-->
|
|
||||||
<defs>
|
<defs>
|
||||||
<rect id="SVGID_7_" x="153" y="970" width="113" height="113"/>
|
<rect id="SVGID_7_" x="153" y="970" width="113" height="113"/>
|
||||||
</defs>
|
</defs>
|
||||||
|
|
|
@ -373,10 +373,10 @@ var Raster = Item.extend(/** @lends Raster# */{
|
||||||
image = document.getElementById(src) || new window.Image();
|
image = document.getElementById(src) || new window.Image();
|
||||||
if (crossOrigin)
|
if (crossOrigin)
|
||||||
image.crossOrigin = crossOrigin;
|
image.crossOrigin = crossOrigin;
|
||||||
this.setImage(image);
|
|
||||||
// A new image created above? Set the source now.
|
// A new image created above? Set the source now.
|
||||||
if (!image.src)
|
if (!image.src)
|
||||||
image.src = 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',
|
var isNode = typeof global === 'object',
|
||||||
|
isPhantom = !!window.callPhantom,
|
||||||
|
isBrowser = !isNode && !isPhantom,
|
||||||
root;
|
root;
|
||||||
|
|
||||||
if (isNode) {
|
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) {
|
function rasterize(item, group, resolution) {
|
||||||
var raster = null;
|
var raster = null;
|
||||||
if (group) {
|
if (group) {
|
||||||
|
@ -152,10 +153,11 @@ var compareRasterized = function(actual, expected, message, options) {
|
||||||
+ '" src="' + raster.source + '">';
|
+ '" src="' + raster.source + '">';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
options = options || {};
|
||||||
// In order to properly compare pixel by pixel, we need to put each item
|
// 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
|
// into a group with a white background of the united dimensions of the
|
||||||
// bounds of both items before rasterizing.
|
// bounds of both items before rasterizing.
|
||||||
var resolution = options.rasterize === true ? 72 : options.rasterize,
|
var resolution = options.resolution || 72,
|
||||||
actualBounds = actual.strokeBounds,
|
actualBounds = actual.strokeBounds,
|
||||||
expecedBounds = expected.strokeBounds,
|
expecedBounds = expected.strokeBounds,
|
||||||
bounds = actualBounds.isEmpty()
|
bounds = actualBounds.isEmpty()
|
||||||
|
@ -164,7 +166,7 @@ var compareRasterized = function(actual, expected, message, options) {
|
||||||
? actualBounds
|
? actualBounds
|
||||||
: actualBounds.unite(expecedBounds);
|
: actualBounds.unite(expecedBounds);
|
||||||
if (bounds.isEmpty()) {
|
if (bounds.isEmpty()) {
|
||||||
QUnit.push(true, 'empty', 'empty', message);
|
QUnit.equal('empty', 'empty', message);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var group = actual && expected && new Group({
|
var group = actual && expected && new Group({
|
||||||
|
@ -179,7 +181,7 @@ var compareRasterized = function(actual, expected, message, options) {
|
||||||
actual = rasterize(actual, group, resolution),
|
actual = rasterize(actual, group, resolution),
|
||||||
expected = rasterize(expected, group, resolution);
|
expected = rasterize(expected, group, resolution);
|
||||||
if (!actual || !expected) {
|
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',
|
(!actual ? 'actual' : 'expected') + ' item is null',
|
||||||
QUnit.stack(2));
|
QUnit.stack(2));
|
||||||
} else {
|
} else {
|
||||||
|
@ -197,16 +199,18 @@ var compareRasterized = function(actual, expected, message, options) {
|
||||||
}
|
}
|
||||||
resemble(actual.getImageData())
|
resemble(actual.getImageData())
|
||||||
.compareTo(expected.getImageData())
|
.compareTo(expected.getImageData())
|
||||||
|
.ignoreAntialiasing()
|
||||||
// When working with imageData, this call is synchronous:
|
// When working with imageData, this call is synchronous:
|
||||||
.onComplete(function(data) { result = data; });
|
.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,
|
fixed = ((1 / tolerance) + '').length - 1,
|
||||||
identical = result ? 100 - result.misMatchPercentage : 0,
|
identical = result ? 100 - result.misMatchPercentage : 0,
|
||||||
reached = identical.toFixed(fixed),
|
reached = identical.toFixed(fixed),
|
||||||
hundred = (100).toFixed(fixed),
|
hundred = (100).toFixed(fixed),
|
||||||
ok = reached == hundred;
|
ok = reached == hundred,
|
||||||
QUnit.push(ok, reached + '% identical', hundred + '% identical',
|
text = reached + '% identical';
|
||||||
message);
|
QUnit.push(ok, text, hundred + '% identical', message);
|
||||||
if (!ok && result && !isNode) {
|
if (!ok && result && !isNode) {
|
||||||
// Get the right entry for this unit test and assertion, and
|
// Get the right entry for this unit test and assertion, and
|
||||||
// replace the results with images
|
// replace the results with images
|
||||||
|
@ -227,29 +231,30 @@ var compareRasterized = function(actual, expected, message, options) {
|
||||||
var compareItem = function(actual, expected, message, options, properties) {
|
var compareItem = function(actual, expected, message, options, properties) {
|
||||||
options = options || {};
|
options = options || {};
|
||||||
if (options.rasterize) {
|
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
|
// A list of comparator functions, based on `expected` type. See equals() for
|
||||||
|
@ -373,10 +378,17 @@ var comparators = {
|
||||||
},
|
},
|
||||||
|
|
||||||
Raster: function(actual, expected, message, options) {
|
Raster: function(actual, expected, message, options) {
|
||||||
compareItem(actual, expected, message, options,
|
var pixels = options && options.pixels,
|
||||||
['size', 'width', 'height', 'ppi', 'source', 'image']);
|
properties = ['size', 'width', 'height', 'resolution'];
|
||||||
equals(actual.toDataURL(), expected.toDataURL(),
|
if (!pixels)
|
||||||
message + '.toDataUrl()');
|
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) {
|
Shape: function(actual, expected, message, options) {
|
||||||
|
|
|
@ -119,3 +119,53 @@ test('Import complex CompoundPath and clone', function() {
|
||||||
var item = paper.project.importSVG(svg);
|
var item = paper.project.importSVG(svg);
|
||||||
equals(item.clone(), item, null, { cloned: true });
|
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