diff --git a/.travis.yml b/.travis.yml index cd3d8207..d5669b40 100644 --- a/.travis.yml +++ b/.travis.yml @@ -73,7 +73,7 @@ jobs: - stage: Unit Tests node_js: "4" - stage: Visual Tests - env: LAUNCHER=Node + env: LAUNCHER=Node CANFAIL=TRUE node_js: "8" script: npm run build:fast && npm run test:visual - stage: Visual Tests diff --git a/test/lib/visualTestLoop.js b/test/lib/visualTestLoop.js index 15934cdb..b83a96a9 100644 --- a/test/lib/visualTestLoop.js +++ b/test/lib/visualTestLoop.js @@ -97,6 +97,9 @@ } return function testCallback(testObj) { + if (testObj.disabled) { + return; + } var testName = testObj.test; var code = testObj.code; var percentage = testObj.percentage; diff --git a/test/visual/clippath.js b/test/visual/clippath.js index 3e07064b..70d68ed1 100644 --- a/test/visual/clippath.js +++ b/test/visual/clippath.js @@ -13,11 +13,6 @@ enableRetinaScaling: false, renderOnAddRemove: false, width: 200, height: 200, }); - // function getFixtureName(filename) { - // var finalName = '/fixtures/' + filename; - // return fabric.isLikelyNode ? (__dirname + '/..' + finalName) : getAbsolutePath('/test' + finalName); - // } - var tests = []; function clipping0(canvas, callback) { @@ -50,7 +45,6 @@ test: 'A clippath ignores fill and stroke for drawing, not positioning', code: clipping01, golden: 'clipping01.png', - newModule: 'Clipping shapes', percentage: 0.06, }); diff --git a/test/visual/svg_export.js b/test/visual/svg_export.js new file mode 100644 index 00000000..fdebbbbc --- /dev/null +++ b/test/visual/svg_export.js @@ -0,0 +1,323 @@ +(function() { + fabric.enableGLFiltering = false; + fabric.isWebglSupported = false; + fabric.Object.prototype.objectCaching = true; + var visualTestLoop; + if (fabric.isLikelyNode) { + visualTestLoop = global.visualTestLoop; + } + else { + visualTestLoop = window.visualTestLoop; + } + var fabricCanvas = this.canvas = new fabric.Canvas(null, { + enableRetinaScaling: false, renderOnAddRemove: false, width: 200, height: 200, + }); + + function svgToDataURL(svgStr) { + var encoded = encodeURIComponent(svgStr) + .replace(/'/g, '%27') + .replace(/"/g, '%22'); + return 'data:image/svg+xml,' + encoded; + } + + function toSVGCanvas(canvas, callback) { + var svg = canvas.toSVG(); + var dataUrl = svgToDataURL(svg); + var image = fabric.document.createElement('img'); + image.onload = function() { + var newCanvas = fabric.util.createCanvasElement(); + newCanvas.width = canvas.width; + newCanvas.height = canvas.height; + newCanvas.getContext('2d').drawImage(image, 0, 0, canvas.width, canvas.height); + callback(newCanvas); + }; + image.onerror = console.log; + if (fabric.isLikelyNode) { + image.src = dataUrl; + } + else { + image.src = dataUrl; + } + } + + var tests = []; + + function clipping0(canvas, callback) { + var clipPath = new fabric.Circle({ radius: 100, strokeWidth: 0, top: -10, left: -10 }); + var obj = new fabric.Rect({ top: 0, left: 0, strokeWidth: 0, width: 200, height: 200, fill: 'rgba(0,255,0,0.5)'}); + obj.clipPath = clipPath; + canvas.add(obj); + toSVGCanvas(canvas, callback); + } + + tests.push({ + test: 'Clip a rect with a circle, no zoom', + code: clipping0, + golden: 'clipping0.png', + newModule: 'Export clippaths to SVG', + percentage: 0.06, + }); + + function clipping01(canvas, callback) { + var clipPath = new fabric.Circle({ radius: 50, strokeWidth: 40, top: -50, left: -50, fill: 'transparent' }); + var obj = new fabric.Rect({ top: 0, left: 0, strokeWidth: 0, width: 200, height: 200, fill: 'rgba(0,255,0,0.5)'}); + obj.clipPath = clipPath; + canvas.add(obj); + toSVGCanvas(canvas, callback); + } + + tests.push({ + test: 'A clippath ignores fill and stroke for drawing, not positioning', + code: clipping01, + golden: 'clipping01.png', + percentage: 0.06, + }); + + function clipping1(canvas, callback) { + var zoom = 20; + canvas.setZoom(zoom); + var clipPath = new fabric.Circle({ radius: 5, strokeWidth: 0, top: -2, left: -2 }); + var obj = new fabric.Rect({ top: 0, left: 0, strokeWidth: 0, width: 10, height: 10, fill: 'rgba(255,0,0,0.5)'}); + obj.clipPath = clipPath; + canvas.add(obj); + toSVGCanvas(canvas, callback); + } + + tests.push({ + test: 'Clip a rect with a circle, with zoom', + code: clipping1, + golden: 'clipping1.png', + percentage: 0.06, + }); + + function clipping2(canvas, callback) { + var clipPath = new fabric.Circle({ + radius: 100, + top: -100, + left: -100 + }); + var group = new fabric.Group([ + new fabric.Rect({ strokeWidth: 0, width: 100, height: 100, fill: 'red' }), + new fabric.Rect({ strokeWidth: 0, width: 100, height: 100, fill: 'yellow', left: 100 }), + new fabric.Rect({ strokeWidth: 0, width: 100, height: 100, fill: 'blue', top: 100 }), + new fabric.Rect({ strokeWidth: 0, width: 100, height: 100, fill: 'green', left: 100, top: 100 }) + ], { strokeWidth: 0 }); + group.clipPath = clipPath; + canvas.add(group); + toSVGCanvas(canvas, callback); + } + + tests.push({ + test: 'Clip a group with a circle', + code: clipping2, + golden: 'clipping2.png', + percentage: 0.06, + }); + + function clipping3(canvas, callback) { + var clipPath = new fabric.Circle({ radius: 100, top: -100, left: -100 }); + var small = new fabric.Circle({ radius: 50, top: -50, left: -50 }); + var small2 = new fabric.Rect({ width: 30, height: 30, top: -50, left: -50 }); + var group = new fabric.Group([ + new fabric.Rect({ strokeWidth: 0, width: 100, height: 100, fill: 'red', clipPath: small }), + new fabric.Rect({ strokeWidth: 0, width: 100, height: 100, fill: 'yellow', left: 100 }), + new fabric.Rect({ strokeWidth: 0, width: 100, height: 100, fill: 'blue', top: 100, clipPath: small2 }), + new fabric.Rect({ strokeWidth: 0, width: 100, height: 100, fill: 'green', left: 100, top: 100 }) + ], { strokeWidth: 0 }); + group.clipPath = clipPath; + canvas.add(group); + toSVGCanvas(canvas, callback); + } + + tests.push({ + test: 'Isolation of clipPath of group and inner objects', + code: clipping3, + golden: 'clipping3.png', + percentage: 0.06, + disabled: true, + }); + + function clipping4(canvas, callback) { + var clipPath = new fabric.Circle({ radius: 20, strokeWidth: 0, top: -10, left: -10, scaleX: 2, skewY: 45 }); + var obj = new fabric.Rect({ top: 0, left: 0, strokeWidth: 0, width: 200, height: 200, fill: 'rgba(0,255,0,0.5)'}); + obj.fill = new fabric.Gradient({ + type: 'linear', + coords: { + x1: 0, + y1: 0, + x2: 200, + y2: 200, + }, + colorStops: [ + { + offset: 0, + color: 'red', + }, + { + offset: 1, + color: 'blue', + } + ] + }); + obj.clipPath = clipPath; + canvas.add(obj); + toSVGCanvas(canvas, callback); + } + + tests.push({ + test: 'ClipPath can be transformed', + code: clipping4, + golden: 'clipping4.png', + percentage: 0.06, + }); + + function clipping5(canvas, callback) { + var clipPath = new fabric.Circle({ radius: 20, strokeWidth: 0, top: -10, left: -10, scaleX: 2, skewY: 45 }); + var clipPath1 = new fabric.Circle({ radius: 15, rotate: 45, strokeWidth: 0, top: -100, left: -50, scaleX: 2, skewY: 45 }); + var clipPath2 = new fabric.Circle({ radius: 10, strokeWidth: 0, top: -20, left: -20, scaleY: 2, skewX: 45 }); + var group = new fabric.Group([clipPath, clipPath1, clipPath2]); + var obj = new fabric.Rect({ top: 0, left: 0, strokeWidth: 0, width: 200, height: 200, fill: 'rgba(0,255,0,0.5)'}); + obj.fill = new fabric.Gradient({ + type: 'linear', + coords: { + x1: 0, + y1: 0, + x2: 200, + y2: 200, + }, + colorStops: [ + { + offset: 0, + color: 'red', + }, + { + offset: 1, + color: 'blue', + } + ] + }); + obj.clipPath = group; + canvas.add(obj); + toSVGCanvas(canvas, callback); + } + + tests.push({ + test: 'ClipPath can be a group with many objects', + code: clipping5, + golden: 'clipping5.png', + percentage: 0.06, + disabled: true, + }); + + function clipping6(canvas, callback) { + var clipPath = new fabric.Circle({ radius: 20, strokeWidth: 0, top: -10, left: -10, scaleX: 2, skewY: 45 }); + var clipPath1 = new fabric.Circle({ radius: 15, rotate: 45, strokeWidth: 0, top: -100, left: -50, scaleX: 2, skewY: 45 }); + var clipPath2 = new fabric.Circle({ radius: 10, strokeWidth: 0, top: -20, left: -20, scaleY: 2, skewX: 45 }); + var group = new fabric.Group([clipPath, clipPath1, clipPath2]); + var obj = new fabric.Rect({ top: 0, left: 0, strokeWidth: 0, width: 200, height: 200, fill: 'rgba(0,255,0,0.5)'}); + obj.fill = new fabric.Gradient({ + type: 'linear', + coords: { + x1: 0, + y1: 0, + x2: 200, + y2: 200, + }, + colorStops: [ + { + offset: 0, + color: 'red', + }, + { + offset: 1, + color: 'blue', + } + ] + }); + obj.clipPath = group; + group.inverted = true; + canvas.add(obj); + toSVGCanvas(canvas, callback); + } + + tests.push({ + test: 'ClipPath can be inverted, it will clip what is outside the clipPath', + code: clipping6, + golden: 'clipping6.png', + percentage: 0.06, + disabled: true, + }); + + function clipping7(canvas, callback) { + var clipPath = new fabric.Circle({ radius: 30, strokeWidth: 0, top: -30, left: -30, skewY: 45 }); + var obj1 = new fabric.Rect({ top: 0, left: 100, strokeWidth: 0, width: 100, height: 100, fill: 'rgba(0,255,0,0.8)'}); + var obj2 = new fabric.Rect({ top: 0, left: 0, strokeWidth: 0, width: 100, height: 100, fill: 'rgba(255,255,0,0.8)'}); + var obj3 = new fabric.Rect({ top: 100, left: 0, strokeWidth: 0, width: 100, height: 100, fill: 'rgba(0,255,255,0.8)'}); + var obj4 = new fabric.Rect({ top: 100, left: 100, strokeWidth: 0, width: 100, height: 100, fill: 'rgba(255,0,0,0.8)'}); + obj1.clipPath = clipPath; + obj2.clipPath = clipPath; + obj3.clipPath = clipPath; + obj4.clipPath = clipPath; + canvas.add(obj1); + canvas.add(obj2); + canvas.add(obj3); + canvas.add(obj4); + toSVGCanvas(canvas, callback); + } + + tests.push({ + test: 'Many Objects can share the same clipPath', + code: clipping7, + golden: 'clipping7.png', + percentage: 0.06, + disabled: true, + }); + + function clipping8(canvas, callback) { + var clipPath = new fabric.Circle({ radius: 60, strokeWidth: 0, top: 40, left: 40, absolutePositioned: true }); + var obj1 = new fabric.Rect({ top: 0, left: 100, strokeWidth: 0, width: 100, height: 100, fill: 'rgba(0,255,0,0.8)'}); + var obj2 = new fabric.Rect({ top: 0, left: 0, strokeWidth: 0, width: 100, height: 100, fill: 'rgba(255,255,0,0.8)'}); + var obj3 = new fabric.Rect({ top: 100, left: 0, strokeWidth: 0, width: 100, height: 100, fill: 'rgba(0,255,255,0.8)'}); + var obj4 = new fabric.Rect({ top: 100, left: 100, strokeWidth: 0, width: 100, height: 100, fill: 'rgba(255,0,0,0.8)'}); + obj1.clipPath = clipPath; + obj2.clipPath = clipPath; + obj3.clipPath = clipPath; + canvas.add(obj1); + canvas.add(obj2); + canvas.add(obj3); + canvas.add(obj4); + toSVGCanvas(canvas, callback); + } + + tests.push({ + test: 'an absolute positioned clipPath, shared', + code: clipping8, + golden: 'clipping8.png', + percentage: 0.06, + disabled: true, + }); + + function clipping9(canvas, callback) { + var clipPath = new fabric.Circle({ radius: 60, strokeWidth: 0, top: 10, left: 10 }); + var obj1 = new fabric.Rect({ top: 0, left: 100, strokeWidth: 0, width: 100, height: 100, fill: 'rgba(0,255,0,0.8)'}); + var obj2 = new fabric.Rect({ top: 0, left: 0, strokeWidth: 0, width: 100, height: 100, fill: 'rgba(255,255,0,0.8)'}); + var obj3 = new fabric.Rect({ top: 100, left: 0, strokeWidth: 0, width: 100, height: 100, fill: 'rgba(0,255,255,0.8)'}); + var obj4 = new fabric.Rect({ top: 100, left: 100, strokeWidth: 0, width: 100, height: 100, fill: 'rgba(255,0,0,0.8)'}); + canvas.add(obj1); + canvas.add(obj2); + canvas.add(obj3); + canvas.add(obj4); + canvas.clipPath = clipPath; + toSVGCanvas(canvas, callback); + } + + tests.push({ + test: 'a clipPath on the canvas', + code: clipping9, + golden: 'clipping9.png', + percentage: 0.06, + disabled: true, + }); + + tests.forEach(visualTestLoop(fabricCanvas, QUnit)); +})();