Merge "Slight improvements to FormSpecialPage behavior."
[lhc/web/wiklou.git] / tests / phpunit / languages / LanguageTest.php
1 <?php
2
3 class LanguageTest extends LanguageClassesTestCase {
4 function testLanguageConvertDoubleWidthToSingleWidth() {
5 $this->assertEquals(
6 "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
7 $this->getLang()->normalizeForSearch(
8 "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
9 ),
10 'convertDoubleWidth() with the full alphabet and digits'
11 );
12 }
13
14 /**
15 * @dataProvider provideFormattableTimes
16 */
17 function testFormatTimePeriod( $seconds, $format, $expected, $desc ) {
18 $this->assertEquals( $expected, $this->getLang()->formatTimePeriod( $seconds, $format ), $desc );
19 }
20
21 public static function provideFormattableTimes() {
22 return array(
23 array(
24 9.45,
25 array(),
26 '9.5 s',
27 'formatTimePeriod() rounding (<10s)'
28 ),
29 array(
30 9.45,
31 array( 'noabbrevs' => true ),
32 '9.5 seconds',
33 'formatTimePeriod() rounding (<10s)'
34 ),
35 array(
36 9.95,
37 array(),
38 '10 s',
39 'formatTimePeriod() rounding (<10s)'
40 ),
41 array(
42 9.95,
43 array( 'noabbrevs' => true ),
44 '10 seconds',
45 'formatTimePeriod() rounding (<10s)'
46 ),
47 array(
48 59.55,
49 array(),
50 '1 min 0 s',
51 'formatTimePeriod() rounding (<60s)'
52 ),
53 array(
54 59.55,
55 array( 'noabbrevs' => true ),
56 '1 minute 0 seconds',
57 'formatTimePeriod() rounding (<60s)'
58 ),
59 array(
60 119.55,
61 array(),
62 '2 min 0 s',
63 'formatTimePeriod() rounding (<1h)'
64 ),
65 array(
66 119.55,
67 array( 'noabbrevs' => true ),
68 '2 minutes 0 seconds',
69 'formatTimePeriod() rounding (<1h)'
70 ),
71 array(
72 3599.55,
73 array(),
74 '1 h 0 min 0 s',
75 'formatTimePeriod() rounding (<1h)'
76 ),
77 array(
78 3599.55,
79 array( 'noabbrevs' => true ),
80 '1 hour 0 minutes 0 seconds',
81 'formatTimePeriod() rounding (<1h)'
82 ),
83 array(
84 7199.55,
85 array(),
86 '2 h 0 min 0 s',
87 'formatTimePeriod() rounding (>=1h)'
88 ),
89 array(
90 7199.55,
91 array( 'noabbrevs' => true ),
92 '2 hours 0 minutes 0 seconds',
93 'formatTimePeriod() rounding (>=1h)'
94 ),
95 array(
96 7199.55,
97 'avoidseconds',
98 '2 h 0 min',
99 'formatTimePeriod() rounding (>=1h), avoidseconds'
100 ),
101 array(
102 7199.55,
103 array( 'avoid' => 'avoidseconds', 'noabbrevs' => true ),
104 '2 hours 0 minutes',
105 'formatTimePeriod() rounding (>=1h), avoidseconds'
106 ),
107 array(
108 7199.55,
109 'avoidminutes',
110 '2 h 0 min',
111 'formatTimePeriod() rounding (>=1h), avoidminutes'
112 ),
113 array(
114 7199.55,
115 array( 'avoid' => 'avoidminutes', 'noabbrevs' => true ),
116 '2 hours 0 minutes',
117 'formatTimePeriod() rounding (>=1h), avoidminutes'
118 ),
119 array(
120 172799.55,
121 'avoidseconds',
122 '48 h 0 min',
123 'formatTimePeriod() rounding (=48h), avoidseconds'
124 ),
125 array(
126 172799.55,
127 array( 'avoid' => 'avoidseconds', 'noabbrevs' => true ),
128 '48 hours 0 minutes',
129 'formatTimePeriod() rounding (=48h), avoidseconds'
130 ),
131 array(
132 259199.55,
133 'avoidminutes',
134 '3 d 0 h',
135 'formatTimePeriod() rounding (>48h), avoidminutes'
136 ),
137 array(
138 259199.55,
139 array( 'avoid' => 'avoidminutes', 'noabbrevs' => true ),
140 '3 days 0 hours',
141 'formatTimePeriod() rounding (>48h), avoidminutes'
142 ),
143 array(
144 176399.55,
145 'avoidseconds',
146 '2 d 1 h 0 min',
147 'formatTimePeriod() rounding (>48h), avoidseconds'
148 ),
149 array(
150 176399.55,
151 array( 'avoid' => 'avoidseconds', 'noabbrevs' => true ),
152 '2 days 1 hour 0 minutes',
153 'formatTimePeriod() rounding (>48h), avoidseconds'
154 ),
155 array(
156 176399.55,
157 'avoidminutes',
158 '2 d 1 h',
159 'formatTimePeriod() rounding (>48h), avoidminutes'
160 ),
161 array(
162 176399.55,
163 array( 'avoid' => 'avoidminutes', 'noabbrevs' => true ),
164 '2 days 1 hour',
165 'formatTimePeriod() rounding (>48h), avoidminutes'
166 ),
167 array(
168 259199.55,
169 'avoidseconds',
170 '3 d 0 h 0 min',
171 'formatTimePeriod() rounding (>48h), avoidseconds'
172 ),
173 array(
174 259199.55,
175 array( 'avoid' => 'avoidseconds', 'noabbrevs' => true ),
176 '3 days 0 hours 0 minutes',
177 'formatTimePeriod() rounding (>48h), avoidseconds'
178 ),
179 array(
180 172801.55,
181 'avoidseconds',
182 '2 d 0 h 0 min',
183 'formatTimePeriod() rounding, (>48h), avoidseconds'
184 ),
185 array(
186 172801.55,
187 array( 'avoid' => 'avoidseconds', 'noabbrevs' => true ),
188 '2 days 0 hours 0 minutes',
189 'formatTimePeriod() rounding, (>48h), avoidseconds'
190 ),
191 array(
192 176460.55,
193 array(),
194 '2 d 1 h 1 min 1 s',
195 'formatTimePeriod() rounding, recursion, (>48h)'
196 ),
197 array(
198 176460.55,
199 array( 'noabbrevs' => true ),
200 '2 days 1 hour 1 minute 1 second',
201 'formatTimePeriod() rounding, recursion, (>48h)'
202 ),
203 );
204 }
205
206 function testTruncate() {
207 $this->assertEquals(
208 "XXX",
209 $this->getLang()->truncate( "1234567890", 0, 'XXX' ),
210 'truncate prefix, len 0, small ellipsis'
211 );
212
213 $this->assertEquals(
214 "12345XXX",
215 $this->getLang()->truncate( "1234567890", 8, 'XXX' ),
216 'truncate prefix, small ellipsis'
217 );
218
219 $this->assertEquals(
220 "123456789",
221 $this->getLang()->truncate( "123456789", 5, 'XXXXXXXXXXXXXXX' ),
222 'truncate prefix, large ellipsis'
223 );
224
225 $this->assertEquals(
226 "XXX67890",
227 $this->getLang()->truncate( "1234567890", -8, 'XXX' ),
228 'truncate suffix, small ellipsis'
229 );
230
231 $this->assertEquals(
232 "123456789",
233 $this->getLang()->truncate( "123456789", -5, 'XXXXXXXXXXXXXXX' ),
234 'truncate suffix, large ellipsis'
235 );
236 }
237
238 /**
239 * @dataProvider provideHTMLTruncateData()
240 */
241 function testTruncateHtml( $len, $ellipsis, $input, $expected ) {
242 // Actual HTML...
243 $this->assertEquals(
244 $expected,
245 $this->getLang()->truncateHTML( $input, $len, $ellipsis )
246 );
247 }
248
249 /**
250 * Array format is ($len, $ellipsis, $input, $expected)
251 */
252 public static function provideHTMLTruncateData() {
253 return array(
254 array( 0, 'XXX', "1234567890", "XXX" ),
255 array( 8, 'XXX', "1234567890", "12345XXX" ),
256 array( 5, 'XXXXXXXXXXXXXXX', '1234567890', "1234567890" ),
257 array( 2, '***',
258 '<p><span style="font-weight:bold;"></span></p>',
259 '<p><span style="font-weight:bold;"></span></p>',
260 ),
261 array( 2, '***',
262 '<p><span style="font-weight:bold;">123456789</span></p>',
263 '<p><span style="font-weight:bold;">***</span></p>',
264 ),
265 array( 2, '***',
266 '<p><span style="font-weight:bold;">&nbsp;23456789</span></p>',
267 '<p><span style="font-weight:bold;">***</span></p>',
268 ),
269 array( 3, '***',
270 '<p><span style="font-weight:bold;">123456789</span></p>',
271 '<p><span style="font-weight:bold;">***</span></p>',
272 ),
273 array( 4, '***',
274 '<p><span style="font-weight:bold;">123456789</span></p>',
275 '<p><span style="font-weight:bold;">1***</span></p>',
276 ),
277 array( 5, '***',
278 '<tt><span style="font-weight:bold;">123456789</span></tt>',
279 '<tt><span style="font-weight:bold;">12***</span></tt>',
280 ),
281 array( 6, '***',
282 '<p><a href="www.mediawiki.org">123456789</a></p>',
283 '<p><a href="www.mediawiki.org">123***</a></p>',
284 ),
285 array( 6, '***',
286 '<p><a href="www.mediawiki.org">12&nbsp;456789</a></p>',
287 '<p><a href="www.mediawiki.org">12&nbsp;***</a></p>',
288 ),
289 array( 7, '***',
290 '<small><span style="font-weight:bold;">123<p id="#moo">456</p>789</span></small>',
291 '<small><span style="font-weight:bold;">123<p id="#moo">4***</p></span></small>',
292 ),
293 array( 8, '***',
294 '<div><span style="font-weight:bold;">123<span>4</span>56789</span></div>',
295 '<div><span style="font-weight:bold;">123<span>4</span>5***</span></div>',
296 ),
297 array( 9, '***',
298 '<p><table style="font-weight:bold;"><tr><td>123456789</td></tr></table></p>',
299 '<p><table style="font-weight:bold;"><tr><td>123456789</td></tr></table></p>',
300 ),
301 array( 10, '***',
302 '<p><font style="font-weight:bold;">123456789</font></p>',
303 '<p><font style="font-weight:bold;">123456789</font></p>',
304 ),
305 );
306 }
307
308 /**
309 * Test Language::isWellFormedLanguageTag()
310 * @dataProvider provideWellFormedLanguageTags
311 */
312 function testWellFormedLanguageTag( $code, $message = '' ) {
313 $this->assertTrue(
314 Language::isWellFormedLanguageTag( $code ),
315 "validating code $code $message"
316 );
317 }
318
319 /**
320 * The test cases are based on the tests in the GaBuZoMeu parser
321 * written by Stéphane Bortzmeyer <bortzmeyer@nic.fr>
322 * and distributed as free software, under the GNU General Public Licence.
323 * http://www.bortzmeyer.org/gabuzomeu-parsing-language-tags.html
324 */
325 public static function provideWellFormedLanguageTags() {
326 return array(
327 array( 'fr', 'two-letter code' ),
328 array( 'fr-latn', 'two-letter code with lower case script code' ),
329 array( 'fr-Latn-FR', 'two-letter code with title case script code and uppercase country code' ),
330 array( 'fr-Latn-419', 'two-letter code with title case script code and region number' ),
331 array( 'fr-FR', 'two-letter code with uppercase' ),
332 array( 'ax-TZ', 'Not in the registry, but well-formed' ),
333 array( 'fr-shadok', 'two-letter code with variant' ),
334 array( 'fr-y-myext-myext2', 'non-x singleton' ),
335 array( 'fra-Latn', 'ISO 639 can be 3-letters' ),
336 array( 'fra', 'three-letter language code' ),
337 array( 'fra-FX', 'three-letter language code with country code' ),
338 array( 'i-klingon', 'grandfathered with singleton' ),
339 array( 'I-kLINgon', 'tags are case-insensitive...' ),
340 array( 'no-bok', 'grandfathered without singleton' ),
341 array( 'i-enochian', 'Grandfathered' ),
342 array( 'x-fr-CH', 'private use' ),
343 array( 'es-419', 'two-letter code with region number' ),
344 array( 'en-Latn-GB-boont-r-extended-sequence-x-private', 'weird, but well-formed' ),
345 array( 'ab-x-abc-x-abc', 'anything goes after x' ),
346 array( 'ab-x-abc-a-a', 'anything goes after x, including several non-x singletons' ),
347 array( 'i-default', 'grandfathered' ),
348 array( 'abcd-Latn', 'Language of 4 chars reserved for future use' ),
349 array( 'AaBbCcDd-x-y-any-x', 'Language of 5-8 chars, registered' ),
350 array( 'de-CH-1901', 'with country and year' ),
351 array( 'en-US-x-twain', 'with country and singleton' ),
352 array( 'zh-cmn', 'three-letter variant' ),
353 array( 'zh-cmn-Hant', 'three-letter variant and script' ),
354 array( 'zh-cmn-Hant-HK', 'three-letter variant, script and country' ),
355 array( 'xr-p-lze', 'Extension' ),
356 );
357 }
358
359 /**
360 * Negative test for Language::isWellFormedLanguageTag()
361 * @dataProvider provideMalformedLanguageTags
362 */
363 function testMalformedLanguageTag( $code, $message = '' ) {
364 $this->assertFalse(
365 Language::isWellFormedLanguageTag( $code ),
366 "validating that code $code is a malformed language tag - $message"
367 );
368 }
369
370 /**
371 * The test cases are based on the tests in the GaBuZoMeu parser
372 * written by Stéphane Bortzmeyer <bortzmeyer@nic.fr>
373 * and distributed as free software, under the GNU General Public Licence.
374 * http://www.bortzmeyer.org/gabuzomeu-parsing-language-tags.html
375 */
376 public static function provideMalformedLanguageTags() {
377 return array(
378 array( 'f', 'language too short' ),
379 array( 'f-Latn', 'language too short with script' ),
380 array( 'xr-lxs-qut', 'variants too short' ), # extlangS
381 array( 'fr-Latn-F', 'region too short' ),
382 array( 'a-value', 'language too short with region' ),
383 array( 'tlh-a-b-foo', 'valid three-letter with wrong variant' ),
384 array( 'i-notexist', 'grandfathered but not registered: invalid, even if we only test well-formedness' ),
385 array( 'abcdefghi-012345678', 'numbers too long' ),
386 array( 'ab-abc-abc-abc-abc', 'invalid extensions' ),
387 array( 'ab-abcd-abc', 'invalid extensions' ),
388 array( 'ab-ab-abc', 'invalid extensions' ),
389 array( 'ab-123-abc', 'invalid extensions' ),
390 array( 'a-Hant-ZH', 'short language with valid extensions' ),
391 array( 'a1-Hant-ZH', 'invalid character in language' ),
392 array( 'ab-abcde-abc', 'invalid extensions' ),
393 array( 'ab-1abc-abc', 'invalid characters in extensions' ),
394 array( 'ab-ab-abcd', 'invalid order of extensions' ),
395 array( 'ab-123-abcd', 'invalid order of extensions' ),
396 array( 'ab-abcde-abcd', 'invalid extensions' ),
397 array( 'ab-1abc-abcd', 'invalid characters in extensions' ),
398 array( 'ab-a-b', 'extensions too short' ),
399 array( 'ab-a-x', 'extensions too short, even with singleton' ),
400 array( 'ab--ab', 'two separators' ),
401 array( 'ab-abc-', 'separator in the end' ),
402 array( '-ab-abc', 'separator in the beginning' ),
403 array( 'abcd-efg', 'language too long' ),
404 array( 'aabbccddE', 'tag too long' ),
405 array( 'pa_guru', 'A tag with underscore is invalid in strict mode' ),
406 array( 'de-f', 'subtag too short' ),
407 );
408 }
409
410 /**
411 * Negative test for Language::isWellFormedLanguageTag()
412 */
413 function testLenientLanguageTag() {
414 $this->assertTrue(
415 Language::isWellFormedLanguageTag( 'pa_guru', true ),
416 'pa_guru is a well-formed language tag in lenient mode'
417 );
418 }
419
420 /**
421 * Test Language::isValidBuiltInCode()
422 * @dataProvider provideLanguageCodes
423 */
424 function testBuiltInCodeValidation( $code, $message = '' ) {
425 $this->assertTrue(
426 (bool)Language::isValidBuiltInCode( $code ),
427 "validating code $code $message"
428 );
429 }
430
431 function testBuiltInCodeValidationRejectUnderscore() {
432 $this->assertFalse(
433 (bool)Language::isValidBuiltInCode( 'be_tarask' ),
434 "reject underscore in language code"
435 );
436 }
437
438 public static function provideLanguageCodes() {
439 return array(
440 array( 'fr', 'Two letters, minor case' ),
441 array( 'EN', 'Two letters, upper case' ),
442 array( 'tyv', 'Three letters' ),
443 array( 'tokipona', 'long language code' ),
444 array( 'be-tarask', 'With dash' ),
445 array( 'Zh-classical', 'Begin with upper case, dash' ),
446 array( 'Be-x-old', 'With extension (two dashes)' ),
447 );
448 }
449
450 /**
451 * Test Language::isKnownLanguageTag()
452 * @dataProvider provideKnownLanguageTags
453 */
454 function testKnownLanguageTag( $code, $message = '' ) {
455 $this->assertTrue(
456 (bool)Language::isKnownLanguageTag( $code ),
457 "validating code $code - $message"
458 );
459 }
460
461 public static function provideKnownLanguageTags() {
462 return array(
463 array( 'fr', 'simple code' ),
464 array( 'bat-smg', 'an MW legacy tag' ),
465 array( 'sgs', 'an internal standard MW name, for which a legacy tag is used externally' ),
466 );
467 }
468
469 /**
470 * Test Language::isKnownLanguageTag()
471 */
472 function testKnownCldrLanguageTag() {
473 if ( !class_exists( 'LanguageNames' ) ) {
474 $this->markTestSkipped( 'The LanguageNames class is not available. The cldr extension is probably not installed.' );
475 }
476
477 $this->assertTrue(
478 (bool)Language::isKnownLanguageTag( 'pal' ),
479 'validating code "pal" an ancient language, which probably will not appear in Names.php, but appears in CLDR in English'
480 );
481 }
482
483 /**
484 * Negative tests for Language::isKnownLanguageTag()
485 * @dataProvider provideUnKnownLanguageTags
486 */
487 function testUnknownLanguageTag( $code, $message = '' ) {
488 $this->assertFalse(
489 (bool)Language::isKnownLanguageTag( $code ),
490 "checking that code $code is invalid - $message"
491 );
492 }
493
494 public static function provideUnknownLanguageTags() {
495 return array(
496 array( 'mw', 'non-existent two-letter code' ),
497 );
498 }
499
500 /**
501 * Test too short timestamp
502 * @expectedException MWException
503 */
504 function testSprintfDateTooShortTimestamp() {
505 $this->getLang()->sprintfDate( 'xiY', '1234567890123' );
506 }
507
508 /**
509 * Test too long timestamp
510 * @expectedException MWException
511 */
512 function testSprintfDateTooLongTimestamp() {
513 $this->getLang()->sprintfDate( 'xiY', '123456789012345' );
514 }
515
516 /**
517 * Test too short timestamp
518 * @expectedException MWException
519 */
520 function testSprintfDateNotAllDigitTimestamp() {
521 $this->getLang()->sprintfDate( 'xiY', '-1234567890123' );
522 }
523
524 /**
525 * @dataProvider provideSprintfDateSamples
526 */
527 function testSprintfDate( $format, $ts, $expected, $msg ) {
528 $this->assertEquals(
529 $expected,
530 $this->getLang()->sprintfDate( $format, $ts ),
531 "sprintfDate('$format', '$ts'): $msg"
532 );
533 }
534
535 /**
536 * sprintfDate should always use UTC when no zone is given.
537 * @dataProvider provideSprintfDateSamples
538 */
539 function testSprintfDateNoZone( $format, $ts, $expected, $ignore, $msg ) {
540 $oldTZ = date_default_timezone_get();
541 $res = date_default_timezone_set( 'Asia/Seoul' );
542 if ( !$res ) {
543 $this->markTestSkipped( "Error setting Timezone" );
544 }
545
546 $this->assertEquals(
547 $expected,
548 $this->getLang()->sprintfDate( $format, $ts ),
549 "sprintfDate('$format', '$ts'): $msg"
550 );
551
552 date_default_timezone_set( $oldTZ );
553 }
554
555 /**
556 * sprintfDate should use passed timezone
557 * @dataProvider provideSprintfDateSamples
558 */
559 function testSprintfDateTZ( $format, $ts, $ignore, $expected, $msg ) {
560 $tz = new DateTimeZone( 'Asia/Seoul' );
561 if ( !$tz ) {
562 $this->markTestSkipped( "Error getting Timezone" );
563 }
564
565 $this->assertEquals(
566 $expected,
567 $this->getLang()->sprintfDate( $format, $ts, $tz ),
568 "sprintfDate('$format', '$ts', 'Asia/Seoul'): $msg"
569 );
570 }
571
572 public static function provideSprintfDateSamples() {
573 return array(
574 array(
575 'xiY',
576 '20111212000000',
577 '1390', // note because we're testing English locale we get Latin-standard digits
578 '1390',
579 'Iranian calendar full year'
580 ),
581 array(
582 'xiy',
583 '20111212000000',
584 '90',
585 '90',
586 'Iranian calendar short year'
587 ),
588 array(
589 'o',
590 '20120101235000',
591 '2011',
592 '2011',
593 'ISO 8601 (week) year'
594 ),
595 array(
596 'W',
597 '20120101235000',
598 '52',
599 '52',
600 'Week number'
601 ),
602 array(
603 'W',
604 '20120102235000',
605 '1',
606 '1',
607 'Week number'
608 ),
609 array(
610 'o-\\WW-N',
611 '20091231235000',
612 '2009-W53-4',
613 '2009-W53-4',
614 'leap week'
615 ),
616 // What follows is mostly copied from http://www.mediawiki.org/wiki/Help:Extension:ParserFunctions#.23time
617 array(
618 'Y',
619 '20120102090705',
620 '2012',
621 '2012',
622 'Full year'
623 ),
624 array(
625 'y',
626 '20120102090705',
627 '12',
628 '12',
629 '2 digit year'
630 ),
631 array(
632 'L',
633 '20120102090705',
634 '1',
635 '1',
636 'Leap year'
637 ),
638 array(
639 'n',
640 '20120102090705',
641 '1',
642 '1',
643 'Month index, not zero pad'
644 ),
645 array(
646 'N',
647 '20120102090705',
648 '01',
649 '01',
650 'Month index. Zero pad'
651 ),
652 array(
653 'M',
654 '20120102090705',
655 'Jan',
656 'Jan',
657 'Month abbrev'
658 ),
659 array(
660 'F',
661 '20120102090705',
662 'January',
663 'January',
664 'Full month'
665 ),
666 array(
667 'xg',
668 '20120102090705',
669 'January',
670 'January',
671 'Genitive month name (same in EN)'
672 ),
673 array(
674 'j',
675 '20120102090705',
676 '2',
677 '2',
678 'Day of month (not zero pad)'
679 ),
680 array(
681 'd',
682 '20120102090705',
683 '02',
684 '02',
685 'Day of month (zero-pad)'
686 ),
687 array(
688 'z',
689 '20120102090705',
690 '1',
691 '1',
692 'Day of year (zero-indexed)'
693 ),
694 array(
695 'D',
696 '20120102090705',
697 'Mon',
698 'Mon',
699 'Day of week (abbrev)'
700 ),
701 array(
702 'l',
703 '20120102090705',
704 'Monday',
705 'Monday',
706 'Full day of week'
707 ),
708 array(
709 'N',
710 '20120101090705',
711 '7',
712 '7',
713 'Day of week (Mon=1, Sun=7)'
714 ),
715 array(
716 'w',
717 '20120101090705',
718 '0',
719 '0',
720 'Day of week (Sun=0, Sat=6)'
721 ),
722 array(
723 'N',
724 '20120102090705',
725 '1',
726 '1',
727 'Day of week'
728 ),
729 array(
730 'a',
731 '20120102090705',
732 'am',
733 'am',
734 'am vs pm'
735 ),
736 array(
737 'A',
738 '20120102120000',
739 'PM',
740 'PM',
741 'AM vs PM'
742 ),
743 array(
744 'a',
745 '20120102000000',
746 'am',
747 'am',
748 'AM vs PM'
749 ),
750 array(
751 'g',
752 '20120102090705',
753 '9',
754 '9',
755 '12 hour, not Zero'
756 ),
757 array(
758 'h',
759 '20120102090705',
760 '09',
761 '09',
762 '12 hour, zero padded'
763 ),
764 array(
765 'G',
766 '20120102090705',
767 '9',
768 '9',
769 '24 hour, not zero'
770 ),
771 array(
772 'H',
773 '20120102090705',
774 '09',
775 '09',
776 '24 hour, zero'
777 ),
778 array(
779 'H',
780 '20120102110705',
781 '11',
782 '11',
783 '24 hour, zero'
784 ),
785 array(
786 'i',
787 '20120102090705',
788 '07',
789 '07',
790 'Minutes'
791 ),
792 array(
793 's',
794 '20120102090705',
795 '05',
796 '05',
797 'seconds'
798 ),
799 array(
800 'U',
801 '20120102090705',
802 '1325495225',
803 '1325462825',
804 'unix time'
805 ),
806 array(
807 't',
808 '20120102090705',
809 '31',
810 '31',
811 'Days in current month'
812 ),
813 array(
814 'c',
815 '20120102090705',
816 '2012-01-02T09:07:05+00:00',
817 '2012-01-02T09:07:05+09:00',
818 'ISO 8601 timestamp'
819 ),
820 array(
821 'r',
822 '20120102090705',
823 'Mon, 02 Jan 2012 09:07:05 +0000',
824 'Mon, 02 Jan 2012 09:07:05 +0900',
825 'RFC 5322'
826 ),
827 array(
828 'e',
829 '20120102090705',
830 'UTC',
831 'Asia/Seoul',
832 'Timezone identifier'
833 ),
834 array(
835 'I',
836 '19880602090705',
837 '0',
838 '1',
839 'DST indicator'
840 ),
841 array(
842 'O',
843 '20120102090705',
844 '+0000',
845 '+0900',
846 'Timezone offset'
847 ),
848 array(
849 'P',
850 '20120102090705',
851 '+00:00',
852 '+09:00',
853 'Timezone offset with colon'
854 ),
855 array(
856 'T',
857 '20120102090705',
858 'UTC',
859 'KST',
860 'Timezone abbreviation'
861 ),
862 array(
863 'Z',
864 '20120102090705',
865 '0',
866 '32400',
867 'Timezone offset in seconds'
868 ),
869 array(
870 'xmj xmF xmn xmY',
871 '20120102090705',
872 '7 Safar 2 1433',
873 '7 Safar 2 1433',
874 'Islamic'
875 ),
876 array(
877 'xij xiF xin xiY',
878 '20120102090705',
879 '12 Dey 10 1390',
880 '12 Dey 10 1390',
881 'Iranian'
882 ),
883 array(
884 'xjj xjF xjn xjY',
885 '20120102090705',
886 '7 Tevet 4 5772',
887 '7 Tevet 4 5772',
888 'Hebrew'
889 ),
890 array(
891 'xjt',
892 '20120102090705',
893 '29',
894 '29',
895 'Hebrew number of days in month'
896 ),
897 array(
898 'xjx',
899 '20120102090705',
900 'Tevet',
901 'Tevet',
902 'Hebrew genitive month name (No difference in EN)'
903 ),
904 array(
905 'xkY',
906 '20120102090705',
907 '2555',
908 '2555',
909 'Thai year'
910 ),
911 array(
912 'xoY',
913 '20120102090705',
914 '101',
915 '101',
916 'Minguo'
917 ),
918 array(
919 'xtY',
920 '20120102090705',
921 '平成24',
922 '平成24',
923 'nengo'
924 ),
925 array(
926 'xrxkYY',
927 '20120102090705',
928 'MMDLV2012',
929 'MMDLV2012',
930 'Roman numerals'
931 ),
932 array(
933 'xhxjYY',
934 '20120102090705',
935 \'תשע"ב2012',
936 \'תשע"ב2012',
937 'Hebrew numberals'
938 ),
939 array(
940 'xnY',
941 '20120102090705',
942 '2012',
943 '2012',
944 'Raw numerals (doesn\'t mean much in EN)'
945 ),
946 array(
947 '[[Y "(yea"\\r)]] \\"xx\\"',
948 '20120102090705',
949 '[[2012 (year)]] "x"',
950 '[[2012 (year)]] "x"',
951 'Various escaping'
952 ),
953
954 );
955 }
956
957 /**
958 * @dataProvider provideFormatSizes
959 */
960 function testFormatSize( $size, $expected, $msg ) {
961 $this->assertEquals(
962 $expected,
963 $this->getLang()->formatSize( $size ),
964 "formatSize('$size'): $msg"
965 );
966 }
967
968 public static function provideFormatSizes() {
969 return array(
970 array(
971 0,
972 "0 B",
973 "Zero bytes"
974 ),
975 array(
976 1024,
977 "1 KB",
978 "1 kilobyte"
979 ),
980 array(
981 1024 * 1024,
982 "1 MB",
983 "1,024 megabytes"
984 ),
985 array(
986 1024 * 1024 * 1024,
987 "1 GB",
988 "1 gigabytes"
989 ),
990 array(
991 pow( 1024, 4 ),
992 "1 TB",
993 "1 terabyte"
994 ),
995 array(
996 pow( 1024, 5 ),
997 "1 PB",
998 "1 petabyte"
999 ),
1000 array(
1001 pow( 1024, 6 ),
1002 "1 EB",
1003 "1,024 exabyte"
1004 ),
1005 array(
1006 pow( 1024, 7 ),
1007 "1 ZB",
1008 "1 zetabyte"
1009 ),
1010 array(
1011 pow( 1024, 8 ),
1012 "1 YB",
1013 "1 yottabyte"
1014 ),
1015 // How big!? THIS BIG!
1016 );
1017 }
1018
1019 /**
1020 * @dataProvider provideFormatBitrate
1021 */
1022 function testFormatBitrate( $bps, $expected, $msg ) {
1023 $this->assertEquals(
1024 $expected,
1025 $this->getLang()->formatBitrate( $bps ),
1026 "formatBitrate('$bps'): $msg"
1027 );
1028 }
1029
1030 public static function provideFormatBitrate() {
1031 return array(
1032 array(
1033 0,
1034 "0 bps",
1035 "0 bits per second"
1036 ),
1037 array(
1038 999,
1039 "999 bps",
1040 "999 bits per second"
1041 ),
1042 array(
1043 1000,
1044 "1 kbps",
1045 "1 kilobit per second"
1046 ),
1047 array(
1048 1000 * 1000,
1049 "1 Mbps",
1050 "1 megabit per second"
1051 ),
1052 array(
1053 pow( 10, 9 ),
1054 "1 Gbps",
1055 "1 gigabit per second"
1056 ),
1057 array(
1058 pow( 10, 12 ),
1059 "1 Tbps",
1060 "1 terabit per second"
1061 ),
1062 array(
1063 pow( 10, 15 ),
1064 "1 Pbps",
1065 "1 petabit per second"
1066 ),
1067 array(
1068 pow( 10, 18 ),
1069 "1 Ebps",
1070 "1 exabit per second"
1071 ),
1072 array(
1073 pow( 10, 21 ),
1074 "1 Zbps",
1075 "1 zetabit per second"
1076 ),
1077 array(
1078 pow( 10, 24 ),
1079 "1 Ybps",
1080 "1 yottabit per second"
1081 ),
1082 array(
1083 pow( 10, 27 ),
1084 "1,000 Ybps",
1085 "1,000 yottabits per second"
1086 ),
1087 );
1088 }
1089
1090
1091 /**
1092 * @dataProvider provideFormatDuration
1093 */
1094 function testFormatDuration( $duration, $expected, $intervals = array() ) {
1095 $this->assertEquals(
1096 $expected,
1097 $this->getLang()->formatDuration( $duration, $intervals ),
1098 "formatDuration('$duration'): $expected"
1099 );
1100 }
1101
1102 public static function provideFormatDuration() {
1103 return array(
1104 array(
1105 0,
1106 '0 seconds',
1107 ),
1108 array(
1109 1,
1110 '1 second',
1111 ),
1112 array(
1113 2,
1114 '2 seconds',
1115 ),
1116 array(
1117 60,
1118 '1 minute',
1119 ),
1120 array(
1121 2 * 60,
1122 '2 minutes',
1123 ),
1124 array(
1125 3600,
1126 '1 hour',
1127 ),
1128 array(
1129 2 * 3600,
1130 '2 hours',
1131 ),
1132 array(
1133 24 * 3600,
1134 '1 day',
1135 ),
1136 array(
1137 2 * 86400,
1138 '2 days',
1139 ),
1140 array(
1141 // ( 365 + ( 24 * 3 + 25 ) / 400 ) * 86400 = 31556952
1142 ( 365 + ( 24 * 3 + 25 ) / 400.0 ) * 86400,
1143 '1 year',
1144 ),
1145 array(
1146 2 * 31556952,
1147 '2 years',
1148 ),
1149 array(
1150 10 * 31556952,
1151 '1 decade',
1152 ),
1153 array(
1154 20 * 31556952,
1155 '2 decades',
1156 ),
1157 array(
1158 100 * 31556952,
1159 '1 century',
1160 ),
1161 array(
1162 200 * 31556952,
1163 '2 centuries',
1164 ),
1165 array(
1166 1000 * 31556952,
1167 '1 millennium',
1168 ),
1169 array(
1170 2000 * 31556952,
1171 '2 millennia',
1172 ),
1173 array(
1174 9001,
1175 '2 hours, 30 minutes and 1 second'
1176 ),
1177 array(
1178 3601,
1179 '1 hour and 1 second'
1180 ),
1181 array(
1182 31556952 + 2 * 86400 + 9000,
1183 '1 year, 2 days, 2 hours and 30 minutes'
1184 ),
1185 array(
1186 42 * 1000 * 31556952 + 42,
1187 '42 millennia and 42 seconds'
1188 ),
1189 array(
1190 60,
1191 '60 seconds',
1192 array( 'seconds' ),
1193 ),
1194 array(
1195 61,
1196 '61 seconds',
1197 array( 'seconds' ),
1198 ),
1199 array(
1200 1,
1201 '1 second',
1202 array( 'seconds' ),
1203 ),
1204 array(
1205 31556952 + 2 * 86400 + 9000,
1206 '1 year, 2 days and 150 minutes',
1207 array( 'years', 'days', 'minutes' ),
1208 ),
1209 array(
1210 42,
1211 '0 days',
1212 array( 'years', 'days' ),
1213 ),
1214 array(
1215 31556952 + 2 * 86400 + 9000,
1216 '1 year, 2 days and 150 minutes',
1217 array( 'minutes', 'days', 'years' ),
1218 ),
1219 array(
1220 42,
1221 '0 days',
1222 array( 'days', 'years' ),
1223 ),
1224 );
1225 }
1226
1227 /**
1228 * @dataProvider provideCheckTitleEncodingData
1229 */
1230 function testCheckTitleEncoding( $s ) {
1231 $this->assertEquals(
1232 $s,
1233 $this->getLang()->checkTitleEncoding( $s ),
1234 "checkTitleEncoding('$s')"
1235 );
1236 }
1237
1238 public static function provideCheckTitleEncodingData() {
1239 return array(
1240 array( "" ),
1241 array( "United States of America" ), // 7bit ASCII
1242 array( rawurldecode( "S%C3%A9rie%20t%C3%A9l%C3%A9vis%C3%A9e" ) ),
1243 array(
1244 rawurldecode(
1245 "Acteur%7CAlbert%20Robbins%7CAnglais%7CAnn%20Donahue%7CAnthony%20E.%20Zuiker%7CCarol%20Mendelsohn"
1246 )
1247 ),
1248 // The following two data sets come from bug 36839. They fail if checkTitleEncoding uses a regexp to test for
1249 // valid UTF-8 encoding and the pcre.recursion_limit is low (like, say, 1024). They succeed if checkTitleEncoding
1250 // uses mb_check_encoding for its test.
1251 array(
1252 rawurldecode(
1253 "Acteur%7CAlbert%20Robbins%7CAnglais%7CAnn%20Donahue%7CAnthony%20E.%20Zuiker%7CCarol%20Mendelsohn%7C"
1254 . "Catherine%20Willows%7CDavid%20Hodges%7CDavid%20Phillips%7CGil%20Grissom%7CGreg%20Sanders%7CHodges%7C"
1255 . "Internet%20Movie%20Database%7CJim%20Brass%7CLady%20Heather%7C"
1256 . "Les%20Experts%20(s%C3%A9rie%20t%C3%A9l%C3%A9vis%C3%A9e)%7CLes%20Experts%20:%20Manhattan%7C"
1257 . "Les%20Experts%20:%20Miami%7CListe%20des%20personnages%20des%20Experts%7C"
1258 . "Liste%20des%20%C3%A9pisodes%20des%20Experts%7CMod%C3%A8le%20discussion:Palette%20Les%20Experts%7C"
1259 . "Nick%20Stokes%7CPersonnage%20de%20fiction%7CPersonnage%20fictif%7CPersonnage%20de%20fiction%7C"
1260 . "Personnages%20r%C3%A9currents%20dans%20Les%20Experts%7CRaymond%20Langston%7CRiley%20Adams%7C"
1261 . "Saison%201%20des%20Experts%7CSaison%2010%20des%20Experts%7CSaison%2011%20des%20Experts%7C"
1262 . "Saison%2012%20des%20Experts%7CSaison%202%20des%20Experts%7CSaison%203%20des%20Experts%7C"
1263 . "Saison%204%20des%20Experts%7CSaison%205%20des%20Experts%7CSaison%206%20des%20Experts%7C"
1264 . "Saison%207%20des%20Experts%7CSaison%208%20des%20Experts%7CSaison%209%20des%20Experts%7C"
1265 . "Sara%20Sidle%7CSofia%20Curtis%7CS%C3%A9rie%20t%C3%A9l%C3%A9vis%C3%A9e%7CWallace%20Langham%7C"
1266 . "Warrick%20Brown%7CWendy%20Simms%7C%C3%89tats-Unis"
1267 ),
1268 ),
1269 array(
1270 rawurldecode(
1271 "Mod%C3%A8le%3AArrondissements%20homonymes%7CMod%C3%A8le%3ABandeau%20standard%20pour%20page%20d'homonymie%7C"
1272 . "Mod%C3%A8le%3ABatailles%20homonymes%7CMod%C3%A8le%3ACantons%20homonymes%7C"
1273 . "Mod%C3%A8le%3ACommunes%20fran%C3%A7aises%20homonymes%7CMod%C3%A8le%3AFilms%20homonymes%7C"
1274 . "Mod%C3%A8le%3AGouvernements%20homonymes%7CMod%C3%A8le%3AGuerres%20homonymes%7CMod%C3%A8le%3AHomonymie%7C"
1275 . "Mod%C3%A8le%3AHomonymie%20bateau%7CMod%C3%A8le%3AHomonymie%20d'%C3%A9tablissements%20scolaires%20ou"
1276 . "%20universitaires%7CMod%C3%A8le%3AHomonymie%20d'%C3%AEles%7CMod%C3%A8le%3AHomonymie%20de%20clubs%20sportifs%7C"
1277 . "Mod%C3%A8le%3AHomonymie%20de%20comt%C3%A9s%7CMod%C3%A8le%3AHomonymie%20de%20monument%7C"
1278 . "Mod%C3%A8le%3AHomonymie%20de%20nom%20romain%7CMod%C3%A8le%3AHomonymie%20de%20parti%20politique%7C"
1279 . "Mod%C3%A8le%3AHomonymie%20de%20route%7CMod%C3%A8le%3AHomonymie%20dynastique%7C"
1280 . "Mod%C3%A8le%3AHomonymie%20vid%C3%A9oludique%7CMod%C3%A8le%3AHomonymie%20%C3%A9difice%20religieux%7C"
1281 . "Mod%C3%A8le%3AInternationalisation%7CMod%C3%A8le%3AIsom%C3%A9rie%7CMod%C3%A8le%3AParonymie%7C"
1282 . "Mod%C3%A8le%3APatronyme%7CMod%C3%A8le%3APatronyme%20basque%7CMod%C3%A8le%3APatronyme%20italien%7C"
1283 . "Mod%C3%A8le%3APatronymie%7CMod%C3%A8le%3APersonnes%20homonymes%7CMod%C3%A8le%3ASaints%20homonymes%7C"
1284 . "Mod%C3%A8le%3ATitres%20homonymes%7CMod%C3%A8le%3AToponymie%7CMod%C3%A8le%3AUnit%C3%A9s%20homonymes%7C"
1285 . "Mod%C3%A8le%3AVilles%20homonymes%7CMod%C3%A8le%3A%C3%89difices%20religieux%20homonymes"
1286 )
1287 )
1288 );
1289 }
1290
1291 /**
1292 * @dataProvider provideRomanNumeralsData
1293 */
1294 function testRomanNumerals( $num, $numerals ) {
1295 $this->assertEquals(
1296 $numerals,
1297 Language::romanNumeral( $num ),
1298 "romanNumeral('$num')"
1299 );
1300 }
1301
1302 public static function provideRomanNumeralsData() {
1303 return array(
1304 array( 1, 'I' ),
1305 array( 2, 'II' ),
1306 array( 3, 'III' ),
1307 array( 4, 'IV' ),
1308 array( 5, 'V' ),
1309 array( 6, 'VI' ),
1310 array( 7, 'VII' ),
1311 array( 8, 'VIII' ),
1312 array( 9, 'IX' ),
1313 array( 10, 'X' ),
1314 array( 20, 'XX' ),
1315 array( 30, 'XXX' ),
1316 array( 40, 'XL' ),
1317 array( 49, 'XLIX' ),
1318 array( 50, 'L' ),
1319 array( 60, 'LX' ),
1320 array( 70, 'LXX' ),
1321 array( 80, 'LXXX' ),
1322 array( 90, 'XC' ),
1323 array( 99, 'XCIX' ),
1324 array( 100, 'C' ),
1325 array( 200, 'CC' ),
1326 array( 300, 'CCC' ),
1327 array( 400, 'CD' ),
1328 array( 500, 'D' ),
1329 array( 600, 'DC' ),
1330 array( 700, 'DCC' ),
1331 array( 800, 'DCCC' ),
1332 array( 900, 'CM' ),
1333 array( 999, 'CMXCIX' ),
1334 array( 1000, 'M' ),
1335 array( 1989, 'MCMLXXXIX' ),
1336 array( 2000, 'MM' ),
1337 array( 3000, 'MMM' ),
1338 array( 4000, 'MMMM' ),
1339 array( 5000, 'MMMMM' ),
1340 array( 6000, 'MMMMMM' ),
1341 array( 7000, 'MMMMMMM' ),
1342 array( 8000, 'MMMMMMMM' ),
1343 array( 9000, 'MMMMMMMMM' ),
1344 array( 9999, 'MMMMMMMMMCMXCIX' ),
1345 array( 10000, 'MMMMMMMMMM' ),
1346 );
1347 }
1348
1349 /**
1350 * @dataProvider providePluralData
1351 */
1352 function testConvertPlural( $expected, $number, $forms ) {
1353 $chosen = $this->getLang()->convertPlural( $number, $forms );
1354 $this->assertEquals( $expected, $chosen );
1355 }
1356
1357 public static function providePluralData() {
1358 // Params are: [expected text, number given, [the plural forms]]
1359 return array(
1360 array( 'plural', 0, array(
1361 'singular', 'plural'
1362 ) ),
1363 array( 'explicit zero', 0, array(
1364 '0=explicit zero', 'singular', 'plural'
1365 ) ),
1366 array( 'explicit one', 1, array(
1367 'singular', 'plural', '1=explicit one',
1368 ) ),
1369 array( 'singular', 1, array(
1370 'singular', 'plural', '0=explicit zero',
1371 ) ),
1372 array( 'plural', 3, array(
1373 '0=explicit zero', '1=explicit one', 'singular', 'plural'
1374 ) ),
1375 array( 'explicit eleven', 11, array(
1376 'singular', 'plural', '11=explicit eleven',
1377 ) ),
1378 array( 'plural', 12, array(
1379 'singular', 'plural', '11=explicit twelve',
1380 ) ),
1381 array( 'plural', 12, array(
1382 'singular', 'plural', '=explicit form',
1383 ) ),
1384 array( 'other', 2, array(
1385 'kissa=kala', '1=2=3', 'other',
1386 ) ),
1387 );
1388 }
1389
1390 /**
1391 * @covers Language::translateBlockExpiry()
1392 * @dataProvider provideTranslateBlockExpiry
1393 */
1394 function testTranslateBlockExpiry( $expectedData, $str, $desc ) {
1395 $lang = $this->getLang();
1396 if ( is_array( $expectedData ) ) {
1397 list( $func, $arg ) = $expectedData;
1398 $expected = $lang->$func( $arg );
1399 } else {
1400 $expected = $expectedData;
1401 }
1402 $this->assertEquals( $expected, $lang->translateBlockExpiry( $str ), $desc );
1403 }
1404
1405 public static function provideTranslateBlockExpiry() {
1406 return array(
1407 array( '2 hours', '2 hours', 'simple data from ipboptions' ),
1408 array( 'indefinite', 'infinite', 'infinite from ipboptions' ),
1409 array( 'indefinite', 'infinity', 'alternative infinite from ipboptions' ),
1410 array( 'indefinite', 'indefinite', 'another alternative infinite from ipboptions' ),
1411 array( array( 'formatDuration', 1023 * 60 * 60 ), '1023 hours', 'relative' ),
1412 array( array( 'formatDuration', -1023 ), '-1023 seconds', 'negative relative' ),
1413 array( array( 'formatDuration', 0 ), 'now', 'now' ),
1414 array( array( 'timeanddate', '20120102070000' ), '2012-1-1 7:00 +1 day', 'mixed, handled as absolute' ),
1415 array( array( 'timeanddate', '19910203040506' ), '1991-2-3 4:05:06', 'absolute' ),
1416 array( array( 'timeanddate', '19700101000000' ), '1970-1-1 0:00:00', 'absolute at epoch' ),
1417 array( array( 'timeanddate', '19691231235959' ), '1969-12-31 23:59:59', 'time before epoch' ),
1418 array( 'dummy', 'dummy', 'return garbage as is' ),
1419 );
1420 }
1421
1422 /**
1423 * @covers Language::commafy()
1424 * @dataProvider provideCommafyData
1425 */
1426 function testCommafy( $number, $numbersWithCommas ) {
1427 $this->assertEquals(
1428 $numbersWithCommas,
1429 $this->getLang()->commafy( $number ),
1430 "commafy('$number')"
1431 );
1432 }
1433
1434 public static function provideCommafyData() {
1435 return array(
1436 array( 1, '1' ),
1437 array( 10, '10' ),
1438 array( 100, '100' ),
1439 array( 1000, '1,000' ),
1440 array( 10000, '10,000' ),
1441 array( 100000, '100,000' ),
1442 array( 1000000, '1,000,000' ),
1443 array( 1.0001, '1.0001' ),
1444 array( 10.0001, '10.0001' ),
1445 array( 100.0001, '100.0001' ),
1446 array( 1000.0001, '1,000.0001' ),
1447 array( 10000.0001, '10,000.0001' ),
1448 array( 100000.0001, '100,000.0001' ),
1449 array( 1000000.0001, '1,000,000.0001' ),
1450 );
1451 }
1452
1453 function testListToText() {
1454 $lang = $this->getLang();
1455 $and = $lang->getMessageFromDB( 'and' );
1456 $s = $lang->getMessageFromDB( 'word-separator' );
1457 $c = $lang->getMessageFromDB( 'comma-separator' );
1458
1459 $this->assertEquals( '', $lang->listToText( array() ) );
1460 $this->assertEquals( 'a', $lang->listToText( array( 'a' ) ) );
1461 $this->assertEquals( "a{$and}{$s}b", $lang->listToText( array( 'a', 'b' ) ) );
1462 $this->assertEquals( "a{$c}b{$and}{$s}c", $lang->listToText( array( 'a', 'b', 'c' ) ) );
1463 $this->assertEquals( "a{$c}b{$c}c{$and}{$s}d", $lang->listToText( array( 'a', 'b', 'c', 'd' ) ) );
1464 }
1465
1466 /**
1467 * @dataProvider provideIsSupportedLanguage
1468 */
1469 function testIsSupportedLanguage( $code, $expected, $comment ) {
1470 $this->assertEquals( $expected, Language::isSupportedLanguage( $code ), $comment );
1471 }
1472
1473 public static function provideIsSupportedLanguage() {
1474 return array(
1475 array( 'en', true, 'is supported language' ),
1476 array( 'fi', true, 'is supported language' ),
1477 array( 'bunny', false, 'is not supported language' ),
1478 array( 'FI', false, 'is not supported language, input should be in lower case' ),
1479 );
1480 }
1481 }