Fixed dependencies for jquery.collapsibleTabs
[lhc/web/wiklou.git] / tests / phpunit / includes / HtmlTest.php
1 <?php
2 /** tests for includes/Html.php */
3
4 class HtmlTest extends MediaWikiTestCase {
5 private static $oldLang;
6 private static $oldContLang;
7 private static $oldLanguageCode;
8 private static $oldNamespaces;
9 private static $oldHTML5;
10
11 public function setUp() {
12 global $wgLang, $wgContLang, $wgLanguageCode, $wgHtml5;
13
14 // Save globals
15 self::$oldLang = $wgLang;
16 self::$oldContLang = $wgContLang;
17 self::$oldNamespaces = $wgContLang->getNamespaces();
18 self::$oldLanguageCode = $wgLanguageCode;
19 self::$oldHTML5 = $wgHtml5;
20
21 $wgLanguageCode = 'en';
22 $wgContLang = $wgLang = Language::factory( $wgLanguageCode );
23
24 // Hardcode namespaces during test runs,
25 // so that html output based on existing namespaces
26 // can be properly evaluated.
27 $wgContLang->setNamespaces( array(
28 -2 => 'Media',
29 -1 => 'Special',
30 0 => '',
31 1 => 'Talk',
32 2 => 'User',
33 3 => 'User_talk',
34 4 => 'MyWiki',
35 5 => 'MyWiki_Talk',
36 6 => 'File',
37 7 => 'File_talk',
38 8 => 'MediaWiki',
39 9 => 'MediaWiki_talk',
40 10 => 'Template',
41 11 => 'Template_talk',
42 14 => 'Category',
43 15 => 'Category_talk',
44 100 => 'Custom',
45 101 => 'Custom_talk',
46 ) );
47 }
48
49 public function tearDown() {
50 global $wgLang, $wgContLang, $wgLanguageCode, $wgHtml5;
51
52 // Restore globals
53 $wgContLang->setNamespaces( self::$oldNamespaces );
54 $wgLang = self::$oldLang;
55 $wgContLang = self::$oldContLang;
56 $wgLanguageCode = self::$oldLanguageCode;
57 $wgHtml5 = self::$oldHTML5;
58 }
59
60 /**
61 * Wrapper to easily set $wgHtml5 = true.
62 * Original value will be restored after test completion.
63 * @todo Move to MediaWikiTestCase
64 */
65 public function enableHTML5() {
66 global $wgHtml5;
67 $wgHtml5 = true;
68 }
69 /**
70 * Wrapper to easily set $wgHtml5 = false
71 * Original value will be restored after test completion.
72 * @todo Move to MediaWikiTestCase
73 */
74 public function disableHTML5() {
75 global $wgHtml5;
76 $wgHtml5 = false;
77 }
78
79 public function testExpandAttributesSkipsNullAndFalse() {
80
81 ### EMPTY ########
82 $this->AssertEmpty(
83 Html::expandAttributes( array( 'foo' => null ) ),
84 'skip keys with null value'
85 );
86 $this->AssertEmpty(
87 Html::expandAttributes( array( 'foo' => false ) ),
88 'skip keys with false value'
89 );
90 $this->AssertNotEmpty(
91 Html::expandAttributes( array( 'foo' => '' ) ),
92 'keep keys with an empty string'
93 );
94 }
95
96 public function testExpandAttributesForBooleans() {
97 global $wgHtml5;
98 $this->AssertEquals(
99 '',
100 Html::expandAttributes( array( 'selected' => false ) ),
101 'Boolean attributes do not generates output when value is false'
102 );
103 $this->AssertEquals(
104 '',
105 Html::expandAttributes( array( 'selected' => null ) ),
106 'Boolean attributes do not generates output when value is null'
107 );
108
109 $this->AssertEquals(
110 $wgHtml5 ? ' selected=""' : ' selected="selected"',
111 Html::expandAttributes( array( 'selected' => true ) ),
112 'Boolean attributes skip value output'
113 );
114 $this->AssertEquals(
115 $wgHtml5 ? ' selected=""' : ' selected="selected"',
116 Html::expandAttributes( array( 'selected' ) ),
117 'Boolean attributes (ex: selected) do not need a value'
118 );
119 }
120
121 /**
122 * Test for Html::expandAttributes()
123 * Please note it output a string prefixed with a space!
124 */
125 public function testExpandAttributesVariousExpansions() {
126 ### NOT EMPTY ####
127 $this->AssertEquals(
128 ' empty_string=""',
129 Html::expandAttributes( array( 'empty_string' => '' ) ),
130 'Value with an empty string'
131 );
132 $this->AssertEquals(
133 ' key="value"',
134 Html::expandAttributes( array( 'key' => 'value' ) ),
135 'Value is a string'
136 );
137 $this->AssertEquals(
138 ' one="1"',
139 Html::expandAttributes( array( 'one' => 1 ) ),
140 'Value is a numeric one'
141 );
142 $this->AssertEquals(
143 ' zero="0"',
144 Html::expandAttributes( array( 'zero' => 0 ) ),
145 'Value is a numeric zero'
146 );
147 }
148
149 /**
150 * Html::expandAttributes has special features for HTML
151 * attributes that use space separated lists and also
152 * allows arrays to be used as values.
153 */
154 public function testExpandAttributesListValueAttributes() {
155 ### STRING VALUES
156 $this->AssertEquals(
157 ' class="redundant spaces here"',
158 Html::expandAttributes( array( 'class' => ' redundant spaces here ' ) ),
159 'Normalization should strip redundant spaces'
160 );
161 $this->AssertEquals(
162 ' class="foo bar"',
163 Html::expandAttributes( array( 'class' => 'foo bar foo bar bar' ) ),
164 'Normalization should remove duplicates in string-lists'
165 );
166 ### "EMPTY" ARRAY VALUES
167 $this->AssertEquals(
168 ' class=""',
169 Html::expandAttributes( array( 'class' => array() ) ),
170 'Value with an empty array'
171 );
172 $this->AssertEquals(
173 ' class=""',
174 Html::expandAttributes( array( 'class' => array( null, '', ' ', ' ' ) ) ),
175 'Array with null, empty string and spaces'
176 );
177 ### NON-EMPTY ARRAY VALUES
178 $this->AssertEquals(
179 ' class="foo bar"',
180 Html::expandAttributes( array( 'class' => array(
181 'foo',
182 'bar',
183 'foo',
184 'bar',
185 'bar',
186 ) ) ),
187 'Normalization should remove duplicates in the array'
188 );
189 $this->AssertEquals(
190 ' class="foo bar"',
191 Html::expandAttributes( array( 'class' => array(
192 'foo bar',
193 'bar foo',
194 'foo',
195 'bar bar',
196 ) ) ),
197 'Normalization should remove duplicates in string-lists in the array'
198 );
199 }
200
201 /**
202 * Test feature added by r96188, let pass attributes values as
203 * a PHP array. Restricted to class,rel, accesskey.
204 */
205 function testExpandAttributesSpaceSeparatedAttributesWithBoolean() {
206 $this->assertEquals(
207 ' class="booltrue one"',
208 Html::expandAttributes( array( 'class' => array(
209 'booltrue' => true,
210 'one' => 1,
211
212 # Method use isset() internally, make sure we do discard
213 # attributes values which have been assigned well known values
214 'emptystring' => '',
215 'boolfalse' => false,
216 'zero' => 0,
217 'null' => null,
218 )))
219 );
220 }
221
222 /**
223 * How do we handle duplicate keys in HTML attributes expansion?
224 * We could pass a "class" the values: 'GREEN' and array( 'GREEN' => false )
225 * The later will take precedence.
226 *
227 * Feature added by r96188
228 */
229 function testValueIsAuthoritativeInSpaceSeparatedAttributesArrays() {
230 $this->assertEquals(
231 ' class=""',
232 Html::expandAttributes( array( 'class' => array(
233 'GREEN',
234 'GREEN' => false,
235 'GREEN',
236 )))
237 );
238 }
239
240 function testNamespaceSelector() {
241 $this->assertEquals(
242 '<select>' . "\n" .
243 '<option value="0">(Main)</option>' . "\n" .
244 '<option value="1">Talk</option>' . "\n" .
245 '<option value="2">User</option>' . "\n" .
246 '<option value="3">User talk</option>' . "\n" .
247 '<option value="4">MyWiki</option>' . "\n" .
248 '<option value="5">MyWiki Talk</option>' . "\n" .
249 '<option value="6">File</option>' . "\n" .
250 '<option value="7">File talk</option>' . "\n" .
251 '<option value="8">MediaWiki</option>' . "\n" .
252 '<option value="9">MediaWiki talk</option>' . "\n" .
253 '<option value="10">Template</option>' . "\n" .
254 '<option value="11">Template talk</option>' . "\n" .
255 '<option value="14">Category</option>' . "\n" .
256 '<option value="15">Category talk</option>' . "\n" .
257 '<option value="100">Custom</option>' . "\n" .
258 '<option value="101">Custom talk</option>' . "\n" .
259 '</select>',
260 Html::namespaceSelector(),
261 'Basic namespace selector without custom options'
262 );
263
264 $this->assertEquals(
265 '<label for="mw-test-namespace">Select a namespace:</label>&#160;' .
266 '<select id="mw-test-namespace" name="wpNamespace">' . "\n" .
267 '<option value="all">all</option>' . "\n" .
268 '<option value="0">(Main)</option>' . "\n" .
269 '<option value="1">Talk</option>' . "\n" .
270 '<option value="2" selected="">User</option>' . "\n" .
271 '<option value="3">User talk</option>' . "\n" .
272 '<option value="4">MyWiki</option>' . "\n" .
273 '<option value="5">MyWiki Talk</option>' . "\n" .
274 '<option value="6">File</option>' . "\n" .
275 '<option value="7">File talk</option>' . "\n" .
276 '<option value="8">MediaWiki</option>' . "\n" .
277 '<option value="9">MediaWiki talk</option>' . "\n" .
278 '<option value="10">Template</option>' . "\n" .
279 '<option value="11">Template talk</option>' . "\n" .
280 '<option value="14">Category</option>' . "\n" .
281 '<option value="15">Category talk</option>' . "\n" .
282 '<option value="100">Custom</option>' . "\n" .
283 '<option value="101">Custom talk</option>' . "\n" .
284 '</select>',
285 Html::namespaceSelector(
286 array( 'selected' => '2', 'all' => 'all', 'label' => 'Select a namespace:' ),
287 array( 'name' => 'wpNamespace', 'id' => 'mw-test-namespace' )
288 ),
289 'Basic namespace selector with custom values'
290 );
291
292 $this->assertEquals(
293 '<label>Select a namespace:</label>&#160;' .
294 '<select>' . "\n" .
295 '<option value="0">(Main)</option>' . "\n" .
296 '<option value="1">Talk</option>' . "\n" .
297 '<option value="2">User</option>' . "\n" .
298 '<option value="3">User talk</option>' . "\n" .
299 '<option value="4">MyWiki</option>' . "\n" .
300 '<option value="5">MyWiki Talk</option>' . "\n" .
301 '<option value="6">File</option>' . "\n" .
302 '<option value="7">File talk</option>' . "\n" .
303 '<option value="8">MediaWiki</option>' . "\n" .
304 '<option value="9">MediaWiki talk</option>' . "\n" .
305 '<option value="10">Template</option>' . "\n" .
306 '<option value="11">Template talk</option>' . "\n" .
307 '<option value="14">Category</option>' . "\n" .
308 '<option value="15">Category talk</option>' . "\n" .
309 '<option value="100">Custom</option>' . "\n" .
310 '<option value="101">Custom talk</option>' . "\n" .
311 '</select>',
312 Html::namespaceSelector(
313 array( 'label' => 'Select a namespace:' )
314 ),
315 'Basic namespace selector with a custom label but no id attribtue for the <select>'
316 );
317 }
318
319 function testCanFilterOutNamespaces() {
320 $this->assertEquals(
321 '<select>' . "\n" .
322 '<option value="2">User</option>' . "\n" .
323 '<option value="4">MyWiki</option>' . "\n" .
324 '<option value="5">MyWiki Talk</option>' . "\n" .
325 '<option value="6">File</option>' . "\n" .
326 '<option value="7">File talk</option>' . "\n" .
327 '<option value="8">MediaWiki</option>' . "\n" .
328 '<option value="9">MediaWiki talk</option>' . "\n" .
329 '<option value="10">Template</option>' . "\n" .
330 '<option value="11">Template talk</option>' . "\n" .
331 '<option value="14">Category</option>' . "\n" .
332 '<option value="15">Category talk</option>' . "\n" .
333 '</select>',
334 Html::namespaceSelector(
335 array( 'exclude' => array( 0, 1, 3, 100, 101 ) )
336 ),
337 'Namespace selector namespace filtering.'
338 );
339 }
340
341 function testCanDisableANamespaces() {
342 $this->assertEquals(
343 '<select>' . "\n" .
344 '<option disabled="" value="0">(Main)</option>' . "\n" .
345 '<option disabled="" value="1">Talk</option>' . "\n" .
346 '<option disabled="" value="2">User</option>' . "\n" .
347 '<option disabled="" value="3">User talk</option>' . "\n" .
348 '<option disabled="" value="4">MyWiki</option>' . "\n" .
349 '<option value="5">MyWiki Talk</option>' . "\n" .
350 '<option value="6">File</option>' . "\n" .
351 '<option value="7">File talk</option>' . "\n" .
352 '<option value="8">MediaWiki</option>' . "\n" .
353 '<option value="9">MediaWiki talk</option>' . "\n" .
354 '<option value="10">Template</option>' . "\n" .
355 '<option value="11">Template talk</option>' . "\n" .
356 '<option value="14">Category</option>' . "\n" .
357 '<option value="15">Category talk</option>' . "\n" .
358 '<option value="100">Custom</option>' . "\n" .
359 '<option value="101">Custom talk</option>' . "\n" .
360 '</select>',
361 Html::namespaceSelector( array(
362 'disable' => array( 0, 1, 2, 3, 4 )
363 ) ),
364 'Namespace selector namespace disabling'
365 );
366 }
367
368 /**
369 * @dataProvider providesHtml5InputTypes
370 */
371 function testHtmlElementAcceptsNewHtml5TypesInHtml5Mode( $HTML5InputType ) {
372 $this->enableHTML5();
373 $this->assertEquals(
374 '<input type="' . $HTML5InputType . '" />',
375 Html::element( 'input', array( 'type' => $HTML5InputType ) ),
376 'In HTML5, HTML::element() should accept type="' . $HTML5InputType . '"'
377 );
378 }
379
380 /**
381 * List of input element types values introduced by HTML5
382 * Full list at http://www.w3.org/TR/html-markup/input.html
383 */
384 function providesHtml5InputTypes() {
385 $types = array(
386 'datetime',
387 'datetime-local',
388 'date',
389 'month',
390 'time',
391 'week',
392 'number',
393 'range',
394 'email',
395 'url',
396 'search',
397 'tel',
398 'color',
399 );
400 $cases = array();
401 foreach( $types as $type ) {
402 $cases[] = array( $type );
403 }
404 return $cases;
405 }
406
407 /**
408 * Test out Html::element drops default value
409 * @cover Html::dropDefaults
410 * @dataProvider provideElementsWithAttributesHavingDefaultValues
411 */
412 function testDropDefaults( $expected, $element, $message = '' ) {
413 $this->enableHTML5();
414 $this->assertEquals( $expected, $element, $message );
415 }
416
417 function provideElementsWithAttributesHavingDefaultValues() {
418 # Use cases in a concise format:
419 # <expected>, <element name>, <array of attributes> [, <message>]
420 # Will be mapped to Html::element()
421 $cases = array();
422
423 ### Generic cases, match $attribDefault static array
424 $cases[] = array( '<area />',
425 'area', array( 'shape' => 'rect' )
426 );
427
428 $cases[] = array( '<button></button>',
429 'button', array( 'formaction' => 'GET' )
430 );
431 $cases[] = array( '<button></button>',
432 'button', array( 'formenctype' => 'application/x-www-form-urlencoded' )
433 );
434 $cases[] = array( '<button></button>',
435 'button', array( 'type' => 'submit' )
436 );
437
438 $cases[] = array( '<canvas></canvas>',
439 'canvas', array( 'height' => '150' )
440 );
441 $cases[] = array( '<canvas></canvas>',
442 'canvas', array( 'width' => '300' )
443 );
444 # Also check with numeric values
445 $cases[] = array( '<canvas></canvas>',
446 'canvas', array( 'height' => 150 )
447 );
448 $cases[] = array( '<canvas></canvas>',
449 'canvas', array( 'width' => 300 )
450 );
451
452 $cases[] = array( '<command />',
453 'command', array( 'type' => 'command' )
454 );
455
456 $cases[] = array( '<form></form>',
457 'form', array( 'action' => 'GET' )
458 );
459 $cases[] = array( '<form></form>',
460 'form', array( 'autocomplete' => 'on' )
461 );
462 $cases[] = array( '<form></form>',
463 'form', array( 'enctype' => 'application/x-www-form-urlencoded' )
464 );
465
466 $cases[] = array( '<input />',
467 'input', array( 'formaction' => 'GET' )
468 );
469 $cases[] = array( '<input />',
470 'input', array( 'type' => 'text' )
471 );
472
473 $cases[] = array( '<keygen />',
474 'keygen', array( 'keytype' => 'rsa' )
475 );
476
477 $cases[] = array( '<link />',
478 'link', array( 'media' => 'all' )
479 );
480
481 $cases[] = array( '<menu></menu>',
482 'menu', array( 'type' => 'list' )
483 );
484
485 $cases[] = array( '<script></script>',
486 'script', array( 'type' => 'text/javascript' )
487 );
488
489 $cases[] = array( '<style></style>',
490 'style', array( 'media' => 'all' )
491 );
492 $cases[] = array( '<style></style>',
493 'style', array( 'type' => 'text/css' )
494 );
495
496 $cases[] = array( '<textarea></textarea>',
497 'textarea', array( 'wrap' => 'soft' )
498 );
499
500 ### SPECIFIC CASES
501
502 # <link type="text/css" />
503 $cases[] = array( '<link />',
504 'link', array( 'type' => 'text/css' )
505 );
506
507 # <input /> specific handling
508 $cases[] = array( '<input type="checkbox" />',
509 'input', array( 'type' => 'checkbox', 'value' => 'on' ),
510 'Default value "on" is stripped of checkboxes',
511 );
512 $cases[] = array( '<input type="radio" />',
513 'input', array( 'type' => 'radio', 'value' => 'on' ),
514 'Default value "on" is stripped of radio buttons',
515 );
516 $cases[] = array( '<input type="submit" value="Submit" />',
517 'input', array( 'type' => 'submit', 'value' => 'Submit' ),
518 'Default value "Submit" is kept on submit buttons (for possible l10n issues)',
519 );
520 $cases[] = array( '<input type="color" />',
521 'input', array( 'type' => 'color', 'value' => '' ),
522 );
523 $cases[] = array( '<input type="range" />',
524 'input', array( 'type' => 'range', 'value' => '' ),
525 );
526
527 # <select /> specifc handling
528 $cases[] = array( '<select multiple=""></select>',
529 'select', array( 'size' => '4', 'multiple' => true ),
530 );
531 # .. with numeric value
532 $cases[] = array( '<select multiple=""></select>',
533 'select', array( 'size' => 4, 'multiple' => true ),
534 );
535 $cases[] = array( '<select></select>',
536 'select', array( 'size' => '1', 'multiple' => false ),
537 );
538 # .. with numeric value
539 $cases[] = array( '<select></select>',
540 'select', array( 'size' => 1, 'multiple' => false ),
541 );
542
543 # Passing an array as value
544 $cases[] = array( '<a class="css-class-one css-class-two"></a>',
545 'a', array( 'class' => array( 'css-class-one', 'css-class-two' ) ),
546 "dropDefaults accepts values given as an array"
547 );
548
549 # FIXME: doDropDefault should remove defaults given in an array
550 # Expected should be '<a></a>'
551 $cases[] = array( '<a class=""></a>',
552 'a', array( 'class' => array( '', '' ) ),
553 "dropDefaults accepts values given as an array"
554 );
555
556
557 # Craft the Html elements
558 $ret = array();
559 foreach( $cases as $case ) {
560 $ret[] = array(
561 $case[0],
562 Html::element( $case[1], $case[2] )
563 );
564 }
565 return $ret;
566 }
567
568 }