X-Git-Url: https://scripts.mit.edu/gitweb/autoinstallsdev/mediawiki.git/blobdiff_plain/19e297c21b10b1b8a3acad5e73fc71dcb35db44a..6932310fd58ebef145fa01eb76edf7150284d8ea:/tests/phpunit/languages/LanguageTest.php diff --git a/tests/phpunit/languages/LanguageTest.php b/tests/phpunit/languages/LanguageTest.php new file mode 100644 index 00000000..cd52366f --- /dev/null +++ b/tests/phpunit/languages/LanguageTest.php @@ -0,0 +1,1783 @@ +assertEquals( + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", + $this->getLang()->normalizeForSearch( + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + ), + 'convertDoubleWidth() with the full alphabet and digits' + ); + } + + /** + * @dataProvider provideFormattableTimes + * @covers Language::formatTimePeriod + */ + public function testFormatTimePeriod( $seconds, $format, $expected, $desc ) { + $this->assertEquals( $expected, $this->getLang()->formatTimePeriod( $seconds, $format ), $desc ); + } + + public static function provideFormattableTimes() { + return [ + [ + 9.45, + [], + '9.5 s', + 'formatTimePeriod() rounding (<10s)' + ], + [ + 9.45, + [ 'noabbrevs' => true ], + '9.5 seconds', + 'formatTimePeriod() rounding (<10s)' + ], + [ + 9.95, + [], + '10 s', + 'formatTimePeriod() rounding (<10s)' + ], + [ + 9.95, + [ 'noabbrevs' => true ], + '10 seconds', + 'formatTimePeriod() rounding (<10s)' + ], + [ + 59.55, + [], + '1 min 0 s', + 'formatTimePeriod() rounding (<60s)' + ], + [ + 59.55, + [ 'noabbrevs' => true ], + '1 minute 0 seconds', + 'formatTimePeriod() rounding (<60s)' + ], + [ + 119.55, + [], + '2 min 0 s', + 'formatTimePeriod() rounding (<1h)' + ], + [ + 119.55, + [ 'noabbrevs' => true ], + '2 minutes 0 seconds', + 'formatTimePeriod() rounding (<1h)' + ], + [ + 3599.55, + [], + '1 h 0 min 0 s', + 'formatTimePeriod() rounding (<1h)' + ], + [ + 3599.55, + [ 'noabbrevs' => true ], + '1 hour 0 minutes 0 seconds', + 'formatTimePeriod() rounding (<1h)' + ], + [ + 7199.55, + [], + '2 h 0 min 0 s', + 'formatTimePeriod() rounding (>=1h)' + ], + [ + 7199.55, + [ 'noabbrevs' => true ], + '2 hours 0 minutes 0 seconds', + 'formatTimePeriod() rounding (>=1h)' + ], + [ + 7199.55, + 'avoidseconds', + '2 h 0 min', + 'formatTimePeriod() rounding (>=1h), avoidseconds' + ], + [ + 7199.55, + [ 'avoid' => 'avoidseconds', 'noabbrevs' => true ], + '2 hours 0 minutes', + 'formatTimePeriod() rounding (>=1h), avoidseconds' + ], + [ + 7199.55, + 'avoidminutes', + '2 h 0 min', + 'formatTimePeriod() rounding (>=1h), avoidminutes' + ], + [ + 7199.55, + [ 'avoid' => 'avoidminutes', 'noabbrevs' => true ], + '2 hours 0 minutes', + 'formatTimePeriod() rounding (>=1h), avoidminutes' + ], + [ + 172799.55, + 'avoidseconds', + '48 h 0 min', + 'formatTimePeriod() rounding (=48h), avoidseconds' + ], + [ + 172799.55, + [ 'avoid' => 'avoidseconds', 'noabbrevs' => true ], + '48 hours 0 minutes', + 'formatTimePeriod() rounding (=48h), avoidseconds' + ], + [ + 259199.55, + 'avoidminutes', + '3 d 0 h', + 'formatTimePeriod() rounding (>48h), avoidminutes' + ], + [ + 259199.55, + [ 'avoid' => 'avoidminutes', 'noabbrevs' => true ], + '3 days 0 hours', + 'formatTimePeriod() rounding (>48h), avoidminutes' + ], + [ + 176399.55, + 'avoidseconds', + '2 d 1 h 0 min', + 'formatTimePeriod() rounding (>48h), avoidseconds' + ], + [ + 176399.55, + [ 'avoid' => 'avoidseconds', 'noabbrevs' => true ], + '2 days 1 hour 0 minutes', + 'formatTimePeriod() rounding (>48h), avoidseconds' + ], + [ + 176399.55, + 'avoidminutes', + '2 d 1 h', + 'formatTimePeriod() rounding (>48h), avoidminutes' + ], + [ + 176399.55, + [ 'avoid' => 'avoidminutes', 'noabbrevs' => true ], + '2 days 1 hour', + 'formatTimePeriod() rounding (>48h), avoidminutes' + ], + [ + 259199.55, + 'avoidseconds', + '3 d 0 h 0 min', + 'formatTimePeriod() rounding (>48h), avoidseconds' + ], + [ + 259199.55, + [ 'avoid' => 'avoidseconds', 'noabbrevs' => true ], + '3 days 0 hours 0 minutes', + 'formatTimePeriod() rounding (>48h), avoidseconds' + ], + [ + 172801.55, + 'avoidseconds', + '2 d 0 h 0 min', + 'formatTimePeriod() rounding, (>48h), avoidseconds' + ], + [ + 172801.55, + [ 'avoid' => 'avoidseconds', 'noabbrevs' => true ], + '2 days 0 hours 0 minutes', + 'formatTimePeriod() rounding, (>48h), avoidseconds' + ], + [ + 176460.55, + [], + '2 d 1 h 1 min 1 s', + 'formatTimePeriod() rounding, recursion, (>48h)' + ], + [ + 176460.55, + [ 'noabbrevs' => true ], + '2 days 1 hour 1 minute 1 second', + 'formatTimePeriod() rounding, recursion, (>48h)' + ], + ]; + } + + /** + * @covers Language::truncate + */ + public function testTruncate() { + $this->assertEquals( + "XXX", + $this->getLang()->truncate( "1234567890", 0, 'XXX' ), + 'truncate prefix, len 0, small ellipsis' + ); + + $this->assertEquals( + "12345XXX", + $this->getLang()->truncate( "1234567890", 8, 'XXX' ), + 'truncate prefix, small ellipsis' + ); + + $this->assertEquals( + "123456789", + $this->getLang()->truncate( "123456789", 5, 'XXXXXXXXXXXXXXX' ), + 'truncate prefix, large ellipsis' + ); + + $this->assertEquals( + "XXX67890", + $this->getLang()->truncate( "1234567890", -8, 'XXX' ), + 'truncate suffix, small ellipsis' + ); + + $this->assertEquals( + "123456789", + $this->getLang()->truncate( "123456789", -5, 'XXXXXXXXXXXXXXX' ), + 'truncate suffix, large ellipsis' + ); + $this->assertEquals( + "123XXX", + $this->getLang()->truncate( "123 ", 9, 'XXX' ), + 'truncate prefix, with spaces' + ); + $this->assertEquals( + "12345XXX", + $this->getLang()->truncate( "12345 8", 11, 'XXX' ), + 'truncate prefix, with spaces and non-space ending' + ); + $this->assertEquals( + "XXX234", + $this->getLang()->truncate( "1 234", -8, 'XXX' ), + 'truncate suffix, with spaces' + ); + $this->assertEquals( + "12345XXX", + $this->getLang()->truncate( "1234567890", 5, 'XXX', false ), + 'truncate without adjustment' + ); + $this->assertEquals( + "泰乐菌...", + $this->getLang()->truncate( "泰乐菌素123456789", 11, '...', false ), + 'truncate does not chop Unicode characters in half' + ); + $this->assertEquals( + "\n泰乐菌...", + $this->getLang()->truncate( "\n泰乐菌素123456789", 12, '...', false ), + 'truncate does not chop Unicode characters in half if there is a preceding newline' + ); + } + + /** + * @dataProvider provideHTMLTruncateData + * @covers Language::truncateHTML + */ + public function testTruncateHtml( $len, $ellipsis, $input, $expected ) { + // Actual HTML... + $this->assertEquals( + $expected, + $this->getLang()->truncateHtml( $input, $len, $ellipsis ) + ); + } + + /** + * @return array Format is ($len, $ellipsis, $input, $expected) + */ + public static function provideHTMLTruncateData() { + return [ + [ 0, 'XXX', "1234567890", "XXX" ], + [ 8, 'XXX', "1234567890", "12345XXX" ], + [ 5, 'XXXXXXXXXXXXXXX', '1234567890', "1234567890" ], + [ 2, '***', + '

