diff --git a/.gitignore b/.gitignore index 64894f781e6577fcaa99c59980628c294451d3d8..c11fe75047ca85ec3b2129e8f82265d0e6a5a4c3 100644 --- a/.gitignore +++ b/.gitignore @@ -168,3 +168,4 @@ map/tool theme/thumb /lib todo +**/*.log diff --git a/test/ut/configure b/test/ut/configure index 7c283925b8d2c14931178fef257e5141d84c2d8f..5aa966b8b547cf6076859bd52a13dccc88135c1b 100755 --- a/test/ut/configure +++ b/test/ut/configure @@ -5,7 +5,6 @@ else # use last release old=$(git rev-list --tags --max-count=1) fi -echo $old mkdir -p tmp cp ../../dist/echarts.js "tmp/newEcharts.js" git show $old:dist/echarts.js > 'tmp/oldEcharts.js' diff --git a/test/ut/core/uiHelper.js b/test/ut/core/uiHelper.js index 407cd0ab1cd3e35c086458c8e1c757974dc67978..8a9d4d069ef085b99f6459c55ea87b248ff9eb00 100644 --- a/test/ut/core/uiHelper.js +++ b/test/ut/core/uiHelper.js @@ -2,16 +2,29 @@ var helper = context.uiHelper = {}; + // canvas comparing strategy, 'stack' or 'content' + var STRATEGY = 'stack'; + + // dom for failed cases + var failedDom = document.createElement('div'); + failedDom.setAttribute('id', 'failed-panel'); + var hasFailedDom = false; + /** * expect canvas.toDataURL to be the same by old and new echarts + * @param {string} title title of suite and case * @param {function} doTest test body * @param {function} done done callback provided by jasmine */ - helper.expectEqualCanvasContent = function(doTest, done) { + helper.expectEqualCanvasContent = function(title, doTest, done) { + var that = this; window.require(['oldEcharts', 'newEcharts'], function (oldE, newE) { - var oldCanvas = doTest(oldE); - var newCanvas = doTest(newE); - expect(oldCanvas.toDataURL()).toEqual(newCanvas.toDataURL()); + var oldImg = doTest(oldE).toDataURL(); + var newImg = doTest(newE).toDataURL(); + if (oldImg !== newImg) { + that.addFailedCases(title, oldImg, newImg); + } + expect(oldImg).toEqual(newImg); done(); }); }; @@ -19,13 +32,22 @@ /** * expect canvas operation stack provided by canteen * to be the same by old and new echarts + * @param {string} title title of suite and case * @param {function} doTest test body * @param {function} done done callback provided by jasmine */ - helper.expectEqualCanvasStack = function(doTest, done) { + helper.expectEqualCanvasStack = function(title, doTest, done) { + var that = this; window.require(['oldEcharts', 'newEcharts'], function (oldE, newE) { - var oldCtx = doTest(oldE).getContext('2d'); - var newCtx = doTest(newE).getContext('2d'); + var oldCanvas = doTest(oldE); + var newCanvas = doTest(newE); + var oldImg = oldCanvas.toDataURL(); + var newImg = newCanvas.toDataURL(); + if (oldImg !== newImg) { + that.addFailedCases(title, oldImg, newImg); + } + var oldCtx = oldCanvas.getContext('2d'); + var newCtx = newCanvas.getContext('2d'); // hash of canvas operation stack, provided by canteen // https://github.com/platfora/Canteen expect(oldCtx.hash()).toEqual(newCtx.hash()); @@ -35,15 +57,15 @@ /** * expect canvas with strategy - * @param {string} strategy 'content' or 'stack' + * @param {string} title title of suite and case * @param {function} doTest test body * @param {function} done done callback provided by jasmine */ - helper.expectEqualCanvas = function(strategy, doTest, done) { - if (strategy === 'content') { - helper.expectEqualCanvasContent(doTest, done); - } else if (strategy === 'stack') { - helper.expectEqualCanvasStack(doTest, done); + helper.expectEqualCanvas = function(title, doTest, done) { + if (STRATEGY === 'content') { + helper.expectEqualCanvasContent(title, doTest, done); + } else if (STRATEGY === 'stack') { + helper.expectEqualCanvasStack(title, doTest, done); } else { console.error('Invalid equal canvas strategy!'); } @@ -68,4 +90,85 @@ return canvas; }; + /** + * run test with only setOption + * @param {string} name name of the test + * @param {object} option echarts option + */ + helper.testOption = function(name, option) { + var that = this; + it(name, function(done) { + that.expectEqualCanvas(name, function(ec) { + var canvas = that.getRenderedCanvas(ec, function(myChart) { + myChart.setOption(option); + }); + return canvas; + }, done); + }); + } + + /** + * run test with setOption for whole spec + * @param {string} specName spec name + * @param {object[]} suites arrary of suites + */ + helper.testOptionSpec = function(specName, suites) { + for (var sid = 0, slen = suites.length; sid < slen; ++sid) { + (function(suiteName, cases) { + describe('show', function() { + for (var cid = 0, clen = cases.length; cid < clen; ++cid) {var name = specName + ' - ' + suiteName + ': ' + + cases[cid].name; + uiHelper.testOption(name, cases[cid].option); + } + }); + })(suites[sid].name, suites[sid].cases); + } + } + + /** + * @param {string} name name of the test + * @param {string} oldImgSrc old canvas.toDataURL value + * @param {string} newImgSrc new canvas.toDataURL value + * add a failed case in dom + */ + helper.addFailedCases = function(name, oldImgSrc, newImgSrc) { + // group of this case + var group = document.createElement('div'); + var title = document.createElement('h6'); + title.innerHTML = name + '. Here are old, new, and diff images.'; + group.appendChild(title); + + // old image and new image + var oldImg = document.createElement('img'); + oldImg.src = oldImgSrc; + oldImg.setAttribute('title', 'Old Image'); + var newImg = document.createElement('img'); + newImg.src = newImgSrc; + newImg.setAttribute('title', 'New Image'); + group.appendChild(oldImg); + group.appendChild(newImg); + + // diff image + var diff = imagediff.diff(oldImg, newImg); + console.log(diff); + var canvas = document.createElement('canvas'); + canvas.width = oldImg.width; + canvas.height = oldImg.height; + var ctx = canvas.getContext('2d'); + ctx.putImageData(diff, 0, 0); + var diffImg = document.createElement('img'); + diffImg.src = canvas.toDataURL(); + diffImg.setAttribute('title', 'Diff Image'); + group.appendChild(diffImg); + + failedDom.appendChild(group); + + // append to dom + if (!hasFailedDom) { + var body = document.getElementsByTagName('body')[0]; + body.appendChild(failedDom); + hasFailedDom = true; + } + }; + })(window); diff --git a/test/ut/lib/imagediff.js b/test/ut/lib/imagediff.js new file mode 100644 index 0000000000000000000000000000000000000000..7760ffaef9eccd6d3830f6c4378685e5263d57c6 --- /dev/null +++ b/test/ut/lib/imagediff.js @@ -0,0 +1,392 @@ +(function (name, definition) { + var root = this; + if (typeof module !== 'undefined') { + var Canvas; + try { + Canvas = require('canvas'); + } catch (e) {} + module.exports = definition(root, name, Canvas); + } else if (typeof define === 'function' && typeof define.amd === 'object') { + define(definition(root, name)); + } else { + root[name] = definition(root, name); + } +})('imagediff', function (root, name, Canvas) { + + var + TYPE_ARRAY = /\[object Array\]/i, + TYPE_CANVAS = /\[object (Canvas|HTMLCanvasElement)\]/i, + TYPE_CONTEXT = /\[object CanvasRenderingContext2D\]/i, + TYPE_IMAGE = /\[object (Image|HTMLImageElement)\]/i, + TYPE_IMAGE_DATA = /\[object ImageData\]/i, + + UNDEFINED = 'undefined', + + canvas = getCanvas(), + context = canvas.getContext('2d'), + previous = root[name], + imagediff, jasmine; + + // Creation + function getCanvas (width, height) { + var canvas; + if (Canvas) { + canvas = new Canvas(); + } else if (root.document && root.document.createElement) { + canvas = document.createElement('canvas'); + } else { + throw new Error( + e.message + '\n' + + 'Please see https://github.com/HumbleSoftware/js-imagediff#cannot-find-module-canvas\n' + ); + } + if (width) canvas.width = width; + if (height) canvas.height = height; + return canvas; + } + function getImageData (width, height) { + canvas.width = width; + canvas.height = height; + context.clearRect(0, 0, width, height); + return context.createImageData(width, height); + } + + + // Type Checking + function isImage (object) { + return isType(object, TYPE_IMAGE); + } + function isCanvas (object) { + return isType(object, TYPE_CANVAS); + } + function isContext (object) { + return isType(object, TYPE_CONTEXT); + } + function isImageData (object) { + return !!(object && + isType(object, TYPE_IMAGE_DATA) && + typeof(object.width) !== UNDEFINED && + typeof(object.height) !== UNDEFINED && + typeof(object.data) !== UNDEFINED); + } + function isImageType (object) { + return ( + isImage(object) || + isCanvas(object) || + isContext(object) || + isImageData(object) + ); + } + function isType (object, type) { + return typeof (object) === 'object' && !!Object.prototype.toString.apply(object).match(type); + } + + + // Type Conversion + function copyImageData (imageData) { + var + height = imageData.height, + width = imageData.width, + data = imageData.data, + newImageData, newData, i; + + canvas.width = width; + canvas.height = height; + newImageData = context.getImageData(0, 0, width, height); + newData = newImageData.data; + + for (i = imageData.data.length; i--;) { + newData[i] = data[i]; + } + + return newImageData; + } + function toImageData (object) { + if (isImage(object)) { return toImageDataFromImage(object); } + if (isCanvas(object)) { return toImageDataFromCanvas(object); } + if (isContext(object)) { return toImageDataFromContext(object); } + if (isImageData(object)) { return object; } + } + function toImageDataFromImage (image) { + var + height = image.height, + width = image.width; + canvas.width = width; + canvas.height = height; + context.clearRect(0, 0, width, height); + context.drawImage(image, 0, 0); + return context.getImageData(0, 0, width, height); + } + function toImageDataFromCanvas (canvas) { + var + height = canvas.height, + width = canvas.width, + context = canvas.getContext('2d'); + return context.getImageData(0, 0, width, height); + } + function toImageDataFromContext (context) { + var + canvas = context.canvas, + height = canvas.height, + width = canvas.width; + return context.getImageData(0, 0, width, height); + } + function toCanvas (object) { + var + data = toImageData(object), + canvas = getCanvas(data.width, data.height), + context = canvas.getContext('2d'); + + context.putImageData(data, 0, 0); + return canvas; + } + + + // ImageData Equality Operators + function equalWidth (a, b) { + return a.width === b.width; + } + function equalHeight (a, b) { + return a.height === b.height; + } + function equalDimensions (a, b) { + return equalHeight(a, b) && equalWidth(a, b); + } + function equal (a, b, tolerance) { + + var + aData = a.data, + bData = b.data, + length = aData.length, + i; + + tolerance = tolerance || 0; + + if (!equalDimensions(a, b)) return false; + for (i = length; i--;) if (aData[i] !== bData[i] && Math.abs(aData[i] - bData[i]) > tolerance) return false; + + return true; + } + + + // Diff + function diff (a, b, options) { + return (equalDimensions(a, b) ? diffEqual : diffUnequal)(a, b, options); + } + function diffEqual (a, b, options) { + + var + height = a.height, + width = a.width, + c = getImageData(width, height), // c = a - b + aData = a.data, + bData = b.data, + cData = c.data, + length = cData.length, + row, column, + i, j, k, v; + + for (i = 0; i < length; i += 4) { + cData[i] = Math.abs(aData[i] - bData[i]); + cData[i+1] = Math.abs(aData[i+1] - bData[i+1]); + cData[i+2] = Math.abs(aData[i+2] - bData[i+2]); + cData[i+3] = Math.abs(255 - Math.abs(aData[i+3] - bData[i+3])); + } + + return c; + } + function diffUnequal (a, b, options) { + + var + height = Math.max(a.height, b.height), + width = Math.max(a.width, b.width), + c = getImageData(width, height), // c = a - b + aData = a.data, + bData = b.data, + cData = c.data, + align = options && options.align, + rowOffset, + columnOffset, + row, column, + i, j, k, v; + + + for (i = cData.length - 1; i > 0; i = i - 4) { + cData[i] = 255; + } + + // Add First Image + offsets(a); + for (row = a.height; row--;){ + for (column = a.width; column--;) { + i = 4 * ((row + rowOffset) * width + (column + columnOffset)); + j = 4 * (row * a.width + column); + cData[i+0] = aData[j+0]; // r + cData[i+1] = aData[j+1]; // g + cData[i+2] = aData[j+2]; // b + // cData[i+3] = aData[j+3]; // a + } + } + + // Subtract Second Image + offsets(b); + for (row = b.height; row--;){ + for (column = b.width; column--;) { + i = 4 * ((row + rowOffset) * width + (column + columnOffset)); + j = 4 * (row * b.width + column); + cData[i+0] = Math.abs(cData[i+0] - bData[j+0]); // r + cData[i+1] = Math.abs(cData[i+1] - bData[j+1]); // g + cData[i+2] = Math.abs(cData[i+2] - bData[j+2]); // b + } + } + + // Helpers + function offsets (imageData) { + if (align === 'top') { + rowOffset = 0; + columnOffset = 0; + } else { + rowOffset = Math.floor((height - imageData.height) / 2); + columnOffset = Math.floor((width - imageData.width) / 2); + } + } + + return c; + } + + + // Validation + function checkType () { + var i; + for (i = 0; i < arguments.length; i++) { + if (!isImageType(arguments[i])) { + throw { + name : 'ImageTypeError', + message : 'Submitted object was not an image.' + }; + } + } + } + + + // Jasmine Matchers + function get (element, content) { + element = document.createElement(element); + if (element && content) { + element.innerHTML = content; + } + return element; + } + + jasmine = { + + toBeImageData : function () { + return imagediff.isImageData(this.actual); + }, + + toImageDiffEqual : function (expected, tolerance) { + + if (typeof (document) !== UNDEFINED) { + this.message = function () { + var + div = get('div'), + a = get('div', '