spritesmith
Version:
Utility that takes images and creates a spritesheet with JSON sprite data
256 lines (221 loc) • 9.48 kB
JavaScript
// Load in modules
var assert = require('assert');
var fs = require('fs');
var path = require('path');
var getPixels = require('get-pixels');
var pixelmatch = require('pixelmatch');
var Vinyl = require('vinyl');
var Spritesmith = require('../src/smith.js');
// Set up paths
var spriteDir = path.join(__dirname, 'test_sprites');
var expectedDir = __dirname + '/expected_files';
// DEV: These were unsorted for testing `sort: false` but these work for all tests as is =D
var multipleSprites = [
path.join(spriteDir, 'sprite1.png'),
path.join(spriteDir, 'sprite3.png'),
path.join(spriteDir, 'sprite2.jpg')
];
// Define common utilities
var spritesmithUtils = {
run: function (params) {
before(function runFn(done) {
// Attempt to process the sprites via Spritesmith
var that = this;
Spritesmith.run(params, function handleRun(err, result) {
that.err = err;
that.result = result;
done();
});
});
after(function cleanup() {
delete this.err;
delete this.result;
});
},
assertNoError: function () {
return function assertNoErrorFn() {
assert.strictEqual(this.err, null);
};
},
assertCoordinates: function (filename) {
return function assertCoordinatesFn() {
// Load in the coordinates
var result = this.result;
// DEV: Write out to actual_files
if (process.env.TEST_DEBUG) {
try { fs.mkdirSync(__dirname + '/actual_files'); } catch (e) { /* Ignore error */ }
fs.writeFileSync(__dirname + '/actual_files/' + filename, JSON.stringify(result.coordinates, null, 4));
}
// Normalize the actual coordinates
// eslint-disable-next-line global-require
var expectedCoords = require(expectedDir + '/' + filename);
var actualCoords = result.coordinates;
var normCoords = {};
assert(actualCoords, 'Result does not have a coordinates property');
Object.getOwnPropertyNames(actualCoords).forEach(function (filepath) {
var file = path.relative(spriteDir, filepath);
normCoords[file] = actualCoords[filepath];
});
// Assert that the returned coordinates deep equal those in the coordinates.json
assert.deepEqual(expectedCoords, normCoords, 'Actual coordinates do not match expected coordinates');
};
},
assertProps: function (filename) {
return function assertPropsFn() {
// Load in the properties
var result = this.result;
// DEV: Write out to actual_files
if (process.env.TEST_DEBUG) {
try { fs.mkdirSync(__dirname + '/actual_files'); } catch (e) { /* Ignore error */ }
fs.writeFileSync(__dirname + '/actual_files/' + filename, JSON.stringify(result.properties, null, 4));
}
// Assert that the returned properties equals the expected properties
var actualProps = result.properties;
// eslint-disable-next-line global-require
var expectedProps = require(expectedDir + '/' + filename);
assert.deepEqual(expectedProps, actualProps, 'Actual properties do not match expected properties');
};
},
assertSpritesheet: function (filename) {
return function assertSpritesheetFn(done) {
// Load our variables
var actualImageBuff = this.result.image;
var expectedFilepath = path.join(expectedDir, filename);
// DEV: Write out to actual_files
if (process.env.TEST_DEBUG) {
try { fs.mkdirSync(__dirname + '/actual_files'); } catch (e) { /* Ignore error */ }
fs.writeFileSync(__dirname + '/actual_files/' + filename, actualImageBuff);
}
// Assert the actual image is close to the expected image
// DEV: We are using pngjs for decoding/encoding in the library but this is testing one more cycle
getPixels(actualImageBuff, 'image/png', function handleActualPixels(err, actualImage) {
if (err) { return done(err); }
getPixels(expectedFilepath, function handleExpectedPixels(err, expectedImage) {
if (err) { return done(err); }
assert.deepEqual(actualImage.shape, expectedImage.shape,
'Actual image shape does not match expected image shape');
var numDiffPixels = pixelmatch(actualImage.data, expectedImage.data, null,
actualImage.shape[0], actualImage.shape[1]);
assert(numDiffPixels < 10, 'Expected at most 10 pixels to be different but received ' + numDiffPixels);
done();
});
});
};
}
};
// Start our tests
describe('An array of sprites', function () {
describe('when processed via spritesmith', function () {
spritesmithUtils.run({
src: multipleSprites
});
it('has no errors', spritesmithUtils.assertNoError());
it('renders a binary-tree spritesheet', spritesmithUtils.assertSpritesheet('binaryTree.pixelsmith.png'));
it('has the proper coordinates', spritesmithUtils.assertCoordinates('binaryTree.coordinates.json'));
it('has the proper properties', spritesmithUtils.assertProps('binaryTree.properties.json'));
});
describe('when converted from left to right', function () {
spritesmithUtils.run({
src: multipleSprites,
algorithm: 'left-right'
});
it('has no errors', spritesmithUtils.assertNoError());
it('renders a left-right spritesheet', spritesmithUtils.assertSpritesheet('leftRight.pixelsmith.png'));
it('has the proper coordinates', spritesmithUtils.assertCoordinates('leftRight.coordinates.json'));
it('has the proper properties', spritesmithUtils.assertProps('leftRight.properties.json'));
});
describe('when provided with a padding parameter', function () {
spritesmithUtils.run({
src: multipleSprites,
algorithm: 'binary-tree',
padding: 2
});
it('has no errors', spritesmithUtils.assertNoError());
it('renders a padded spritesheet', spritesmithUtils.assertSpritesheet('padding.pixelsmith.png'));
it('has the proper coordinates', spritesmithUtils.assertCoordinates('padding.coordinates.json'));
it('has the proper properties', spritesmithUtils.assertProps('padding.properties.json'));
});
describe('when told not to sort', function () {
spritesmithUtils.run({
src: multipleSprites,
algorithm: 'top-down',
algorithmOpts: {sort: false}
});
it('has no errors', spritesmithUtils.assertNoError());
it('renders an unsorted spritesheet', spritesmithUtils.assertSpritesheet('unsorted.pixelsmith.png'));
it('has the proper coordinates', spritesmithUtils.assertCoordinates('unsorted.coordinates.json'));
it('has the proper properties', spritesmithUtils.assertProps('unsorted.properties.json'));
});
});
describe('An array of vinyl object sprites', function () {
describe('when processed via spritesmith', function () {
spritesmithUtils.run({
src: multipleSprites.map(function createVinylObject(filepath) {
return new Vinyl({
path: filepath,
contents: fs.readFileSync(filepath)
});
})
});
it('has no errors', spritesmithUtils.assertNoError());
it('renders a binary-tree spritesheet', spritesmithUtils.assertSpritesheet('binaryTree.pixelsmith.png'));
it('has the proper coordinates', spritesmithUtils.assertCoordinates('binaryTree.coordinates.json'));
it('has the proper properties', spritesmithUtils.assertProps('binaryTree.properties.json'));
});
});
describe('An empty array', function () {
var emptySprites = [];
describe('when processed via spritesmith', function () {
spritesmithUtils.run({
src: emptySprites
});
it('has no errors', spritesmithUtils.assertNoError());
it('renders an empty spritesheet', function () {
assert.deepEqual(this.result.image, Buffer.alloc(0));
});
it('returns an empty coordinate mapping', function () {
assert.deepEqual(this.result.coordinates, {});
});
it('has the proper properties', spritesmithUtils.assertProps('empty.properties.json'));
});
});
describe('`spritesmith` using a custom engine via string', function () {
describe('processing a set of images', function () {
spritesmithUtils.run({
src: multipleSprites,
engine: 'phantomjssmith'
});
it('has no errors', spritesmithUtils.assertNoError());
it('renders a spritesheet', spritesmithUtils.assertSpritesheet('binaryTree.phantomjs.png'));
});
});
describe('`spritesmith` using a custom engine via an object', function () {
describe('processing a set of images', function () {
spritesmithUtils.run({
src: multipleSprites,
// eslint-disable-next-line global-require
engine: require('phantomjssmith')
});
it('has no errors', spritesmithUtils.assertNoError());
it('renders a spritesheet', spritesmithUtils.assertSpritesheet('binaryTree.phantomjs.png'));
});
});
// Edge cases
// Test for https://github.com/twolfson/gulp.spritesmith/issues/22
var canvassmith;
try {
// eslint-disable-next-line global-require
canvassmith = require('canvassmith');
} catch (err) { /* Ignore error */ }
var describeIfCanvassmithExists = canvassmith ? describe : describe.skip;
describeIfCanvassmithExists('`spritesmith` using `canvassmith`', function () {
describe('processing a bad image', function () {
spritesmithUtils.run({
src: [path.join(spriteDir, 'malformed.png')],
engine: 'canvassmith'
});
it('calls back with an error', function () {
assert.notEqual(this.err, null);
});
});
});