', + '

', + ], + [ 2, '***', + '

123456789

', + '

***

', + ], + [ 2, '***', + '

 23456789

', + '

***

', + ], + [ 3, '***', + '

123456789

', + '

***

', + ], + [ 4, '***', + '

123456789

', + '

1***

', + ], + [ 5, '***', + '123456789', + '12***', + ], + [ 6, '***', + '

123456789

', + '

123***

', + ], + [ 6, '***', + '

12 456789

', + '

12 ***

', + ], + [ 7, '***', + '123

456

789
', + '123

4***

', + ], + [ 8, '***', + '
123456789
', + '
12345***
', + ], + [ 9, '***', + '

123456789

', + '

123456789

', + ], + [ 10, '***', + '

123456789

', + '

123456789

', + ], + ]; + } + + /** + * Test Language::isWellFormedLanguageTag() + * @dataProvider provideWellFormedLanguageTags + * @covers Language::isWellFormedLanguageTag + */ + public function testWellFormedLanguageTag( $code, $message = '' ) { + $this->assertTrue( + Language::isWellFormedLanguageTag( $code ), + "validating code $code $message" + ); + } + + /** + * The test cases are based on the tests in the GaBuZoMeu parser + * written by Stéphane Bortzmeyer + * and distributed as free software, under the GNU General Public Licence. + * http://www.bortzmeyer.org/gabuzomeu-parsing-language-tags.html + */ + public static function provideWellFormedLanguageTags() { + return [ + [ 'fr', 'two-letter code' ], + [ 'fr-latn', 'two-letter code with lower case script code' ], + [ 'fr-Latn-FR', 'two-letter code with title case script code and uppercase country code' ], + [ 'fr-Latn-419', 'two-letter code with title case script code and region number' ], + [ 'fr-FR', 'two-letter code with uppercase' ], + [ 'ax-TZ', 'Not in the registry, but well-formed' ], + [ 'fr-shadok', 'two-letter code with variant' ], + [ 'fr-y-myext-myext2', 'non-x singleton' ], + [ 'fra-Latn', 'ISO 639 can be 3-letters' ], + [ 'fra', 'three-letter language code' ], + [ 'fra-FX', 'three-letter language code with country code' ], + [ 'i-klingon', 'grandfathered with singleton' ], + [ 'I-kLINgon', 'tags are case-insensitive...' ], + [ 'no-bok', 'grandfathered without singleton' ], + [ 'i-enochian', 'Grandfathered' ], + [ 'x-fr-CH', 'private use' ], + [ 'es-419', 'two-letter code with region number' ], + [ 'en-Latn-GB-boont-r-extended-sequence-x-private', 'weird, but well-formed' ], + [ 'ab-x-abc-x-abc', 'anything goes after x' ], + [ 'ab-x-abc-a-a', 'anything goes after x, including several non-x singletons' ], + [ 'i-default', 'grandfathered' ], + [ 'abcd-Latn', 'Language of 4 chars reserved for future use' ], + [ 'AaBbCcDd-x-y-any-x', 'Language of 5-8 chars, registered' ], + [ 'de-CH-1901', 'with country and year' ], + [ 'en-US-x-twain', 'with country and singleton' ], + [ 'zh-cmn', 'three-letter variant' ], + [ 'zh-cmn-Hant', 'three-letter variant and script' ], + [ 'zh-cmn-Hant-HK', 'three-letter variant, script and country' ], + [ 'xr-p-lze', 'Extension' ], + ]; + } + + /** + * Negative test for Language::isWellFormedLanguageTag() + * @dataProvider provideMalformedLanguageTags + * @covers Language::isWellFormedLanguageTag + */ + public function testMalformedLanguageTag( $code, $message = '' ) { + $this->assertFalse( + Language::isWellFormedLanguageTag( $code ), + "validating that code $code is a malformed language tag - $message" + ); + } + + /** + * The test cases are based on the tests in the GaBuZoMeu parser + * written by Stéphane Bortzmeyer + * and distributed as free software, under the GNU General Public Licence. + * http://www.bortzmeyer.org/gabuzomeu-parsing-language-tags.html + */ + public static function provideMalformedLanguageTags() { + return [ + [ 'f', 'language too short' ], + [ 'f-Latn', 'language too short with script' ], + [ 'xr-lxs-qut', 'variants too short' ], # extlangS + [ 'fr-Latn-F', 'region too short' ], + [ 'a-value', 'language too short with region' ], + [ 'tlh-a-b-foo', 'valid three-letter with wrong variant' ], + [ + 'i-notexist', + 'grandfathered but not registered: invalid, even if we only test well-formedness' + ], + [ 'abcdefghi-012345678', 'numbers too long' ], + [ 'ab-abc-abc-abc-abc', 'invalid extensions' ], + [ 'ab-abcd-abc', 'invalid extensions' ], + [ 'ab-ab-abc', 'invalid extensions' ], + [ 'ab-123-abc', 'invalid extensions' ], + [ 'a-Hant-ZH', 'short language with valid extensions' ], + [ 'a1-Hant-ZH', 'invalid character in language' ], + [ 'ab-abcde-abc', 'invalid extensions' ], + [ 'ab-1abc-abc', 'invalid characters in extensions' ], + [ 'ab-ab-abcd', 'invalid order of extensions' ], + [ 'ab-123-abcd', 'invalid order of extensions' ], + [ 'ab-abcde-abcd', 'invalid extensions' ], + [ 'ab-1abc-abcd', 'invalid characters in extensions' ], + [ 'ab-a-b', 'extensions too short' ], + [ 'ab-a-x', 'extensions too short, even with singleton' ], + [ 'ab--ab', 'two separators' ], + [ 'ab-abc-', 'separator in the end' ], + [ '-ab-abc', 'separator in the beginning' ], + [ 'abcd-efg', 'language too long' ], + [ 'aabbccddE', 'tag too long' ], + [ 'pa_guru', 'A tag with underscore is invalid in strict mode' ], + [ 'de-f', 'subtag too short' ], + ]; + } + + /** + * Negative test for Language::isWellFormedLanguageTag() + * @covers Language::isWellFormedLanguageTag + */ + public function testLenientLanguageTag() { + $this->assertTrue( + Language::isWellFormedLanguageTag( 'pa_guru', true ), + 'pa_guru is a well-formed language tag in lenient mode' + ); + } + + /** + * Test Language::isValidBuiltInCode() + * @dataProvider provideLanguageCodes + * @covers Language::isValidBuiltInCode + */ + public function testBuiltInCodeValidation( $code, $expected, $message = '' ) { + $this->assertEquals( $expected, + (bool)Language::isValidBuiltInCode( $code ), + "validating code $code $message" + ); + } + + public static function provideLanguageCodes() { + return [ + [ 'fr', true, 'Two letters, minor case' ], + [ 'EN', false, 'Two letters, upper case' ], + [ 'tyv', true, 'Three letters' ], + [ 'be-tarask', true, 'With dash' ], + [ 'be-x-old', true, 'With extension (two dashes)' ], + [ 'be_tarask', false, 'Reject underscores' ], + ]; + } + + /** + * Test Language::isKnownLanguageTag() + * @dataProvider provideKnownLanguageTags + * @covers Language::isKnownLanguageTag + */ + public function testKnownLanguageTag( $code, $message = '' ) { + $this->assertTrue( + (bool)Language::isKnownLanguageTag( $code ), + "validating code $code - $message" + ); + } + + public static function provideKnownLanguageTags() { + return [ + [ 'fr', 'simple code' ], + [ 'bat-smg', 'an MW legacy tag' ], + [ 'sgs', 'an internal standard MW name, for which a legacy tag is used externally' ], + ]; + } + + /** + * @covers Language::isKnownLanguageTag + */ + public function testKnownCldrLanguageTag() { + if ( !class_exists( 'LanguageNames' ) ) { + $this->markTestSkipped( 'The LanguageNames class is not available. ' + . 'The CLDR extension is probably not installed.' ); + } + + $this->assertTrue( + (bool)Language::isKnownLanguageTag( 'pal' ), + 'validating code "pal" an ancient language, which probably will ' + . 'not appear in Names.php, but appears in CLDR in English' + ); + } + + /** + * Negative tests for Language::isKnownLanguageTag() + * @dataProvider provideUnKnownLanguageTags + * @covers Language::isKnownLanguageTag + */ + public function testUnknownLanguageTag( $code, $message = '' ) { + $this->assertFalse( + (bool)Language::isKnownLanguageTag( $code ), + "checking that code $code is invalid - $message" + ); + } + + public static function provideUnknownLanguageTags() { + return [ + [ 'mw', 'non-existent two-letter code' ], + [ 'foo"getLang()->sprintfDate( 'xiY', '1234567890123' ); + } + + /** + * Test too long timestamp + * @expectedException MWException + * @covers Language::sprintfDate + */ + public function testSprintfDateTooLongTimestamp() { + $this->getLang()->sprintfDate( 'xiY', '123456789012345' ); + } + + /** + * Test too short timestamp + * @expectedException MWException + * @covers Language::sprintfDate + */ + public function testSprintfDateNotAllDigitTimestamp() { + $this->getLang()->sprintfDate( 'xiY', '-1234567890123' ); + } + + /** + * @dataProvider provideSprintfDateSamples + * @covers Language::sprintfDate + */ + public function testSprintfDate( $format, $ts, $expected, $msg ) { + $ttl = null; + $this->assertEquals( + $expected, + $this->getLang()->sprintfDate( $format, $ts, null, $ttl ), + "sprintfDate('$format', '$ts'): $msg" + ); + if ( $ttl ) { + $dt = new DateTime( $ts ); + $lastValidTS = $dt->add( new DateInterval( 'PT' . ( $ttl - 1 ) . 'S' ) )->format( 'YmdHis' ); + $this->assertEquals( + $expected, + $this->getLang()->sprintfDate( $format, $lastValidTS, null ), + "sprintfDate('$format', '$ts'): TTL $ttl too high (output was different at $lastValidTS)" + ); + } else { + // advance the time enough to make all of the possible outputs different (except possibly L) + $dt = new DateTime( $ts ); + $newTS = $dt->add( new DateInterval( 'P1Y1M8DT13H1M1S' ) )->format( 'YmdHis' ); + $this->assertEquals( + $expected, + $this->getLang()->sprintfDate( $format, $newTS, null ), + "sprintfDate('$format', '$ts'): Missing TTL (output was different at $newTS)" + ); + } + } + + /** + * sprintfDate should always use UTC when no zone is given. + * @dataProvider provideSprintfDateSamples + * @covers Language::sprintfDate + */ + public function testSprintfDateNoZone( $format, $ts, $expected, $ignore, $msg ) { + $oldTZ = date_default_timezone_get(); + $res = date_default_timezone_set( 'Asia/Seoul' ); + if ( !$res ) { + $this->markTestSkipped( "Error setting Timezone" ); + } + + $this->assertEquals( + $expected, + $this->getLang()->sprintfDate( $format, $ts ), + "sprintfDate('$format', '$ts'): $msg" + ); + + date_default_timezone_set( $oldTZ ); + } + + /** + * sprintfDate should use passed timezone + * @dataProvider provideSprintfDateSamples + * @covers Language::sprintfDate + */ + public function testSprintfDateTZ( $format, $ts, $ignore, $expected, $msg ) { + $tz = new DateTimeZone( 'Asia/Seoul' ); + if ( !$tz ) { + $this->markTestSkipped( "Error getting Timezone" ); + } + + $this->assertEquals( + $expected, + $this->getLang()->sprintfDate( $format, $ts, $tz ), + "sprintfDate('$format', '$ts', 'Asia/Seoul'): $msg" + ); + } + + /** + * sprintfDate should only calculate a TTL if the caller is going to use it. + * @covers Language::sprintfDate + */ + public function testSprintfDateNoTtlIfNotNeeded() { + $noTtl = 'unused'; // Value used to represent that the caller didn't pass a variable in. + $ttl = null; + $this->getLang()->sprintfDate( 'YmdHis', wfTimestampNow(), null, $noTtl ); + $this->getLang()->sprintfDate( 'YmdHis', wfTimestampNow(), null, $ttl ); + + $this->assertSame( + 'unused', + $noTtl, + 'If the caller does not set the $ttl variable, do not compute it.' + ); + $this->assertInternalType( 'int', $ttl, 'TTL should have been computed.' ); + } + + public static function provideSprintfDateSamples() { + return [ + [ + 'xiY', + '20111212000000', + '1390', // note because we're testing English locale we get Latin-standard digits + '1390', + 'Iranian calendar full year' + ], + [ + 'xiy', + '20111212000000', + '90', + '90', + 'Iranian calendar short year' + ], + [ + 'o', + '20120101235000', + '2011', + '2011', + 'ISO 8601 (week) year' + ], + [ + 'W', + '20120101235000', + '52', + '52', + 'Week number' + ], + [ + 'W', + '20120102235000', + '1', + '1', + 'Week number' + ], + [ + 'o-\\WW-N', + '20091231235000', + '2009-W53-4', + '2009-W53-4', + 'leap week' + ], + // What follows is mostly copied from + // https://www.mediawiki.org/wiki/Help:Extension:ParserFunctions#.23time + [ + 'Y', + '20120102090705', + '2012', + '2012', + 'Full year' + ], + [ + 'y', + '20120102090705', + '12', + '12', + '2 digit year' + ], + [ + 'L', + '20120102090705', + '1', + '1', + 'Leap year' + ], + [ + 'n', + '20120102090705', + '1', + '1', + 'Month index, not zero pad' + ], + [ + 'N', + '20120102090705', + '01', + '01', + 'Month index. Zero pad' + ], + [ + 'M', + '20120102090705', + 'Jan', + 'Jan', + 'Month abbrev' + ], + [ + 'F', + '20120102090705', + 'January', + 'January', + 'Full month' + ], + [ + 'xg', + '20120102090705', + 'January', + 'January', + 'Genitive month name (same in EN)' + ], + [ + 'j', + '20120102090705', + '2', + '2', + 'Day of month (not zero pad)' + ], + [ + 'd', + '20120102090705', + '02', + '02', + 'Day of month (zero-pad)' + ], + [ + 'z', + '20120102090705', + '1', + '1', + 'Day of year (zero-indexed)' + ], + [ + 'D', + '20120102090705', + 'Mon', + 'Mon', + 'Day of week (abbrev)' + ], + [ + 'l', + '20120102090705', + 'Monday', + 'Monday', + 'Full day of week' + ], + [ + 'N', + '20120101090705', + '7', + '7', + 'Day of week (Mon=1, Sun=7)' + ], + [ + 'w', + '20120101090705', + '0', + '0', + 'Day of week (Sun=0, Sat=6)' + ], + [ + 'N', + '20120102090705', + '1', + '1', + 'Day of week' + ], + [ + 'a', + '20120102090705', + 'am', + 'am', + 'am vs pm' + ], + [ + 'A', + '20120102120000', + 'PM', + 'PM', + 'AM vs PM' + ], + [ + 'a', + '20120102000000', + 'am', + 'am', + 'AM vs PM' + ], + [ + 'g', + '20120102090705', + '9', + '9', + '12 hour, not Zero' + ], + [ + 'h', + '20120102090705', + '09', + '09', + '12 hour, zero padded' + ], + [ + 'G', + '20120102090705', + '9', + '9', + '24 hour, not zero' + ], + [ + 'H', + '20120102090705', + '09', + '09', + '24 hour, zero' + ], + [ + 'H', + '20120102110705', + '11', + '11', + '24 hour, zero' + ], + [ + 'i', + '20120102090705', + '07', + '07', + 'Minutes' + ], + [ + 's', + '20120102090705', + '05', + '05', + 'seconds' + ], + [ + 'U', + '20120102090705', + '1325495225', + '1325462825', + 'unix time' + ], + [ + 't', + '20120102090705', + '31', + '31', + 'Days in current month' + ], + [ + 'c', + '20120102090705', + '2012-01-02T09:07:05+00:00', + '2012-01-02T09:07:05+09:00', + 'ISO 8601 timestamp' + ], + [ + 'r', + '20120102090705', + 'Mon, 02 Jan 2012 09:07:05 +0000', + 'Mon, 02 Jan 2012 09:07:05 +0900', + 'RFC 5322' + ], + [ + 'e', + '20120102090705', + 'UTC', + 'Asia/Seoul', + 'Timezone identifier' + ], + [ + 'I', + '19880602090705', + '0', + '1', + 'DST indicator' + ], + [ + 'O', + '20120102090705', + '+0000', + '+0900', + 'Timezone offset' + ], + [ + 'P', + '20120102090705', + '+00:00', + '+09:00', + 'Timezone offset with colon' + ], + [ + 'T', + '20120102090705', + 'UTC', + 'KST', + 'Timezone abbreviation' + ], + [ + 'Z', + '20120102090705', + '0', + '32400', + 'Timezone offset in seconds' + ], + [ + 'xmj xmF xmn xmY', + '20120102090705', + '7 Safar 2 1433', + '7 Safar 2 1433', + 'Islamic' + ], + [ + 'xij xiF xin xiY', + '20120102090705', + '12 Dey 10 1390', + '12 Dey 10 1390', + 'Iranian' + ], + [ + 'xjj xjF xjn xjY', + '20120102090705', + '7 Tevet 4 5772', + '7 Tevet 4 5772', + 'Hebrew' + ], + [ + 'xjt', + '20120102090705', + '29', + '29', + 'Hebrew number of days in month' + ], + [ + 'xjx', + '20120102090705', + 'Tevet', + 'Tevet', + 'Hebrew genitive month name (No difference in EN)' + ], + [ + 'xkY', + '20120102090705', + '2555', + '2555', + 'Thai year' + ], + [ + 'xoY', + '20120102090705', + '101', + '101', + 'Minguo' + ], + [ + 'xtY', + '20120102090705', + '平成24', + '平成24', + 'nengo' + ], + [ + 'xrxkYY', + '20120102090705', + 'MMDLV2012', + 'MMDLV2012', + 'Roman numerals' + ], + [ + 'xhxjYY', + '20120102090705', + 'ה\'תשע"ב2012', + 'ה\'תשע"ב2012', + 'Hebrew numberals' + ], + [ + 'xnY', + '20120102090705', + '2012', + '2012', + 'Raw numerals (doesn\'t mean much in EN)' + ], + [ + '[[Y "(yea"\\r)]] \\"xx\\"', + '20120102090705', + '[[2012 (year)]] "x"', + '[[2012 (year)]] "x"', + 'Various escaping' + ], + + ]; + } + + /** + * @dataProvider provideFormatSizes + * @covers Language::formatSize + */ + public function testFormatSize( $size, $expected, $msg ) { + $this->assertEquals( + $expected, + $this->getLang()->formatSize( $size ), + "formatSize('$size'): $msg" + ); + } + + public static function provideFormatSizes() { + return [ + [ + 0, + "0 bytes", + "Zero bytes" + ], + [ + 1024, + "1 KB", + "1 kilobyte" + ], + [ + 1024 * 1024, + "1 MB", + "1,024 megabytes" + ], + [ + 1024 * 1024 * 1024, + "1 GB", + "1 gigabyte" + ], + [ + pow( 1024, 4 ), + "1 TB", + "1 terabyte" + ], + [ + pow( 1024, 5 ), + "1 PB", + "1 petabyte" + ], + [ + pow( 1024, 6 ), + "1 EB", + "1,024 exabyte" + ], + [ + pow( 1024, 7 ), + "1 ZB", + "1 zetabyte" + ], + [ + pow( 1024, 8 ), + "1 YB", + "1 yottabyte" + ], + // How big!? THIS BIG! + ]; + } + + /** + * @dataProvider provideFormatBitrate + * @covers Language::formatBitrate + */ + public function testFormatBitrate( $bps, $expected, $msg ) { + $this->assertEquals( + $expected, + $this->getLang()->formatBitrate( $bps ), + "formatBitrate('$bps'): $msg" + ); + } + + public static function provideFormatBitrate() { + return [ + [ + 0, + "0 bps", + "0 bits per second" + ], + [ + 999, + "999 bps", + "999 bits per second" + ], + [ + 1000, + "1 kbps", + "1 kilobit per second" + ], + [ + 1000 * 1000, + "1 Mbps", + "1 megabit per second" + ], + [ + pow( 10, 9 ), + "1 Gbps", + "1 gigabit per second" + ], + [ + pow( 10, 12 ), + "1 Tbps", + "1 terabit per second" + ], + [ + pow( 10, 15 ), + "1 Pbps", + "1 petabit per second" + ], + [ + pow( 10, 18 ), + "1 Ebps", + "1 exabit per second" + ], + [ + pow( 10, 21 ), + "1 Zbps", + "1 zetabit per second" + ], + [ + pow( 10, 24 ), + "1 Ybps", + "1 yottabit per second" + ], + [ + pow( 10, 27 ), + "1,000 Ybps", + "1,000 yottabits per second" + ], + ]; + } + + /** + * @dataProvider provideFormatDuration + * @covers Language::formatDuration + */ + public function testFormatDuration( $duration, $expected, $intervals = [] ) { + $this->assertEquals( + $expected, + $this->getLang()->formatDuration( $duration, $intervals ), + "formatDuration('$duration'): $expected" + ); + } + + public static function provideFormatDuration() { + return [ + [ + 0, + '0 seconds', + ], + [ + 1, + '1 second', + ], + [ + 2, + '2 seconds', + ], + [ + 60, + '1 minute', + ], + [ + 2 * 60, + '2 minutes', + ], + [ + 3600, + '1 hour', + ], + [ + 2 * 3600, + '2 hours', + ], + [ + 24 * 3600, + '1 day', + ], + [ + 2 * 86400, + '2 days', + ], + [ + // ( 365 + ( 24 * 3 + 25 ) / 400 ) * 86400 = 31556952 + ( 365 + ( 24 * 3 + 25 ) / 400.0 ) * 86400, + '1 year', + ], + [ + 2 * 31556952, + '2 years', + ], + [ + 10 * 31556952, + '1 decade', + ], + [ + 20 * 31556952, + '2 decades', + ], + [ + 100 * 31556952, + '1 century', + ], + [ + 200 * 31556952, + '2 centuries', + ], + [ + 1000 * 31556952, + '1 millennium', + ], + [ + 2000 * 31556952, + '2 millennia', + ], + [ + 9001, + '2 hours, 30 minutes and 1 second' + ], + [ + 3601, + '1 hour and 1 second' + ], + [ + 31556952 + 2 * 86400 + 9000, + '1 year, 2 days, 2 hours and 30 minutes' + ], + [ + 42 * 1000 * 31556952 + 42, + '42 millennia and 42 seconds' + ], + [ + 60, + '60 seconds', + [ 'seconds' ], + ], + [ + 61, + '61 seconds', + [ 'seconds' ], + ], + [ + 1, + '1 second', + [ 'seconds' ], + ], + [ + 31556952 + 2 * 86400 + 9000, + '1 year, 2 days and 150 minutes', + [ 'years', 'days', 'minutes' ], + ], + [ + 42, + '0 days', + [ 'years', 'days' ], + ], + [ + 31556952 + 2 * 86400 + 9000, + '1 year, 2 days and 150 minutes', + [ 'minutes', 'days', 'years' ], + ], + [ + 42, + '0 days', + [ 'days', 'years' ], + ], + ]; + } + + /** + * @dataProvider provideCheckTitleEncodingData + * @covers Language::checkTitleEncoding + */ + public function testCheckTitleEncoding( $s ) { + $this->assertEquals( + $s, + $this->getLang()->checkTitleEncoding( $s ), + "checkTitleEncoding('$s')" + ); + } + + public static function provideCheckTitleEncodingData() { + // @codingStandardsIgnoreStart Ignore Generic.Files.LineLength.TooLong + return [ + [ "" ], + [ "United States of America" ], // 7bit ASCII + [ rawurldecode( "S%C3%A9rie%20t%C3%A9l%C3%A9vis%C3%A9e" ) ], + [ + rawurldecode( + "Acteur%7CAlbert%20Robbins%7CAnglais%7CAnn%20Donahue%7CAnthony%20E.%20Zuiker%7CCarol%20Mendelsohn" + ) + ], + // The following two data sets come from T38839. They fail if checkTitleEncoding uses a regexp to test for + // valid UTF-8 encoding and the pcre.recursion_limit is low (like, say, 1024). They succeed if checkTitleEncoding + // uses mb_check_encoding for its test. + [ + rawurldecode( + "Acteur%7CAlbert%20Robbins%7CAnglais%7CAnn%20Donahue%7CAnthony%20E.%20Zuiker%7CCarol%20Mendelsohn%7C" + . "Catherine%20Willows%7CDavid%20Hodges%7CDavid%20Phillips%7CGil%20Grissom%7CGreg%20Sanders%7CHodges%7C" + . "Internet%20Movie%20Database%7CJim%20Brass%7CLady%20Heather%7C" + . "Les%20Experts%20(s%C3%A9rie%20t%C3%A9l%C3%A9vis%C3%A9e)%7CLes%20Experts%20:%20Manhattan%7C" + . "Les%20Experts%20:%20Miami%7CListe%20des%20personnages%20des%20Experts%7C" + . "Liste%20des%20%C3%A9pisodes%20des%20Experts%7CMod%C3%A8le%20discussion:Palette%20Les%20Experts%7C" + . "Nick%20Stokes%7CPersonnage%20de%20fiction%7CPersonnage%20fictif%7CPersonnage%20de%20fiction%7C" + . "Personnages%20r%C3%A9currents%20dans%20Les%20Experts%7CRaymond%20Langston%7CRiley%20Adams%7C" + . "Saison%201%20des%20Experts%7CSaison%2010%20des%20Experts%7CSaison%2011%20des%20Experts%7C" + . "Saison%2012%20des%20Experts%7CSaison%202%20des%20Experts%7CSaison%203%20des%20Experts%7C" + . "Saison%204%20des%20Experts%7CSaison%205%20des%20Experts%7CSaison%206%20des%20Experts%7C" + . "Saison%207%20des%20Experts%7CSaison%208%20des%20Experts%7CSaison%209%20des%20Experts%7C" + . "Sara%20Sidle%7CSofia%20Curtis%7CS%C3%A9rie%20t%C3%A9l%C3%A9vis%C3%A9e%7CWallace%20Langham%7C" + . "Warrick%20Brown%7CWendy%20Simms%7C%C3%89tats-Unis" + ), + ], + [ + rawurldecode( + "Mod%C3%A8le%3AArrondissements%20homonymes%7CMod%C3%A8le%3ABandeau%20standard%20pour%20page%20d'homonymie%7C" + . "Mod%C3%A8le%3ABatailles%20homonymes%7CMod%C3%A8le%3ACantons%20homonymes%7C" + . "Mod%C3%A8le%3ACommunes%20fran%C3%A7aises%20homonymes%7CMod%C3%A8le%3AFilms%20homonymes%7C" + . "Mod%C3%A8le%3AGouvernements%20homonymes%7CMod%C3%A8le%3AGuerres%20homonymes%7CMod%C3%A8le%3AHomonymie%7C" + . "Mod%C3%A8le%3AHomonymie%20bateau%7CMod%C3%A8le%3AHomonymie%20d'%C3%A9tablissements%20scolaires%20ou" + . "%20universitaires%7CMod%C3%A8le%3AHomonymie%20d'%C3%AEles%7CMod%C3%A8le%3AHomonymie%20de%20clubs%20sportifs%7C" + . "Mod%C3%A8le%3AHomonymie%20de%20comt%C3%A9s%7CMod%C3%A8le%3AHomonymie%20de%20monument%7C" + . "Mod%C3%A8le%3AHomonymie%20de%20nom%20romain%7CMod%C3%A8le%3AHomonymie%20de%20parti%20politique%7C" + . "Mod%C3%A8le%3AHomonymie%20de%20route%7CMod%C3%A8le%3AHomonymie%20dynastique%7C" + . "Mod%C3%A8le%3AHomonymie%20vid%C3%A9oludique%7CMod%C3%A8le%3AHomonymie%20%C3%A9difice%20religieux%7C" + . "Mod%C3%A8le%3AInternationalisation%7CMod%C3%A8le%3AIsom%C3%A9rie%7CMod%C3%A8le%3AParonymie%7C" + . "Mod%C3%A8le%3APatronyme%7CMod%C3%A8le%3APatronyme%20basque%7CMod%C3%A8le%3APatronyme%20italien%7C" + . "Mod%C3%A8le%3APatronymie%7CMod%C3%A8le%3APersonnes%20homonymes%7CMod%C3%A8le%3ASaints%20homonymes%7C" + . "Mod%C3%A8le%3ATitres%20homonymes%7CMod%C3%A8le%3AToponymie%7CMod%C3%A8le%3AUnit%C3%A9s%20homonymes%7C" + . "Mod%C3%A8le%3AVilles%20homonymes%7CMod%C3%A8le%3A%C3%89difices%20religieux%20homonymes" + ) + ] + ]; + // @codingStandardsIgnoreEnd + } + + /** + * @dataProvider provideRomanNumeralsData + * @covers Language::romanNumeral + */ + public function testRomanNumerals( $num, $numerals ) { + $this->assertEquals( + $numerals, + Language::romanNumeral( $num ), + "romanNumeral('$num')" + ); + } + + public static function provideRomanNumeralsData() { + return [ + [ 1, 'I' ], + [ 2, 'II' ], + [ 3, 'III' ], + [ 4, 'IV' ], + [ 5, 'V' ], + [ 6, 'VI' ], + [ 7, 'VII' ], + [ 8, 'VIII' ], + [ 9, 'IX' ], + [ 10, 'X' ], + [ 20, 'XX' ], + [ 30, 'XXX' ], + [ 40, 'XL' ], + [ 49, 'XLIX' ], + [ 50, 'L' ], + [ 60, 'LX' ], + [ 70, 'LXX' ], + [ 80, 'LXXX' ], + [ 90, 'XC' ], + [ 99, 'XCIX' ], + [ 100, 'C' ], + [ 200, 'CC' ], + [ 300, 'CCC' ], + [ 400, 'CD' ], + [ 500, 'D' ], + [ 600, 'DC' ], + [ 700, 'DCC' ], + [ 800, 'DCCC' ], + [ 900, 'CM' ], + [ 999, 'CMXCIX' ], + [ 1000, 'M' ], + [ 1989, 'MCMLXXXIX' ], + [ 2000, 'MM' ], + [ 3000, 'MMM' ], + [ 4000, 'MMMM' ], + [ 5000, 'MMMMM' ], + [ 6000, 'MMMMMM' ], + [ 7000, 'MMMMMMM' ], + [ 8000, 'MMMMMMMM' ], + [ 9000, 'MMMMMMMMM' ], + [ 9999, 'MMMMMMMMMCMXCIX' ], + [ 10000, 'MMMMMMMMMM' ], + ]; + } + + /** + * @dataProvider provideHebrewNumeralsData + * @covers Language::hebrewNumeral + */ + public function testHebrewNumeral( $num, $numerals ) { + $this->assertEquals( + $numerals, + Language::hebrewNumeral( $num ), + "hebrewNumeral('$num')" + ); + } + + public static function provideHebrewNumeralsData() { + return [ + [ -1, -1 ], + [ 0, 0 ], + [ 1, "א'" ], + [ 2, "ב'" ], + [ 3, "ג'" ], + [ 4, "ד'" ], + [ 5, "ה'" ], + [ 6, "ו'" ], + [ 7, "ז'" ], + [ 8, "ח'" ], + [ 9, "ט'" ], + [ 10, "י'" ], + [ 11, 'י"א' ], + [ 14, 'י"ד' ], + [ 15, 'ט"ו' ], + [ 16, 'ט"ז' ], + [ 17, 'י"ז' ], + [ 20, "כ'" ], + [ 21, 'כ"א' ], + [ 30, "ל'" ], + [ 40, "מ'" ], + [ 50, "נ'" ], + [ 60, "ס'" ], + [ 70, "ע'" ], + [ 80, "פ'" ], + [ 90, "צ'" ], + [ 99, 'צ"ט' ], + [ 100, "ק'" ], + [ 101, 'ק"א' ], + [ 110, 'ק"י' ], + [ 200, "ר'" ], + [ 300, "ש'" ], + [ 400, "ת'" ], + [ 500, 'ת"ק' ], + [ 800, 'ת"ת' ], + [ 1000, "א' אלף" ], + [ 1001, "א'א'" ], + [ 1012, "א'י\"ב" ], + [ 1020, "א'ך'" ], + [ 1030, "א'ל'" ], + [ 1081, "א'פ\"א" ], + [ 2000, "ב' אלפים" ], + [ 2016, "ב'ט\"ז" ], + [ 3000, "ג' אלפים" ], + [ 4000, "ד' אלפים" ], + [ 4904, "ד'תתק\"ד" ], + [ 5000, "ה' אלפים" ], + [ 5680, "ה'תר\"ף" ], + [ 5690, "ה'תר\"ץ" ], + [ 5708, "ה'תש\"ח" ], + [ 5720, "ה'תש\"ך" ], + [ 5740, "ה'תש\"ם" ], + [ 5750, "ה'תש\"ן" ], + [ 5775, "ה'תשע\"ה" ], + ]; + } + + /** + * @dataProvider providePluralData + * @covers Language::convertPlural + */ + public function testConvertPlural( $expected, $number, $forms ) { + $chosen = $this->getLang()->convertPlural( $number, $forms ); + $this->assertEquals( $expected, $chosen ); + } + + public static function providePluralData() { + // Params are: [expected text, number given, [the plural forms]] + return [ + [ 'plural', 0, [ + 'singular', 'plural' + ] ], + [ 'explicit zero', 0, [ + '0=explicit zero', 'singular', 'plural' + ] ], + [ 'explicit one', 1, [ + 'singular', 'plural', '1=explicit one', + ] ], + [ 'singular', 1, [ + 'singular', 'plural', '0=explicit zero', + ] ], + [ 'plural', 3, [ + '0=explicit zero', '1=explicit one', 'singular', 'plural' + ] ], + [ 'explicit eleven', 11, [ + 'singular', 'plural', '11=explicit eleven', + ] ], + [ 'plural', 12, [ + 'singular', 'plural', '11=explicit twelve', + ] ], + [ 'plural', 12, [ + 'singular', 'plural', '=explicit form', + ] ], + [ 'other', 2, [ + 'kissa=kala', '1=2=3', 'other', + ] ], + [ '', 2, [ + '0=explicit zero', '1=explicit one', + ] ], + ]; + } + + /** + * @covers Language::embedBidi() + */ + public function testEmbedBidi() { + $lre = "\xE2\x80\xAA"; // U+202A LEFT-TO-RIGHT EMBEDDING + $rle = "\xE2\x80\xAB"; // U+202B RIGHT-TO-LEFT EMBEDDING + $pdf = "\xE2\x80\xAC"; // U+202C POP DIRECTIONAL FORMATTING + $lang = $this->getLang(); + $this->assertEquals( + '123', + $lang->embedBidi( '123' ), + 'embedBidi with neutral argument' + ); + $this->assertEquals( + $lre . 'Ben_(WMF)' . $pdf, + $lang->embedBidi( 'Ben_(WMF)' ), + 'embedBidi with LTR argument' + ); + $this->assertEquals( + $rle . 'יהודי (מנוחין)' . $pdf, + $lang->embedBidi( 'יהודי (מנוחין)' ), + 'embedBidi with RTL argument' + ); + } + + /** + * @covers Language::translateBlockExpiry() + * @dataProvider provideTranslateBlockExpiry + */ + public function testTranslateBlockExpiry( $expectedData, $str, $now, $desc ) { + $lang = $this->getLang(); + if ( is_array( $expectedData ) ) { + list( $func, $arg ) = $expectedData; + $expected = $lang->$func( $arg ); + } else { + $expected = $expectedData; + } + $this->assertEquals( $expected, $lang->translateBlockExpiry( $str, null, $now ), $desc ); + } + + public static function provideTranslateBlockExpiry() { + return [ + [ '2 hours', '2 hours', 0, 'simple data from ipboptions' ], + [ 'indefinite', 'infinite', 0, 'infinite from ipboptions' ], + [ 'indefinite', 'infinity', 0, 'alternative infinite from ipboptions' ], + [ 'indefinite', 'indefinite', 0, 'another alternative infinite from ipboptions' ], + [ [ 'formatDuration', 1023 * 60 * 60 ], '1023 hours', 0, 'relative' ], + [ [ 'formatDuration', -1023 ], '-1023 seconds', 0, 'negative relative' ], + [ + [ 'formatDuration', 1023 * 60 * 60 ], + '1023 hours', + wfTimestamp( TS_UNIX, '19910203040506' ), + 'relative with initial timestamp' + ], + [ [ 'formatDuration', 0 ], 'now', 0, 'now' ], + [ + [ 'timeanddate', '20120102070000' ], + '2012-1-1 7:00 +1 day', + 0, + 'mixed, handled as absolute' + ], + [ [ 'timeanddate', '19910203040506' ], '1991-2-3 4:05:06', 0, 'absolute' ], + [ [ 'timeanddate', '19700101000000' ], '1970-1-1 0:00:00', 0, 'absolute at epoch' ], + [ [ 'timeanddate', '19691231235959' ], '1969-12-31 23:59:59', 0, 'time before epoch' ], + [ + [ 'timeanddate', '19910910000000' ], + '10 september', + wfTimestamp( TS_UNIX, '19910203040506' ), + 'partial' + ], + [ 'dummy', 'dummy', 0, 'return garbage as is' ], + ]; + } + + /** + * @dataProvider parseFormattedNumberProvider + */ + public function testParseFormattedNumber( $langCode, $number ) { + $lang = Language::factory( $langCode ); + + $localisedNum = $lang->formatNum( $number ); + $normalisedNum = $lang->parseFormattedNumber( $localisedNum ); + + $this->assertEquals( $number, $normalisedNum ); + } + + public function parseFormattedNumberProvider() { + return [ + [ 'de', 377.01 ], + [ 'fa', 334 ], + [ 'fa', 382.772 ], + [ 'ar', 1844 ], + [ 'lzh', 3731 ], + [ 'zh-classical', 7432 ] + ]; + } + + /** + * @covers Language::commafy() + * @dataProvider provideCommafyData + */ + public function testCommafy( $number, $numbersWithCommas ) { + $this->assertEquals( + $numbersWithCommas, + $this->getLang()->commafy( $number ), + "commafy('$number')" + ); + } + + public static function provideCommafyData() { + return [ + [ -1, '-1' ], + [ 10, '10' ], + [ 100, '100' ], + [ 1000, '1,000' ], + [ 10000, '10,000' ], + [ 100000, '100,000' ], + [ 1000000, '1,000,000' ], + [ -1.0001, '-1.0001' ], + [ 1.0001, '1.0001' ], + [ 10.0001, '10.0001' ], + [ 100.0001, '100.0001' ], + [ 1000.0001, '1,000.0001' ], + [ 10000.0001, '10,000.0001' ], + [ 100000.0001, '100,000.0001' ], + [ 1000000.0001, '1,000,000.0001' ], + [ '200000000000000000000', '200,000,000,000,000,000,000' ], + [ '-200000000000000000000', '-200,000,000,000,000,000,000' ], + ]; + } + + /** + * @covers Language::listToText + */ + public function testListToText() { + $lang = $this->getLang(); + $and = $lang->getMessageFromDB( 'and' ); + $s = $lang->getMessageFromDB( 'word-separator' ); + $c = $lang->getMessageFromDB( 'comma-separator' ); + + $this->assertEquals( '', $lang->listToText( [] ) ); + $this->assertEquals( 'a', $lang->listToText( [ 'a' ] ) ); + $this->assertEquals( "a{$and}{$s}b", $lang->listToText( [ 'a', 'b' ] ) ); + $this->assertEquals( "a{$c}b{$and}{$s}c", $lang->listToText( [ 'a', 'b', 'c' ] ) ); + $this->assertEquals( "a{$c}b{$c}c{$and}{$s}d", $lang->listToText( [ 'a', 'b', 'c', 'd' ] ) ); + } + + /** + * @dataProvider provideIsSupportedLanguage + * @covers Language::isSupportedLanguage + */ + public function testIsSupportedLanguage( $code, $expected, $comment ) { + $this->assertEquals( $expected, Language::isSupportedLanguage( $code ), $comment ); + } + + public static function provideIsSupportedLanguage() { + return [ + [ 'en', true, 'is supported language' ], + [ 'fi', true, 'is supported language' ], + [ 'bunny', false, 'is not supported language' ], + [ 'FI', false, 'is not supported language, input should be in lower case' ], + ]; + } + + /** + * @dataProvider provideGetParentLanguage + * @covers Language::getParentLanguage + */ + public function testGetParentLanguage( $code, $expected, $comment ) { + $lang = Language::factory( $code ); + if ( is_null( $expected ) ) { + $this->assertNull( $lang->getParentLanguage(), $comment ); + } else { + $this->assertEquals( $expected, $lang->getParentLanguage()->getCode(), $comment ); + } + } + + public static function provideGetParentLanguage() { + return [ + [ 'zh-cn', 'zh', 'zh is the parent language of zh-cn' ], + [ 'zh', 'zh', 'zh is defined as the parent language of zh, ' + . 'because zh converter can convert zh-cn to zh' ], + [ 'zh-invalid', null, 'do not be fooled by arbitrarily composed language codes' ], + [ 'de-formal', null, 'de does not have converter' ], + [ 'de', null, 'de does not have converter' ], + ]; + } + + /** + * @dataProvider provideGetNamespaceAliases + * @covers Language::getNamespaceAliases + */ + public function testGetNamespaceAliases( $languageCode, $subset ) { + $language = Language::factory( $languageCode ); + $aliases = $language->getNamespaceAliases(); + foreach ( $subset as $alias => $nsId ) { + $this->assertEquals( $nsId, $aliases[$alias] ); + } + } + + public static function provideGetNamespaceAliases() { + // TODO: Add tests for NS_PROJECT_TALK and GenderNamespaces + return [ + [ + 'zh', + [ + '文件' => NS_FILE, + '檔案' => NS_FILE, + ], + ], + ]; + } + + public function testEquals() { + $en1 = new Language(); + $en1->setCode( 'en' ); + + $en2 = Language::factory( 'en' ); + $en2->setCode( 'en' ); + + $this->assertTrue( $en1->equals( $en2 ), 'en equals en' ); + + $fr = Language::factory( 'fr' ); + $this->assertFalse( $en1->equals( $fr ), 'en not equals fr' ); + } +}