diff --git a/.jshintrc b/.jshintrc index 7a26e42f8..cfe3116ff 100644 --- a/.jshintrc +++ b/.jshintrc @@ -3,7 +3,8 @@ "phantom": false, "describe": false, "before": false, - "it": false + "it": false, + "beforeEach": false }, "node": true, "strict": "global", diff --git a/lib/svgo/coa.js b/lib/svgo/coa.js index d66b900c7..8a5ebda46 100644 --- a/lib/svgo/coa.js +++ b/lib/svgo/coa.js @@ -142,7 +142,7 @@ module.exports = require('coa').Cmd() // --show-plugins if (opts['show-plugins']) { showAvailablePlugins(); - process.exit(0); + return; } // w/o anything @@ -243,48 +243,56 @@ module.exports = require('coa').Cmd() // --folder if (opts.folder) { - optimizeFolder(opts.folder, config, output); - - return; + return optimizeFolder(opts.folder, config, output); } // --input if (input) { - // STDIN - if (input === '-') { - var data = ''; - - process.stdin.pause(); - - process.stdin - .on('data', function(chunk) { - data += chunk; - }) - .once('end', function() { - optimizeFromString(data, config, opts.datauri, input, output); - }) - .resume(); - // file - } else { - FS.readFile(input, 'utf8', function(err, data) { - if (err) { - if (err.code === 'EISDIR') - optimizeFolder(input, config, output); - else if (err.code === 'ENOENT') - console.error(`Error: no such file or directory '${input}'.`); - else - console.error(err); - return; - } - optimizeFromString(data, config, opts.datauri, input, output); - }); - } + return new Promise((resolve, reject) => { + // STDIN + if (input === '-') { + var data = ''; + + process.stdin.pause(); + + process.stdin + .on('data', function(chunk) { + data += chunk; + }) + .once('end', function() { + optimizeFromString(data, config, opts.datauri, input, output) + .then(resolve) + .catch(reject); + }) + .resume(); + // file + } else { + FS.readFile(input, 'utf8', function(err, data) { + if (err) { + switch(err.code) { + case 'EISDIR': + return optimizeFolder(input, config, output) + .then(resolve) + .catch(reject); + case 'ENOENT': + return reject(`Error: no such file or directory '${input}'.`); + default: + return reject(err); + } + } + + optimizeFromString(data, config, opts.datauri, input, output) + .then(resolve) + .catch(reject); + }); + } + }); // --string } else if (opts.string) { opts.string = decodeSVGDatauri(opts.string); - optimizeFromString(opts.string, config, opts.datauri, input, output); + return optimizeFromString(opts.string, config, opts.datauri, input, output); } }); @@ -295,7 +303,7 @@ function optimizeFromString(svgstr, config, datauri, input, output) { outBytes, svgo = new SVGO(config); - svgo.optimize(svgstr).then(function(result) { + return svgo.optimize(svgstr).then(function(result) { if (datauri) { result.data = encodeSVGDatauri(result.data, datauri); } @@ -305,7 +313,7 @@ function optimizeFromString(svgstr, config, datauri, input, output) { // stdout if (output === '-' || (!input || input === '-') && !output) { - process.stdout.write(result.data + '\n'); + console.log(result.data + '\n'); // file } else { // overwrite input file if there is no output @@ -317,23 +325,26 @@ function optimizeFromString(svgstr, config, datauri, input, output) { console.log('\r'); } - saveFileAndPrintInfo(config, result.data, output, inBytes, outBytes, time); + return saveFileAndPrintInfo(config, result.data, output, inBytes, outBytes, time); } - }, error => console.error(error)); + }).catch(console.error); } function saveFileAndPrintInfo(config, data, path, inBytes, outBytes, time) { - FS.writeFile(path, data, 'utf8', function() { - if (config.quiet) { - return; - } + return new Promise((resolve, reject) => { + FS.writeFile(path, data, 'utf8', function(err) { + if (err) return reject(err); - // print time info - printTimeInfo(time); + if (!config.quiet) { + // print time info + printTimeInfo(time); - // print optimization profit info - printProfitInfo(inBytes, outBytes); + // print optimization profit info + printProfitInfo(inBytes, outBytes); + } + resolve(); + }); }); } @@ -411,94 +422,96 @@ function optimizeFolder(dir, config, output) { var path = PATH.resolve(dir); // list folder content - FS.readdir(path, function(err, files) { - if (err) { - console.error(err); - return; - } - - if (!files.length) { - console.log(`Directory '${dir}' is empty.`); - return; - } - - var i = 0, - found = false; - - function optimizeFile(file) { - // absoluted file path - var filepath = PATH.resolve(path, file); - var fileStat = FS.lstatSync(filepath); - var outfilepath = output ? PATH.resolve(output, file) : filepath; - - if (config.recursive && fileStat.isDirectory()) { - optimizeFolder(filepath, config, output); + return new Promise((resolve, reject) => { + FS.readdir(path, function(err, files) { + if (err) { + return reject(err); } - // check if file name matches *.svg - if (regSVGFile.test(filepath)) { - found = true; - FS.readFile(filepath, 'utf8', function(err, data) { - if (err) { - console.error(err); - return; - } - - var startTime = Date.now(), - time, - inBytes = Buffer.byteLength(data, 'utf8'), - outBytes; + if (!files.length) { + console.log(`Directory '${dir}' is empty.`); + return resolve(); + } - svgo.optimize(data).then(function(result) { - outBytes = Buffer.byteLength(result.data, 'utf8'); - time = Date.now() - startTime; + var found = false; - writeOutput(); + Promise.all(files.map(optimizeFile)) + .then(() => { + if (!found) { + console.log('No SVG files have been found.'); + } + resolve(); + }) + .catch(reject); + + function optimizeFile(file) { + // absoluted file path + var filepath = PATH.resolve(path, file); + var fileStat = FS.lstatSync(filepath); + var outfilepath = output ? PATH.resolve(output, file) : filepath; + + if (config.recursive && fileStat.isDirectory()) { + return optimizeFolder(filepath, config, output); + } - function writeOutput() { - FS.writeFile(outfilepath, result.data, 'utf8', report); - } + // check if file name matches *.svg + if (regSVGFile.test(file)) { + found = true; - function report(err) { + return new Promise((resolve, reject) => { + FS.readFile(filepath, 'utf8', function(err, data) { if (err) { - if (err.code === 'ENOENT') { - mkdirp(output, writeOutput); - return; - } else if (err.code === 'ENOTDIR') { - console.error(`Error: output '${output}' is not a directory.`); - return; - } - console.error(err); + reject(err); return; } - if (!config.quiet) { - console.log(file + ':'); - - // print time info - printTimeInfo(time); - - // print optimization profit info - printProfitInfo(inBytes, outBytes); - } - - //move on to the next file - if (++i < files.length) { - optimizeFile(files[i]); - } - } - }, error => console.error(error)); - }); - } - //move on to the next file - else if (++i < files.length) { - optimizeFile(files[i]); - } else if (!found) { - console.log('No SVG files have been found.'); + var startTime = Date.now(), + time, + inBytes = Buffer.byteLength(data, 'utf8'), + outBytes; + + svgo.optimize(data) + .then(function(result) { + outBytes = Buffer.byteLength(result.data, 'utf8'); + time = Date.now() - startTime; + + writeOutput(); + + function writeOutput() { + FS.writeFile(outfilepath, result.data, 'utf8', report); + } + + function report(err) { + if (err) { + switch(err.code) { + case 'ENOENT': + return mkdirp(output, writeOutput); + case 'ENOTDIR': + return reject(`Error: output '${output}' is not a directory.`); + default: + return reject(err); + } + } + + if (!config.quiet) { + console.log(file + ':'); + + // print time info + printTimeInfo(time); + + // print optimization profit info + printProfitInfo(inBytes, outBytes); + } + + resolve(); + } + }) + .catch(reject); + }); + }); + } } - } - - optimizeFile(files[i]); + }); }); } diff --git a/package.json b/package.json index 3839ed584..ef31e48ae 100644 --- a/package.json +++ b/package.json @@ -48,12 +48,14 @@ "jshint": "jshint --show-non-errors ." }, "dependencies": { - "sax": "~1.2.1", "coa": "~1.0.1", - "js-yaml": "~3.7.0", "colors": "~1.1.2", + "csso": "~2.3.1", + "fs-extra": "^4.0.1", + "js-yaml": "~3.7.0", "mkdirp": "~0.5.1", - "csso": "~2.3.1" + "mock-stdin": "^0.3.1", + "sax": "~1.2.1" }, "devDependencies": { "mocha": "~3.2.0", diff --git a/test/coa/_index.js b/test/coa/_index.js new file mode 100644 index 000000000..9621cbaf5 --- /dev/null +++ b/test/coa/_index.js @@ -0,0 +1,169 @@ +'use strict'; + +const fs = require('fs'), + svgo = require(process.env.COVERAGE ? + '../../lib-cov/svgo/coa.js' : + '../../lib/svgo/coa.js').api, + path = require('path'), + svgPath = path.resolve(__dirname, 'test.svg'), + svgFolderPath = path.resolve(__dirname, 'testSvg'), + fse = require('fs-extra'); + +describe('coa', function() { + let output; + + beforeEach(function() { + output = ''; + + if(fs.existsSync('temp')) { + fse.removeSync('temp'); + } + + fs.mkdirSync('temp'); + }); + + function replaceConsoleLog() { + const initialConsoleLog = global.console.log; + + global.console.log = function() { + output += arguments[0]; + + initialConsoleLog.apply(console, arguments); + }; + } + + function replaceConsoleError() { + const initialConsoleError = global.console.error; + + global.console.error = function() { + output += arguments[0]; + + initialConsoleError.apply(console, arguments); + }; + } + + function calcFolderSvgWeight(folderPath) { + return fs.readdirSync(folderPath).reduce((initWeight, fileName) => { + return initWeight + + (/.svg/.test(fileName) ? fs.statSync(folderPath + '/' + fileName).size : 0); + }, 0); + } + + it('should throw an error if "config" can not be parsed', function(done) { + replaceConsoleError(); + + svgo({ input: svgPath, config: '{' }) + .then(() => done( /Error: Couldn't parse config JSON/.test(output) ? null : 'Error was not thrown' )); + }); + + it('should work properly with string input', function(done) { + svgo({ string: fs.readFileSync(svgPath, 'utf8'), output: 'temp.svg' }) + .then(done); + }); + + it('should optimize folder', function(done) { + const initWeight = calcFolderSvgWeight(svgFolderPath); + + svgo({ folder: svgFolderPath }) + .then(() => { + const optimizedWeight = calcFolderSvgWeight(svgFolderPath); + + done(initWeight <= optimizedWeight ? null : 'Folder was not optimized'); + }); + }); + + it('should optimize file', function(done) { + const initialFileLength = fs.readFileSync(path.resolve(__dirname, 'test.svg')).length; + + svgo({ input: svgPath, output: 'temp.svg' }) + .then(() => { + const optimizedFileLength = fs.readFileSync('temp.svg').length; + + done(optimizedFileLength <= initialFileLength ? null : 'File was not optimized'); + }); + }); + + it('should optimize file from process.stdin', function(done) { + const initialFile = fs.readFileSync(path.resolve(__dirname, 'test.svg')); + + const stdin = require('mock-stdin').stdin(); + + setTimeout(() => { stdin.send(initialFile, 'ascii').end(); }, 0); + + svgo({ input: '-', output: 'temp.svg', string: fs.readFileSync(svgPath, 'utf8') }) + .then(() => { + const optimizedFileLength = fs.readFileSync('temp.svg').length; + + done(optimizedFileLength <= initialFile.length ? null : 'Files were not optimized'); + }); + }); + + it('should optimize folder, when it stated in input', function(done) { + const initWeight = calcFolderSvgWeight(svgFolderPath); + + svgo({ input: svgFolderPath, output: 'temp' }) + .then(() => { + let optimizedWeight = calcFolderSvgWeight(svgFolderPath); + + done(initWeight <= optimizedWeight ? null : 'Files were not optimized'); + }); + }); + + it('should throw error when stated in input folder does not exist', function(done) { + svgo({ input: svgFolderPath + 'temp', output: 'temp' }) + .catch(err => done(/no such file or directory/.test(err) ? null : 'Error was not thrown')); + }); + + describe('stdout', function() { + it('should show file content when no output set', function(done) { + replaceConsoleLog(); + + svgo({ string: fs.readFileSync(svgPath, 'utf8'), output: '-', datauri: 'unenc' }) + .then(() => done(/www.w3.org\/2000\/svg/.test(output) ? null : 'File content was not shown')); + }); + + it('should show message when the folder is empty', function(done) { + const emptyFolderPath = path.resolve(__dirname, 'testSvgEmpty'); + if(!fs.existsSync(emptyFolderPath)) + fs.mkdirSync(emptyFolderPath); + + replaceConsoleLog(); + + svgo({ folder: emptyFolderPath }) + .then(() => done(/is empty/.test(output) ? null : 'Empty folder message was not shown')); + }); + + it('should show message when folder does not consists any svg files', function(done) { + replaceConsoleLog(); + + svgo({ folder: path.resolve(__dirname, 'testFolderWithNoSvg') }) + .then(() => done(/No SVG files have been found/.test(output) ? + null : 'Error "No SVG files have been found" was not shown')); + }); + + it('should create output directory when it does not exist', function(done) { + const initWeight = calcFolderSvgWeight(svgFolderPath); + const outputFolder = path.resolve(__dirname, 'temp'); + + replaceConsoleLog(); + + if(fs.existsSync(outputFolder)) { + fse.removeSync(outputFolder); + } + + svgo({ folder: svgFolderPath, output: outputFolder }) + .then(() => { + const optimizedWeight = calcFolderSvgWeight(outputFolder); + + done(initWeight <= optimizedWeight ? null : 'Files were not optimized'); + }); + }); + + it('should show plugins', function(done) { + replaceConsoleLog(); + + svgo({ 'show-plugins': true }) + .then(() => done(/Currently available plugins:/.test(output) ? null : 'List of plugins was not shown')); + }); + }); +}); diff --git a/test/coa/test.svg b/test/coa/test.svg new file mode 100644 index 000000000..de7975de7 --- /dev/null +++ b/test/coa/test.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/coa/testFolderWithNoSvg/test b/test/coa/testFolderWithNoSvg/test new file mode 100644 index 000000000..e69de29bb diff --git a/test/coa/testSvg/test.1.svg b/test/coa/testSvg/test.1.svg new file mode 100644 index 000000000..de7975de7 --- /dev/null +++ b/test/coa/testSvg/test.1.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/coa/testSvg/test.svg b/test/coa/testSvg/test.svg new file mode 100644 index 000000000..de7975de7 --- /dev/null +++ b/test/coa/testSvg/test.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/mocha.opts b/test/mocha.opts index 3b56cd1ac..b4f63a92f 100644 --- a/test/mocha.opts +++ b/test/mocha.opts @@ -5,3 +5,4 @@ test/svg2js test/plugins test/jsapi test/svgo +test/coa