MediaWiki  REL1_24
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(
00029             $expectedOutput,
00030             $minified,
00031             'Minified output should be in the form expected.'
00032         );
00033     }
00034 
00035     public static function provideMinifyCases() {
00036         return array(
00037             // Whitespace
00038             array( "\r\t\f \v\n\r", "" ),
00039             array( "foo, bar {\n\tprop: value;\n}", "foo,bar{prop:value}" ),
00040 
00041             // Loose comments
00042             array( "/* foo */", "" ),
00043             array( "/*******\n foo\n *******/", "" ),
00044             array( "/*!\n foo\n */", "" ),
00045 
00046             // Inline comments in various different places
00047             array( "/* comment */foo, bar {\n\tprop: value;\n}", "foo,bar{prop:value}" ),
00048             array( "foo/* comment */, bar {\n\tprop: value;\n}", "foo,bar{prop:value}" ),
00049             array( "foo,/* comment */ bar {\n\tprop: value;\n}", "foo,bar{prop:value}" ),
00050             array( "foo, bar/* comment */ {\n\tprop: value;\n}", "foo,bar{prop:value}" ),
00051             array( "foo, bar {\n\t/* comment */prop: value;\n}", "foo,bar{prop:value}" ),
00052             array( "foo, bar {\n\tprop: /* comment */value;\n}", "foo,bar{prop:value}" ),
00053             array( "foo, bar {\n\tprop: value /* comment */;\n}", "foo,bar{prop:value }" ),
00054             array( "foo, bar {\n\tprop: value; /* comment */\n}", "foo,bar{prop:value; }" ),
00055 
00056             // Keep track of things that aren't as minified as much as they
00057             // could be (bug 35493)
00058             array( 'foo { prop: value ;}', 'foo{prop:value }' ),
00059             array( 'foo { prop : value; }', 'foo{prop :value}' ),
00060             array( 'foo { prop: value ; }', 'foo{prop:value }' ),
00061             array( 'foo { font-family: "foo" , "bar"; }', 'foo{font-family:"foo" ,"bar"}' ),
00062             array( "foo { src:\n\turl('foo') ,\n\turl('bar') ; }", "foo{src:url('foo') ,url('bar') }" ),
00063 
00064             // Interesting cases with string values
00065             // - Double quotes, single quotes
00066             array( 'foo { content: ""; }', 'foo{content:""}' ),
00067             array( "foo { content: ''; }", "foo{content:''}" ),
00068             array( 'foo { content: "\'"; }', 'foo{content:"\'"}' ),
00069             array( "foo { content: '\"'; }", "foo{content:'\"'}" ),
00070             // - Whitespace in string values
00071             array( 'foo { content: " "; }', 'foo{content:" "}' ),
00072         );
00073     }
00074 
00082     public function testRemap( $message, $params, $expectedOutput ) {
00083         $remapped = call_user_func_array( 'CSSMin::remap', $params );
00084 
00085         $messageAdd = " Case: $message";
00086         $this->assertEquals(
00087             $expectedOutput,
00088             $remapped,
00089             'CSSMin::remap should return the expected url form.' . $messageAdd
00090         );
00091     }
00092 
00093     public static function provideRemapCases() {
00094         // Parameter signature:
00095         // CSSMin::remap( $code, $local, $remote, $embedData = true )
00096         return array(
00097             array(
00098                 'Simple case',
00099                 array( 'foo { prop: url(bar.png); }', false, 'http://example.org', false ),
00100                 'foo { prop: url(http://example.org/bar.png); }',
00101             ),
00102             array(
00103                 'Without trailing slash',
00104                 array( 'foo { prop: url(../bar.png); }', false, 'http://example.org/quux', false ),
00105                 'foo { prop: url(http://example.org/quux/../bar.png); }',
00106             ),
00107             array(
00108                 'With trailing slash on remote (bug 27052)',
00109                 array( 'foo { prop: url(../bar.png); }', false, 'http://example.org/quux/', false ),
00110                 'foo { prop: url(http://example.org/quux/../bar.png); }',
00111             ),
00112             array(
00113                 'Guard against stripping double slashes from query',
00114                 array( 'foo { prop: url(bar.png?corge=//grault); }', false, 'http://example.org/quux/', false ),
00115                 'foo { prop: url(http://example.org/quux/bar.png?corge=//grault); }',
00116             ),
00117             array(
00118                 'Expand absolute paths',
00119                 array( 'foo { prop: url(/w/skin/images/bar.png); }', false, 'http://example.org/quux', false ),
00120                 'foo { prop: url(http://doc.example.org/w/skin/images/bar.png); }',
00121             ),
00122         );
00123     }
00124 
00131     public function testRemapRemapping( $message, $input, $expectedOutput ) {
00132         $localPath = __DIR__ . '/../../data/cssmin/';
00133         $remotePath = 'http://localhost/w/';
00134 
00135         $realOutput = CSSMin::remap( $input, $localPath, $remotePath );
00136 
00137         $this->assertEquals(
00138             $expectedOutput,
00139             preg_replace( '/\d+-\d+-\d+T\d+:\d+:\d+Z/', 'timestamp', $realOutput ),
00140             "CSSMin::remap: $message"
00141         );
00142     }
00143 
00144     public static function provideRemapRemappingCases() {
00145         // red.gif and green.gif are one-pixel 35-byte GIFs.
00146         // large.png is a 35K PNG that should be non-embeddable.
00147         // Full paths start with http://localhost/w/.
00148         // Timestamps in output are replaced with 'timestamp'.
00149 
00150         // data: URIs for red.gif and green.gif
00151         $red   = 'data:image/gif;base64,R0lGODlhAQABAIAAAP8AADAAACwAAAAAAQABAAACAkQBADs=';
00152         $green = 'data:image/gif;base64,R0lGODlhAQABAIAAAACAADAAACwAAAAAAQABAAACAkQBADs=';
00153 
00154         return array(
00155             array(
00156                 'Regular file',
00157                 'foo { background: url(red.gif); }',
00158                 'foo { background: url(http://localhost/w/red.gif?timestamp); }',
00159             ),
00160             array(
00161                 'Regular file (missing)',
00162                 'foo { background: url(theColorOfHerHair.gif); }',
00163                 'foo { background: url(http://localhost/w/theColorOfHerHair.gif); }',
00164             ),
00165             array(
00166                 'Remote URL',
00167                 'foo { background: url(http://example.org/w/foo.png); }',
00168                 'foo { background: url(http://example.org/w/foo.png); }',
00169             ),
00170             array(
00171                 'Protocol-relative remote URL',
00172                 'foo { background: url(//example.org/w/foo.png); }',
00173                 'foo { background: url(//example.org/w/foo.png); }',
00174             ),
00175             array(
00176                 'Remote URL with query',
00177                 'foo { background: url(http://example.org/w/foo.png?query=yes); }',
00178                 'foo { background: url(http://example.org/w/foo.png?query=yes); }',
00179             ),
00180             array(
00181                 'Protocol-relative remote URL with query',
00182                 'foo { background: url(//example.org/w/foo.png?query=yes); }',
00183                 'foo { background: url(//example.org/w/foo.png?query=yes); }',
00184             ),
00185             array(
00186                 'Domain-relative URL',
00187                 'foo { background: url(/static/foo.png); }',
00188                 'foo { background: url(http://doc.example.org/static/foo.png); }',
00189             ),
00190             array(
00191                 'Domain-relative URL with query',
00192                 'foo { background: url(/static/foo.png?query=yes); }',
00193                 'foo { background: url(http://doc.example.org/static/foo.png?query=yes); }',
00194             ),
00195             array(
00196                 'Remote URL (unnecessary quotes not preserved)',
00197                 'foo { background: url("http://example.org/w/foo.png"); }',
00198                 'foo { background: url(http://example.org/w/foo.png); }',
00199             ),
00200             array(
00201                 'Embedded file',
00202                 'foo { /* @embed */ background: url(red.gif); }',
00203                 "foo { background: url($red); background: url(http://localhost/w/red.gif?timestamp)!ie; }",
00204             ),
00205             array(
00206                 'Embedded file, other comments before the rule',
00207                 "foo { /* Bar. */ /* @embed */ background: url(red.gif); }",
00208                 "foo { /* Bar. */ background: url($red); /* Bar. */ background: url(http://localhost/w/red.gif?timestamp)!ie; }",
00209             ),
00210             array(
00211                 'Can not re-embed data: URIs',
00212                 "foo { /* @embed */ background: url($red); }",
00213                 "foo { background: url($red); }",
00214             ),
00215             array(
00216                 'Can not remap data: URIs',
00217                 "foo { background: url($red); }",
00218                 "foo { background: url($red); }",
00219             ),
00220             array(
00221                 'Can not embed remote URLs',
00222                 'foo { /* @embed */ background: url(http://example.org/w/foo.png); }',
00223                 'foo { background: url(http://example.org/w/foo.png); }',
00224             ),
00225             array(
00226                 'Embedded file (inline @embed)',
00227                 'foo { background: /* @embed */ url(red.gif); }',
00228                 "foo { background: url($red); "
00229                     . "background: url(http://localhost/w/red.gif?timestamp)!ie; }",
00230             ),
00231             array(
00232                 'Can not embed large files',
00233                 'foo { /* @embed */ background: url(large.png); }',
00234                 "foo { background: url(http://localhost/w/large.png?timestamp); }",
00235             ),
00236             array(
00237                 'Two regular files in one rule',
00238                 'foo { background: url(red.gif), url(green.gif); }',
00239                 'foo { background: url(http://localhost/w/red.gif?timestamp), '
00240                     . 'url(http://localhost/w/green.gif?timestamp); }',
00241             ),
00242             array(
00243                 'Two embedded files in one rule',
00244                 'foo { /* @embed */ background: url(red.gif), url(green.gif); }',
00245                 "foo { background: url($red), url($green); "
00246                     . "background: url(http://localhost/w/red.gif?timestamp), "
00247                     . "url(http://localhost/w/green.gif?timestamp)!ie; }",
00248             ),
00249             array(
00250                 'Two embedded files in one rule (inline @embed)',
00251                 'foo { background: /* @embed */ url(red.gif), /* @embed */ url(green.gif); }',
00252                 "foo { background: url($red), url($green); "
00253                     . "background: url(http://localhost/w/red.gif?timestamp), "
00254                     . "url(http://localhost/w/green.gif?timestamp)!ie; }",
00255             ),
00256             array(
00257                 'Two embedded files in one rule (inline @embed), one too large',
00258                 'foo { background: /* @embed */ url(red.gif), /* @embed */ url(large.png); }',
00259                 "foo { background: url($red), url(http://localhost/w/large.png?timestamp); "
00260                     . "background: url(http://localhost/w/red.gif?timestamp), "
00261                     . "url(http://localhost/w/large.png?timestamp)!ie; }",
00262             ),
00263             array(
00264                 'Practical example with some noise',
00265                 'foo { /* @embed */ background: #f9f9f9 url(red.gif) 0 0 no-repeat; }',
00266                 "foo { background: #f9f9f9 url($red) 0 0 no-repeat; "
00267                     . "background: #f9f9f9 url(http://localhost/w/red.gif?timestamp) 0 0 no-repeat!ie; }",
00268             ),
00269             array(
00270                 'Does not mess with other properties',
00271                 'foo { color: red; background: url(red.gif); font-size: small; }',
00272                 'foo { color: red; background: url(http://localhost/w/red.gif?timestamp); font-size: small; }',
00273             ),
00274             array(
00275                 'Spacing and miscellanea not changed (1)',
00276                 'foo {   background:    url(red.gif);  }',
00277                 'foo {   background:    url(http://localhost/w/red.gif?timestamp);  }',
00278             ),
00279             array(
00280                 'Spacing and miscellanea not changed (2)',
00281                 'foo {background:url(red.gif)}',
00282                 'foo {background:url(http://localhost/w/red.gif?timestamp)}',
00283             ),
00284             array(
00285                 'Spaces within url() parentheses are ignored',
00286                 'foo { background: url( red.gif ); }',
00287                 'foo { background: url(http://localhost/w/red.gif?timestamp); }',
00288             ),
00289             array(
00290                 '@import rule to local file (should we remap this?)',
00291                 '@import url(/styles.css)',
00292                 '@import url(http://doc.example.org/styles.css)',
00293             ),
00294             array(
00295                 '@import rule to URL (should we remap this?)',
00296                 '@import url(//localhost/styles.css?query=yes)',
00297                 '@import url(//localhost/styles.css?query=yes)',
00298             ),
00299             array(
00300                 'Simple case with comments before url',
00301                 'foo { prop: /* some {funny;} comment */ url(bar.png); }',
00302                 'foo { prop: /* some {funny;} comment */ url(http://localhost/w/bar.png); }',
00303             ),
00304             array(
00305                 'Simple case with comments after url',
00306                 'foo { prop: url(red.gif)/* some {funny;} comment */ ; }',
00307                 'foo { prop: url(http://localhost/w/red.gif?timestamp)/* some {funny;} comment */ ; }',
00308             ),
00309             array(
00310                 'Embedded file with comment before url',
00311                 'foo { /* @embed */ background: /* some {funny;} comment */ url(red.gif); }',
00312                 "foo { background: /* some {funny;} comment */ url($red); background: /* some {funny;} comment */ url(http://localhost/w/red.gif?timestamp)!ie; }",
00313             ),
00314             array(
00315                 'Embedded file with comments inside and outside the rule',
00316                 'foo { /* @embed */ background: url(red.gif) /* some {foo;} comment */; /* some {bar;} comment */ }',
00317                 "foo { background: url($red) /* some {foo;} comment */; background: url(http://localhost/w/red.gif?timestamp) /* some {foo;} comment */!ie; /* some {bar;} comment */ }",
00318             ),
00319             array(
00320                 'Embedded file with comment outside the rule',
00321                 'foo { /* @embed */ background: url(red.gif); /* some {funny;} comment */ }',
00322                 "foo { background: url($red); background: url(http://localhost/w/red.gif?timestamp)!ie; /* some {funny;} comment */ }",
00323             ),
00324             array(
00325                 'Rule with two urls, each with comments',
00326                 '{ background: /*asd*/ url(something.png); background: /*jkl*/ url(something.png); }',
00327                 '{ background: /*asd*/ url(http://localhost/w/something.png); background: /*jkl*/ url(http://localhost/w/something.png); }',
00328             ),
00329             array(
00330                 'Sanity check for offending line from jquery.ui.theme.css (bug 60077)',
00331                 '.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #d3d3d3/*{borderColorDefault}*/; background: #e6e6e6/*{bgColorDefault}*/ url(images/ui-bg_glass_75_e6e6e6_1x400.png)/*{bgImgUrlDefault}*/ 50%/*{bgDefaultXPos}*/ 50%/*{bgDefaultYPos}*/ repeat-x/*{bgDefaultRepeat}*/; font-weight: normal/*{fwDefault}*/; color: #555555/*{fcDefault}*/; }',
00332                 '.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #d3d3d3/*{borderColorDefault}*/; background: #e6e6e6/*{bgColorDefault}*/ url(http://localhost/w/images/ui-bg_glass_75_e6e6e6_1x400.png)/*{bgImgUrlDefault}*/ 50%/*{bgDefaultXPos}*/ 50%/*{bgDefaultYPos}*/ repeat-x/*{bgDefaultRepeat}*/; font-weight: normal/*{fwDefault}*/; color: #555555/*{fcDefault}*/; }',
00333             ),
00334         );
00335     }
00336 
00343     public function testBuildUrlValue( $message, $input, $expectedOutput ) {
00344         $this->assertEquals(
00345             $expectedOutput,
00346             CSSMin::buildUrlValue( $input ),
00347             "CSSMin::buildUrlValue: $message"
00348         );
00349     }
00350 
00351     public static function provideBuildUrlValueCases() {
00352         return array(
00353             array(
00354                 'Full URL',
00355                 'scheme://user@domain:port/~user/fi%20le.png?query=yes&really=y+s',
00356                 'url(scheme://user@domain:port/~user/fi%20le.png?query=yes&really=y+s)',
00357             ),
00358             array(
00359                 'data: URI',
00360                 'data:image/png;base64,R0lGODlh/+==',
00361                 'url(data:image/png;base64,R0lGODlh/+==)',
00362             ),
00363             array(
00364                 'URL with quotes',
00365                 "https://en.wikipedia.org/wiki/Wendy's",
00366                 "url(\"https://en.wikipedia.org/wiki/Wendy's\")",
00367             ),
00368             array(
00369                 'URL with parentheses',
00370                 'https://en.wikipedia.org/wiki/Boston_(band)',
00371                 'url("https://en.wikipedia.org/wiki/Boston_(band)")',
00372             ),
00373         );
00374     }
00375 
00383     public function testMinifyWithCSSStringValues( $code, $expectedOutput ) {
00384         $this->testMinifyOutput( $code, $expectedOutput );
00385     }
00386 
00387     public static function provideStringCases() {
00388         return array(
00389             // String values should be respected
00390             // - More than one space in a string value
00391             array( 'foo { content: "  "; }', 'foo{content:"  "}' ),
00392             // - Using a tab in a string value (turns into a space)
00393             array( "foo { content: '\t'; }", "foo{content:'\t'}" ),
00394             // - Using css-like syntax in string values
00395             array(
00396                 'foo::after { content: "{;}"; position: absolute; }',
00397                 'foo::after{content:"{;}";position:absolute}'
00398             ),
00399         );
00400     }
00401 }