Final fixup to r103910 and follow-ups.
[lhc/web/wiklou.git] / tests / phpunit / includes / libs / JavaScriptMinifierTest.php
1 <?php
2
3 class JavaScriptMinifierTest extends MediaWikiTestCase {
4
5 function provideCases() {
6 return array(
7 // Basic tokens
8 array( "\r\t\f \v\n\r", "" ),
9 array( "/* Foo *\n*bar\n*/", "" ),
10 /**
11 * ' Foo \' bar \
12 * baz \' quox ' .
13 */
14 array( "' Foo \\' bar \\\n baz \\' quox ' .length", "' Foo \\' bar \\\n baz \\' quox '.length" ),
15 array( "\" Foo \\\" bar \\\n baz \\\" quox \" .length", "\" Foo \\\" bar \\\n baz \\\" quox \".length" ),
16 array( "// Foo b/ar baz", "" ),
17 array( "/ Foo \\/ bar [ / \\] / ] baz / .length", "/ Foo \\/ bar [ / \\] / ] baz /.length" ),
18 // HTML comments
19 array( "<!-- Foo bar", "" ),
20 array( "<!-- Foo --> bar", "" ),
21 array( "--> Foo", "" ),
22 array( "x --> y", "x-->y" ),
23 // Semicolon insertion
24 array( "(function(){return\nx;})", "(function(){return\nx;})" ),
25 array( "throw\nx;", "throw\nx;" ),
26 array( "while(p){continue\nx;}", "while(p){continue\nx;}" ),
27 array( "while(p){break\nx;}", "while(p){break\nx;}" ),
28 array( "var\nx;", "var x;" ),
29 array( "x\ny;", "x\ny;" ),
30 array( "x\n++y;", "x\n++y;" ),
31 array( "x\n!y;", "x\n!y;" ),
32 array( "x\n{y}", "x\n{y}" ),
33 array( "x\n+y;", "x+y;" ),
34 array( "x\n(y);", "x(y);" ),
35 array( "5.\nx;", "5.\nx;" ),
36 array( "0xFF.\nx;", "0xFF.x;" ),
37 array( "5.3.\nx;", "5.3.x;" ),
38 // Token separation
39 array( "x in y", "x in y" ),
40 array( "/x/g in y", "/x/g in y" ),
41 array( "x in 30", "x in 30" ),
42 array( "x + ++ y", "x+ ++y" ),
43 array( "x / /y/.exec(z)", "x/ /y/.exec(z)" ),
44 // State machine
45 array( "/ x/g", "/ x/g" ),
46 array( "(function(){return/ x/g})", "(function(){return/ x/g})" ),
47 array( "+/ x/g", "+/ x/g" ),
48 array( "++/ x/g", "++/ x/g" ),
49 array( "x/ x/g", "x/x/g" ),
50 array( "(/ x/g)", "(/ x/g)" ),
51 array( "if(/ x/g);", "if(/ x/g);" ),
52 array( "(x/ x/g)", "(x/x/g)" ),
53 array( "([/ x/g])", "([/ x/g])" ),
54 array( "+x/ x/g", "+x/x/g" ),
55 array( "{}/ x/g", "{}/ x/g" ),
56 array( "+{}/ x/g", "+{}/x/g" ),
57 array( "(x)/ x/g", "(x)/x/g" ),
58 array( "if(x)/ x/g", "if(x)/ x/g" ),
59 array( "for(x;x;{}/ x/g);", "for(x;x;{}/x/g);" ),
60 array( "x;x;{}/ x/g", "x;x;{}/ x/g" ),
61 array( "x:{}/ x/g", "x:{}/ x/g" ),
62 array( "switch(x){case y?z:{}/ x/g:{}/ x/g;}", "switch(x){case y?z:{}/x/g:{}/ x/g;}" ),
63 array( "function x(){}/ x/g", "function x(){}/ x/g" ),
64 array( "+function x(){}/ x/g", "+function x(){}/x/g" ),
65
66 // Tests for things that broke in the past
67 // Multiline quoted string
68 array( "var foo=\"\\\nblah\\\n\";", "var foo=\"\\\nblah\\\n\";" ),
69 // Multiline quoted string followed by string with spaces
70 array( "var foo=\"\\\nblah\\\n\";\nvar baz = \" foo \";\n", "var foo=\"\\\nblah\\\n\";var baz=\" foo \";" ),
71 // URL in quoted string ( // is not a comment)
72 array( "aNode.setAttribute('href','http://foo.bar.org/baz');", "aNode.setAttribute('href','http://foo.bar.org/baz');" ),
73 // URL in quoted string after multiline quoted string
74 array( "var foo=\"\\\nblah\\\n\";\naNode.setAttribute('href','http://foo.bar.org/baz');", "var foo=\"\\\nblah\\\n\";aNode.setAttribute('href','http://foo.bar.org/baz');" ),
75 // Division vs. regex nastiness
76 array( "alert( (10+10) / '/'.charCodeAt( 0 ) + '//' );", "alert((10+10)/'/'.charCodeAt(0)+'//');" ),
77 array( "if(1)/a /g.exec('Pa ss');", "if(1)/a /g.exec('Pa ss');" ),
78
79 // newline insertion after 1000 chars: break after the "++", not before
80 array( str_repeat( ';', 996 ) . "if(x++);", str_repeat( ';', 996 ) . "if(x++\n);" ),
81
82 // Unicode letter characters should pass through ok in identifiers (bug 31187)
83 array( "var KaŝSkatolVal = {}", 'var KaŝSkatolVal={}'),
84 // And also per spec unicode char escape values should work in identifiers,
85 // as long as it's a valid char. In future it might get normalized.
86 array( "var Ka\\u015dSkatolVal = {}", 'var Ka\\u015dSkatolVal={}'),
87
88 /* Some structures that might look invalid at first sight */
89 array( "var a = 5.;", "var a=5.;" ),
90 array( "5.0.toString();", "5.0.toString();" ),
91 array( "5..toString();", "5..toString();" ),
92 array( "5...toString();", false ),
93 array( "5.\n.toString();", '5..toString();' ),
94 );
95 }
96
97 /**
98 * @dataProvider provideCases
99 */
100 function testJavaScriptMinifierOutput( $code, $expectedOutput ) {
101 $minified = JavaScriptMinifier::minify( $code );
102
103 // JSMin+'s parser will throw an exception if output is not valid JS.
104 // suppression of warnings needed for stupid crap
105 wfSuppressWarnings();
106 $parser = new JSParser();
107 wfRestoreWarnings();
108 $parser->parse( $minified, 'minify-test.js', 1 );
109
110 $this->assertEquals( $expectedOutput, $minified, "Minified output should be in the form expected." );
111 }
112
113 /**
114 * @dataProvider provideBug32548
115 */
116 function testBug32548Exponent($num) {
117 // Long line breaking was being incorrectly done between the base and
118 // exponent part of a number, causing a syntax error. The line should
119 // instead break at the start of the number.
120 $prefix = 'var longVarName' . str_repeat('_', 973) . '=';
121 $suffix = ',shortVarName=0;';
122
123 $input = $prefix . $num . $suffix;
124 $expected = $prefix . "\n" . $num . $suffix;
125
126 $minified = JavaScriptMinifier::minify( $input );
127
128 $this->assertEquals( $expected, $minified, "Line breaks must not occur in middle of exponent");
129 }
130
131 function provideBug32548() {
132 return array(
133 array(
134 // This one gets interpreted all together by the prior code;
135 // no break at the 'E' happens.
136 '1.23456789E55',
137 ),
138 array(
139 // This one breaks under the bad code; splits between 'E' and '+'
140 '1.23456789E+5',
141 ),
142 array(
143 // This one breaks under the bad code; splits between 'E' and '-'
144 '1.23456789E-5',
145 ),
146 );
147 }
148 }