LanguageTest.php 46 KB

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