Add support for new Japanese era name "Reiwa"
[lhc/web/wiklou.git] / tests / phpunit / languages / LanguageTest.php
1 <?php
2
3 class LanguageTest extends LanguageClassesTestCase {
4 /**
5 * @covers Language::convertDoubleWidth
6 * @covers Language::normalizeForSearch
7 */
8 public function testLanguageConvertDoubleWidthToSingleWidth() {
9 $this->assertEquals(
10 "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
11 $this->getLang()->normalizeForSearch(
12 "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
13 ),
14 'convertDoubleWidth() with the full alphabet and digits'
15 );
16 }
17
18 /**
19 * @dataProvider provideFormattableTimes
20 * @covers Language::formatTimePeriod
21 */
22 public function testFormatTimePeriod( $seconds, $format, $expected, $desc ) {
23 $this->assertEquals( $expected, $this->getLang()->formatTimePeriod( $seconds, $format ), $desc );
24 }
25
26 public static function provideFormattableTimes() {
27 return [
28 [
29 9.45,
30 [],
31 '9.5 s',
32 'formatTimePeriod() rounding (<10s)'
33 ],
34 [
35 9.45,
36 [ 'noabbrevs' => true ],
37 '9.5 seconds',
38 'formatTimePeriod() rounding (<10s)'
39 ],
40 [
41 9.95,
42 [],
43 '10 s',
44 'formatTimePeriod() rounding (<10s)'
45 ],
46 [
47 9.95,
48 [ 'noabbrevs' => true ],
49 '10 seconds',
50 'formatTimePeriod() rounding (<10s)'
51 ],
52 [
53 59.55,
54 [],
55 '1 min 0 s',
56 'formatTimePeriod() rounding (<60s)'
57 ],
58 [
59 59.55,
60 [ 'noabbrevs' => true ],
61 '1 minute 0 seconds',
62 'formatTimePeriod() rounding (<60s)'
63 ],
64 [
65 119.55,
66 [],
67 '2 min 0 s',
68 'formatTimePeriod() rounding (<1h)'
69 ],
70 [
71 119.55,
72 [ 'noabbrevs' => true ],
73 '2 minutes 0 seconds',
74 'formatTimePeriod() rounding (<1h)'
75 ],
76 [
77 3599.55,
78 [],
79 '1 h 0 min 0 s',
80 'formatTimePeriod() rounding (<1h)'
81 ],
82 [
83 3599.55,
84 [ 'noabbrevs' => true ],
85 '1 hour 0 minutes 0 seconds',
86 'formatTimePeriod() rounding (<1h)'
87 ],
88 [
89 7199.55,
90 [],
91 '2 h 0 min 0 s',
92 'formatTimePeriod() rounding (>=1h)'
93 ],
94 [
95 7199.55,
96 [ 'noabbrevs' => true ],
97 '2 hours 0 minutes 0 seconds',
98 'formatTimePeriod() rounding (>=1h)'
99 ],
100 [
101 7199.55,
102 'avoidseconds',
103 '2 h 0 min',
104 'formatTimePeriod() rounding (>=1h), avoidseconds'
105 ],
106 [
107 7199.55,
108 [ 'avoid' => 'avoidseconds', 'noabbrevs' => true ],
109 '2 hours 0 minutes',
110 'formatTimePeriod() rounding (>=1h), avoidseconds'
111 ],
112 [
113 7199.55,
114 'avoidminutes',
115 '2 h 0 min',
116 'formatTimePeriod() rounding (>=1h), avoidminutes'
117 ],
118 [
119 7199.55,
120 [ 'avoid' => 'avoidminutes', 'noabbrevs' => true ],
121 '2 hours 0 minutes',
122 'formatTimePeriod() rounding (>=1h), avoidminutes'
123 ],
124 [
125 172799.55,
126 'avoidseconds',
127 '48 h 0 min',
128 'formatTimePeriod() rounding (=48h), avoidseconds'
129 ],
130 [
131 172799.55,
132 [ 'avoid' => 'avoidseconds', 'noabbrevs' => true ],
133 '48 hours 0 minutes',
134 'formatTimePeriod() rounding (=48h), avoidseconds'
135 ],
136 [
137 259199.55,
138 'avoidminutes',
139 '3 d 0 h',
140 'formatTimePeriod() rounding (>48h), avoidminutes'
141 ],
142 [
143 259199.55,
144 [ 'avoid' => 'avoidminutes', 'noabbrevs' => true ],
145 '3 days 0 hours',
146 'formatTimePeriod() rounding (>48h), avoidminutes'
147 ],
148 [
149 176399.55,
150 'avoidseconds',
151 '2 d 1 h 0 min',
152 'formatTimePeriod() rounding (>48h), avoidseconds'
153 ],
154 [
155 176399.55,
156 [ 'avoid' => 'avoidseconds', 'noabbrevs' => true ],
157 '2 days 1 hour 0 minutes',
158 'formatTimePeriod() rounding (>48h), avoidseconds'
159 ],
160 [
161 176399.55,
162 'avoidminutes',
163 '2 d 1 h',
164 'formatTimePeriod() rounding (>48h), avoidminutes'
165 ],
166 [
167 176399.55,
168 [ 'avoid' => 'avoidminutes', 'noabbrevs' => true ],
169 '2 days 1 hour',
170 'formatTimePeriod() rounding (>48h), avoidminutes'
171 ],
172 [
173 259199.55,
174 'avoidseconds',
175 '3 d 0 h 0 min',
176 'formatTimePeriod() rounding (>48h), avoidseconds'
177 ],
178 [
179 259199.55,
180 [ 'avoid' => 'avoidseconds', 'noabbrevs' => true ],
181 '3 days 0 hours 0 minutes',
182 'formatTimePeriod() rounding (>48h), avoidseconds'
183 ],
184 [
185 172801.55,
186 'avoidseconds',
187 '2 d 0 h 0 min',
188 'formatTimePeriod() rounding, (>48h), avoidseconds'
189 ],
190 [
191 172801.55,
192 [ 'avoid' => 'avoidseconds', 'noabbrevs' => true ],
193 '2 days 0 hours 0 minutes',
194 'formatTimePeriod() rounding, (>48h), avoidseconds'
195 ],
196 [
197 176460.55,
198 [],
199 '2 d 1 h 1 min 1 s',
200 'formatTimePeriod() rounding, recursion, (>48h)'
201 ],
202 [
203 176460.55,
204 [ 'noabbrevs' => true ],
205 '2 days 1 hour 1 minute 1 second',
206 'formatTimePeriod() rounding, recursion, (>48h)'
207 ],
208 ];
209 }
210
211 /**
212 * @covers Language::truncateForDatabase
213 * @covers Language::truncateInternal
214 */
215 public function testTruncateForDatabase() {
216 $this->assertEquals(
217 "XXX",
218 $this->getLang()->truncateForDatabase( "1234567890", 0, 'XXX' ),
219 'truncate prefix, len 0, small ellipsis'
220 );
221
222 $this->assertEquals(
223 "12345XXX",
224 $this->getLang()->truncateForDatabase( "1234567890", 8, 'XXX' ),
225 'truncate prefix, small ellipsis'
226 );
227
228 $this->assertEquals(
229 "123456789",
230 $this->getLang()->truncateForDatabase( "123456789", 5, 'XXXXXXXXXXXXXXX' ),
231 'truncate prefix, large ellipsis'
232 );
233
234 $this->assertEquals(
235 "XXX67890",
236 $this->getLang()->truncateForDatabase( "1234567890", -8, 'XXX' ),
237 'truncate suffix, small ellipsis'
238 );
239
240 $this->assertEquals(
241 "123456789",
242 $this->getLang()->truncateForDatabase( "123456789", -5, 'XXXXXXXXXXXXXXX' ),
243 'truncate suffix, large ellipsis'
244 );
245 $this->assertEquals(
246 "123XXX",
247 $this->getLang()->truncateForDatabase( "123 ", 9, 'XXX' ),
248 'truncate prefix, with spaces'
249 );
250 $this->assertEquals(
251 "12345XXX",
252 $this->getLang()->truncateForDatabase( "12345 8", 11, 'XXX' ),
253 'truncate prefix, with spaces and non-space ending'
254 );
255 $this->assertEquals(
256 "XXX234",
257 $this->getLang()->truncateForDatabase( "1 234", -8, 'XXX' ),
258 'truncate suffix, with spaces'
259 );
260 $this->assertEquals(
261 "12345XXX",
262 $this->getLang()->truncateForDatabase( "1234567890", 5, 'XXX', false ),
263 'truncate without adjustment'
264 );
265 $this->assertEquals(
266 "泰乐菌...",
267 $this->getLang()->truncateForDatabase( "泰乐菌素123456789", 11, '...', false ),
268 'truncate does not chop Unicode characters in half'
269 );
270 $this->assertEquals(
271 "\n泰乐菌...",
272 $this->getLang()->truncateForDatabase( "\n泰乐菌素123456789", 12, '...', false ),
273 'truncate does not chop Unicode characters in half if there is a preceding newline'
274 );
275 }
276
277 /**
278 * @dataProvider provideTruncateData
279 * @covers Language::truncateForVisual
280 * @covers Language::truncateInternal
281 */
282 public function testTruncateForVisual(
283 $expected, $string, $length, $ellipsis = '...', $adjustLength = true
284 ) {
285 $this->assertEquals(
286 $expected,
287 $this->getLang()->truncateForVisual( $string, $length, $ellipsis, $adjustLength )
288 );
289 }
290
291 /**
292 * @return array Format is ($expected, $string, $length, $ellipsis, $adjustLength)
293 */
294 public static function provideTruncateData() {
295 return [
296 [ "XXX", "тестирам да ли ради", 0, "XXX" ],
297 [ "testnXXX", "testni scenarij", 8, "XXX" ],
298 [ "حالة اختبار", "حالة اختبار", 5, "XXXXXXXXXXXXXXX" ],
299 [ "XXXедент", "прецедент", -8, "XXX" ],
300 [ "XXപിൾ", "ആപ്പിൾ", -5, "XX" ],
301 [ "神秘XXX", "神秘 ", 9, "XXX" ],
302 [ "ΔημιουργXXX", "Δημιουργία Σύμπαντος", 11, "XXX" ],
303 [ "XXXの家です", "地球は私たちの唯 の家です", -8, "XXX" ],
304 [ "زندگیXXX", "زندگی زیباست", 6, "XXX", false ],
305 [ "ცხოვრება...", "ცხოვრება არის საოცარი", 8, "...", false ],
306 [ "\nທ່ານ...", "\nທ່ານບໍ່ຮູ້ຫນັງສື", 5, "...", false ],
307 ];
308 }
309
310 /**
311 * @dataProvider provideHTMLTruncateData
312 * @covers Language::truncateHTML
313 */
314 public function testTruncateHtml( $len, $ellipsis, $input, $expected ) {
315 // Actual HTML...
316 $this->assertEquals(
317 $expected,
318 $this->getLang()->truncateHtml( $input, $len, $ellipsis )
319 );
320 }
321
322 /**
323 * @return array Format is ($len, $ellipsis, $input, $expected)
324 */
325 public static function provideHTMLTruncateData() {
326 return [
327 [ 0, 'XXX', "1234567890", "XXX" ],
328 [ 8, 'XXX', "1234567890", "12345XXX" ],
329 [ 5, 'XXXXXXXXXXXXXXX', '1234567890', "1234567890" ],
330 [ 2, '***',
331 '<p><span style="font-weight:bold;"></span></p>',
332 '<p><span style="font-weight:bold;"></span></p>',
333 ],
334 [ 2, '***',
335 '<p><span style="font-weight:bold;">123456789</span></p>',
336 '<p><span style="font-weight:bold;">***</span></p>',
337 ],
338 [ 2, '***',
339 '<p><span style="font-weight:bold;">&nbsp;23456789</span></p>',
340 '<p><span style="font-weight:bold;">***</span></p>',
341 ],
342 [ 3, '***',
343 '<p><span style="font-weight:bold;">123456789</span></p>',
344 '<p><span style="font-weight:bold;">***</span></p>',
345 ],
346 [ 4, '***',
347 '<p><span style="font-weight:bold;">123456789</span></p>',
348 '<p><span style="font-weight:bold;">1***</span></p>',
349 ],
350 [ 5, '***',
351 '<tt><span style="font-weight:bold;">123456789</span></tt>',
352 '<tt><span style="font-weight:bold;">12***</span></tt>',
353 ],
354 [ 6, '***',
355 '<p><a href="www.mediawiki.org">123456789</a></p>',
356 '<p><a href="www.mediawiki.org">123***</a></p>',
357 ],
358 [ 6, '***',
359 '<p><a href="www.mediawiki.org">12&nbsp;456789</a></p>',
360 '<p><a href="www.mediawiki.org">12&nbsp;***</a></p>',
361 ],
362 [ 7, '***',
363 '<small><span style="font-weight:bold;">123<p id="#moo">456</p>789</span></small>',
364 '<small><span style="font-weight:bold;">123<p id="#moo">4***</p></span></small>',
365 ],
366 [ 8, '***',
367 '<div><span style="font-weight:bold;">123<span>4</span>56789</span></div>',
368 '<div><span style="font-weight:bold;">123<span>4</span>5***</span></div>',
369 ],
370 [ 9, '***',
371 '<p><table style="font-weight:bold;"><tr><td>123456789</td></tr></table></p>',
372 '<p><table style="font-weight:bold;"><tr><td>123456789</td></tr></table></p>',
373 ],
374 [ 10, '***',
375 '<p><font style="font-weight:bold;">123456789</font></p>',
376 '<p><font style="font-weight:bold;">123456789</font></p>',
377 ],
378 ];
379 }
380
381 /**
382 * Test Language::isWellFormedLanguageTag()
383 * @dataProvider provideWellFormedLanguageTags
384 * @covers Language::isWellFormedLanguageTag
385 */
386 public function testWellFormedLanguageTag( $code, $message = '' ) {
387 $this->assertTrue(
388 Language::isWellFormedLanguageTag( $code ),
389 "validating code $code $message"
390 );
391 }
392
393 /**
394 * The test cases are based on the tests in the GaBuZoMeu parser
395 * written by Stéphane Bortzmeyer <bortzmeyer@nic.fr>
396 * and distributed as free software, under the GNU General Public Licence.
397 * http://www.bortzmeyer.org/gabuzomeu-parsing-language-tags.html
398 */
399 public static function provideWellFormedLanguageTags() {
400 return [
401 [ 'fr', 'two-letter code' ],
402 [ 'fr-latn', 'two-letter code with lower case script code' ],
403 [ 'fr-Latn-FR', 'two-letter code with title case script code and uppercase country code' ],
404 [ 'fr-Latn-419', 'two-letter code with title case script code and region number' ],
405 [ 'fr-FR', 'two-letter code with uppercase' ],
406 [ 'ax-TZ', 'Not in the registry, but well-formed' ],
407 [ 'fr-shadok', 'two-letter code with variant' ],
408 [ 'fr-y-myext-myext2', 'non-x singleton' ],
409 [ 'fra-Latn', 'ISO 639 can be 3-letters' ],
410 [ 'fra', 'three-letter language code' ],
411 [ 'fra-FX', 'three-letter language code with country code' ],
412 [ 'i-klingon', 'grandfathered with singleton' ],
413 [ 'I-kLINgon', 'tags are case-insensitive...' ],
414 [ 'no-bok', 'grandfathered without singleton' ],
415 [ 'i-enochian', 'Grandfathered' ],
416 [ 'x-fr-CH', 'private use' ],
417 [ 'es-419', 'two-letter code with region number' ],
418 [ 'en-Latn-GB-boont-r-extended-sequence-x-private', 'weird, but well-formed' ],
419 [ 'ab-x-abc-x-abc', 'anything goes after x' ],
420 [ 'ab-x-abc-a-a', 'anything goes after x, including several non-x singletons' ],
421 [ 'i-default', 'grandfathered' ],
422 [ 'abcd-Latn', 'Language of 4 chars reserved for future use' ],
423 [ 'AaBbCcDd-x-y-any-x', 'Language of 5-8 chars, registered' ],
424 [ 'de-CH-1901', 'with country and year' ],
425 [ 'en-US-x-twain', 'with country and singleton' ],
426 [ 'zh-cmn', 'three-letter variant' ],
427 [ 'zh-cmn-Hant', 'three-letter variant and script' ],
428 [ 'zh-cmn-Hant-HK', 'three-letter variant, script and country' ],
429 [ 'xr-p-lze', 'Extension' ],
430 ];
431 }
432
433 /**
434 * Negative test for Language::isWellFormedLanguageTag()
435 * @dataProvider provideMalformedLanguageTags
436 * @covers Language::isWellFormedLanguageTag
437 */
438 public function testMalformedLanguageTag( $code, $message = '' ) {
439 $this->assertFalse(
440 Language::isWellFormedLanguageTag( $code ),
441 "validating that code $code is a malformed language tag - $message"
442 );
443 }
444
445 /**
446 * The test cases are based on the tests in the GaBuZoMeu parser
447 * written by Stéphane Bortzmeyer <bortzmeyer@nic.fr>
448 * and distributed as free software, under the GNU General Public Licence.
449 * http://www.bortzmeyer.org/gabuzomeu-parsing-language-tags.html
450 */
451 public static function provideMalformedLanguageTags() {
452 return [
453 [ 'f', 'language too short' ],
454 [ 'f-Latn', 'language too short with script' ],
455 [ 'xr-lxs-qut', 'variants too short' ], # extlangS
456 [ 'fr-Latn-F', 'region too short' ],
457 [ 'a-value', 'language too short with region' ],
458 [ 'tlh-a-b-foo', 'valid three-letter with wrong variant' ],
459 [
460 'i-notexist',
461 'grandfathered but not registered: invalid, even if we only test well-formedness'
462 ],
463 [ 'abcdefghi-012345678', 'numbers too long' ],
464 [ 'ab-abc-abc-abc-abc', 'invalid extensions' ],
465 [ 'ab-abcd-abc', 'invalid extensions' ],
466 [ 'ab-ab-abc', 'invalid extensions' ],
467 [ 'ab-123-abc', 'invalid extensions' ],
468 [ 'a-Hant-ZH', 'short language with valid extensions' ],
469 [ 'a1-Hant-ZH', 'invalid character in language' ],
470 [ 'ab-abcde-abc', 'invalid extensions' ],
471 [ 'ab-1abc-abc', 'invalid characters in extensions' ],
472 [ 'ab-ab-abcd', 'invalid order of extensions' ],
473 [ 'ab-123-abcd', 'invalid order of extensions' ],
474 [ 'ab-abcde-abcd', 'invalid extensions' ],
475 [ 'ab-1abc-abcd', 'invalid characters in extensions' ],
476 [ 'ab-a-b', 'extensions too short' ],
477 [ 'ab-a-x', 'extensions too short, even with singleton' ],
478 [ 'ab--ab', 'two separators' ],
479 [ 'ab-abc-', 'separator in the end' ],
480 [ '-ab-abc', 'separator in the beginning' ],
481 [ 'abcd-efg', 'language too long' ],
482 [ 'aabbccddE', 'tag too long' ],
483 [ 'pa_guru', 'A tag with underscore is invalid in strict mode' ],
484 [ 'de-f', 'subtag too short' ],
485 ];
486 }
487
488 /**
489 * Negative test for Language::isWellFormedLanguageTag()
490 * @covers Language::isWellFormedLanguageTag
491 */
492 public function testLenientLanguageTag() {
493 $this->assertTrue(
494 Language::isWellFormedLanguageTag( 'pa_guru', true ),
495 'pa_guru is a well-formed language tag in lenient mode'
496 );
497 }
498
499 /**
500 * Test Language::isValidBuiltInCode()
501 * @dataProvider provideLanguageCodes
502 * @covers Language::isValidBuiltInCode
503 */
504 public function testBuiltInCodeValidation( $code, $expected, $message = '' ) {
505 $this->assertEquals( $expected,
506 (bool)Language::isValidBuiltInCode( $code ),
507 "validating code $code $message"
508 );
509 }
510
511 public static function provideLanguageCodes() {
512 return [
513 [ 'fr', true, 'Two letters, minor case' ],
514 [ 'EN', false, 'Two letters, upper case' ],
515 [ 'tyv', true, 'Three letters' ],
516 [ 'be-tarask', true, 'With dash' ],
517 [ 'be-x-old', true, 'With extension (two dashes)' ],
518 [ 'be_tarask', false, 'Reject underscores' ],
519 ];
520 }
521
522 /**
523 * Test Language::isKnownLanguageTag()
524 * @dataProvider provideKnownLanguageTags
525 * @covers Language::isKnownLanguageTag
526 */
527 public function testKnownLanguageTag( $code, $message = '' ) {
528 $this->assertTrue(
529 (bool)Language::isKnownLanguageTag( $code ),
530 "validating code $code - $message"
531 );
532 }
533
534 public static function provideKnownLanguageTags() {
535 return [
536 [ 'fr', 'simple code' ],
537 [ 'bat-smg', 'an MW legacy tag' ],
538 [ 'sgs', 'an internal standard MW name, for which a legacy tag is used externally' ],
539 ];
540 }
541
542 /**
543 * @covers Language::isKnownLanguageTag
544 */
545 public function testKnownCldrLanguageTag() {
546 if ( !class_exists( 'LanguageNames' ) ) {
547 $this->markTestSkipped( 'The LanguageNames class is not available. '
548 . 'The CLDR extension is probably not installed.' );
549 }
550
551 $this->assertTrue(
552 (bool)Language::isKnownLanguageTag( 'pal' ),
553 'validating code "pal" an ancient language, which probably will '
554 . 'not appear in Names.php, but appears in CLDR in English'
555 );
556 }
557
558 /**
559 * Negative tests for Language::isKnownLanguageTag()
560 * @dataProvider provideUnKnownLanguageTags
561 * @covers Language::isKnownLanguageTag
562 */
563 public function testUnknownLanguageTag( $code, $message = '' ) {
564 $this->assertFalse(
565 (bool)Language::isKnownLanguageTag( $code ),
566 "checking that code $code is invalid - $message"
567 );
568 }
569
570 public static function provideUnknownLanguageTags() {
571 return [
572 [ 'mw', 'non-existent two-letter code' ],
573 [ 'foo"<bar', 'very invalid language code' ],
574 ];
575 }
576
577 /**
578 * Test too short timestamp
579 * @expectedException MWException
580 * @covers Language::sprintfDate
581 */
582 public function testSprintfDateTooShortTimestamp() {
583 $this->getLang()->sprintfDate( 'xiY', '1234567890123' );
584 }
585
586 /**
587 * Test too long timestamp
588 * @expectedException MWException
589 * @covers Language::sprintfDate
590 */
591 public function testSprintfDateTooLongTimestamp() {
592 $this->getLang()->sprintfDate( 'xiY', '123456789012345' );
593 }
594
595 /**
596 * Test too short timestamp
597 * @expectedException MWException
598 * @covers Language::sprintfDate
599 */
600 public function testSprintfDateNotAllDigitTimestamp() {
601 $this->getLang()->sprintfDate( 'xiY', '-1234567890123' );
602 }
603
604 /**
605 * @dataProvider provideSprintfDateSamples
606 * @covers Language::sprintfDate
607 */
608 public function testSprintfDate( $format, $ts, $expected, $msg ) {
609 $ttl = null;
610 $this->assertEquals(
611 $expected,
612 $this->getLang()->sprintfDate( $format, $ts, null, $ttl ),
613 "sprintfDate('$format', '$ts'): $msg"
614 );
615 if ( $ttl ) {
616 $dt = new DateTime( $ts );
617 $lastValidTS = $dt->add( new DateInterval( 'PT' . ( $ttl - 1 ) . 'S' ) )->format( 'YmdHis' );
618 $this->assertEquals(
619 $expected,
620 $this->getLang()->sprintfDate( $format, $lastValidTS, null ),
621 "sprintfDate('$format', '$ts'): TTL $ttl too high (output was different at $lastValidTS)"
622 );
623 } else {
624 // advance the time enough to make all of the possible outputs different (except possibly L)
625 $dt = new DateTime( $ts );
626 $newTS = $dt->add( new DateInterval( 'P1Y1M8DT13H1M1S' ) )->format( 'YmdHis' );
627 $this->assertEquals(
628 $expected,
629 $this->getLang()->sprintfDate( $format, $newTS, null ),
630 "sprintfDate('$format', '$ts'): Missing TTL (output was different at $newTS)"
631 );
632 }
633 }
634
635 /**
636 * sprintfDate should always use UTC when no zone is given.
637 * @dataProvider provideSprintfDateSamples
638 * @covers Language::sprintfDate
639 */
640 public function testSprintfDateNoZone( $format, $ts, $expected, $ignore, $msg ) {
641 $oldTZ = date_default_timezone_get();
642 $res = date_default_timezone_set( 'Asia/Seoul' );
643 if ( !$res ) {
644 $this->markTestSkipped( "Error setting Timezone" );
645 }
646
647 $this->assertEquals(
648 $expected,
649 $this->getLang()->sprintfDate( $format, $ts ),
650 "sprintfDate('$format', '$ts'): $msg"
651 );
652
653 date_default_timezone_set( $oldTZ );
654 }
655
656 /**
657 * sprintfDate should use passed timezone
658 * @dataProvider provideSprintfDateSamples
659 * @covers Language::sprintfDate
660 */
661 public function testSprintfDateTZ( $format, $ts, $ignore, $expected, $msg ) {
662 $tz = new DateTimeZone( 'Asia/Seoul' );
663 if ( !$tz ) {
664 $this->markTestSkipped( "Error getting Timezone" );
665 }
666
667 $this->assertEquals(
668 $expected,
669 $this->getLang()->sprintfDate( $format, $ts, $tz ),
670 "sprintfDate('$format', '$ts', 'Asia/Seoul'): $msg"
671 );
672 }
673
674 /**
675 * sprintfDate should only calculate a TTL if the caller is going to use it.
676 * @covers Language::sprintfDate
677 */
678 public function testSprintfDateNoTtlIfNotNeeded() {
679 $noTtl = 'unused'; // Value used to represent that the caller didn't pass a variable in.
680 $ttl = null;
681 $this->getLang()->sprintfDate( 'YmdHis', wfTimestampNow(), null, $noTtl );
682 $this->getLang()->sprintfDate( 'YmdHis', wfTimestampNow(), null, $ttl );
683
684 $this->assertSame(
685 'unused',
686 $noTtl,
687 'If the caller does not set the $ttl variable, do not compute it.'
688 );
689 $this->assertInternalType( 'int', $ttl, 'TTL should have been computed.' );
690 }
691
692 public static function provideSprintfDateSamples() {
693 return [
694 [
695 'xiY',
696 '20111212000000',
697 '1390', // note because we're testing English locale we get Latin-standard digits
698 '1390',
699 'Iranian calendar full year'
700 ],
701 [
702 'xiy',
703 '20111212000000',
704 '90',
705 '90',
706 'Iranian calendar short year'
707 ],
708 [
709 'o',
710 '20120101235000',
711 '2011',
712 '2011',
713 'ISO 8601 (week) year'
714 ],
715 [
716 'W',
717 '20120101235000',
718 '52',
719 '52',
720 'Week number'
721 ],
722 [
723 'W',
724 '20120102235000',
725 '1',
726 '1',
727 'Week number'
728 ],
729 [
730 'o-\\WW-N',
731 '20091231235000',
732 '2009-W53-4',
733 '2009-W53-4',
734 'leap week'
735 ],
736 // What follows is mostly copied from
737 // https://www.mediawiki.org/wiki/Help:Extension:ParserFunctions#.23time
738 [
739 'Y',
740 '20120102090705',
741 '2012',
742 '2012',
743 'Full year'
744 ],
745 [
746 'y',
747 '20120102090705',
748 '12',
749 '12',
750 '2 digit year'
751 ],
752 [
753 'L',
754 '20120102090705',
755 '1',
756 '1',
757 'Leap year'
758 ],
759 [
760 'n',
761 '20120102090705',
762 '1',
763 '1',
764 'Month index, not zero pad'
765 ],
766 [
767 'N',
768 '20120102090705',
769 '01',
770 '01',
771 'Month index. Zero pad'
772 ],
773 [
774 'M',
775 '20120102090705',
776 'Jan',
777 'Jan',
778 'Month abbrev'
779 ],
780 [
781 'F',
782 '20120102090705',
783 'January',
784 'January',
785 'Full month'
786 ],
787 [
788 'xg',
789 '20120102090705',
790 'January',
791 'January',
792 'Genitive month name (same in EN)'
793 ],
794 [
795 'j',
796 '20120102090705',
797 '2',
798 '2',
799 'Day of month (not zero pad)'
800 ],
801 [
802 'd',
803 '20120102090705',
804 '02',
805 '02',
806 'Day of month (zero-pad)'
807 ],
808 [
809 'z',
810 '20120102090705',
811 '1',
812 '1',
813 'Day of year (zero-indexed)'
814 ],
815 [
816 'D',
817 '20120102090705',
818 'Mon',
819 'Mon',
820 'Day of week (abbrev)'
821 ],
822 [
823 'l',
824 '20120102090705',
825 'Monday',
826 'Monday',
827 'Full day of week'
828 ],
829 [
830 'N',
831 '20120101090705',
832 '7',
833 '7',
834 'Day of week (Mon=1, Sun=7)'
835 ],
836 [
837 'w',
838 '20120101090705',
839 '0',
840 '0',
841 'Day of week (Sun=0, Sat=6)'
842 ],
843 [
844 'N',
845 '20120102090705',
846 '1',
847 '1',
848 'Day of week'
849 ],
850 [
851 'a',
852 '20120102090705',
853 'am',
854 'am',
855 'am vs pm'
856 ],
857 [
858 'A',
859 '20120102120000',
860 'PM',
861 'PM',
862 'AM vs PM'
863 ],
864 [
865 'a',
866 '20120102000000',
867 'am',
868 'am',
869 'AM vs PM'
870 ],
871 [
872 'g',
873 '20120102090705',
874 '9',
875 '9',
876 '12 hour, not Zero'
877 ],
878 [
879 'h',
880 '20120102090705',
881 '09',
882 '09',
883 '12 hour, zero padded'
884 ],
885 [
886 'G',
887 '20120102090705',
888 '9',
889 '9',
890 '24 hour, not zero'
891 ],
892 [
893 'H',
894 '20120102090705',
895 '09',
896 '09',
897 '24 hour, zero'
898 ],
899 [
900 'H',
901 '20120102110705',
902 '11',
903 '11',
904 '24 hour, zero'
905 ],
906 [
907 'i',
908 '20120102090705',
909 '07',
910 '07',
911 'Minutes'
912 ],
913 [
914 's',
915 '20120102090705',
916 '05',
917 '05',
918 'seconds'
919 ],
920 [
921 'U',
922 '20120102090705',
923 '1325495225',
924 '1325462825',
925 'unix time'
926 ],
927 [
928 't',
929 '20120102090705',
930 '31',
931 '31',
932 'Days in current month'
933 ],
934 [
935 'c',
936 '20120102090705',
937 '2012-01-02T09:07:05+00:00',
938 '2012-01-02T09:07:05+09:00',
939 'ISO 8601 timestamp'
940 ],
941 [
942 'r',
943 '20120102090705',
944 'Mon, 02 Jan 2012 09:07:05 +0000',
945 'Mon, 02 Jan 2012 09:07:05 +0900',
946 'RFC 5322'
947 ],
948 [
949 'e',
950 '20120102090705',
951 'UTC',
952 'Asia/Seoul',
953 'Timezone identifier'
954 ],
955 [
956 'I',
957 '19880602090705',
958 '0',
959 '1',
960 'DST indicator'
961 ],
962 [
963 'O',
964 '20120102090705',
965 '+0000',
966 '+0900',
967 'Timezone offset'
968 ],
969 [
970 'P',
971 '20120102090705',
972 '+00:00',
973 '+09:00',
974 'Timezone offset with colon'
975 ],
976 [
977 'T',
978 '20120102090705',
979 'UTC',
980 'KST',
981 'Timezone abbreviation'
982 ],
983 [
984 'Z',
985 '20120102090705',
986 '0',
987 '32400',
988 'Timezone offset in seconds'
989 ],
990 [
991 'xmj xmF xmn xmY',
992 '20120102090705',
993 '7 Safar 2 1433',
994 '7 Safar 2 1433',
995 'Islamic'
996 ],
997 [
998 'xij xiF xin xiY',
999 '20120102090705',
1000 '12 Dey 10 1390',
1001 '12 Dey 10 1390',
1002 'Iranian'
1003 ],
1004 [
1005 'xjj xjF xjn xjY',
1006 '20120102090705',
1007 '7 Tevet 4 5772',
1008 '7 Tevet 4 5772',
1009 'Hebrew'
1010 ],
1011 [
1012 'xjt',
1013 '20120102090705',
1014 '29',
1015 '29',
1016 'Hebrew number of days in month'
1017 ],
1018 [
1019 'xjx',
1020 '20120102090705',
1021 'Tevet',
1022 'Tevet',
1023 'Hebrew genitive month name (No difference in EN)'
1024 ],
1025 [
1026 'xkY',
1027 '20120102090705',
1028 '2555',
1029 '2555',
1030 'Thai year'
1031 ],
1032 [
1033 'xoY',
1034 '20120102090705',
1035 '101',
1036 '101',
1037 'Minguo'
1038 ],
1039 [
1040 'xtY',
1041 '20120102090705',
1042 '平成24',
1043 '平成24',
1044 'nengo'
1045 ],
1046 [
1047 'xtY',
1048 '20190430235959',
1049 '平成31',
1050 '平成31',
1051 'nengo - last day of heisei'
1052 ],
1053 [
1054 'xtY',
1055 '20190501000000',
1056 '令和元',
1057 '令和元',
1058 'nengo - first day of reiwa'
1059 ],
1060 [
1061 'xtY',
1062 '20200501000000',
1063 '令和2',
1064 '令和2',
1065 'nengo - second year of reiwa'
1066 ],
1067 [
1068 'xrxkYY',
1069 '20120102090705',
1070 'MMDLV2012',
1071 'MMDLV2012',
1072 'Roman numerals'
1073 ],
1074 [
1075 'xhxjYY',
1076 '20120102090705',
1077 \'תשע"ב2012',
1078 \'תשע"ב2012',
1079 'Hebrew numberals'
1080 ],
1081 [
1082 'xnY',
1083 '20120102090705',
1084 '2012',
1085 '2012',
1086 'Raw numerals (doesn\'t mean much in EN)'
1087 ],
1088 [
1089 '[[Y "(yea"\\r)]] \\"xx\\"',
1090 '20120102090705',
1091 '[[2012 (year)]] "x"',
1092 '[[2012 (year)]] "x"',
1093 'Various escaping'
1094 ],
1095
1096 ];
1097 }
1098
1099 /**
1100 * @dataProvider provideFormatSizes
1101 * @covers Language::formatSize
1102 */
1103 public function testFormatSize( $size, $expected, $msg ) {
1104 $this->assertEquals(
1105 $expected,
1106 $this->getLang()->formatSize( $size ),
1107 "formatSize('$size'): $msg"
1108 );
1109 }
1110
1111 public static function provideFormatSizes() {
1112 return [
1113 [
1114 0,
1115 "0 bytes",
1116 "Zero bytes"
1117 ],
1118 [
1119 1024,
1120 "1 KB",
1121 "1 kilobyte"
1122 ],
1123 [
1124 1024 * 1024,
1125 "1 MB",
1126 "1,024 megabytes"
1127 ],
1128 [
1129 1024 * 1024 * 1024,
1130 "1 GB",
1131 "1 gigabyte"
1132 ],
1133 [
1134 pow( 1024, 4 ),
1135 "1 TB",
1136 "1 terabyte"
1137 ],
1138 [
1139 pow( 1024, 5 ),
1140 "1 PB",
1141 "1 petabyte"
1142 ],
1143 [
1144 pow( 1024, 6 ),
1145 "1 EB",
1146 "1,024 exabyte"
1147 ],
1148 [
1149 pow( 1024, 7 ),
1150 "1 ZB",
1151 "1 zetabyte"
1152 ],
1153 [
1154 pow( 1024, 8 ),
1155 "1 YB",
1156 "1 yottabyte"
1157 ],
1158 // How big!? THIS BIG!
1159 ];
1160 }
1161
1162 /**
1163 * @dataProvider provideFormatBitrate
1164 * @covers Language::formatBitrate
1165 */
1166 public function testFormatBitrate( $bps, $expected, $msg ) {
1167 $this->assertEquals(
1168 $expected,
1169 $this->getLang()->formatBitrate( $bps ),
1170 "formatBitrate('$bps'): $msg"
1171 );
1172 }
1173
1174 public static function provideFormatBitrate() {
1175 return [
1176 [
1177 0,
1178 "0 bps",
1179 "0 bits per second"
1180 ],
1181 [
1182 999,
1183 "999 bps",
1184 "999 bits per second"
1185 ],
1186 [
1187 1000,
1188 "1 kbps",
1189 "1 kilobit per second"
1190 ],
1191 [
1192 1000 * 1000,
1193 "1 Mbps",
1194 "1 megabit per second"
1195 ],
1196 [
1197 pow( 10, 9 ),
1198 "1 Gbps",
1199 "1 gigabit per second"
1200 ],
1201 [
1202 pow( 10, 12 ),
1203 "1 Tbps",
1204 "1 terabit per second"
1205 ],
1206 [
1207 pow( 10, 15 ),
1208 "1 Pbps",
1209 "1 petabit per second"
1210 ],
1211 [
1212 pow( 10, 18 ),
1213 "1 Ebps",
1214 "1 exabit per second"
1215 ],
1216 [
1217 pow( 10, 21 ),
1218 "1 Zbps",
1219 "1 zetabit per second"
1220 ],
1221 [
1222 pow( 10, 24 ),
1223 "1 Ybps",
1224 "1 yottabit per second"
1225 ],
1226 [
1227 pow( 10, 27 ),
1228 "1,000 Ybps",
1229 "1,000 yottabits per second"
1230 ],
1231 ];
1232 }
1233
1234 /**
1235 * @dataProvider provideFormatDuration
1236 * @covers Language::formatDuration
1237 */
1238 public function testFormatDuration( $duration, $expected, $intervals = [] ) {
1239 $this->assertEquals(
1240 $expected,
1241 $this->getLang()->formatDuration( $duration, $intervals ),
1242 "formatDuration('$duration'): $expected"
1243 );
1244 }
1245
1246 public static function provideFormatDuration() {
1247 return [
1248 [
1249 0,
1250 '0 seconds',
1251 ],
1252 [
1253 1,
1254 '1 second',
1255 ],
1256 [
1257 2,
1258 '2 seconds',
1259 ],
1260 [
1261 60,
1262 '1 minute',
1263 ],
1264 [
1265 2 * 60,
1266 '2 minutes',
1267 ],
1268 [
1269 3600,
1270 '1 hour',
1271 ],
1272 [
1273 2 * 3600,
1274 '2 hours',
1275 ],
1276 [
1277 24 * 3600,
1278 '1 day',
1279 ],
1280 [
1281 2 * 86400,
1282 '2 days',
1283 ],
1284 [
1285 // ( 365 + ( 24 * 3 + 25 ) / 400 ) * 86400 = 31556952
1286 ( 365 + ( 24 * 3 + 25 ) / 400.0 ) * 86400,
1287 '1 year',
1288 ],
1289 [
1290 2 * 31556952,
1291 '2 years',
1292 ],
1293 [
1294 10 * 31556952,
1295 '1 decade',
1296 ],
1297 [
1298 20 * 31556952,
1299 '2 decades',
1300 ],
1301 [
1302 100 * 31556952,
1303 '1 century',
1304 ],
1305 [
1306 200 * 31556952,
1307 '2 centuries',
1308 ],
1309 [
1310 1000 * 31556952,
1311 '1 millennium',
1312 ],
1313 [
1314 2000 * 31556952,
1315 '2 millennia',
1316 ],
1317 [
1318 9001,
1319 '2 hours, 30 minutes and 1 second'
1320 ],
1321 [
1322 3601,
1323 '1 hour and 1 second'
1324 ],
1325 [
1326 31556952 + 2 * 86400 + 9000,
1327 '1 year, 2 days, 2 hours and 30 minutes'
1328 ],
1329 [
1330 42 * 1000 * 31556952 + 42,
1331 '42 millennia and 42 seconds'
1332 ],
1333 [
1334 60,
1335 '60 seconds',
1336 [ 'seconds' ],
1337 ],
1338 [
1339 61,
1340 '61 seconds',
1341 [ 'seconds' ],
1342 ],
1343 [
1344 1,
1345 '1 second',
1346 [ 'seconds' ],
1347 ],
1348 [
1349 31556952 + 2 * 86400 + 9000,
1350 '1 year, 2 days and 150 minutes',
1351 [ 'years', 'days', 'minutes' ],
1352 ],
1353 [
1354 42,
1355 '0 days',
1356 [ 'years', 'days' ],
1357 ],
1358 [
1359 31556952 + 2 * 86400 + 9000,
1360 '1 year, 2 days and 150 minutes',
1361 [ 'minutes', 'days', 'years' ],
1362 ],
1363 [
1364 42,
1365 '0 days',
1366 [ 'days', 'years' ],
1367 ],
1368 ];
1369 }
1370
1371 /**
1372 * @dataProvider provideCheckTitleEncodingData
1373 * @covers Language::checkTitleEncoding
1374 */
1375 public function testCheckTitleEncoding( $s ) {
1376 $this->assertEquals(
1377 $s,
1378 $this->getLang()->checkTitleEncoding( $s ),
1379 "checkTitleEncoding('$s')"
1380 );
1381 }
1382
1383 public static function provideCheckTitleEncodingData() {
1384 // phpcs:disable Generic.Files.LineLength
1385 return [
1386 [ "" ],
1387 [ "United States of America" ], // 7bit ASCII
1388 [ rawurldecode( "S%C3%A9rie%20t%C3%A9l%C3%A9vis%C3%A9e" ) ],
1389 [
1390 rawurldecode(
1391 "Acteur%7CAlbert%20Robbins%7CAnglais%7CAnn%20Donahue%7CAnthony%20E.%20Zuiker%7CCarol%20Mendelsohn"
1392 )
1393 ],
1394 // The following two data sets come from T38839. They fail if checkTitleEncoding uses a regexp to test for
1395 // valid UTF-8 encoding and the pcre.recursion_limit is low (like, say, 1024). They succeed if checkTitleEncoding
1396 // uses mb_check_encoding for its test.
1397 [
1398 rawurldecode(
1399 "Acteur%7CAlbert%20Robbins%7CAnglais%7CAnn%20Donahue%7CAnthony%20E.%20Zuiker%7CCarol%20Mendelsohn%7C"
1400 . "Catherine%20Willows%7CDavid%20Hodges%7CDavid%20Phillips%7CGil%20Grissom%7CGreg%20Sanders%7CHodges%7C"
1401 . "Internet%20Movie%20Database%7CJim%20Brass%7CLady%20Heather%7C"
1402 . "Les%20Experts%20(s%C3%A9rie%20t%C3%A9l%C3%A9vis%C3%A9e)%7CLes%20Experts%20:%20Manhattan%7C"
1403 . "Les%20Experts%20:%20Miami%7CListe%20des%20personnages%20des%20Experts%7C"
1404 . "Liste%20des%20%C3%A9pisodes%20des%20Experts%7CMod%C3%A8le%20discussion:Palette%20Les%20Experts%7C"
1405 . "Nick%20Stokes%7CPersonnage%20de%20fiction%7CPersonnage%20fictif%7CPersonnage%20de%20fiction%7C"
1406 . "Personnages%20r%C3%A9currents%20dans%20Les%20Experts%7CRaymond%20Langston%7CRiley%20Adams%7C"
1407 . "Saison%201%20des%20Experts%7CSaison%2010%20des%20Experts%7CSaison%2011%20des%20Experts%7C"
1408 . "Saison%2012%20des%20Experts%7CSaison%202%20des%20Experts%7CSaison%203%20des%20Experts%7C"
1409 . "Saison%204%20des%20Experts%7CSaison%205%20des%20Experts%7CSaison%206%20des%20Experts%7C"
1410 . "Saison%207%20des%20Experts%7CSaison%208%20des%20Experts%7CSaison%209%20des%20Experts%7C"
1411 . "Sara%20Sidle%7CSofia%20Curtis%7CS%C3%A9rie%20t%C3%A9l%C3%A9vis%C3%A9e%7CWallace%20Langham%7C"
1412 . "Warrick%20Brown%7CWendy%20Simms%7C%C3%89tats-Unis"
1413 ),
1414 ],
1415 [
1416 rawurldecode(
1417 "Mod%C3%A8le%3AArrondissements%20homonymes%7CMod%C3%A8le%3ABandeau%20standard%20pour%20page%20d'homonymie%7C"
1418 . "Mod%C3%A8le%3ABatailles%20homonymes%7CMod%C3%A8le%3ACantons%20homonymes%7C"
1419 . "Mod%C3%A8le%3ACommunes%20fran%C3%A7aises%20homonymes%7CMod%C3%A8le%3AFilms%20homonymes%7C"
1420 . "Mod%C3%A8le%3AGouvernements%20homonymes%7CMod%C3%A8le%3AGuerres%20homonymes%7CMod%C3%A8le%3AHomonymie%7C"
1421 . "Mod%C3%A8le%3AHomonymie%20bateau%7CMod%C3%A8le%3AHomonymie%20d'%C3%A9tablissements%20scolaires%20ou"
1422 . "%20universitaires%7CMod%C3%A8le%3AHomonymie%20d'%C3%AEles%7CMod%C3%A8le%3AHomonymie%20de%20clubs%20sportifs%7C"
1423 . "Mod%C3%A8le%3AHomonymie%20de%20comt%C3%A9s%7CMod%C3%A8le%3AHomonymie%20de%20monument%7C"
1424 . "Mod%C3%A8le%3AHomonymie%20de%20nom%20romain%7CMod%C3%A8le%3AHomonymie%20de%20parti%20politique%7C"
1425 . "Mod%C3%A8le%3AHomonymie%20de%20route%7CMod%C3%A8le%3AHomonymie%20dynastique%7C"
1426 . "Mod%C3%A8le%3AHomonymie%20vid%C3%A9oludique%7CMod%C3%A8le%3AHomonymie%20%C3%A9difice%20religieux%7C"
1427 . "Mod%C3%A8le%3AInternationalisation%7CMod%C3%A8le%3AIsom%C3%A9rie%7CMod%C3%A8le%3AParonymie%7C"
1428 . "Mod%C3%A8le%3APatronyme%7CMod%C3%A8le%3APatronyme%20basque%7CMod%C3%A8le%3APatronyme%20italien%7C"
1429 . "Mod%C3%A8le%3APatronymie%7CMod%C3%A8le%3APersonnes%20homonymes%7CMod%C3%A8le%3ASaints%20homonymes%7C"
1430 . "Mod%C3%A8le%3ATitres%20homonymes%7CMod%C3%A8le%3AToponymie%7CMod%C3%A8le%3AUnit%C3%A9s%20homonymes%7C"
1431 . "Mod%C3%A8le%3AVilles%20homonymes%7CMod%C3%A8le%3A%C3%89difices%20religieux%20homonymes"
1432 )
1433 ]
1434 ];
1435 // phpcs:enable
1436 }
1437
1438 /**
1439 * @dataProvider provideRomanNumeralsData
1440 * @covers Language::romanNumeral
1441 */
1442 public function testRomanNumerals( $num, $numerals ) {
1443 $this->assertEquals(
1444 $numerals,
1445 Language::romanNumeral( $num ),
1446 "romanNumeral('$num')"
1447 );
1448 }
1449
1450 public static function provideRomanNumeralsData() {
1451 return [
1452 [ 1, 'I' ],
1453 [ 2, 'II' ],
1454 [ 3, 'III' ],
1455 [ 4, 'IV' ],
1456 [ 5, 'V' ],
1457 [ 6, 'VI' ],
1458 [ 7, 'VII' ],
1459 [ 8, 'VIII' ],
1460 [ 9, 'IX' ],
1461 [ 10, 'X' ],
1462 [ 20, 'XX' ],
1463 [ 30, 'XXX' ],
1464 [ 40, 'XL' ],
1465 [ 49, 'XLIX' ],
1466 [ 50, 'L' ],
1467 [ 60, 'LX' ],
1468 [ 70, 'LXX' ],
1469 [ 80, 'LXXX' ],
1470 [ 90, 'XC' ],
1471 [ 99, 'XCIX' ],
1472 [ 100, 'C' ],
1473 [ 200, 'CC' ],
1474 [ 300, 'CCC' ],
1475 [ 400, 'CD' ],
1476 [ 500, 'D' ],
1477 [ 600, 'DC' ],
1478 [ 700, 'DCC' ],
1479 [ 800, 'DCCC' ],
1480 [ 900, 'CM' ],
1481 [ 999, 'CMXCIX' ],
1482 [ 1000, 'M' ],
1483 [ 1989, 'MCMLXXXIX' ],
1484 [ 2000, 'MM' ],
1485 [ 3000, 'MMM' ],
1486 [ 4000, 'MMMM' ],
1487 [ 5000, 'MMMMM' ],
1488 [ 6000, 'MMMMMM' ],
1489 [ 7000, 'MMMMMMM' ],
1490 [ 8000, 'MMMMMMMM' ],
1491 [ 9000, 'MMMMMMMMM' ],
1492 [ 9999, 'MMMMMMMMMCMXCIX' ],
1493 [ 10000, 'MMMMMMMMMM' ],
1494 ];
1495 }
1496
1497 /**
1498 * @dataProvider provideHebrewNumeralsData
1499 * @covers Language::hebrewNumeral
1500 */
1501 public function testHebrewNumeral( $num, $numerals ) {
1502 $this->assertEquals(
1503 $numerals,
1504 Language::hebrewNumeral( $num ),
1505 "hebrewNumeral('$num')"
1506 );
1507 }
1508
1509 public static function provideHebrewNumeralsData() {
1510 return [
1511 [ -1, -1 ],
1512 [ 0, 0 ],
1513 [ 1, "א'" ],
1514 [ 2, "ב'" ],
1515 [ 3, "ג'" ],
1516 [ 4, "ד'" ],
1517 [ 5, "ה'" ],
1518 [ 6, "ו'" ],
1519 [ 7, "ז'" ],
1520 [ 8, "ח'" ],
1521 [ 9, "ט'" ],
1522 [ 10, "י'" ],
1523 [ 11, 'י"א' ],
1524 [ 14, 'י"ד' ],
1525 [ 15, 'ט"ו' ],
1526 [ 16, 'ט"ז' ],
1527 [ 17, 'י"ז' ],
1528 [ 20, "כ'" ],
1529 [ 21, 'כ"א' ],
1530 [ 30, "ל'" ],
1531 [ 40, "מ'" ],
1532 [ 50, "נ'" ],
1533 [ 60, "ס'" ],
1534 [ 70, "ע'" ],
1535 [ 80, "פ'" ],
1536 [ 90, "צ'" ],
1537 [ 99, 'צ"ט' ],
1538 [ 100, "ק'" ],
1539 [ 101, 'ק"א' ],
1540 [ 110, 'ק"י' ],
1541 [ 200, "ר'" ],
1542 [ 300, "ש'" ],
1543 [ 400, "ת'" ],
1544 [ 500, 'ת"ק' ],
1545 [ 800, 'ת"ת' ],
1546 [ 1000, "א' אלף" ],
1547 [ 1001, "א'א'" ],
1548 [ 1012, "א'י\"ב" ],
1549 [ 1020, "א'ך'" ],
1550 [ 1030, "א'ל'" ],
1551 [ 1081, "א'פ\"א" ],
1552 [ 2000, "ב' אלפים" ],
1553 [ 2016, "ב'ט\"ז" ],
1554 [ 3000, "ג' אלפים" ],
1555 [ 4000, "ד' אלפים" ],
1556 [ 4904, "ד'תתק\"ד" ],
1557 [ 5000, "ה' אלפים" ],
1558 [ 5680, "ה'תר\"ף" ],
1559 [ 5690, "ה'תר\"ץ" ],
1560 [ 5708, "ה'תש\"ח" ],
1561 [ 5720, "ה'תש\"ך" ],
1562 [ 5740, "ה'תש\"ם" ],
1563 [ 5750, "ה'תש\"ן" ],
1564 [ 5775, "ה'תשע\"ה" ],
1565 ];
1566 }
1567
1568 /**
1569 * @dataProvider providePluralData
1570 * @covers Language::convertPlural
1571 */
1572 public function testConvertPlural( $expected, $number, $forms ) {
1573 $chosen = $this->getLang()->convertPlural( $number, $forms );
1574 $this->assertEquals( $expected, $chosen );
1575 }
1576
1577 public static function providePluralData() {
1578 // Params are: [expected text, number given, [the plural forms]]
1579 return [
1580 [ 'plural', 0, [
1581 'singular', 'plural'
1582 ] ],
1583 [ 'explicit zero', 0, [
1584 '0=explicit zero', 'singular', 'plural'
1585 ] ],
1586 [ 'explicit one', 1, [
1587 'singular', 'plural', '1=explicit one',
1588 ] ],
1589 [ 'singular', 1, [
1590 'singular', 'plural', '0=explicit zero',
1591 ] ],
1592 [ 'plural', 3, [
1593 '0=explicit zero', '1=explicit one', 'singular', 'plural'
1594 ] ],
1595 [ 'explicit eleven', 11, [
1596 'singular', 'plural', '11=explicit eleven',
1597 ] ],
1598 [ 'plural', 12, [
1599 'singular', 'plural', '11=explicit twelve',
1600 ] ],
1601 [ 'plural', 12, [
1602 'singular', 'plural', '=explicit form',
1603 ] ],
1604 [ 'other', 2, [
1605 'kissa=kala', '1=2=3', 'other',
1606 ] ],
1607 [ '', 2, [
1608 '0=explicit zero', '1=explicit one',
1609 ] ],
1610 ];
1611 }
1612
1613 /**
1614 * @covers Language::embedBidi()
1615 */
1616 public function testEmbedBidi() {
1617 $lre = "\xE2\x80\xAA"; // U+202A LEFT-TO-RIGHT EMBEDDING
1618 $rle = "\xE2\x80\xAB"; // U+202B RIGHT-TO-LEFT EMBEDDING
1619 $pdf = "\xE2\x80\xAC"; // U+202C POP DIRECTIONAL FORMATTING
1620 $lang = $this->getLang();
1621 $this->assertEquals(
1622 '123',
1623 $lang->embedBidi( '123' ),
1624 'embedBidi with neutral argument'
1625 );
1626 $this->assertEquals(
1627 $lre . 'Ben_(WMF)' . $pdf,
1628 $lang->embedBidi( 'Ben_(WMF)' ),
1629 'embedBidi with LTR argument'
1630 );
1631 $this->assertEquals(
1632 $rle . 'יהודי (מנוחין)' . $pdf,
1633 $lang->embedBidi( 'יהודי (מנוחין)' ),
1634 'embedBidi with RTL argument'
1635 );
1636 }
1637
1638 /**
1639 * @covers Language::translateBlockExpiry()
1640 * @dataProvider provideTranslateBlockExpiry
1641 */
1642 public function testTranslateBlockExpiry( $expectedData, $str, $now, $desc ) {
1643 $lang = $this->getLang();
1644 if ( is_array( $expectedData ) ) {
1645 list( $func, $arg ) = $expectedData;
1646 $expected = $lang->$func( $arg );
1647 } else {
1648 $expected = $expectedData;
1649 }
1650 $this->assertEquals( $expected, $lang->translateBlockExpiry( $str, null, $now ), $desc );
1651 }
1652
1653 public static function provideTranslateBlockExpiry() {
1654 return [
1655 [ '2 hours', '2 hours', 0, 'simple data from ipboptions' ],
1656 [ 'indefinite', 'infinite', 0, 'infinite from ipboptions' ],
1657 [ 'indefinite', 'infinity', 0, 'alternative infinite from ipboptions' ],
1658 [ 'indefinite', 'indefinite', 0, 'another alternative infinite from ipboptions' ],
1659 [ [ 'formatDuration', 1023 * 60 * 60 ], '1023 hours', 0, 'relative' ],
1660 [ [ 'formatDuration', -1023 ], '-1023 seconds', 0, 'negative relative' ],
1661 [
1662 [ 'formatDuration', 1023 * 60 * 60 ],
1663 '1023 hours',
1664 wfTimestamp( TS_UNIX, '19910203040506' ),
1665 'relative with initial timestamp'
1666 ],
1667 [ [ 'formatDuration', 0 ], 'now', 0, 'now' ],
1668 [
1669 [ 'timeanddate', '20120102070000' ],
1670 '2012-1-1 7:00 +1 day',
1671 0,
1672 'mixed, handled as absolute'
1673 ],
1674 [ [ 'timeanddate', '19910203040506' ], '1991-2-3 4:05:06', 0, 'absolute' ],
1675 [ [ 'timeanddate', '19700101000000' ], '1970-1-1 0:00:00', 0, 'absolute at epoch' ],
1676 [ [ 'timeanddate', '19691231235959' ], '1969-12-31 23:59:59', 0, 'time before epoch' ],
1677 [
1678 [ 'timeanddate', '19910910000000' ],
1679 '10 september',
1680 wfTimestamp( TS_UNIX, '19910203040506' ),
1681 'partial'
1682 ],
1683 [ 'dummy', 'dummy', 0, 'return garbage as is' ],
1684 ];
1685 }
1686
1687 /**
1688 * @dataProvider provideFormatNum
1689 * @covers Language::formatNum
1690 */
1691 public function testFormatNum(
1692 $translateNumerals, $langCode, $number, $nocommafy, $expected
1693 ) {
1694 $this->setMwGlobals( [ 'wgTranslateNumerals' => $translateNumerals ] );
1695 $lang = Language::factory( $langCode );
1696 $formattedNum = $lang->formatNum( $number, $nocommafy );
1697 $this->assertType( 'string', $formattedNum );
1698 $this->assertEquals( $expected, $formattedNum );
1699 }
1700
1701 public function provideFormatNum() {
1702 return [
1703 [ true, 'en', 100, false, '100' ],
1704 [ true, 'en', 101, true, '101' ],
1705 [ false, 'en', 103, false, '103' ],
1706 [ false, 'en', 104, true, '104' ],
1707 [ true, 'en', '105', false, '105' ],
1708 [ true, 'en', '106', true, '106' ],
1709 [ false, 'en', '107', false, '107' ],
1710 [ false, 'en', '108', true, '108' ],
1711 ];
1712 }
1713
1714 /**
1715 * @covers Language::parseFormattedNumber
1716 * @dataProvider parseFormattedNumberProvider
1717 */
1718 public function testParseFormattedNumber( $langCode, $number ) {
1719 $lang = Language::factory( $langCode );
1720
1721 $localisedNum = $lang->formatNum( $number );
1722 $normalisedNum = $lang->parseFormattedNumber( $localisedNum );
1723
1724 $this->assertEquals( $number, $normalisedNum );
1725 }
1726
1727 public function parseFormattedNumberProvider() {
1728 return [
1729 [ 'de', 377.01 ],
1730 [ 'fa', 334 ],
1731 [ 'fa', 382.772 ],
1732 [ 'ar', 1844 ],
1733 [ 'lzh', 3731 ],
1734 [ 'zh-classical', 7432 ]
1735 ];
1736 }
1737
1738 /**
1739 * @covers Language::commafy()
1740 * @dataProvider provideCommafyData
1741 */
1742 public function testCommafy( $number, $numbersWithCommas ) {
1743 $this->assertEquals(
1744 $numbersWithCommas,
1745 $this->getLang()->commafy( $number ),
1746 "commafy('$number')"
1747 );
1748 }
1749
1750 public static function provideCommafyData() {
1751 return [
1752 [ -1, '-1' ],
1753 [ 10, '10' ],
1754 [ 100, '100' ],
1755 [ 1000, '1,000' ],
1756 [ 10000, '10,000' ],
1757 [ 100000, '100,000' ],
1758 [ 1000000, '1,000,000' ],
1759 [ -1.0001, '-1.0001' ],
1760 [ 1.0001, '1.0001' ],
1761 [ 10.0001, '10.0001' ],
1762 [ 100.0001, '100.0001' ],
1763 [ 1000.0001, '1,000.0001' ],
1764 [ 10000.0001, '10,000.0001' ],
1765 [ 100000.0001, '100,000.0001' ],
1766 [ 1000000.0001, '1,000,000.0001' ],
1767 [ '200000000000000000000', '200,000,000,000,000,000,000' ],
1768 [ '-200000000000000000000', '-200,000,000,000,000,000,000' ],
1769 ];
1770 }
1771
1772 /**
1773 * @covers Language::listToText
1774 */
1775 public function testListToText() {
1776 $lang = $this->getLang();
1777 $and = $lang->getMessageFromDB( 'and' );
1778 $s = $lang->getMessageFromDB( 'word-separator' );
1779 $c = $lang->getMessageFromDB( 'comma-separator' );
1780
1781 $this->assertEquals( '', $lang->listToText( [] ) );
1782 $this->assertEquals( 'a', $lang->listToText( [ 'a' ] ) );
1783 $this->assertEquals( "a{$and}{$s}b", $lang->listToText( [ 'a', 'b' ] ) );
1784 $this->assertEquals( "a{$c}b{$and}{$s}c", $lang->listToText( [ 'a', 'b', 'c' ] ) );
1785 $this->assertEquals( "a{$c}b{$c}c{$and}{$s}d", $lang->listToText( [ 'a', 'b', 'c', 'd' ] ) );
1786 }
1787
1788 /**
1789 * @dataProvider provideIsSupportedLanguage
1790 * @covers Language::isSupportedLanguage
1791 */
1792 public function testIsSupportedLanguage( $code, $expected, $comment ) {
1793 $this->assertEquals( $expected, Language::isSupportedLanguage( $code ), $comment );
1794 }
1795
1796 public static function provideIsSupportedLanguage() {
1797 return [
1798 [ 'en', true, 'is supported language' ],
1799 [ 'fi', true, 'is supported language' ],
1800 [ 'bunny', false, 'is not supported language' ],
1801 [ 'FI', false, 'is not supported language, input should be in lower case' ],
1802 ];
1803 }
1804
1805 /**
1806 * @dataProvider provideGetParentLanguage
1807 * @covers Language::getParentLanguage
1808 */
1809 public function testGetParentLanguage( $code, $expected, $comment ) {
1810 $lang = Language::factory( $code );
1811 if ( is_null( $expected ) ) {
1812 $this->assertNull( $lang->getParentLanguage(), $comment );
1813 } else {
1814 $this->assertEquals( $expected, $lang->getParentLanguage()->getCode(), $comment );
1815 }
1816 }
1817
1818 public static function provideGetParentLanguage() {
1819 return [
1820 [ 'zh-cn', 'zh', 'zh is the parent language of zh-cn' ],
1821 [ 'zh', 'zh', 'zh is defined as the parent language of zh, '
1822 . 'because zh converter can convert zh-cn to zh' ],
1823 [ 'zh-invalid', null, 'do not be fooled by arbitrarily composed language codes' ],
1824 [ 'de-formal', null, 'de does not have converter' ],
1825 [ 'de', null, 'de does not have converter' ],
1826 ];
1827 }
1828
1829 /**
1830 * @dataProvider provideGetNamespaceAliases
1831 * @covers Language::getNamespaceAliases
1832 */
1833 public function testGetNamespaceAliases( $languageCode, $subset ) {
1834 $language = Language::factory( $languageCode );
1835 $aliases = $language->getNamespaceAliases();
1836 foreach ( $subset as $alias => $nsId ) {
1837 $this->assertEquals( $nsId, $aliases[$alias] );
1838 }
1839 }
1840
1841 public static function provideGetNamespaceAliases() {
1842 // TODO: Add tests for NS_PROJECT_TALK and GenderNamespaces
1843 return [
1844 [
1845 'zh',
1846 [
1847 '文件' => NS_FILE,
1848 '檔案' => NS_FILE,
1849 ],
1850 ],
1851 ];
1852 }
1853
1854 /**
1855 * @covers Language::equals
1856 */
1857 public function testEquals() {
1858 $en1 = new Language();
1859 $en1->setCode( 'en' );
1860
1861 $en2 = Language::factory( 'en' );
1862 $en2->setCode( 'en' );
1863
1864 $this->assertTrue( $en1->equals( $en2 ), 'en equals en' );
1865
1866 $fr = Language::factory( 'fr' );
1867 $this->assertFalse( $en1->equals( $fr ), 'en not equals fr' );
1868 }
1869 }