bb59cdad48f504f205b924a9afa4119876972712
[html-minifier.git] / tests / minifier.js
1 /* global minify */
2 'use strict';
3
4 QUnit.config.autostart = false;
5 if (typeof minify === 'undefined') {
6   self.minify = require('html-minifier').minify;
7 }
8
9 QUnit.test('`minifiy` exists', function(assert) {
10   assert.ok(minify);
11 });
12
13 QUnit.test('parsing non-trivial markup', function(assert) {
14   var input, output;
15
16   assert.equal(minify('</td>'), '');
17   assert.equal(minify('</p>'), '<p></p>');
18   assert.equal(minify('</br>'), '<br>');
19   assert.equal(minify('<br>x</br>'), '<br>x<br>');
20   assert.equal(minify('<p title="</p>">x</p>'), '<p title="</p>">x</p>');
21   assert.equal(minify('<p title=" <!-- hello world --> ">x</p>'), '<p title=" <!-- hello world --> ">x</p>');
22   assert.equal(minify('<p title=" <![CDATA[ \n\n foobar baz ]]> ">x</p>'), '<p title=" <![CDATA[ \n\n foobar baz ]]> ">x</p>');
23   assert.equal(minify('<p foo-bar=baz>xxx</p>'), '<p foo-bar="baz">xxx</p>');
24   assert.equal(minify('<p foo:bar=baz>xxx</p>'), '<p foo:bar="baz">xxx</p>');
25   assert.equal(minify('<p foo.bar=baz>xxx</p>'), '<p foo.bar="baz">xxx</p>');
26
27   input = '<div><div><div><div><div><div><div><div><div><div>' +
28             'i\'m 10 levels deep' +
29           '</div></div></div></div></div></div></div></div></div></div>';
30
31   assert.equal(minify(input), input);
32
33   assert.equal(minify('<script>alert(\'<!--\')</script>'), '<script>alert(\'<!--\')</script>');
34   assert.equal(minify('<script>alert(\'<!-- foo -->\')</script>'), '<script>alert(\'<!-- foo -->\')</script>');
35   assert.equal(minify('<script>alert(\'-->\')</script>'), '<script>alert(\'-->\')</script>');
36
37   assert.equal(minify('<a title="x"href=" ">foo</a>'), '<a title="x" href="">foo</a>');
38   assert.equal(minify('<p id=""class=""title="">x'), '<p id="" class="" title="">x</p>');
39   assert.equal(minify('<p x="x\'"">x</p>'), '<p x="x\'">x</p>', 'trailing quote should be ignored');
40   assert.equal(minify('<a href="#"><p>Click me</p></a>'), '<a href="#"><p>Click me</p></a>');
41   assert.equal(minify('<span><button>Hit me</button></span>'), '<span><button>Hit me</button></span>');
42   assert.equal(minify('<object type="image/svg+xml" data="image.svg"><div>[fallback image]</div></object>'),
43     '<object type="image/svg+xml" data="image.svg"><div>[fallback image]</div></object>'
44   );
45
46   assert.equal(minify('<ng-include src="x"></ng-include>'), '<ng-include src="x"></ng-include>');
47   assert.equal(minify('<ng:include src="x"></ng:include>'), '<ng:include src="x"></ng:include>');
48   assert.equal(minify('<ng-include src="\'views/partial-notification.html\'"></ng-include><div ng-view=""></div>'),
49     '<ng-include src="\'views/partial-notification.html\'"></ng-include><div ng-view=""></div>'
50   );
51
52   // will cause test to time-out if fail
53   input = '<p>For more information, read <a href=https://stackoverflow.com/questions/17408815/fieldset-resizes-wrong-appears-to-have-unremovable-min-width-min-content/17863685#17863685>this Stack Overflow answer</a>.</p>';
54   output = '<p>For more information, read <a href="https://stackoverflow.com/questions/17408815/fieldset-resizes-wrong-appears-to-have-unremovable-min-width-min-content/17863685#17863685">this Stack Overflow answer</a>.</p>';
55   assert.equal(minify(input), output);
56
57   input = '<html âš¡></html>';
58   assert.equal(minify(input), input);
59
60   input = '<h:ællæ></h:ællæ>';
61   assert.equal(minify(input), input);
62
63   input = '<$unicorn>';
64   assert.throws(function() {
65     minify(input);
66   }, 'Invalid tag name');
67
68   input = '<begriffs.pagination ng-init="perPage=20" collection="logs" url="\'/api/logs?user=-1\'" per-page="perPage" per-page-presets="[10,20,50,100]" template-url="/assets/paginate-anything.html"></begriffs.pagination>';
69   assert.equal(minify(input), input);
70
71   // https://github.com/kangax/html-minifier/issues/41
72   assert.equal(minify('<some-tag-1></some-tag-1><some-tag-2></some-tag-2>'),
73     '<some-tag-1></some-tag-1><some-tag-2></some-tag-2>'
74   );
75
76   // https://github.com/kangax/html-minifier/issues/40
77   assert.equal(minify('[\']["]'), '[\']["]');
78
79   // https://github.com/kangax/html-minifier/issues/21
80   assert.equal(minify('<a href="test.html"><div>hey</div></a>'), '<a href="test.html"><div>hey</div></a>');
81
82   // https://github.com/kangax/html-minifier/issues/17
83   assert.equal(minify(':) <a href="http://example.com">link</a>'), ':) <a href="http://example.com">link</a>');
84
85   // https://github.com/kangax/html-minifier/issues/169
86   assert.equal(minify('<a href>ok</a>'), '<a href>ok</a>');
87
88   assert.equal(minify('<a onclick></a>'), '<a onclick></a>');
89
90   // https://github.com/kangax/html-minifier/issues/229
91   assert.equal(minify('<CUSTOM-TAG></CUSTOM-TAG><div>Hello :)</div>'), '<custom-tag></custom-tag><div>Hello :)</div>');
92
93   // https://github.com/kangax/html-minifier/issues/507
94   input = '<tag v-ref:vm_pv :imgs=" objpicsurl_ "></tag>';
95   assert.equal(minify(input), input);
96   assert.throws(function() {
97     minify('<tag v-ref:vm_pv :imgs=" objpicsurl_ " ss"123></tag>');
98   }, 'invalid attribute name');
99
100   // https://github.com/kangax/html-minifier/issues/512
101   input = '<input class="form-control" type="text" style="" id="{{vm.formInputName}}" name="{{vm.formInputName}}"' +
102           ' placeholder="YYYY-MM-DD"' +
103           ' date-range-picker' +
104           ' data-ng-model="vm.value"' +
105           ' data-ng-model-options="{ debounce: 1000 }"' +
106           ' data-ng-pattern="vm.options.format"' +
107           ' data-options="vm.datepickerOptions">';
108   assert.equal(minify(input), input);
109   assert.throws(function() {
110     minify(
111       '<input class="form-control" type="text" style="" id="{{vm.formInputName}}" name="{{vm.formInputName}}"' +
112       ' <!--FIXME hardcoded placeholder - dates may not be used for service required fields yet. -->' +
113       ' placeholder="YYYY-MM-DD"' +
114       ' date-range-picker' +
115       ' data-ng-model="vm.value"' +
116       ' data-ng-model-options="{ debounce: 1000 }"' +
117       ' data-ng-pattern="vm.options.format"' +
118       ' data-options="vm.datepickerOptions">'
119     );
120   }, 'HTML comment inside tag');
121
122   input = '<br a=\u00A0 b="&nbsp;" c="\u00A0">';
123   output = '<br a="\u00A0" b="&nbsp;" c="\u00A0">';
124   assert.equal(minify(input), output);
125   output = '<br a="\u00A0"b="\u00A0"c="\u00A0">';
126   assert.equal(minify(input, {
127     decodeEntities: true,
128     removeTagWhitespace: true,
129   }), output);
130   output = '<br a=\u00A0 b=\u00A0 c=\u00A0>';
131   assert.equal(minify(input, {
132     decodeEntities: true,
133     removeAttributeQuotes: true
134   }), output);
135   assert.equal(minify(input, {
136     decodeEntities: true,
137     removeAttributeQuotes: true,
138     removeTagWhitespace: true,
139   }), output);
140 });
141
142 QUnit.test('options', function(assert) {
143   var input = '<p>blah<span>blah 2<span>blah 3</span></span></p>';
144   assert.equal(minify(input), input);
145   assert.equal(minify(input, {}), input);
146 });
147
148 QUnit.test('case normalization', function(assert) {
149   assert.equal(minify('<P>foo</p>'), '<p>foo</p>');
150   assert.equal(minify('<DIV>boo</DIV>'), '<div>boo</div>');
151   assert.equal(minify('<DIV title="moo">boo</DiV>'), '<div title="moo">boo</div>');
152   assert.equal(minify('<DIV TITLE="blah">boo</DIV>'), '<div title="blah">boo</div>');
153   assert.equal(minify('<DIV tItLe="blah">boo</DIV>'), '<div title="blah">boo</div>');
154   assert.equal(minify('<DiV tItLe="blah">boo</DIV>'), '<div title="blah">boo</div>');
155 });
156
157 QUnit.test('space normalization between attributes', function(assert) {
158   assert.equal(minify('<p title="bar">foo</p>'), '<p title="bar">foo</p>');
159   assert.equal(minify('<img src="test"/>'), '<img src="test">');
160   assert.equal(minify('<p title = "bar">foo</p>'), '<p title="bar">foo</p>');
161   assert.equal(minify('<p title\n\n\t  =\n     "bar">foo</p>'), '<p title="bar">foo</p>');
162   assert.equal(minify('<img src="test" \n\t />'), '<img src="test">');
163   assert.equal(minify('<input title="bar"       id="boo"    value="hello world">'), '<input title="bar" id="boo" value="hello world">');
164 });
165
166 QUnit.test('space normalization around text', function(assert) {
167   var input, output;
168   input = '   <p>blah</p>\n\n\n   ';
169   assert.equal(minify(input), input);
170   output = '<p>blah</p>';
171   assert.equal(minify(input, { collapseWhitespace: true }), output);
172   output = ' <p>blah</p> ';
173   assert.equal(minify(input, {
174     collapseWhitespace: true,
175     conservativeCollapse: true
176   }), output);
177   output = '<p>blah</p>\n';
178   assert.equal(minify(input, {
179     collapseWhitespace: true,
180     preserveLineBreaks: true
181   }), output);
182   output = ' <p>blah</p>\n';
183   assert.equal(minify(input, {
184     collapseWhitespace: true,
185     conservativeCollapse: true,
186     preserveLineBreaks: true
187   }), output);
188   [
189     'a', 'abbr', 'acronym', 'b', 'big', 'del', 'em', 'font', 'i', 'ins', 'kbd',
190     'mark', 's', 'samp', 'small', 'span', 'strike', 'strong', 'sub', 'sup',
191     'time', 'tt', 'u', 'var'
192   ].forEach(function(el) {
193     assert.equal(minify('foo <' + el + '>baz</' + el + '> bar', { collapseWhitespace: true }), 'foo <' + el + '>baz</' + el + '> bar');
194     assert.equal(minify('foo<' + el + '>baz</' + el + '>bar', { collapseWhitespace: true }), 'foo<' + el + '>baz</' + el + '>bar');
195     assert.equal(minify('foo <' + el + '>baz</' + el + '>bar', { collapseWhitespace: true }), 'foo <' + el + '>baz</' + el + '>bar');
196     assert.equal(minify('foo<' + el + '>baz</' + el + '> bar', { collapseWhitespace: true }), 'foo<' + el + '>baz</' + el + '> bar');
197     assert.equal(minify('foo <' + el + '> baz </' + el + '> bar', { collapseWhitespace: true }), 'foo <' + el + '>baz </' + el + '>bar');
198     assert.equal(minify('foo<' + el + '> baz </' + el + '>bar', { collapseWhitespace: true }), 'foo<' + el + '> baz </' + el + '>bar');
199     assert.equal(minify('foo <' + el + '> baz </' + el + '>bar', { collapseWhitespace: true }), 'foo <' + el + '>baz </' + el + '>bar');
200     assert.equal(minify('foo<' + el + '> baz </' + el + '> bar', { collapseWhitespace: true }), 'foo<' + el + '> baz </' + el + '>bar');
201     assert.equal(minify('<div>foo <' + el + '>baz</' + el + '> bar</div>', { collapseWhitespace: true }), '<div>foo <' + el + '>baz</' + el + '> bar</div>');
202     assert.equal(minify('<div>foo<' + el + '>baz</' + el + '>bar</div>', { collapseWhitespace: true }), '<div>foo<' + el + '>baz</' + el + '>bar</div>');
203     assert.equal(minify('<div>foo <' + el + '>baz</' + el + '>bar</div>', { collapseWhitespace: true }), '<div>foo <' + el + '>baz</' + el + '>bar</div>');
204     assert.equal(minify('<div>foo<' + el + '>baz</' + el + '> bar</div>', { collapseWhitespace: true }), '<div>foo<' + el + '>baz</' + el + '> bar</div>');
205     assert.equal(minify('<div>foo <' + el + '> baz </' + el + '> bar</div>', { collapseWhitespace: true }), '<div>foo <' + el + '>baz </' + el + '>bar</div>');
206     assert.equal(minify('<div>foo<' + el + '> baz </' + el + '>bar</div>', { collapseWhitespace: true }), '<div>foo<' + el + '> baz </' + el + '>bar</div>');
207     assert.equal(minify('<div>foo <' + el + '> baz </' + el + '>bar</div>', { collapseWhitespace: true }), '<div>foo <' + el + '>baz </' + el + '>bar</div>');
208     assert.equal(minify('<div>foo<' + el + '> baz </' + el + '> bar</div>', { collapseWhitespace: true }), '<div>foo<' + el + '> baz </' + el + '>bar</div>');
209   });
210   // Don't trim whitespace around element, but do trim within
211   [
212     'bdi', 'bdo', 'button', 'cite', 'code', 'dfn', 'math', 'q', 'rt', 'rtc', 'ruby', 'svg'
213   ].forEach(function(el) {
214     assert.equal(minify('foo <' + el + '>baz</' + el + '> bar', { collapseWhitespace: true }), 'foo <' + el + '>baz</' + el + '> bar');
215     assert.equal(minify('foo<' + el + '>baz</' + el + '>bar', { collapseWhitespace: true }), 'foo<' + el + '>baz</' + el + '>bar');
216     assert.equal(minify('foo <' + el + '>baz</' + el + '>bar', { collapseWhitespace: true }), 'foo <' + el + '>baz</' + el + '>bar');
217     assert.equal(minify('foo<' + el + '>baz</' + el + '> bar', { collapseWhitespace: true }), 'foo<' + el + '>baz</' + el + '> bar');
218     assert.equal(minify('foo <' + el + '> baz </' + el + '> bar', { collapseWhitespace: true }), 'foo <' + el + '>baz</' + el + '> bar');
219     assert.equal(minify('foo<' + el + '> baz </' + el + '>bar', { collapseWhitespace: true }), 'foo<' + el + '>baz</' + el + '>bar');
220     assert.equal(minify('foo <' + el + '> baz </' + el + '>bar', { collapseWhitespace: true }), 'foo <' + el + '>baz</' + el + '>bar');
221     assert.equal(minify('foo<' + el + '> baz </' + el + '> bar', { collapseWhitespace: true }), 'foo<' + el + '>baz</' + el + '> bar');
222     assert.equal(minify('<div>foo <' + el + '>baz</' + el + '> bar</div>', { collapseWhitespace: true }), '<div>foo <' + el + '>baz</' + el + '> bar</div>');
223     assert.equal(minify('<div>foo<' + el + '>baz</' + el + '>bar</div>', { collapseWhitespace: true }), '<div>foo<' + el + '>baz</' + el + '>bar</div>');
224     assert.equal(minify('<div>foo <' + el + '>baz</' + el + '>bar</div>', { collapseWhitespace: true }), '<div>foo <' + el + '>baz</' + el + '>bar</div>');
225     assert.equal(minify('<div>foo<' + el + '>baz</' + el + '> bar</div>', { collapseWhitespace: true }), '<div>foo<' + el + '>baz</' + el + '> bar</div>');
226     assert.equal(minify('<div>foo <' + el + '> baz </' + el + '> bar</div>', { collapseWhitespace: true }), '<div>foo <' + el + '>baz</' + el + '> bar</div>');
227     assert.equal(minify('<div>foo<' + el + '> baz </' + el + '>bar</div>', { collapseWhitespace: true }), '<div>foo<' + el + '>baz</' + el + '>bar</div>');
228     assert.equal(minify('<div>foo <' + el + '> baz </' + el + '>bar</div>', { collapseWhitespace: true }), '<div>foo <' + el + '>baz</' + el + '>bar</div>');
229     assert.equal(minify('<div>foo<' + el + '> baz </' + el + '> bar</div>', { collapseWhitespace: true }), '<div>foo<' + el + '>baz</' + el + '> bar</div>');
230   });
231   [
232     ['<span> foo </span>', '<span>foo</span>'],
233     [' <span> foo </span> ', '<span>foo</span>'],
234     ['<nobr>a</nobr>', '<nobr>a</nobr>'],
235     ['<nobr>a </nobr>', '<nobr>a</nobr>'],
236     ['<nobr> a</nobr>', '<nobr>a</nobr>'],
237     ['<nobr> a </nobr>', '<nobr>a</nobr>'],
238     ['a<nobr>b</nobr>c', 'a<nobr>b</nobr>c'],
239     ['a<nobr>b </nobr>c', 'a<nobr>b </nobr>c'],
240     ['a<nobr> b</nobr>c', 'a<nobr> b</nobr>c'],
241     ['a<nobr> b </nobr>c', 'a<nobr> b </nobr>c'],
242     ['a<nobr>b</nobr> c', 'a<nobr>b</nobr> c'],
243     ['a<nobr>b </nobr> c', 'a<nobr>b</nobr> c'],
244     ['a<nobr> b</nobr> c', 'a<nobr> b</nobr> c'],
245     ['a<nobr> b </nobr> c', 'a<nobr> b</nobr> c'],
246     ['a <nobr>b</nobr>c', 'a <nobr>b</nobr>c'],
247     ['a <nobr>b </nobr>c', 'a <nobr>b </nobr>c'],
248     ['a <nobr> b</nobr>c', 'a <nobr>b</nobr>c'],
249     ['a <nobr> b </nobr>c', 'a <nobr>b </nobr>c'],
250     ['a <nobr>b</nobr> c', 'a <nobr>b</nobr> c'],
251     ['a <nobr>b </nobr> c', 'a <nobr>b</nobr> c'],
252     ['a <nobr> b</nobr> c', 'a <nobr>b</nobr> c'],
253     ['a <nobr> b </nobr> c', 'a <nobr>b</nobr> c']
254   ].forEach(function(inputs) {
255     assert.equal(minify(inputs[0], {
256       collapseWhitespace: true,
257       conservativeCollapse: true
258     }), inputs[0], inputs[0]);
259     assert.equal(minify(inputs[0], { collapseWhitespace: true }), inputs[1], inputs[0]);
260     var input = '<div>' + inputs[0] + '</div>';
261     assert.equal(minify(input, {
262       collapseWhitespace: true,
263       conservativeCollapse: true
264     }), input, input);
265     var output = '<div>' + inputs[1] + '</div>';
266     assert.equal(minify(input, { collapseWhitespace: true }), output, input);
267   });
268   assert.equal(minify('<p>foo <img> bar</p>', { collapseWhitespace: true }), '<p>foo <img> bar</p>');
269   assert.equal(minify('<p>foo<img>bar</p>', { collapseWhitespace: true }), '<p>foo<img>bar</p>');
270   assert.equal(minify('<p>foo <img>bar</p>', { collapseWhitespace: true }), '<p>foo <img>bar</p>');
271   assert.equal(minify('<p>foo<img> bar</p>', { collapseWhitespace: true }), '<p>foo<img> bar</p>');
272   assert.equal(minify('<p>foo <wbr> bar</p>', { collapseWhitespace: true }), '<p>foo<wbr> bar</p>');
273   assert.equal(minify('<p>foo<wbr>bar</p>', { collapseWhitespace: true }), '<p>foo<wbr>bar</p>');
274   assert.equal(minify('<p>foo <wbr>bar</p>', { collapseWhitespace: true }), '<p>foo <wbr>bar</p>');
275   assert.equal(minify('<p>foo<wbr> bar</p>', { collapseWhitespace: true }), '<p>foo<wbr> bar</p>');
276   assert.equal(minify('<p>foo <wbr baz moo=""> bar</p>', { collapseWhitespace: true }), '<p>foo<wbr baz moo=""> bar</p>');
277   assert.equal(minify('<p>foo<wbr baz moo="">bar</p>', { collapseWhitespace: true }), '<p>foo<wbr baz moo="">bar</p>');
278   assert.equal(minify('<p>foo <wbr baz moo="">bar</p>', { collapseWhitespace: true }), '<p>foo <wbr baz moo="">bar</p>');
279   assert.equal(minify('<p>foo<wbr baz moo=""> bar</p>', { collapseWhitespace: true }), '<p>foo<wbr baz moo=""> bar</p>');
280   assert.equal(minify('<p>  <a href="#">  <code>foo</code></a> bar</p>', { collapseWhitespace: true }), '<p><a href="#"><code>foo</code></a> bar</p>');
281   assert.equal(minify('<p><a href="#"><code>foo  </code></a> bar</p>', { collapseWhitespace: true }), '<p><a href="#"><code>foo</code></a> bar</p>');
282   assert.equal(minify('<p>  <a href="#">  <code>   foo</code></a> bar   </p>', { collapseWhitespace: true }), '<p><a href="#"><code>foo</code></a> bar</p>');
283   assert.equal(minify('<div> Empty <!-- or --> not </div>', { collapseWhitespace: true }), '<div>Empty<!-- or --> not</div>');
284   assert.equal(minify('<div> a <input><!-- b --> c </div>', {
285     collapseWhitespace: true,
286     removeComments: true
287   }), '<div>a <input> c</div>');
288   [
289     ' a <? b ?> c ',
290     '<!-- d --> a <? b ?> c ',
291     ' <!-- d -->a <? b ?> c ',
292     ' a<!-- d --> <? b ?> c ',
293     ' a <!-- d --><? b ?> c ',
294     ' a <? b ?><!-- d --> c ',
295     ' a <? b ?> <!-- d -->c ',
296     ' a <? b ?> c<!-- d --> ',
297     ' a <? b ?> c <!-- d -->'
298   ].forEach(function(input) {
299     assert.equal(minify(input, {
300       collapseWhitespace: true,
301       conservativeCollapse: true
302     }), input, input);
303     assert.equal(minify(input, {
304       collapseWhitespace: true,
305       removeComments: true
306     }), 'a <? b ?> c', input);
307     assert.equal(minify(input, {
308       collapseWhitespace: true,
309       conservativeCollapse: true,
310       removeComments: true
311     }), ' a <? b ?> c ', input);
312     input = '<p>' + input + '</p>';
313     assert.equal(minify(input, {
314       collapseWhitespace: true,
315       conservativeCollapse: true
316     }), input, input);
317     assert.equal(minify(input, {
318       collapseWhitespace: true,
319       removeComments: true
320     }), '<p>a <? b ?> c</p>', input);
321     assert.equal(minify(input, {
322       collapseWhitespace: true,
323       conservativeCollapse: true,
324       removeComments: true
325     }), '<p> a <? b ?> c </p>', input);
326   });
327   input = '<li><i></i> <b></b> foo</li>';
328   output = '<li><i></i> <b></b> foo</li>';
329   assert.equal(minify(input, { collapseWhitespace: true }), output);
330   input = '<li><i> </i> <b></b> foo</li>';
331   assert.equal(minify(input, { collapseWhitespace: true }), output);
332   input = '<li> <i></i> <b></b> foo</li>';
333   assert.equal(minify(input, { collapseWhitespace: true }), output);
334   input = '<li><i></i> <b> </b> foo</li>';
335   assert.equal(minify(input, { collapseWhitespace: true }), output);
336   input = '<li> <i> </i> <b> </b> foo</li>';
337   assert.equal(minify(input, { collapseWhitespace: true }), output);
338   input = '<div> <a href="#"> <span> <b> foo </b> <i> bar </i> </span> </a> </div>';
339   output = '<div><a href="#"><span><b>foo </b><i>bar</i></span></a></div>';
340   assert.equal(minify(input, { collapseWhitespace: true }), output);
341   input = '<head> <!-- a --> <!-- b --><link> </head>';
342   output = '<head><!-- a --><!-- b --><link></head>';
343   assert.equal(minify(input, { collapseWhitespace: true }), output);
344   input = '<head> <!-- a --> <!-- b --> <!-- c --><link> </head>';
345   output = '<head><!-- a --><!-- b --><!-- c --><link></head>';
346   assert.equal(minify(input, { collapseWhitespace: true }), output);
347   input = '<p> foo\u00A0bar\nbaz  \u00A0\nmoo\t</p>';
348   output = '<p>foo\u00A0bar baz \u00A0 moo</p>';
349   assert.equal(minify(input, { collapseWhitespace: true }), output);
350   input = '<label> foo </label>\n' +
351           '<input>\n' +
352           '<object> bar </object>\n' +
353           '<select> baz </select>\n' +
354           '<textarea> moo </textarea>\n';
355   output = '<label>foo</label> <input> <object>bar</object> <select>baz</select> <textarea> moo </textarea>';
356   assert.equal(minify(input, { collapseWhitespace: true }), output);
357   input = '<pre>\n' +
358           'foo\n' +
359           '<br>\n' +
360           'bar\n' +
361           '</pre>\n' +
362           'baz\n';
363   output = '<pre>\nfoo\n<br>\nbar\n</pre>baz';
364   assert.equal(minify(input, { collapseWhitespace: true }), output);
365 });
366
367 QUnit.test('types of whitespace that should always be preserved', function(assert) {
368   // Hair space:
369   var input = '<div>\u200afo\u200ao\u200a</div>';
370   assert.equal(minify(input, { collapseWhitespace: true }), input);
371
372   // Hair space passed as HTML entity:
373   var inputWithEntities = '<div>&#8202;fo&#8202;o&#8202;</div>';
374   assert.equal(minify(inputWithEntities, { collapseWhitespace: true }), inputWithEntities);
375
376   // Hair space passed as HTML entity, in decodeEntities:true mode:
377   assert.equal(minify(inputWithEntities, { collapseWhitespace: true, decodeEntities: true }), input);
378
379
380   // Non-breaking space:
381   input = '<div>\xa0fo\xa0o\xa0</div>';
382   assert.equal(minify(input, { collapseWhitespace: true }), input);
383
384   // Non-breaking space passed as HTML entity:
385   inputWithEntities = '<div>&nbsp;fo&nbsp;o&nbsp;</div>';
386   assert.equal(minify(inputWithEntities, { collapseWhitespace: true }), inputWithEntities);
387
388   // Non-breaking space passed as HTML entity, in decodeEntities:true mode:
389   assert.equal(minify(inputWithEntities, { collapseWhitespace: true, decodeEntities: true }), input);
390
391   // Do not remove hair space when preserving line breaks between tags:
392   input = '<p></p>\u200a\n<p></p>\n';
393   assert.equal(minify(input, { collapseWhitespace: true, preserveLineBreaks: true }), input);
394
395   // Preserve hair space in attributes:
396   input = '<p class="foo\u200abar"></p>';
397   assert.equal(minify(input, { collapseWhitespace: true }), input);
398
399   // Preserve hair space in class names when deduplicating and reordering:
400   input = '<a class="0 1\u200a3 2 3"></a>';
401   assert.equal(minify(input, { sortClassName: false }), input);
402   assert.equal(minify(input, { sortClassName: true }), input);
403 });
404
405 QUnit.test('doctype normalization', function(assert) {
406   var input;
407   var output = '<!doctype html>';
408
409   input = '<!DOCTYPE html>';
410   assert.equal(minify(input, { useShortDoctype: false }), input);
411   assert.equal(minify(input, { useShortDoctype: true }), output);
412
413   input = '<!DOCTYPE\nhtml>';
414   assert.equal(minify(input, { useShortDoctype: false }), '<!DOCTYPE html>');
415   assert.equal(minify(input, { useShortDoctype: true }), output);
416
417   input = '<!DOCTYPE\thtml>';
418   assert.equal(minify(input, { useShortDoctype: false }), input);
419   assert.equal(minify(input, { useShortDoctype: true }), output);
420
421   input = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"\n    "http://www.w3.org/TR/html4/strict.dtd">';
422   assert.equal(minify(input, { useShortDoctype: false }), '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">');
423   assert.equal(minify(input, { useShortDoctype: true }), output);
424 });
425
426 QUnit.test('removing comments', function(assert) {
427   var input;
428
429   input = '<!-- test -->';
430   assert.equal(minify(input, { removeComments: true }), '');
431
432   input = '<!-- foo --><div>baz</div><!-- bar\n\n moo -->';
433   assert.equal(minify(input, { removeComments: true }), '<div>baz</div>');
434   assert.equal(minify(input, { removeComments: false }), input);
435
436   input = '<p title="<!-- comment in attribute -->">foo</p>';
437   assert.equal(minify(input, { removeComments: true }), input);
438
439   input = '<script><!-- alert(1) --></script>';
440   assert.equal(minify(input, { removeComments: true }), input);
441
442   input = '<STYLE><!-- alert(1) --></STYLE>';
443   assert.equal(minify(input, { removeComments: true }), '<style><!-- alert(1) --></style>');
444 });
445
446 QUnit.test('ignoring comments', function(assert) {
447   var input, output;
448
449   input = '<!--! test -->';
450   assert.equal(minify(input, { removeComments: true }), input);
451   assert.equal(minify(input, { removeComments: false }), input);
452
453   input = '<!--! foo --><div>baz</div><!--! bar\n\n moo -->';
454   assert.equal(minify(input, { removeComments: true }), input);
455   assert.equal(minify(input, { removeComments: false }), input);
456
457   input = '<!--! foo --><div>baz</div><!-- bar\n\n moo -->';
458   assert.equal(minify(input, { removeComments: true }), '<!--! foo --><div>baz</div>');
459   assert.equal(minify(input, { removeComments: false }), input);
460
461   input = '<!-- ! test -->';
462   assert.equal(minify(input, { removeComments: true }), '');
463   assert.equal(minify(input, { removeComments: false }), input);
464
465   input = '<div>\n\n   \t<div><div>\n\n<p>\n\n<!--!      \t\n\nbar\n\n moo         -->      \n\n</p>\n\n        </div>  </div></div>';
466   output = '<div><div><div><p><!--!      \t\n\nbar\n\n moo         --></p></div></div></div>';
467   assert.equal(minify(input, { removeComments: true }), input);
468   assert.equal(minify(input, { removeComments: true, collapseWhitespace: true }), output);
469   assert.equal(minify(input, { removeComments: false }), input);
470   assert.equal(minify(input, { removeComments: false, collapseWhitespace: true }), output);
471
472   input = '<p rel="<!-- comment in attribute -->" title="<!--! ignored comment in attribute -->">foo</p>';
473   assert.equal(minify(input, { removeComments: true }), input);
474 });
475
476 QUnit.test('conditional comments', function(assert) {
477   var input, output;
478
479   input = '<![if IE 5]>test<![endif]>';
480   assert.equal(minify(input, { removeComments: true }), input);
481
482   input = '<!--[if IE 6]>test<![endif]-->';
483   assert.equal(minify(input, { removeComments: true }), input);
484
485   input = '<!--[if IE 7]>-->test<!--<![endif]-->';
486   assert.equal(minify(input, { removeComments: true }), input);
487
488   input = '<!--[if IE 8]><!-->test<!--<![endif]-->';
489   assert.equal(minify(input, { removeComments: true }), input);
490
491   input = '<!--[if lt IE 5.5]>test<![endif]-->';
492   assert.equal(minify(input, { removeComments: true }), input);
493
494   input = '<!--[if (gt IE 5)&(lt IE 7)]>test<![endif]-->';
495   assert.equal(minify(input, { removeComments: true }), input);
496
497   input = '<html>\n' +
498           '  <head>\n' +
499           '    <!--[if lte IE 8]>\n' +
500           '      <script type="text/javascript">\n' +
501           '        alert("ie8!");\n' +
502           '      </script>\n' +
503           '    <![endif]-->\n' +
504           '  </head>\n' +
505           '  <body>\n' +
506           '  </body>\n' +
507           '</html>';
508   output = '<head><!--[if lte IE 8]>\n' +
509            '      <script type="text/javascript">\n' +
510            '        alert("ie8!");\n' +
511            '      </script>\n' +
512            '    <![endif]-->';
513   assert.equal(minify(input, {
514     minifyJS: true,
515     removeComments: true,
516     collapseWhitespace: true,
517     removeOptionalTags: true,
518     removeScriptTypeAttributes: true
519   }), output);
520   output = '<head><!--[if lte IE 8]><script>alert("ie8!")</script><![endif]-->';
521   assert.equal(minify(input, {
522     minifyJS: true,
523     removeComments: true,
524     collapseWhitespace: true,
525     removeOptionalTags: true,
526     removeScriptTypeAttributes: true,
527     processConditionalComments: true
528   }), output);
529
530   input = '<!DOCTYPE html>\n' +
531           '<html lang="en">\n' +
532           '  <head>\n' +
533           '    <meta http-equiv="X-UA-Compatible"\n' +
534           '          content="IE=edge,chrome=1">\n' +
535           '    <meta charset="utf-8">\n' +
536           '    <!--[if lt IE 7]><html class="no-js ie6"><![endif]-->\n' +
537           '    <!--[if IE 7]><html class="no-js ie7"><![endif]-->\n' +
538           '    <!--[if IE 8]><html class="no-js ie8"><![endif]-->\n' +
539           '    <!--[if gt IE 8]><!--><html class="no-js"><!--<![endif]-->\n' +
540           '\n' +
541           '    <title>Document</title>\n' +
542           '  </head>\n' +
543           '  <body>\n' +
544           '  </body>\n' +
545           '</html>';
546   output = '<!DOCTYPE html>' +
547            '<html lang="en">' +
548            '<head>' +
549            '<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">' +
550            '<meta charset="utf-8">' +
551            '<!--[if lt IE 7]><html class="no-js ie6"><![endif]-->' +
552            '<!--[if IE 7]><html class="no-js ie7"><![endif]-->' +
553            '<!--[if IE 8]><html class="no-js ie8"><![endif]-->' +
554            '<!--[if gt IE 8]><!--><html class="no-js"><!--<![endif]-->' +
555            '<title>Document</title></head><body></body></html>';
556   assert.equal(minify(input, {
557     removeComments: true,
558     collapseWhitespace: true
559   }), output);
560   assert.equal(minify(input, {
561     removeComments: true,
562     collapseWhitespace: true,
563     processConditionalComments: true
564   }), output);
565 });
566
567 QUnit.test('collapsing space in conditional comments', function(assert) {
568   var input, output;
569
570   input = '<!--[if IE 7]>\n\n   \t\n   \t\t ' +
571             '<link rel="stylesheet" href="/css/ie7-fixes.css" type="text/css" />\n\t' +
572           '<![endif]-->';
573   assert.equal(minify(input, { removeComments: true }), input);
574   assert.equal(minify(input, { removeComments: true, collapseWhitespace: true }), input);
575   output = '<!--[if IE 7]>\n\n   \t\n   \t\t ' +
576              '<link rel="stylesheet" href="/css/ie7-fixes.css" type="text/css">\n\t' +
577            '<![endif]-->';
578   assert.equal(minify(input, { removeComments: true, processConditionalComments: true }), output);
579   output = '<!--[if IE 7]>' +
580              '<link rel="stylesheet" href="/css/ie7-fixes.css" type="text/css">' +
581            '<![endif]-->';
582   assert.equal(minify(input, {
583     removeComments: true,
584     collapseWhitespace: true,
585     processConditionalComments: true
586   }), output);
587
588   input = '<!--[if lte IE 6]>\n    \n   \n\n\n\t' +
589             '<p title=" sigificant     whitespace   ">blah blah</p>' +
590           '<![endif]-->';
591   assert.equal(minify(input, { removeComments: true }), input);
592   assert.equal(minify(input, { removeComments: true, collapseWhitespace: true }), input);
593   output = '<!--[if lte IE 6]>' +
594              '<p title=" sigificant     whitespace   ">blah blah</p>' +
595            '<![endif]-->';
596   assert.equal(minify(input, {
597     removeComments: true,
598     collapseWhitespace: true,
599     processConditionalComments: true
600   }), output);
601 });
602
603 QUnit.test('remove comments from scripts', function(assert) {
604   var input, output;
605
606   input = '<script><!--\nalert(1);\n--></script>';
607   assert.equal(minify(input), input);
608   output = '<script>alert(1)</script>';
609   assert.equal(minify(input, { minifyJS: true }), output);
610
611   input = '<script><!--alert(2);--></script>';
612   assert.equal(minify(input), input);
613   output = '<script></script>';
614   assert.equal(minify(input, { minifyJS: true }), output);
615
616   input = '<script><!--alert(3);\n--></script>';
617   assert.equal(minify(input), input);
618   output = '<script></script>';
619   assert.equal(minify(input, { minifyJS: true }), output);
620
621   input = '<script><!--\nalert(4);--></script>';
622   assert.equal(minify(input), input);
623   assert.equal(minify(input, { minifyJS: true }), input);
624
625   input = '<script><!--alert(5);\nalert(6);\nalert(7);--></script>';
626   assert.equal(minify(input), input);
627   assert.equal(minify(input, { minifyJS: true }), input);
628
629   input = '<script><!--alert(8)</script>';
630   assert.equal(minify(input), input);
631   output = '<script></script>';
632   assert.equal(minify(input, { minifyJS: true }), output);
633
634   input = '<script type="text/javascript"> \n <!--\nalert("-->"); -->\n\n   </script>';
635   assert.equal(minify(input), input);
636   assert.equal(minify(input, { minifyJS: true }), input);
637
638   input = '<script type="text/javascript"> \n <!--\nalert("-->");\n -->\n\n   </script>';
639   assert.equal(minify(input), input);
640   output = '<script type="text/javascript">alert("--\\x3e")</script>';
641   assert.equal(minify(input, { minifyJS: true }), output);
642
643   input = '<script> //   <!--   \n  alert(1)   //  --> </script>';
644   assert.equal(minify(input), input);
645   output = '<script>alert(1)</script>';
646   assert.equal(minify(input, { minifyJS: true }), output);
647
648   input = '<script type="text/html">\n<div>\n</div>\n<!-- aa -->\n</script>';
649   assert.equal(minify(input), input);
650   assert.equal(minify(input, { minifyJS: true }), input);
651 });
652
653 QUnit.test('remove comments from styles', function(assert) {
654   var input, output;
655
656   input = '<style><!--\np.a{background:red}\n--></style>';
657   assert.equal(minify(input), input);
658   output = '<style>p.a{background:red}</style>';
659   assert.equal(minify(input, { minifyCSS: true }), output);
660
661   input = '<style><!--p.b{background:red}--></style>';
662   assert.equal(minify(input), input);
663   output = '<style>p.b{background:red}</style>';
664   assert.equal(minify(input, { minifyCSS: true }), output);
665
666   input = '<style><!--p.c{background:red}\n--></style>';
667   assert.equal(minify(input), input);
668   output = '<style>p.c{background:red}</style>';
669   assert.equal(minify(input, { minifyCSS: true }), output);
670
671   input = '<style><!--\np.d{background:red}--></style>';
672   assert.equal(minify(input), input);
673   output = '<style>p.d{background:red}</style>';
674   assert.equal(minify(input, { minifyCSS: true }), output);
675
676   input = '<style><!--p.e{background:red}\np.f{background:red}\np.g{background:red}--></style>';
677   assert.equal(minify(input), input);
678   output = '<style>p.e{background:red}p.f{background:red}p.g{background:red}</style>';
679   assert.equal(minify(input, { minifyCSS: true }), output);
680
681   input = '<style>p.h{background:red}<!--\np.i{background:red}\n-->p.j{background:red}</style>';
682   assert.equal(minify(input), input);
683   output = '<style>p.h{background:red}p.i{background:red}p.j{background:red}</style>';
684   assert.equal(minify(input, { minifyCSS: true }), output);
685
686   input = '<style type="text/css"><!-- p { color: red } --></style>';
687   assert.equal(minify(input), input);
688   output = '<style type="text/css">p{color:red}</style>';
689   assert.equal(minify(input, { minifyCSS: true }), output);
690
691   input = '<style type="text/css">p::before { content: "<!--" }</style>';
692   assert.equal(minify(input), input);
693   output = '<style type="text/css">p::before{content:"<!--"}</style>';
694   assert.equal(minify(input, { minifyCSS: true }), output);
695
696   input = '<style type="text/html">\n<div>\n</div>\n<!-- aa -->\n</style>';
697   assert.equal(minify(input), input);
698   assert.equal(minify(input, { minifyCSS: true }), input);
699 });
700
701 QUnit.test('remove CDATA sections from scripts/styles', function(assert) {
702   var input, output;
703
704   input = '<script><![CDATA[\nalert(1)\n]]></script>';
705   assert.equal(minify(input), input);
706   assert.equal(minify(input, { minifyJS: true }), input);
707
708   input = '<script><![CDATA[alert(2)]]></script>';
709   assert.equal(minify(input), input);
710   assert.equal(minify(input, { minifyJS: true }), input);
711
712   input = '<script><![CDATA[alert(3)\n]]></script>';
713   assert.equal(minify(input), input);
714   assert.equal(minify(input, { minifyJS: true }), input);
715
716   input = '<script><![CDATA[\nalert(4)]]></script>';
717   assert.equal(minify(input), input);
718   assert.equal(minify(input, { minifyJS: true }), input);
719
720   input = '<script><![CDATA[alert(5)\nalert(6)\nalert(7)]]></script>';
721   assert.equal(minify(input), input);
722   assert.equal(minify(input, { minifyJS: true }), input);
723
724   input = '<script>/*<![CDATA[*/alert(8)/*]]>*/</script>';
725   assert.equal(minify(input), input);
726   output = '<script>alert(8)</script>';
727   assert.equal(minify(input, { minifyJS: true }), output);
728
729   input = '<script>//<![CDATA[\nalert(9)\n//]]></script>';
730   assert.equal(minify(input), input);
731   output = '<script>alert(9)</script>';
732   assert.equal(minify(input, { minifyJS: true }), output);
733
734   input = '<script type="text/javascript"> /* \n\t  <![CDATA[  */ alert(10) /*  ]]>  */ \n </script>';
735   assert.equal(minify(input), input);
736   output = '<script type="text/javascript">alert(10)</script>';
737   assert.equal(minify(input, { minifyJS: true }), output);
738
739   input = '<script>\n\n//<![CDATA[\nalert(11)//]]></script>';
740   assert.equal(minify(input), input);
741   output = '<script>alert(11)</script>';
742   assert.equal(minify(input, { minifyJS: true }), output);
743
744   input = '<style><![CDATA[\np.a{background:red}\n]]></style>';
745   assert.equal(minify(input), input);
746   output = '<style></style>';
747   assert.equal(minify(input, { minifyCSS: true }), output);
748
749   input = '<style><![CDATA[p.b{background:red}]]></style>';
750   assert.equal(minify(input), input);
751   output = '<style></style>';
752   assert.equal(minify(input, { minifyCSS: true }), output);
753
754   input = '<style><![CDATA[p.c{background:red}\n]]></style>';
755   assert.equal(minify(input), input);
756   output = '<style></style>';
757   assert.equal(minify(input, { minifyCSS: true }), output);
758
759   input = '<style><![CDATA[\np.d{background:red}]]></style>';
760   assert.equal(minify(input), input);
761   output = '<style></style>';
762   assert.equal(minify(input, { minifyCSS: true }), output);
763
764   input = '<style><![CDATA[p.e{background:red}\np.f{background:red}\np.g{background:red}]]></style>';
765   assert.equal(minify(input), input);
766   output = '<style>p.f{background:red}p.g{background:red}</style>';
767   assert.equal(minify(input, { minifyCSS: true }), output);
768
769   input = '<style>p.h{background:red}<![CDATA[\np.i{background:red}\n]]>p.j{background:red}</style>';
770   assert.equal(minify(input), input);
771   output = '<style>p.h{background:red}]]>p.j{background:red}</style>';
772   assert.equal(minify(input, { minifyCSS: true }), output);
773
774   input = '<style>/* <![CDATA[ */p { color: red } // ]]></style>';
775   assert.equal(minify(input), input);
776   output = '<style>p{color:red}</style>';
777   assert.equal(minify(input, { minifyCSS: true }), output);
778
779   input = '<style type="text/html">\n<div>\n</div>\n<![CDATA[ aa ]]>\n</style>';
780   assert.equal(minify(input), input);
781   assert.equal(minify(input, { minifyCSS: true }), input);
782 });
783
784 QUnit.test('custom processors', function(assert) {
785   var input, output;
786
787   function css(text, type) {
788     return (type || 'Normal') + ' CSS';
789   }
790
791   input = '<style>\n.foo { font: 12pt "bar" } </style>';
792   assert.equal(minify(input), input);
793   assert.equal(minify(input, { minifyCSS: null }), input);
794   assert.equal(minify(input, { minifyCSS: false }), input);
795   output = '<style>Normal CSS</style>';
796   assert.equal(minify(input, { minifyCSS: css }), output);
797
798   input = '<p style="font: 12pt \'bar\'"></p>';
799   assert.equal(minify(input), input);
800   assert.equal(minify(input, { minifyCSS: null }), input);
801   assert.equal(minify(input, { minifyCSS: false }), input);
802   output = '<p style="inline CSS"></p>';
803   assert.equal(minify(input, { minifyCSS: css }), output);
804
805   input = '<link rel="stylesheet" href="css/style-mobile.css" media="(max-width: 737px)">';
806   assert.equal(minify(input), input);
807   assert.equal(minify(input, { minifyCSS: null }), input);
808   assert.equal(minify(input, { minifyCSS: false }), input);
809   output = '<link rel="stylesheet" href="css/style-mobile.css" media="media CSS">';
810   assert.equal(minify(input, { minifyCSS: css }), output);
811
812   input = '<style media="(max-width: 737px)"></style>';
813   assert.equal(minify(input), input);
814   assert.equal(minify(input, { minifyCSS: null }), input);
815   assert.equal(minify(input, { minifyCSS: false }), input);
816   output = '<style media="media CSS">Normal CSS</style>';
817   assert.equal(minify(input, { minifyCSS: css }), output);
818
819   function js(text, inline) {
820     return inline ? 'Inline JS' : 'Normal JS';
821   }
822
823   input = '<script>\nalert(1); </script>';
824   assert.equal(minify(input), input);
825   assert.equal(minify(input, { minifyJS: null }), input);
826   assert.equal(minify(input, { minifyJS: false }), input);
827   output = '<script>Normal JS</script>';
828   assert.equal(minify(input, { minifyJS: js }), output);
829
830   input = '<p onload="alert(1);"></p>';
831   assert.equal(minify(input), input);
832   assert.equal(minify(input, { minifyJS: null }), input);
833   assert.equal(minify(input, { minifyJS: false }), input);
834   output = '<p onload="Inline JS"></p>';
835   assert.equal(minify(input, { minifyJS: js }), output);
836
837   function url() {
838     return 'URL';
839   }
840
841   input = '<a href="http://site.com/foo">bar</a>';
842   assert.equal(minify(input), input);
843   assert.equal(minify(input, { minifyURLs: null }), input);
844   assert.equal(minify(input, { minifyURLs: false }), input);
845   output = '<a href="URL">bar</a>';
846   assert.equal(minify(input, { minifyURLs: url }), output);
847
848   input = '<style>\n.foo { background: url("http://site.com/foo") } </style>';
849   assert.equal(minify(input), input);
850   assert.equal(minify(input, { minifyURLs: null }), input);
851   assert.equal(minify(input, { minifyURLs: false }), input);
852   assert.equal(minify(input, { minifyURLs: url }), input);
853   output = '<style>.foo{background:url(URL)}</style>';
854   assert.equal(minify(input, { minifyCSS: true, minifyURLs: url }), output);
855 });
856
857 QUnit.test('empty attributes', function(assert) {
858   var input;
859
860   input = '<p id="" class="" STYLE=" " title="\n" lang="" dir="">x</p>';
861   assert.equal(minify(input, { removeEmptyAttributes: true }), '<p>x</p>');
862
863   input = '<p onclick=""   ondblclick=" " onmousedown="" ONMOUSEUP="" onmouseover=" " onmousemove="" onmouseout="" ' +
864           'onkeypress=\n\n  "\n     " onkeydown=\n"" onkeyup\n="">x</p>';
865   assert.equal(minify(input, { removeEmptyAttributes: true }), '<p>x</p>');
866
867   input = '<input onfocus="" onblur="" onchange=" " value=" boo ">';
868   assert.equal(minify(input, { removeEmptyAttributes: true }), '<input value=" boo ">');
869
870   input = '<input value="" name="foo">';
871   assert.equal(minify(input, { removeEmptyAttributes: true }), '<input name="foo">');
872
873   input = '<img src="" alt="">';
874   assert.equal(minify(input, { removeEmptyAttributes: true }), '<img src="" alt="">');
875
876   // preserve unrecognized attribute
877   // remove recognized attrs with unspecified values
878   input = '<div data-foo class id style title lang dir onfocus onblur onchange onclick ondblclick onmousedown onmouseup onmouseover onmousemove onmouseout onkeypress onkeydown onkeyup></div>';
879   assert.equal(minify(input, { removeEmptyAttributes: true }), '<div data-foo></div>');
880
881   // additional remove attributes
882   input = '<img src="" alt="">';
883   assert.equal(minify(input, { removeEmptyAttributes: function(attrName, tag) { return tag === 'img' && attrName === 'src'; } }), '<img alt="">');
884 });
885
886 QUnit.test('cleaning class/style attributes', function(assert) {
887   var input, output;
888
889   input = '<p class=" foo bar  ">foo bar baz</p>';
890   assert.equal(minify(input), '<p class="foo bar">foo bar baz</p>');
891
892   input = '<p class=" foo      ">foo bar baz</p>';
893   assert.equal(minify(input), '<p class="foo">foo bar baz</p>');
894   assert.equal(minify(input, { removeAttributeQuotes: true }), '<p class=foo>foo bar baz</p>');
895
896   input = '<p class="\n  \n foo   \n\n\t  \t\n   ">foo bar baz</p>';
897   output = '<p class="foo">foo bar baz</p>';
898   assert.equal(minify(input), output);
899
900   input = '<p class="\n  \n foo   \n\n\t  \t\n  class1 class-23 ">foo bar baz</p>';
901   output = '<p class="foo class1 class-23">foo bar baz</p>';
902   assert.equal(minify(input), output);
903
904   input = '<p style="    color: red; background-color: rgb(100, 75, 200);  "></p>';
905   output = '<p style="color: red; background-color: rgb(100, 75, 200);"></p>';
906   assert.equal(minify(input), output);
907
908   input = '<p style="font-weight: bold  ; "></p>';
909   output = '<p style="font-weight: bold;"></p>';
910   assert.equal(minify(input), output);
911 });
912
913 QUnit.test('cleaning URI-based attributes', function(assert) {
914   var input, output;
915
916   input = '<a href="   http://example.com  ">x</a>';
917   output = '<a href="http://example.com">x</a>';
918   assert.equal(minify(input), output);
919
920   input = '<a href="  \t\t  \n \t  ">x</a>';
921   output = '<a href="">x</a>';
922   assert.equal(minify(input), output);
923
924   input = '<img src="   http://example.com  " title="bleh   " longdesc="  http://example.com/longdesc \n\n   \t ">';
925   output = '<img src="http://example.com" title="bleh   " longdesc="http://example.com/longdesc">';
926   assert.equal(minify(input), output);
927
928   input = '<img src="" usemap="   http://example.com  ">';
929   output = '<img src="" usemap="http://example.com">';
930   assert.equal(minify(input), output);
931
932   input = '<form action="  somePath/someSubPath/someAction?foo=bar&baz=qux     "></form>';
933   output = '<form action="somePath/someSubPath/someAction?foo=bar&baz=qux"></form>';
934   assert.equal(minify(input), output);
935
936   input = '<BLOCKQUOTE cite=" \n\n\n http://www.mycom.com/tolkien/twotowers.html     "><P>foobar</P></BLOCKQUOTE>';
937   output = '<blockquote cite="http://www.mycom.com/tolkien/twotowers.html"><p>foobar</p></blockquote>';
938   assert.equal(minify(input), output);
939
940   input = '<head profile="       http://gmpg.org/xfn/11    "></head>';
941   output = '<head profile="http://gmpg.org/xfn/11"></head>';
942   assert.equal(minify(input), output);
943
944   input = '<object codebase="   http://example.com  "></object>';
945   output = '<object codebase="http://example.com"></object>';
946   assert.equal(minify(input), output);
947
948   input = '<span profile="   1, 2, 3  ">foo</span>';
949   assert.equal(minify(input), input);
950
951   input = '<div action="  foo-bar-baz ">blah</div>';
952   assert.equal(minify(input), input);
953 });
954
955 QUnit.test('cleaning Number-based attributes', function(assert) {
956   var input, output;
957
958   input = '<a href="#" tabindex="   1  ">x</a><button tabindex="   2  ">y</button>';
959   output = '<a href="#" tabindex="1">x</a><button tabindex="2">y</button>';
960   assert.equal(minify(input), output);
961
962   input = '<input value="" maxlength="     5 ">';
963   output = '<input value="" maxlength="5">';
964   assert.equal(minify(input), output);
965
966   input = '<select size="  10   \t\t "><option>x</option></select>';
967   output = '<select size="10"><option>x</option></select>';
968   assert.equal(minify(input), output);
969
970   input = '<textarea rows="   20  " cols="  30      "></textarea>';
971   output = '<textarea rows="20" cols="30"></textarea>';
972   assert.equal(minify(input), output);
973
974   input = '<COLGROUP span="   40  "><COL span="  39 "></COLGROUP>';
975   output = '<colgroup span="40"><col span="39"></colgroup>';
976   assert.equal(minify(input), output);
977
978   input = '<tr><td colspan="    2   ">x</td><td rowspan="   3 "></td></tr>';
979   output = '<tr><td colspan="2">x</td><td rowspan="3"></td></tr>';
980   assert.equal(minify(input), output);
981 });
982
983 QUnit.test('cleaning other attributes', function(assert) {
984   var input, output;
985
986   input = '<a href="#" onclick="  window.prompt(\'boo\'); " onmouseover=" \n\n alert(123)  \t \n\t  ">blah</a>';
987   output = '<a href="#" onclick="window.prompt(\'boo\');" onmouseover="alert(123)">blah</a>';
988   assert.equal(minify(input), output);
989
990   input = '<body onload="  foo();   bar() ;  "><p>x</body>';
991   output = '<body onload="foo();   bar() ;"><p>x</p></body>';
992   assert.equal(minify(input), output);
993 });
994
995 QUnit.test('removing redundant attributes (&lt;form method="get" ...>)', function(assert) {
996   var input;
997
998   input = '<form method="get">hello world</form>';
999   assert.equal(minify(input, { removeRedundantAttributes: true }), '<form>hello world</form>');
1000
1001   input = '<form method="post">hello world</form>';
1002   assert.equal(minify(input, { removeRedundantAttributes: true }), '<form method="post">hello world</form>');
1003 });
1004
1005 QUnit.test('removing redundant attributes (&lt;input type="text" ...>)', function(assert) {
1006   var input;
1007
1008   input = '<input type="text">';
1009   assert.equal(minify(input, { removeRedundantAttributes: true }), '<input>');
1010
1011   input = '<input type="  TEXT  " value="foo">';
1012   assert.equal(minify(input, { removeRedundantAttributes: true }), '<input value="foo">');
1013
1014   input = '<input type="checkbox">';
1015   assert.equal(minify(input, { removeRedundantAttributes: true }), '<input type="checkbox">');
1016 });
1017
1018 QUnit.test('removing redundant attributes (&lt;a name="..." id="..." ...>)', function(assert) {
1019   var input;
1020
1021   input = '<a id="foo" name="foo">blah</a>';
1022   assert.equal(minify(input, { removeRedundantAttributes: true }), '<a id="foo">blah</a>');
1023
1024   input = '<input id="foo" name="foo">';
1025   assert.equal(minify(input, { removeRedundantAttributes: true }), input);
1026
1027   input = '<a name="foo">blah</a>';
1028   assert.equal(minify(input, { removeRedundantAttributes: true }), input);
1029
1030   input = '<a href="..." name="  bar  " id="bar" >blah</a>';
1031   assert.equal(minify(input, { removeRedundantAttributes: true }), '<a href="..." id="bar">blah</a>');
1032 });
1033
1034 QUnit.test('removing redundant attributes (&lt;script src="..." charset="...">)', function(assert) {
1035   var input, output;
1036
1037   input = '<script type="text/javascript" charset="UTF-8">alert(222);</script>';
1038   output = '<script type="text/javascript">alert(222);</script>';
1039   assert.equal(minify(input, { removeRedundantAttributes: true }), output);
1040
1041   input = '<script type="text/javascript" src="http://example.com" charset="UTF-8">alert(222);</script>';
1042   assert.equal(minify(input, { removeRedundantAttributes: true }), input);
1043
1044   input = '<script CHARSET=" ... ">alert(222);</script>';
1045   output = '<script>alert(222);</script>';
1046   assert.equal(minify(input, { removeRedundantAttributes: true }), output);
1047 });
1048
1049 QUnit.test('removing redundant attributes (&lt;... language="javascript" ...>)', function(assert) {
1050   var input;
1051
1052   input = '<script language="Javascript">x=2,y=4</script>';
1053   assert.equal(minify(input, { removeRedundantAttributes: true }), '<script>x=2,y=4</script>');
1054
1055   input = '<script LANGUAGE = "  javaScript  ">x=2,y=4</script>';
1056   assert.equal(minify(input, { removeRedundantAttributes: true }), '<script>x=2,y=4</script>');
1057 });
1058
1059 QUnit.test('removing redundant attributes (&lt;area shape="rect" ...>)', function(assert) {
1060   var input = '<area shape="rect" coords="696,25,958,47" href="#" title="foo">';
1061   var output = '<area coords="696,25,958,47" href="#" title="foo">';
1062   assert.equal(minify(input, { removeRedundantAttributes: true }), output);
1063 });
1064
1065 QUnit.test('removing redundant attributes (&lt;... = "javascript: ..." ...>)', function(assert) {
1066   var input;
1067
1068   input = '<p onclick="javascript:alert(1)">x</p>';
1069   assert.equal(minify(input), '<p onclick="alert(1)">x</p>');
1070
1071   input = '<p onclick="javascript:x">x</p>';
1072   assert.equal(minify(input, { removeAttributeQuotes: true }), '<p onclick=x>x</p>');
1073
1074   input = '<p onclick=" JavaScript: x">x</p>';
1075   assert.equal(minify(input), '<p onclick="x">x</p>');
1076
1077   input = '<p title="javascript:(function() { /* some stuff here */ })()">x</p>';
1078   assert.equal(minify(input), input);
1079 });
1080
1081 QUnit.test('removing javascript type attributes', function(assert) {
1082   var input, output;
1083
1084   input = '<script type="">alert(1)</script>';
1085   assert.equal(minify(input, { removeScriptTypeAttributes: false }), input);
1086   output = '<script>alert(1)</script>';
1087   assert.equal(minify(input, { removeScriptTypeAttributes: true }), output);
1088
1089   input = '<script type="text/javascript">alert(1)</script>';
1090   assert.equal(minify(input, { removeScriptTypeAttributes: false }), input);
1091   output = '<script>alert(1)</script>';
1092   assert.equal(minify(input, { removeScriptTypeAttributes: true }), output);
1093
1094   input = '<SCRIPT TYPE="  text/javascript ">alert(1)</script>';
1095   output = '<script>alert(1)</script>';
1096   assert.equal(minify(input, { removeScriptTypeAttributes: true }), output);
1097
1098   input = '<script type="application/javascript;version=1.8">alert(1)</script>';
1099   output = '<script>alert(1)</script>';
1100   assert.equal(minify(input, { removeScriptTypeAttributes: true }), output);
1101
1102   input = '<script type="text/vbscript">MsgBox("foo bar")</script>';
1103   output = '<script type="text/vbscript">MsgBox("foo bar")</script>';
1104   assert.equal(minify(input, { removeScriptTypeAttributes: true }), output);
1105 });
1106
1107 QUnit.test('removing type="text/css" attributes', function(assert) {
1108   var input, output;
1109
1110   input = '<style type="">.foo { color: red }</style>';
1111   assert.equal(minify(input, { removeStyleLinkTypeAttributes: false }), input);
1112   output = '<style>.foo { color: red }</style>';
1113   assert.equal(minify(input, { removeStyleLinkTypeAttributes: true }), output);
1114
1115   input = '<style type="text/css">.foo { color: red }</style>';
1116   assert.equal(minify(input, { removeStyleLinkTypeAttributes: false }), input);
1117   output = '<style>.foo { color: red }</style>';
1118   assert.equal(minify(input, { removeStyleLinkTypeAttributes: true }), output);
1119
1120   input = '<STYLE TYPE = "  text/CSS ">body { font-size: 1.75em }</style>';
1121   output = '<style>body { font-size: 1.75em }</style>';
1122   assert.equal(minify(input, { removeStyleLinkTypeAttributes: true }), output);
1123
1124   input = '<style type="text/plain">.foo { background: green }</style>';
1125   assert.equal(minify(input, { removeStyleLinkTypeAttributes: true }), input);
1126
1127   input = '<link rel="stylesheet" type="text/css" href="http://example.com">';
1128   output = '<link rel="stylesheet" href="http://example.com">';
1129   assert.equal(minify(input, { removeStyleLinkTypeAttributes: true }), output);
1130
1131   input = '<link rel="alternate" type="application/atom+xml" href="data.xml">';
1132   assert.equal(minify(input, { removeStyleLinkTypeAttributes: true }), input);
1133 });
1134
1135 QUnit.test('removing attribute quotes', function(assert) {
1136   var input;
1137
1138   input = '<p title="blah" class="a23B-foo.bar_baz:qux" id="moo">foo</p>';
1139   assert.equal(minify(input, { removeAttributeQuotes: true }), '<p title=blah class=a23B-foo.bar_baz:qux id=moo>foo</p>');
1140
1141   input = '<input value="hello world">';
1142   assert.equal(minify(input, { removeAttributeQuotes: true }), '<input value="hello world">');
1143
1144   input = '<a href="#" title="foo#bar">x</a>';
1145   assert.equal(minify(input, { removeAttributeQuotes: true }), '<a href=# title=foo#bar>x</a>');
1146
1147   input = '<a href="http://example.com/" title="blah">\nfoo\n\n</a>';
1148   assert.equal(minify(input, { removeAttributeQuotes: true }), '<a href=http://example.com/ title=blah>\nfoo\n\n</a>');
1149
1150   input = '<a title="blah" href="http://example.com/">\nfoo\n\n</a>';
1151   assert.equal(minify(input, { removeAttributeQuotes: true }), '<a title=blah href=http://example.com/ >\nfoo\n\n</a>');
1152
1153   input = '<a href="http://example.com/" title="">\nfoo\n\n</a>';
1154   assert.equal(minify(input, { removeAttributeQuotes: true, removeEmptyAttributes: true }), '<a href=http://example.com/ >\nfoo\n\n</a>');
1155
1156   input = '<p class=foo|bar:baz></p>';
1157   assert.equal(minify(input, { removeAttributeQuotes: true }), '<p class=foo|bar:baz></p>');
1158 });
1159
1160 QUnit.test('preserving custom attribute-wrapping markup', function(assert) {
1161   var input, customAttrOptions;
1162
1163   // With a single rule
1164   customAttrOptions = {
1165     customAttrSurround: [[/\{\{#if\s+\w+\}\}/, /\{\{\/if\}\}/]]
1166   };
1167
1168   input = '<input {{#if value}}checked="checked"{{/if}}>';
1169   assert.equal(minify(input, customAttrOptions), input);
1170
1171   input = '<input checked="checked">';
1172   assert.equal(minify(input, customAttrOptions), input);
1173
1174   // With multiple rules
1175   customAttrOptions = {
1176     customAttrSurround: [
1177       [/\{\{#if\s+\w+\}\}/, /\{\{\/if\}\}/],
1178       [/\{\{#unless\s+\w+\}\}/, /\{\{\/unless\}\}/]
1179     ]
1180   };
1181
1182   input = '<input {{#if value}}checked="checked"{{/if}}>';
1183   assert.equal(minify(input, customAttrOptions), input);
1184
1185   input = '<input {{#unless value}}checked="checked"{{/unless}}>';
1186   assert.equal(minify(input, customAttrOptions), input);
1187
1188   input = '<input {{#if value1}}data-attr="example" {{/if}}{{#unless value2}}checked="checked"{{/unless}}>';
1189   assert.equal(minify(input, customAttrOptions), input);
1190
1191   input = '<input checked="checked">';
1192   assert.equal(minify(input, customAttrOptions), input);
1193
1194   // With multiple rules and richer options
1195   customAttrOptions = {
1196     customAttrSurround: [
1197       [/\{\{#if\s+\w+\}\}/, /\{\{\/if\}\}/],
1198       [/\{\{#unless\s+\w+\}\}/, /\{\{\/unless\}\}/]
1199     ],
1200     collapseBooleanAttributes: true,
1201     removeAttributeQuotes: true
1202   };
1203
1204   input = '<input {{#if value}}checked="checked"{{/if}}>';
1205   assert.equal(minify(input, customAttrOptions), '<input {{#if value}}checked{{/if}}>');
1206
1207   input = '<input {{#if value1}}checked="checked"{{/if}} {{#if value2}}data-attr="foo"{{/if}}/>';
1208   assert.equal(minify(input, customAttrOptions), '<input {{#if value1}}checked {{/if}}{{#if value2}}data-attr=foo{{/if}}>');
1209
1210   customAttrOptions.keepClosingSlash = true;
1211   assert.equal(minify(input, customAttrOptions), '<input {{#if value1}}checked {{/if}}{{#if value2}}data-attr=foo {{/if}}/>');
1212 });
1213
1214 QUnit.test('preserving custom attribute-joining markup', function(assert) {
1215   var input;
1216   var polymerConditionalAttributeJoin = /\?=/;
1217   var customAttrOptions = {
1218     customAttrAssign: [polymerConditionalAttributeJoin]
1219   };
1220   input = '<div flex?="{{mode != cover}}"></div>';
1221   assert.equal(minify(input, customAttrOptions), input);
1222   input = '<div flex?="{{mode != cover}}" class="foo"></div>';
1223   assert.equal(minify(input, customAttrOptions), input);
1224 });
1225
1226 QUnit.test('collapsing whitespace', function(assert) {
1227   var input, output;
1228
1229   input = '<script type="text/javascript">  \n\t   alert(1) \n\n\n  \t </script>';
1230   output = '<script type="text/javascript">alert(1)</script>';
1231   assert.equal(minify(input, { collapseWhitespace: true }), output);
1232
1233   input = '<p>foo</p>    <p> bar</p>\n\n   \n\t\t  <div title="quz">baz  </div>';
1234   output = '<p>foo</p><p>bar</p><div title="quz">baz</div>';
1235   assert.equal(minify(input, { collapseWhitespace: true }), output);
1236
1237   input = '<p> foo    bar</p>';
1238   output = '<p>foo bar</p>';
1239   assert.equal(minify(input, { collapseWhitespace: true }), output);
1240
1241   input = '<p>foo\nbar</p>';
1242   output = '<p>foo bar</p>';
1243   assert.equal(minify(input, { collapseWhitespace: true }), output);
1244
1245   input = '<p> foo    <span>  blah     <i>   22</i>    </span> bar <img src=""></p>';
1246   output = '<p>foo <span>blah <i>22</i> </span>bar <img src=""></p>';
1247   assert.equal(minify(input, { collapseWhitespace: true }), output);
1248
1249   input = '<textarea> foo bar     baz \n\n   x \t    y </textarea>';
1250   output = '<textarea> foo bar     baz \n\n   x \t    y </textarea>';
1251   assert.equal(minify(input, { collapseWhitespace: true }), output);
1252
1253   input = '<div><textarea></textarea>    </div>';
1254   output = '<div><textarea></textarea></div>';
1255   assert.equal(minify(input, { collapseWhitespace: true }), output);
1256
1257   input = '<div><pRe> $foo = "baz"; </pRe>    </div>';
1258   output = '<div><pre> $foo = "baz"; </pre></div>';
1259   assert.equal(minify(input, { collapseWhitespace: true }), output);
1260   output = '<div><pRe>$foo = "baz";</pRe></div>';
1261   assert.equal(minify(input, { collapseWhitespace: true, caseSensitive: true }), output);
1262
1263   input = '<script type="text/javascript">var = "hello";</script>\r\n\r\n\r\n' +
1264           '<style type="text/css">#foo { color: red;        }          </style>\r\n\r\n\r\n' +
1265           '<div>\r\n  <div>\r\n    <div><!-- hello -->\r\n      <div>' +
1266           '<!--! hello -->\r\n        <div>\r\n          <div class="">\r\n\r\n            ' +
1267           '<textarea disabled="disabled">     this is a textarea </textarea>\r\n          ' +
1268           '</div>\r\n        </div>\r\n      </div>\r\n    </div>\r\n  </div>\r\n</div>' +
1269           '<pre>       \r\nxxxx</pre><span>x</span> <span>Hello</span> <b>billy</b>     \r\n' +
1270           '<input type="text">\r\n<textarea></textarea>\r\n<pre></pre>';
1271   output = '<script type="text/javascript">var = "hello";</script>' +
1272            '<style type="text/css">#foo { color: red;        }</style>' +
1273            '<div><div><div>' +
1274            '<!-- hello --><div><!--! hello --><div><div class="">' +
1275            '<textarea disabled="disabled">     this is a textarea </textarea>' +
1276            '</div></div></div></div></div></div>' +
1277            '<pre>       \r\nxxxx</pre><span>x</span> <span>Hello</span> <b>billy</b> ' +
1278            '<input type="text"> <textarea></textarea><pre></pre>';
1279   assert.equal(minify(input, { collapseWhitespace: true }), output);
1280
1281   input = '<pre title="some title...">   hello     world </pre>';
1282   output = '<pre title="some title...">   hello     world </pre>';
1283   assert.equal(minify(input, { collapseWhitespace: true }), output);
1284
1285   input = '<pre title="some title..."><code>   hello     world </code></pre>';
1286   output = '<pre title="some title..."><code>   hello     world </code></pre>';
1287   assert.equal(minify(input, { collapseWhitespace: true }), output);
1288
1289   input = '<script>alert("foo     bar")    </script>';
1290   output = '<script>alert("foo     bar")</script>';
1291   assert.equal(minify(input, { collapseWhitespace: true }), output);
1292
1293   input = '<style>alert("foo     bar")    </style>';
1294   output = '<style>alert("foo     bar")</style>';
1295   assert.equal(minify(input, { collapseWhitespace: true }), output);
1296 });
1297
1298 QUnit.test('removing empty elements', function(assert) {
1299   var input, output;
1300
1301   assert.equal(minify('<p>x</p>', { removeEmptyElements: true }), '<p>x</p>');
1302   assert.equal(minify('<p></p>', { removeEmptyElements: true }), '');
1303
1304   input = '<p>foo<span>bar</span><span></span></p>';
1305   output = '<p>foo<span>bar</span></p>';
1306   assert.equal(minify(input, { removeEmptyElements: true }), output);
1307
1308   input = '<a href="http://example/com" title="hello world"></a>';
1309   output = '';
1310   assert.equal(minify(input, { removeEmptyElements: true }), output);
1311
1312   input = '<iframe></iframe>';
1313   output = '';
1314   assert.equal(minify(input, { removeEmptyElements: true }), output);
1315
1316   input = '<iframe src="page.html"></iframe>';
1317   assert.equal(minify(input, { removeEmptyElements: true }), input);
1318
1319   input = '<iframe srcdoc="<h1>Foo</h1>"></iframe>';
1320   assert.equal(minify(input, { removeEmptyElements: true }), input);
1321
1322   input = '<video></video>';
1323   output = '';
1324   assert.equal(minify(input, { removeEmptyElements: true }), output);
1325
1326   input = '<video src="preview.ogg"></video>';
1327   assert.equal(minify(input, { removeEmptyElements: true }), input);
1328
1329   input = '<audio autoplay></audio>';
1330   output = '';
1331   assert.equal(minify(input, { removeEmptyElements: true }), output);
1332
1333   input = '<audio src="startup.mp3" autoplay></audio>';
1334   assert.equal(minify(input, { removeEmptyElements: true }), input);
1335
1336   input = '<object type="application/x-shockwave-flash"></object>';
1337   output = '';
1338   assert.equal(minify(input, { removeEmptyElements: true }), output);
1339
1340   input = '<object data="game.swf" type="application/x-shockwave-flash"></object>';
1341   assert.equal(minify(input, { removeEmptyElements: true }), input);
1342
1343   input = '<applet archive="game.zip" width="250" height="150"></applet>';
1344   output = '';
1345   assert.equal(minify(input, { removeEmptyElements: true }), output);
1346
1347   input = '<applet code="game.class" archive="game.zip" width="250" height="150"></applet>';
1348   assert.equal(minify(input, { removeEmptyElements: true }), input);
1349
1350   input = '<textarea cols="10" rows="10"></textarea>';
1351   assert.equal(minify(input, { removeEmptyElements: true }), input);
1352
1353   input = '<div>hello<span>world</span></div>';
1354   assert.equal(minify(input, { removeEmptyElements: true }), input);
1355
1356   input = '<p>x<span title="<" class="blah-moo"></span></p>';
1357   output = '<p>x</p>';
1358   assert.equal(minify(input, { removeEmptyElements: true }), output);
1359
1360   input = '<div>x<div>y <div>blah</div><div></div>foo</div>z</div>';
1361   output = '<div>x<div>y <div>blah</div>foo</div>z</div>';
1362   assert.equal(minify(input, { removeEmptyElements: true }), output);
1363
1364   input = '<img src="">';
1365   assert.equal(minify(input, { removeEmptyElements: true }), input);
1366
1367   input = '<p><!-- x --></p>';
1368   output = '';
1369   assert.equal(minify(input, { removeEmptyElements: true }), output);
1370
1371   input = '<script src="foo.js"></script>';
1372   assert.equal(minify(input, { removeEmptyElements: true }), input);
1373   input = '<script></script>';
1374   assert.equal(minify(input, { removeEmptyElements: true }), '');
1375
1376   input = '<div>after<span></span> </div>';
1377   output = '<div>after </div>';
1378   assert.equal(minify(input, { removeEmptyElements: true }), output);
1379   output = '<div>after</div>';
1380   assert.equal(minify(input, { collapseWhitespace: true, removeEmptyElements: true }), output);
1381
1382   input = '<div>before <span></span></div>';
1383   output = '<div>before </div>';
1384   assert.equal(minify(input, { removeEmptyElements: true }), output);
1385   output = '<div>before</div>';
1386   assert.equal(minify(input, { collapseWhitespace: true, removeEmptyElements: true }), output);
1387
1388   input = '<div>both <span></span> </div>';
1389   output = '<div>both  </div>';
1390   assert.equal(minify(input, { removeEmptyElements: true }), output);
1391   output = '<div>both</div>';
1392   assert.equal(minify(input, { collapseWhitespace: true, removeEmptyElements: true }), output);
1393
1394   input = '<div>unary <span></span><link></div>';
1395   output = '<div>unary <link></div>';
1396   assert.equal(minify(input, { removeEmptyElements: true }), output);
1397   output = '<div>unary<link></div>';
1398   assert.equal(minify(input, { collapseWhitespace: true, removeEmptyElements: true }), output);
1399
1400   input = '<div>Empty <!-- NOT --> </div>';
1401   assert.equal(minify(input, { removeEmptyElements: true }), input);
1402   output = '<div>Empty<!-- NOT --></div>';
1403   assert.equal(minify(input, { collapseWhitespace: true, removeEmptyElements: true }), output);
1404 });
1405
1406 QUnit.test('collapsing boolean attributes', function(assert) {
1407   var input, output;
1408
1409   input = '<input disabled="disabled">';
1410   assert.equal(minify(input, { collapseBooleanAttributes: true }), '<input disabled>');
1411
1412   input = '<input CHECKED = "checked" readonly="readonly">';
1413   assert.equal(minify(input, { collapseBooleanAttributes: true }), '<input checked readonly>');
1414
1415   input = '<option name="blah" selected="selected">moo</option>';
1416   assert.equal(minify(input, { collapseBooleanAttributes: true }), '<option name="blah" selected>moo</option>');
1417
1418   input = '<input autofocus="autofocus">';
1419   assert.equal(minify(input, { collapseBooleanAttributes: true }), '<input autofocus>');
1420
1421   input = '<input required="required">';
1422   assert.equal(minify(input, { collapseBooleanAttributes: true }), '<input required>');
1423
1424   input = '<input multiple="multiple">';
1425   assert.equal(minify(input, { collapseBooleanAttributes: true }), '<input multiple>');
1426
1427   input = '<div Allowfullscreen=foo Async=foo Autofocus=foo Autoplay=foo Checked=foo Compact=foo Controls=foo ' +
1428     'Declare=foo Default=foo Defaultchecked=foo Defaultmuted=foo Defaultselected=foo Defer=foo Disabled=foo ' +
1429     'Enabled=foo Formnovalidate=foo Hidden=foo Indeterminate=foo Inert=foo Ismap=foo Itemscope=foo ' +
1430     'Loop=foo Multiple=foo Muted=foo Nohref=foo Noresize=foo Noshade=foo Novalidate=foo Nowrap=foo Open=foo ' +
1431     'Pauseonexit=foo Readonly=foo Required=foo Reversed=foo Scoped=foo Seamless=foo Selected=foo Sortable=foo ' +
1432     'Truespeed=foo Typemustmatch=foo Visible=foo></div>';
1433   output = '<div allowfullscreen async autofocus autoplay checked compact controls declare default defaultchecked ' +
1434     'defaultmuted defaultselected defer disabled enabled formnovalidate hidden indeterminate inert ' +
1435     'ismap itemscope loop multiple muted nohref noresize noshade novalidate nowrap open pauseonexit readonly ' +
1436     'required reversed scoped seamless selected sortable truespeed typemustmatch visible></div>';
1437   assert.equal(minify(input, { collapseBooleanAttributes: true }), output);
1438   output = '<div Allowfullscreen Async Autofocus Autoplay Checked Compact Controls Declare Default Defaultchecked ' +
1439     'Defaultmuted Defaultselected Defer Disabled Enabled Formnovalidate Hidden Indeterminate Inert ' +
1440     'Ismap Itemscope Loop Multiple Muted Nohref Noresize Noshade Novalidate Nowrap Open Pauseonexit Readonly ' +
1441     'Required Reversed Scoped Seamless Selected Sortable Truespeed Typemustmatch Visible></div>';
1442   assert.equal(minify(input, { collapseBooleanAttributes: true, caseSensitive: true }), output);
1443 });
1444
1445 QUnit.test('collapsing enumerated attributes', function(assert) {
1446   assert.equal(minify('<div draggable="auto"></div>', { collapseBooleanAttributes: true }), '<div draggable></div>');
1447   assert.equal(minify('<div draggable="true"></div>', { collapseBooleanAttributes: true }), '<div draggable="true"></div>');
1448   assert.equal(minify('<div draggable="false"></div>', { collapseBooleanAttributes: true }), '<div draggable="false"></div>');
1449   assert.equal(minify('<div draggable="foo"></div>', { collapseBooleanAttributes: true }), '<div draggable></div>');
1450   assert.equal(minify('<div draggable></div>', { collapseBooleanAttributes: true }), '<div draggable></div>');
1451   assert.equal(minify('<div Draggable="auto"></div>', { collapseBooleanAttributes: true }), '<div draggable></div>');
1452   assert.equal(minify('<div Draggable="true"></div>', { collapseBooleanAttributes: true }), '<div draggable="true"></div>');
1453   assert.equal(minify('<div Draggable="false"></div>', { collapseBooleanAttributes: true }), '<div draggable="false"></div>');
1454   assert.equal(minify('<div Draggable="foo"></div>', { collapseBooleanAttributes: true }), '<div draggable></div>');
1455   assert.equal(minify('<div Draggable></div>', { collapseBooleanAttributes: true }), '<div draggable></div>');
1456   assert.equal(minify('<div draggable="Auto"></div>', { collapseBooleanAttributes: true }), '<div draggable></div>');
1457 });
1458
1459 QUnit.test('keeping trailing slashes in tags', function(assert) {
1460   assert.equal(minify('<img src="test"/>', { keepClosingSlash: true }), '<img src="test"/>');
1461   // https://github.com/kangax/html-minifier/issues/233
1462   assert.equal(minify('<img src="test"/>', { keepClosingSlash: true, removeAttributeQuotes: true }), '<img src=test />');
1463   assert.equal(minify('<img src="test" id=""/>', { keepClosingSlash: true, removeAttributeQuotes: true, removeEmptyAttributes: true }), '<img src=test />');
1464   assert.equal(minify('<img title="foo" src="test"/>', { keepClosingSlash: true, removeAttributeQuotes: true }), '<img title=foo src=test />');
1465 });
1466
1467 QUnit.test('removing optional tags', function(assert) {
1468   var input, output;
1469
1470   input = '<p>foo';
1471   assert.equal(minify(input, { removeOptionalTags: true }), input);
1472
1473   input = '</p>';
1474   output = '<p>';
1475   assert.equal(minify(input, { removeOptionalTags: true }), output);
1476
1477   input = '<body></body>';
1478   output = '';
1479   assert.equal(minify(input, { removeOptionalTags: true }), output);
1480   assert.equal(minify(input, { removeOptionalTags: true, removeEmptyElements: true }), output);
1481
1482   input = '<html><head></head><body></body></html>';
1483   output = '';
1484   assert.equal(minify(input, { removeOptionalTags: true }), output);
1485   assert.equal(minify(input, { removeOptionalTags: true, removeEmptyElements: true }), output);
1486
1487   input = ' <html></html>';
1488   output = ' ';
1489   assert.equal(minify(input, { removeOptionalTags: true }), output);
1490   output = '';
1491   assert.equal(minify(input, { collapseWhitespace: true, removeOptionalTags: true }), output);
1492
1493   input = '<html> </html>';
1494   output = ' ';
1495   assert.equal(minify(input, { removeOptionalTags: true }), output);
1496   output = '';
1497   assert.equal(minify(input, { collapseWhitespace: true, removeOptionalTags: true }), output);
1498
1499   input = '<html></html> ';
1500   output = ' ';
1501   assert.equal(minify(input, { removeOptionalTags: true }), output);
1502   output = '';
1503   assert.equal(minify(input, { collapseWhitespace: true, removeOptionalTags: true }), output);
1504
1505   input = ' <html><body></body></html>';
1506   output = ' ';
1507   assert.equal(minify(input, { removeOptionalTags: true }), output);
1508   output = '';
1509   assert.equal(minify(input, { collapseWhitespace: true, removeOptionalTags: true }), output);
1510
1511   input = '<html> <body></body></html>';
1512   output = ' ';
1513   assert.equal(minify(input, { removeOptionalTags: true }), output);
1514   output = '';
1515   assert.equal(minify(input, { collapseWhitespace: true, removeOptionalTags: true }), output);
1516
1517   input = '<html><body> </body></html>';
1518   output = '<body> ';
1519   assert.equal(minify(input, { removeOptionalTags: true }), output);
1520   output = '';
1521   assert.equal(minify(input, { collapseWhitespace: true, removeOptionalTags: true }), output);
1522
1523   input = '<html><body></body> </html>';
1524   output = ' ';
1525   assert.equal(minify(input, { removeOptionalTags: true }), output);
1526   output = '';
1527   assert.equal(minify(input, { collapseWhitespace: true, removeOptionalTags: true }), output);
1528
1529   input = '<html><body></body></html> ';
1530   output = ' ';
1531   assert.equal(minify(input, { removeOptionalTags: true }), output);
1532   output = '';
1533   assert.equal(minify(input, { collapseWhitespace: true, removeOptionalTags: true }), output);
1534
1535   input = '<html><head><title>hello</title></head><body><p>foo<span>bar</span></p></body></html>';
1536   assert.equal(minify(input), input);
1537   output = '<title>hello</title><p>foo<span>bar</span>';
1538   assert.equal(minify(input, { removeOptionalTags: true }), output);
1539
1540   input = '<html lang=""><head><title>hello</title></head><body style=""><p>foo<span>bar</span></p></body></html>';
1541   output = '<html lang=""><title>hello</title><body style=""><p>foo<span>bar</span>';
1542   assert.equal(minify(input, { removeOptionalTags: true }), output);
1543   output = '<title>hello</title><p>foo<span>bar</span>';
1544   assert.equal(minify(input, { removeOptionalTags: true, removeEmptyAttributes: true }), output);
1545
1546   input = '<html><head><title>a</title><link href="b.css" rel="stylesheet"/></head><body><a href="c.html"></a><div class="d"><input value="e"/></div></body></html>';
1547   output = '<title>a</title><link href="b.css" rel="stylesheet"><a href="c.html"></a><div class="d"><input value="e"></div>';
1548   assert.equal(minify(input, { removeOptionalTags: true }), output);
1549
1550   input = '<!DOCTYPE html><html><head><title>Blah</title></head><body><div><p>This is some text in a div</p><details>Followed by some details</details></div><div><p>This is some more text in a div</p></div></body></html>';
1551   output = '<!DOCTYPE html><title>Blah</title><div><p>This is some text in a div<details>Followed by some details</details></div><div><p>This is some more text in a div</div>';
1552   assert.equal(minify(input, { removeOptionalTags: true }), output);
1553
1554   input = '<!DOCTYPE html><html><head><title>Blah</title></head><body><noscript><p>This is some text in a noscript</p><details>Followed by some details</details></noscript><noscript><p>This is some more text in a noscript</p></noscript></body></html>';
1555   output = '<!DOCTYPE html><title>Blah</title><body><noscript><p>This is some text in a noscript<details>Followed by some details</details></noscript><noscript><p>This is some more text in a noscript</p></noscript>';
1556   assert.equal(minify(input, { removeOptionalTags: true }), output);
1557
1558   input = '<md-list-item ui-sref=".app-config"><md-icon md-font-icon="mdi-settings"></md-icon><p translate>Configure</p></md-list-item>';
1559   assert.equal(minify(input, { removeOptionalTags: true }), input);
1560 });
1561
1562 QUnit.test('removing optional tags in tables', function(assert) {
1563   var input, output;
1564
1565   input = '<table>' +
1566             '<thead><tr><th>foo</th><th>bar</th> <th>baz</th></tr></thead> ' +
1567             '<tbody><tr><td>boo</td><td>moo</td><td>loo</td></tr> </tbody>' +
1568             '<tfoot><tr><th>baz</th> <th>qux</th><td>boo</td></tr></tfoot>' +
1569           '</table>';
1570   assert.equal(minify(input), input);
1571
1572   output = '<table>' +
1573              '<thead><tr><th>foo<th>bar</th> <th>baz</thead> ' +
1574              '<tr><td>boo<td>moo<td>loo</tr> ' +
1575              '<tfoot><tr><th>baz</th> <th>qux<td>boo' +
1576            '</table>';
1577   assert.equal(minify(input, { removeOptionalTags: true }), output);
1578
1579   output = '<table>' +
1580              '<thead><tr><th>foo<th>bar<th>baz' +
1581              '<tbody><tr><td>boo<td>moo<td>loo' +
1582              '<tfoot><tr><th>baz<th>qux<td>boo' +
1583            '</table>';
1584   assert.equal(minify(input, { collapseWhitespace: true, removeOptionalTags: true }), output);
1585   assert.equal(minify(output, { collapseWhitespace: true, removeOptionalTags: true }), output);
1586
1587   input = '<table>' +
1588             '<caption>foo</caption>' +
1589             '<!-- blah -->' +
1590             '<colgroup><col span="2"><col></colgroup>' +
1591             '<!-- blah -->' +
1592             '<tbody><tr><th>bar</th><td>baz</td><th>qux</th></tr></tbody>' +
1593           '</table>';
1594   assert.equal(minify(input), input);
1595
1596   output = '<table>' +
1597              '<caption>foo</caption>' +
1598              '<!-- blah -->' +
1599              '<col span="2"><col></colgroup>' +
1600              '<!-- blah -->' +
1601              '<tr><th>bar<td>baz<th>qux' +
1602            '</table>';
1603   assert.equal(minify(input, { removeOptionalTags: true }), output);
1604   assert.equal(minify(output, { removeOptionalTags: true }), output);
1605
1606   output = '<table>' +
1607              '<caption>foo' +
1608              '<col span="2"><col>' +
1609              '<tr><th>bar<td>baz<th>qux' +
1610            '</table>';
1611   assert.equal(minify(input, { removeComments: true, removeOptionalTags: true }), output);
1612
1613   input = '<table>' +
1614             '<tbody></tbody>' +
1615           '</table>';
1616   assert.equal(minify(input), input);
1617
1618   output = '<table><tbody></table>';
1619   assert.equal(minify(input, { removeOptionalTags: true }), output);
1620 });
1621
1622 QUnit.test('removing optional tags in options', function(assert) {
1623   var input, output;
1624
1625   input = '<select><option>foo</option><option>bar</option></select>';
1626   output = '<select><option>foo<option>bar</select>';
1627   assert.equal(minify(input, { removeOptionalTags: true }), output);
1628
1629   input = '<select>\n' +
1630           '  <option>foo</option>\n' +
1631           '  <option>bar</option>\n' +
1632           '</select>';
1633   assert.equal(minify(input, { removeOptionalTags: true }), input);
1634   output = '<select><option>foo<option>bar</select>';
1635   assert.equal(minify(input, { removeOptionalTags: true, collapseWhitespace: true }), output);
1636   output = '<select> <option>foo</option> <option>bar</option> </select>';
1637   assert.equal(minify(input, { removeOptionalTags: true, collapseWhitespace: true, conservativeCollapse: true }), output);
1638
1639   // example from htmldog.com
1640   input = '<select name="catsndogs">' +
1641             '<optgroup label="Cats">' +
1642               '<option>Tiger</option><option>Leopard</option><option>Lynx</option>' +
1643             '</optgroup>' +
1644             '<optgroup label="Dogs">' +
1645               '<option>Grey Wolf</option><option>Red Fox</option><option>Fennec</option>' +
1646             '</optgroup>' +
1647           '</select>';
1648
1649   output = '<select name="catsndogs">' +
1650              '<optgroup label="Cats">' +
1651                '<option>Tiger<option>Leopard<option>Lynx' +
1652              '<optgroup label="Dogs">' +
1653                '<option>Grey Wolf<option>Red Fox<option>Fennec' +
1654            '</select>';
1655
1656   assert.equal(minify(input, { removeOptionalTags: true }), output);
1657 });
1658
1659 QUnit.test('custom components', function(assert) {
1660   var input = '<custom-component>Oh, my.</custom-component>';
1661   var output = '<custom-component>Oh, my.</custom-component>';
1662   assert.equal(minify(input), output);
1663 });
1664
1665 QUnit.test('HTML4: anchor with inline elements', function(assert) {
1666   var input = '<a href="#"><span>Well, look at me! I\'m a span!</span></a>';
1667   assert.equal(minify(input, { html5: false }), input);
1668 });
1669
1670 QUnit.test('HTML5: anchor with inline elements', function(assert) {
1671   var input = '<a href="#"><span>Well, look at me! I\'m a span!</span></a>';
1672   assert.equal(minify(input, { html5: true }), input);
1673 });
1674
1675 QUnit.test('HTML4: anchor with block elements', function(assert) {
1676   var input = '<a href="#"><div>Well, look at me! I\'m a div!</div></a>';
1677   var output = '<a href="#"></a><div>Well, look at me! I\'m a div!</div>';
1678   assert.equal(minify(input, { html5: false }), output);
1679 });
1680
1681 QUnit.test('HTML5: anchor with block elements', function(assert) {
1682   var input = '<a href="#"><div>Well, look at me! I\'m a div!</div></a>';
1683   var output = '<a href="#"><div>Well, look at me! I\'m a div!</div></a>';
1684   assert.equal(minify(input, { html5: true }), output);
1685 });
1686
1687 QUnit.test('HTML5: enabled by default', function(assert) {
1688   var input = '<a href="#"><div>Well, look at me! I\'m a div!</div></a>';
1689   assert.equal(minify(input, { html5: true }), minify(input));
1690 });
1691
1692 QUnit.test('phrasing content', function(assert) {
1693   var input, output;
1694
1695   input = '<p>a<div>b</div>';
1696   output = '<p>a</p><div>b</div>';
1697   assert.equal(minify(input, { html5: true }), output);
1698   output = '<p>a<div>b</div></p>';
1699   assert.equal(minify(input, { html5: false }), output);
1700
1701   input = '<label>a<div>b</div>c</label>';
1702   assert.equal(minify(input, { html5: true }), input);
1703 });
1704
1705 // https://github.com/kangax/html-minifier/issues/888
1706 QUnit.test('ul/ol should be phrasing content', function(assert) {
1707   var input, output;
1708
1709   input = '<p>a<ul><li>item</li></ul>';
1710   output = '<p>a</p><ul><li>item</li></ul>';
1711   assert.equal(minify(input, { html5: true }), output);
1712
1713   output = '<p>a<ul><li>item</ul>';
1714   assert.equal(minify(input, { html5: true, removeOptionalTags: true }), output);
1715
1716   output = '<p>a<ul><li>item</li></ul></p>';
1717   assert.equal(minify(input, { html5: false }), output);
1718
1719   input = '<p>a<ol><li>item</li></ol></p>';
1720   output = '<p>a</p><ol><li>item</li></ol><p></p>';
1721   assert.equal(minify(input, { html5: true }), output);
1722
1723   output = '<p>a<ol><li>item</ol><p>';
1724   assert.equal(minify(input, { html5: true, removeOptionalTags: true }), output);
1725
1726   output = '<p>a</p><ol><li>item</li></ol>';
1727   assert.equal(minify(input, { html5: true, removeEmptyElements: true }), output);
1728 });
1729
1730 QUnit.test('phrasing content with Web Components', function(assert) {
1731   var input = '<span><phrasing-element></phrasing-element></span>';
1732   var output = '<span><phrasing-element></phrasing-element></span>';
1733   assert.equal(minify(input, { html5: true }), output);
1734 });
1735
1736 // https://github.com/kangax/html-minifier/issues/10
1737 QUnit.test('Ignore custom fragments', function(assert) {
1738   var input, output;
1739   var reFragments = [/<\?[^?]+\?>/, /<%[^%]+%>/, /\{\{[^}]*\}\}/];
1740
1741   input = 'This is the start. <% ... %>\r\n<%= ... %>\r\n<? ... ?>\r\n<!-- This is the middle, and a comment. -->\r\nNo comment, but middle.\r\n{{ ... }}\r\n<?php ... ?>\r\n<?xml ... ?>\r\nHello, this is the end!';
1742   output = 'This is the start. <% ... %> <%= ... %> <? ... ?> No comment, but middle. {{ ... }} <?php ... ?> <?xml ... ?> Hello, this is the end!';
1743   assert.equal(minify(input, {}), input);
1744   assert.equal(minify(input, { removeComments: true, collapseWhitespace: true }), output);
1745   assert.equal(minify(input, {
1746     removeComments: true,
1747     collapseWhitespace: true,
1748     ignoreCustomFragments: reFragments
1749   }), output);
1750
1751   output = 'This is the start. <% ... %>\n<%= ... %>\n<? ... ?>\nNo comment, but middle. {{ ... }}\n<?php ... ?>\n<?xml ... ?>\nHello, this is the end!';
1752   assert.equal(minify(input, {
1753     removeComments: true,
1754     collapseWhitespace: true,
1755     preserveLineBreaks: true
1756   }), output);
1757
1758   output = 'This is the start. <% ... %>\n<%= ... %>\n<? ... ?>\nNo comment, but middle.\n{{ ... }}\n<?php ... ?>\n<?xml ... ?>\nHello, this is the end!';
1759   assert.equal(minify(input, {
1760     removeComments: true,
1761     collapseWhitespace: true,
1762     preserveLineBreaks: true,
1763     ignoreCustomFragments: reFragments
1764   }), output);
1765
1766   input = '{{ if foo? }}\r\n  <div class="bar">\r\n    ...\r\n  </div>\r\n{{ end \n}}';
1767   output = '{{ if foo? }}<div class="bar">...</div>{{ end }}';
1768   assert.equal(minify(input, {}), input);
1769   assert.equal(minify(input, { collapseWhitespace: true }), output);
1770   assert.equal(minify(input, { collapseWhitespace: true, ignoreCustomFragments: [] }), output);
1771
1772   output = '{{ if foo? }} <div class="bar">...</div> {{ end \n}}';
1773   assert.equal(minify(input, { collapseWhitespace: true, ignoreCustomFragments: reFragments }), output);
1774
1775   output = '{{ if foo? }}\n<div class="bar">\n...\n</div>\n{{ end \n}}';
1776   assert.equal(minify(input, {
1777     collapseWhitespace: true,
1778     preserveLineBreaks: true,
1779     ignoreCustomFragments: reFragments
1780   }), output);
1781
1782   input = '<a class="<% if foo? %>bar<% end %> {{ ... }}"></a>';
1783   assert.equal(minify(input, {}), input);
1784   assert.equal(minify(input, { ignoreCustomFragments: reFragments }), input);
1785
1786   input = '<img src="{% static "images/logo.png" %}">';
1787   output = '<img src="{% static "images/logo.png" %}">';
1788   assert.equal(minify(input, { ignoreCustomFragments: [/\{%[^%]*?%\}/g] }), output);
1789
1790   input = '<p{% if form.name.errors %}class=\'error\'{% endif %}>' +
1791             '{{ form.name.label_tag }}' +
1792             '{{ form.name }}' +
1793             ' <label>{{ label }}</label> ' +
1794             '{% if form.name.errors %}' +
1795             '{% for error in form.name.errors %}' +
1796             '<span class=\'error_msg\' style=\'color:#ff0000\'>{{ error }}</span>' +
1797             '{% endfor %}' +
1798             '{% endif %}' +
1799           '</p>';
1800   assert.equal(minify(input, {
1801     ignoreCustomFragments: [
1802       /\{%[\s\S]*?%\}/g,
1803       /\{\{[\s\S]*?\}\}/g
1804     ],
1805     quoteCharacter: '\''
1806   }), input);
1807   output = '<p {% if form.name.errors %} class=\'error\' {% endif %}>' +
1808              '{{ form.name.label_tag }}' +
1809              '{{ form.name }}' +
1810              ' <label>{{ label }}</label> ' +
1811              '{% if form.name.errors %}' +
1812              '{% for error in form.name.errors %}' +
1813              '<span class=\'error_msg\' style=\'color:#ff0000\'>{{ error }}</span>' +
1814              '{% endfor %}' +
1815              '{% endif %}' +
1816            '</p>';
1817   assert.equal(minify(input, {
1818     ignoreCustomFragments: [
1819       /\{%[\s\S]*?%\}/g,
1820       /\{\{[\s\S]*?\}\}/g
1821     ],
1822     quoteCharacter: '\'',
1823     collapseWhitespace: true
1824   }), output);
1825
1826   input = '<a href="/legal.htm"<?php echo e(Request::path() == \'/\' ? \' rel="nofollow"\':\'\'); ?>>Legal Notices</a>';
1827   assert.equal(minify(input, {
1828     ignoreCustomFragments: [
1829       /<\?php[\s\S]*?\?>/g
1830     ]
1831   }), input);
1832
1833   input = '<input type="checkbox"<%= (model.isChecked ? \'checked="checked"\' : \'\') %>>';
1834   assert.equal(minify(input, {
1835     ignoreCustomFragments: [
1836       /<%=[\s\S]*?%>/g
1837     ]
1838   }), input);
1839
1840   input = '<div' +
1841             '{{IF text}}' +
1842             'data-yashareDescription="{{shorted(text, 300)}}"' +
1843             '{{END IF}}></div>';
1844   assert.equal(minify(input, {
1845     ignoreCustomFragments: [
1846       /\{\{[\s\S]*?\}\}/g
1847     ],
1848     caseSensitive: true
1849   }), input);
1850
1851   input = '<img class="{% foo %} {% bar %}">';
1852   assert.equal(minify(input, {
1853     ignoreCustomFragments: [
1854       /\{%[^%]*?%\}/g
1855     ]
1856   }), input);
1857   // trimCustomFragments withOUT collapseWhitespace, does
1858   // not break the "{% foo %} {% bar %}" test
1859   assert.equal(minify(input, {
1860     ignoreCustomFragments: [
1861       /\{%[^%]*?%\}/g
1862     ],
1863     trimCustomFragments: true
1864   }), input);
1865   // trimCustomFragments WITH collapseWhitespace, changes output
1866   output = '<img class="{% foo %}{% bar %}">';
1867   assert.equal(minify(input, {
1868     ignoreCustomFragments: [
1869       /\{%[^%]*?%\}/g
1870     ],
1871     collapseWhitespace: true,
1872     trimCustomFragments: true
1873   }), output);
1874
1875   input = '<img class="titi.<%=tsItem_[0]%>">';
1876   assert.equal(minify(input), input);
1877   assert.equal(minify(input, {
1878     collapseWhitespace: true
1879   }), input);
1880
1881   input = '<table id="<?php echo $this->escapeHtmlAttr($this->table_id); ?>"></table>';
1882   assert.equal(minify(input), input);
1883   assert.equal(minify(input, {
1884     collapseWhitespace: true
1885   }), input);
1886
1887   input = '<!--{{comment}}-->{{if a}}<div>b</div>{{/if}}';
1888   assert.equal(minify(input), input);
1889   output = '{{if a}}<div>b</div>{{/if}}';
1890   assert.equal(minify(input, {
1891     removeComments: true,
1892     ignoreCustomFragments: [
1893       /\{\{.*?\}\}/g
1894     ]
1895   }), output);
1896
1897   // https://github.com/kangax/html-minifier/issues/722
1898   input = '<? echo "foo"; ?> <span>bar</span>';
1899   assert.equal(minify(input), input);
1900   assert.equal(minify(input, {
1901     collapseWhitespace: true
1902   }), input);
1903   output = '<? echo "foo"; ?><span>bar</span>';
1904   assert.equal(minify(input, {
1905     collapseWhitespace: true,
1906     trimCustomFragments: true
1907   }), output);
1908
1909   input = ' <? echo "foo"; ?> bar';
1910   assert.equal(minify(input), input);
1911   output = '<? echo "foo"; ?> bar';
1912   assert.equal(minify(input, {
1913     collapseWhitespace: true
1914   }), output);
1915   output = '<? echo "foo"; ?>bar';
1916   assert.equal(minify(input, {
1917     collapseWhitespace: true,
1918     trimCustomFragments: true
1919   }), output);
1920
1921   input = '<span>foo</span> <? echo "bar"; ?> baz';
1922   assert.equal(minify(input), input);
1923   assert.equal(minify(input, {
1924     collapseWhitespace: true
1925   }), input);
1926   output = '<span>foo</span><? echo "bar"; ?>baz';
1927   assert.equal(minify(input, {
1928     collapseWhitespace: true,
1929     trimCustomFragments: true
1930   }), output);
1931
1932   input = '<span>foo</span> <? echo "bar"; ?> <? echo "baz"; ?> <span>foo</span>';
1933   assert.equal(minify(input), input);
1934   assert.equal(minify(input, {
1935     collapseWhitespace: true
1936   }), input);
1937   output = '<span>foo</span><? echo "bar"; ?><? echo "baz"; ?><span>foo</span>';
1938   assert.equal(minify(input, {
1939     collapseWhitespace: true,
1940     trimCustomFragments: true
1941   }), output);
1942
1943   input = 'foo <WC@bar> baz moo </WC@bar> loo';
1944   assert.equal(minify(input, {
1945     collapseWhitespace: true,
1946     ignoreCustomFragments: [
1947       /<(WC@[\s\S]*?)>(.*?)<\/\1>/
1948     ]
1949   }), input);
1950   output = 'foo<wc @bar>baz moo</wc>loo';
1951   assert.equal(minify(input, {
1952     collapseWhitespace: true
1953   }), output);
1954
1955   input = '<link href="<?php echo \'http://foo/\' ?>">';
1956   assert.equal(minify(input), input);
1957   assert.equal(minify(input, { removeAttributeQuotes: true }), input);
1958
1959   input = '<pre>\nfoo\n<? bar ?>\nbaz\n</pre>';
1960   assert.equal(minify(input), input);
1961   assert.equal(minify(input, { collapseWhitespace: true }), input);
1962 });
1963
1964 QUnit.test('bootstrap\'s span > button > span', function(assert) {
1965   var input = '<span class="input-group-btn">' +
1966                 '\n  <button class="btn btn-default" type="button">' +
1967                   '\n    <span class="glyphicon glyphicon-search"></span>' +
1968                 '\n  </button>' +
1969               '</span>';
1970   var output = '<span class=input-group-btn><button class="btn btn-default" type=button><span class="glyphicon glyphicon-search"></span></button></span>';
1971   assert.equal(minify(input, { collapseWhitespace: true, removeAttributeQuotes: true }), output);
1972 });
1973
1974 QUnit.test('caseSensitive', function(assert) {
1975   var input = '<div mixedCaseAttribute="value"></div>';
1976   var caseSensitiveOutput = '<div mixedCaseAttribute="value"></div>';
1977   var caseInSensitiveOutput = '<div mixedcaseattribute="value"></div>';
1978   assert.equal(minify(input), caseInSensitiveOutput);
1979   assert.equal(minify(input, { caseSensitive: true }), caseSensitiveOutput);
1980 });
1981
1982 QUnit.test('source & track', function(assert) {
1983   var input = '<audio controls="controls">' +
1984                 '<source src="foo.wav">' +
1985                 '<source src="far.wav">' +
1986                 '<source src="foobar.wav">' +
1987                 '<track kind="captions" src="sampleCaptions.vtt" srclang="en">' +
1988               '</audio>';
1989   assert.equal(minify(input), input);
1990   assert.equal(minify(input, { removeOptionalTags: true }), input);
1991 });
1992
1993 QUnit.test('mixed html and svg', function(assert) {
1994   var input = '<html><body>\n' +
1995     '  <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"\n' +
1996     '     width="612px" height="502.174px" viewBox="0 65.326 612 502.174" enable-background="new 0 65.326 612 502.174"\n' +
1997     '     xml:space="preserve" class="logo">' +
1998     '' +
1999     '    <ellipse class="ground" cx="283.5" cy="487.5" rx="259" ry="80"/>' +
2000     '    <polygon points="100,10 40,198 190,78 10,78 160,198"\n' +
2001     '      style="fill:lime;stroke:purple;stroke-width:5;fill-rule:evenodd;" />\n' +
2002     '    <filter id="pictureFilter">\n' +
2003     '      <feGaussianBlur stdDeviation="15" />\n' +
2004     '    </filter>\n' +
2005     '  </svg>\n' +
2006     '</body></html>';
2007   var output = '<html><body>' +
2008     '<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="612px" height="502.174px" viewBox="0 65.326 612 502.174" enable-background="new 0 65.326 612 502.174" xml:space="preserve" class="logo">' +
2009     '<ellipse class="ground" cx="283.5" cy="487.5" rx="259" ry="80"/>' +
2010     '<polygon points="100,10 40,198 190,78 10,78 160,198" style="fill:lime;stroke:purple;stroke-width:5;fill-rule:evenodd;"/>' +
2011     '<filter id="pictureFilter"><feGaussianBlur stdDeviation="15"/></filter>' +
2012     '</svg>' +
2013     '</body></html>';
2014   // Should preserve case-sensitivity and closing slashes within svg tags
2015   assert.equal(minify(input, { collapseWhitespace: true }), output);
2016 });
2017
2018 QUnit.test('nested quotes', function(assert) {
2019   var input, output;
2020
2021   input = '<div data=\'{"test":"\\"test\\""}\'></div>';
2022   assert.equal(minify(input), input);
2023   assert.equal(minify(input, { quoteCharacter: '\'' }), input);
2024
2025   output = '<div data="{&#34;test&#34;:&#34;\\&#34;test\\&#34;&#34;}"></div>';
2026   assert.equal(minify(input, { quoteCharacter: '"' }), output);
2027 });
2028
2029 QUnit.test('script minification', function(assert) {
2030   var input, output;
2031
2032   input = '<script></script>(function(){ var foo = 1; var bar = 2; alert(foo + " " + bar); })()';
2033
2034   assert.equal(minify(input, { minifyJS: true }), input);
2035
2036   input = '<script>(function(){ var foo = 1; var bar = 2; alert(foo + " " + bar); })()</script>';
2037   output = '<script>alert("1 2")</script>';
2038
2039   assert.equal(minify(input, { minifyJS: true }), output);
2040
2041   input = '<script type="text/JavaScript">(function(){ var foo = 1; var bar = 2; alert(foo + " " + bar); })()</script>';
2042   output = '<script type="text/JavaScript">alert("1 2")</script>';
2043
2044   assert.equal(minify(input, { minifyJS: true }), output);
2045
2046   input = '<script type="application/javascript;version=1.8">(function(){ var foo = 1; var bar = 2; alert(foo + " " + bar); })()</script>';
2047   output = '<script type="application/javascript;version=1.8">alert("1 2")</script>';
2048
2049   assert.equal(minify(input, { minifyJS: true }), output);
2050
2051   input = '<script type=" application/javascript  ; charset=utf-8 ">(function(){ var foo = 1; var bar = 2; alert(foo + " " + bar); })()</script>';
2052   output = '<script type="application/javascript;charset=utf-8">alert("1 2")</script>';
2053
2054   assert.equal(minify(input, { minifyJS: true }), output);
2055
2056   input = '<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({\'gtm.start\':new Date().getTime(),event:\'gtm.js\'});var f=d.getElementsByTagName(s)[0],j=d.createElement(s),dl=l!=\'dataLayer\'?\'&l=\'+l:\'\';j.async=true;j.src=\'//www.googletagmanager.com/gtm.js?id=\'+i+dl;f.parentNode.insertBefore(j,f);})(window,document,\'script\',\'dataLayer\',\'GTM-67NT\');</script>';
2057   output = '<script>!function(w,d,s,l,i){w[l]=w[l]||[],w[l].push({"gtm.start":(new Date).getTime(),event:"gtm.js"});var f=d.getElementsByTagName(s)[0],j=d.createElement(s);j.async=!0,j.src="//www.googletagmanager.com/gtm.js?id=GTM-67NT",f.parentNode.insertBefore(j,f)}(window,document,"script","dataLayer")</script>';
2058
2059   assert.equal(minify(input, { minifyJS: { mangle: false } }), output);
2060
2061   input = '<script>\n' +
2062           '  <!--\n' +
2063           '    Platform.Mobile.Bootstrap.init(function () {\n' +
2064           '      Platform.Mobile.Core.Navigation.go("Login", {\n' +
2065           '        "error": ""\n' +
2066           '      });\n' +
2067           '    });\n' +
2068           '  //-->\n' +
2069           '</script>';
2070   output = '<script>Platform.Mobile.Bootstrap.init(function(){Platform.Mobile.Core.Navigation.go("Login",{error:""})})</script>';
2071
2072   assert.equal(minify(input, { minifyJS: true }), output);
2073 });
2074
2075 QUnit.test('minification of scripts with different mimetypes', function(assert) {
2076   var input, output;
2077
2078   input = '<script type="">function f(){  return 1  }</script>';
2079   output = '<script type="">function f(){return 1}</script>';
2080   assert.equal(minify(input, { minifyJS: true }), output);
2081
2082   input = '<script type="text/javascript">function f(){  return 1  }</script>';
2083   output = '<script type="text/javascript">function f(){return 1}</script>';
2084   assert.equal(minify(input, { minifyJS: true }), output);
2085
2086   input = '<script foo="bar">function f(){  return 1  }</script>';
2087   output = '<script foo="bar">function f(){return 1}</script>';
2088   assert.equal(minify(input, { minifyJS: true }), output);
2089
2090   input = '<script type="text/ecmascript">function f(){  return 1  }</script>';
2091   output = '<script type="text/ecmascript">function f(){return 1}</script>';
2092   assert.equal(minify(input, { minifyJS: true }), output);
2093
2094   input = '<script type="application/javascript">function f(){  return 1  }</script>';
2095   output = '<script type="application/javascript">function f(){return 1}</script>';
2096   assert.equal(minify(input, { minifyJS: true }), output);
2097
2098   input = '<script type="boo">function f(){  return 1  }</script>';
2099   assert.equal(minify(input, { minifyJS: true }), input);
2100
2101   input = '<script type="text/html"><!-- ko if: true -->\n\n\n<div></div>\n\n\n<!-- /ko --></script>';
2102   assert.equal(minify(input, { minifyJS: true }), input);
2103 });
2104
2105 QUnit.test('minification of scripts with custom fragments', function(assert) {
2106   var input, output;
2107
2108   input = '<script><?php ?></script>';
2109   assert.equal(minify(input, { minifyJS: true }), input);
2110   assert.equal(minify(input, { collapseWhitespace: true, minifyJS: true }), input);
2111   assert.equal(minify(input, {
2112     collapseWhitespace: true,
2113     minifyJS: true,
2114     preserveLineBreaks: true
2115   }), input);
2116
2117   input = '<script>\n<?php ?></script>';
2118   assert.equal(minify(input, { minifyJS: true }), input);
2119   output = '<script> <?php ?></script>';
2120   assert.equal(minify(input, { collapseWhitespace: true, minifyJS: true }), output);
2121   assert.equal(minify(input, {
2122     collapseWhitespace: true,
2123     minifyJS: true,
2124     preserveLineBreaks: true
2125   }), input);
2126
2127   input = '<script><?php ?>\n</script>';
2128   assert.equal(minify(input, { minifyJS: true }), input);
2129   output = '<script><?php ?> </script>';
2130   assert.equal(minify(input, { collapseWhitespace: true, minifyJS: true }), output);
2131   assert.equal(minify(input, {
2132     collapseWhitespace: true,
2133     minifyJS: true,
2134     preserveLineBreaks: true
2135   }), input);
2136
2137   input = '<script>\n<?php ?>\n</script>';
2138   assert.equal(minify(input, { minifyJS: true }), input);
2139   output = '<script> <?php ?> </script>';
2140   assert.equal(minify(input, { collapseWhitespace: true, minifyJS: true }), output);
2141   assert.equal(minify(input, {
2142     collapseWhitespace: true,
2143     minifyJS: true,
2144     preserveLineBreaks: true
2145   }), input);
2146
2147   input = '<script>// <% ... %></script>';
2148   output = '<script></script>';
2149   assert.equal(minify(input, { minifyJS: true }), output);
2150   assert.equal(minify(input, { collapseWhitespace: true, minifyJS: true }), output);
2151   assert.equal(minify(input, {
2152     collapseWhitespace: true,
2153     minifyJS: true,
2154     preserveLineBreaks: true
2155   }), output);
2156
2157   input = '<script>// \n<% ... %></script>';
2158   output = '<script> \n<% ... %></script>';
2159   assert.equal(minify(input, { minifyJS: true }), output);
2160   output = '<script> <% ... %></script>';
2161   assert.equal(minify(input, { collapseWhitespace: true, minifyJS: true }), output);
2162   output = '<script>\n<% ... %></script>';
2163   assert.equal(minify(input, {
2164     collapseWhitespace: true,
2165     minifyJS: true,
2166     preserveLineBreaks: true
2167   }), output);
2168
2169   input = '<script>// <% ... %>\n</script>';
2170   output = '<script></script>';
2171   assert.equal(minify(input, { minifyJS: true }), output);
2172   assert.equal(minify(input, { collapseWhitespace: true, minifyJS: true }), output);
2173   assert.equal(minify(input, {
2174     collapseWhitespace: true,
2175     minifyJS: true,
2176     preserveLineBreaks: true
2177   }), output);
2178
2179   input = '<script>// \n<% ... %>\n</script>';
2180   output = '<script> \n<% ... %>\n</script>';
2181   assert.equal(minify(input, { minifyJS: true }), output);
2182   output = '<script> <% ... %> </script>';
2183   assert.equal(minify(input, { collapseWhitespace: true, minifyJS: true }), output);
2184   output = '<script>\n<% ... %>\n</script>';
2185   assert.equal(minify(input, {
2186     collapseWhitespace: true,
2187     minifyJS: true,
2188     preserveLineBreaks: true
2189   }), output);
2190
2191   input = '<script>function f(){  return <?php ?>  }</script>';
2192   output = '<script>function f(){return <?php ?>  }</script>';
2193   assert.equal(minify(input, { minifyJS: true }), output);
2194   output = '<script>function f(){return <?php ?> }</script>';
2195   assert.equal(minify(input, { collapseWhitespace: true, minifyJS: true }), output);
2196
2197   input = '<script>function f(){  return "<?php ?>"  }</script>';
2198   output = '<script>function f(){return"<?php ?>"}</script>';
2199   assert.equal(minify(input, { minifyJS: true }), output);
2200   assert.equal(minify(input, { collapseWhitespace: true, minifyJS: true }), output);
2201 });
2202
2203 QUnit.test('event minification', function(assert) {
2204   var input, output;
2205
2206   input = '<div only="alert(a + b)" one=";return false;"></div>';
2207   assert.equal(minify(input, { minifyJS: true }), input);
2208
2209   input = '<div onclick="alert(a + b)"></div>';
2210   output = '<div onclick="alert(a+b)"></div>';
2211   assert.equal(minify(input, { minifyJS: true }), output);
2212
2213   input = '<a href="/" onclick="this.href = getUpdatedURL (this.href);return true;">test</a>';
2214   output = '<a href="/" onclick="return this.href=getUpdatedURL(this.href),!0">test</a>';
2215   assert.equal(minify(input, { minifyJS: true }), output);
2216
2217   input = '<a onclick="try{ dcsMultiTrack(\'DCS.dcsuri\',\'USPS\',\'WT.ti\') }catch(e){}"> foobar</a>';
2218   output = '<a onclick=\'try{dcsMultiTrack("DCS.dcsuri","USPS","WT.ti")}catch(e){}\'> foobar</a>';
2219   assert.equal(minify(input, { minifyJS: { mangle: false } }), output);
2220   assert.equal(minify(input, { minifyJS: { mangle: false }, quoteCharacter: '\'' }), output);
2221
2222   input = '<a onclick="try{ dcsMultiTrack(\'DCS.dcsuri\',\'USPS\',\'WT.ti\') }catch(e){}"> foobar</a>';
2223   output = '<a onclick="try{dcsMultiTrack(&#34;DCS.dcsuri&#34;,&#34;USPS&#34;,&#34;WT.ti&#34;)}catch(e){}"> foobar</a>';
2224   assert.equal(minify(input, { minifyJS: { mangle: false }, quoteCharacter: '"' }), output);
2225
2226   input = '<a onClick="_gaq.push([\'_trackEvent\', \'FGF\', \'banner_click\']);"></a>';
2227   output = '<a onclick=\'_gaq.push(["_trackEvent","FGF","banner_click"])\'></a>';
2228   assert.equal(minify(input, { minifyJS: true }), output);
2229   assert.equal(minify(input, { minifyJS: true, quoteCharacter: '\'' }), output);
2230
2231   input = '<a onClick="_gaq.push([\'_trackEvent\', \'FGF\', \'banner_click\']);"></a>';
2232   output = '<a onclick="_gaq.push([&#34;_trackEvent&#34;,&#34;FGF&#34;,&#34;banner_click&#34;])"></a>';
2233   assert.equal(minify(input, { minifyJS: true, quoteCharacter: '"' }), output);
2234
2235   input = '<button type="button" onclick=";return false;" id="appbar-guide-button"></button>';
2236   output = '<button type="button" onclick="return!1" id="appbar-guide-button"></button>';
2237   assert.equal(minify(input, { minifyJS: true }), output);
2238
2239   input = '<button type="button" onclick=";return false;" ng-click="a(1 + 2)" data-click="a(1 + 2)"></button>';
2240   output = '<button type="button" onclick="return!1" ng-click="a(1 + 2)" data-click="a(1 + 2)"></button>';
2241   assert.equal(minify(input, { minifyJS: true }), output);
2242   assert.equal(minify(input, { minifyJS: true, customEventAttributes: [] }), input);
2243   output = '<button type="button" onclick=";return false;" ng-click="a(3)" data-click="a(1 + 2)"></button>';
2244   assert.equal(minify(input, { minifyJS: true, customEventAttributes: [/^ng-/] }), output);
2245   output = '<button type="button" onclick="return!1" ng-click="a(3)" data-click="a(1 + 2)"></button>';
2246   assert.equal(minify(input, { minifyJS: true, customEventAttributes: [/^on/, /^ng-/] }), output);
2247
2248   input = '<div onclick="<?= b ?>"></div>';
2249   assert.equal(minify(input, { minifyJS: true }), input);
2250
2251   input = '<div onclick="alert(a + <?= b ?>)"></div>';
2252   output = '<div onclick="alert(a+ <?= b ?>)"></div>';
2253   assert.equal(minify(input, { minifyJS: true }), output);
2254
2255   input = '<div onclick="alert(a + \'<?= b ?>\')"></div>';
2256   output = '<div onclick=\'alert(a+"<?= b ?>")\'></div>';
2257   assert.equal(minify(input, { minifyJS: true }), output);
2258 });
2259
2260 QUnit.test('escaping closing script tag', function(assert) {
2261   var input = '<script>window.jQuery || document.write(\'<script src="jquery.js"><\\/script>\')</script>';
2262   var output = '<script>window.jQuery||document.write(\'<script src="jquery.js"><\\/script>\')</script>';
2263   assert.equal(minify(input, { minifyJS: true }), output);
2264 });
2265
2266 QUnit.test('style minification', function(assert) {
2267   var input, output;
2268
2269   input = '<style></style>div#foo { background-color: red; color: white }';
2270   assert.equal(minify(input, { minifyCSS: true }), input);
2271
2272   input = '<style>div#foo { background-color: red; color: white }</style>';
2273   output = '<style>div#foo{background-color:red;color:#fff}</style>';
2274   assert.equal(minify(input), input);
2275   assert.equal(minify(input, { minifyCSS: true }), output);
2276
2277   input = '<style>div > p.foo + span { border: 10px solid black }</style>';
2278   output = '<style>div>p.foo+span{border:10px solid #000}</style>';
2279   assert.equal(minify(input, { minifyCSS: true }), output);
2280
2281   input = '<div style="background: url(images/<% image %>);"></div>';
2282   assert.equal(minify(input), input);
2283   output = '<div style="background:url(images/<% image %>)"></div>';
2284   assert.equal(minify(input, { minifyCSS: true }), output);
2285   assert.equal(minify(input, {
2286     collapseWhitespace: true,
2287     minifyCSS: true
2288   }), output);
2289
2290   input = '<div style="background: url(\'images/<% image %>\')"></div>';
2291   assert.equal(minify(input), input);
2292   output = '<div style="background:url(images/<% image %>)"></div>';
2293   assert.equal(minify(input, { minifyCSS: true }), output);
2294   assert.equal(minify(input, {
2295     collapseWhitespace: true,
2296     minifyCSS: true
2297   }), output);
2298
2299   input = '<style>\np {\n  background: url(images/<% image %>);\n}\n</style>';
2300   assert.equal(minify(input), input);
2301   output = '<style>p{background:url(images/<% image %>)}</style>';
2302   assert.equal(minify(input, { minifyCSS: true }), output);
2303   assert.equal(minify(input, {
2304     collapseWhitespace: true,
2305     minifyCSS: true
2306   }), output);
2307
2308   input = '<style>p { background: url("images/<% image %>") }</style>';
2309   assert.equal(minify(input), input);
2310   output = '<style>p{background:url(images/<% image %>)}</style>';
2311   assert.equal(minify(input, { minifyCSS: true }), output);
2312   assert.equal(minify(input, {
2313     collapseWhitespace: true,
2314     minifyCSS: true
2315   }), output);
2316
2317   input = '<link rel="stylesheet" href="css/style-mobile.css" media="(max-width: 737px)">';
2318   assert.equal(minify(input), input);
2319   output = '<link rel="stylesheet" href="css/style-mobile.css" media="(max-width:737px)">';
2320   assert.equal(minify(input, { minifyCSS: true }), output);
2321   output = '<link rel=stylesheet href=css/style-mobile.css media=(max-width:737px)>';
2322   assert.equal(minify(input, {
2323     minifyCSS: true,
2324     removeAttributeQuotes: true
2325   }), output);
2326
2327   input = '<style media="(max-width: 737px)"></style>';
2328   assert.equal(minify(input), input);
2329   output = '<style media="(max-width:737px)"></style>';
2330   assert.equal(minify(input, { minifyCSS: true }), output);
2331   output = '<style media=(max-width:737px)></style>';
2332   assert.equal(minify(input, {
2333     minifyCSS: true,
2334     removeAttributeQuotes: true
2335   }), output);
2336 });
2337
2338 QUnit.test('style attribute minification', function(assert) {
2339   var input = '<div style="color: red; background-color: yellow; font-family: Verdana, Arial, sans-serif;"></div>';
2340   var output = '<div style="color:red;background-color:#ff0;font-family:Verdana,Arial,sans-serif"></div>';
2341   assert.equal(minify(input, { minifyCSS: true }), output);
2342 });
2343
2344 QUnit.test('url attribute minification', function(assert) {
2345   var input, output;
2346
2347   input = '<link rel="stylesheet" href="http://website.com/style.css"><form action="http://website.com/folder/folder2/index.html"><a href="http://website.com/folder/file.html">link</a></form>';
2348   output = '<link rel="stylesheet" href="/style.css"><form action="folder2/"><a href="file.html">link</a></form>';
2349   assert.equal(minify(input, { minifyURLs: 'http://website.com/folder/' }), output);
2350   assert.equal(minify(input, { minifyURLs: { site: 'http://website.com/folder/' } }), output);
2351
2352   input = '<link rel="canonical" href="http://website.com/">';
2353   assert.equal(minify(input, { minifyURLs: 'http://website.com/' }), input);
2354   assert.equal(minify(input, { minifyURLs: { site: 'http://website.com/' } }), input);
2355
2356   input = '<style>body { background: url(\'http://website.com/bg.png\') }</style>';
2357   assert.equal(minify(input, { minifyURLs: 'http://website.com/' }), input);
2358   assert.equal(minify(input, { minifyURLs: { site: 'http://website.com/' } }), input);
2359   output = '<style>body{background:url(http://website.com/bg.png)}</style>';
2360   assert.equal(minify(input, { minifyCSS: true }), output);
2361   output = '<style>body{background:url(bg.png)}</style>';
2362   assert.equal(minify(input, {
2363     minifyCSS: true,
2364     minifyURLs: 'http://website.com/'
2365   }), output);
2366   assert.equal(minify(input, {
2367     minifyCSS: true,
2368     minifyURLs: { site: 'http://website.com/' }
2369   }), output);
2370
2371   input = '<style>body { background: url("http://website.com/foo bar/bg.png") }</style>';
2372   assert.equal(minify(input, { minifyURLs: { site: 'http://website.com/foo bar/' } }), input);
2373   output = '<style>body{background:url("http://website.com/foo bar/bg.png")}</style>';
2374   assert.equal(minify(input, { minifyCSS: true }), output);
2375   output = '<style>body{background:url(bg.png)}</style>';
2376   assert.equal(minify(input, {
2377     minifyCSS: true,
2378     minifyURLs: { site: 'http://website.com/foo bar/' }
2379   }), output);
2380
2381   input = '<style>body { background: url("http://website.com/foo bar/(baz)/bg.png") }</style>';
2382   assert.equal(minify(input, { minifyURLs: { site: 'http://website.com/' } }), input);
2383   assert.equal(minify(input, { minifyURLs: { site: 'http://website.com/foo%20bar/' } }), input);
2384   assert.equal(minify(input, { minifyURLs: { site: 'http://website.com/foo%20bar/(baz)/' } }), input);
2385   output = '<style>body{background:url("foo%20bar/(baz)/bg.png")}</style>';
2386   assert.equal(minify(input, {
2387     minifyCSS: true,
2388     minifyURLs: { site: 'http://website.com/' }
2389   }), output);
2390   output = '<style>body{background:url("(baz)/bg.png")}</style>';
2391   assert.equal(minify(input, {
2392     minifyCSS: true,
2393     minifyURLs: { site: 'http://website.com/foo%20bar/' }
2394   }), output);
2395   output = '<style>body{background:url(bg.png)}</style>';
2396   assert.equal(minify(input, {
2397     minifyCSS: true,
2398     minifyURLs: { site: 'http://website.com/foo%20bar/(baz)/' }
2399   }), output);
2400
2401   input = '<img src="http://cdn.site.com/foo.png">';
2402   output = '<img src="//cdn.site.com/foo.png">';
2403   assert.equal(minify(input, { minifyURLs: { site: 'http://site.com/' } }), output);
2404 });
2405
2406 QUnit.test('srcset attribute minification', function(assert) {
2407   var input, output;
2408   input = '<source srcset="http://site.com/foo.gif ,http://site.com/bar.jpg 1x, baz moo 42w,' +
2409           '\n\n\n\n\n\t    http://site.com/zo om.png 1.00x">';
2410   output = '<source srcset="http://site.com/foo.gif, http://site.com/bar.jpg, baz moo 42w, http://site.com/zo om.png">';
2411   assert.equal(minify(input), output);
2412   output = '<source srcset="foo.gif, bar.jpg, baz%20moo 42w, zo%20om.png">';
2413   assert.equal(minify(input, { minifyURLs: { site: 'http://site.com/' } }), output);
2414 });
2415
2416 QUnit.test('valueless attributes', function(assert) {
2417   var input = '<br foo>';
2418   assert.equal(minify(input), input);
2419 });
2420
2421 QUnit.test('newlines becoming whitespaces', function(assert) {
2422   var input = 'test\n\n<input>\n\ntest';
2423   var output = 'test <input> test';
2424   assert.equal(minify(input, { collapseWhitespace: true }), output);
2425 });
2426
2427 QUnit.test('conservative collapse', function(assert) {
2428   var input, output;
2429
2430   input = '<b>   foo \n\n</b>';
2431   output = '<b> foo </b>';
2432   assert.equal(minify(input, {
2433     collapseWhitespace: true,
2434     conservativeCollapse: true
2435   }), output);
2436
2437   input = '<html>\n\n<!--test-->\n\n</html>';
2438   output = '<html> </html>';
2439   assert.equal(minify(input, {
2440     removeComments: true,
2441     collapseWhitespace: true,
2442     conservativeCollapse: true
2443   }), output);
2444
2445   input = '<p>\u00A0</p>';
2446   assert.equal(minify(input, { collapseWhitespace: true }), input);
2447   assert.equal(minify(input, {
2448     collapseWhitespace: true,
2449     conservativeCollapse: true
2450   }), input);
2451
2452   input = '<p> \u00A0</p>';
2453   output = '<p>\u00A0</p>';
2454   assert.equal(minify(input, { collapseWhitespace: true }), output);
2455   assert.equal(minify(input, {
2456     collapseWhitespace: true,
2457     conservativeCollapse: true
2458   }), output);
2459
2460   input = '<p>\u00A0 </p>';
2461   output = '<p>\u00A0</p>';
2462   assert.equal(minify(input, { collapseWhitespace: true }), output);
2463   assert.equal(minify(input, {
2464     collapseWhitespace: true,
2465     conservativeCollapse: true
2466   }), output);
2467
2468   input = '<p> \u00A0 </p>';
2469   output = '<p>\u00A0</p>';
2470   assert.equal(minify(input, { collapseWhitespace: true }), output);
2471   assert.equal(minify(input, {
2472     collapseWhitespace: true,
2473     conservativeCollapse: true
2474   }), output);
2475
2476   input = '<p>  \u00A0\u00A0  \u00A0  </p>';
2477   output = '<p>\u00A0\u00A0 \u00A0</p>';
2478   assert.equal(minify(input, { collapseWhitespace: true }), output);
2479   assert.equal(minify(input, {
2480     collapseWhitespace: true,
2481     conservativeCollapse: true
2482   }), output);
2483
2484   input = '<p>foo  \u00A0\u00A0  \u00A0  </p>';
2485   output = '<p>foo \u00A0\u00A0 \u00A0</p>';
2486   assert.equal(minify(input, { collapseWhitespace: true }), output);
2487   assert.equal(minify(input, {
2488     collapseWhitespace: true,
2489     conservativeCollapse: true
2490   }), output);
2491
2492   input = '<p>  \u00A0\u00A0  \u00A0  bar</p>';
2493   output = '<p>\u00A0\u00A0 \u00A0 bar</p>';
2494   assert.equal(minify(input, { collapseWhitespace: true }), output);
2495   assert.equal(minify(input, {
2496     collapseWhitespace: true,
2497     conservativeCollapse: true
2498   }), output);
2499
2500   input = '<p>foo  \u00A0\u00A0  \u00A0  bar</p>';
2501   output = '<p>foo \u00A0\u00A0 \u00A0 bar</p>';
2502   assert.equal(minify(input, { collapseWhitespace: true }), output);
2503   assert.equal(minify(input, {
2504     collapseWhitespace: true,
2505     conservativeCollapse: true
2506   }), output);
2507
2508   input = '<p> \u00A0foo\u00A0\t</p>';
2509   output = '<p>\u00A0foo\u00A0</p>';
2510   assert.equal(minify(input, { collapseWhitespace: true }), output);
2511   assert.equal(minify(input, {
2512     collapseWhitespace: true,
2513     conservativeCollapse: true
2514   }), output);
2515
2516
2517   input = '<p> \u00A0\nfoo\u00A0\t</p>';
2518   output = '<p>\u00A0 foo\u00A0</p>';
2519   assert.equal(minify(input, { collapseWhitespace: true }), output);
2520   assert.equal(minify(input, {
2521     collapseWhitespace: true,
2522     conservativeCollapse: true
2523   }), output);
2524
2525
2526   input = '<p> \u00A0foo \u00A0\t</p>';
2527   output = '<p>\u00A0foo \u00A0</p>';
2528   assert.equal(minify(input, { collapseWhitespace: true }), output);
2529   assert.equal(minify(input, {
2530     collapseWhitespace: true,
2531     conservativeCollapse: true
2532   }), output);
2533
2534   input = '<p> \u00A0\nfoo \u00A0\t</p>';
2535   output = '<p>\u00A0 foo \u00A0</p>';
2536   assert.equal(minify(input, { collapseWhitespace: true }), output);
2537   assert.equal(minify(input, {
2538     collapseWhitespace: true,
2539     conservativeCollapse: true
2540   }), output);
2541 });
2542
2543 QUnit.test('collapse preseving a line break', function(assert) {
2544   var input, output;
2545
2546   input = '\n\n\n<!DOCTYPE html>   \n<html lang="en" class="no-js">\n' +
2547           '  <head>\n    <meta charset="utf-8">\n    <meta http-equiv="X-UA-Compatible" content="IE=edge">\n\n\n\n' +
2548           '\t<!-- Copyright Notice -->\n' +
2549           '    <title>Carbon</title>\n\n\t<meta name="title" content="Carbon">\n\t\n\n' +
2550           '\t<meta name="description" content="A front-end framework.">\n' +
2551           '    <meta name="apple-mobile-web-app-capable" content="yes">\n' +
2552           '    <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">\n' +
2553           '    <meta name="viewport" content="width=device-width, initial-scale=1">\n\n' +
2554           '<link href="stylesheets/application.css" rel="stylesheet">\n' +
2555           '    <script src="scripts/application.js"></script>\n' +
2556           '    <link href="images/icn-32x32.png" rel="shortcut icon">\n' +
2557           '    <link href="images/icn-152x152.png" rel="apple-touch-icon">\n  </head>\n  <body><p>\n   test test\n\ttest\n\n</p></body>\n</html>';
2558   output = '\n<!DOCTYPE html>\n<html lang="en" class="no-js">\n' +
2559            '<head>\n<meta charset="utf-8">\n<meta http-equiv="X-UA-Compatible" content="IE=edge">\n' +
2560            '<!-- Copyright Notice -->\n' +
2561            '<title>Carbon</title>\n<meta name="title" content="Carbon">\n' +
2562            '<meta name="description" content="A front-end framework.">\n' +
2563            '<meta name="apple-mobile-web-app-capable" content="yes">\n' +
2564            '<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">\n' +
2565            '<meta name="viewport" content="width=device-width,initial-scale=1">\n' +
2566            '<link href="stylesheets/application.css" rel="stylesheet">\n' +
2567            '<script src="scripts/application.js"></script>\n' +
2568            '<link href="images/icn-32x32.png" rel="shortcut icon">\n' +
2569            '<link href="images/icn-152x152.png" rel="apple-touch-icon">\n</head>\n<body><p>\ntest test test\n</p></body>\n</html>';
2570   assert.equal(minify(input, {
2571     collapseWhitespace: true,
2572     preserveLineBreaks: true
2573   }), output);
2574   assert.equal(minify(input, {
2575     collapseWhitespace: true,
2576     conservativeCollapse: true,
2577     preserveLineBreaks: true
2578   }), output);
2579   output = '\n<!DOCTYPE html>\n<html lang="en" class="no-js">\n' +
2580            '<head>\n<meta charset="utf-8">\n<meta http-equiv="X-UA-Compatible" content="IE=edge">\n' +
2581            '<title>Carbon</title>\n<meta name="title" content="Carbon">\n' +
2582            '<meta name="description" content="A front-end framework.">\n' +
2583            '<meta name="apple-mobile-web-app-capable" content="yes">\n' +
2584            '<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">\n' +
2585            '<meta name="viewport" content="width=device-width,initial-scale=1">\n' +
2586            '<link href="stylesheets/application.css" rel="stylesheet">\n' +
2587            '<script src="scripts/application.js"></script>\n' +
2588            '<link href="images/icn-32x32.png" rel="shortcut icon">\n' +
2589            '<link href="images/icn-152x152.png" rel="apple-touch-icon">\n</head>\n<body><p>\ntest test test\n</p></body>\n</html>';
2590   assert.equal(minify(input, {
2591     collapseWhitespace: true,
2592     conservativeCollapse: true,
2593     preserveLineBreaks: true,
2594     removeComments: true
2595   }), output);
2596
2597   input = '<div> text <span>\n text</span> \n</div>';
2598   output = '<div>text <span>\ntext</span>\n</div>';
2599   assert.equal(minify(input, {
2600     collapseWhitespace: true,
2601     preserveLineBreaks: true
2602   }), output);
2603
2604   input = '<div>  text \n </div>';
2605   output = '<div>text\n</div>';
2606   assert.equal(minify(input, {
2607     collapseWhitespace: true,
2608     preserveLineBreaks: true
2609   }), output);
2610   output = '<div> text\n</div>';
2611   assert.equal(minify(input, {
2612     collapseWhitespace: true,
2613     conservativeCollapse: true,
2614     preserveLineBreaks: true
2615   }), output);
2616
2617   input = '<div>\ntext  </div>';
2618   output = '<div>\ntext</div>';
2619   assert.equal(minify(input, {
2620     collapseWhitespace: true,
2621     preserveLineBreaks: true
2622   }), output);
2623   output = '<div>\ntext </div>';
2624   assert.equal(minify(input, {
2625     collapseWhitespace: true,
2626     conservativeCollapse: true,
2627     preserveLineBreaks: true
2628   }), output);
2629
2630   input = 'This is the start. <% ... %>\r\n<%= ... %>\r\n<? ... ?>\r\n<!-- This is the middle, and a comment. -->\r\nNo comment, but middle.\r\n<?= ... ?>\r\n<?php ... ?>\r\n<?xml ... ?>\r\nHello, this is the end!';
2631   output = 'This is the start. <% ... %>\n<%= ... %>\n<? ... ?>\nNo comment, but middle.\n<?= ... ?>\n<?php ... ?>\n<?xml ... ?>\nHello, this is the end!';
2632   assert.equal(minify(input, {
2633     removeComments: true,
2634     collapseWhitespace: true,
2635     preserveLineBreaks: true
2636   }), output);
2637 });
2638
2639 QUnit.test('collapse inline tag whitespace', function(assert) {
2640   var input, output;
2641
2642   input = '<button>a</button> <button>b</button>';
2643   assert.equal(minify(input, {
2644     collapseWhitespace: true
2645   }), input);
2646
2647   output = '<button>a</button><button>b</button>';
2648   assert.equal(minify(input, {
2649     collapseWhitespace: true,
2650     collapseInlineTagWhitespace: true
2651   }), output);
2652
2653   input = '<p>where <math> <mi>R</mi> </math> is the Rici tensor.</p>';
2654   output = '<p>where <math><mi>R</mi></math> is the Rici tensor.</p>';
2655   assert.equal(minify(input, {
2656     collapseWhitespace: true
2657   }), output);
2658
2659   output = '<p>where<math><mi>R</mi></math>is the Rici tensor.</p>';
2660   assert.equal(minify(input, {
2661     collapseWhitespace: true,
2662     collapseInlineTagWhitespace: true
2663   }), output);
2664 });
2665
2666 QUnit.test('ignore custom comments', function(assert) {
2667   var input, output;
2668
2669   input = '<!--! test -->';
2670   assert.equal(minify(input), input);
2671   assert.equal(minify(input, { removeComments: true }), input);
2672   assert.equal(minify(input, { ignoreCustomComments: false }), input);
2673   assert.equal(minify(input, {
2674     removeComments: true,
2675     ignoreCustomComments: []
2676   }), '');
2677   assert.equal(minify(input, {
2678     removeComments: true,
2679     ignoreCustomComments: false
2680   }), '');
2681
2682   input = '<!-- htmlmin:ignore -->test<!-- htmlmin:ignore -->';
2683   output = 'test';
2684   assert.equal(minify(input), output);
2685   assert.equal(minify(input, { removeComments: true }), output);
2686   assert.equal(minify(input, { ignoreCustomComments: false }), output);
2687   assert.equal(minify(input, {
2688     removeComments: true,
2689     ignoreCustomComments: []
2690   }), output);
2691   assert.equal(minify(input, {
2692     removeComments: true,
2693     ignoreCustomComments: false
2694   }), output);
2695
2696   input = '<!-- ko if: someExpressionGoesHere --><li>test</li><!-- /ko -->';
2697   assert.equal(minify(input, {
2698     removeComments: true,
2699     // ignore knockout comments
2700     ignoreCustomComments: [
2701       /^\s+ko/,
2702       /\/ko\s+$/
2703     ]
2704   }), input);
2705
2706   input = '<!--#include virtual="/cgi-bin/counter.pl" -->';
2707   assert.equal(minify(input, {
2708     removeComments: true,
2709     // ignore Apache SSI includes
2710     ignoreCustomComments: [
2711       /^\s*#/
2712     ]
2713   }), input);
2714 });
2715
2716 QUnit.test('processScripts', function(assert) {
2717   var input = '<script type="text/ng-template"><!--test--><div>   <span> foobar </span> \n\n</div></script>';
2718   var output = '<script type="text/ng-template"><div><span>foobar</span></div></script>';
2719   assert.equal(minify(input, {
2720     collapseWhitespace: true,
2721     removeComments: true,
2722     processScripts: ['text/ng-template']
2723   }), output);
2724 });
2725
2726 QUnit.test('ignore', function(assert) {
2727   var input, output;
2728
2729   input = '<!-- htmlmin:ignore --><div class="blah" style="color: red">\n   test   <span> <input disabled/>  foo </span>\n\n   </div><!-- htmlmin:ignore -->' +
2730           '<div class="blah" style="color: red">\n   test   <span> <input disabled/>  foo </span>\n\n   </div>';
2731   output = '<div class="blah" style="color: red">\n   test   <span> <input disabled/>  foo </span>\n\n   </div>' +
2732            '<div class="blah" style="color: red">test <span><input disabled="disabled"> foo</span></div>';
2733   assert.equal(minify(input, { collapseWhitespace: true }), output);
2734
2735   input = '<!-- htmlmin:ignore --><!-- htmlmin:ignore -->';
2736   assert.equal(minify(input), '');
2737
2738   input = '<p>.....</p><!-- htmlmin:ignore -->' +
2739           '@for( $i = 0 ; $i < $criterions->count() ; $i++ )' +
2740               '<h1>{{ $criterions[$i]->value }}</h1>' +
2741           '@endfor' +
2742           '<!-- htmlmin:ignore --><p>....</p>';
2743   output = '<p>.....</p>' +
2744            '@for( $i = 0 ; $i < $criterions->count() ; $i++ )' +
2745                '<h1>{{ $criterions[$i]->value }}</h1>' +
2746            '@endfor' +
2747            '<p>....</p>';
2748   assert.equal(minify(input, { removeComments: true }), output);
2749
2750   input = '<!-- htmlmin:ignore --> <p class="logged"|cond="$is_logged === true" id="foo"> bar</p> <!-- htmlmin:ignore -->';
2751   output = ' <p class="logged"|cond="$is_logged === true" id="foo"> bar</p> ';
2752   assert.equal(minify(input), output);
2753
2754   input = '<!-- htmlmin:ignore --><body <?php body_class(); ?>><!-- htmlmin:ignore -->';
2755   output = '<body <?php body_class(); ?>>';
2756   assert.equal(minify(input, { ignoreCustomFragments: [/<\?php[\s\S]*?\?>/] }), output);
2757
2758   input = 'a\n<!-- htmlmin:ignore -->b<!-- htmlmin:ignore -->';
2759   output = 'a b';
2760   assert.equal(minify(input, { collapseWhitespace: true }), output);
2761
2762   input = '<p>foo <!-- htmlmin:ignore --><span>\n\tbar\n</span><!-- htmlmin:ignore -->.</p>';
2763   output = '<p>foo <span>\n\tbar\n</span>.</p>';
2764   assert.equal(minify(input, { collapseWhitespace: true }), output);
2765 });
2766
2767 QUnit.test('meta viewport', function(assert) {
2768   var input, output;
2769
2770   input = '<meta name="viewport" content="width=device-width, initial-scale=1.0">';
2771   output = '<meta name="viewport" content="width=device-width,initial-scale=1">';
2772   assert.equal(minify(input), output);
2773
2774   input = '<meta name="viewport" content="initial-scale=1, maximum-scale=1.0">';
2775   output = '<meta name="viewport" content="initial-scale=1,maximum-scale=1">';
2776   assert.equal(minify(input), output);
2777
2778   input = '<meta name="viewport" content="width= 500 ,  initial-scale=1">';
2779   output = '<meta name="viewport" content="width=500,initial-scale=1">';
2780   assert.equal(minify(input), output);
2781
2782   input = '<meta name="viewport" content="width=device-width, initial-scale=1.0001, maximum-scale=3.140000">';
2783   output = '<meta name="viewport" content="width=device-width,initial-scale=1.0001,maximum-scale=3.14">';
2784   assert.equal(minify(input), output);
2785 });
2786
2787 QUnit.test('downlevel-revealed conditional comments', function(assert) {
2788   var input = '<![if !IE]><link href="non-ie.css" rel="stylesheet"><![endif]>';
2789   assert.equal(minify(input), input);
2790   assert.equal(minify(input, { removeComments: true }), input);
2791 });
2792
2793 QUnit.test('noscript', function(assert) {
2794   var input;
2795
2796   input = '<SCRIPT SRC="x"></SCRIPT><NOSCRIPT>x</NOSCRIPT>';
2797   assert.equal(minify(input), '<script src="x"></script><noscript>x</noscript>');
2798
2799   input = '<noscript>\n<!-- anchor linking to external file -->\n' +
2800           '<a href="#" onclick="javascript:">External Link</a>\n</noscript>';
2801   assert.equal(minify(input, { removeComments: true, collapseWhitespace: true, removeEmptyAttributes: true }),
2802     '<noscript><a href="#">External Link</a></noscript>');
2803 });
2804
2805 QUnit.test('max line length', function(assert) {
2806   var input;
2807   var options = { maxLineLength: 25 };
2808
2809   input = '123456789012345678901234567890';
2810   assert.equal(minify(input, options), input);
2811
2812   input = '<div data-attr="foo"></div>';
2813   assert.equal(minify(input, options), '<div data-attr="foo">\n</div>');
2814
2815   input = '<code>    hello   world  \n    world   hello  </code>';
2816   assert.equal(minify(input, options), '<code>\n    hello   world  \n    world   hello  \n</code>');
2817
2818   assert.equal(minify('<p title="</p>">x</p>'), '<p title="</p>">x</p>');
2819   assert.equal(minify('<p title=" <!-- hello world --> ">x</p>'), '<p title=" <!-- hello world --> ">x</p>');
2820   assert.equal(minify('<p title=" <![CDATA[ \n\n foobar baz ]]> ">x</p>'), '<p title=" <![CDATA[ \n\n foobar baz ]]> ">x</p>');
2821   assert.equal(minify('<p foo-bar=baz>xxx</p>'), '<p foo-bar="baz">xxx</p>');
2822   assert.equal(minify('<p foo:bar=baz>xxx</p>'), '<p foo:bar="baz">xxx</p>');
2823
2824   input = '<div><div><div><div><div><div><div><div><div><div>' +
2825             'i\'m 10 levels deep' +
2826           '</div></div></div></div></div></div></div></div></div></div>';
2827   assert.equal(minify(input), input);
2828
2829   assert.equal(minify('<script>alert(\'<!--\')</script>', options), '<script>alert(\'<!--\')\n</script>');
2830   input = '<script>\nalert(\'<!-- foo -->\')\n</script>';
2831   assert.equal(minify('<script>alert(\'<!-- foo -->\')</script>', options), input);
2832   assert.equal(minify(input, options), input);
2833   assert.equal(minify('<script>alert(\'-->\')</script>', options), '<script>alert(\'-->\')\n</script>');
2834
2835   assert.equal(minify('<a title="x"href=" ">foo</a>', options), '<a title="x" href="">foo\n</a>');
2836   assert.equal(minify('<p id=""class=""title="">x', options), '<p id="" class="" \ntitle="">x</p>');
2837   assert.equal(minify('<p x="x\'"">x</p>', options), '<p x="x\'">x</p>', 'trailing quote should be ignored');
2838   assert.equal(minify('<a href="#"><p>Click me</p></a>', options), '<a href="#"><p>Click me\n</p></a>');
2839   input = '<span><button>Hit me\n</button></span>';
2840   assert.equal(minify('<span><button>Hit me</button></span>', options), input);
2841   assert.equal(minify(input, options), input);
2842   assert.equal(minify('<object type="image/svg+xml" data="image.svg"><div>[fallback image]</div></object>', options),
2843     '<object \ntype="image/svg+xml" \ndata="image.svg"><div>\n[fallback image]</div>\n</object>'
2844   );
2845
2846   assert.equal(minify('<ng-include src="x"></ng-include>', options), '<ng-include src="x">\n</ng-include>');
2847   assert.equal(minify('<ng:include src="x"></ng:include>', options), '<ng:include src="x">\n</ng:include>');
2848   assert.equal(minify('<ng-include src="\'views/partial-notification.html\'"></ng-include><div ng-view=""></div>', options),
2849     '<ng-include \nsrc="\'views/partial-notification.html\'">\n</ng-include><div \nng-view=""></div>'
2850   );
2851   assert.equal(minify('<some-tag-1></some-tag-1><some-tag-2></some-tag-2>', options),
2852     '<some-tag-1>\n</some-tag-1>\n<some-tag-2>\n</some-tag-2>'
2853   );
2854   assert.equal(minify('[\']["]', options), '[\']["]');
2855   assert.equal(minify('<a href="test.html"><div>hey</div></a>', options), '<a href="test.html">\n<div>hey</div></a>');
2856   assert.equal(minify(':) <a href="http://example.com">link</a>', options), ':) <a \nhref="http://example.com">\nlink</a>');
2857   assert.equal(minify(':) <a href="http://example.com">\nlink</a>', options), ':) <a \nhref="http://example.com">\nlink</a>');
2858   assert.equal(minify(':) <a href="http://example.com">\n\nlink</a>', options), ':) <a \nhref="http://example.com">\n\nlink</a>');
2859
2860   assert.equal(minify('<a href>ok</a>', options), '<a href>ok</a>');
2861 });
2862
2863 QUnit.test('custom attribute collapse', function(assert) {
2864   var input, output;
2865
2866   input = '<div data-bind="\n' +
2867             'css: {\n' +
2868               'fadeIn: selected(),\n' +
2869               'fadeOut: !selected()\n' +
2870             '},\n' +
2871             'visible: function () {\n' +
2872               'return pageWeAreOn() == \'home\';\n' +
2873             '}\n' +
2874           '">foo</div>';
2875   output = '<div data-bind="css: {fadeIn: selected(),fadeOut: !selected()},visible: function () {return pageWeAreOn() == \'home\';}">foo</div>';
2876
2877   assert.equal(minify(input), input);
2878   assert.equal(minify(input, { customAttrCollapse: /data-bind/ }), output);
2879
2880   input = '<div style="' +
2881             'color: red;' +
2882             'font-size: 100em;' +
2883           '">bar</div>';
2884   output = '<div style="color: red;font-size: 100em;">bar</div>';
2885   assert.equal(minify(input, { customAttrCollapse: /style/ }), output);
2886
2887   input = '<div ' +
2888     'class="fragment square" ' +
2889     'ng-hide="square1.hide" ' +
2890     'ng-class="{ \n\n' +
2891       '\'bounceInDown\': !square1.hide, ' +
2892       '\'bounceOutDown\': square1.hide ' +
2893     '}" ' +
2894   '> ' +
2895   '</div>';
2896   output = '<div class="fragment square" ng-hide="square1.hide" ng-class="{\'bounceInDown\': !square1.hide, \'bounceOutDown\': square1.hide }"> </div>';
2897   assert.equal(minify(input, { customAttrCollapse: /ng-class/ }), output);
2898 });
2899
2900 QUnit.test('custom attribute collapse with empty attribute value', function(assert) {
2901   var input = '<div ng-some\n\n></div>';
2902   var output = '<div ng-some></div>';
2903   assert.equal(minify(input, { customAttrCollapse: /.+/ }), output);
2904 });
2905
2906 QUnit.test('custom attribute collapse with newlines, whitespace, and carriage returns', function(assert) {
2907   var input = '<div ng-class="{ \n\r' +
2908           '               value:true, \n\r' +
2909           '               value2:false \n\r' +
2910           '               }"></div>';
2911   var output = '<div ng-class="{value:true,value2:false}"></div>';
2912   assert.equal(minify(input, { customAttrCollapse: /ng-class/ }), output);
2913 });
2914
2915 QUnit.test('do not escape attribute value', function(assert) {
2916   var input, output;
2917
2918   input = '<div data=\'{\n' +
2919           '\t"element": "<div class=\\"test\\"></div>\n"' +
2920           '}\'></div>';
2921   assert.equal(minify(input), input);
2922   assert.equal(minify(input, { preventAttributesEscaping: true }), input);
2923
2924   input = '<div foo bar=\'\' baz="" moo=1 loo=\'2\' haa="3"></div>';
2925   assert.equal(minify(input, { preventAttributesEscaping: true }), input);
2926   output = '<div foo bar="" baz="" moo="1" loo="2" haa="3"></div>';
2927   assert.equal(minify(input), output);
2928 });
2929
2930 QUnit.test('quoteCharacter is single quote', function(assert) {
2931   assert.equal(minify('<div class=\'bar\'>foo</div>', { quoteCharacter: '\'' }), '<div class=\'bar\'>foo</div>');
2932   assert.equal(minify('<div class="bar">foo</div>', { quoteCharacter: '\'' }), '<div class=\'bar\'>foo</div>');
2933 });
2934
2935 QUnit.test('quoteCharacter is not single quote or double quote', function(assert) {
2936   assert.equal(minify('<div class=\'bar\'>foo</div>', { quoteCharacter: 'm' }), '<div class="bar">foo</div>');
2937   assert.equal(minify('<div class="bar">foo</div>', { quoteCharacter: 'm' }), '<div class="bar">foo</div>');
2938 });
2939
2940 QUnit.test('remove space between attributes', function(assert) {
2941   var input, output;
2942   var options = {
2943     collapseBooleanAttributes: true,
2944     keepClosingSlash: true,
2945     removeAttributeQuotes: true,
2946     removeTagWhitespace: true
2947   };
2948
2949   input = '<input data-attr="example" value="hello world!" checked="checked">';
2950   output = '<input data-attr=example value="hello world!"checked>';
2951   assert.equal(minify(input, options), output);
2952
2953   input = '<input checked="checked" value="hello world!" data-attr="example">';
2954   output = '<input checked value="hello world!"data-attr=example>';
2955   assert.equal(minify(input, options), output);
2956
2957   input = '<input checked="checked" data-attr="example" value="hello world!">';
2958   output = '<input checked data-attr=example value="hello world!">';
2959   assert.equal(minify(input, options), output);
2960
2961   input = '<input data-attr="example" value="hello world!" checked="checked"/>';
2962   output = '<input data-attr=example value="hello world!"checked/>';
2963   assert.equal(minify(input, options), output);
2964
2965   input = '<input checked="checked" value="hello world!" data-attr="example"/>';
2966   output = '<input checked value="hello world!"data-attr=example />';
2967   assert.equal(minify(input, options), output);
2968
2969   input = '<input checked="checked" data-attr="example" value="hello world!"/>';
2970   output = '<input checked data-attr=example value="hello world!"/>';
2971   assert.equal(minify(input, options), output);
2972 });
2973
2974 QUnit.test('markups from Angular 2', function(assert) {
2975   var input, output;
2976   input = '<template ngFor #hero [ngForOf]="heroes">\n' +
2977           '  <hero-detail *ngIf="hero" [hero]="hero"></hero-detail>\n' +
2978           '</template>\n' +
2979           '<form (ngSubmit)="onSubmit(theForm)" #theForm="ngForm">\n' +
2980           '  <div class="form-group">\n' +
2981           '    <label for="name">Name</label>\n' +
2982           '    <input class="form-control" required ngControl="firstName"\n' +
2983           '      [(ngModel)]="currentHero.firstName">\n' +
2984           '  </div>\n' +
2985           '  <button type="submit" [disabled]="!theForm.form.valid">Submit</button>\n' +
2986           '</form>';
2987   output = '<template ngFor #hero [ngForOf]="heroes">\n' +
2988            '  <hero-detail *ngIf="hero" [hero]="hero"></hero-detail>\n' +
2989            '</template>\n' +
2990            '<form (ngSubmit)="onSubmit(theForm)" #theForm="ngForm">\n' +
2991            '  <div class="form-group">\n' +
2992            '    <label for="name">Name</label>\n' +
2993            '    <input class="form-control" required ngControl="firstName" [(ngModel)]="currentHero.firstName">\n' +
2994            '  </div>\n' +
2995            '  <button type="submit" [disabled]="!theForm.form.valid">Submit</button>\n' +
2996            '</form>';
2997   assert.equal(minify(input, { caseSensitive: true }), output);
2998   output = '<template ngFor #hero [ngForOf]=heroes>' +
2999            '<hero-detail *ngIf=hero [hero]=hero></hero-detail>' +
3000            '</template>' +
3001            '<form (ngSubmit)=onSubmit(theForm) #theForm=ngForm>' +
3002            '<div class=form-group>' +
3003            '<label for=name>Name</label>' +
3004            ' <input class=form-control required ngControl=firstName [(ngModel)]=currentHero.firstName>' +
3005            '</div>' +
3006            '<button type=submit [disabled]=!theForm.form.valid>Submit</button>' +
3007            '</form>';
3008   assert.equal(minify(input, {
3009     caseSensitive: true,
3010     collapseBooleanAttributes: true,
3011     collapseWhitespace: true,
3012     removeAttributeQuotes: true,
3013     removeComments: true,
3014     removeEmptyAttributes: true,
3015     removeOptionalTags: true,
3016     removeRedundantAttributes: true,
3017     removeScriptTypeAttributes: true,
3018     removeStyleLinkTypeAttributes: true,
3019     removeTagWhitespace: true,
3020     useShortDoctype: true
3021   }), output);
3022 });
3023
3024 QUnit.test('auto-generated tags', function(assert) {
3025   var input, output;
3026
3027   input = '</p>';
3028   assert.equal(minify(input, { includeAutoGeneratedTags: false }), input);
3029
3030   input = '<p id=""class=""title="">x';
3031   output = '<p id="" class="" title="">x';
3032   assert.equal(minify(input, { includeAutoGeneratedTags: false }), output);
3033   output = '<p id="" class="" title="">x</p>';
3034   assert.equal(minify(input), output);
3035   assert.equal(minify(input, { includeAutoGeneratedTags: true }), output);
3036
3037   input = '<body onload="  foo();   bar() ;  "><p>x</body>';
3038   output = '<body onload="foo();   bar() ;"><p>x</body>';
3039   assert.equal(minify(input, { includeAutoGeneratedTags: false }), output);
3040
3041   input = '<a href="#"><div>Well, look at me! I\'m a div!</div></a>';
3042   output = '<a href="#"><div>Well, look at me! I\'m a div!</div>';
3043   assert.equal(minify(input, { html5: false, includeAutoGeneratedTags: false }), output);
3044   assert.equal(minify('<p id=""class=""title="">x', {
3045     maxLineLength: 25,
3046     includeAutoGeneratedTags: false
3047   }), '<p id="" class="" \ntitle="">x');
3048
3049   input = '<p>foo';
3050   assert.equal(minify(input, { includeAutoGeneratedTags: false }), input);
3051   assert.equal(minify(input, {
3052     includeAutoGeneratedTags: false,
3053     removeOptionalTags: true
3054   }), input);
3055
3056   input = '</p>';
3057   assert.equal(minify(input, { includeAutoGeneratedTags: false }), input);
3058   output = '';
3059   assert.equal(minify(input, {
3060     includeAutoGeneratedTags: false,
3061     removeOptionalTags: true
3062   }), output);
3063
3064   input = '<select><option>foo<option>bar</select>';
3065   assert.equal(minify(input, { includeAutoGeneratedTags: false }), input);
3066   output = '<select><option>foo</option><option>bar</option></select>';
3067   assert.equal(minify(input, { includeAutoGeneratedTags: true }), output);
3068
3069   input = '<datalist><option label="A" value="1"><option label="B" value="2"></datalist>';
3070   assert.equal(minify(input, { includeAutoGeneratedTags: false }), input);
3071   output = '<datalist><option label="A" value="1"></option><option label="B" value="2"></option></datalist>';
3072   assert.equal(minify(input, { includeAutoGeneratedTags: true }), output);
3073 });
3074
3075 QUnit.test('sort attributes', function(assert) {
3076   var input, output;
3077
3078   input = '<link href="foo">' +
3079           '<link rel="bar" href="baz">' +
3080           '<link type="text/css" href="app.css" rel="stylesheet" async>';
3081   assert.equal(minify(input), input);
3082   assert.equal(minify(input, { sortAttributes: false }), input);
3083   output = '<link href="foo">' +
3084            '<link href="baz" rel="bar">' +
3085            '<link href="app.css" rel="stylesheet" async type="text/css">';
3086   assert.equal(minify(input, { sortAttributes: true }), output);
3087
3088   input = '<link href="foo">' +
3089           '<link rel="bar" href="baz">' +
3090           '<script type="text/html"><link type="text/css" href="app.css" rel="stylesheet" async></script>';
3091   assert.equal(minify(input), input);
3092   assert.equal(minify(input, { sortAttributes: false }), input);
3093   output = '<link href="foo">' +
3094            '<link href="baz" rel="bar">' +
3095            '<script type="text/html"><link type="text/css" href="app.css" rel="stylesheet" async></script>';
3096   assert.equal(minify(input, { sortAttributes: true }), output);
3097   output = '<link href="foo">' +
3098            '<link href="baz" rel="bar">' +
3099            '<script type="text/html"><link href="app.css" rel="stylesheet" async type="text/css"></script>';
3100   assert.equal(minify(input, {
3101     processScripts: [
3102       'text/html'
3103     ],
3104     sortAttributes: true
3105   }), output);
3106
3107   input = '<link type="text/css" href="foo.css">' +
3108           '<link rel="stylesheet" type="text/abc" href="bar.css">' +
3109           '<link href="baz.css">';
3110   output = '<link href="foo.css" type="text/css">' +
3111            '<link href="bar.css" type="text/abc" rel="stylesheet">' +
3112            '<link href="baz.css">';
3113   assert.equal(minify(input, { sortAttributes: true }), output);
3114   output = '<link href="foo.css">' +
3115            '<link href="bar.css" rel="stylesheet" type="text/abc">' +
3116            '<link href="baz.css">';
3117   assert.equal(minify(input, {
3118     removeStyleLinkTypeAttributes: true,
3119     sortAttributes: true
3120   }), output);
3121
3122   input = '<a foo moo></a>' +
3123           '<a bar foo></a>' +
3124           '<a baz bar foo></a>' +
3125           '<a baz foo moo></a>' +
3126           '<a moo baz></a>';
3127   assert.equal(minify(input), input);
3128   assert.equal(minify(input, { sortAttributes: false }), input);
3129   output = '<a foo moo></a>' +
3130            '<a foo bar></a>' +
3131            '<a foo bar baz></a>' +
3132            '<a foo baz moo></a>' +
3133            '<a baz moo></a>';
3134   assert.equal(minify(input, { sortAttributes: true }), output);
3135
3136   input = '<span nav_sv_fo_v_column <#=(j === 0) ? \'nav_sv_fo_v_first\' : \'\' #> foo_bar></span>';
3137   assert.equal(minify(input, {
3138     ignoreCustomFragments: [/<#[\s\S]*?#>/]
3139   }), input);
3140   assert.equal(minify(input, {
3141     ignoreCustomFragments: [/<#[\s\S]*?#>/],
3142     sortAttributes: false
3143   }), input);
3144   output = '<span foo_bar nav_sv_fo_v_column <#=(j === 0) ? \'nav_sv_fo_v_first\' : \'\' #> ></span>';
3145   assert.equal(minify(input, {
3146     ignoreCustomFragments: [/<#[\s\S]*?#>/],
3147     sortAttributes: true
3148   }), output);
3149
3150   input = '<a 0 1 2 3 4 5 6 7 8 9 a b c d e f g h i j k l m n o p q r s t u v w x y z></a>';
3151   assert.equal(minify(input, { sortAttributes: true }), input);
3152 });
3153
3154 QUnit.test('sort style classes', function(assert) {
3155   var input, output;
3156
3157   input = '<a class="foo moo"></a>' +
3158           '<b class="bar foo"></b>' +
3159           '<i class="baz bar foo"></i>' +
3160           '<s class="baz foo moo"></s>' +
3161           '<u class="moo baz"></u>';
3162   assert.equal(minify(input), input);
3163   assert.equal(minify(input, { sortClassName: false }), input);
3164   output = '<a class="foo moo"></a>' +
3165            '<b class="foo bar"></b>' +
3166            '<i class="foo bar baz"></i>' +
3167            '<s class="foo baz moo"></s>' +
3168            '<u class="baz moo"></u>';
3169   assert.equal(minify(input, { sortClassName: true }), output);
3170
3171   input = '<a class="moo <!-- htmlmin:ignore -->bar<!-- htmlmin:ignore --> foo baz"></a>';
3172   output = '<a class="moo bar foo baz"></a>';
3173   assert.equal(minify(input), output);
3174   assert.equal(minify(input, { sortClassName: false }), output);
3175   output = '<a class="baz foo moo bar"></a>';
3176   assert.equal(minify(input, { sortClassName: true }), output);
3177
3178   input = '<div class="nav_sv_fo_v_column <#=(j === 0) ? \'nav_sv_fo_v_first\' : \'\' #> foo_bar"></div>';
3179   assert.equal(minify(input, {
3180     ignoreCustomFragments: [/<#[\s\S]*?#>/]
3181   }), input);
3182   assert.equal(minify(input, {
3183     ignoreCustomFragments: [/<#[\s\S]*?#>/],
3184     sortClassName: false
3185   }), input);
3186   assert.equal(minify(input, {
3187     ignoreCustomFragments: [/<#[\s\S]*?#>/],
3188     sortClassName: true
3189   }), input);
3190
3191   input = '<a class="0 1 2 3 4 5 6 7 8 9 a b c d e f g h i j k l m n o p q r s t u v w x y z"></a>';
3192   assert.equal(minify(input, { sortClassName: false }), input);
3193   assert.equal(minify(input, { sortClassName: true }), input);
3194
3195   input = '<a class="add sort keys createSorter"></a>';
3196   assert.equal(minify(input, { sortClassName: false }), input);
3197   output = '<a class="add createSorter keys sort"></a>';
3198   assert.equal(minify(input, { sortClassName: true }), output);
3199
3200   input = '<span class="sprite sprite-{{sprite}}"></span>';
3201   assert.equal(minify(input, {
3202     collapseWhitespace: true,
3203     ignoreCustomFragments: [/{{.*?}}/],
3204     removeAttributeQuotes: true,
3205     sortClassName: true
3206   }), input);
3207
3208   input = '<span class="{{sprite}}-sprite sprite"></span>';
3209   assert.equal(minify(input, {
3210     collapseWhitespace: true,
3211     ignoreCustomFragments: [/{{.*?}}/],
3212     removeAttributeQuotes: true,
3213     sortClassName: true
3214   }), input);
3215
3216   input = '<span class="sprite-{{sprite}}-sprite"></span>';
3217   assert.equal(minify(input, {
3218     collapseWhitespace: true,
3219     ignoreCustomFragments: [/{{.*?}}/],
3220     removeAttributeQuotes: true,
3221     sortClassName: true
3222   }), input);
3223
3224   input = '<span class="{{sprite}}"></span>';
3225   assert.equal(minify(input, {
3226     collapseWhitespace: true,
3227     ignoreCustomFragments: [/{{.*?}}/],
3228     removeAttributeQuotes: true,
3229     sortClassName: true
3230   }), input);
3231
3232   input = '<span class={{sprite}}></span>';
3233   output = '<span class="{{sprite}}"></span>';
3234   assert.equal(minify(input, {
3235     collapseWhitespace: true,
3236     ignoreCustomFragments: [/{{.*?}}/],
3237     removeAttributeQuotes: true,
3238     sortClassName: true
3239   }), output);
3240
3241   input = '<div class></div>';
3242   assert.equal(minify(input, { sortClassName: false }), input);
3243   assert.equal(minify(input, { sortClassName: true }), input);
3244 });
3245
3246 QUnit.test('decode entity characters', function(assert) {
3247   var input, output;
3248
3249   input = '<!-- &ne; -->';
3250   assert.equal(minify(input), input);
3251   assert.equal(minify(input, { decodeEntities: false }), input);
3252   assert.equal(minify(input, { decodeEntities: true }), input);
3253
3254   input = '<script type="text/html">&colon;</script>';
3255   assert.equal(minify(input), input);
3256   assert.equal(minify(input, { decodeEntities: false }), input);
3257   assert.equal(minify(input, { decodeEntities: true }), input);
3258   output = '<script type="text/html">:</script>';
3259   assert.equal(minify(input, { decodeEntities: true, processScripts: ['text/html'] }), output);
3260
3261   input = '<div style="font: &quot;monospace&#34;">foo&dollar;</div>';
3262   assert.equal(minify(input), input);
3263   assert.equal(minify(input, { decodeEntities: false }), input);
3264   output = '<div style=\'font: "monospace"\'>foo$</div>';
3265   assert.equal(minify(input, { decodeEntities: true }), output);
3266   output = '<div style="font:&quot">foo&dollar;</div>';
3267   assert.equal(minify(input, { minifyCSS: true }), output);
3268   assert.equal(minify(input, { decodeEntities: false, minifyCSS: true }), output);
3269   output = '<div style="font:monospace">foo$</div>';
3270   assert.equal(minify(input, { decodeEntities: true, minifyCSS: true }), output);
3271
3272   input = '<a href="/?foo=1&amp;bar=&lt;2&gt;">baz&lt;moo&gt;&copy;</a>';
3273   assert.equal(minify(input), input);
3274   assert.equal(minify(input, { decodeEntities: false }), input);
3275   output = '<a href="/?foo=1&bar=<2>">baz&lt;moo>\u00a9</a>';
3276   assert.equal(minify(input, { decodeEntities: true }), output);
3277
3278   input = '<? &amp; ?>&amp;<pre><? &amp; ?>&amp;</pre>';
3279   assert.equal(minify(input), input);
3280   assert.equal(minify(input, { collapseWhitespace: false, decodeEntities: false }), input);
3281   assert.equal(minify(input, { collapseWhitespace: true, decodeEntities: false }), input);
3282   output = '<? &amp; ?>&<pre><? &amp; ?>&</pre>';
3283   assert.equal(minify(input, { collapseWhitespace: false, decodeEntities: true }), output);
3284   assert.equal(minify(input, { collapseWhitespace: true, decodeEntities: true }), output);
3285 });
3286
3287 QUnit.test('tests from PHPTAL', function(assert) {
3288   [
3289     // trailing </p> removed by minifier, but not by PHPTAL
3290     ['<p>foo bar baz', '<p>foo     \t bar\n\n\n baz</p>'],
3291     ['<p>foo bar<pre>  \tfoo\t   \nbar   </pre>', '<p>foo   \t\n bar</p><pre>  \tfoo\t   \nbar   </pre>'],
3292     ['<p>foo <a href="">bar </a>baz', '<p>foo <a href=""> bar </a> baz  </p>'],
3293     ['<p>foo <a href="">bar </a>baz', ' <p>foo <a href=""> bar </a>baz </p>'],
3294     ['<p>foo<a href=""> bar </a>baz', ' <p> foo<a href=""> bar </a>baz </p>  '],
3295     ['<p>foo <a href="">bar</a> baz', ' <p> foo <a href="">bar</a> baz</p>'],
3296     ['<p>foo<br>', '<p>foo <br/></p>'],
3297     // PHPTAL remove whitespace after 'foo' - problematic if <span> is used as icon font
3298     ['<p>foo <span></span>', '<p>foo <span></span></p>'],
3299     ['<p>foo <span></span>', '<p>foo <span></span> </p>'],
3300     // comments removed by minifier, but not by PHPTAL
3301     ['<p>foo', '<p>foo <!-- --> </p>'],
3302     ['<div>a<div>b</div>c<div>d</div>e</div>', '<div>a <div>b</div> c <div> d </div> e </div>'],
3303     // unary slashes removed by minifier, but not by PHPTAL
3304     ['<div><img></div>', '<div> <img/> </div>'],
3305     ['<div>x <img></div>', '<div> x <img/> </div>'],
3306     ['<div>x <img> y</div>', '<div> x <img/> y </div>'],
3307     ['<div><img> y</div>', '<div><img/> y </div>'],
3308     ['<div><button>Z</button></div>', '<div> <button>Z</button> </div>'],
3309     ['<div>x <button>Z</button></div>', '<div> x <button>Z</button> </div>'],
3310     ['<div>x <button>Z</button> y</div>', '<div> x <button>Z</button> y </div>'],
3311     ['<div><button>Z</button> y</div>', '<div><button>Z</button> y </div>'],
3312     ['<div><button>Z</button></div>', '<div> <button> Z </button> </div>'],
3313     ['<div>x <button>Z</button></div>', '<div> x <button> Z </button> </div>'],
3314     ['<div>x <button>Z</button> y</div>', '<div> x <button> Z </button> y </div>'],
3315     ['<div><button>Z</button> y</div>', '<div><button> Z </button> y </div>'],
3316     ['<script>//foo\nbar()</script>', '<script>//foo\nbar()</script>'],
3317     // optional tags removed by minifier, but not by PHPTAL
3318     // parser cannot handle <script/>
3319     [
3320       '<title></title><link><script>" ";</script><script></script><meta><style></style>',
3321       '<html >\n' +
3322       '<head > <title > </title > <link /> <script >" ";</script> <script>\n</script>\n' +
3323       ' <meta /> <style\n' +
3324       '  > </style >\n' +
3325       '   </head > </html>'
3326     ],
3327     ['<div><p>test 123<p>456<ul><li>x</ul></div>', '<div> <p> test 123 </p> <p> 456 </p> <ul> <li>x</li> </ul> </div>'],
3328     ['<div><p>test 123<pre> 456 </pre><p>x</div>', '<div> <p> test 123 </p> <pre> 456 </pre> <p> x </p> </div>'],
3329     /* minifier does not assume <li> as "display: inline"
3330     ['<div><ul><li><a>a </a></li><li>b </li><li>c</li></ul></div>', '<div> <ul> <li> <a> a </a> </li> <li> b </li> <li> c </li> </ul> </div>'],*/
3331     ['<table>x<tr>x<td>foo</td>x</tr>x</table>', '<table> x <tr> x <td> foo </td> x </tr> x </table>'],
3332     ['<select>x<option></option>x<optgroup>x<option></option>x</optgroup>x</select>', '<select> x <option> </option> x <optgroup> x <option> </option> x </optgroup> x </select> '],
3333     // closing slash and optional attribute quotes removed by minifier, but not by PHPTAL
3334     // attribute ordering differences between minifier and PHPTAL
3335     ['<img alt=x height=5 src=foo width=10>', '<img width="10" height="5" src="foo" alt="x" />'],
3336     ['<img alpha=1 beta=2 gamma=3>', '<img gamma="3" alpha="1" beta="2" />'],
3337     ['<pre>\n\n\ntest</pre>', '<pre>\n\n\ntest</pre>'],
3338     /* single line-break preceding <pre> is redundant, assuming <pre> is block element
3339     ['<pre>test</pre>', '<pre>\ntest</pre>'],*/
3340     // closing slash and optional attribute quotes removed by minifier, but not by PHPTAL
3341     // attribute ordering differences between minifier and PHPTAL
3342     // redundant inter-attribute spacing removed by minifier, but not by PHPTAL
3343     ['<meta content="text/plain;charset=UTF-8"http-equiv=Content-Type>', '<meta http-equiv=\'Content-Type\' content=\'text/plain;charset=UTF-8\'/>'],
3344     /* minifier does not optimise <meta/> in HTML5 mode
3345     ['<meta charset=utf-8>', '<meta http-equiv=\'Content-Type\' content=\'text/plain;charset=UTF-8\'/>'],*/
3346     /* minifier does not optimise <script/> in HTML5 mode
3347     [
3348       '<script></script><style></style>',
3349       '<script type=\'text/javascript ;charset=utf-8\'\n' +
3350       'language=\'javascript\'></script><style type=\'text/css\'></style>'
3351     ],*/
3352     // minifier removes more javascript type attributes than PHPTAL
3353     ['<script></script><script type=text/hack></script>', '<script type="text/javascript;e4x=1"></script><script type="text/hack"></script>']
3354     /* trim "title" attribute value in <a>
3355     [
3356       '<title>Foo</title><p><a title="x"href=test>x </a>xu</p><br>foo',
3357       '<html> <head> <title> Foo </title> </head>\n' +
3358       '<body>\n' +
3359       '<p>\n' +
3360       '<a title="   x " href=" test "> x </a> xu\n' +
3361       '</p>\n' +
3362       '<br/>\n' +
3363       'foo</body> </html>  <!-- bla -->'
3364     ]*/
3365   ].forEach(function(tokens) {
3366     assert.equal(minify(tokens[1], {
3367       collapseBooleanAttributes: true,
3368       collapseWhitespace: true,
3369       removeAttributeQuotes: true,
3370       removeComments: true,
3371       removeEmptyAttributes: true,
3372       removeOptionalTags: true,
3373       removeRedundantAttributes: true,
3374       removeScriptTypeAttributes: true,
3375       removeStyleLinkTypeAttributes: true,
3376       removeTagWhitespace: true,
3377       sortAttributes: true,
3378       useShortDoctype: true
3379     }), tokens[0]);
3380   });
3381 });
3382
3383 QUnit.test('canCollapseWhitespace and canTrimWhitespace hooks', function(assert) {
3384   function canCollapseAndTrimWhitespace(tagName, attrs, defaultFn) {
3385     if ((attrs || []).some(function(attr) { return attr.name === 'class' && attr.value === 'leaveAlone'; })) {
3386       return false;
3387     }
3388     return defaultFn(tagName, attrs);
3389   }
3390
3391   var input = '<div class="leaveAlone"><span> </span> foo  bar</div>';
3392   var output = '<div class="leaveAlone"><span> </span> foo  bar</div>';
3393
3394   assert.equal(minify(input, {
3395     collapseWhitespace: true,
3396     canTrimWhitespace: canCollapseAndTrimWhitespace,
3397     canCollapseWhitespace: canCollapseAndTrimWhitespace
3398   }), output);
3399
3400   // Regression test: Previously the first </div> would clear the internal
3401   // stackNo{Collapse,Trim}Whitespace, so that ' foo  bar' turned into ' foo bar'
3402   input = '<div class="leaveAlone"><div></div><span> </span> foo  bar</div>';
3403   output = '<div class="leaveAlone"><div></div><span> </span> foo  bar</div>';
3404
3405   assert.equal(minify(input, {
3406     collapseWhitespace: true,
3407     canTrimWhitespace: canCollapseAndTrimWhitespace,
3408     canCollapseWhitespace: canCollapseAndTrimWhitespace
3409   }), output);
3410
3411   // Make sure that the stack does get reset when leaving the element for which
3412   // the hooks returned false:
3413   input = '<div class="leaveAlone"></div><div> foo  bar </div>';
3414   output = '<div class="leaveAlone"></div><div>foo bar</div>';
3415
3416   assert.equal(minify(input, {
3417     collapseWhitespace: true,
3418     canTrimWhitespace: canCollapseAndTrimWhitespace,
3419     canCollapseWhitespace: canCollapseAndTrimWhitespace
3420   }), output);
3421 });