Better visual tests, test passing, more SVGs (#5076)
* improving golddens * more tests * test passing * removed dist * fixed travis * make visual running in browser too * more travis * fixed travis * fixed travis * more coffee
11
.travis.yml
|
|
@ -30,6 +30,8 @@ jobs:
|
|||
fast_finish: true
|
||||
allow_failures:
|
||||
- env: LAUNCHER=Node CANFAIL=TRUE
|
||||
- env: LAUNCHER=Firefox CANFAIL=TRUE
|
||||
- env: LAUNCHER=Chrome CANFAIL=TRUE
|
||||
include:
|
||||
- stage: Linting and Building
|
||||
env: STEP=LINT
|
||||
|
|
@ -70,8 +72,15 @@ jobs:
|
|||
node_js: "4"
|
||||
- stage: Visual Tests
|
||||
node_js: "8"
|
||||
env: LAUNCHER=Node CANFAIL=TRUE
|
||||
script: npm run test:visual
|
||||
- stage: Visual Tests
|
||||
env: LAUNCHER=Chrome CANFAIL=TRUE
|
||||
install: npm install testem@1.18.4 qunit@2.4.1
|
||||
script: npm run build:fast && testem ci --port 8080 -f testem-visual.json -l $LAUNCHER
|
||||
- stage: Visual Tests
|
||||
env: LAUNCHER=Firefox CANFAIL=TRUE
|
||||
install: npm install testem@1.18.4 qunit@2.4.1
|
||||
script: npm run build:fast && testem ci --port 8080 -f testem-visual.json -l $LAUNCHER
|
||||
|
||||
script: 'npm run build:fast && testem ci --port 8080 -f testem.json -l $LAUNCHER'
|
||||
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@
|
|||
"export_tests_to_site": "cp test/unit/*.js ../fabricjs.com/test/unit",
|
||||
"all": "npm run build && npm run test && npm run lint && npm run lint_tests && npm run export_dist_to_site && npm run export_tests_to_site",
|
||||
"testem": "testem .",
|
||||
"testem:visual": "testem --file testem-visual.json",
|
||||
"testem:ci": "testem ci"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
|
|
|
|||
|
|
@ -1,28 +0,0 @@
|
|||
var testrunner = require('node-qunit');
|
||||
|
||||
testrunner.options.log.summary = true;
|
||||
testrunner.options.log.tests = false;
|
||||
testrunner.options.log.assertions = false;
|
||||
testrunner.options.log.coverage = false;
|
||||
|
||||
testrunner.options.coverage = true;
|
||||
testrunner.options.maxBlockDuration = 120000;
|
||||
|
||||
testrunner.run({
|
||||
deps: './test/fixtures/test_script.js',
|
||||
code: './dist/fabric.js',
|
||||
tests: [
|
||||
'./test/visual/svg_import.js',
|
||||
],
|
||||
// tests: ['./test/unit/object_clipPath.js',],
|
||||
}, function(err, report) {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
process.exit(1);
|
||||
}
|
||||
if(report.failed > 0){
|
||||
process.on('exit', function() {
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
});
|
||||
156
test/lib/pixelmatch.js
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
'use strict';
|
||||
|
||||
function pixelmatch(img1, img2, output, width, height, options) {
|
||||
|
||||
if (!options) options = {};
|
||||
|
||||
var threshold = options.threshold === undefined ? 0.1 : options.threshold;
|
||||
|
||||
// maximum acceptable square distance between two colors;
|
||||
// 35215 is the maximum possible value for the YIQ difference metric
|
||||
var maxDelta = 35215 * threshold * threshold,
|
||||
diff = 0;
|
||||
|
||||
// compare each pixel of one image against the other one
|
||||
for (var y = 0; y < height; y++) {
|
||||
for (var x = 0; x < width; x++) {
|
||||
|
||||
var pos = (y * width + x) * 4;
|
||||
|
||||
// squared YUV distance between colors at this pixel position
|
||||
var delta = colorDelta(img1, img2, pos, pos);
|
||||
|
||||
// the color difference is above the threshold
|
||||
if (delta > maxDelta) {
|
||||
// check it's a real rendering difference or just anti-aliasing
|
||||
if (!options.includeAA && (antialiased(img1, x, y, width, height, img2) ||
|
||||
antialiased(img2, x, y, width, height, img1))) {
|
||||
// one of the pixels is anti-aliasing; draw as yellow and do not count as difference
|
||||
if (output) drawPixel(output, pos, 255, 255, 0);
|
||||
|
||||
} else {
|
||||
// found substantial difference not caused by anti-aliasing; draw it as red
|
||||
if (output) drawPixel(output, pos, 255, 0, 0);
|
||||
diff++;
|
||||
}
|
||||
|
||||
} else if (output) {
|
||||
// pixels are similar; draw background as grayscale image blended with white
|
||||
var val = blend(grayPixel(img1, pos), 0.1);
|
||||
drawPixel(output, pos, val, val, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// return the number of different pixels
|
||||
return diff;
|
||||
}
|
||||
|
||||
// check if a pixel is likely a part of anti-aliasing;
|
||||
// based on "Anti-aliased Pixel and Intensity Slope Detector" paper by V. Vysniauskas, 2009
|
||||
|
||||
function antialiased(img, x1, y1, width, height, img2) {
|
||||
var x0 = Math.max(x1 - 1, 0),
|
||||
y0 = Math.max(y1 - 1, 0),
|
||||
x2 = Math.min(x1 + 1, width - 1),
|
||||
y2 = Math.min(y1 + 1, height - 1),
|
||||
pos = (y1 * width + x1) * 4,
|
||||
zeroes = 0,
|
||||
positives = 0,
|
||||
negatives = 0,
|
||||
min = 0,
|
||||
max = 0,
|
||||
minX, minY, maxX, maxY;
|
||||
|
||||
// go through 8 adjacent pixels
|
||||
for (var x = x0; x <= x2; x++) {
|
||||
for (var y = y0; y <= y2; y++) {
|
||||
if (x === x1 && y === y1) continue;
|
||||
|
||||
// brightness delta between the center pixel and adjacent one
|
||||
var delta = colorDelta(img, img, pos, (y * width + x) * 4, true);
|
||||
|
||||
// count the number of equal, darker and brighter adjacent pixels
|
||||
if (delta === 0) zeroes++;
|
||||
else if (delta < 0) negatives++;
|
||||
else if (delta > 0) positives++;
|
||||
|
||||
// if found more than 2 equal siblings, it's definitely not anti-aliasing
|
||||
if (zeroes > 2) return false;
|
||||
|
||||
if (!img2) continue;
|
||||
|
||||
// remember the darkest pixel
|
||||
if (delta < min) {
|
||||
min = delta;
|
||||
minX = x;
|
||||
minY = y;
|
||||
}
|
||||
// remember the brightest pixel
|
||||
if (delta > max) {
|
||||
max = delta;
|
||||
maxX = x;
|
||||
maxY = y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!img2) return true;
|
||||
|
||||
// if there are no both darker and brighter pixels among siblings, it's not anti-aliasing
|
||||
if (negatives === 0 || positives === 0) return false;
|
||||
|
||||
// if either the darkest or the brightest pixel has more than 2 equal siblings in both images
|
||||
// (definitely not anti-aliased), this pixel is anti-aliased
|
||||
return (!antialiased(img, minX, minY, width, height) && !antialiased(img2, minX, minY, width, height)) ||
|
||||
(!antialiased(img, maxX, maxY, width, height) && !antialiased(img2, maxX, maxY, width, height));
|
||||
}
|
||||
|
||||
// calculate color difference according to the paper "Measuring perceived color difference
|
||||
// using YIQ NTSC transmission color space in mobile applications" by Y. Kotsarenko and F. Ramos
|
||||
|
||||
function colorDelta(img1, img2, k, m, yOnly) {
|
||||
var a1 = img1[k + 3] / 255,
|
||||
a2 = img2[m + 3] / 255,
|
||||
|
||||
r1 = blend(img1[k + 0], a1),
|
||||
g1 = blend(img1[k + 1], a1),
|
||||
b1 = blend(img1[k + 2], a1),
|
||||
|
||||
r2 = blend(img2[m + 0], a2),
|
||||
g2 = blend(img2[m + 1], a2),
|
||||
b2 = blend(img2[m + 2], a2),
|
||||
|
||||
y = rgb2y(r1, g1, b1) - rgb2y(r2, g2, b2);
|
||||
|
||||
if (yOnly) return y; // brightness difference only
|
||||
|
||||
var i = rgb2i(r1, g1, b1) - rgb2i(r2, g2, b2),
|
||||
q = rgb2q(r1, g1, b1) - rgb2q(r2, g2, b2);
|
||||
|
||||
return 0.5053 * y * y + 0.299 * i * i + 0.1957 * q * q;
|
||||
}
|
||||
|
||||
function rgb2y(r, g, b) { return r * 0.29889531 + g * 0.58662247 + b * 0.11448223; }
|
||||
function rgb2i(r, g, b) { return r * 0.59597799 - g * 0.27417610 - b * 0.32180189; }
|
||||
function rgb2q(r, g, b) { return r * 0.21147017 - g * 0.52261711 + b * 0.31114694; }
|
||||
|
||||
// blend semi-transparent color with white
|
||||
function blend(c, a) {
|
||||
return 255 + (c - 255) * a;
|
||||
}
|
||||
|
||||
function drawPixel(output, pos, r, g, b) {
|
||||
output[pos + 0] = r;
|
||||
output[pos + 1] = g;
|
||||
output[pos + 2] = b;
|
||||
output[pos + 3] = 255;
|
||||
}
|
||||
|
||||
function grayPixel(img, i) {
|
||||
var a = img[i + 3] / 255,
|
||||
r = blend(img[i + 0], a),
|
||||
g = blend(img[i + 1], a),
|
||||
b = blend(img[i + 2], a);
|
||||
return rgb2y(r, g, b);
|
||||
}
|
||||
13
test/visual/assets/svg_linear_1.svg
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" standalone="no" ?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="105" height="77" xml:space="preserve">
|
||||
<desc>Created with Fabric.js 1.0.12
|
||||
</desc>
|
||||
<linearGradient id="linear" gradientUnits="userSpaceOnUse" x1="0" y1="0" x2="0" y2="77" >
|
||||
<stop offset="0" style="stop-color:#cc5232"/>
|
||||
<stop offset="0.3" style="stop-color:#b0c72d"/>
|
||||
<stop offset="0.6" style="stop-color:aqua"/>
|
||||
<stop offset="1" style="stop-color:teal"/>
|
||||
</linearGradient>
|
||||
<rect x="0" y="0" rx="0" ry="0" width="105" height="77" style="stroke: none; stroke-width: 0; stroke-dasharray: ; fill: url(#linear); opacity: 1;"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 796 B |
11
test/visual/assets/svg_linear_2.svg
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" standalone="no" ?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="105" height="77" xml:space="preserve">
|
||||
<desc>Created with Fabric.js 1.0.12
|
||||
</desc>
|
||||
<linearGradient id="linear" gradientUnits="userSpaceOnUse" x1="0" y1="0" x2="0" y2="77" >
|
||||
<stop offset="0%" style="stop-color:#cc5232;stop-opacity:0"/>
|
||||
<stop offset="100%" style="stop-color:#b0c72d;stop-opacity:1"/>
|
||||
</linearGradient>
|
||||
<rect x="0" y="0" rx="0" ry="0" width="105" height="77" style="stroke: none; stroke-width: 0; stroke-dasharray: ; fill: url(#linear); opacity: 1;"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 732 B |
11
test/visual/assets/svg_linear_3.svg
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" standalone="no" ?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="105" height="77" xml:space="preserve">
|
||||
<desc>Created with Fabric.js 1.0.12
|
||||
</desc>
|
||||
<linearGradient id="linear" gradientUnits="userSpaceOnUse" x1="0" y1="0" x2="0" y2="77" >
|
||||
<stop offset="0%" style="stop-color:blue;stop-opacity:0"/>
|
||||
<stop offset="100%" style="stop-color:red;stop-opacity:1"/>
|
||||
</linearGradient>
|
||||
<rect x="0" y="0" rx="0" ry="0" width="105" height="77" style="stroke: none; stroke-width: 0; stroke-dasharray: ; fill: url(#linear); opacity: 1;"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 725 B |
20
test/visual/assets/svg_linear_4.svg
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" standalone="no"?>
|
||||
|
||||
<svg width="120" height="240" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient id="Gradient1" gradientUnits="objectBoundingBox">
|
||||
<stop offset="0%" stop-color="red"/>
|
||||
<stop offset="50%" stop-color="black" stop-opacity="0"/>
|
||||
<stop offset="100%" stop-color="blue"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="Gradient2" x1="0" x2="0" y1="0" y2="100%">
|
||||
<stop offset="0%" stop-color="red"/>
|
||||
<stop offset="50%" stop-color="black" stop-opacity="0"/>
|
||||
<stop offset="100%" stop-color="blue"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<rect id="rect1" x="10" y="10" rx="15" ry="15" width="100" height="100" fill="url(#Gradient1)"/>
|
||||
<rect x="10" y="120" rx="15" ry="15" width="100" height="100" fill="url(#Gradient2)"/>
|
||||
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 845 B |
13
test/visual/assets/svg_linear_5.svg
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" standalone="no"?>
|
||||
|
||||
<svg width="120" height="120" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient id="Gradient2" x1="0" x2="0" y1="0" y2="100%">
|
||||
<stop offset="0%" stop-color="red"/>
|
||||
<stop offset="50%" stop-color="black" stop-opacity="0.5"/>
|
||||
<stop offset="100%" stop-color="blue"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect x="10" y="10" rx="15" ry="15" width="100" height="100" fill="url(#Gradient2)"/>
|
||||
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 486 B |
13
test/visual/assets/svg_linear_6.svg
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" standalone="no" ?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="105" height="77" xml:space="preserve">
|
||||
<desc>Created with Fabric.js 1.0.12
|
||||
</desc>
|
||||
<linearGradient id="linear" gradientUnits="userSpaceOnUse" x1="52.5" y1="0" x2="0" y2="77" >
|
||||
<stop offset="0" style="stop-color:#cc5232"/>
|
||||
<stop offset="0.3" style="stop-color:#b0c72d"/>
|
||||
<stop offset="0.6" style="stop-color:rgba(0,0,153,0.5)"/>
|
||||
<stop offset="1" style="stop-color:#009999" stop-opacity="0.9"/>
|
||||
</linearGradient>
|
||||
<rect x="0" y="0" rx="0" ry="0" width="105" height="77" style="stroke: none; stroke-width: 0; stroke-dasharray: ; fill: url(#linear); opacity: 1;"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 834 B |
13
test/visual/assets/svg_linear_7.svg
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" standalone="no" ?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="105" height="77" xml:space="preserve">
|
||||
<desc>Created with Fabric.js 1.0.12
|
||||
</desc>
|
||||
<linearGradient id="linear" gradientUnits="userSpaceOnUse" x1="52.5" y1="0" x2="0" y2="77" >
|
||||
<stop offset="0" style="stop-color:#cc5232"/>
|
||||
<stop offset="0.3" style="stop-color:#b0c72d; stop-opacity: 0.7;"/>
|
||||
<stop offset="0.6" style="stop-color:rgba(0,0,153,0.5)" stop-opacity="0.5"/>
|
||||
<stop offset="1" style="stop-color:#009999" stop-opacity="0.9"/>
|
||||
</linearGradient>
|
||||
<rect x="0" y="0" rx="0" ry="0" width="105" height="77" style="stroke: none; stroke-width: 0; stroke-dasharray: ; fill: url(#linear); opacity: 1;"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 873 B |
13
test/visual/assets/svg_linear_8.svg
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<defs>
|
||||
<linearGradient id="three_stops">
|
||||
<stop offset="0%" style="stop-color: #ffcc00;"/>
|
||||
<stop offset="33.3%" style="stop-color: #cc6699"/>
|
||||
<stop offset="100%" style="stop-color: #66cc99;"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<rect x="0" y="0" width="200" height="100"
|
||||
style="fill: url(#three_stops); stroke: black;"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 427 B |
11
test/visual/assets/svg_radial_1.svg
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="220" height="200" version="1.1">
|
||||
<defs>
|
||||
<radialGradient id="grey_blue" cx="20%" cy="40%" r="50%" fx="50%" fy="50%">
|
||||
<stop offset="0%" style="stop-color:rgb(200,200,200);stop-opacity:0"/>
|
||||
<stop offset="100%" style="stop-color:rgb(0,0,255);stop-opacity:1"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
<ellipse cx="110" cy="100" rx="110" ry="100" style="fill:url(#grey_blue)"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 552 B |
16
test/visual/assets/svg_radial_10.svg
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
|
||||
<defs>
|
||||
<radialGradient id="myRadialGradient4"
|
||||
fx="5%" fy="5%" r="65%"
|
||||
spreadMethod="pad">
|
||||
<stop offset="0%" stop-color="#00ee00" stop-opacity="1"/>
|
||||
<stop offset="100%" stop-color="#006600" stop-opacity="1" />
|
||||
</radialGradient>
|
||||
</defs>
|
||||
|
||||
<rect x="0" y="0" width="100" height="100" rx="10" ry="10"
|
||||
style="fill:url(#myRadialGradient4);
|
||||
stroke: #005000; stroke-width: 3;" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 571 B |
13
test/visual/assets/svg_radial_11.svg
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<defs>
|
||||
<radialGradient id="three_stops">
|
||||
<stop offset="0%" style="stop-color: #f96;"/>
|
||||
<stop offset="50%" style="stop-color: #9c9;"/>
|
||||
<stop offset="100%" style="stop-color: #906;"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
|
||||
<rect x="0" y="0" width="100" height="100"
|
||||
style="fill: url(#three_stops); stroke: black;"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 417 B |
14
test/visual/assets/svg_radial_12.svg
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<defs>
|
||||
<radialGradient id="center_origin"
|
||||
cx="0" cy="0" r="100%">
|
||||
<stop offset="0%" style="stop-color: #f96;"/>
|
||||
<stop offset="50%" style="stop-color: #9c9;"/>
|
||||
<stop offset="100%" style="stop-color: #906;"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
|
||||
<rect x="0" y="0" width="100" height="100"
|
||||
style="fill: url(#center_origin); stroke: black;"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 448 B |
14
test/visual/assets/svg_radial_13.svg
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<defs>
|
||||
<radialGradient id="focal_set"
|
||||
cx="0" cy="0" fx="30%" fy="30%" r="100%">
|
||||
<stop offset="0%" style="stop-color: #f96;"/>
|
||||
<stop offset="50%" style="stop-color: #9c9;"/>
|
||||
<stop offset="100%" style="stop-color: #906;"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
|
||||
<rect x="0" y="0" width="100" height="100"
|
||||
style="fill: url(#focal_set); stroke: black;"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 458 B |
12
test/visual/assets/svg_radial_2.svg
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
|
||||
<svg version="1.1" width="360" height="200" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<radialGradient id="grey_blue" cx="50%" cy="50%" r="50%" fx="50%" fy="50%">
|
||||
<stop offset="0%" style="stop-color:rgb(200,200,200); stop-opacity:0"/>
|
||||
<stop offset="100%" style="stop-color:rgb(0,0,255); stop-opacity:1"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
<ellipse cx="180" cy="100" rx="180" ry="100" style="fill:url(#grey_blue)"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 555 B |
13
test/visual/assets/svg_radial_3.svg
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" standalone="no" ?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="220" height="300" version="1.1">
|
||||
<defs>
|
||||
<radialGradient id="grey_blue" cx="50%" cy="50%" r="50%" fx="50%" fy="50%">
|
||||
<stop offset="0%" style="stop-color:red;stop-opacity:0.8"/>
|
||||
<stop offset="30%" style="stop-color:#0000ff;"/>
|
||||
<stop offset="60%" style="stop-color:rgba(0,153,153,0.5);"/>
|
||||
<stop offset="100%" style="stop-color:blue;stop-opacity:1"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
<ellipse cx="110" cy="150" rx="110" ry="150" style="fill:url(#grey_blue)"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 648 B |
11
test/visual/assets/svg_radial_4.svg
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" standalone="no" ?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="220" height="200" version="1.1">
|
||||
<defs>
|
||||
<radialGradient id="grey_blue" cx="50%" cy="50%" r="50%" fx="20%" fy="40%">
|
||||
<stop offset="0%" style="stop-color:gray;stop-opacity:0"/>
|
||||
<stop offset="100%" style="stop-color:blue;stop-opacity:1"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
<ellipse cx="110" cy="100" rx="110" ry="100" style="fill:url(#grey_blue)"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 533 B |
10
test/visual/assets/svg_radial_5.svg
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="220" height="200" version="1.1">
|
||||
<defs>
|
||||
<radialGradient id="grey_blue" cx="50%" cy="50%" r="50%" fx="50%" fy="50%">
|
||||
<stop offset="0%" style="stop-color:#808080;stop-opacity:0"></stop>
|
||||
<stop offset="50%" style="stop-color:#009999;stop-opacity:0.6"></stop>
|
||||
<stop offset="100%" style="stop-color:#0000FF;stop-opacity:1"></stop>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
<ellipse cx="110" cy="100" rx="110" ry="100" style="fill:url(#grey_blue)"></ellipse>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 495 B |
11
test/visual/assets/svg_radial_6.svg
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" standalone="no"?>
|
||||
<svg width="100" height="100" version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<radialGradient id="Gradient1">
|
||||
<stop offset="0%" stop-color="red"/>
|
||||
<stop offset="100%" stop-color="blue"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
<rect x="0" y="0" rx="15" ry="15" width="100" height="100" fill="url(#Gradient1)"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 386 B |
11
test/visual/assets/svg_radial_8.svg
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="220" height="200" version="1.1">
|
||||
<defs>
|
||||
<radialGradient id="grey_blue" cx="20%" cy="40%" r="50%" fx="50%" fy="50%">
|
||||
<stop offset="0%" style="stop-color:rgb(200,200,200);stop-opacity:0"/>
|
||||
<stop offset="100%" style="stop-color:rgba(0,120,120,0.2);stop-opacity:1"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
<ellipse cx="110" cy="100" rx="110" ry="100" style="fill:url(#grey_blue)"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 559 B |
10
test/visual/assets/svg_radial_9.svg
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" standalone="no"?>
|
||||
<svg width="100" height="100" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<radialGradient id="Gradient2" cx="25%" cy="25%" r="25%">
|
||||
<stop offset="0%" stop-color="red"/>
|
||||
<stop offset="100%" stop-color="blue"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
<rect x="0" y="0" rx="15" ry="15" width="100" height="100" fill="url(#Gradient2)"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 404 B |
|
|
@ -7,9 +7,11 @@
|
|||
<desc>Example stroke01 - stroke-width</desc>
|
||||
|
||||
<g fill="none" stroke="black">
|
||||
<path stroke-width="1" d="M5 5 l215 0" />
|
||||
<path stroke-width="1" d="M5 9.5 l215 0" />
|
||||
<path stroke-width="2" d="M5 20 l215 0" />
|
||||
<path stroke-width="4" d="M5 40 l215 0" />
|
||||
<path stroke-width="6" d="M5 60 l215 0" />
|
||||
<path style="stroke: #000; stroke-width: 10;" d="M5 80 l215 0" />
|
||||
</g>
|
||||
</svg>
|
||||
</svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 541 B After Width: | Height: | Size: 639 B |
|
|
@ -7,9 +7,9 @@
|
|||
<desc>Example stroke5 - stroke-dasharray</desc>
|
||||
|
||||
<g fill="none" stroke="black" stroke-width="3">
|
||||
<path stroke-dasharray="5,5" d="M5 20 l215 0"/>
|
||||
<path stroke-dasharray="10,10" d="M5 40 l215 0"/>
|
||||
<path stroke-dasharray="15,10,5,5,5,10" d="M5 60 l215 0"/>
|
||||
<path style="stroke-dasharray:10,10,2.5,10;" d="M5 80 l215 0"/>
|
||||
<path stroke-dasharray="5,5" d="M5 20.5 l215 0"/>
|
||||
<path stroke-dasharray="10,10" d="M5 40.5 l215 0"/>
|
||||
<path stroke-dasharray="15,10,5,5,5,10" d="M5 60.5 l215 0"/>
|
||||
<path style="stroke-dasharray:10,10,2.5,10;" d="M5 80.5 l215 0"/>
|
||||
</g>
|
||||
</svg>
|
||||
</svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 576 B After Width: | Height: | Size: 585 B |
BIN
test/visual/golden/svg_linear_1.png
Normal file
|
After Width: | Height: | Size: 595 B |
BIN
test/visual/golden/svg_linear_2.png
Normal file
|
After Width: | Height: | Size: 722 B |
BIN
test/visual/golden/svg_linear_3.png
Normal file
|
After Width: | Height: | Size: 703 B |
BIN
test/visual/golden/svg_linear_4.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
test/visual/golden/svg_linear_5.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
test/visual/golden/svg_linear_6.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
test/visual/golden/svg_linear_7.png
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
BIN
test/visual/golden/svg_linear_8.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
test/visual/golden/svg_radial_1.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
test/visual/golden/svg_radial_10.png
Normal file
|
After Width: | Height: | Size: 6 KiB |
BIN
test/visual/golden/svg_radial_11.png
Normal file
|
After Width: | Height: | Size: 9.1 KiB |
BIN
test/visual/golden/svg_radial_12.png
Normal file
|
After Width: | Height: | Size: 7.5 KiB |
BIN
test/visual/golden/svg_radial_13.png
Normal file
|
After Width: | Height: | Size: 8.4 KiB |
BIN
test/visual/golden/svg_radial_2.png
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
test/visual/golden/svg_radial_3.png
Normal file
|
After Width: | Height: | Size: 65 KiB |
BIN
test/visual/golden/svg_radial_4.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
test/visual/golden/svg_radial_5.png
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
test/visual/golden/svg_radial_6.png
Normal file
|
After Width: | Height: | Size: 6.3 KiB |
BIN
test/visual/golden/svg_radial_8.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
test/visual/golden/svg_radial_9.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 754 B After Width: | Height: | Size: 777 B |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1,006 B |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 778 B After Width: | Height: | Size: 802 B |
|
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 947 B After Width: | Height: | Size: 943 B |
|
Before Width: | Height: | Size: 811 B After Width: | Height: | Size: 755 B |
46
test/visual/goldenMaker.html
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
<html>
|
||||
<body>
|
||||
<input type="file" id="fileselect" name="fileselect[]" multiple="multiple" />
|
||||
<div id="c" ></div>
|
||||
</body>
|
||||
<script>
|
||||
var div = document.getElementById('c');
|
||||
var fileselect = document.getElementById('fileselect');
|
||||
|
||||
function downloadHandler(e) {
|
||||
var url = e.target.toDataURL();
|
||||
var a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = e.target.getAttribute('file-name').replace('.svg', '.png');
|
||||
a.click();
|
||||
}
|
||||
|
||||
function handleFile(f) {
|
||||
var reader = new FileReader();
|
||||
var canvas = document.createElement('canvas');
|
||||
div.appendChild(canvas)
|
||||
reader.onload = function(e) {
|
||||
var image = new Image();
|
||||
image.onload = function() {
|
||||
canvas.setAttribute('file-name', f.name)
|
||||
canvas.addEventListener('click', downloadHandler);
|
||||
canvas.width = image.width;
|
||||
canvas.height = image.height;
|
||||
canvas.getContext('2d').drawImage(image, 0, 0);
|
||||
}
|
||||
image.src = e.target.result;
|
||||
}
|
||||
reader.readAsDataURL(f);
|
||||
}
|
||||
|
||||
|
||||
function FileSelectHandler(e) {
|
||||
var files = e.target.files || e.dataTransfer.files;
|
||||
// process all File objects
|
||||
for (var i = 0, f; f = files[i]; i++) {
|
||||
handleFile(f);
|
||||
}
|
||||
}
|
||||
fileselect.addEventListener("change", FileSelectHandler, false);
|
||||
</script>
|
||||
</html>
|
||||
|
|
@ -1,10 +1,13 @@
|
|||
(function(global) {
|
||||
var fs = global.fs;
|
||||
var pixelmatch = global.pixelmatch;
|
||||
(function() {
|
||||
var _pixelMatch = pixelmatch;
|
||||
if (fabric.isLikelyNode) {
|
||||
var fs = global.fs;
|
||||
_pixelMatch = global.pixelmatch;
|
||||
}
|
||||
var fabricCanvas = this.canvas = new fabric.Canvas(null, {enableRetinaScaling: false, renderOnAddRemove: false});
|
||||
var pixelmatchOptions = {
|
||||
includeAA: true,
|
||||
threshold: 0.05,
|
||||
threshold: 0.1
|
||||
};
|
||||
|
||||
function getAbsolutePath(path) {
|
||||
|
|
@ -17,19 +20,25 @@
|
|||
return src;
|
||||
}
|
||||
|
||||
function getAsset(filename) {
|
||||
function getAsset(filename, callback) {
|
||||
var finalName = '/assets/' + filename + '.svg';
|
||||
if (fabric.isLikelyNode) {
|
||||
var path = (__dirname + finalName);
|
||||
return fs.readFileSync(path, { encoding: 'utf8' });
|
||||
} else {
|
||||
|
||||
return fs.readFile(path, { encoding: 'utf8' }, callback);
|
||||
}
|
||||
else {
|
||||
var path = getAbsolutePath('test/visual' + finalName);
|
||||
fabric.util.request(path, {
|
||||
onComplete: function(xhr) {
|
||||
callback(null, xhr.responseText);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getGolden(filename) {
|
||||
var finalName = '/golden/' + filename + '.png';
|
||||
return fabric.isLikelyNode ? (__dirname + finalName) : getAbsolutePath(finalName);
|
||||
return fabric.isLikelyNode ? (__dirname + finalName) : getAbsolutePath('test/visual' + finalName);
|
||||
}
|
||||
|
||||
function getGoldenImage(filename, callback) {
|
||||
|
|
@ -58,11 +67,13 @@
|
|||
var goldenImageData = ctx.getImageData(0, 0, width, height).data;
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
var outputImageData = ctx.getImageData(0, 0, width, height).data;
|
||||
fabric.loadSVGFromString(getAsset(filename), function(objects) {
|
||||
fabricCanvas.add.apply(fabricCanvas, objects);
|
||||
fabricCanvas.renderAll();
|
||||
var fabricImageData = fabricCanvas.contextContainer.getImageData(0, 0, width, height).data;
|
||||
callback(fabricImageData, goldenImageData, width, height, outputImageData);
|
||||
getAsset(filename, function(err, string) {
|
||||
fabric.loadSVGFromString(string, function(objects) {
|
||||
fabricCanvas.add.apply(fabricCanvas, objects);
|
||||
fabricCanvas.renderAll();
|
||||
var fabricImageData = fabricCanvas.contextContainer.getImageData(0, 0, width, height).data;
|
||||
callback(fabricImageData, goldenImageData, width, height, outputImageData);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -75,24 +86,48 @@
|
|||
});
|
||||
|
||||
[
|
||||
'svg_stroke_1',
|
||||
'svg_stroke_2',
|
||||
'svg_stroke_3',
|
||||
'svg_stroke_4',
|
||||
'svg_stroke_5',
|
||||
'svg_stroke_6',
|
||||
'svg_stroke_7',
|
||||
'svg_stroke_8',
|
||||
].forEach(function(filename) {
|
||||
['svg_stroke_1', 0],
|
||||
['svg_stroke_2', 0],
|
||||
['svg_stroke_3', 0],
|
||||
['svg_stroke_4', 8],
|
||||
['svg_stroke_5', 4],
|
||||
['svg_stroke_6', 83],
|
||||
['svg_stroke_7', 0],
|
||||
['svg_stroke_8', 0],
|
||||
['svg_linear_1', 0],
|
||||
['svg_linear_2', 0],
|
||||
['svg_linear_3', 0],
|
||||
['svg_linear_4', 14],
|
||||
['svg_linear_5', 8],
|
||||
['svg_linear_6', 83],
|
||||
['svg_linear_7', 0],
|
||||
['svg_linear_8', 0],
|
||||
['svg_radial_1', 100],
|
||||
['svg_radial_2', 0],
|
||||
['svg_radial_3', 0],
|
||||
['svg_radial_4', 143],
|
||||
['svg_radial_5', 143],
|
||||
['svg_radial_6', 8],
|
||||
['svg_radial_8', 0],
|
||||
['svg_radial_9', 8],
|
||||
['svg_radial_10', 12],
|
||||
['svg_radial_11', 0],
|
||||
['svg_radial_12', 8],
|
||||
['svg_radial_13', 4],
|
||||
].forEach(function(filenameArray) {
|
||||
var filename = filenameArray[0];
|
||||
var expectedPixels = filenameArray[1];
|
||||
QUnit.test('Import test for file ' + filename, function(assert) {
|
||||
var done = assert.async();
|
||||
loadAndPrepareCanvasFor(filename, function(imageDataCanvas, imageDataGolden, width, height, output) {
|
||||
var totalPixels = width * height;
|
||||
var percentage = 0.01;
|
||||
var differentPixels = pixelmatch(imageDataCanvas, imageDataGolden, output, width, height, pixelmatchOptions);
|
||||
assert.ok(differentPixels < totalPixels * percentage, 'Image ' + filename + ' has too many different pixels ' + differentPixels + ' representing ' + differentPixels / totalPixels * 100 + '%');
|
||||
var differentPixels = _pixelMatch(imageDataCanvas, imageDataGolden, output, width, height, pixelmatchOptions);
|
||||
var percDiff = differentPixels / totalPixels * 100;
|
||||
assert.ok(differentPixels < totalPixels * percentage, 'Image ' + filename + ' has too many different pixels ' + differentPixels + ' representing ' + percDiff + '%');
|
||||
done();
|
||||
console.log('Different pixels for', filename, ':', differentPixels, '/', totalPixels, 'expected:', expectedPixels, ' diff:', percDiff.toFixed(3), '%');
|
||||
});
|
||||
});
|
||||
});
|
||||
})(global);
|
||||
})();
|
||||
|
|
|
|||
34
testem-visual.json
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"framework": "qunit",
|
||||
"serve_files": [
|
||||
"dist/fabric.js",
|
||||
"test/lib/pixelmatch.js",
|
||||
"test/visual/*.js"
|
||||
],
|
||||
"routes": {
|
||||
"/fixtures": "test/fixtures"
|
||||
},
|
||||
"test_page": "tests.mustache?hidepassed&hideskipped&timeout=60000",
|
||||
"browser_args": {
|
||||
"Chrome": [ "--headless", "--disable-gpu", "--remote-debugging-port=9222" ],
|
||||
"Firefox": [ "--headless" ]
|
||||
},
|
||||
"launch_in_dev": [
|
||||
"Chrome",
|
||||
"Node",
|
||||
"Firefox"
|
||||
],
|
||||
"launch_in_ci": [
|
||||
"Chrome",
|
||||
"Node",
|
||||
"Firefox"
|
||||
],
|
||||
"launchers": {
|
||||
"Node": {
|
||||
"command": "npm run test:visual",
|
||||
"protocol": "tap"
|
||||
}
|
||||
},
|
||||
"timeout": 540,
|
||||
"parallel": 4
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test'em</title>
|
||||
<title>Unit Tests</title>
|
||||
<script src="/node_modules/qunit/qunit/qunit.js"></script>
|
||||
<script src="/testem.js"></script>
|
||||
{{#serve_files}}<script src="{{{src}}}"{{#attrs}} {{&.}}{{/attrs}}></script>{{/serve_files}}
|
||||
|
|
|
|||