1###############################################################################
2#
3# Copyright (c) 2011 Ruslan Spivak
4#
5# Permission is hereby granted, free of charge, to any person obtaining a copy
6# of this software and associated documentation files (the "Software"), to deal
7# in the Software without restriction, including without limitation the rights
8# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9# copies of the Software, and to permit persons to whom the Software is
10# furnished to do so, subject to the following conditions:
11#
12# The above copyright notice and this permission notice shall be included in
13# all copies or substantial portions of the Software.
14#
15# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21# THE SOFTWARE.
22#
23###############################################################################
24
25__author__ = 'Ruslan Spivak <ruslan.spivak@gmail.com>'
26
27import unittest
28
29from slimit import minify
30
31
32class MinifierTestCase(unittest.TestCase):
33
34    def assertMinified(self, source, expected):
35        minified = minify(source)
36        self.maxDiff = None
37        self.assertSequenceEqual(minified, expected)
38
39    TEST_CASES = [
40        ("""
41        jQuery.fn = jQuery.prototype = {
42                // For internal use only.
43                _data: function( elem, name, data ) {
44                        return jQuery.data( elem, name, data, true );
45                }
46        };
47        """,
48         'jQuery.fn=jQuery.prototype={_data:function(elem,name,data){return jQuery.data(elem,name,data,true);}};'),
49
50        ('context = context instanceof jQuery ? context[0] : context;',
51         'context=context instanceof jQuery?context[0]:context;'
52         ),
53
54        ("""
55        /*
56        * A number of helper functions used for managing events.
57        * Many of the ideas behind this code originated from
58        * Dean Edwards' addEvent library.
59        */
60        if ( elem && elem.parentNode ) {
61                // Handle the case where IE and Opera return items
62                // by name instead of ID
63                if ( elem.id !== match[2] ) {
64                        return rootjQuery.find( selector );
65                }
66
67                // Otherwise, we inject the element directly into the jQuery object
68                this.length = 1;
69                this[0] = elem;
70        }
71        """,
72
73         'if(elem&&elem.parentNode){if(elem.id!==match[2])return rootjQuery.find(selector);this.length=1;this[0]=elem;}'
74         ),
75
76        ("""
77        var a = function( obj ) {
78                for ( var name in obj ) {
79                        return false;
80                }
81                return true;
82        };
83        """,
84         'var a=function(obj){for(var name in obj)return false;return true;};'
85         ),
86
87        ("""
88        x = "string", y = 5;
89
90        (x = 5) ? true : false;
91
92        for (p in obj)
93        ;
94
95        if (true)
96          val = null;
97        else
98          val = false;
99
100        """,
101         'x="string",y=5;(x=5)?true:false;for(p in obj);if(true)val=null;else val=false;'
102         ),
103
104        # for loops + empty statement in loop body
105        ("""
106        for (x = 0; true; x++)
107        ;
108        for (; true; x++)
109        ;
110        for (x = 0, y = 5; true; x++)
111        ;
112
113        y = (x + 5) * 20;
114
115        """,
116         'for(x=0;true;x++);for(;true;x++);for(x=0,y=5;true;x++);y=(x+5)*20;'),
117
118
119        # unary expressions
120        ("""
121        delete x;
122        typeof x;
123        void x;
124        x += (!y)++;
125        """,
126         'delete x;typeof x;void x;x+=(!y)++;'),
127
128        # label + break label + continue label
129        ("""
130        label:
131        if ( i == 0 )
132          continue label;
133        switch (day) {
134        case 5:
135          break ;
136        default:
137          break label;
138        }
139        """,
140         'label:if(i==0)continue label;switch(day){case 5:break;default:break label;}'),
141
142        # break + continue: no labels
143        ("""
144        while (i <= 7) {
145          if ( i == 3 )
146              continue;
147          if ( i == 0 )
148              break;
149        }
150        """,
151         'while(i<=7){if(i==3)continue;if(i==0)break;}'),
152
153        # regex + one line statements in if and if .. else
154        ("""
155        function a(x, y) {
156         var re = /ab+c/;
157         if (x == 1)
158           return x + y;
159         if (x == 3)
160           return {x: 1};
161         else
162           return;
163        }
164        """,
165         'function a(x,y){var re=/ab+c/;if(x==1)return x+y;if(x==3)return{x:1};else return;}'),
166
167        # new
168        ('return new jQuery.fn.init( selector, context, rootjQuery );',
169         'return new jQuery.fn.init(selector,context,rootjQuery);'
170         ),
171
172        # no space after 'else' when the next token is (, {
173        ("""
174        if (true) {
175          x = true;
176          y = 3;
177        } else {
178          x = false
179          y = 5
180        }
181        """,
182         'if(true){x=true;y=3;}else{x=false;y=5;}'),
183
184        ("""
185        if (true) {
186          x = true;
187          y = 3;
188        } else
189          (x + ' qw').split(' ');
190        """,
191         "if(true){x=true;y=3;}else(x+' qw').split(' ');"),
192
193
194        ##############################################################
195        # Block braces removal
196        ##############################################################
197
198        # do while
199        ('do { x += 1; } while(true);', 'do x+=1;while(true);'),
200        # do while: multiple statements
201        ('do { x += 1; y += 1;} while(true);', 'do{x+=1;y+=1;}while(true);'),
202
203        # elision
204        ('var a = [1, 2, 3, ,,,5];', 'var a=[1,2,3,,,,5];'),
205
206        # with
207        ("""
208        with (obj) {
209          a = b;
210        }
211        """,
212         'with(obj)a=b;'),
213
214        # with: multiple statements
215        ("""
216        with (obj) {
217          a = b;
218          c = d;
219        }
220        """,
221         'with(obj){a=b;c=d;}'),
222
223        # if else
224        ("""
225        if (true) {
226          x = true;
227        } else {
228          x = false
229        }
230        """,
231         'if(true)x=true;else x=false;'),
232
233        # if: multiple statements
234        ("""
235        if (true) {
236          x = true;
237          y = false;
238        } else {
239          x = false;
240          y = true;
241        }
242        """,
243         'if(true){x=true;y=false;}else{x=false;y=true;}'),
244
245        # try catch finally: one statement
246        ("""
247        try {
248          throw "my_exception"; // generates an exception
249        }
250        catch (e) {
251          // statements to handle any exceptions
252          log(e); // pass exception object to error handler
253        }
254        finally {
255          closefiles(); // always close the resource
256        }
257        """,
258         'try{throw "my_exception";}catch(e){log(e);}finally{closefiles();}'
259         ),
260
261        # try catch finally: no statements
262        ("""
263        try {
264        }
265        catch (e) {
266        }
267        finally {
268        }
269        """,
270         'try{}catch(e){}finally{}'
271         ),
272
273        # try catch finally: multiple statements
274        ("""
275        try {
276          x = 3;
277          y = 5;
278        }
279        catch (e) {
280          log(e);
281          log('e');
282        }
283        finally {
284          z = 7;
285          log('z');
286        }
287        """,
288         "try{x=3;y=5;}catch(e){log(e);log('e');}finally{z=7;log('z');}"
289         ),
290
291        # tricky case with an 'if' nested in 'if .. else'
292        # We need to preserve braces in the first 'if' otherwise
293        # 'else' might get associated with nested 'if' instead
294        ("""
295        if ( obj ) {
296                for ( n in obj ) {
297                        if ( v === false) {
298                                break;
299                        }
300                }
301        } else {
302                for ( ; i < l; ) {
303                        if ( nv === false ) {
304                                break;
305                        }
306                }
307        }
308        """,
309         'if(obj){for(n in obj)if(v===false)break;}else for(;i<l;)if(nv===false)break;'),
310
311        # We don't care about nested 'if' when enclosing 'if' block
312        # contains multiple statements because braces won't be removed
313        # by visit_Block when there are multiple statements in the block
314        ("""
315        if ( obj ) {
316                for ( n in obj ) {
317                        if ( v === false) {
318                                break;
319                        }
320                }
321                x = 5;
322        } else {
323                for ( ; i < l; ) {
324                        if ( nv === false ) {
325                                break;
326                        }
327                }
328        }
329        """,
330         'if(obj){for(n in obj)if(v===false)break;x=5;}else for(;i<l;)if(nv===false)break;'),
331
332
333        # No dangling 'else' - remove braces
334        ("""
335        if ( obj ) {
336                for ( n in obj ) {
337                        if ( v === false) {
338                                break;
339                        } else {
340                                n = 3;
341                        }
342                }
343        } else {
344                for ( ; i < l; ) {
345                        if ( nv === false ) {
346                                break;
347                        }
348                }
349        }
350        """,
351         'if(obj)for(n in obj)if(v===false)break;else n=3;else for(;i<l;)if(nv===false)break;'),
352
353        # foo["bar"] --> foo.bar
354        ('foo["bar"];', 'foo.bar;'),
355        ("foo['bar'];", 'foo.bar;'),
356        ("""foo['bar"']=42;""", """foo['bar"']=42;"""),
357        ("""foo["bar'"]=42;""", """foo["bar'"]=42;"""),
358        ('foo["bar bar"];', 'foo["bar bar"];'),
359        ('foo["bar"+"bar"];', 'foo["bar"+"bar"];'),
360        # https://github.com/rspivak/slimit/issues/34
361        # test some reserved keywords
362        ('foo["for"];', 'foo["for"];'),
363        ('foo["class"];', 'foo["class"];'),
364
365
366        # https://github.com/rspivak/slimit/issues/21
367        # c||(c=393,a=323,b=2321); --> c||c=393,a=323,b=2321; ERROR
368        ('c||(c=393);', 'c||(c=393);'),
369        ('c||(c=393,a=323,b=2321);', 'c||(c=393,a=323,b=2321);'),
370
371        # https://github.com/rspivak/slimit/issues/25
372        ('for(a?b:c;d;)e=1;', 'for(a?b:c;d;)e=1;'),
373
374        # https://github.com/rspivak/slimit/issues/26
375        ('"begin"+ ++a+"end";', '"begin"+ ++a+"end";'),
376
377        # https://github.com/rspivak/slimit/issues/28
378        ("""
379         (function($) {
380             $.hello = 'world';
381         }(jQuery));
382         """,
383         "(function($){$.hello='world';}(jQuery));"),
384
385        # function call in FOR init
386        ('for(o(); i < 3; i++) {}', 'for(o();i<3;i++){}'),
387
388        # unary increment operator in FOR init
389        ('for(i++; i < 3; i++) {}', 'for(i++;i<3;i++){}'),
390
391        # unary decrement operator in FOR init
392        ('for(i--; i < 3; i++) {}', 'for(i--;i<3;i++){}'),
393
394        # issue-37, simple identifier in FOR init
395        ('for(i; i < 3; i++) {}', 'for(i;i<3;i++){}'),
396
397        # https://github.com/rspivak/slimit/issues/32
398        ("""
399         Name.prototype = {
400           getPageProp: function Page_getPageProp(key) {
401             return this.pageDict.get(key);
402           },
403
404           get fullName() {
405             return this.first + " " + this.last;
406           },
407
408           set fullName(name) {
409             var names = name.split(" ");
410             this.first = names[0];
411             this.last = names[1];
412           }
413         };
414         """,
415         ('Name.prototype={getPageProp:function Page_getPageProp(key){'
416          'return this.pageDict.get(key);},'
417          'get fullName(){return this.first+" "+this.last;},'
418          'set fullName(name){var names=name.split(" ");this.first=names[0];'
419          'this.last=names[1];}};')
420        ),
421        ]
422
423
424def make_test_function(input, expected):
425
426    def test_func(self):
427        self.assertMinified(input, expected)
428
429    return test_func
430
431for index, (input, expected) in enumerate(MinifierTestCase.TEST_CASES):
432    func = make_test_function(input, expected)
433    setattr(MinifierTestCase, 'test_case_%d' % index, func)
434