├── test ├── .eslintrc ├── .jshintrc ├── benchmark.js ├── test.html └── main.js ├── .travis.yml ├── html-uglify.png ├── .eslintrc ├── .editorconfig ├── .gitignore ├── LICENSE ├── package.json ├── README.md └── lib └── main.js /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 5 4 | - 4 5 | - 0.12 6 | -------------------------------------------------------------------------------- /html-uglify.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DV/html-uglify/master/html-uglify.png -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint:recommended", 3 | "env": { 4 | "node": true 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | 8 | [*.{js,json}] 9 | indent_style = space 10 | indent_size = 2 11 | -------------------------------------------------------------------------------- /test/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "strict": true, 4 | "expr": true, 5 | "predef": [ 6 | "before", 7 | "beforeEach", 8 | "after", 9 | "afterEach", 10 | "describe", 11 | "it" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /test/benchmark.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs'); 4 | var Benchmark = require('benchmark'); 5 | var HTMLUglify = require('../lib/main.js'); 6 | 7 | var suite = new Benchmark.Suite(); 8 | var htmlUglify = new HTMLUglify(); 9 | 10 | console.log('Running benchmark'); 11 | 12 | var html = fs.readFileSync('./test/test.html'); 13 | 14 | suite 15 | .add('#process', function() { 16 | htmlUglify.process(html); 17 | }) 18 | .on('cycle', function(event) { 19 | console.log(String(event.target)); 20 | }) 21 | .run({ 'async': true }); 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ############ 2 | # Node # 3 | ############ 4 | 5 | # Logs 6 | logs 7 | *.log 8 | 9 | # Runtime data 10 | pids 11 | *.pid 12 | *.seed 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directory 30 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 31 | node_modules -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, RebelMail 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "html-uglify", 3 | "version": "1.2.1", 4 | "description": "Uglifies an HTML file and its associated CSS for compression. Great for HTML emails!", 5 | "main": "lib/main.js", 6 | "scripts": { 7 | "test": "mocha test/main.js", 8 | "bench": "node test/benchmark.js" 9 | }, 10 | "keywords": [ 11 | "html", 12 | "uglify", 13 | "compress", 14 | "css", 15 | "shrink", 16 | "email" 17 | ], 18 | "author": "RebelMail", 19 | "license": "ISC", 20 | "dependencies": { 21 | "cheerio": "^0.19.0", 22 | "hashids": "^1.0.2", 23 | "postcss-safe-parser": "^1.0.4", 24 | "postcss-selector-parser": "^1.3.0" 25 | }, 26 | "devDependencies": { 27 | "benchmark": "^2.0.0", 28 | "chai": "^3.4.1", 29 | "mocha": "^2.3.4" 30 | }, 31 | "repository": { 32 | "type": "git", 33 | "url": "https://github.com/RebelMail/html-uglify.git" 34 | }, 35 | "bugs": { 36 | "url": "https://github.com/RebelMail/html-uglify/issues" 37 | }, 38 | "homepage": "https://github.com/RebelMail/html-uglify" 39 | } 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # html-uglify 2 | 3 | [![Build Status](https://travis-ci.org/Rebelmail/html-uglify.svg?branch=master)](https://travis-ci.org/Rebelmail/html-uglify) 4 | [![NPM version](https://badge.fury.io/js/html-uglify.png)](http://badge.fury.io/js/html-uglify) 5 | 6 | ![html-uglify](../master/html-uglify.png?raw=true) 7 | 8 | Uglify your HTML and CSS for purposes of compression and obfuscation. 9 | 10 | Great for HTML emails. 11 | 12 | ```javascript 13 | var HTMLUglify = require('html-uglify'); 14 | var htmlUglify = new HTMLUglify({ salt: 'your-custom-salt', whitelist: ['#noform', '#withform', '.someclass'] }); 15 | var uglified = htmlUglify.process(htmlString); 16 | ``` 17 | 18 | ## Installation 19 | 20 | ```sh 21 | npm install html-uglify --save 22 | ``` 23 | 24 | ## Usage 25 | 26 | You pass an html string to `.process` and it returns the uglified html. 27 | 28 | ```javascript 29 | var HTMLUglify = require('html-uglify'); 30 | var htmlUglify = new HTMLUglify({ salt: 'your-custom-salt', whitelist: [] }); 31 | var htmlString = "

