setNamespaces( array( -2 => 'Media', -1 => 'Special', 0 => '', 1 => 'Talk', 2 => 'User', 3 => 'User_talk', 4 => 'MyWiki', 5 => 'MyWiki_Talk', 6 => 'File', 7 => 'File_talk', 8 => 'MediaWiki', 9 => 'MediaWiki_talk', 10 => 'Template', 11 => 'Template_talk', 14 => 'Category', 15 => 'Category_talk', 100 => 'Custom', 101 => 'Custom_talk', ) ); $this->setMwGlobals( array( 'wgLanguageCode' => $langCode, 'wgContLang' => $langObj, 'wgLang' => $langObj, 'wgWellFormedXml' => false, ) ); } /** * @covers Html::element */ public function testElementBasics() { $this->assertEquals( '', Html::element( 'img', null, '' ), 'No close tag for short-tag elements' ); $this->assertEquals( '', Html::element( 'element', null, null ), 'Close tag for empty element (null, null)' ); $this->assertEquals( '', Html::element( 'element', array(), '' ), 'Close tag for empty element (array, string)' ); $this->setMwGlobals( 'wgWellFormedXml', true ); $this->assertEquals( '', Html::element( 'img', null, '' ), 'Self-closing tag for short-tag elements (wgWellFormedXml = true)' ); } public function dataXmlMimeType() { return array( // ( $mimetype, $isXmlMimeType ) # HTML is not an XML MimeType array( 'text/html', false ), # XML is an XML MimeType array( 'text/xml', true ), array( 'application/xml', true ), # XHTML is an XML MimeType array( 'application/xhtml+xml', true ), # Make sure other +xml MimeTypes are supported # SVG is another random MimeType even though we don't use it array( 'image/svg+xml', true ), # Complete random other MimeTypes are not XML array( 'text/plain', false ), ); } /** * @dataProvider dataXmlMimeType * @covers Html::isXmlMimeType */ public function testXmlMimeType( $mimetype, $isXmlMimeType ) { $this->assertEquals( $isXmlMimeType, Html::isXmlMimeType( $mimetype ) ); } /** * @covers HTML::expandAttributes */ public function testExpandAttributesSkipsNullAndFalse() { ### EMPTY ######## $this->assertEmpty( Html::expandAttributes( array( 'foo' => null ) ), 'skip keys with null value' ); $this->assertEmpty( Html::expandAttributes( array( 'foo' => false ) ), 'skip keys with false value' ); $this->assertEquals( ' foo=""', Html::expandAttributes( array( 'foo' => '' ) ), 'keep keys with an empty string' ); } /** * @covers HTML::expandAttributes */ public function testExpandAttributesForBooleans() { $this->assertEquals( '', Html::expandAttributes( array( 'selected' => false ) ), 'Boolean attributes do not generates output when value is false' ); $this->assertEquals( '', Html::expandAttributes( array( 'selected' => null ) ), 'Boolean attributes do not generates output when value is null' ); $this->assertEquals( ' selected', Html::expandAttributes( array( 'selected' => true ) ), 'Boolean attributes have no value when value is true' ); $this->assertEquals( ' selected', Html::expandAttributes( array( 'selected' ) ), 'Boolean attributes have no value when value is true (passed as numerical array)' ); $this->setMwGlobals( 'wgWellFormedXml', true ); $this->assertEquals( ' selected=""', Html::expandAttributes( array( 'selected' => true ) ), 'Boolean attributes have empty string value when value is true (wgWellFormedXml)' ); } /** * @covers HTML::expandAttributes */ public function testExpandAttributesForNumbers() { $this->assertEquals( ' value=1', Html::expandAttributes( array( 'value' => 1 ) ), 'Integer value is cast to a string' ); $this->assertEquals( ' value=1.1', Html::expandAttributes( array( 'value' => 1.1 ) ), 'Float value is cast to a string' ); } /** * @covers HTML::expandAttributes */ public function testExpandAttributesForObjects() { $this->assertEquals( ' value=stringValue', Html::expandAttributes( array( 'value' => new HtmlTestValue() ) ), 'Object value is converted to a string' ); } /** * Test for Html::expandAttributes() * Please note it output a string prefixed with a space! * @covers Html::expandAttributes */ public function testExpandAttributesVariousExpansions() { ### NOT EMPTY #### $this->assertEquals( ' empty_string=""', Html::expandAttributes( array( 'empty_string' => '' ) ), 'Empty string is always quoted' ); $this->assertEquals( ' key=value', Html::expandAttributes( array( 'key' => 'value' ) ), 'Simple string value needs no quotes' ); $this->assertEquals( ' one=1', Html::expandAttributes( array( 'one' => 1 ) ), 'Number 1 value needs no quotes' ); $this->assertEquals( ' zero=0', Html::expandAttributes( array( 'zero' => 0 ) ), 'Number 0 value needs no quotes' ); $this->setMwGlobals( 'wgWellFormedXml', true ); $this->assertEquals( ' empty_string=""', Html::expandAttributes( array( 'empty_string' => '' ) ), 'Attribute values are always quoted (wgWellFormedXml): Empty string' ); $this->assertEquals( ' key="value"', Html::expandAttributes( array( 'key' => 'value' ) ), 'Attribute values are always quoted (wgWellFormedXml): Simple string' ); $this->assertEquals( ' one="1"', Html::expandAttributes( array( 'one' => 1 ) ), 'Attribute values are always quoted (wgWellFormedXml): Number 1' ); $this->assertEquals( ' zero="0"', Html::expandAttributes( array( 'zero' => 0 ) ), 'Attribute values are always quoted (wgWellFormedXml): Number 0' ); } /** * Html::expandAttributes has special features for HTML * attributes that use space separated lists and also * allows arrays to be used as values. * @covers Html::expandAttributes */ public function testExpandAttributesListValueAttributes() { ### STRING VALUES $this->assertEquals( ' class="redundant spaces here"', Html::expandAttributes( array( 'class' => ' redundant spaces here ' ) ), 'Normalization should strip redundant spaces' ); $this->assertEquals( ' class="foo bar"', Html::expandAttributes( array( 'class' => 'foo bar foo bar bar' ) ), 'Normalization should remove duplicates in string-lists' ); ### "EMPTY" ARRAY VALUES $this->assertEquals( ' class=""', Html::expandAttributes( array( 'class' => array() ) ), 'Value with an empty array' ); $this->assertEquals( ' class=""', Html::expandAttributes( array( 'class' => array( null, '', ' ', ' ' ) ) ), 'Array with null, empty string and spaces' ); ### NON-EMPTY ARRAY VALUES $this->assertEquals( ' class="foo bar"', Html::expandAttributes( array( 'class' => array( 'foo', 'bar', 'foo', 'bar', 'bar', ) ) ), 'Normalization should remove duplicates in the array' ); $this->assertEquals( ' class="foo bar"', Html::expandAttributes( array( 'class' => array( 'foo bar', 'bar foo', 'foo', 'bar bar', ) ) ), 'Normalization should remove duplicates in string-lists in the array' ); } /** * Test feature added by r96188, let pass attributes values as * a PHP array. Restricted to class,rel, accesskey. * @covers Html::expandAttributes */ public function testExpandAttributesSpaceSeparatedAttributesWithBoolean() { $this->assertEquals( ' class="booltrue one"', Html::expandAttributes( array( 'class' => array( 'booltrue' => true, 'one' => 1, # Method use isset() internally, make sure we do discard # attributes values which have been assigned well known values 'emptystring' => '', 'boolfalse' => false, 'zero' => 0, 'null' => null, ) ) ) ); } /** * How do we handle duplicate keys in HTML attributes expansion? * We could pass a "class" the values: 'GREEN' and array( 'GREEN' => false ) * The later will take precedence. * * Feature added by r96188 * @covers Html::expandAttributes */ public function testValueIsAuthoritativeInSpaceSeparatedAttributesArrays() { $this->assertEquals( ' class=""', Html::expandAttributes( array( 'class' => array( 'GREEN', 'GREEN' => false, 'GREEN', ) ) ) ); } /** * @covers Html::expandAttributes * @expectedException MWException */ public function testExpandAttributes_ArrayOnNonListValueAttribute_ThrowsException() { // Real-life test case found in the Popups extension (see Gerrit cf0fd64), // when used with an outdated BetaFeatures extension (see Gerrit deda1e7) Html::expandAttributes( array( 'src' => array( 'ltr' => 'ltr.svg', 'rtl' => 'rtl.svg' ) ) ); } /** * @covers Html::namespaceSelector */ public function testNamespaceSelector() { $this->assertEquals( '', Html::namespaceSelector(), 'Basic namespace selector without custom options' ); $this->assertEquals( ' ' . '', Html::namespaceSelector( array( 'selected' => '2', 'all' => 'all', 'label' => 'Select a namespace:' ), array( 'name' => 'wpNamespace', 'id' => 'mw-test-namespace' ) ), 'Basic namespace selector with custom values' ); $this->assertEquals( ' ' . '', Html::namespaceSelector( array( 'label' => 'Select a namespace:' ) ), 'Basic namespace selector with a custom label but no id attribtue for the ' . "\n" . '' . "\n" . '' . "\n" . '' . "\n" . '' . "\n" . '' . "\n" . '' . "\n" . '' . "\n" . '' . "\n" . '' . "\n" . '' . "\n" . '' . "\n" . '', Html::namespaceSelector( array( 'exclude' => array( 0, 1, 3, 100, 101 ) ) ), 'Namespace selector namespace filtering.' ); } public function testCanDisableANamespaces() { $this->assertEquals( '', Html::namespaceSelector( array( 'disable' => array( 0, 1, 2, 3, 4 ) ) ), 'Namespace selector namespace disabling' ); } /** * @dataProvider provideHtml5InputTypes * @covers Html::element */ public function testHtmlElementAcceptsNewHtml5TypesInHtml5Mode( $HTML5InputType ) { $this->assertEquals( '', Html::element( 'input', array( 'type' => $HTML5InputType ) ), 'In HTML5, HTML::element() should accept type="' . $HTML5InputType . '"' ); } /** * List of input element types values introduced by HTML5 * Full list at http://www.w3.org/TR/html-markup/input.html */ public static function provideHtml5InputTypes() { $types = array( 'datetime', 'datetime-local', 'date', 'month', 'time', 'week', 'number', 'range', 'email', 'url', 'search', 'tel', 'color', ); $cases = array(); foreach ( $types as $type ) { $cases[] = array( $type ); } return $cases; } /** * Test out Html::element drops or enforces default value * @covers Html::dropDefaults * @dataProvider provideElementsWithAttributesHavingDefaultValues */ public function testDropDefaults( $expected, $element, $attribs, $message = '' ) { $this->assertEquals( $expected, Html::element( $element, $attribs ), $message ); } public static function provideElementsWithAttributesHavingDefaultValues() { # Use cases in a concise format: # , , [, ] # Will be mapped to Html::element() $cases = array(); ### Generic cases, match $attribDefault static array $cases[] = array( '', 'area', array( 'shape' => 'rect' ) ); $cases[] = array( '', 'button', array( 'formaction' => 'GET' ) ); $cases[] = array( '', 'button', array( 'formenctype' => 'application/x-www-form-urlencoded' ) ); $cases[] = array( '', 'canvas', array( 'height' => '150' ) ); $cases[] = array( '', 'canvas', array( 'width' => '300' ) ); # Also check with numeric values $cases[] = array( '', 'canvas', array( 'height' => 150 ) ); $cases[] = array( '', 'canvas', array( 'width' => 300 ) ); $cases[] = array( '', 'command', array( 'type' => 'command' ) ); $cases[] = array( '
', 'form', array( 'action' => 'GET' ) ); $cases[] = array( '
', 'form', array( 'autocomplete' => 'on' ) ); $cases[] = array( '
', 'form', array( 'enctype' => 'application/x-www-form-urlencoded' ) ); $cases[] = array( '', 'input', array( 'formaction' => 'GET' ) ); $cases[] = array( '', 'input', array( 'type' => 'text' ) ); $cases[] = array( '', 'keygen', array( 'keytype' => 'rsa' ) ); $cases[] = array( '', 'link', array( 'media' => 'all' ) ); $cases[] = array( '', 'menu', array( 'type' => 'list' ) ); $cases[] = array( '', 'script', array( 'type' => 'text/javascript' ) ); $cases[] = array( '', 'style', array( 'media' => 'all' ) ); $cases[] = array( '', 'style', array( 'type' => 'text/css' ) ); $cases[] = array( '', 'textarea', array( 'wrap' => 'soft' ) ); ### SPECIFIC CASES # $cases[] = array( '', 'link', array( 'type' => 'text/css' ) ); # specific handling $cases[] = array( '', 'input', array( 'type' => 'checkbox', 'value' => 'on' ), 'Default value "on" is stripped of checkboxes', ); $cases[] = array( '', 'input', array( 'type' => 'radio', 'value' => 'on' ), 'Default value "on" is stripped of radio buttons', ); $cases[] = array( '', 'input', array( 'type' => 'submit', 'value' => 'Submit' ), 'Default value "Submit" is kept on submit buttons (for possible l10n issues)', ); $cases[] = array( '', 'input', array( 'type' => 'color', 'value' => '' ), ); $cases[] = array( '', 'input', array( 'type' => 'range', 'value' => '' ), ); # ', 'button', array( 'type' => 'submit' ), 'According to standard the default type is "submit". ' . 'Depending on compatibility mode IE might use "button", instead.', ); # ', 'select', array( 'size' => '4', 'multiple' => true ), ); # .. with numeric value $cases[] = array( '', 'select', array( 'size' => 4, 'multiple' => true ), ); $cases[] = array( '', 'select', array( 'size' => '1', 'multiple' => false ), ); # .. with numeric value $cases[] = array( '', 'select', array( 'size' => 1, 'multiple' => false ), ); # Passing an array as value $cases[] = array( '', 'a', array( 'class' => array( 'css-class-one', 'css-class-two' ) ), "dropDefaults accepts values given as an array" ); # FIXME: doDropDefault should remove defaults given in an array # Expected should be '' $cases[] = array( '', 'a', array( 'class' => array( '', '' ) ), "dropDefaults accepts values given as an array" ); # Craft the Html elements $ret = array(); foreach ( $cases as $case ) { $ret[] = array( $case[0], $case[1], $case[2], isset( $case[3] ) ? $case[3] : '' ); } return $ret; } /** * @covers Html::expandAttributes */ public function testFormValidationBlacklist() { $this->assertEmpty( Html::expandAttributes( array( 'min' => 1, 'max' => 100, 'pattern' => 'abc', 'required' => true, 'step' => 2 ) ), 'Blacklist form validation attributes.' ); $this->assertEquals( ' step=any', Html::expandAttributes( array( 'min' => 1, 'max' => 100, 'pattern' => 'abc', 'required' => true, 'step' => 'any' ), 'Allow special case "step=any".' ) ); } public function testWrapperInput() { $this->assertEquals( '', Html::input( 'testname', 'testval', 'radio' ), 'Input wrapper with type and value.' ); $this->assertEquals( '', Html::input( 'testname' ), 'Input wrapper with all default values.' ); } public function testWrapperCheck() { $this->assertEquals( '', Html::check( 'testname' ), 'Checkbox wrapper unchecked.' ); $this->assertEquals( '', Html::check( 'testname', true ), 'Checkbox wrapper checked.' ); $this->assertEquals( '', Html::check( 'testname', false, array( 'value' => 'testval' ) ), 'Checkbox wrapper with a value override.' ); } public function testWrapperRadio() { $this->assertEquals( '', Html::radio( 'testname' ), 'Radio wrapper unchecked.' ); $this->assertEquals( '', Html::radio( 'testname', true ), 'Radio wrapper checked.' ); $this->assertEquals( '', Html::radio( 'testname', false, array( 'value' => 'testval' ) ), 'Radio wrapper with a value override.' ); } public function testWrapperLabel() { $this->assertEquals( '', Html::label( 'testlabel', 'testid' ), 'Label wrapper' ); } public static function provideSrcSetImages() { return array( array( array(), '', 'when there are no images, return empty string' ), array( array( '1x' => '1x.png', '1.5x' => '1_5x.png', '2x' => '2x.png' ), '1x.png 1x, 1_5x.png 1.5x, 2x.png 2x', 'pixel depth keys may include a trailing "x"' ), array( array( '1' => '1x.png', '1.5' => '1_5x.png', '2' => '2x.png' ), '1x.png 1x, 1_5x.png 1.5x, 2x.png 2x', 'pixel depth keys may omit a trailing "x"' ), ); } /** * @dataProvider provideSrcSetImages * @covers Html::srcSet */ public function testSrcSet( $images, $expected, $message ) { $this->assertEquals( Html::srcSet( $images ), $expected, $message ); } } class HtmlTestValue { function __toString() { return 'stringValue'; } }