MediaWiki
REL1_23
|
00001 <?php 00008 class CSSMinTest extends MediaWikiTestCase { 00009 00010 protected function setUp() { 00011 parent::setUp(); 00012 00013 $server = 'http://doc.example.org'; 00014 00015 $this->setMwGlobals( array( 00016 'wgServer' => $server, 00017 'wgCanonicalServer' => $server, 00018 ) ); 00019 } 00020 00025 public function testMinify( $code, $expectedOutput ) { 00026 $minified = CSSMin::minify( $code ); 00027 00028 $this->assertEquals( $expectedOutput, $minified, 'Minified output should be in the form expected.' ); 00029 } 00030 00031 public static function provideMinifyCases() { 00032 return array( 00033 // Whitespace 00034 array( "\r\t\f \v\n\r", "" ), 00035 array( "foo, bar {\n\tprop: value;\n}", "foo,bar{prop:value}" ), 00036 00037 // Loose comments 00038 array( "/* foo */", "" ), 00039 array( "/*******\n foo\n *******/", "" ), 00040 array( "/*!\n foo\n */", "" ), 00041 00042 // Inline comments in various different places 00043 array( "/* comment */foo, bar {\n\tprop: value;\n}", "foo,bar{prop:value}" ), 00044 array( "foo/* comment */, bar {\n\tprop: value;\n}", "foo,bar{prop:value}" ), 00045 array( "foo,/* comment */ bar {\n\tprop: value;\n}", "foo,bar{prop:value}" ), 00046 array( "foo, bar/* comment */ {\n\tprop: value;\n}", "foo,bar{prop:value}" ), 00047 array( "foo, bar {\n\t/* comment */prop: value;\n}", "foo,bar{prop:value}" ), 00048 array( "foo, bar {\n\tprop: /* comment */value;\n}", "foo,bar{prop:value}" ), 00049 array( "foo, bar {\n\tprop: value /* comment */;\n}", "foo,bar{prop:value }" ), 00050 array( "foo, bar {\n\tprop: value; /* comment */\n}", "foo,bar{prop:value; }" ), 00051 00052 // Keep track of things that aren't as minified as much as they 00053 // could be (bug 35493) 00054 array( 'foo { prop: value ;}', 'foo{prop:value }' ), 00055 array( 'foo { prop : value; }', 'foo{prop :value}' ), 00056 array( 'foo { prop: value ; }', 'foo{prop:value }' ), 00057 array( 'foo { font-family: "foo" , "bar"; }', 'foo{font-family:"foo" ,"bar"}' ), 00058 array( "foo { src:\n\turl('foo') ,\n\turl('bar') ; }", "foo{src:url('foo') ,url('bar') }" ), 00059 00060 // Interesting cases with string values 00061 // - Double quotes, single quotes 00062 array( 'foo { content: ""; }', 'foo{content:""}' ), 00063 array( "foo { content: ''; }", "foo{content:''}" ), 00064 array( 'foo { content: "\'"; }', 'foo{content:"\'"}' ), 00065 array( "foo { content: '\"'; }", "foo{content:'\"'}" ), 00066 // - Whitespace in string values 00067 array( 'foo { content: " "; }', 'foo{content:" "}' ), 00068 ); 00069 } 00070 00077 public function testRemap( $message, $params, $expectedOutput ) { 00078 $remapped = call_user_func_array( 'CSSMin::remap', $params ); 00079 00080 $messageAdd = " Case: $message"; 00081 $this->assertEquals( $expectedOutput, $remapped, 'CSSMin::remap should return the expected url form.' . $messageAdd ); 00082 } 00083 00084 public static function provideRemapCases() { 00085 // Parameter signature: 00086 // CSSMin::remap( $code, $local, $remote, $embedData = true ) 00087 return array( 00088 array( 00089 'Simple case', 00090 array( 'foo { prop: url(bar.png); }', false, 'http://example.org', false ), 00091 'foo { prop: url(http://example.org/bar.png); }', 00092 ), 00093 array( 00094 'Without trailing slash', 00095 array( 'foo { prop: url(../bar.png); }', false, 'http://example.org/quux', false ), 00096 'foo { prop: url(http://example.org/quux/../bar.png); }', 00097 ), 00098 array( 00099 'With trailing slash on remote (bug 27052)', 00100 array( 'foo { prop: url(../bar.png); }', false, 'http://example.org/quux/', false ), 00101 'foo { prop: url(http://example.org/quux/../bar.png); }', 00102 ), 00103 array( 00104 'Guard against stripping double slashes from query', 00105 array( 'foo { prop: url(bar.png?corge=//grault); }', false, 'http://example.org/quux/', false ), 00106 'foo { prop: url(http://example.org/quux/bar.png?corge=//grault); }', 00107 ), 00108 array( 00109 'Expand absolute paths', 00110 array( 'foo { prop: url(/w/skin/images/bar.png); }', false, 'http://example.org/quux', false ), 00111 'foo { prop: url(http://doc.example.org/w/skin/images/bar.png); }', 00112 ), 00113 ); 00114 } 00115 00122 public function testRemapRemapping( $message, $input, $expectedOutput ) { 00123 $localPath = __DIR__ . '/../../data/cssmin/'; 00124 $remotePath = 'http://localhost/w/'; 00125 00126 $realOutput = CSSMin::remap( $input, $localPath, $remotePath ); 00127 00128 $this->assertEquals( 00129 $expectedOutput, 00130 preg_replace( '/\d+-\d+-\d+T\d+:\d+:\d+Z/', 'timestamp', $realOutput ), 00131 "CSSMin::remap: $message" 00132 ); 00133 } 00134 00135 public static function provideRemapRemappingCases() { 00136 // red.gif and green.gif are one-pixel 35-byte GIFs. 00137 // large.png is a 35K PNG that should be non-embeddable. 00138 // Full paths start with http://localhost/w/. 00139 // Timestamps in output are replaced with 'timestamp'. 00140 00141 // data: URIs for red.gif and green.gif 00142 $red = 'data:image/gif;base64,R0lGODlhAQABAIAAAP8AADAAACwAAAAAAQABAAACAkQBADs='; 00143 $green = 'data:image/gif;base64,R0lGODlhAQABAIAAAACAADAAACwAAAAAAQABAAACAkQBADs='; 00144 00145 return array( 00146 array( 00147 'Regular file', 00148 'foo { background: url(red.gif); }', 00149 'foo { background: url(http://localhost/w/red.gif?timestamp); }', 00150 ), 00151 array( 00152 'Regular file (missing)', 00153 'foo { background: url(theColorOfHerHair.gif); }', 00154 'foo { background: url(http://localhost/w/theColorOfHerHair.gif); }', 00155 ), 00156 array( 00157 'Remote URL', 00158 'foo { background: url(http://example.org/w/foo.png); }', 00159 'foo { background: url(http://example.org/w/foo.png); }', 00160 ), 00161 array( 00162 'Protocol-relative remote URL', 00163 'foo { background: url(//example.org/w/foo.png); }', 00164 'foo { background: url(//example.org/w/foo.png); }', 00165 ), 00166 array( 00167 'Remote URL with query', 00168 'foo { background: url(http://example.org/w/foo.png?query=yes); }', 00169 'foo { background: url(http://example.org/w/foo.png?query=yes); }', 00170 ), 00171 array( 00172 'Protocol-relative remote URL with query', 00173 'foo { background: url(//example.org/w/foo.png?query=yes); }', 00174 'foo { background: url(//example.org/w/foo.png?query=yes); }', 00175 ), 00176 array( 00177 'Domain-relative URL', 00178 'foo { background: url(/static/foo.png); }', 00179 'foo { background: url(http://doc.example.org/static/foo.png); }', 00180 ), 00181 array( 00182 'Domain-relative URL with query', 00183 'foo { background: url(/static/foo.png?query=yes); }', 00184 'foo { background: url(http://doc.example.org/static/foo.png?query=yes); }', 00185 ), 00186 array( 00187 'Remote URL (unnecessary quotes not preserved)', 00188 'foo { background: url("http://example.org/w/foo.png"); }', 00189 'foo { background: url(http://example.org/w/foo.png); }', 00190 ), 00191 array( 00192 'Embedded file', 00193 'foo { /* @embed */ background: url(red.gif); }', 00194 "foo { background: url($red); background: url(http://localhost/w/red.gif?timestamp)!ie; }", 00195 ), 00196 array( 00197 'Can not embed remote URLs', 00198 'foo { /* @embed */ background: url(http://example.org/w/foo.png); }', 00199 'foo { background: url(http://example.org/w/foo.png); }', 00200 ), 00201 array( 00202 'Embedded file (inline @embed)', 00203 'foo { background: /* @embed */ url(red.gif); }', 00204 "foo { background: url($red); background: url(http://localhost/w/red.gif?timestamp)!ie; }", 00205 ), 00206 array( 00207 'Can not embed large files', 00208 'foo { /* @embed */ background: url(large.png); }', 00209 "foo { background: url(http://localhost/w/large.png?timestamp); }", 00210 ), 00211 array( 00212 'Two regular files in one rule', 00213 'foo { background: url(red.gif), url(green.gif); }', 00214 'foo { background: url(http://localhost/w/red.gif?timestamp), url(http://localhost/w/green.gif?timestamp); }', 00215 ), 00216 array( 00217 'Two embedded files in one rule', 00218 'foo { /* @embed */ background: url(red.gif), url(green.gif); }', 00219 "foo { background: url($red), url($green); background: url(http://localhost/w/red.gif?timestamp), url(http://localhost/w/green.gif?timestamp)!ie; }", 00220 ), 00221 array( 00222 'Two embedded files in one rule (inline @embed)', 00223 'foo { background: /* @embed */ url(red.gif), /* @embed */ url(green.gif); }', 00224 "foo { background: url($red), url($green); background: url(http://localhost/w/red.gif?timestamp), url(http://localhost/w/green.gif?timestamp)!ie; }", 00225 ), 00226 array( 00227 'Two embedded files in one rule (inline @embed), one too large', 00228 'foo { background: /* @embed */ url(red.gif), /* @embed */ url(large.png); }', 00229 "foo { background: url($red), url(http://localhost/w/large.png?timestamp); background: url(http://localhost/w/red.gif?timestamp), url(http://localhost/w/large.png?timestamp)!ie; }", 00230 ), 00231 array( 00232 'Practical example with some noise', 00233 'foo { /* @embed */ background: #f9f9f9 url(red.gif) 0 0 no-repeat; }', 00234 "foo { background: #f9f9f9 url($red) 0 0 no-repeat; background: #f9f9f9 url(http://localhost/w/red.gif?timestamp) 0 0 no-repeat!ie; }", 00235 ), 00236 array( 00237 'Does not mess with other properties', 00238 'foo { color: red; background: url(red.gif); font-size: small; }', 00239 'foo { color: red; background: url(http://localhost/w/red.gif?timestamp); font-size: small; }', 00240 ), 00241 array( 00242 'Spacing and miscellanea not changed (1)', 00243 'foo { background: url(red.gif); }', 00244 'foo { background: url(http://localhost/w/red.gif?timestamp); }', 00245 ), 00246 array( 00247 'Spacing and miscellanea not changed (2)', 00248 'foo {background:url(red.gif)}', 00249 'foo {background:url(http://localhost/w/red.gif?timestamp)}', 00250 ), 00251 array( 00252 'Spaces within url() parentheses are ignored', 00253 'foo { background: url( red.gif ); }', 00254 'foo { background: url(http://localhost/w/red.gif?timestamp); }', 00255 ), 00256 array( 00257 '@import rule to local file (should we remap this?)', 00258 '@import url(/styles.css)', 00259 '@import url(http://doc.example.org/styles.css)', 00260 ), 00261 array( 00262 '@import rule to URL (should we remap this?)', 00263 '@import url(//localhost/styles.css?query=yes)', 00264 '@import url(//localhost/styles.css?query=yes)', 00265 ), 00266 ); 00267 } 00268 00275 public function testBuildUrlValue( $message, $input, $expectedOutput ) { 00276 $this->assertEquals( 00277 $expectedOutput, 00278 CSSMin::buildUrlValue( $input ), 00279 "CSSMin::buildUrlValue: $message" 00280 ); 00281 } 00282 00283 public static function provideBuildUrlValueCases() { 00284 return array( 00285 array( 00286 'Full URL', 00287 'scheme://user@domain:port/~user/fi%20le.png?query=yes&really=y+s', 00288 'url(scheme://user@domain:port/~user/fi%20le.png?query=yes&really=y+s)', 00289 ), 00290 array( 00291 'data: URI', 00292 'data:image/png;base64,R0lGODlh/+==', 00293 'url(data:image/png;base64,R0lGODlh/+==)', 00294 ), 00295 array( 00296 'URL with quotes', 00297 "https://en.wikipedia.org/wiki/Wendy's", 00298 "url(\"https://en.wikipedia.org/wiki/Wendy's\")", 00299 ), 00300 array( 00301 'URL with parentheses', 00302 'https://en.wikipedia.org/wiki/Boston_(band)', 00303 'url("https://en.wikipedia.org/wiki/Boston_(band)")', 00304 ), 00305 ); 00306 } 00307 00315 public function testMinifyWithCSSStringValues( $code, $expectedOutput ) { 00316 $this->testMinifyOutput( $code, $expectedOutput ); 00317 } 00318 00319 public static function provideStringCases() { 00320 return array( 00321 // String values should be respected 00322 // - More than one space in a string value 00323 array( 'foo { content: " "; }', 'foo{content:" "}' ), 00324 // - Using a tab in a string value (turns into a space) 00325 array( "foo { content: '\t'; }", "foo{content:'\t'}" ), 00326 // - Using css-like syntax in string values 00327 array( 'foo::after { content: "{;}"; position: absolute; }', 'foo::after{content:"{;}";position:absolute}' ), 00328 ); 00329 } 00330 }