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