MediaWiki  REL1_22
HtmlTest.php
Go to the documentation of this file.
00001 <?php
00004 class HtmlTest extends MediaWikiTestCase {
00005 
00006     protected function setUp() {
00007         parent::setUp();
00008 
00009         $langCode = 'en';
00010         $langObj = Language::factory( $langCode );
00011 
00012         // Hardcode namespaces during test runs,
00013         // so that html output based on existing namespaces
00014         // can be properly evaluated.
00015         $langObj->setNamespaces( array(
00016             -2 => 'Media',
00017             -1 => 'Special',
00018             0 => '',
00019             1 => 'Talk',
00020             2 => 'User',
00021             3 => 'User_talk',
00022             4 => 'MyWiki',
00023             5 => 'MyWiki_Talk',
00024             6 => 'File',
00025             7 => 'File_talk',
00026             8 => 'MediaWiki',
00027             9 => 'MediaWiki_talk',
00028             10 => 'Template',
00029             11 => 'Template_talk',
00030             14 => 'Category',
00031             15 => 'Category_talk',
00032             100 => 'Custom',
00033             101 => 'Custom_talk',
00034         ) );
00035 
00036         $this->setMwGlobals( array(
00037             'wgLanguageCode' => $langCode,
00038             'wgContLang' => $langObj,
00039             'wgLang' => $langObj,
00040             'wgWellFormedXml' => false,
00041         ) );
00042     }
00043 
00044     public function testElementBasics() {
00045         $this->assertEquals(
00046             '<img>',
00047             Html::element( 'img', null, '' ),
00048             'No close tag for short-tag elements'
00049         );
00050 
00051         $this->assertEquals(
00052             '<element></element>',
00053             Html::element( 'element', null, null ),
00054             'Close tag for empty element (null, null)'
00055         );
00056 
00057         $this->assertEquals(
00058             '<element></element>',
00059             Html::element( 'element', array(), '' ),
00060             'Close tag for empty element (array, string)'
00061         );
00062 
00063         $this->setMwGlobals( 'wgWellFormedXml', true );
00064 
00065         $this->assertEquals(
00066             '<img />',
00067             Html::element( 'img', null, '' ),
00068             'Self-closing tag for short-tag elements (wgWellFormedXml = true)'
00069         );
00070     }
00071 
00072     public function dataXmlMimeType() {
00073         return array(
00074             // ( $mimetype, $isXmlMimeType )
00075             # HTML is not an XML MimeType
00076             array( 'text/html', false ),
00077             # XML is an XML MimeType
00078             array( 'text/xml', true ),
00079             array( 'application/xml', true ),
00080             # XHTML is an XML MimeType
00081             array( 'application/xhtml+xml', true ),
00082             # Make sure other +xml MimeTypes are supported
00083             # SVG is another random MimeType even though we don't use it
00084             array( 'image/svg+xml', true ),
00085             # Complete random other MimeTypes are not XML
00086             array( 'text/plain', false ),
00087         );
00088     }
00089 
00093     public function testXmlMimeType( $mimetype, $isXmlMimeType ) {
00094         $this->assertEquals( $isXmlMimeType, Html::isXmlMimeType( $mimetype ) );
00095     }
00096 
00097     public function testExpandAttributesSkipsNullAndFalse() {
00098 
00099         ### EMPTY ########
00100         $this->assertEmpty(
00101             Html::expandAttributes( array( 'foo' => null ) ),
00102             'skip keys with null value'
00103         );
00104         $this->assertEmpty(
00105             Html::expandAttributes( array( 'foo' => false ) ),
00106             'skip keys with false value'
00107         );
00108         $this->assertNotEmpty(
00109             Html::expandAttributes( array( 'foo' => '' ) ),
00110             'keep keys with an empty string'
00111         );
00112     }
00113 
00114     public function testExpandAttributesForBooleans() {
00115         $this->assertEquals(
00116             '',
00117             Html::expandAttributes( array( 'selected' => false ) ),
00118             'Boolean attributes do not generates output when value is false'
00119         );
00120         $this->assertEquals(
00121             '',
00122             Html::expandAttributes( array( 'selected' => null ) ),
00123             'Boolean attributes do not generates output when value is null'
00124         );
00125 
00126         $this->assertEquals(
00127             ' selected',
00128             Html::expandAttributes( array( 'selected' => true ) ),
00129             'Boolean attributes have no value when value is true'
00130         );
00131         $this->assertEquals(
00132             ' selected',
00133             Html::expandAttributes( array( 'selected' ) ),
00134             'Boolean attributes have no value when value is true (passed as numerical array)'
00135         );
00136 
00137         $this->setMwGlobals( 'wgWellFormedXml', true );
00138 
00139         $this->assertEquals(
00140             ' selected=""',
00141             Html::expandAttributes( array( 'selected' => true ) ),
00142             'Boolean attributes have empty string value when value is true (wgWellFormedXml)'
00143         );
00144     }
00145 
00150     public function testExpandAttributesVariousExpansions() {
00151         ### NOT EMPTY ####
00152         $this->assertEquals(
00153             ' empty_string=""',
00154             Html::expandAttributes( array( 'empty_string' => '' ) ),
00155             'Empty string is always quoted'
00156         );
00157         $this->assertEquals(
00158             ' key=value',
00159             Html::expandAttributes( array( 'key' => 'value' ) ),
00160             'Simple string value needs no quotes'
00161         );
00162         $this->assertEquals(
00163             ' one=1',
00164             Html::expandAttributes( array( 'one' => 1 ) ),
00165             'Number 1 value needs no quotes'
00166         );
00167         $this->assertEquals(
00168             ' zero=0',
00169             Html::expandAttributes( array( 'zero' => 0 ) ),
00170             'Number 0 value needs no quotes'
00171         );
00172 
00173         $this->setMwGlobals( 'wgWellFormedXml', true );
00174 
00175         $this->assertEquals(
00176             ' empty_string=""',
00177             Html::expandAttributes( array( 'empty_string' => '' ) ),
00178             'Attribute values are always quoted (wgWellFormedXml): Empty string'
00179         );
00180         $this->assertEquals(
00181             ' key="value"',
00182             Html::expandAttributes( array( 'key' => 'value' ) ),
00183             'Attribute values are always quoted (wgWellFormedXml): Simple string'
00184         );
00185         $this->assertEquals(
00186             ' one="1"',
00187             Html::expandAttributes( array( 'one' => 1 ) ),
00188             'Attribute values are always quoted (wgWellFormedXml): Number 1'
00189         );
00190         $this->assertEquals(
00191             ' zero="0"',
00192             Html::expandAttributes( array( 'zero' => 0 ) ),
00193             'Attribute values are always quoted (wgWellFormedXml): Number 0'
00194         );
00195     }
00196 
00202     public function testExpandAttributesListValueAttributes() {
00203         ### STRING VALUES
00204         $this->assertEquals(
00205             ' class="redundant spaces here"',
00206             Html::expandAttributes( array( 'class' => ' redundant  spaces  here  ' ) ),
00207             'Normalization should strip redundant spaces'
00208         );
00209         $this->assertEquals(
00210             ' class="foo bar"',
00211             Html::expandAttributes( array( 'class' => 'foo bar foo bar bar' ) ),
00212             'Normalization should remove duplicates in string-lists'
00213         );
00214         ### "EMPTY" ARRAY VALUES
00215         $this->assertEquals(
00216             ' class=""',
00217             Html::expandAttributes( array( 'class' => array() ) ),
00218             'Value with an empty array'
00219         );
00220         $this->assertEquals(
00221             ' class=""',
00222             Html::expandAttributes( array( 'class' => array( null, '', ' ', '  ' ) ) ),
00223             'Array with null, empty string and spaces'
00224         );
00225         ### NON-EMPTY ARRAY VALUES
00226         $this->assertEquals(
00227             ' class="foo bar"',
00228             Html::expandAttributes( array( 'class' => array(
00229                 'foo',
00230                 'bar',
00231                 'foo',
00232                 'bar',
00233                 'bar',
00234             ) ) ),
00235             'Normalization should remove duplicates in the array'
00236         );
00237         $this->assertEquals(
00238             ' class="foo bar"',
00239             Html::expandAttributes( array( 'class' => array(
00240                 'foo bar',
00241                 'bar foo',
00242                 'foo',
00243                 'bar bar',
00244             ) ) ),
00245             'Normalization should remove duplicates in string-lists in the array'
00246         );
00247     }
00248 
00253     public function testExpandAttributesSpaceSeparatedAttributesWithBoolean() {
00254         $this->assertEquals(
00255             ' class="booltrue one"',
00256             Html::expandAttributes( array( 'class' => array(
00257                 'booltrue' => true,
00258                 'one' => 1,
00259 
00260                 # Method use isset() internally, make sure we do discard
00261                 # attributes values which have been assigned well known values
00262                 'emptystring' => '',
00263                 'boolfalse' => false,
00264                 'zero' => 0,
00265                 'null' => null,
00266             ) ) )
00267         );
00268     }
00269 
00277     public function testValueIsAuthoritativeInSpaceSeparatedAttributesArrays() {
00278         $this->assertEquals(
00279             ' class=""',
00280             Html::expandAttributes( array( 'class' => array(
00281                 'GREEN',
00282                 'GREEN' => false,
00283                 'GREEN',
00284             ) ) )
00285         );
00286     }
00287 
00288     public function testNamespaceSelector() {
00289         $this->assertEquals(
00290             '<select id=namespace name=namespace>' . "\n" .
00291                 '<option value=0>(Main)</option>' . "\n" .
00292                 '<option value=1>Talk</option>' . "\n" .
00293                 '<option value=2>User</option>' . "\n" .
00294                 '<option value=3>User talk</option>' . "\n" .
00295                 '<option value=4>MyWiki</option>' . "\n" .
00296                 '<option value=5>MyWiki Talk</option>' . "\n" .
00297                 '<option value=6>File</option>' . "\n" .
00298                 '<option value=7>File talk</option>' . "\n" .
00299                 '<option value=8>MediaWiki</option>' . "\n" .
00300                 '<option value=9>MediaWiki talk</option>' . "\n" .
00301                 '<option value=10>Template</option>' . "\n" .
00302                 '<option value=11>Template talk</option>' . "\n" .
00303                 '<option value=14>Category</option>' . "\n" .
00304                 '<option value=15>Category talk</option>' . "\n" .
00305                 '<option value=100>Custom</option>' . "\n" .
00306                 '<option value=101>Custom talk</option>' . "\n" .
00307                 '</select>',
00308             Html::namespaceSelector(),
00309             'Basic namespace selector without custom options'
00310         );
00311 
00312         $this->assertEquals(
00313             '<label for=mw-test-namespace>Select a namespace:</label>&#160;' .
00314                 '<select id=mw-test-namespace name=wpNamespace>' . "\n" .
00315                 '<option value=all>all</option>' . "\n" .
00316                 '<option value=0>(Main)</option>' . "\n" .
00317                 '<option value=1>Talk</option>' . "\n" .
00318                 '<option value=2 selected>User</option>' . "\n" .
00319                 '<option value=3>User talk</option>' . "\n" .
00320                 '<option value=4>MyWiki</option>' . "\n" .
00321                 '<option value=5>MyWiki Talk</option>' . "\n" .
00322                 '<option value=6>File</option>' . "\n" .
00323                 '<option value=7>File talk</option>' . "\n" .
00324                 '<option value=8>MediaWiki</option>' . "\n" .
00325                 '<option value=9>MediaWiki talk</option>' . "\n" .
00326                 '<option value=10>Template</option>' . "\n" .
00327                 '<option value=11>Template talk</option>' . "\n" .
00328                 '<option value=14>Category</option>' . "\n" .
00329                 '<option value=15>Category talk</option>' . "\n" .
00330                 '<option value=100>Custom</option>' . "\n" .
00331                 '<option value=101>Custom talk</option>' . "\n" .
00332                 '</select>',
00333             Html::namespaceSelector(
00334                 array( 'selected' => '2', 'all' => 'all', 'label' => 'Select a namespace:' ),
00335                 array( 'name' => 'wpNamespace', 'id' => 'mw-test-namespace' )
00336             ),
00337             'Basic namespace selector with custom values'
00338         );
00339 
00340         $this->assertEquals(
00341             '<label for=namespace>Select a namespace:</label>&#160;' .
00342                 '<select id=namespace name=namespace>' . "\n" .
00343                 '<option value=0>(Main)</option>' . "\n" .
00344                 '<option value=1>Talk</option>' . "\n" .
00345                 '<option value=2>User</option>' . "\n" .
00346                 '<option value=3>User talk</option>' . "\n" .
00347                 '<option value=4>MyWiki</option>' . "\n" .
00348                 '<option value=5>MyWiki Talk</option>' . "\n" .
00349                 '<option value=6>File</option>' . "\n" .
00350                 '<option value=7>File talk</option>' . "\n" .
00351                 '<option value=8>MediaWiki</option>' . "\n" .
00352                 '<option value=9>MediaWiki talk</option>' . "\n" .
00353                 '<option value=10>Template</option>' . "\n" .
00354                 '<option value=11>Template talk</option>' . "\n" .
00355                 '<option value=14>Category</option>' . "\n" .
00356                 '<option value=15>Category talk</option>' . "\n" .
00357                 '<option value=100>Custom</option>' . "\n" .
00358                 '<option value=101>Custom talk</option>' . "\n" .
00359                 '</select>',
00360             Html::namespaceSelector(
00361                 array( 'label' => 'Select a namespace:' )
00362             ),
00363             'Basic namespace selector with a custom label but no id attribtue for the <select>'
00364         );
00365     }
00366 
00367     public function testCanFilterOutNamespaces() {
00368         $this->assertEquals(
00369             '<select id=namespace name=namespace>' . "\n" .
00370                 '<option value=2>User</option>' . "\n" .
00371                 '<option value=4>MyWiki</option>' . "\n" .
00372                 '<option value=5>MyWiki Talk</option>' . "\n" .
00373                 '<option value=6>File</option>' . "\n" .
00374                 '<option value=7>File talk</option>' . "\n" .
00375                 '<option value=8>MediaWiki</option>' . "\n" .
00376                 '<option value=9>MediaWiki talk</option>' . "\n" .
00377                 '<option value=10>Template</option>' . "\n" .
00378                 '<option value=11>Template talk</option>' . "\n" .
00379                 '<option value=14>Category</option>' . "\n" .
00380                 '<option value=15>Category talk</option>' . "\n" .
00381                 '</select>',
00382             Html::namespaceSelector(
00383                 array( 'exclude' => array( 0, 1, 3, 100, 101 ) )
00384             ),
00385             'Namespace selector namespace filtering.'
00386         );
00387     }
00388 
00389     public function testCanDisableANamespaces() {
00390         $this->assertEquals(
00391             '<select id=namespace name=namespace>' . "\n" .
00392                 '<option disabled value=0>(Main)</option>' . "\n" .
00393                 '<option disabled value=1>Talk</option>' . "\n" .
00394                 '<option disabled value=2>User</option>' . "\n" .
00395                 '<option disabled value=3>User talk</option>' . "\n" .
00396                 '<option disabled value=4>MyWiki</option>' . "\n" .
00397                 '<option value=5>MyWiki Talk</option>' . "\n" .
00398                 '<option value=6>File</option>' . "\n" .
00399                 '<option value=7>File talk</option>' . "\n" .
00400                 '<option value=8>MediaWiki</option>' . "\n" .
00401                 '<option value=9>MediaWiki talk</option>' . "\n" .
00402                 '<option value=10>Template</option>' . "\n" .
00403                 '<option value=11>Template talk</option>' . "\n" .
00404                 '<option value=14>Category</option>' . "\n" .
00405                 '<option value=15>Category talk</option>' . "\n" .
00406                 '<option value=100>Custom</option>' . "\n" .
00407                 '<option value=101>Custom talk</option>' . "\n" .
00408                 '</select>',
00409             Html::namespaceSelector( array(
00410                 'disable' => array( 0, 1, 2, 3, 4 )
00411             ) ),
00412             'Namespace selector namespace disabling'
00413         );
00414     }
00415 
00419     public function testHtmlElementAcceptsNewHtml5TypesInHtml5Mode( $HTML5InputType ) {
00420         $this->assertEquals(
00421             '<input type=' . $HTML5InputType . '>',
00422             Html::element( 'input', array( 'type' => $HTML5InputType ) ),
00423             'In HTML5, HTML::element() should accept type="' . $HTML5InputType . '"'
00424         );
00425     }
00426 
00431     public static function provideHtml5InputTypes() {
00432         $types = array(
00433             'datetime',
00434             'datetime-local',
00435             'date',
00436             'month',
00437             'time',
00438             'week',
00439             'number',
00440             'range',
00441             'email',
00442             'url',
00443             'search',
00444             'tel',
00445             'color',
00446         );
00447         $cases = array();
00448         foreach ( $types as $type ) {
00449             $cases[] = array( $type );
00450         }
00451 
00452         return $cases;
00453     }
00454 
00460     public function testDropDefaults( $expected, $element, $attribs, $message = '' ) {
00461         $this->assertEquals( $expected, Html::element( $element, $attribs ), $message );
00462     }
00463 
00464     public static function provideElementsWithAttributesHavingDefaultValues() {
00465         # Use cases in a concise format:
00466         # <expected>, <element name>, <array of attributes> [, <message>]
00467         # Will be mapped to Html::element()
00468         $cases = array();
00469 
00470         ### Generic cases, match $attribDefault static array
00471         $cases[] = array( '<area>',
00472             'area', array( 'shape' => 'rect' )
00473         );
00474 
00475         $cases[] = array( '<button type=submit></button>',
00476             'button', array( 'formaction' => 'GET' )
00477         );
00478         $cases[] = array( '<button type=submit></button>',
00479             'button', array( 'formenctype' => 'application/x-www-form-urlencoded' )
00480         );
00481 
00482         $cases[] = array( '<canvas></canvas>',
00483             'canvas', array( 'height' => '150' )
00484         );
00485         $cases[] = array( '<canvas></canvas>',
00486             'canvas', array( 'width' => '300' )
00487         );
00488         # Also check with numeric values
00489         $cases[] = array( '<canvas></canvas>',
00490             'canvas', array( 'height' => 150 )
00491         );
00492         $cases[] = array( '<canvas></canvas>',
00493             'canvas', array( 'width' => 300 )
00494         );
00495 
00496         $cases[] = array( '<command>',
00497             'command', array( 'type' => 'command' )
00498         );
00499 
00500         $cases[] = array( '<form></form>',
00501             'form', array( 'action' => 'GET' )
00502         );
00503         $cases[] = array( '<form></form>',
00504             'form', array( 'autocomplete' => 'on' )
00505         );
00506         $cases[] = array( '<form></form>',
00507             'form', array( 'enctype' => 'application/x-www-form-urlencoded' )
00508         );
00509 
00510         $cases[] = array( '<input>',
00511             'input', array( 'formaction' => 'GET' )
00512         );
00513         $cases[] = array( '<input>',
00514             'input', array( 'type' => 'text' )
00515         );
00516 
00517         $cases[] = array( '<keygen>',
00518             'keygen', array( 'keytype' => 'rsa' )
00519         );
00520 
00521         $cases[] = array( '<link>',
00522             'link', array( 'media' => 'all' )
00523         );
00524 
00525         $cases[] = array( '<menu></menu>',
00526             'menu', array( 'type' => 'list' )
00527         );
00528 
00529         $cases[] = array( '<script></script>',
00530             'script', array( 'type' => 'text/javascript' )
00531         );
00532 
00533         $cases[] = array( '<style></style>',
00534             'style', array( 'media' => 'all' )
00535         );
00536         $cases[] = array( '<style></style>',
00537             'style', array( 'type' => 'text/css' )
00538         );
00539 
00540         $cases[] = array( '<textarea></textarea>',
00541             'textarea', array( 'wrap' => 'soft' )
00542         );
00543 
00544         ### SPECIFIC CASES
00545 
00546         # <link type="text/css">
00547         $cases[] = array( '<link>',
00548             'link', array( 'type' => 'text/css' )
00549         );
00550 
00551         # <input> specific handling
00552         $cases[] = array( '<input type=checkbox>',
00553             'input', array( 'type' => 'checkbox', 'value' => 'on' ),
00554             'Default value "on" is stripped of checkboxes',
00555         );
00556         $cases[] = array( '<input type=radio>',
00557             'input', array( 'type' => 'radio', 'value' => 'on' ),
00558             'Default value "on" is stripped of radio buttons',
00559         );
00560         $cases[] = array( '<input type=submit value=Submit>',
00561             'input', array( 'type' => 'submit', 'value' => 'Submit' ),
00562             'Default value "Submit" is kept on submit buttons (for possible l10n issues)',
00563         );
00564         $cases[] = array( '<input type=color>',
00565             'input', array( 'type' => 'color', 'value' => '' ),
00566         );
00567         $cases[] = array( '<input type=range>',
00568             'input', array( 'type' => 'range', 'value' => '' ),
00569         );
00570 
00571         # <button> specific handling
00572         # see remarks on http://msdn.microsoft.com/en-us/library/ie/ms535211%28v=vs.85%29.aspx
00573         $cases[] = array( '<button type=submit></button>',
00574             'button', array( 'type' => 'submit' ),
00575             'According to standard the default type is "submit". Depending on compatibility mode IE might use "button", instead.',
00576         );
00577 
00578         # <select> specifc handling
00579         $cases[] = array( '<select multiple></select>',
00580             'select', array( 'size' => '4', 'multiple' => true ),
00581         );
00582         # .. with numeric value
00583         $cases[] = array( '<select multiple></select>',
00584             'select', array( 'size' => 4, 'multiple' => true ),
00585         );
00586         $cases[] = array( '<select></select>',
00587             'select', array( 'size' => '1', 'multiple' => false ),
00588         );
00589         # .. with numeric value
00590         $cases[] = array( '<select></select>',
00591             'select', array( 'size' => 1, 'multiple' => false ),
00592         );
00593 
00594         # Passing an array as value
00595         $cases[] = array( '<a class="css-class-one css-class-two"></a>',
00596             'a', array( 'class' => array( 'css-class-one', 'css-class-two' ) ),
00597             "dropDefaults accepts values given as an array"
00598         );
00599 
00600         # FIXME: doDropDefault should remove defaults given in an array
00601         # Expected should be '<a></a>'
00602         $cases[] = array( '<a class=""></a>',
00603             'a', array( 'class' => array( '', '' ) ),
00604             "dropDefaults accepts values given as an array"
00605         );
00606 
00607         # Craft the Html elements
00608         $ret = array();
00609         foreach ( $cases as $case ) {
00610             $ret[] = array(
00611                 $case[0],
00612                 $case[1], $case[2],
00613                 isset( $case[3] ) ? $case[3] : ''
00614             );
00615         }
00616 
00617         return $ret;
00618     }
00619 
00620     public function testFormValidationBlacklist() {
00621         $this->assertEmpty(
00622             Html::expandAttributes( array( 'min' => 1, 'max' => 100, 'pattern' => 'abc', 'required' => true, 'step' => 2 ) ),
00623             'Blacklist form validation attributes.'
00624         );
00625         $this->assertEquals(
00626             ' step=any',
00627             Html::expandAttributes( array( 'min' => 1, 'max' => 100, 'pattern' => 'abc', 'required' => true, 'step' => 'any' ) ),
00628             'Allow special case "step=any".'
00629         );
00630     }
00631 }