X | Y ',
209 | ' X | Y ',
210 | ),
211 | 'th_self_close': (
212 | ' X | Y ',
213 | ' X | Y ',
214 | ),
215 | 'a_p_interaction': ( # the 'pre' functionality continues after the
216 | ' X Y',
217 | ' X Y',
218 | ),
219 | }
220 |
221 | SELF_OPENING_TEXTS = {
222 | 'html_closed_no_open': (
223 | ' X ',
224 | ' X '
225 | ),
226 | 'head_closed_no_open': (
227 | ' X ',
228 | ' X ' # TODO: we could theoretically kill that leading
229 | # space. See HTMLMinParse.handle_endtag
230 | ),
231 | 'body_closed_no_open': (
232 | ' X ',
233 | ' X '
234 | ),
235 | 'colgroup_self_open': (
236 | ' ',
237 | ' ',
238 | ),
239 | 'tbody_self_open': (
240 | ' ',
241 | ' ',
242 | ),
243 | 'p_closed_no_open': ( # this isn't valid html, but its worth accounting for
244 | ' X Y ',
245 | ' X Y ',
246 | ),
247 | }
248 |
249 | class HTMLMinTestMeta(type):
250 | def __new__(cls, name, bases, dct):
251 | def make_test(text):
252 | def inner_test(self):
253 | self.assertEqual(self.minify(text[0]), text[1])
254 | return inner_test
255 |
256 | for k, v in dct.get('__reference_texts__',{}).items():
257 | if 'test_'+k not in dct:
258 | dct['test_'+k] = make_test(v)
259 | return type.__new__(cls, str(name), bases, dct)
260 |
261 | class HTMLMinTestCase(
262 | HTMLMinTestMeta('HTMLMinTestCase', (unittest.TestCase, ), {})):
263 | def setUp(self):
264 | self.minify = htmlmin.minify
265 |
266 | class TestMinifyFunction(HTMLMinTestCase):
267 | __reference_texts__ = MINIFY_FUNCTION_TEXTS
268 |
269 | def test_basic_minification_quality(self):
270 | import codecs
271 | with codecs.open('htmlmin/tests/large_test.html', encoding='utf-8') as inpf:
272 | inp = inpf.read()
273 | out = self.minify(inp)
274 | self.assertEqual(len(inp) - len(out), 8806)
275 |
276 | def test_high_minification_quality(self):
277 | import codecs
278 | with codecs.open('htmlmin/tests/large_test.html', encoding='utf-8') as inpf:
279 | inp = inpf.read()
280 | out = self.minify(inp, remove_all_empty_space=True, remove_comments=True)
281 | self.assertEqual(len(inp) - len(out), 12043)
282 |
283 | class TestMinifierObject(HTMLMinTestCase):
284 | __reference_texts__ = MINIFY_FUNCTION_TEXTS
285 |
286 | def setUp(self):
287 | HTMLMinTestCase.setUp(self)
288 | self.minifier = htmlmin.Minifier()
289 | self.minify = self.minifier.minify
290 |
291 | def test_reuse(self):
292 | text = self.__reference_texts__['simple_text']
293 | self.assertEqual(self.minify(text[0]), text[1])
294 | self.assertEqual(self.minify(text[0]), text[1])
295 |
296 | def test_buffered_input(self):
297 | text = self.__reference_texts__['long_text']
298 | self.minifier.input(text[0][:len(text[0]) // 2])
299 | self.minifier.input(text[0][len(text[0]) // 2:])
300 | self.assertEqual(self.minifier.finalize(), text[1])
301 |
302 | class TestMinifyFeatures(HTMLMinTestCase):
303 | __reference_texts__ = FEATURES_TEXTS
304 |
305 | def test_remove_comments(self):
306 | text = self.__reference_texts__['remove_comments']
307 | self.assertEqual(htmlmin.minify(text[0], remove_comments=True), text[1])
308 |
309 | def test_reduce_boolean_attributes(self):
310 | text = self.__reference_texts__['reduce_boolean_attributes']
311 | self.assertEqual(htmlmin.minify(text[0], reduce_boolean_attributes=True), text[1])
312 |
313 | def test_keep_comments(self):
314 | text = self.__reference_texts__['keep_comments']
315 | self.assertEqual(htmlmin.minify(text[0], remove_comments=True), text[1])
316 |
317 | def test_keep_pre_attribute(self):
318 | text = self.__reference_texts__['keep_pre_attribute']
319 | self.assertEqual(htmlmin.minify(text[0], keep_pre=True), text[1])
320 |
321 | def test_custom_pre_attribute(self):
322 | text = self.__reference_texts__['custom_pre_attribute']
323 | self.assertEqual(htmlmin.minify(text[0], pre_attr='custom'), text[1])
324 |
325 | def test_keep_empty(self):
326 | text = self.__reference_texts__['keep_empty']
327 | self.assertEqual(htmlmin.minify(text[0]), text[1])
328 |
329 | def test_remove_empty(self):
330 | text = self.__reference_texts__['remove_empty']
331 | self.assertEqual(htmlmin.minify(text[0], remove_empty_space=True), text[1])
332 |
333 | def test_remove_all_empty(self):
334 | text = self.__reference_texts__['remove_all_empty']
335 | self.assertEqual(htmlmin.minify(text[0], remove_all_empty_space=True),
336 | text[1])
337 |
338 | def test_dont_minify_div(self):
339 | text = self.__reference_texts__['dont_minify_div']
340 | self.assertEqual(htmlmin.minify(text[0], pre_tags=('div',)), text[1])
341 |
342 | def test_minify_pre(self):
343 | text = self.__reference_texts__['minify_pre']
344 | self.assertEqual(htmlmin.minify(text[0], pre_tags=('div',)), text[1])
345 |
346 | def test_remove_head_spaces(self):
347 | text = self.__reference_texts__['remove_head_spaces']
348 | self.assertEqual(htmlmin.minify(text[0]), text[1])
349 |
350 | def test_dont_minify_scripts_or_styles(self):
351 | text = self.__reference_texts__['dont_minify_scripts_or_styles']
352 | self.assertEqual(htmlmin.minify(text[0], pre_tags=[]), text[1])
353 |
354 | class TestSelfClosingTags(HTMLMinTestCase):
355 | __reference_texts__ = SELF_CLOSE_TEXTS
356 |
357 | class TestSelfOpeningTags(HTMLMinTestCase):
358 | __reference_texts__ = SELF_OPENING_TEXTS
359 |
360 | class TestDecorator(HTMLMinTestCase):
361 | def test_direct_decorator(self):
362 | @htmlmindecorator
363 | def directly_decorated():
364 | return ' X Y '
365 |
366 | self.assertEqual(' X Y ', directly_decorated())
367 |
368 | def test_options_decorator(self):
369 | @htmlmindecorator(remove_comments=True)
370 | def directly_decorated():
371 | return ' X Y '
372 |
373 | self.assertEqual(' X Y ', directly_decorated())
374 |
375 | class TestMiddleware(HTMLMinTestCase):
376 | def setUp(self):
377 | HTMLMinTestCase.setUp(self)
378 | def wsgi_app(environ, start_response):
379 | start_response(environ['status'], environ['headers'])
380 | yield environ['content']
381 |
382 | self.wsgi_app = wsgi_app
383 |
384 | def call_app(self, app, status, headers, content):
385 | response_status = [] # these need to be mutable so that they can be changed
386 | response_headers = [] # within our inner function.
387 | def start_response(status, headers, exc_info=None):
388 | response_status.append(status)
389 | response_headers.append(headers)
390 | response_body = ''.join(app({'status': status,
391 | 'content': content,
392 | 'headers': headers},
393 | start_response))
394 | return response_status[0], response_headers[0], response_body
395 |
396 | def test_middlware(self):
397 | app = HTMLMinMiddleware(self.wsgi_app)
398 | status, headers, body = self.call_app(
399 | app, '200 OK', (('Content-Type', 'text/html'),),
400 | ' X Y ')
401 | self.assertEqual(body, ' X Y ')
402 |
403 | def test_middlware_minifier_options(self):
404 | app = HTMLMinMiddleware(self.wsgi_app, remove_comments=True)
405 | status, headers, body = self.call_app(
406 | app, '200 OK', (('Content-Type', 'text/html'),),
407 | ' X Y ')
408 | self.assertEqual(body, ' X Y ')
409 |
410 | def test_middlware_off_by_default(self):
411 | app = HTMLMinMiddleware(self.wsgi_app, by_default=False)
412 | status, headers, body = self.call_app(
413 | app, '200 OK', (('Content-Type', 'text/html'),),
414 | ' X Y ')
415 | self.assertEqual(body, ' X Y ')
416 |
417 | def test_middlware_on_by_header(self):
418 | app = HTMLMinMiddleware(self.wsgi_app, by_default=False)
419 | status, headers, body = self.call_app(
420 | app, '200 OK', (
421 | ('Content-Type', 'text/html'),
422 | ('X-HTML-Min-Enable', 'True'),
423 | ),
424 | ' X Y ')
425 | self.assertEqual(body, ' X Y ')
426 |
427 | def test_middlware_off_by_header(self):
428 | app = HTMLMinMiddleware(self.wsgi_app)
429 | status, headers, body = self.call_app(
430 | app, '200 OK', (
431 | ('Content-Type', 'text/html'),
432 | ('X-HTML-Min-Enable', 'False'),
433 | ),
434 | ' X Y ')
435 | self.assertEqual(body, ' X Y ')
436 |
437 | def test_middlware_remove_header(self):
438 | app = HTMLMinMiddleware(self.wsgi_app)
439 | status, headers, body = self.call_app(
440 | app, '200 OK', (
441 | ('Content-Type', 'text/html'),
442 | ('X-HTML-Min-Enable', 'False'),
443 | ),
444 | ' X Y ')
445 | self.assertFalse(any((h == 'X-HTML-Min-Enable' for h, v in headers)))
446 |
447 | def test_middlware_keep_header(self):
448 | app = HTMLMinMiddleware(self.wsgi_app, keep_header=True)
449 | status, headers, body = self.call_app(
450 | app, '200 OK', [
451 | ('Content-Type', 'text/html'),
452 | ('X-HTML-Min-Enable', 'False'),
453 | ],
454 | ' X Y ')
455 | self.assertTrue(any((h == 'X-HTML-Min-Enable' for h, v in headers)))
456 |
457 | def suite():
458 | minify_function_suite = unittest.TestLoader().\
459 | loadTestsFromTestCase(TestMinifyFunction)
460 | minifier_object_suite = unittest.TestLoader().\
461 | loadTestsFromTestCase(TestMinifierObject)
462 | minify_features_suite = unittest.TestLoader().\
463 | loadTestsFromTestCase(TestMinifyFeatures)
464 | self_closing_tags_suite = unittest.TestLoader().\
465 | loadTestsFromTestCase(TestSelfClosingTags)
466 | self_opening_tags_suite = unittest.TestLoader().\
467 | loadTestsFromTestCase(TestSelfOpeningTags)
468 | decorator_suite = unittest.TestLoader().\
469 | loadTestsFromTestCase(TestDecorator)
470 | middleware_suite = unittest.TestLoader().\
471 | loadTestsFromTestCase(TestMiddleware)
472 | return unittest.TestSuite([
473 | minify_function_suite,
474 | minifier_object_suite,
475 | minify_features_suite,
476 | self_closing_tags_suite,
477 | self_opening_tags_suite,
478 | decorator_suite,
479 | middleware_suite,
480 | ])
481 |
482 | if __name__ == '__main__':
483 | unittest.main()
484 |
--------------------------------------------------------------------------------
/modules/jsmin/__init__.py:
--------------------------------------------------------------------------------
1 | # This code is original from jsmin by Douglas Crockford, it was translated to
2 | # Python by Baruch Even. It was rewritten by Dave St.Germain for speed.
3 | #
4 | # The MIT License (MIT)
5 | #
6 | # Copyright (c) 2013 Dave St.Germain
7 | #
8 | # Permission is hereby granted, free of charge, to any person obtaining a copy
9 | # of this software and associated documentation files (the "Software"), to deal
10 | # in the Software without restriction, including without limitation the rights
11 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | # copies of the Software, and to permit persons to whom the Software is
13 | # furnished to do so, subject to the following conditions:
14 | #
15 | # The above copyright notice and this permission notice shall be included in
16 | # all copies or substantial portions of the Software.
17 | #
18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 | # THE SOFTWARE.
25 |
26 |
27 | import sys
28 | is_3 = sys.version_info >= (3, 0)
29 | if is_3:
30 | import io
31 | else:
32 | import StringIO
33 | try:
34 | import cStringIO
35 | except ImportError:
36 | cStringIO = None
37 |
38 |
39 | __all__ = ['jsmin', 'JavascriptMinify']
40 | __version__ = '2.0.11'
41 |
42 |
43 | def jsmin(js):
44 | """
45 | returns a minified version of the javascript string
46 | """
47 | if not is_3:
48 | if cStringIO and not isinstance(js, unicode):
49 | # strings can use cStringIO for a 3x performance
50 | # improvement, but unicode (in python2) cannot
51 | klass = cStringIO.StringIO
52 | else:
53 | klass = StringIO.StringIO
54 | else:
55 | klass = io.StringIO
56 | ins = klass(js)
57 | outs = klass()
58 | JavascriptMinify(ins, outs).minify()
59 | return outs.getvalue()
60 |
61 |
62 | class JavascriptMinify(object):
63 | """
64 | Minify an input stream of javascript, writing
65 | to an output stream
66 | """
67 |
68 | def __init__(self, instream=None, outstream=None):
69 | self.ins = instream
70 | self.outs = outstream
71 |
72 | def minify(self, instream=None, outstream=None):
73 | if instream and outstream:
74 | self.ins, self.outs = instream, outstream
75 |
76 | self.is_return = False
77 | self.return_buf = ''
78 |
79 | def write(char):
80 | # all of this is to support literal regular expressions.
81 | # sigh
82 | if char in 'return':
83 | self.return_buf += char
84 | self.is_return = self.return_buf == 'return'
85 | self.outs.write(char)
86 | if self.is_return:
87 | self.return_buf = ''
88 |
89 | read = self.ins.read
90 |
91 | space_strings = "abcdefghijklmnopqrstuvwxyz"\
92 | "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_$\\"
93 | starters, enders = '{[(+-', '}])+-"\''
94 | newlinestart_strings = starters + space_strings
95 | newlineend_strings = enders + space_strings
96 | do_newline = False
97 | do_space = False
98 | escape_slash_count = 0
99 | doing_single_comment = False
100 | previous_before_comment = ''
101 | doing_multi_comment = False
102 | in_re = False
103 | in_quote = ''
104 | quote_buf = []
105 |
106 | previous = read(1)
107 | if previous == '\\':
108 | escape_slash_count += 1
109 | next1 = read(1)
110 | if previous == '/':
111 | if next1 == '/':
112 | doing_single_comment = True
113 | elif next1 == '*':
114 | doing_multi_comment = True
115 | previous = next1
116 | next1 = read(1)
117 | else:
118 | in_re = True # literal regex at start of script
119 | write(previous)
120 | elif not previous:
121 | return
122 | elif previous >= '!':
123 | if previous in "'\"":
124 | in_quote = previous
125 | write(previous)
126 | previous_non_space = previous
127 | else:
128 | previous_non_space = ' '
129 | if not next1:
130 | return
131 |
132 | while 1:
133 | next2 = read(1)
134 | if not next2:
135 | last = next1.strip()
136 | if not (doing_single_comment or doing_multi_comment)\
137 | and last not in ('', '/'):
138 | if in_quote:
139 | write(''.join(quote_buf))
140 | write(last)
141 | break
142 | if doing_multi_comment:
143 | if next1 == '*' and next2 == '/':
144 | doing_multi_comment = False
145 | if previous_before_comment and previous_before_comment in space_strings:
146 | do_space = True
147 | next2 = read(1)
148 | elif doing_single_comment:
149 | if next1 in '\r\n':
150 | doing_single_comment = False
151 | while next2 in '\r\n':
152 | next2 = read(1)
153 | if not next2:
154 | break
155 | if previous_before_comment in ')}]':
156 | do_newline = True
157 | elif previous_before_comment in space_strings:
158 | write('\n')
159 | elif in_quote:
160 | quote_buf.append(next1)
161 |
162 | if next1 == in_quote:
163 | numslashes = 0
164 | for c in reversed(quote_buf[:-1]):
165 | if c != '\\':
166 | break
167 | else:
168 | numslashes += 1
169 | if numslashes % 2 == 0:
170 | in_quote = ''
171 | write(''.join(quote_buf))
172 | elif next1 in '\r\n':
173 | if previous_non_space in newlineend_strings \
174 | or previous_non_space > '~':
175 | while 1:
176 | if next2 < '!':
177 | next2 = read(1)
178 | if not next2:
179 | break
180 | else:
181 | if next2 in newlinestart_strings \
182 | or next2 > '~' or next2 == '/':
183 | do_newline = True
184 | break
185 | elif next1 < '!' and not in_re:
186 | if (previous_non_space in space_strings \
187 | or previous_non_space > '~') \
188 | and (next2 in space_strings or next2 > '~'):
189 | do_space = True
190 | elif previous_non_space in '-+' and next2 == previous_non_space:
191 | # protect against + ++ or - -- sequences
192 | do_space = True
193 | elif self.is_return and next2 == '/':
194 | # returning a regex...
195 | write(' ')
196 | elif next1 == '/':
197 | if do_space:
198 | write(' ')
199 | if in_re:
200 | if previous != '\\' or (not escape_slash_count % 2) or next2 in 'gimy':
201 | in_re = False
202 | write('/')
203 | elif next2 == '/':
204 | doing_single_comment = True
205 | previous_before_comment = previous_non_space
206 | elif next2 == '*':
207 | doing_multi_comment = True
208 | previous_before_comment = previous_non_space
209 | previous = next1
210 | next1 = next2
211 | next2 = read(1)
212 | else:
213 | in_re = previous_non_space in '(,=:[?!&|;' or self.is_return # literal regular expression
214 | write('/')
215 | else:
216 | if do_space:
217 | do_space = False
218 | write(' ')
219 | if do_newline:
220 | write('\n')
221 | do_newline = False
222 |
223 | write(next1)
224 | if not in_re and next1 in "'\"":
225 | in_quote = next1
226 | quote_buf = []
227 |
228 | previous = next1
229 | next1 = next2
230 |
231 | if previous >= '!':
232 | previous_non_space = previous
233 |
234 | if previous == '\\':
235 | escape_slash_count += 1
236 | else:
237 | escape_slash_count = 0
238 |
--------------------------------------------------------------------------------
/modules/jsmin/__main__.py:
--------------------------------------------------------------------------------
1 | import sys, os, glob
2 | from jsmin import JavascriptMinify
3 |
4 | for f in sys.argv[1:]:
5 | with open(f, 'r') as js:
6 | minifier = JavascriptMinify(js, sys.stdout)
7 | minifier.minify()
8 | sys.stdout.write('\n')
9 |
10 |
11 |
--------------------------------------------------------------------------------
/modules/jsmin/test.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import jsmin
3 | import sys
4 |
5 | class JsTests(unittest.TestCase):
6 | def _minify(self, js):
7 | return jsmin.jsmin(js)
8 |
9 | def assertEqual(self, thing1, thing2):
10 | if thing1 != thing2:
11 | print(repr(thing1), repr(thing2))
12 | raise AssertionError
13 | return True
14 |
15 | def assertMinified(self, js_input, expected):
16 | minified = jsmin.jsmin(js_input)
17 | assert minified == expected, "%r != %r" % (minified, expected)
18 |
19 | def testQuoted(self):
20 | js = r'''
21 | Object.extend(String, {
22 | interpret: function(value) {
23 | return value == null ? '' : String(value);
24 | },
25 | specialChar: {
26 | '\b': '\\b',
27 | '\t': '\\t',
28 | '\n': '\\n',
29 | '\f': '\\f',
30 | '\r': '\\r',
31 | '\\': '\\\\'
32 | }
33 | });
34 |
35 | '''
36 | expected = r"""Object.extend(String,{interpret:function(value){return value==null?'':String(value);},specialChar:{'\b':'\\b','\t':'\\t','\n':'\\n','\f':'\\f','\r':'\\r','\\':'\\\\'}});"""
37 | self.assertMinified(js, expected)
38 |
39 | def testSingleComment(self):
40 | js = r'''// use native browser JS 1.6 implementation if available
41 | if (Object.isFunction(Array.prototype.forEach))
42 | Array.prototype._each = Array.prototype.forEach;
43 |
44 | if (!Array.prototype.indexOf) Array.prototype.indexOf = function(item, i) {
45 |
46 | // hey there
47 | function() {// testing comment
48 | foo;
49 | //something something
50 |
51 | location = 'http://foo.com;'; // goodbye
52 | }
53 | //bye
54 | '''
55 | expected = r"""
56 | if(Object.isFunction(Array.prototype.forEach))
57 | Array.prototype._each=Array.prototype.forEach;if(!Array.prototype.indexOf)Array.prototype.indexOf=function(item,i){ function(){ foo; location='http://foo.com;';}"""
58 | # print expected
59 | self.assertMinified(js, expected)
60 |
61 | def testEmpty(self):
62 | self.assertMinified('', '')
63 | self.assertMinified(' ', '')
64 | self.assertMinified('\n', '')
65 | self.assertMinified('\r\n', '')
66 | self.assertMinified('\t', '')
67 |
68 |
69 | def testMultiComment(self):
70 | js = r"""
71 | function foo() {
72 | print('hey');
73 | }
74 | /*
75 | if(this.options.zindex) {
76 | this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
77 | this.element.style.zIndex = this.options.zindex;
78 | }
79 | */
80 | another thing;
81 | """
82 | expected = r"""function foo(){print('hey');}
83 | another thing;"""
84 | self.assertMinified(js, expected)
85 |
86 | def testLeadingComment(self):
87 | js = r"""/* here is a comment at the top
88 |
89 | it ends here */
90 | function foo() {
91 | alert('crud');
92 | }
93 |
94 | """
95 | expected = r"""function foo(){alert('crud');}"""
96 | self.assertMinified(js, expected)
97 |
98 | def testBlockCommentStartingWithSlash(self):
99 | self.assertMinified('A; /*/ comment */ B', 'A;B')
100 |
101 | def testBlockCommentEndingWithSlash(self):
102 | self.assertMinified('A; /* comment /*/ B', 'A;B')
103 |
104 | def testLeadingBlockCommentStartingWithSlash(self):
105 | self.assertMinified('/*/ comment */ A', 'A')
106 |
107 | def testLeadingBlockCommentEndingWithSlash(self):
108 | self.assertMinified('/* comment /*/ A', 'A')
109 |
110 | def testEmptyBlockComment(self):
111 | self.assertMinified('/**/ A', 'A')
112 |
113 | def testBlockCommentMultipleOpen(self):
114 | self.assertMinified('/* A /* B */ C', 'C')
115 |
116 | def testJustAComment(self):
117 | self.assertMinified(' // a comment', '')
118 |
119 | def test_issue_10(self):
120 | js = '''
121 | files = [{name: value.replace(/^.*\\\\/, '')}];
122 | // comment
123 | A
124 | '''
125 | expected = '''files=[{name:value.replace(/^.*\\\\/,'')}]; A'''
126 | self.assertMinified(js, expected)
127 |
128 | def testRe(self):
129 | js = r'''
130 | var str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, '');
131 | return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str);
132 | });'''
133 | expected = r"""var str=this.replace(/\\./g,'@').replace(/"[^"\\\n\r]*"/g,'');return(/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str);});"""
134 | self.assertMinified(js, expected)
135 |
136 | def testIgnoreComment(self):
137 | js = r"""
138 | var options_for_droppable = {
139 | overlap: options.overlap,
140 | containment: options.containment,
141 | tree: options.tree,
142 | hoverclass: options.hoverclass,
143 | onHover: Sortable.onHover
144 | }
145 |
146 | var options_for_tree = {
147 | onHover: Sortable.onEmptyHover,
148 | overlap: options.overlap,
149 | containment: options.containment,
150 | hoverclass: options.hoverclass
151 | }
152 |
153 | // fix for gecko engine
154 | Element.cleanWhitespace(element);
155 | """
156 | expected = r"""var options_for_droppable={overlap:options.overlap,containment:options.containment,tree:options.tree,hoverclass:options.hoverclass,onHover:Sortable.onHover}
157 | var options_for_tree={onHover:Sortable.onEmptyHover,overlap:options.overlap,containment:options.containment,hoverclass:options.hoverclass}
158 | Element.cleanWhitespace(element);"""
159 | self.assertMinified(js, expected)
160 |
161 | def testHairyRe(self):
162 | js = r"""
163 | inspect: function(useDoubleQuotes) {
164 | var escapedString = this.gsub(/[\x00-\x1f\\]/, function(match) {
165 | var character = String.specialChar[match[0]];
166 | return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16);
167 | });
168 | if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"';
169 | return "'" + escapedString.replace(/'/g, '\\\'') + "'";
170 | },
171 |
172 | toJSON: function() {
173 | return this.inspect(true);
174 | },
175 |
176 | unfilterJSON: function(filter) {
177 | return this.sub(filter || Prototype.JSONFilter, '#{1}');
178 | },
179 | """
180 | expected = r"""inspect:function(useDoubleQuotes){var escapedString=this.gsub(/[\x00-\x1f\\]/,function(match){var character=String.specialChar[match[0]];return character?character:'\\u00'+match[0].charCodeAt().toPaddedString(2,16);});if(useDoubleQuotes)return'"'+escapedString.replace(/"/g,'\\"')+'"';return"'"+escapedString.replace(/'/g,'\\\'')+"'";},toJSON:function(){return this.inspect(true);},unfilterJSON:function(filter){return this.sub(filter||Prototype.JSONFilter,'#{1}');},"""
181 | self.assertMinified(js, expected)
182 |
183 | def testLiteralRe(self):
184 | js = r"""
185 | myString.replace(/\\/g, '/');
186 | console.log("hi");
187 | """
188 | expected = r"""myString.replace(/\\/g,'/');console.log("hi");"""
189 | self.assertMinified(js, expected)
190 |
191 | js = r''' return /^data:image\//i.test(url) ||
192 | /^(https?|ftp|file|about|chrome|resource):/.test(url);
193 | '''
194 | expected = r'''return /^data:image\//i.test(url)||/^(https?|ftp|file|about|chrome|resource):/.test(url);'''
195 | self.assertMinified(js, expected)
196 |
197 | def testNoBracesWithComment(self):
198 | js = r"""
199 | onSuccess: function(transport) {
200 | var js = transport.responseText.strip();
201 | if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check
202 | throw 'Server returned an invalid collection representation.';
203 | this._collection = eval(js);
204 | this.checkForExternalText();
205 | }.bind(this),
206 | onFailure: this.onFailure
207 | });
208 | """
209 | expected = r"""onSuccess:function(transport){var js=transport.responseText.strip();if(!/^\[.*\]$/.test(js))
210 | throw'Server returned an invalid collection representation.';this._collection=eval(js);this.checkForExternalText();}.bind(this),onFailure:this.onFailure});"""
211 | self.assertMinified(js, expected)
212 |
213 | def testSpaceInRe(self):
214 | js = r"""
215 | num = num.replace(/ /g,'');
216 | """
217 | self.assertMinified(js, "num=num.replace(/ /g,'');")
218 |
219 | def testEmptyString(self):
220 | js = r'''
221 | function foo('') {
222 |
223 | }
224 | '''
225 | self.assertMinified(js, "function foo(''){}")
226 |
227 | def testDoubleSpace(self):
228 | js = r'''
229 | var foo = "hey";
230 | '''
231 | self.assertMinified(js, 'var foo="hey";')
232 |
233 | def testLeadingRegex(self):
234 | js = r'/[d]+/g '
235 | self.assertMinified(js, js.strip())
236 |
237 | def testLeadingString(self):
238 | js = r"'a string in the middle of nowhere'; // and a comment"
239 | self.assertMinified(js, "'a string in the middle of nowhere';")
240 |
241 | def testSingleCommentEnd(self):
242 | js = r'// a comment\n'
243 | self.assertMinified(js, '')
244 |
245 | def testInputStream(self):
246 | try:
247 | from StringIO import StringIO
248 | except ImportError:
249 | from io import StringIO
250 |
251 | ins = StringIO(r'''
252 | function foo('') {
253 |
254 | }
255 | ''')
256 | outs = StringIO()
257 | m = jsmin.JavascriptMinify()
258 | m.minify(ins, outs)
259 | output = outs.getvalue()
260 | assert output == "function foo(''){}"
261 |
262 | def testUnicode(self):
263 | instr = u'\u4000 //foo'
264 | expected = u'\u4000'
265 | output = jsmin.jsmin(instr)
266 | self.assertEqual(output, expected)
267 |
268 | def testCommentBeforeEOF(self):
269 | self.assertMinified("//test\r\n", "")
270 |
271 | def testCommentInObj(self):
272 | self.assertMinified("""{
273 | a: 1,//comment
274 | }""", "{a:1,}")
275 |
276 | def testCommentInObj2(self):
277 | self.assertMinified("{a: 1//comment\r\n}", "{a:1\n}")
278 |
279 | def testImplicitSemicolon(self):
280 | # return \n 1 is equivalent with return; 1
281 | # so best make sure jsmin retains the newline
282 | self.assertMinified("return;//comment\r\na", "return;a")
283 |
284 | def testImplicitSemicolon2(self):
285 | self.assertMinified("return//comment...\r\na", "return\na")
286 |
287 | def testSingleComment2(self):
288 | self.assertMinified('x.replace(/\//, "_")// slash to underscore',
289 | 'x.replace(/\//,"_")')
290 |
291 | def testSlashesNearComments(self):
292 | original = '''
293 | { a: n / 2, }
294 | // comment
295 | '''
296 | expected = '''{a:n/2,}'''
297 | self.assertMinified(original, expected)
298 |
299 | def testReturn(self):
300 | original = '''
301 | return foo;//comment
302 | return bar;'''
303 | expected = 'return foo; return bar;'
304 | self.assertMinified(original, expected)
305 |
306 | def test_space_plus(self):
307 | original = '"s" + ++e + "s"'
308 | expected = '"s"+ ++e+"s"'
309 | self.assertMinified(original, expected)
310 |
311 | def test_no_final_newline(self):
312 | original = '"s"'
313 | expected = '"s"'
314 | self.assertMinified(original, expected)
315 |
316 | def test_space_with_regex_repeats(self):
317 | original = '/(NaN| {2}|^$)/.test(a)&&(a="M 0 0");'
318 | self.assertMinified(original, original) # there should be nothing jsmin can do here
319 |
320 | def test_space_with_regex_repeats_not_at_start(self):
321 | original = 'aaa;/(NaN| {2}|^$)/.test(a)&&(a="M 0 0");'
322 | self.assertMinified(original, original) # there should be nothing jsmin can do here
323 |
324 | def test_space_in_regex(self):
325 | original = '/a (a)/.test("a")'
326 | self.assertMinified(original, original)
327 |
328 | def test_angular_1(self):
329 | original = '''var /** holds major version number for IE or NaN for real browsers */
330 | msie,
331 | jqLite, // delay binding since jQuery could be loaded after us.'''
332 | minified = jsmin.jsmin(original)
333 | self.assertTrue('var msie' in minified)
334 |
335 | def test_angular_2(self):
336 | original = 'var/* comment */msie;'
337 | expected = 'var msie;'
338 | self.assertMinified(original, expected)
339 |
340 | def test_angular_3(self):
341 | original = 'var /* comment */msie;'
342 | expected = 'var msie;'
343 | self.assertMinified(original, expected)
344 |
345 | def test_angular_4(self):
346 | original = 'var /* comment */ msie;'
347 | expected = 'var msie;'
348 | self.assertMinified(original, expected)
349 |
350 | def test_angular_4(self):
351 | original = 'a/b'
352 | self.assertMinified(original, original)
353 |
354 | if __name__ == '__main__':
355 | unittest.main()
356 |
--------------------------------------------------------------------------------
| | | |