MediaWiki  REL1_23
CSSMinTest.php
Go to the documentation of this file.
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 }