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