/*
* 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 = typeof window === 'object' && !!window.callPhantom,
scope;
if (isNode) {
scope = global;
// Resemble.js needs the Image constructor global.
global.Image = paper.window.Image;
} else {
scope = 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(scope);
// Override console.error, so that we can catch errors that are only logged to
// the console.
var errorHandler = console.error;
console.error = function() {
var current = QUnit.config.current;
if (current) {
QUnit.pushFailure([].join.call(arguments, ' '), 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) {
var parent = item.parent,
index = item.index;
group.addChild(item);
raster = group.rasterize(resolution, false);
if (parent) {
parent.insertChild(index, item);
} else {
item.remove();
}
}
return raster;
}
function getImageTag(raster) {
return '';
}
if (!expected) {
return QUnit.strictEqual(actual, expected, message, options);
} else if (!actual) {
// In order to compare pixels, just create an empty item that can be
// rasterized to an empty raster.
actual = new Group();
}
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'
})
]
}),
actualRaster = rasterize(actual, group, resolution),
expectedRaster = rasterize(expected, group, resolution);
if (!actualRaster || !expectedRaster) {
QUnit.push(false, null, null, 'Unable to compare rasterized items: ' +
(!actualRaster ? '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(actualRaster.getImageData())
.compareTo(expectedRaster.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 = tolerance < 1 ? ((1 / tolerance) + '').length - 1 : 0,
identical = result ? 100 - result.misMatchPercentage : 0,
ok = Math.abs(100 - identical) <= tolerance,
text = identical.toFixed(fixed) + '% identical',
detail = text;
if (!ok &&
actual instanceof PathItem && expected instanceof PathItem) {
detail += '\nExpected:\n' + expected.pathData +
'\nActual:\n' + actual.pathData;
}
QUnit.push(ok, text, (100).toFixed(fixed) + '% 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(expectedRaster);
entry.querySelector('.test-actual td').innerHTML =
getImageTag(actualRaster);
entry.querySelector('.test-diff td').innerHTML = '
' + detail + '