35bb1f057f9b8f9a7118b471ce52d397cc74aeb0
[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 'xkY',
1034 '19410101090705',
1035 '2484',
1036 '2484',
1037 'Thai year'
1038 ],
1039 [
1040 'xoY',
1041 '20120102090705',
1042 '101',
1043 '101',
1044 'Minguo'
1045 ],
1046 [
1047 'xtY',
1048 '20120102090705',
1049 '平成24',
1050 '平成24',
1051 'nengo'
1052 ],
1053 [
1054 'xrxkYY',
1055 '20120102090705',
1056 'MMDLV2012',
1057 'MMDLV2012',
1058 'Roman numerals'
1059 ],
1060 [
1061 'xhxjYY',
1062 '20120102090705',
1063 \'תשע"ב2012',
1064 \'תשע"ב2012',
1065 'Hebrew numberals'
1066 ],
1067 [
1068 'xnY',
1069 '20120102090705',
1070 '2012',
1071 '2012',
1072 'Raw numerals (doesn\'t mean much in EN)'
1073 ],
1074 [
1075 '[[Y "(yea"\\r)]] \\"xx\\"',
1076 '20120102090705',
1077 '[[2012 (year)]] "x"',
1078 '[[2012 (year)]] "x"',
1079 'Various escaping'
1080 ],
1081
1082 ];
1083 }
1084
1085 /**
1086 * @dataProvider provideFormatSizes
1087 * @covers Language::formatSize
1088 */
1089 public function testFormatSize( $size, $expected, $msg ) {
1090 $this->assertEquals(
1091 $expected,
1092 $this->getLang()->formatSize( $size ),
1093 "formatSize('$size'): $msg"
1094 );
1095 }
1096
1097 public static function provideFormatSizes() {
1098 return [
1099 [
1100 0,
1101 "0 bytes",
1102 "Zero bytes"
1103 ],
1104 [
1105 1024,
1106 "1 KB",
1107 "1 kilobyte"
1108 ],
1109 [
1110 1024 * 1024,
1111 "1 MB",
1112 "1,024 megabytes"
1113 ],
1114 [
1115 1024 * 1024 * 1024,
1116 "1 GB",
1117 "1 gigabyte"
1118 ],
1119 [
1120 1024 ** 4,
1121 "1 TB",
1122 "1 terabyte"
1123 ],
1124 [
1125 1024 ** 5,
1126 "1 PB",
1127 "1 petabyte"
1128 ],
1129 [
1130 1024 ** 6,
1131 "1 EB",
1132 "1,024 exabyte"
1133 ],
1134 [
1135 1024 ** 7,
1136 "1 ZB",
1137 "1 zetabyte"
1138 ],
1139 [
1140 1024 ** 8,
1141 "1 YB",
1142 "1 yottabyte"
1143 ],
1144 // How big!? THIS BIG!
1145 ];
1146 }
1147
1148 /**
1149 * @dataProvider provideFormatBitrate
1150 * @covers Language::formatBitrate
1151 */
1152 public function testFormatBitrate( $bps, $expected, $msg ) {
1153 $this->assertEquals(
1154 $expected,
1155 $this->getLang()->formatBitrate( $bps ),
1156 "formatBitrate('$bps'): $msg"
1157 );
1158 }
1159
1160 public static function provideFormatBitrate() {
1161 return [
1162 [
1163 0,
1164 "0 bps",
1165 "0 bits per second"
1166 ],
1167 [
1168 999,
1169 "999 bps",
1170 "999 bits per second"
1171 ],
1172 [
1173 1000,
1174 "1 kbps",
1175 "1 kilobit per second"
1176 ],
1177 [
1178 1000 * 1000,
1179 "1 Mbps",
1180 "1 megabit per second"
1181 ],
1182 [
1183 10 ** 9,
1184 "1 Gbps",
1185 "1 gigabit per second"
1186 ],
1187 [
1188 10 ** 12,
1189 "1 Tbps",
1190 "1 terabit per second"
1191 ],
1192 [
1193 10 ** 15,
1194 "1 Pbps",
1195 "1 petabit per second"
1196 ],
1197 [
1198 10 ** 18,
1199 "1 Ebps",
1200 "1 exabit per second"
1201 ],
1202 [
1203 10 ** 21,
1204 "1 Zbps",
1205 "1 zetabit per second"
1206 ],
1207 [
1208 10 ** 24,
1209 "1 Ybps",
1210 "1 yottabit per second"
1211 ],
1212 [
1213 10 ** 27,
1214 "1,000 Ybps",
1215 "1,000 yottabits per second"
1216 ],
1217 ];
1218 }
1219
1220 /**
1221 * @dataProvider provideFormatDuration
1222 * @covers Language::formatDuration
1223 */
1224 public function testFormatDuration( $duration, $expected, $intervals = [] ) {
1225 $this->assertEquals(
1226 $expected,
1227 $this->getLang()->formatDuration( $duration, $intervals ),
1228 "formatDuration('$duration'): $expected"
1229 );
1230 }
1231
1232 public static function provideFormatDuration() {
1233 return [
1234 [
1235 0,
1236 '0 seconds',
1237 ],
1238 [
1239 1,
1240 '1 second',
1241 ],
1242 [
1243 2,
1244 '2 seconds',
1245 ],
1246 [
1247 60,
1248 '1 minute',
1249 ],
1250 [
1251 2 * 60,
1252 '2 minutes',
1253 ],
1254 [
1255 3600,
1256 '1 hour',
1257 ],
1258 [
1259 2 * 3600,
1260 '2 hours',
1261 ],
1262 [
1263 24 * 3600,
1264 '1 day',
1265 ],
1266 [
1267 2 * 86400,
1268 '2 days',
1269 ],
1270 [
1271 // ( 365 + ( 24 * 3 + 25 ) / 400 ) * 86400 = 31556952
1272 ( 365 + ( 24 * 3 + 25 ) / 400.0 ) * 86400,
1273 '1 year',
1274 ],
1275 [
1276 2 * 31556952,
1277 '2 years',
1278 ],
1279 [
1280 10 * 31556952,
1281 '1 decade',
1282 ],
1283 [
1284 20 * 31556952,
1285 '2 decades',
1286 ],
1287 [
1288 100 * 31556952,
1289 '1 century',
1290 ],
1291 [
1292 200 * 31556952,
1293 '2 centuries',
1294 ],
1295 [
1296 1000 * 31556952,
1297 '1 millennium',
1298 ],
1299 [
1300 2000 * 31556952,
1301 '2 millennia',
1302 ],
1303 [
1304 9001,
1305 '2 hours, 30 minutes and 1 second'
1306 ],
1307 [
1308 3601,
1309 '1 hour and 1 second'
1310 ],
1311 [
1312 31556952 + 2 * 86400 + 9000,
1313 '1 year, 2 days, 2 hours and 30 minutes'
1314 ],
1315 [
1316 42 * 1000 * 31556952 + 42,
1317 '42 millennia and 42 seconds'
1318 ],
1319 [
1320 60,
1321 '60 seconds',
1322 [ 'seconds' ],
1323 ],
1324 [
1325 61,
1326 '61 seconds',
1327 [ 'seconds' ],
1328 ],
1329 [
1330 1,
1331 '1 second',
1332 [ 'seconds' ],
1333 ],
1334 [
1335 31556952 + 2 * 86400 + 9000,
1336 '1 year, 2 days and 150 minutes',
1337 [ 'years', 'days', 'minutes' ],
1338 ],
1339 [
1340 42,
1341 '0 days',
1342 [ 'years', 'days' ],
1343 ],
1344 [
1345 31556952 + 2 * 86400 + 9000,
1346 '1 year, 2 days and 150 minutes',
1347 [ 'minutes', 'days', 'years' ],
1348 ],
1349 [
1350 42,
1351 '0 days',
1352 [ 'days', 'years' ],
1353 ],
1354 ];
1355 }
1356
1357 /**
1358 * @dataProvider provideCheckTitleEncodingData
1359 * @covers Language::checkTitleEncoding
1360 */
1361 public function testCheckTitleEncoding( $s ) {
1362 $this->assertEquals(
1363 $s,
1364 $this->getLang()->checkTitleEncoding( $s ),
1365 "checkTitleEncoding('$s')"
1366 );
1367 }
1368
1369 public static function provideCheckTitleEncodingData() {
1370 // phpcs:disable Generic.Files.LineLength
1371 return [
1372 [ "" ],
1373 [ "United States of America" ], // 7bit ASCII
1374 [ rawurldecode( "S%C3%A9rie%20t%C3%A9l%C3%A9vis%C3%A9e" ) ],
1375 [
1376 rawurldecode(
1377 "Acteur%7CAlbert%20Robbins%7CAnglais%7CAnn%20Donahue%7CAnthony%20E.%20Zuiker%7CCarol%20Mendelsohn"
1378 )
1379 ],
1380 // The following two data sets come from T38839. They fail if checkTitleEncoding uses a regexp to test for
1381 // valid UTF-8 encoding and the pcre.recursion_limit is low (like, say, 1024). They succeed if checkTitleEncoding
1382 // uses mb_check_encoding for its test.
1383 [
1384 rawurldecode(
1385 "Acteur%7CAlbert%20Robbins%7CAnglais%7CAnn%20Donahue%7CAnthony%20E.%20Zuiker%7CCarol%20Mendelsohn%7C"
1386 . "Catherine%20Willows%7CDavid%20Hodges%7CDavid%20Phillips%7CGil%20Grissom%7CGreg%20Sanders%7CHodges%7C"
1387 . "Internet%20Movie%20Database%7CJim%20Brass%7CLady%20Heather%7C"
1388 . "Les%20Experts%20(s%C3%A9rie%20t%C3%A9l%C3%A9vis%C3%A9e)%7CLes%20Experts%20:%20Manhattan%7C"
1389 . "Les%20Experts%20:%20Miami%7CListe%20des%20personnages%20des%20Experts%7C"
1390 . "Liste%20des%20%C3%A9pisodes%20des%20Experts%7CMod%C3%A8le%20discussion:Palette%20Les%20Experts%7C"
1391 . "Nick%20Stokes%7CPersonnage%20de%20fiction%7CPersonnage%20fictif%7CPersonnage%20de%20fiction%7C"
1392 . "Personnages%20r%C3%A9currents%20dans%20Les%20Experts%7CRaymond%20Langston%7CRiley%20Adams%7C"
1393 . "Saison%201%20des%20Experts%7CSaison%2010%20des%20Experts%7CSaison%2011%20des%20Experts%7C"
1394 . "Saison%2012%20des%20Experts%7CSaison%202%20des%20Experts%7CSaison%203%20des%20Experts%7C"
1395 . "Saison%204%20des%20Experts%7CSaison%205%20des%20Experts%7CSaison%206%20des%20Experts%7C"
1396 . "Saison%207%20des%20Experts%7CSaison%208%20des%20Experts%7CSaison%209%20des%20Experts%7C"
1397 . "Sara%20Sidle%7CSofia%20Curtis%7CS%C3%A9rie%20t%C3%A9l%C3%A9vis%C3%A9e%7CWallace%20Langham%7C"
1398 . "Warrick%20Brown%7CWendy%20Simms%7C%C3%89tats-Unis"
1399 ),
1400 ],
1401 [
1402 rawurldecode(
1403 "Mod%C3%A8le%3AArrondissements%20homonymes%7CMod%C3%A8le%3ABandeau%20standard%20pour%20page%20d'homonymie%7C"
1404 . "Mod%C3%A8le%3ABatailles%20homonymes%7CMod%C3%A8le%3ACantons%20homonymes%7C"
1405 . "Mod%C3%A8le%3ACommunes%20fran%C3%A7aises%20homonymes%7CMod%C3%A8le%3AFilms%20homonymes%7C"
1406 . "Mod%C3%A8le%3AGouvernements%20homonymes%7CMod%C3%A8le%3AGuerres%20homonymes%7CMod%C3%A8le%3AHomonymie%7C"
1407 . "Mod%C3%A8le%3AHomonymie%20bateau%7CMod%C3%A8le%3AHomonymie%20d'%C3%A9tablissements%20scolaires%20ou"
1408 . "%20universitaires%7CMod%C3%A8le%3AHomonymie%20d'%C3%AEles%7CMod%C3%A8le%3AHomonymie%20de%20clubs%20sportifs%7C"
1409 . "Mod%C3%A8le%3AHomonymie%20de%20comt%C3%A9s%7CMod%C3%A8le%3AHomonymie%20de%20monument%7C"
1410 . "Mod%C3%A8le%3AHomonymie%20de%20nom%20romain%7CMod%C3%A8le%3AHomonymie%20de%20parti%20politique%7C"
1411 . "Mod%C3%A8le%3AHomonymie%20de%20route%7CMod%C3%A8le%3AHomonymie%20dynastique%7C"
1412 . "Mod%C3%A8le%3AHomonymie%20vid%C3%A9oludique%7CMod%C3%A8le%3AHomonymie%20%C3%A9difice%20religieux%7C"
1413 . "Mod%C3%A8le%3AInternationalisation%7CMod%C3%A8le%3AIsom%C3%A9rie%7CMod%C3%A8le%3AParonymie%7C"
1414 . "Mod%C3%A8le%3APatronyme%7CMod%C3%A8le%3APatronyme%20basque%7CMod%C3%A8le%3APatronyme%20italien%7C"
1415 . "Mod%C3%A8le%3APatronymie%7CMod%C3%A8le%3APersonnes%20homonymes%7CMod%C3%A8le%3ASaints%20homonymes%7C"
1416 . "Mod%C3%A8le%3ATitres%20homonymes%7CMod%C3%A8le%3AToponymie%7CMod%C3%A8le%3AUnit%C3%A9s%20homonymes%7C"
1417 . "Mod%C3%A8le%3AVilles%20homonymes%7CMod%C3%A8le%3A%C3%89difices%20religieux%20homonymes"
1418 )
1419 ]
1420 ];
1421 // phpcs:enable
1422 }
1423
1424 /**
1425 * @dataProvider provideRomanNumeralsData
1426 * @covers Language::romanNumeral
1427 */
1428 public function testRomanNumerals( $num, $numerals ) {
1429 $this->assertEquals(
1430 $numerals,
1431 Language::romanNumeral( $num ),
1432 "romanNumeral('$num')"
1433 );
1434 }
1435
1436 public static function provideRomanNumeralsData() {
1437 return [
1438 [ 1, 'I' ],
1439 [ 2, 'II' ],
1440 [ 3, 'III' ],
1441 [ 4, 'IV' ],
1442 [ 5, 'V' ],
1443 [ 6, 'VI' ],
1444 [ 7, 'VII' ],
1445 [ 8, 'VIII' ],
1446 [ 9, 'IX' ],
1447 [ 10, 'X' ],
1448 [ 20, 'XX' ],
1449 [ 30, 'XXX' ],
1450 [ 40, 'XL' ],
1451 [ 49, 'XLIX' ],
1452 [ 50, 'L' ],
1453 [ 60, 'LX' ],
1454 [ 70, 'LXX' ],
1455 [ 80, 'LXXX' ],
1456 [ 90, 'XC' ],
1457 [ 99, 'XCIX' ],
1458 [ 100, 'C' ],
1459 [ 200, 'CC' ],
1460 [ 300, 'CCC' ],
1461 [ 400, 'CD' ],
1462 [ 500, 'D' ],
1463 [ 600, 'DC' ],
1464 [ 700, 'DCC' ],
1465 [ 800, 'DCCC' ],
1466 [ 900, 'CM' ],
1467 [ 999, 'CMXCIX' ],
1468 [ 1000, 'M' ],
1469 [ 1989, 'MCMLXXXIX' ],
1470 [ 2000, 'MM' ],
1471 [ 3000, 'MMM' ],
1472 [ 4000, 'MMMM' ],
1473 [ 5000, 'MMMMM' ],
1474 [ 6000, 'MMMMMM' ],
1475 [ 7000, 'MMMMMMM' ],
1476 [ 8000, 'MMMMMMMM' ],
1477 [ 9000, 'MMMMMMMMM' ],
1478 [ 9999, 'MMMMMMMMMCMXCIX' ],
1479 [ 10000, 'MMMMMMMMMM' ],
1480 ];
1481 }
1482
1483 /**
1484 * @dataProvider provideHebrewNumeralsData
1485 * @covers Language::hebrewNumeral
1486 */
1487 public function testHebrewNumeral( $num, $numerals ) {
1488 $this->assertEquals(
1489 $numerals,
1490 Language::hebrewNumeral( $num ),
1491 "hebrewNumeral('$num')"
1492 );
1493 }
1494
1495 public static function provideHebrewNumeralsData() {
1496 return [
1497 [ -1, -1 ],
1498 [ 0, 0 ],
1499 [ 1, "א'" ],
1500 [ 2, "ב'" ],
1501 [ 3, "ג'" ],
1502 [ 4, "ד'" ],
1503 [ 5, "ה'" ],
1504 [ 6, "ו'" ],
1505 [ 7, "ז'" ],
1506 [ 8, "ח'" ],
1507 [ 9, "ט'" ],
1508 [ 10, "י'" ],
1509 [ 11, 'י"א' ],
1510 [ 14, 'י"ד' ],
1511 [ 15, 'ט"ו' ],
1512 [ 16, 'ט"ז' ],
1513 [ 17, 'י"ז' ],
1514 [ 20, "כ'" ],
1515 [ 21, 'כ"א' ],
1516 [ 30, "ל'" ],
1517 [ 40, "מ'" ],
1518 [ 50, "נ'" ],
1519 [ 60, "ס'" ],
1520 [ 70, "ע'" ],
1521 [ 80, "פ'" ],
1522 [ 90, "צ'" ],
1523 [ 99, 'צ"ט' ],
1524 [ 100, "ק'" ],
1525 [ 101, 'ק"א' ],
1526 [ 110, 'ק"י' ],
1527 [ 200, "ר'" ],
1528 [ 300, "ש'" ],
1529 [ 400, "ת'" ],
1530 [ 500, 'ת"ק' ],
1531 [ 800, 'ת"ת' ],
1532 [ 1000, "א' אלף" ],
1533 [ 1001, "א'א'" ],
1534 [ 1012, "א'י\"ב" ],
1535 [ 1020, "א'ך'" ],
1536 [ 1030, "א'ל'" ],
1537 [ 1081, "א'פ\"א" ],
1538 [ 2000, "ב' אלפים" ],
1539 [ 2016, "ב'ט\"ז" ],
1540 [ 3000, "ג' אלפים" ],
1541 [ 4000, "ד' אלפים" ],
1542 [ 4904, "ד'תתק\"ד" ],
1543 [ 5000, "ה' אלפים" ],
1544 [ 5680, "ה'תר\"ף" ],
1545 [ 5690, "ה'תר\"ץ" ],
1546 [ 5708, "ה'תש\"ח" ],
1547 [ 5720, "ה'תש\"ך" ],
1548 [ 5740, "ה'תש\"ם" ],
1549 [ 5750, "ה'תש\"ן" ],
1550 [ 5775, "ה'תשע\"ה" ],
1551 ];
1552 }
1553
1554 /**
1555 * @dataProvider providePluralData
1556 * @covers Language::convertPlural
1557 */
1558 public function testConvertPlural( $expected, $number, $forms ) {
1559 $chosen = $this->getLang()->convertPlural( $number, $forms );
1560 $this->assertEquals( $expected, $chosen );
1561 }
1562
1563 public static function providePluralData() {
1564 // Params are: [expected text, number given, [the plural forms]]
1565 return [
1566 [ 'plural', 0, [
1567 'singular', 'plural'
1568 ] ],
1569 [ 'explicit zero', 0, [
1570 '0=explicit zero', 'singular', 'plural'
1571 ] ],
1572 [ 'explicit one', 1, [
1573 'singular', 'plural', '1=explicit one',
1574 ] ],
1575 [ 'singular', 1, [
1576 'singular', 'plural', '0=explicit zero',
1577 ] ],
1578 [ 'plural', 3, [
1579 '0=explicit zero', '1=explicit one', 'singular', 'plural'
1580 ] ],
1581 [ 'explicit eleven', 11, [
1582 'singular', 'plural', '11=explicit eleven',
1583 ] ],
1584 [ 'plural', 12, [
1585 'singular', 'plural', '11=explicit twelve',
1586 ] ],
1587 [ 'plural', 12, [
1588 'singular', 'plural', '=explicit form',
1589 ] ],
1590 [ 'other', 2, [
1591 'kissa=kala', '1=2=3', 'other',
1592 ] ],
1593 [ '', 2, [
1594 '0=explicit zero', '1=explicit one',
1595 ] ],
1596 ];
1597 }
1598
1599 /**
1600 * @covers Language::embedBidi()
1601 */
1602 public function testEmbedBidi() {
1603 $lre = "\u{202A}"; // U+202A LEFT-TO-RIGHT EMBEDDING
1604 $rle = "\u{202B}"; // U+202B RIGHT-TO-LEFT EMBEDDING
1605 $pdf = "\u{202C}"; // U+202C POP DIRECTIONAL FORMATTING
1606 $lang = $this->getLang();
1607 $this->assertEquals(
1608 '123',
1609 $lang->embedBidi( '123' ),
1610 'embedBidi with neutral argument'
1611 );
1612 $this->assertEquals(
1613 $lre . 'Ben_(WMF)' . $pdf,
1614 $lang->embedBidi( 'Ben_(WMF)' ),
1615 'embedBidi with LTR argument'
1616 );
1617 $this->assertEquals(
1618 $rle . 'יהודי (מנוחין)' . $pdf,
1619 $lang->embedBidi( 'יהודי (מנוחין)' ),
1620 'embedBidi with RTL argument'
1621 );
1622 }
1623
1624 /**
1625 * @covers Language::translateBlockExpiry()
1626 * @dataProvider provideTranslateBlockExpiry
1627 */
1628 public function testTranslateBlockExpiry( $expectedData, $str, $now, $desc ) {
1629 $lang = $this->getLang();
1630 if ( is_array( $expectedData ) ) {
1631 list( $func, $arg ) = $expectedData;
1632 $expected = $lang->$func( $arg );
1633 } else {
1634 $expected = $expectedData;
1635 }
1636 $this->assertEquals( $expected, $lang->translateBlockExpiry( $str, null, $now ), $desc );
1637 }
1638
1639 public static function provideTranslateBlockExpiry() {
1640 return [
1641 [ '2 hours', '2 hours', 0, 'simple data from ipboptions' ],
1642 [ 'indefinite', 'infinite', 0, 'infinite from ipboptions' ],
1643 [ 'indefinite', 'infinity', 0, 'alternative infinite from ipboptions' ],
1644 [ 'indefinite', 'indefinite', 0, 'another alternative infinite from ipboptions' ],
1645 [ [ 'formatDuration', 1023 * 60 * 60 ], '1023 hours', 0, 'relative' ],
1646 [ [ 'formatDuration', -1023 ], '-1023 seconds', 0, 'negative relative' ],
1647 [
1648 [ 'formatDuration', 1023 * 60 * 60 ],
1649 '1023 hours',
1650 wfTimestamp( TS_UNIX, '19910203040506' ),
1651 'relative with initial timestamp'
1652 ],
1653 [ [ 'formatDuration', 0 ], 'now', 0, 'now' ],
1654 [
1655 [ 'timeanddate', '20120102070000' ],
1656 '2012-1-1 7:00 +1 day',
1657 0,
1658 'mixed, handled as absolute'
1659 ],
1660 [ [ 'timeanddate', '19910203040506' ], '1991-2-3 4:05:06', 0, 'absolute' ],
1661 [ [ 'timeanddate', '19700101000000' ], '1970-1-1 0:00:00', 0, 'absolute at epoch' ],
1662 [ [ 'timeanddate', '19691231235959' ], '1969-12-31 23:59:59', 0, 'time before epoch' ],
1663 [
1664 [ 'timeanddate', '19910910000000' ],
1665 '10 september',
1666 wfTimestamp( TS_UNIX, '19910203040506' ),
1667 'partial'
1668 ],
1669 [ 'dummy', 'dummy', 0, 'return garbage as is' ],
1670 ];
1671 }
1672
1673 /**
1674 * @dataProvider provideFormatNum
1675 * @covers Language::formatNum
1676 */
1677 public function testFormatNum(
1678 $translateNumerals, $langCode, $number, $nocommafy, $expected
1679 ) {
1680 $this->setMwGlobals( [ 'wgTranslateNumerals' => $translateNumerals ] );
1681 $lang = Language::factory( $langCode );
1682 $formattedNum = $lang->formatNum( $number, $nocommafy );
1683 $this->assertType( 'string', $formattedNum );
1684 $this->assertEquals( $expected, $formattedNum );
1685 }
1686
1687 public function provideFormatNum() {
1688 return [
1689 [ true, 'en', 100, false, '100' ],
1690 [ true, 'en', 101, true, '101' ],
1691 [ false, 'en', 103, false, '103' ],
1692 [ false, 'en', 104, true, '104' ],
1693 [ true, 'en', '105', false, '105' ],
1694 [ true, 'en', '106', true, '106' ],
1695 [ false, 'en', '107', false, '107' ],
1696 [ false, 'en', '108', true, '108' ],
1697 ];
1698 }
1699
1700 /**
1701 * @covers Language::parseFormattedNumber
1702 * @dataProvider parseFormattedNumberProvider
1703 */
1704 public function testParseFormattedNumber( $langCode, $number ) {
1705 $lang = Language::factory( $langCode );
1706
1707 $localisedNum = $lang->formatNum( $number );
1708 $normalisedNum = $lang->parseFormattedNumber( $localisedNum );
1709
1710 $this->assertEquals( $number, $normalisedNum );
1711 }
1712
1713 public function parseFormattedNumberProvider() {
1714 return [
1715 [ 'de', 377.01 ],
1716 [ 'fa', 334 ],
1717 [ 'fa', 382.772 ],
1718 [ 'ar', 1844 ],
1719 [ 'lzh', 3731 ],
1720 [ 'zh-classical', 7432 ]
1721 ];
1722 }
1723
1724 /**
1725 * @covers Language::commafy()
1726 * @dataProvider provideCommafyData
1727 */
1728 public function testCommafy( $number, $numbersWithCommas ) {
1729 $this->assertEquals(
1730 $numbersWithCommas,
1731 $this->getLang()->commafy( $number ),
1732 "commafy('$number')"
1733 );
1734 }
1735
1736 public static function provideCommafyData() {
1737 return [
1738 [ -1, '-1' ],
1739 [ 10, '10' ],
1740 [ 100, '100' ],
1741 [ 1000, '1,000' ],
1742 [ 10000, '10,000' ],
1743 [ 100000, '100,000' ],
1744 [ 1000000, '1,000,000' ],
1745 [ -1.0001, '-1.0001' ],
1746 [ 1.0001, '1.0001' ],
1747 [ 10.0001, '10.0001' ],
1748 [ 100.0001, '100.0001' ],
1749 [ 1000.0001, '1,000.0001' ],
1750 [ 10000.0001, '10,000.0001' ],
1751 [ 100000.0001, '100,000.0001' ],
1752 [ 1000000.0001, '1,000,000.0001' ],
1753 [ '200000000000000000000', '200,000,000,000,000,000,000' ],
1754 [ '-200000000000000000000', '-200,000,000,000,000,000,000' ],
1755 ];
1756 }
1757
1758 /**
1759 * @covers Language::listToText
1760 */
1761 public function testListToText() {
1762 $lang = $this->getLang();
1763 $and = $lang->getMessageFromDB( 'and' );
1764 $s = $lang->getMessageFromDB( 'word-separator' );
1765 $c = $lang->getMessageFromDB( 'comma-separator' );
1766
1767 $this->assertEquals( '', $lang->listToText( [] ) );
1768 $this->assertEquals( 'a', $lang->listToText( [ 'a' ] ) );
1769 $this->assertEquals( "a{$and}{$s}b", $lang->listToText( [ 'a', 'b' ] ) );
1770 $this->assertEquals( "a{$c}b{$and}{$s}c", $lang->listToText( [ 'a', 'b', 'c' ] ) );
1771 $this->assertEquals( "a{$c}b{$c}c{$and}{$s}d", $lang->listToText( [ 'a', 'b', 'c', 'd' ] ) );
1772 }
1773
1774 /**
1775 * @dataProvider provideIsSupportedLanguage
1776 * @covers Language::isSupportedLanguage
1777 */
1778 public function testIsSupportedLanguage( $code, $expected, $comment ) {
1779 $this->assertEquals( $expected, Language::isSupportedLanguage( $code ), $comment );
1780 }
1781
1782 public static function provideIsSupportedLanguage() {
1783 return [
1784 [ 'en', true, 'is supported language' ],
1785 [ 'fi', true, 'is supported language' ],
1786 [ 'bunny', false, 'is not supported language' ],
1787 [ 'FI', false, 'is not supported language, input should be in lower case' ],
1788 ];
1789 }
1790
1791 /**
1792 * @dataProvider provideGetParentLanguage
1793 * @covers Language::getParentLanguage
1794 */
1795 public function testGetParentLanguage( $code, $expected, $comment ) {
1796 $lang = Language::factory( $code );
1797 if ( is_null( $expected ) ) {
1798 $this->assertNull( $lang->getParentLanguage(), $comment );
1799 } else {
1800 $this->assertEquals( $expected, $lang->getParentLanguage()->getCode(), $comment );
1801 }
1802 }
1803
1804 public static function provideGetParentLanguage() {
1805 return [
1806 [ 'zh-cn', 'zh', 'zh is the parent language of zh-cn' ],
1807 [ 'zh', 'zh', 'zh is defined as the parent language of zh, '
1808 . 'because zh converter can convert zh-cn to zh' ],
1809 [ 'zh-invalid', null, 'do not be fooled by arbitrarily composed language codes' ],
1810 [ 'de-formal', null, 'de does not have converter' ],
1811 [ 'de', null, 'de does not have converter' ],
1812 ];
1813 }
1814
1815 /**
1816 * @dataProvider provideGetNamespaceAliases
1817 * @covers Language::getNamespaceAliases
1818 */
1819 public function testGetNamespaceAliases( $languageCode, $subset ) {
1820 $language = Language::factory( $languageCode );
1821 $aliases = $language->getNamespaceAliases();
1822 foreach ( $subset as $alias => $nsId ) {
1823 $this->assertEquals( $nsId, $aliases[$alias] );
1824 }
1825 }
1826
1827 public static function provideGetNamespaceAliases() {
1828 // TODO: Add tests for NS_PROJECT_TALK and GenderNamespaces
1829 return [
1830 [
1831 'zh',
1832 [
1833 '文件' => NS_FILE,
1834 '檔案' => NS_FILE,
1835 ],
1836 ],
1837 ];
1838 }
1839
1840 /**
1841 * @covers Language::equals
1842 */
1843 public function testEquals() {
1844 $en1 = new Language();
1845 $en1->setCode( 'en' );
1846
1847 $en2 = Language::factory( 'en' );
1848 $en2->setCode( 'en' );
1849
1850 $this->assertTrue( $en1->equals( $en2 ), 'en equals en' );
1851
1852 $fr = Language::factory( 'fr' );
1853 $this->assertFalse( $en1->equals( $fr ), 'en not equals fr' );
1854 }
1855 }