├── .editorconfig ├── .gitignore ├── .jshintrc ├── .npmignore ├── .travis.yml ├── Gruntfile.js ├── LICENSE-MIT ├── README.md ├── package.json ├── tasks ├── lib │ └── runner.js └── smushit.js └── test ├── build_cases.js ├── cases.json ├── expected └── single │ ├── dp.jpg │ └── dp.png ├── fixtures └── single │ ├── dp.jpg │ └── dp.png └── smushit_test.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # http://editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | 9 | # Change these settings to your own preference 10 | indent_style = space 11 | indent_size = 4 12 | 13 | # We recommend you to keep these unchanged 14 | end_of_line = lf 15 | charset = utf-8 16 | trim_trailing_whitespace = true 17 | insert_final_newline = true 18 | 19 | [*.md] 20 | trim_trailing_whitespace = false 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | test/tmp/ 4 | test/fixtures/nested1 5 | test/fixtures/nested2 6 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "curly": true, 3 | "eqeqeq": true, 4 | "immed": true, 5 | "latedef": true, 6 | "newcap": true, 7 | "noarg": true, 8 | "sub": true, 9 | "undef": true, 10 | "boss": true, 11 | "eqnull": true, 12 | "node": true, 13 | "white": true, 14 | "indent": 4, 15 | "strict": true, 16 | "trailing": true 17 | } 18 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test/ 2 | test/tmp/ 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - "stable" 5 | - "4" 6 | - "0.12" 7 | - "0.10" 8 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | /* 2 | * grunt-smushit 3 | * https://github.com/heldr/grunt-smushit 4 | * 5 | * Copyright (c) 2013 Helder Santana 6 | * Licensed under the MIT license. 7 | */ 8 | 9 | 'use strict'; 10 | 11 | require('./test/build_cases'); 12 | 13 | module.exports = function (grunt) { 14 | var cases = grunt.file.readJSON('test/cases.json'); 15 | 16 | grunt.initConfig({ 17 | jshint: { 18 | all: [ 19 | 'Gruntfile.js', 20 | 'tasks/**/*.js', 21 | 'test/build_cases.js', 22 | '<%= nodeunit.tests %>' 23 | ], 24 | options: { 25 | jshintrc: '.jshintrc', 26 | } 27 | }, 28 | 29 | // Configuration to be run (and then tested). 30 | smushit: cases, 31 | 32 | // Unit tests. 33 | nodeunit: { 34 | tests: ['test/*_test.js'], 35 | } 36 | }); 37 | 38 | grunt.task.registerTask('smushit-noverbose', 'run smushit without log', function () { 39 | grunt.config.merge({ 40 | smushit: { 41 | options: { 42 | verbose: false 43 | } 44 | } 45 | }); 46 | grunt.task.run(['smushit']); 47 | }); 48 | 49 | grunt.loadTasks('tasks'); 50 | 51 | grunt.loadNpmTasks('grunt-contrib-jshint'); 52 | grunt.loadNpmTasks('grunt-contrib-nodeunit'); 53 | 54 | grunt.registerTask('test', ['jshint', 'smushit', 'nodeunit']); 55 | 56 | grunt.registerTask('default', ['test']); 57 | }; 58 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Helder Santana 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # grunt-smushit [![Build Status](https://secure.travis-ci.org/heldr/grunt-smushit.svg)](http://travis-ci.org/heldr/grunt-smushit) 2 | 3 | Grunt plugin to remove unnecessary bytes of PNG and JPG using reSmush.it 4 | 5 | > reSmush.it is a FREE alternative to Yahoo Smush.it (deprecated on March 2015). This tool provides a online way to optimize pictures size via a documented webservice. 6 | 7 | [Read more about reSmush.it](http://resmush.it/) 8 | 9 | Prefer Gulp? [gulp-smushit](https://github.com/heldr/gulp-smushit) 10 | 11 | ## Getting Started 12 | This plugin requires Grunt `0.4.x` 13 | 14 | If you haven't used [Grunt](http://gruntjs.com/) before, be sure to check out the [Getting Started](http://gruntjs.com/getting-started) guide, as it explains how to create a [Gruntfile](http://gruntjs.com/sample-gruntfile) as well as install and use Grunt plugins. Once you're familiar with that process, you may install this plugin with this command: 15 | 16 | ```shell 17 | npm install grunt-smushit --save-dev 18 | ``` 19 | 20 | Once the plugin has been installed, it may be enabled inside your Gruntfile with this line of JavaScript: 21 | 22 | ```js 23 | grunt.loadNpmTasks('grunt-smushit'); 24 | ``` 25 | 26 | ## The "smushit" task 27 | 28 | ### Overview 29 | In your project's Gruntfile, add a section named `smushit` to the data object passed into `grunt.initConfig()`. 30 | 31 | ```js 32 | grunt.initConfig({ 33 | smushit: { 34 | mygroup: { 35 | src: ['tests/img/**/*.png','tests/img/**/*.jpg'], 36 | dest: 'tests/img/min' 37 | } 38 | } 39 | }); 40 | ``` 41 | 42 | ### Options 43 | 44 | #### options.service 45 | Type: `String` 46 | Default value: `null` 47 | Call another service instead of Yahoo Smushit 48 | 49 | #### options.verbose 50 | Type: `boolean` 51 | Default value: `true` 52 | Show image compressing rate log 53 | 54 | ### Usage Examples 55 | 56 | #### Output Folder 57 | Move your files to a target folder (should not be into the same). 58 | 59 | ```js 60 | grunt.initConfig({ 61 | smushit: { 62 | 63 | // catch all files from a single folder 64 | group1: { 65 | src: 'tests/img', 66 | dest: 'tests/opt_img' 67 | }, 68 | 69 | // filter by filetype 70 | group2: { 71 | src: ['tests/img/**/*.png','tests/img/**/*.jpg'], 72 | dest: 'tests/img/min' 73 | }, 74 | 75 | // set each file 76 | group3:{ 77 | src: ['tests/img/logo.png','tests/img/whatever.png'], 78 | dest: 'tests/img/min' 79 | } 80 | 81 | } 82 | }); 83 | ``` 84 | 85 | #### Replace files 86 | Be safe to replace all of your old files with this option. 87 | 88 | ```js 89 | grunt.initConfig({ 90 | smushit: { 91 | 92 | // a single directory 93 | group1: { 94 | src: 'tests/img' 95 | }, 96 | 97 | // filter by filetype 98 | group2: { 99 | src: ['tests/img/**/*.png','tests/img/**/*.jpg'] 100 | }, 101 | 102 | //replace recursive 103 | group3: { 104 | src: ['tests/img/logo.png','tests/img/tellme.jpg'] 105 | } 106 | 107 | } 108 | }); 109 | ``` 110 | 111 | #### Work with a nested folder 112 | Recursively walk into folders and smushit files 113 | 114 | ```js 115 | grunt.initConfig({ 116 | smushit: { 117 | 118 | // catch all files from a nested folder 119 | group1: { 120 | src: 'tests/nested/img', 121 | dest: 'tests/opt_img' 122 | }, 123 | 124 | // filter files in a nested folder by filetype 125 | group2: { 126 | src: ['tests/nested/img/**/*.png','tests/nested/img/**/*.jpg'], 127 | dest: 'tests/opt_img' 128 | }, 129 | 130 | // retrieve files in a nested folder by file name 131 | group3: { 132 | src: ['tests/nested/img/**/southpark.png','tests/nested/img/**/southpark.jpg'], 133 | dest: 'tests/opt_img' 134 | }, 135 | } 136 | }); 137 | ``` 138 | 139 | #### Provide multiple source 140 | Smushit one folder, or many of them 141 | 142 | ```js 143 | grunt.initConfig({ 144 | smushit: { 145 | 146 | // catch all files from a nested folder 147 | group1: { 148 | src: ['tests/img1', 'tests/img2'], 149 | dest: 'tests/opt_img' 150 | }, 151 | 152 | // filter files in a folder by filetype 153 | group2: { 154 | src: ['tests/img1/**/*.png','tests/img2/**/*.jpg'], 155 | dest: 'tests/opt_img' 156 | }, 157 | 158 | // retrieve files in a folder by file name 159 | group3: { 160 | src: ['tests/img1/**/southpark.png','tests/img2/**/southpark.jpg'], 161 | dest: 'tests/opt_img' 162 | }, 163 | } 164 | }); 165 | ``` 166 | 167 | #### Use of cwd 168 | Provide your base directory 169 | 170 | ```js 171 | grunt.initConfig({ 172 | smushit: { 173 | 174 | // src folder is 'tests/img' and dest is 'tests/opt_img' 175 | group1: { 176 | cwd: 'tests' 177 | expand: true, 178 | src: 'img', 179 | dest: 'tests/opt_img' 180 | }, 181 | 182 | // multiple src folders: src folder is ['tests/img1', 'tests/img2'] and dest is 'tests/img/min' 183 | group2: { 184 | cwd: 'tests' 185 | expand: true, 186 | src: ['img1/**/*.png','img2/**/*.jpg'], 187 | dest: 'tests/img/min' 188 | }, 189 | } 190 | }); 191 | ``` 192 | 193 | 194 | #### Your own service 195 | There is an option that you can set your own image optimizer service. Its a good alternative if you don't want to wait for smush.it web service latency. 196 | 197 | ```js 198 | grunt.initConfig({ 199 | smushit: { 200 | options: { 201 | service: 'http://myimgopt.com/exec' 202 | }, 203 | mygroup: { 204 | src: ['tests/img/**/*.png','tests/img/**/*.jpg'], 205 | dest: 'tests/img/min' 206 | } 207 | } 208 | }); 209 | ``` 210 | 211 | ## Contributing 212 | 213 | ```cli 214 | $ git clone git://github.com/heldr/grunt-smushit.git 215 | $ cd grunt-smushit 216 | $ npm install 217 | $ npm test 218 | ``` 219 | NOTE: Be sure to keep up to date the plugin tests and jshint code quality. 220 | 221 | ## Release History 222 | * 2015-09-05 v2.0.0 Use resmush.it due Yahoo Smushit deprecation 223 | * 2014-05-19 v1.3.0 Pass node-smushit options through grunt file 224 | * 2014-03-30 v1.2.1 Bugfix. Fixes [issue 29](https://github.com/heldr/grunt-smushit/issues/29) 225 | * 2014-03-03   v1.2.0   Use cwd only for source files, following the [grunt pattern][grunt-cwd-pattern] 226 | * 2013-07-15   v1.1.0   Support nested folder structure, support for multiple source folders 227 | * 2013-07-15   v1.1.0   Enable the use of cwd parameter 228 | * 2013-06-16   v1.0.0   Rewrite task on top of [grunt-init-gruntplugin][grunt-init-gruntplugin] 229 | * 2013-05-26   v0.4.2   Add support to different service #16 230 | 231 | ## Credits 232 | * [node-smushit][node-smushit] 233 | 234 | ## License 235 | 236 | MIT License 237 | (c) [Helder Santana](http://heldr.com) 238 | 239 | [node-smushit]: https://github.com/colorhook/node-smushit 240 | [grunt-init-gruntplugin]: https://github.com/gruntjs/grunt-init-gruntplugin 241 | [grunt-cwd-pattern]: http://gruntjs.com/configuring-tasks#building-the-files-object-dynamically 242 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "grunt-smushit", 3 | "description": "Grunt plugin to remove unnecessary bytes of PNG and JPG using Yahoo Smushit", 4 | "version": "2.1.0", 5 | "homepage": "https://github.com/heldr/grunt-smushit", 6 | "author": { 7 | "name": "Helder Santana", 8 | "email": "heldr@me.com", 9 | "url": "http://heldr.com" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git://github.com/heldr/grunt-smushit.git" 14 | }, 15 | "bugs": { 16 | "url": "https://github.com/heldr/grunt-smushit/issues" 17 | }, 18 | "licenses": [ 19 | { 20 | "type": "MIT", 21 | "url": "https://github.com/heldr/grunt-smushit/blob/master/LICENSE-MIT" 22 | } 23 | ], 24 | "main": "Gruntfile.js", 25 | "engines": { 26 | "node": ">= 0.8.0" 27 | }, 28 | "scripts": { 29 | "test": "grunt test" 30 | }, 31 | "devDependencies": { 32 | "grunt-contrib-clean": "^1.0.0", 33 | "grunt-contrib-nodeunit": "^1.0.0", 34 | "grunt": "^0.4.5", 35 | "grunt-contrib-copy": "^1.0.0", 36 | "grunt-contrib-jshint": "^1.0.0", 37 | "wrench": "^1.5.8", 38 | "grunt-cli": "^1.1.0" 39 | }, 40 | "peerDependencies": { 41 | "grunt": ">=0.4.0" 42 | }, 43 | "keywords": [ 44 | "smushit", 45 | "png", 46 | "jpg", 47 | "gif", 48 | "image optimizer", 49 | "compress image", 50 | "image", 51 | "web performance", 52 | "webperf", 53 | "gruntplugin" 54 | ], 55 | "dependencies": { 56 | "node-smushit": "~0.5.1" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /tasks/lib/runner.js: -------------------------------------------------------------------------------- 1 | module.exports = function (target, done) { 2 | 'use strict'; 3 | 4 | var smushit = require('node-smushit'), 5 | smushit_settings = this.options({ 6 | onComplete: function (response) { 7 | done(true); 8 | }, 9 | service: 'http://api.resmush.it/ws.php' 10 | }); 11 | 12 | if (!smushit_settings.recursive) { 13 | smushit_settings.recursive = true; 14 | } 15 | 16 | smushit.smushit(target, smushit_settings); 17 | }; 18 | -------------------------------------------------------------------------------- /tasks/smushit.js: -------------------------------------------------------------------------------- 1 | /* 2 | * grunt-smushit 3 | * https://github.com/heldr/grunt-smushit 4 | * 5 | * Copyright (c) 2013 Helder Santana 6 | * Licensed under the MIT license. 7 | */ 8 | 9 | 'use strict'; 10 | 11 | var path = require('path'); 12 | var runner = require('./lib/runner'); 13 | 14 | function getSubPath(fileDir, filepath) { 15 | fileDir = path.normalize(fileDir.replace(/\/(\*\*\/|\*)[\w\W]*[\/]?$/, "") + '/'); // regex: remove **/... or *... if it's a dir needed to be expanded 16 | 17 | return path.normalize(filepath).substring(fileDir.length); 18 | } 19 | 20 | module.exports = function (grunt) { 21 | 22 | grunt.registerMultiTask('smushit', 'A Grunt task to remove unnecessary bytes of PNG and JPG using Yahoo Smushit.', function () { 23 | 24 | if (!this.files[0]) { 25 | grunt.fail.fatal('No src or invalid src provided.'); 26 | 27 | return; 28 | } 29 | 30 | var done = this.async(), 31 | src = this.files[0].orig.src, 32 | dest = this.files[0].orig.dest, 33 | cwd = this.files[0].orig.cwd, 34 | target, finalpath; 35 | 36 | if (dest) { // if we have dest param, copy everything from source to dest 37 | target = dest; 38 | 39 | if (!grunt.file.exists(target)) { 40 | grunt.file.mkdir(target); 41 | } 42 | 43 | src.forEach(function (source) { 44 | 45 | source = (cwd) ? cwd + '/' + source : source; // if cwd is provided, add cwd into the source path so we can expand 46 | 47 | if (grunt.option('verbose')) { 48 | grunt.log.writeln(source); 49 | } 50 | 51 | if (grunt.file.isDir(source)) { 52 | grunt.file.recurse(source, function (abspath, rootdir, subdir, filename) { 53 | finalpath = (subdir) ? target + '/' + subdir + '/' + filename : target + '/' + filename; 54 | grunt.file.copy(abspath, finalpath); 55 | }); 56 | } else { 57 | 58 | if (!cwd || grunt.file.doesPathContain(cwd, source)) { // if there is no cwd, we won't test if it's in cwd path (for obvious reason) 59 | 60 | if (!grunt.file.exists(source.replace(/\/(\*\*\/|\*)[\w\W]*[\/]?$/, ""))) { 61 | grunt.log.error().error('Path "' + source + '" not found.'); 62 | } 63 | 64 | grunt.file.expand(source).forEach(function (filepath) { 65 | var fileDir = (grunt.file.isDir(source)) ? source : path.dirname(source); 66 | finalpath = target + '/' + getSubPath(fileDir, filepath); 67 | grunt.file.copy(filepath, finalpath); 68 | }); 69 | 70 | } else { 71 | 72 | grunt.log.error().error('Path "' + path.join(cwd, source) + '" is not in cwd (' + cwd + ')'); 73 | grunt.log.error('Smushit will not process this path'); 74 | 75 | } 76 | } 77 | }); 78 | 79 | } else { 80 | this.files.forEach(function (f) { 81 | src = f.src.filter(function (filepath) { 82 | if (!grunt.file.exists(filepath)) { 83 | grunt.log.warn('Path "' + filepath + '" not found.'); 84 | return false; 85 | } else { 86 | return true; 87 | } 88 | }); 89 | }); 90 | 91 | target = src; 92 | } 93 | 94 | runner.call(this, target, done); 95 | }); 96 | }; 97 | -------------------------------------------------------------------------------- /test/build_cases.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = (function () { 4 | var fs = require('fs'), 5 | wrench = require('wrench'), 6 | tmp_path = 'test/tmp/', 7 | source_path = 'test/fixtures/', 8 | source_folder, target_folders, 9 | nested_folders; 10 | 11 | // clean tmp path if it exist 12 | if (fs.existsSync(tmp_path)) { 13 | wrench.rmdirSyncRecursive(tmp_path); 14 | } 15 | fs.mkdirSync(tmp_path); 16 | 17 | source_folder = source_path + 'single'; 18 | 19 | // set up src files for nested folders 20 | nested_folders = ['nested1', 'nested1/a', 'nested1/a/b', 'nested2', 'nested2/a1', 'nested2/a1/b2']; 21 | nested_folders.forEach(function (folder) { 22 | folder = source_path + folder; 23 | 24 | if (fs.existsSync(folder)) { // remove the folder if it exists previously 25 | wrench.rmdirSyncRecursive(folder); 26 | } 27 | wrench.copyDirSyncRecursive(source_folder, folder); 28 | }); 29 | 30 | // replace file cases 31 | target_folders = ['replace_single_dir', 'replace_single_filter', 'replace_multiple_filters', 'replace_single_file', 'replace_multiple_files']; 32 | target_folders.forEach(function (folder) { 33 | folder = tmp_path + folder; 34 | wrench.copyDirSyncRecursive(source_folder, folder); 35 | }); 36 | 37 | }()); 38 | -------------------------------------------------------------------------------- /test/cases.json: -------------------------------------------------------------------------------- 1 | { 2 | "replace_single_dir": { 3 | "src": "test/tmp/replace_single_dir" 4 | }, 5 | "replace_single_file": { 6 | "src": "test/tmp/replace_single_file/dp.png" 7 | }, 8 | "replace_single_filter": { 9 | "src": ["test/tmp/replace_single_filter/**/*.png"] 10 | }, 11 | "replace_multiple_filters": { 12 | "src": ["test/tmp/replace_multiple_filters/**/*.png", "test/tmp/replace_multiple_filters/**/*.jpg"] 13 | }, 14 | "replace_multiple_files": { 15 | "src": ["test/tmp/replace_multiple_files/dp.png", "test/tmp/replace_multiple_files/dp.jpg"] 16 | }, 17 | "output_single_dir": { 18 | "src": "test/fixtures/single", 19 | "dest": "test/tmp/output_single_dir" 20 | }, 21 | "output_single_dir_with_sub": { 22 | "src": "test/fixtures", 23 | "dest": "test/tmp/output_single_dir_with_sub" 24 | }, 25 | "output_single_file": { 26 | "src": "test/fixtures/single/dp.png", 27 | "dest": "test/tmp/output_single_file" 28 | }, 29 | "output_single_filter": { 30 | "src": ["test/fixtures/single/**/*.png"], 31 | "dest": "test/tmp/output_single_filter" 32 | }, 33 | "output_multiple_filters": { 34 | "src": ["test/fixtures/single/**/*.png", "test/fixtures/single/**/*.jpg"], 35 | "dest": "test/tmp/output_multiple_filters" 36 | }, 37 | "output_multiple_files": { 38 | "src": ["test/fixtures/single/dp.png", "test/fixtures/single/dp.jpg"], 39 | "dest": "test/tmp/output_multiple_files" 40 | }, 41 | "output_single_dir_with_cwd": { 42 | "cwd": "test", 43 | "expand": true, 44 | "src": "fixtures/single", 45 | "dest": "test/tmp/output_single_dir_with_cwd" 46 | }, 47 | "output_single_dir_with_sub_with_cwd": { 48 | "cwd": "test", 49 | "expand": true, 50 | "src": "fixtures/single", 51 | "dest": "test/tmp/output_single_dir_with_sub_with_cwd" 52 | }, 53 | "output_single_file_with_cwd": { 54 | "cwd": "test", 55 | "expand": true, 56 | "src": "fixtures/single/dp.png", 57 | "dest": "test/tmp/output_single_file_with_cwd" 58 | }, 59 | "output_single_filter_with_cwd": { 60 | "cwd": "test", 61 | "expand": true, 62 | "src": ["fixtures/single/**/*.png"], 63 | "dest": "test/tmp/output_single_filter_with_cwd" 64 | }, 65 | "output_multiple_filters_with_cwd": { 66 | "cwd": "test", 67 | "expand": true, 68 | "src": ["fixtures/single/**/*.png", "fixtures/single/**/*.jpg"], 69 | "dest": "test/tmp/output_multiple_filters_with_cwd" 70 | }, 71 | "output_multiple_files_with_cwd": { 72 | "cwd": "test", 73 | "expand": true, 74 | "src": ["fixtures/single/dp.png", "fixtures/single/dp.jpg"], 75 | "dest": "test/tmp/output_multiple_files_with_cwd" 76 | }, 77 | "output_single_nested_dir": { 78 | "src": ["test/fixtures/nested1"], 79 | "dest": "test/tmp/output_single_nested_dir" 80 | }, 81 | "output_single_nested_dir_ending_with_slash": { 82 | "src": ["test/fixtures/nested1/"], 83 | "dest": "test/tmp/output_single_nested_dir_ending_with_slash" 84 | }, 85 | "output_single_nested_dir_with_filter": { 86 | "src": ["test/fixtures/nested1/**/*.png"], 87 | "dest": "test/tmp/output_single_nested_dir_with_filter" 88 | }, 89 | "output_multiple_nested_dir": { 90 | "src": ["test/fixtures/nested1", "test/fixtures/nested2"], 91 | "dest": "test/tmp/output_multiple_nested_dir" 92 | }, 93 | "output_multiple_nested_dir_with_filter": { 94 | "src": ["test/fixtures/nested1/**/*.png", "test/fixtures/nested2/**/*.jpg"], 95 | "dest": "test/tmp/output_multiple_nested_dir_with_filter" 96 | }, 97 | "output_single_nested_dir_with_cwd": { 98 | "cwd": "test", 99 | "expand": true, 100 | "src": ["fixtures/nested1"], 101 | "dest": "test/tmp/output_single_nested_dir_with_cwd" 102 | }, 103 | "output_single_nested_dir_ending_with_slash_with_cwd": { 104 | "cwd": "test", 105 | "expand": true, 106 | "src": ["fixtures/nested1/"], 107 | "dest": "test/tmp/output_single_nested_dir_ending_with_slash_with_cwd" 108 | }, 109 | "output_single_nested_dir_with_filter_with_cwd": { 110 | "cwd": "test", 111 | "expand": true, 112 | "src": ["fixtures/nested1/**/*.png"], 113 | "dest": "test/tmp/output_single_nested_dir_with_filter_with_cwd" 114 | }, 115 | "output_multiple_nested_dir_with_cwd": { 116 | "cwd": "test", 117 | "expand": true, 118 | "src": ["fixtures/nested1", "fixtures/nested2"], 119 | "dest": "test/tmp/output_multiple_nested_dir_with_cwd" 120 | }, 121 | "output_multiple_nested_dir_with_filter_with_cwd": { 122 | "cwd": "test", 123 | "expand": true, 124 | "src": ["fixtures/nested1/**/*.png", "fixtures/nested2/**/*.jpg"], 125 | "dest": "test/tmp/output_multiple_nested_dir_with_filter_with_cwd" 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /test/expected/single/dp.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heldr/grunt-smushit/b85f8d0185f5bc3f47109a4b7014eea53dfa762f/test/expected/single/dp.jpg -------------------------------------------------------------------------------- /test/expected/single/dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heldr/grunt-smushit/b85f8d0185f5bc3f47109a4b7014eea53dfa762f/test/expected/single/dp.png -------------------------------------------------------------------------------- /test/fixtures/single/dp.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heldr/grunt-smushit/b85f8d0185f5bc3f47109a4b7014eea53dfa762f/test/fixtures/single/dp.jpg -------------------------------------------------------------------------------- /test/fixtures/single/dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heldr/grunt-smushit/b85f8d0185f5bc3f47109a4b7014eea53dfa762f/test/fixtures/single/dp.png -------------------------------------------------------------------------------- /test/smushit_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var grunt = require('grunt'), 4 | fs = require('fs'), 5 | PNG_FILE_NAME = 'dp.png', 6 | JPG_FILE_NAME = 'dp.jpg', 7 | EXPECTED_PNG_SIZE = null, 8 | EXPECTED_JPG_SIZE = null; 9 | 10 | /* 11 | ======== A Handy Little Nodeunit Reference ======== 12 | https://github.com/caolan/nodeunit 13 | 14 | Test methods: 15 | test.expect(numAssertions) 16 | test.done() 17 | Test assertions: 18 | test.ok(value, [message]) 19 | test.equal(actual, expected, [message]) 20 | test.notEqual(actual, expected, [message]) 21 | test.deepEqual(actual, expected, [message]) 22 | test.notDeepEqual(actual, expected, [message]) 23 | test.strictEqual(actual, expected, [message]) 24 | test.notStrictEqual(actual, expected, [message]) 25 | test.throws(block, [error], [message]) 26 | test.doesNotThrow(block, [error], [message]) 27 | test.ifError(value) 28 | */ 29 | 30 | function getSize(file) { 31 | return fs.lstatSync(file).size; 32 | } 33 | 34 | EXPECTED_PNG_SIZE = getSize('test/expected/single/' + PNG_FILE_NAME); 35 | EXPECTED_JPG_SIZE = getSize('test/expected/single/' + JPG_FILE_NAME); 36 | 37 | function ensureIsAnArray(dirs) { 38 | grunt.log.writeln(!dirs.length); 39 | if (dirs && !(dirs instanceof Array)) { 40 | var newDirs = []; 41 | newDirs.push(dirs); 42 | return newDirs; 43 | } 44 | return dirs; 45 | } 46 | function testSingleFile(test, dir, msg) { 47 | var new_size = getSize('test/tmp/' + dir + '/' + PNG_FILE_NAME); 48 | 49 | test.expect(1); 50 | test.deepEqual(new_size, EXPECTED_PNG_SIZE, msg); 51 | test.done(); 52 | } 53 | 54 | function testFileNotExists(test, dirs, png_msg, jpg_msg) { 55 | dirs = ensureIsAnArray(dirs); 56 | test.expect((((png_msg !== '') ? 1 : 0) + ((jpg_msg !== '') ? 1 : 0)) * dirs.length); 57 | 58 | dirs.forEach(function (dir) { 59 | if (png_msg !== '') { 60 | var png_exists = grunt.file.exists('test/tmp/' + dir + '/' + PNG_FILE_NAME); 61 | test.equal(false, png_exists, png_msg); 62 | } 63 | if (jpg_msg !== '') { 64 | var jpg_exists = grunt.file.exists('test/tmp/' + dir + '/' + JPG_FILE_NAME); 65 | test.equal(false, jpg_exists, jpg_msg); 66 | } 67 | }); 68 | 69 | test.done(); 70 | } 71 | 72 | function testFileNotDownsized(test, dirs, png_msg, jpg_msg) { 73 | var new_png_size, new_jpg_size; 74 | 75 | dirs = ensureIsAnArray(dirs); 76 | test.expect((((png_msg !== '') ? 1 : 0) + ((jpg_msg !== '') ? 1 : 0)) * dirs.length); 77 | 78 | dirs.forEach(function (dir) { 79 | if (png_msg !== '') { 80 | new_png_size = getSize('test/tmp/' + dir + '/' + PNG_FILE_NAME); 81 | test.notDeepEqual(new_png_size, EXPECTED_PNG_SIZE, png_msg); 82 | } 83 | if (jpg_msg !== '') { 84 | new_jpg_size = getSize('test/tmp/' + dir + '/' + JPG_FILE_NAME); 85 | test.notDeepEqual(new_jpg_size, EXPECTED_JPG_SIZE, jpg_msg); 86 | } 87 | }); 88 | 89 | test.done(); 90 | } 91 | 92 | function testMultipleFiles(test, dir, png_msg, jpg_msg) { 93 | var new_png_size = getSize('test/tmp/' + dir + '/' + PNG_FILE_NAME), 94 | new_jpg_size = getSize('test/tmp/' + dir + '/' + JPG_FILE_NAME); 95 | 96 | test.expect(2); 97 | 98 | test.deepEqual(new_png_size, EXPECTED_PNG_SIZE, png_msg); 99 | test.deepEqual(new_jpg_size, EXPECTED_JPG_SIZE, jpg_msg); 100 | test.done(); 101 | } 102 | 103 | function testNestedDir(test, dirs, png_msg, jpg_msg) { 104 | var new_png_size, new_jpg_size; 105 | 106 | dirs = ensureIsAnArray(dirs); 107 | test.expect((((png_msg !== '') ? 1 : 0) + ((jpg_msg !== '') ? 1 : 0)) * dirs.length); 108 | 109 | dirs.forEach(function (dir) { 110 | if (png_msg !== '') { 111 | new_png_size = getSize('test/tmp/' + dir + '/' + PNG_FILE_NAME); 112 | test.deepEqual(new_png_size, EXPECTED_PNG_SIZE, png_msg); 113 | } 114 | if (jpg_msg !== '') { 115 | new_jpg_size = getSize('test/tmp/' + dir + '/' + JPG_FILE_NAME); 116 | test.deepEqual(new_jpg_size, EXPECTED_JPG_SIZE, jpg_msg); 117 | } 118 | }); 119 | test.done(); 120 | } 121 | 122 | exports.smushit = { 123 | setUp: function (done) { 124 | done(); 125 | }, 126 | replace_single_dir: function (test) { 127 | testMultipleFiles(test, 'replace_single_dir', 'should run with a single directory and replace a png file', 'should run with a single directory and replace a jpg file'); 128 | }, 129 | replace_single_file: function (test) { 130 | testSingleFile(test, 'replace_single_file', 'should replace a single png file'); 131 | }, 132 | replace_single_file_not_downsize_jpg: function (test) { 133 | testFileNotDownsized(test, 'replace_single_file', '', 'jpg file should not send to smushit and size should stay the same'); 134 | }, 135 | replace_single_filter: function (test) { 136 | testSingleFile(test, 'replace_single_filter', 'should run with png extension filter and replace a png file'); 137 | }, 138 | replace_single_filter_not_downsize_jpg: function (test) { 139 | testFileNotDownsized(test, 'replace_single_filter', '', 'jpg file should not send to smushit and size should stay the same'); 140 | }, 141 | replace_multiple_files: function (test) { 142 | testMultipleFiles(test, 'replace_multiple_files', 'should run with multiple files and replace png files', 'should run with a multiple files and replace jpg files'); 143 | }, 144 | replace_multiple_filters: function (test) { 145 | testMultipleFiles(test, 'replace_multiple_filters', 'should filter and replace png files', 'should filter and replace jpg files'); 146 | }, 147 | output_single_dir: function (test) { 148 | testMultipleFiles(test, 'output_single_dir', 'should run with a single directory and move the optimized png file', 'should run with a single directory and move the optimized jpg file'); 149 | }, 150 | output_single_dir_with_sub: function (test) { 151 | testMultipleFiles(test, 'output_single_dir_with_sub/single', 'should run with a single directory and move the optimized png file', 'should run with a single directory and move the optimized jpg file'); 152 | }, 153 | output_single_file: function (test) { 154 | testSingleFile(test, 'output_single_file', 'should move the optimized png file'); 155 | }, 156 | output_single_file_not_exist_jpg: function (test) { 157 | testFileNotExists(test, 'output_single_file', '', 'jpg file should not be copied to dest folder'); 158 | }, 159 | output_single_filter: function (test) { 160 | testSingleFile(test, 'output_single_filter', 'should run with png extension filter and move the optimized png file'); 161 | }, 162 | output_single_filter_not_exist_jpg: function (test) { 163 | testFileNotExists(test, 'output_single_filter', '', 'jpg file should not be copied to dest folder'); 164 | }, 165 | output_multiple_filters: function (test) { 166 | testMultipleFiles(test, 'output_multiple_filters', 'should filter and move the optimized png files', 'should filter and move the optimized jpg files'); 167 | }, 168 | output_multiple_files: function (test) { 169 | testMultipleFiles(test, 'output_multiple_files', 'should run with multiple files and move the optimized png files', 'should run with a multiple files and move the optimized jpg files'); 170 | }, 171 | output_single_dir_with_cwd: function (test) { 172 | testMultipleFiles(test, 'output_single_dir_with_cwd', 'should run with a single directory and move the optimized png file provided with cwd', 'should run with a single directory and move the optimized jpg file provided with cwd'); 173 | }, 174 | output_single_dir_with_sub_with_cwd: function (test) { 175 | testMultipleFiles(test, 'output_single_dir_with_sub_with_cwd', 'should run with a single directory and move the optimized png file provided with cwd', 'should run with a single directory and move the optimized jpg file provided with cwd'); 176 | }, 177 | output_single_file_with_cwd: function (test) { 178 | testSingleFile(test, 'output_single_file_with_cwd', 'should move the optimized png file provided with cwd'); 179 | }, 180 | output_single_file_with_cwd_not_exist_jpg: function (test) { 181 | testFileNotExists(test, 'output_single_file_with_cwd', '', 'jpg file should not be copied to dest folder'); 182 | }, 183 | output_single_filter_with_cwd: function (test) { 184 | testSingleFile(test, 'output_single_filter_with_cwd', 'should run with png extension filter and move the optimized png file provided with cwd'); 185 | }, 186 | output_single_filter_with_cwd_not_exist_jpg: function (test) { 187 | testFileNotExists(test, 'output_single_filter_with_cwd', '', 'jpg file should not be copied to dest folder'); 188 | }, 189 | output_multiple_filters_with_cwd: function (test) { 190 | testMultipleFiles(test, 'output_multiple_filters_with_cwd', 'should filter and move the optimized png files provided with cwd', 'should filter and move the optimized jpg files provided with cwd'); 191 | }, 192 | output_multiple_files_with_cwd: function (test) { 193 | testMultipleFiles(test, 'output_multiple_files_with_cwd', 'should run with multiple files and move the optimized png files provided with cwd', 'should run with a multiple files and move the optimized jpg files provided with cwd'); 194 | }, 195 | output_single_nested_dir: function (test) { 196 | testNestedDir(test, ['output_single_nested_dir', 'output_single_nested_dir/a', 'output_single_nested_dir/a/b'], 197 | 'should run with a single directory and move the optimized png file', 'should run with a single directory and move the optimized jpg file'); 198 | }, 199 | output_single_nested_dir_ending_with_slash: function (test) { 200 | testNestedDir(test, ['output_single_nested_dir_ending_with_slash', 'output_single_nested_dir_ending_with_slash/a', 'output_single_nested_dir_ending_with_slash/a/b'], 201 | 'should run with a single directory and move the optimized png file', 'should run with a single directory and move the optimized jpg file'); 202 | }, 203 | output_single_nested_dir_with_filter: function (test) { 204 | testNestedDir(test, ['output_single_nested_dir_with_filter', 'output_single_nested_dir_with_filter/a', 'output_single_nested_dir_with_filter/a/b'], 205 | 'should run with a single directory and move the optimized png file', ''); 206 | }, 207 | output_single_nested_dir_with_filter_not_exist_jpg: function (test) { 208 | testFileNotExists(test, ['output_single_nested_dir_with_filter', 'output_single_nested_dir_with_filter/a', 'output_single_nested_dir_with_filter/a/b'], 209 | '', 'jpg file should not be copied to dest folder'); 210 | }, 211 | output_multiple_nested_dir1: function (test) { 212 | testNestedDir(test, ['output_multiple_nested_dir', 'output_multiple_nested_dir/a', 'output_multiple_nested_dir/a/b'], 213 | 'should run with a single directory and move the optimized png file', 'should run with a single directory and move the optimized jpg file'); 214 | }, 215 | output_multiple_nested_dir2: function (test) { 216 | testNestedDir(test, ['output_multiple_nested_dir', 'output_multiple_nested_dir/a1', 'output_multiple_nested_dir/a1/b2'], 217 | 'should run with a single directory and move the optimized png file', 'should run with a single directory and move the optimized jpg file'); 218 | }, 219 | output_multiple_nested_dir_with_filter1: function (test) { 220 | testNestedDir(test, ['output_multiple_nested_dir_with_filter', 'output_multiple_nested_dir_with_filter/a', 'output_multiple_nested_dir_with_filter/a/b'], 221 | 'should run with a single directory and move the optimized png file', ''); 222 | }, 223 | output_multiple_nested_dir_with_filter_not_exist_jpg: function (test) { 224 | testFileNotExists(test, ['output_multiple_nested_dir_with_filter/a', 'output_multiple_nested_dir_with_filter/a/b'], 225 | '', 'jpg file should not be copied to dest folder'); 226 | }, 227 | output_multiple_nested_dir_with_filter2: function (test) { 228 | testNestedDir(test, ['output_multiple_nested_dir_with_filter', 'output_multiple_nested_dir_with_filter/a1', 'output_multiple_nested_dir_with_filter/a1/b2'], 229 | '', 'should run with a single directory and move the optimized jpg file'); 230 | }, 231 | output_multiple_nested_dir_with_filter2_not_exist_png: function (test) { 232 | testFileNotExists(test, ['output_multiple_nested_dir_with_filter/a1', 'output_multiple_nested_dir_with_filter/a1/b2'], 233 | 'png file should not be copied to dest folder', ''); 234 | }, 235 | output_single_nested_dir_with_cwd: function (test) { 236 | testNestedDir(test, ['output_single_nested_dir_with_cwd', 'output_single_nested_dir_with_cwd/a', 'output_single_nested_dir_with_cwd/a/b'], 237 | 'should run with a single nested directory and move the optimized png file provided with cwd', 'should run with a single nested directory and move the optimized jpg file provided with cwd'); 238 | }, 239 | output_single_nested_dir_ending_with_slash_with_cwd: function (test) { 240 | testNestedDir(test, ['output_single_nested_dir_ending_with_slash_with_cwd', 'output_single_nested_dir_ending_with_slash_with_cwd/a', 'output_single_nested_dir_ending_with_slash_with_cwd/a/b'], 241 | 'should run with a single nested directory and move the optimized png file provided with cwd', 'should run with a single nested directory and move the optimized jpg file provided with cwd'); 242 | }, 243 | output_single_nested_dir_with_filter_with_cwd: function (test) { 244 | testNestedDir(test, ['output_single_nested_dir_with_filter_with_cwd', 'output_single_nested_dir_with_filter_with_cwd/a', 'output_single_nested_dir_with_filter_with_cwd/a/b'], 245 | 'should run with a single nested directory and move the optimized png file provided with cwd', ''); 246 | }, 247 | output_single_nested_dir_with_filter_with_cwd_not_exist_jpg: function (test) { 248 | testFileNotExists(test, ['output_single_nested_dir_with_filter_with_cwd', 'output_single_nested_dir_with_filter_with_cwd/a', 'output_single_nested_dir_with_filter_with_cwd/a/b'], 249 | '', 'jpg file should not be copied to dest folder'); 250 | }, 251 | output_multiple_nested_dir_with_cwd1: function (test) { 252 | testNestedDir(test, ['output_multiple_nested_dir_with_cwd', 'output_multiple_nested_dir_with_cwd/a', 'output_multiple_nested_dir_with_cwd/a/b'], 253 | 'should run with multiple nested directory and move the optimized png file provided with cwd', 'should run with multiple nested directories and move the optimized jpg file provided with cwd'); 254 | }, 255 | output_multiple_nested_dir_with_cwd2: function (test) { 256 | testNestedDir(test, ['output_multiple_nested_dir_with_cwd', 'output_multiple_nested_dir_with_cwd/a1', 'output_multiple_nested_dir_with_cwd/a1/b2'], 257 | 'should run with multiple nested directory and move the optimized png file provided with cwd', 'should run with multiple nested directories and move the optimized jpg file provided with cwd'); 258 | }, 259 | output_multiple_nested_dir_with_filter_with_cwd1: function (test) { 260 | testNestedDir(test, ['output_multiple_nested_dir_with_filter_with_cwd', 'output_multiple_nested_dir_with_filter_with_cwd/a', 'output_multiple_nested_dir_with_filter_with_cwd/a/b'], 261 | 'should run with multiple nested directory and move the optimized png file provided with cwd', ''); 262 | }, 263 | output_multiple_nested_dir_with_filter_with_cwd1_not_exist_jpg: function (test) { 264 | testFileNotExists(test, ['output_multiple_nested_dir_with_filter_with_cwd/a', 'output_multiple_nested_dir_with_filter_with_cwd/a/b'], 265 | '', 'jpg file should not be copied to dest folder'); 266 | }, 267 | output_multiple_nested_dir_with_filter_with_cwd2: function (test) { 268 | testNestedDir(test, ['output_multiple_nested_dir_with_filter_with_cwd', 'output_multiple_nested_dir_with_filter_with_cwd/a1', 'output_multiple_nested_dir_with_filter_with_cwd/a1/b2'], 269 | '', 'should run with multiple nested directory and move the optimized jpg file provided with cwd'); 270 | }, 271 | output_multiple_nested_dir_with_filter_with_cwd2_not_exist_png: function (test) { 272 | testFileNotExists(test, ['output_multiple_nested_dir_with_filter_with_cwd/a1', 'output_multiple_nested_dir_with_filter_with_cwd/a1/b2'], 273 | 'png file should not be copied to dest folder', ''); 274 | } 275 | }; 276 | --------------------------------------------------------------------------------