4 private $restoreWarnings;
6 protected function setUp() {
10 'wgUseMediaWikiUIEverywhere' =>
false,
18 $contLangObj->setNamespaces( [
30 9 =>
'MediaWiki_talk',
32 11 =>
'Template_talk',
34 15 =>
'Category_talk',
41 $userLangObj->setNamespaces( [
47 3 =>
"Usuario discusión",
49 5 =>
"Wiki discusión",
51 7 =>
"Archivo discusión",
53 9 =>
"MediaWiki discusión",
55 11 =>
"Plantilla discusión",
57 13 =>
"Ayuda discusión",
59 15 =>
"Categoría discusión",
60 100 =>
"Personalizado",
61 101 =>
"Personalizado discusión",
65 $this->restoreWarnings =
false;
71 if ( $this->restoreWarnings ) {
72 $this->restoreWarnings =
false;
73 Wikimedia\restoreWarnings();
84 public function testOpenElement() {
85 Html::openElement(
'span id="x"' );
94 public function testElementBasics() {
97 Html::element(
'img',
null,
'' ),
98 'Self-closing tag for short-tag elements'
102 '<element></element>',
103 Html::element(
'element',
null,
null ),
104 'Close tag for empty element (null, null)'
108 '<element></element>',
109 Html::element(
'element', [],
'' ),
110 'Close tag for empty element (array, string)'
114 public function dataXmlMimeType() {
117 # HTML is not an XML MimeType
118 [
'text/html',
false ],
119 # XML is an XML MimeType
120 [
'text/xml',
true ],
121 [
'application/xml',
true ],
122 # XHTML is an XML MimeType
123 [
'application/xhtml+xml',
true ],
124 # Make sure other +xml MimeTypes are supported
125 # SVG is another random MimeType even though we don't use it
126 [
'image/svg+xml',
true ],
127 # Complete random other MimeTypes are not XML
128 [
'text/plain',
false ],
136 public function testXmlMimeType( $mimetype, $isXmlMimeType ) {
137 $this->assertEquals( $isXmlMimeType, Html::isXmlMimeType( $mimetype ) );
143 public function testExpandAttributesSkipsNullAndFalse() {
146 Html::expandAttributes( [
'foo' =>
null ] ),
147 'skip keys with null value'
150 Html::expandAttributes( [
'foo' =>
false ] ),
151 'skip keys with false value'
155 Html::expandAttributes( [
'foo' =>
'' ] ),
156 'keep keys with an empty string'
163 public function testExpandAttributesForBooleans() {
166 Html::expandAttributes( [
'selected' =>
false ] ),
167 'Boolean attributes do not generates output when value is false'
171 Html::expandAttributes( [
'selected' =>
null ] ),
172 'Boolean attributes do not generates output when value is null'
177 Html::expandAttributes( [
'selected' =>
true ] ),
178 'Boolean attributes have no value when value is true'
182 Html::expandAttributes( [
'selected' ] ),
183 'Boolean attributes have no value when value is true (passed as numerical array)'
190 public function testExpandAttributesForNumbers() {
193 Html::expandAttributes( [
'value' => 1 ] ),
194 'Integer value is cast to a string'
198 Html::expandAttributes( [
'value' => 1.1 ] ),
199 'Float value is cast to a string'
206 public function testExpandAttributesForObjects() {
208 ' value="stringValue"',
209 Html::expandAttributes( [
'value' =>
new HtmlTestValue() ] ),
210 'Object value is converted to a string'
218 public function testExpandAttributesVariousExpansions() {
222 Html::expandAttributes( [
'empty_string' =>
'' ] ),
223 'Empty string is always quoted'
227 Html::expandAttributes( [
'key' =>
'value' ] ),
228 'Simple string value needs no quotes'
232 Html::expandAttributes( [
'one' => 1 ] ),
233 'Number 1 value needs no quotes'
237 Html::expandAttributes( [
'zero' => 0 ] ),
238 'Number 0 value needs no quotes'
248 public function testExpandAttributesListValueAttributes() {
251 ' class="redundant spaces here"',
252 Html::expandAttributes( [
'class' =>
' redundant spaces here ' ] ),
253 'Normalization should strip redundant spaces'
257 Html::expandAttributes( [
'class' =>
'foo bar foo bar bar' ] ),
258 'Normalization should remove duplicates in string-lists'
260 # ## "EMPTY" ARRAY VALUES
263 Html::expandAttributes( [
'class' => [] ] ),
264 'Value with an empty array'
268 Html::expandAttributes( [
'class' => [
null,
'',
' ',
' ' ] ] ),
269 'Array with null, empty string and spaces'
271 # ## NON-EMPTY ARRAY VALUES
274 Html::expandAttributes( [
'class' => [
281 'Normalization should remove duplicates in the array'
285 Html::expandAttributes( [
'class' => [
291 'Normalization should remove duplicates in string-lists in the array'
300 public function testExpandAttributesSpaceSeparatedAttributesWithBoolean() {
302 ' class="booltrue one"',
303 Html::expandAttributes( [
'class' => [
307 # Method
use isset() internally, make sure we
do discard
310 'boolfalse' =>
false,
325 public function testValueIsAuthoritativeInSpaceSeparatedAttributesArrays() {
328 Html::expandAttributes( [
'class' => [
340 public function testExpandAttributes_ArrayOnNonListValueAttribute_ThrowsException() {
343 Html::expandAttributes( [
355 public function testNamespaceSelector() {
357 '<select id="namespace" name="namespace">' .
"\n" .
358 '<option value="0">(Principal)</option>' .
"\n" .
359 '<option value="1">Talk</option>' .
"\n" .
360 '<option value="2">User</option>' .
"\n" .
361 '<option value="3">User talk</option>' .
"\n" .
362 '<option value="4">MyWiki</option>' .
"\n" .
363 '<option value="5">MyWiki Talk</option>' .
"\n" .
364 '<option value="6">File</option>' .
"\n" .
365 '<option value="7">File talk</option>' .
"\n" .
366 '<option value="8">MediaWiki</option>' .
"\n" .
367 '<option value="9">MediaWiki talk</option>' .
"\n" .
368 '<option value="10">Template</option>' .
"\n" .
369 '<option value="11">Template talk</option>' .
"\n" .
370 '<option value="14">Category</option>' .
"\n" .
371 '<option value="15">Category talk</option>' .
"\n" .
372 '<option value="100">Custom</option>' .
"\n" .
373 '<option value="101">Custom talk</option>' .
"\n" .
375 Html::namespaceSelector(),
376 'Basic namespace selector without custom options'
380 '<label for="mw-test-namespace">Select a namespace:</label>' .
"\u{00A0}" .
381 '<select id="mw-test-namespace" name="wpNamespace">' .
"\n" .
382 '<option value="all">todos</option>' .
"\n" .
383 '<option value="0">(Principal)</option>' .
"\n" .
384 '<option value="1">Talk</option>' .
"\n" .
385 '<option value="2" selected="">User</option>' .
"\n" .
386 '<option value="3">User talk</option>' .
"\n" .
387 '<option value="4">MyWiki</option>' .
"\n" .
388 '<option value="5">MyWiki Talk</option>' .
"\n" .
389 '<option value="6">File</option>' .
"\n" .
390 '<option value="7">File talk</option>' .
"\n" .
391 '<option value="8">MediaWiki</option>' .
"\n" .
392 '<option value="9">MediaWiki talk</option>' .
"\n" .
393 '<option value="10">Template</option>' .
"\n" .
394 '<option value="11">Template talk</option>' .
"\n" .
395 '<option value="14">Category</option>' .
"\n" .
396 '<option value="15">Category talk</option>' .
"\n" .
397 '<option value="100">Custom</option>' .
"\n" .
398 '<option value="101">Custom talk</option>' .
"\n" .
400 Html::namespaceSelector(
401 [
'selected' =>
'2',
'all' =>
'all',
'label' =>
'Select a namespace:' ],
402 [
'name' =>
'wpNamespace',
'id' =>
'mw-test-namespace' ]
404 'Basic namespace selector with custom values'
408 '<label for="namespace">Select a namespace:</label>' .
"\u{00A0}" .
409 '<select id="namespace" name="namespace">' .
"\n" .
410 '<option value="0">(Principal)</option>' .
"\n" .
411 '<option value="1">Talk</option>' .
"\n" .
412 '<option value="2">User</option>' .
"\n" .
413 '<option value="3">User talk</option>' .
"\n" .
414 '<option value="4">MyWiki</option>' .
"\n" .
415 '<option value="5">MyWiki Talk</option>' .
"\n" .
416 '<option value="6">File</option>' .
"\n" .
417 '<option value="7">File talk</option>' .
"\n" .
418 '<option value="8">MediaWiki</option>' .
"\n" .
419 '<option value="9">MediaWiki talk</option>' .
"\n" .
420 '<option value="10">Template</option>' .
"\n" .
421 '<option value="11">Template talk</option>' .
"\n" .
422 '<option value="14">Category</option>' .
"\n" .
423 '<option value="15">Category talk</option>' .
"\n" .
424 '<option value="100">Custom</option>' .
"\n" .
425 '<option value="101">Custom talk</option>' .
"\n" .
427 Html::namespaceSelector(
428 [
'label' =>
'Select a namespace:' ]
430 'Basic namespace selector with a custom label but no id attribtue for the <select>'
434 '<select id="namespace" name="namespace">' .
"\n" .
435 '<option value="0">(Principal)</option>' .
"\n" .
436 '<option value="1">Discusión</option>' .
"\n" .
437 '<option value="2">Usuario</option>' .
"\n" .
438 '<option value="3">Usuario discusión</option>' .
"\n" .
439 '<option value="4">Wiki</option>' .
"\n" .
440 '<option value="5">Wiki discusión</option>' .
"\n" .
441 '<option value="6">Archivo</option>' .
"\n" .
442 '<option value="7">Archivo discusión</option>' .
"\n" .
443 '<option value="8">MediaWiki</option>' .
"\n" .
444 '<option value="9">MediaWiki discusión</option>' .
"\n" .
445 '<option value="10">Plantilla</option>' .
"\n" .
446 '<option value="11">Plantilla discusión</option>' .
"\n" .
447 '<option value="12">Ayuda</option>' .
"\n" .
448 '<option value="13">Ayuda discusión</option>' .
"\n" .
449 '<option value="14">Categoría</option>' .
"\n" .
450 '<option value="15">Categoría discusión</option>' .
"\n" .
451 '<option value="100">Personalizado</option>' .
"\n" .
452 '<option value="101">Personalizado discusión</option>' .
"\n" .
454 Html::namespaceSelector(
455 [
'in-user-lang' =>
true ]
457 'Basic namespace selector in user language'
464 public function testCanFilterOutNamespaces() {
466 '<select id="namespace" name="namespace">' .
"\n" .
467 '<option value="2">User</option>' .
"\n" .
468 '<option value="4">MyWiki</option>' .
"\n" .
469 '<option value="5">MyWiki Talk</option>' .
"\n" .
470 '<option value="6">File</option>' .
"\n" .
471 '<option value="7">File talk</option>' .
"\n" .
472 '<option value="8">MediaWiki</option>' .
"\n" .
473 '<option value="9">MediaWiki talk</option>' .
"\n" .
474 '<option value="10">Template</option>' .
"\n" .
475 '<option value="11">Template talk</option>' .
"\n" .
476 '<option value="14">Category</option>' .
"\n" .
477 '<option value="15">Category talk</option>' .
"\n" .
479 Html::namespaceSelector(
480 [
'exclude' => [ 0, 1, 3, 100, 101 ] ]
482 'Namespace selector namespace filtering.'
489 public function testCanDisableANamespaces() {
491 '<select id="namespace" name="namespace">' .
"\n" .
492 '<option disabled="" value="0">(Principal)</option>' .
"\n" .
493 '<option disabled="" value="1">Talk</option>' .
"\n" .
494 '<option disabled="" value="2">User</option>' .
"\n" .
495 '<option disabled="" value="3">User talk</option>' .
"\n" .
496 '<option disabled="" value="4">MyWiki</option>' .
"\n" .
497 '<option value="5">MyWiki Talk</option>' .
"\n" .
498 '<option value="6">File</option>' .
"\n" .
499 '<option value="7">File talk</option>' .
"\n" .
500 '<option value="8">MediaWiki</option>' .
"\n" .
501 '<option value="9">MediaWiki talk</option>' .
"\n" .
502 '<option value="10">Template</option>' .
"\n" .
503 '<option value="11">Template talk</option>' .
"\n" .
504 '<option value="14">Category</option>' .
"\n" .
505 '<option value="15">Category talk</option>' .
"\n" .
506 '<option value="100">Custom</option>' .
"\n" .
507 '<option value="101">Custom talk</option>' .
"\n" .
509 Html::namespaceSelector( [
510 'disable' => [ 0, 1, 2, 3, 4 ]
512 'Namespace selector namespace disabling'
520 public function testHtmlElementAcceptsNewHtml5TypesInHtml5Mode( $HTML5InputType ) {
522 '<input type="' . $HTML5InputType .
'"/>',
523 Html::element(
'input', [
'type' => $HTML5InputType ] ),
524 'In HTML5, Html::element() should accept type="' . $HTML5InputType .
'"'
532 public function testWarningBox() {
534 Html::warningBox(
'warn' ),
535 '<div class="warningbox">warn</div>'
543 public function testErrorBox() {
545 Html::errorBox(
'err' ),
546 '<div class="errorbox">err</div>'
549 Html::errorBox(
'err',
'heading' ),
550 '<div class="errorbox"><h2>heading</h2>err</div>'
553 Html::errorBox(
'err',
'0' ),
554 '<div class="errorbox"><h2>0</h2>err</div>'
562 public function testSuccessBox() {
564 Html::successBox(
'great' ),
565 '<div class="successbox">great</div>'
568 Html::successBox(
'<script>beware no escaping!</script>' ),
569 '<div class="successbox"><script>beware no escaping!</script></div>'
577 public static function provideHtml5InputTypes() {
595 $cases[] = [
$type ];
606 public function testDropDefaults( $expected, $element,
$attribs, $message =
'' ) {
607 $this->assertEquals( $expected, Html::element( $element,
$attribs ), $message );
610 public static function provideElementsWithAttributesHavingDefaultValues() {
611 # Use cases in a concise format:
612 # <expected>, <element name>, <array of attributes> [, <message>]
613 # Will be mapped to Html::element()
616 # ## Generic cases, match $attribDefault static array
617 $cases[] = [
'<area/>',
618 'area', [
'shape' =>
'rect' ]
621 $cases[] = [
'<button type="submit"></button>',
622 'button', [
'formaction' =>
'GET' ]
624 $cases[] = [
'<button type="submit"></button>',
625 'button', [
'formenctype' =>
'application/x-www-form-urlencoded' ]
628 $cases[] = [
'<canvas></canvas>',
629 'canvas', [
'height' =>
'150' ]
631 $cases[] = [
'<canvas></canvas>',
632 'canvas', [
'width' =>
'300' ]
634 # Also check with numeric values
635 $cases[] = [
'<canvas></canvas>',
636 'canvas', [
'height' => 150 ]
638 $cases[] = [
'<canvas></canvas>',
639 'canvas', [
'width' => 300 ]
642 $cases[] = [
'<form></form>',
643 'form', [
'action' =>
'GET' ]
645 $cases[] = [
'<form></form>',
646 'form', [
'autocomplete' =>
'on' ]
648 $cases[] = [
'<form></form>',
649 'form', [
'enctype' =>
'application/x-www-form-urlencoded' ]
652 $cases[] = [
'<input/>',
653 'input', [
'formaction' =>
'GET' ]
655 $cases[] = [
'<input/>',
656 'input', [
'type' =>
'text' ]
659 $cases[] = [
'<keygen/>',
660 'keygen', [
'keytype' =>
'rsa' ]
663 $cases[] = [
'<link/>',
664 'link', [
'media' =>
'all' ]
667 $cases[] = [
'<menu></menu>',
668 'menu', [
'type' =>
'list' ]
671 $cases[] = [
'<script></script>',
672 'script', [
'type' =>
'text/javascript' ]
675 $cases[] = [
'<style></style>',
676 'style', [
'media' =>
'all' ]
678 $cases[] = [
'<style></style>',
679 'style', [
'type' =>
'text/css' ]
682 $cases[] = [
'<textarea></textarea>',
683 'textarea', [
'wrap' =>
'soft' ]
688 # <link type="text/css">
689 $cases[] = [
'<link/>',
690 'link', [
'type' =>
'text/css' ]
693 # <input> specific handling
694 $cases[] = [
'<input type="checkbox"/>',
695 'input', [
'type' =>
'checkbox',
'value' =>
'on' ],
696 'Default value "on" is stripped of checkboxes',
698 $cases[] = [
'<input type="radio"/>',
699 'input', [
'type' =>
'radio',
'value' =>
'on' ],
700 'Default value "on" is stripped of radio buttons',
702 $cases[] = [
'<input type="submit" value="Submit"/>',
703 'input', [
'type' =>
'submit',
'value' =>
'Submit' ],
704 'Default value "Submit" is kept on submit buttons (for possible l10n issues)',
706 $cases[] = [
'<input type="color"/>',
707 'input', [
'type' =>
'color',
'value' =>
'' ],
709 $cases[] = [
'<input type="range"/>',
710 'input', [
'type' =>
'range',
'value' =>
'' ],
713 # <button> specific handling
714 # see remarks on https://msdn.microsoft.com/library/ms535211(v=vs.85).aspx
715 $cases[] = [
'<button type="submit"></button>',
716 'button', [
'type' =>
'submit' ],
717 'According to standard the default type is "submit". '
718 .
'Depending on compatibility mode IE might use "button", instead.',
721 # <select> specific handling
722 $cases[] = [
'<select multiple=""></select>',
723 'select', [
'size' =>
'4',
'multiple' =>
true ],
725 # .. with numeric value
726 $cases[] = [
'<select multiple=""></select>',
727 'select', [
'size' => 4,
'multiple' =>
true ],
729 $cases[] = [
'<select></select>',
730 'select', [
'size' =>
'1',
'multiple' =>
false ],
732 # .. with numeric value
733 $cases[] = [
'<select></select>',
734 'select', [
'size' => 1,
'multiple' =>
false ],
737 # Passing an array as value
738 $cases[] = [
'<a class="css-class-one css-class-two"></a>',
739 'a', [
'class' => [
'css-class-one',
'css-class-two' ] ],
740 "dropDefaults accepts values given as an array"
743 # FIXME: doDropDefault should remove defaults given in an array
744 # Expected should be '<a></a>'
745 $cases[] = [
'<a class=""></a>',
746 'a', [
'class' => [
'',
'' ] ],
747 "dropDefaults accepts values given as an array"
750 # Craft the Html elements
752 foreach ( $cases
as $case ) {
766 public function testWrapperInput() {
768 '<input type="radio" value="testval" name="testname"/>',
769 Html::input(
'testname',
'testval',
'radio' ),
770 'Input wrapper with type and value.'
773 '<input name="testname"/>',
774 Html::input(
'testname' ),
775 'Input wrapper with all default values.'
782 public function testWrapperCheck() {
784 '<input type="checkbox" value="1" name="testname"/>',
786 'Checkbox wrapper unchecked.'
789 '<input checked="" type="checkbox" value="1" name="testname"/>',
791 'Checkbox wrapper checked.'
794 '<input type="checkbox" value="testval" name="testname"/>',
795 Html::check(
'testname',
false, [
'value' =>
'testval' ] ),
796 'Checkbox wrapper with a value override.'
803 public function testWrapperRadio() {
805 '<input type="radio" value="1" name="testname"/>',
806 Html::radio(
'testname' ),
807 'Radio wrapper unchecked.'
810 '<input checked="" type="radio" value="1" name="testname"/>',
811 Html::radio(
'testname',
true ),
812 'Radio wrapper checked.'
815 '<input type="radio" value="testval" name="testname"/>',
816 Html::radio(
'testname',
false, [
'value' =>
'testval' ] ),
817 'Radio wrapper with a value override.'
824 public function testWrapperLabel() {
826 '<label for="testid">testlabel</label>',
827 Html::label(
'testlabel',
'testid' ),
832 public static function provideSrcSetImages() {
834 [ [],
'',
'when there are no images, return empty string' ],
836 [
'1x' =>
'1x.png',
'1.5x' =>
'1_5x.png',
'2x' =>
'2x.png' ],
837 '1x.png 1x, 1_5x.png 1.5x, 2x.png 2x',
838 'pixel depth keys may include a trailing "x"'
841 [
'1' =>
'1x.png',
'1.5' =>
'1_5x.png',
'2' =>
'2x.png' ],
842 '1x.png 1x, 1_5x.png 1.5x, 2x.png 2x',
843 'pixel depth keys may omit a trailing "x"'
846 [
'1' =>
'small.png',
'1.5' =>
'large.png',
'2' =>
'large.png' ],
847 'small.png 1x, large.png 1.5x',
848 'omit larger duplicates'
851 [
'1' =>
'small.png',
'2' =>
'large.png',
'1.5' =>
'large.png' ],
852 'small.png 1x, large.png 1.5x',
853 'omit larger duplicates in irregular order'
862 public function testSrcSet( $images, $expected, $message ) {
863 $this->assertEquals( Html::srcSet( $images ), $expected, $message );
866 public static function provideInlineScript() {
873 'EXAMPLE.label("foo");',
874 '<script>EXAMPLE.label("foo");</script>'
877 'EXAMPLE.is(a && b);',
878 '<script>EXAMPLE.is(a && b);</script>'
881 'EXAMPLE.label("<a>");',
882 '<script>EXAMPLE.label("<a>");</script>'
884 'Script closing string (lower)' => [
885 'EXAMPLE.label("</script>");',
886 '<script>/* ERROR: Invalid script */</script>',
889 'Script closing with non-standard attributes (mixed)' => [
890 'EXAMPLE.label("</SCriPT and STyLE>");',
891 '<script>/* ERROR: Invalid script */</script>',
894 'HTML-comment-open and script-open' => [
899 'var a = "<!--<script>";',
900 '<script>/* ERROR: Invalid script */</script>',
910 public function testInlineScript(
$code, $expected, $error =
false ) {
912 Wikimedia\suppressWarnings();
913 $this->restoreWarnings =
true;
915 $this->assertSame( Html::inlineScript(
$code ), $expected );
919 class HtmlTestValue {
920 function __toString() {
921 return 'stringValue';