/* * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey * http://scratchdisk.com/ & http://jonathanpuckey.com/ * * Distributed under the MIT license. See LICENSE file for details. * * All rights reserved. */ var isNode = typeof global === 'object', isPhantom = !!window.callPhantom, isBrowser = !isNode && !isPhantom, root; if (isNode) { root = global; // Resemble.js needs the Image constructor global. global.Image = paper.window.Image; } else { root = window; // This is only required when running in the browser: // Until window.history.pushState() works when running locally, we need to // trick qunit into thinking that the feature is not present. This appears // to work... // TODO: Ideally we should fix this in QUnit instead. delete window.history; window.history = {}; QUnit.begin(function() { if (QUnit.urlParams.hidepassed) { document.getElementById('qunit-tests').className += ' hidepass'; } }); } // The unit-tests expect the paper classes to be global. paper.install(root); // Override console.error, so that we can catch errors that are only logged to // the console. var errorHandler = console.error; console.error = function() { QUnit.pushFailure([].join.call(arguments, ' '), QUnit.config.current.stack); errorHandler.apply(this, arguments); }; QUnit.done(function(details) { console.error = errorHandler; }); var currentProject; // NOTE: In order to "export" all methods into the shared Prepro.js scope when // using node-qunit, we need to define global functions as: // `var name = function() {}`. `function name() {}` does not work! var test = function(testName, expected) { return QUnit.test(testName, function(assert) { // Since tests can be asynchronous, remove the old project before // running the next test. if (currentProject) currentProject.remove(); currentProject = new Project(); expected(assert); }); }; // Override equals to convert functions to message and execute them as tests() var equals = function(actual, expected, message, options) { // Allow the use of functions for actual, which will get called and their // source content extracted for readable reports. if (typeof actual === 'function') { if (!message) message = getFunctionMessage(actual); actual = actual(); } // Get the comparator based on the expected value's type only and ignore the // actual value's type. var type = typeof expected, cls; type = expected === null && 'Null' || type === 'number' && 'Number' || type === 'boolean' && 'Boolean' || type === 'undefined' && 'Undefined' || Array.isArray(expected) && 'Array' || expected instanceof window.Element && 'Element' // handle DOM Elements || (cls = expected && expected._class) // check _class 2nd last || type === 'object' && 'Object'; // Object as catch-all var comparator = type && comparators[type]; if (!message) message = type ? type.toLowerCase() : 'value'; if (comparator) { comparator(actual, expected, message, options); } else if (expected && expected.equals) { // Fall back to equals QUnit.push(expected.equals(actual), actual, expected, message); } else { // Finally perform a strict compare QUnit.push(actual === expected, actual, expected, message); } if (options && options.cloned && cls) { var identical = identicalAfterCloning[cls]; QUnit.push(identical ? actual === expected : actual !== expected, actual, identical ? expected : 'not ' + expected, message + ': identical after cloning'); } }; // A list of classes that should be identical after their owners were cloned. var identicalAfterCloning = { Gradient: true, SymbolDefinition: true }; // Register a jsDump parser for Base. QUnit.jsDump.setParser('Base', function (obj, stack) { // Just compare the string representation of classes inheriting from Base, // since they hide the internal values. return obj.toString(); }); // Override the default object parser to handle Base objects. // We need to keep a reference to the previous implementation. var objectParser = QUnit.jsDump.parsers.object; QUnit.jsDump.setParser('object', function (obj, stack) { return (obj instanceof Base ? QUnit.jsDump.parsers.Base : objectParser).call(this, obj, stack); }); var compareProperties = function(actual, expected, properties, message, options) { for (var i = 0, l = properties.length; i < l; i++) { var key = properties[i]; equals(actual[key], expected[key], message + '.' + key, options); } }; var comparePixels = function(actual, expected, message, options) { function rasterize(item, group, resolution) { var raster = null; if (group) { group.addChild(item); raster = group.rasterize(resolution, false); item.remove(); } return raster; } function getImageTag(raster) { return ''; } 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.resolution || 72, actualBounds = actual.strokeBounds, expecedBounds = expected.strokeBounds, bounds = actualBounds.isEmpty() ? expecedBounds : expecedBounds.isEmpty() ? actualBounds : actualBounds.unite(expecedBounds); if (bounds.isEmpty()) { QUnit.equal('empty', 'empty', message); return; } var group = actual && expected && new Group({ insert: false, children: [ new Shape.Rectangle({ rectangle: bounds, fillColor: 'white' }) ] }), actual = rasterize(actual, group, resolution), expected = rasterize(expected, group, resolution); if (!actual || !expected) { QUnit.push(false, null, null, 'Unable to compare rasterized items: ' + (!actual ? 'actual' : 'expected') + ' item is null', QUnit.stack(2)); } else { // Use resemble.js to compare the two rasterized items. var id = QUnit.config.current.testId, index = QUnit.config.current.assertions.length + 1, result; if (!resemble._setup) { resemble._setup = true; resemble.outputSettings({ errorColor: { red: 255, green: 51, blue: 0 }, errorType: 'flat', transparency: 1 }); } resemble(actual.getImageData()) .compareTo(expected.getImageData()) .ignoreAntialiasing() // When working with imageData, this call is synchronous: .onComplete(function(data) { result = data; }); // 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, 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 var entry = document.getElementById('qunit-test-output-' + id) .querySelector('li:nth-child(' + (index) + ')'), bounds = result.diffBounds; entry.querySelector('.test-expected td').innerHTML = getImageTag(expected); entry.querySelector('.test-actual td').innerHTML = getImageTag(actual); entry.querySelector('.test-diff td').innerHTML = '
' + text + '