Hello

"; 32 | 33 | var uglified = htmlUglify.process(htmlString); 34 | ``` 35 | 36 | ## Contributing 37 | 38 | 1. Fork it 39 | 2. Create your feature branch 40 | 3. Commit your changes (`git commit -am 'Added some feature'`) 41 | 4. Push to the branch (`git push origin my-new-feature`) 42 | 5. Create new Pull Request 43 | 44 | ## Running tests 45 | 46 | ```sh 47 | npm install 48 | npm test 49 | ``` 50 | -------------------------------------------------------------------------------- /test/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 32 | 33 | 34 |
35 |
36 | 37 | 38 |
39 |
40 |
41 | 42 |
43 |
44 |
45 | 46 |
47 |
48 |
49 | 50 |
51 |
52 |
53 | 54 | 55 | -------------------------------------------------------------------------------- /lib/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var cheerio = require('cheerio'); 4 | var Hashids = require('hashids'); 5 | var postcssSafeParser = require('postcss-safe-parser'); 6 | var postcssSelectorParser = require('postcss-selector-parser'); 7 | 8 | var ALPHABET = 'abcdefghijklmnopqrstuvwxyz'; 9 | var VERSION = require('../package.json').version; 10 | 11 | function HTMLUglify(config) { 12 | this.version = VERSION; 13 | 14 | config = config || {}; 15 | 16 | var salt = config.salt || 'use the force harry'; 17 | this.hashids = new Hashids(salt, 0, ALPHABET); 18 | this.whitelist = config.whitelist || []; 19 | } 20 | 21 | HTMLUglify.prototype.checkForStandardPointer = function(lookups, type, value) { 22 | return lookups[type] && lookups[type][value]; 23 | }; 24 | 25 | HTMLUglify.prototype.checkForAttributePointer = function(lookups, type, value) { 26 | var typeLookups = lookups[type] || {}; 27 | var keys = Object.keys(typeLookups); 28 | var pointer; 29 | 30 | keys.some(function(key) { 31 | if (value.indexOf(key) !== -1) { 32 | pointer = value.replace(key, typeLookups[key]); 33 | return true; 34 | } 35 | return false; 36 | }); 37 | 38 | return pointer; 39 | }; 40 | 41 | HTMLUglify.prototype.generatePointer = function(lookups) { 42 | var idCount = Object.keys(lookups['id'] || {}).length; 43 | var classCount = Object.keys(lookups['class'] || {}).length; 44 | var counter = idCount + classCount; 45 | 46 | return this.hashids.encode(counter); 47 | }; 48 | 49 | HTMLUglify.prototype.pointer = function(type, value, lookups) { 50 | return this.checkForStandardPointer(lookups, type, value) || 51 | this.checkForAttributePointer(lookups, type, value) || 52 | this.generatePointer(lookups); 53 | }; 54 | 55 | HTMLUglify.prototype.insertLookup = function(type, value, pointer, lookups) { 56 | if (!lookups[type]) { 57 | lookups[type] = {}; 58 | } 59 | lookups[type][value] = pointer; 60 | }; 61 | 62 | HTMLUglify.prototype.createLookup = function(type, value, lookups) { 63 | var pointer; 64 | if (value && !this.isWhitelisted(type, value)) { 65 | pointer = this.pointer(type, value, lookups); 66 | this.insertLookup(type, value, pointer, lookups); 67 | } 68 | return pointer; 69 | }; 70 | 71 | HTMLUglify.prototype.isWhitelisted = function(type, value) { 72 | switch(type) { 73 | case 'class': 74 | value = ['.', value].join(''); 75 | break; 76 | case 'id': 77 | value = ['#', value].join(''); 78 | break; 79 | default: 80 | break; 81 | } 82 | 83 | return this.whitelist.indexOf(value) >= 0; 84 | }; 85 | 86 | HTMLUglify.prototype.pointerizeClass = function($element, lookups) { 87 | var self = this; 88 | var value = $element.attr('class'); 89 | 90 | if (value) { 91 | var splitClasses = value.split(/\s+/); 92 | 93 | splitClasses.forEach(function(value) { 94 | var pointer = self.createLookup('class', value, lookups); 95 | if (pointer) { 96 | $element.removeClass(value); 97 | $element.addClass(pointer); 98 | } 99 | }); 100 | } 101 | }; 102 | 103 | HTMLUglify.prototype.pointerizeIdAndFor = function(type, $element, lookups) { 104 | var value = $element.attr(type); 105 | 106 | var pointer = this.createLookup('id', value, lookups); 107 | if (pointer) { 108 | $element.attr(type, pointer); 109 | } 110 | }; 111 | 112 | HTMLUglify.prototype.processRules = function(rules, lookups) { 113 | var self = this; 114 | 115 | rules.forEach(function(rule) { 116 | // go deeper inside media rule to find css rules 117 | if (rule.type === 'atrule' && (rule.name === 'media' || rule.name === 'supports')) { 118 | self.processRules(rule.nodes, lookups); 119 | } else if (rule.type === 'rule') { 120 | postcssSelectorParser(function(selectors) { 121 | selectors.eachInside(function(selector) { 122 | var pointer; 123 | 124 | if ((selector.type === 'id') 125 | || (selector.type === 'attribute' && selector.attribute === 'id') 126 | || (selector.type === 'attribute' && selector.attribute === 'for')) { 127 | pointer = self.createLookup('id', selector.value, lookups); 128 | } else if ((selector.type === 'class') 129 | || (selector.type === 'attribute' && selector.attribute === 'class')) { 130 | pointer = self.createLookup('class', selector.value, lookups); 131 | } 132 | 133 | if (pointer) { 134 | selector.value = pointer; 135 | } 136 | }); 137 | 138 | rule.selector = String(selectors); 139 | }).process(rule.selector); 140 | } 141 | }); 142 | }; 143 | 144 | HTMLUglify.prototype.rewriteElements = function($, lookups) { 145 | var self = this; 146 | 147 | lookups = lookups || {}; 148 | 149 | $('*[id]').each(function() { 150 | self.pointerizeIdAndFor('id', $(this), lookups); 151 | }); 152 | 153 | $('*[for]').each(function() { 154 | self.pointerizeIdAndFor('for', $(this), lookups); 155 | }); 156 | 157 | $('*[class]').each(function() { 158 | self.pointerizeClass($(this), lookups); 159 | }); 160 | 161 | return $; 162 | }; 163 | 164 | HTMLUglify.prototype.rewriteStyles = function($, lookups) { 165 | var self = this; 166 | 167 | lookups = lookups || {}; 168 | 169 | $('style').each(function() { 170 | var $style = $(this); 171 | var ast = postcssSafeParser($style.text()); 172 | self.processRules(ast.nodes, lookups); 173 | $style.text(ast.toString()); 174 | }); 175 | 176 | return $; 177 | }; 178 | 179 | HTMLUglify.prototype.process = function(html) { 180 | var lookups = {}; 181 | 182 | var $ = cheerio.load(html); 183 | $ = this.rewriteStyles($, lookups); 184 | $ = this.rewriteElements($, lookups); 185 | 186 | return $.html(); 187 | }; 188 | 189 | module.exports = HTMLUglify; 190 | -------------------------------------------------------------------------------- /test/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('chai').assert; 4 | var cheerio = require('cheerio'); 5 | var HTMLUglify = require('../lib/main.js'); 6 | 7 | var htmlUglify = new HTMLUglify(); 8 | 9 | describe('HTMLUglify', function() { 10 | describe('#isWhitelisted', function() { 11 | var whitelist; 12 | var htmlUglify; 13 | 14 | beforeEach(function() { 15 | whitelist = ['#theid', '.theclass', '#★', '.★']; 16 | htmlUglify = new HTMLUglify({whitelist: whitelist}); 17 | }); 18 | it('returns true if id is in whitelist', function() { 19 | var whitelisted = htmlUglify.isWhitelisted('id', 'theid'); 20 | assert.isTrue(whitelisted); 21 | }); 22 | it('returns false if id is in the whitelist but only checking for classes', function() { 23 | var whitelisted = htmlUglify.isWhitelisted('class', 'theid'); 24 | assert.isFalse(whitelisted); 25 | }); 26 | it('returns true if class is in whitelist', function() { 27 | var whitelisted = htmlUglify.isWhitelisted('class', 'theclass'); 28 | assert.isTrue(whitelisted); 29 | }); 30 | it('returns true if id is in whitelist for a unicode character', function() { 31 | var whitelisted = htmlUglify.isWhitelisted('id', '★'); 32 | assert.isTrue(whitelisted); 33 | }); 34 | it('returns true if class is in whitelist for a unicode character', function() { 35 | var whitelisted = htmlUglify.isWhitelisted('class', '★'); 36 | assert.isTrue(whitelisted); 37 | }); 38 | }); 39 | describe('#checkForStandardPointer', function() { 40 | it('returns undefined when name not found', function() { 41 | var lookups = { 42 | 'class': { 'something': 'zzz' } 43 | }; 44 | var value = 'other'; 45 | var pointer = htmlUglify.checkForStandardPointer(lookups, 'class', value); 46 | 47 | assert.isUndefined(pointer); 48 | }); 49 | it('returns pointer when found', function() { 50 | var lookups = { 51 | 'class': { 'something': 'zzz' } 52 | }; 53 | var value = 'something'; 54 | var pointer = htmlUglify.checkForStandardPointer(lookups, 'class', value); 55 | 56 | assert.equal(pointer, 'zzz'); 57 | }); 58 | }); 59 | describe('#checkForAttributePointer', function() { 60 | it('returns undefined when not found', function() { 61 | var lookups = { 62 | 'class': { 'something': 'zzz' } 63 | }; 64 | var value = 'other'; 65 | var pointer = htmlUglify.checkForAttributePointer(lookups, 'class', value); 66 | 67 | assert.isUndefined(pointer); 68 | }); 69 | it('returns the pointer when value contains same string as an existing lookup', function() { 70 | var lookups = { 71 | 'class': { 'something': 'zzz' } 72 | }; 73 | var value = 'somethingElse'; 74 | var pointer = htmlUglify.checkForAttributePointer(lookups, 'class', value); 75 | 76 | assert.equal(pointer, 'zzzElse'); 77 | }); 78 | }); 79 | describe('#generatePointer', function() { 80 | it('returns xz for counter 0 lookups', function() { 81 | var lookups = {}; 82 | var pointer = htmlUglify.generatePointer(lookups); 83 | assert.equal(pointer, 'xz'); 84 | }); 85 | it('returns wk for 1 lookups', function() { 86 | var lookups = { 'id': { 'a': 'xz' } }; 87 | var pointer = htmlUglify.generatePointer(lookups); 88 | assert.equal(pointer, 'wk'); 89 | }); 90 | it('returns en for 2 lookups', function() { 91 | var lookups = { 'id': { 'a': 'xz' }, 'class': { 'b': 'wk' } }; 92 | var pointer = htmlUglify.generatePointer(lookups); 93 | assert.equal(pointer, 'en'); 94 | }); 95 | }); 96 | describe('#pointer', function() { 97 | it('generates a new pointer', function() { 98 | var lookups = {}; 99 | var pointer = htmlUglify.pointer('class', 'newClass', {}); 100 | assert.equal(pointer, 'xz', lookups); 101 | }); 102 | it('generates a new pointer given a different one exists', function() { 103 | var lookups = { 104 | 'class': { 'otherClass': 'wk' } 105 | }; 106 | var pointer = htmlUglify.pointer('class', 'newClass', lookups); 107 | assert.equal(pointer, 'wk', lookups); 108 | }); 109 | it('generates a new pointer given a different one exists in a different attribute', function() { 110 | var lookups = { 111 | 'id': { 'someId': 'wk' } 112 | }; 113 | var pointer = htmlUglify.pointer('class', 'newClass', lookups); 114 | assert.equal(pointer, 'wk', lookups); 115 | }); 116 | it('finds an existing class pointer', function() { 117 | var lookups = { 118 | 'class': { 'someClass': 'xz' } 119 | }; 120 | var pointer = htmlUglify.pointer('class', 'someClass', lookups); 121 | assert.equal(pointer, 'xz', lookups); 122 | }); 123 | it('finds an existing id pointer', function() { 124 | var lookups = { 125 | 'id': { 'someId': 'en' } 126 | }; 127 | var pointer = htmlUglify.pointer('id', 'someId', lookups); 128 | assert.equal(pointer, 'en'); 129 | }); 130 | it('finds a more complex existing pointer', function() { 131 | var lookups = { 132 | class: { 133 | test: 'xz', 134 | testOther: 'wk', 135 | otratest: 'en' 136 | } 137 | }; 138 | var pointer = htmlUglify.pointer('class', 'test', lookups); 139 | 140 | assert.equal(pointer, 'xz'); 141 | }); 142 | }); 143 | 144 | describe('#rewriteStyles', function() { 145 | it('rewrites an id given lookups', function() { 146 | var lookups = { 'id=abe': 'xz' }; 147 | var html = ''; 148 | var $ = cheerio.load(html); 149 | var results = htmlUglify.rewriteStyles($, lookups).html(); 150 | assert.equal(results, ''); 151 | }); 152 | it('rewrites an id', function() { 153 | var lookups = { }; 154 | var html = ''; 155 | var $ = cheerio.load(html); 156 | var results = htmlUglify.rewriteStyles($, lookups).html(); 157 | assert.equal(results, ''); 158 | }); 159 | it('rewrites an id with the same name as the element', function() { 160 | var lookups = {'id': {'label': 'ab' }}; 161 | var html = ''; 162 | var $ = cheerio.load(html); 163 | var results = htmlUglify.rewriteStyles($, lookups).html(); 164 | assert.equal(results, ''); 165 | }); 166 | it('rewrites a for= given lookups', function() { 167 | var lookups = { 'id': {'email': 'ab'} }; 168 | var html = ''; 169 | var $ = cheerio.load(html); 170 | var results = htmlUglify.rewriteStyles($, lookups).html(); 171 | assert.equal(results, ""); 172 | }); 173 | it('does rewrites a for=', function() { 174 | var lookups = {}; 175 | var html = ''; 176 | var $ = cheerio.load(html); 177 | var results = htmlUglify.rewriteStyles($, lookups).html(); 178 | assert.equal(results, ""); 179 | }); 180 | it('rewrites a for= with quotes given lookups', function() { 181 | var lookups = { 'id': {'email': 'ab'} }; 182 | var html = ''; 183 | var $ = cheerio.load(html); 184 | var results = htmlUglify.rewriteStyles($, lookups).html(); 185 | assert.equal(results, ''); 186 | }); 187 | it('rewrites a for= with the same name as the element', function() { 188 | var lookups = { 'id': { 'label': 'ab' }}; 189 | var html = ''; 190 | var $ = cheerio.load(html); 191 | var results = htmlUglify.rewriteStyles($, lookups).html(); 192 | assert.equal(results, ''); 193 | }); 194 | it('rewrites an id= given lookups', function() { 195 | var lookups = { 'id': {'email': 'ab'} }; 196 | var html = ''; 197 | var $ = cheerio.load(html); 198 | var results = htmlUglify.rewriteStyles($, lookups).html(); 199 | assert.equal(results, ''); 200 | }); 201 | it('rewrites an id= with quotes given lookups', function() { 202 | var lookups = { 'id': { 'email': 'ab' } }; 203 | var html = ''; 204 | var $ = cheerio.load(html); 205 | var results = htmlUglify.rewriteStyles($, lookups).html(); 206 | assert.equal(results, ''); 207 | }); 208 | it('rewrites an id= with quotes and with the same name as the element', function() { 209 | var lookups = { 'id': {'label': 'ab'} }; 210 | var html = ''; 211 | var $ = cheerio.load(html); 212 | var results = htmlUglify.rewriteStyles($, lookups).html(); 213 | assert.equal(results, ''); 214 | }); 215 | it('rewrites a class given lookups', function() { 216 | var lookups = { 'class': { 'email': 'ab' }}; 217 | var html = ''; 218 | var $ = cheerio.load(html); 219 | var results = htmlUglify.rewriteStyles($, lookups).html(); 220 | assert.equal(results, ''); 221 | }); 222 | it('rewrites a class with the same name as the element', function() { 223 | var lookups = { 'class': { 'label': 'ab' }}; 224 | var html = ''; 225 | var $ = cheerio.load(html); 226 | var results = htmlUglify.rewriteStyles($, lookups).html(); 227 | assert.equal(results, ''); 228 | }); 229 | it('rewrites a class= given lookups', function() { 230 | var lookups = { 'class': { 'email': 'ab' }}; 231 | var html = ''; 232 | var $ = cheerio.load(html); 233 | var results = htmlUglify.rewriteStyles($, lookups).html(); 234 | assert.equal(results, ""); 235 | }); 236 | it('rewrites multi-selector rule', function() { 237 | var lookups = { 'class': { 'email': 'ab' }}; 238 | var html = ''; 239 | var $ = cheerio.load(html); 240 | var results = htmlUglify.rewriteStyles($, lookups).html(); 241 | assert.equal(results, ''); 242 | }); 243 | it('rewrites css media queries', function() { 244 | var lookups = { 'id': { 'abe': 'wz' }}; 245 | 246 | var html = ''; 247 | var $ = cheerio.load(html); 248 | var results = htmlUglify.rewriteStyles($, lookups).html(); 249 | assert.equal(results, ''); 250 | }); 251 | it('rewrites nested css media queries', function() { 252 | var lookups = { 'id': { 'abe': 'wz' }}; 253 | 254 | var html = ''; 255 | var $ = cheerio.load(html); 256 | var results = htmlUglify.rewriteStyles($, lookups).html(); 257 | assert.equal(results, ''); 258 | }); 259 | it('handles malformed syntax', function() { 260 | var html = ''; 261 | var $ = cheerio.load(html); 262 | var results = htmlUglify.rewriteStyles($).html(); 263 | assert.equal(results, ''); 264 | }); 265 | }); 266 | 267 | describe('#pointerizeClass', function() { 268 | var $element; 269 | 270 | beforeEach(function() { 271 | var html = '

'; 272 | var $ = cheerio.load(html); 273 | 274 | $element = $('p').first(); 275 | }); 276 | 277 | it('works with empty lookups', function() { 278 | var lookups = {}; 279 | htmlUglify.pointerizeClass($element, lookups); 280 | assert.deepEqual(lookups, { class: { one: 'xz', two: 'wk' } }); 281 | }); 282 | it('works with single lookup', function() { 283 | var lookups = { class: { one: 'ab' } }; 284 | htmlUglify.pointerizeClass($element, lookups); 285 | assert.deepEqual(lookups, { class: { one: 'ab', two: 'wk' } }); 286 | }); 287 | it('works with whitelist', function() { 288 | var lookups = {}; 289 | htmlUglify.whitelist = [ '.two' ]; 290 | htmlUglify.pointerizeClass($element, lookups); 291 | assert.deepEqual(lookups, { class: { one: 'xz' } }); 292 | }); 293 | }); 294 | describe('#pointerizeIdAndFor', function() { 295 | var $element; 296 | 297 | beforeEach(function() { 298 | var html = '

'; 299 | var $ = cheerio.load(html); 300 | 301 | $element = $('p').first(); 302 | }); 303 | 304 | it('works with empty lookups', function() { 305 | var lookups = {}; 306 | htmlUglify.pointerizeIdAndFor('id', $element, lookups); 307 | assert.deepEqual(lookups, { id: { one: 'xz' } }); 308 | }); 309 | it('works with existing lookup', function() { 310 | var lookups = { class: { one: 'ab' } }; 311 | htmlUglify.pointerizeClass($element, lookups); 312 | assert.deepEqual(lookups, { class: { one: 'ab' } }); 313 | }); 314 | it('works with whitelist', function() { 315 | var lookups = {}; 316 | htmlUglify.whitelist = [ '#one' ]; 317 | htmlUglify.pointerizeClass($element, lookups); 318 | assert.deepEqual(lookups, {}); 319 | }); 320 | }); 321 | describe('#rewriteElements', function() { 322 | it('rewrites an id', function() { 323 | var html = '

Header

'; 324 | var $ = cheerio.load(html); 325 | var results = htmlUglify.rewriteElements($).html(); 326 | assert.equal(results, '

Header

'); 327 | }); 328 | it('rewrites a class', function() { 329 | var html = '

Header

'; 330 | var $ = cheerio.load(html); 331 | var results = htmlUglify.rewriteElements($).html(); 332 | assert.equal(results, '

Header

'); 333 | }); 334 | it('rewrites a multiple classes', function() { 335 | var html = '

Header

'; 336 | var $ = cheerio.load(html); 337 | var results = htmlUglify.rewriteElements($).html(); 338 | assert.equal(results, '

Header

'); 339 | }); 340 | it('rewrites a multiple classes with more than one space between them', function() { 341 | var html = '

Header

'; 342 | var $ = cheerio.load(html); 343 | var results = htmlUglify.rewriteElements($).html(); 344 | assert.equal(results, '

Header

'); 345 | }); 346 | it('rewrites a for', function() { 347 | var html = '