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