I18nExtractCommandTest.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * CakePHP : Rapid Development Framework (https://cakephp.org)
  5. * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  6. *
  7. * Licensed under The MIT License
  8. * For full copyright and license information, please see the LICENSE.txt
  9. * Redistributions of files must retain the above copyright notice.
  10. *
  11. * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  12. * @link https://cakephp.org CakePHP Project
  13. * @since 1.2.0
  14. * @license https://opensource.org/licenses/mit-license.php MIT License
  15. */
  16. namespace Cake\Test\TestCase\Command;
  17. use Cake\Console\TestSuite\ConsoleIntegrationTestTrait;
  18. use Cake\Core\Configure;
  19. use Cake\TestSuite\TestCase;
  20. use Cake\Utility\Filesystem;
  21. /**
  22. * I18nExtractCommandTest
  23. */
  24. class I18nExtractCommandTest extends TestCase
  25. {
  26. use ConsoleIntegrationTestTrait;
  27. /**
  28. * @var string
  29. */
  30. protected $path;
  31. /**
  32. * setUp method
  33. */
  34. public function setUp(): void
  35. {
  36. parent::setUp();
  37. $this->setAppNamespace();
  38. $this->path = TMP . 'tests/extract_task_test';
  39. $fs = new Filesystem();
  40. $fs->deleteDir($this->path);
  41. $fs->mkdir($this->path . DS . 'locale');
  42. }
  43. /**
  44. * tearDown method
  45. */
  46. public function tearDown(): void
  47. {
  48. parent::tearDown();
  49. $fs = new Filesystem();
  50. $fs->deleteDir($this->path);
  51. $this->clearPlugins();
  52. }
  53. /**
  54. * testExecute method
  55. */
  56. public function testExecute(): void
  57. {
  58. $this->exec(
  59. 'i18n extract ' .
  60. '--merge=no ' .
  61. '--extract-core=no ' .
  62. '--paths=' . TEST_APP . 'templates' . DS . 'Pages ' .
  63. '--output=' . $this->path . DS
  64. );
  65. $this->assertExitSuccess();
  66. $this->assertFileExists($this->path . DS . 'default.pot');
  67. $result = file_get_contents($this->path . DS . 'default.pot');
  68. $this->assertFileDoesNotExist($this->path . DS . 'cake.pot');
  69. // The additional "./tests/test_app" is just due to the wonky folder structure of the test app.
  70. // In a regular app the path would start with "./templates".
  71. $pattern = '@\#: \./tests/test_app/templates/Pages/extract\.php:\d+\n';
  72. $pattern .= '\#: \./tests/test_app/templates/Pages/extract\.php:\d+\n';
  73. $pattern .= 'msgid "You have %d new message."\nmsgid_plural "You have %d new messages."@';
  74. $this->assertMatchesRegularExpression($pattern, $result);
  75. $pattern = '/msgid "You have %d new message."\nmsgstr ""/';
  76. $this->assertDoesNotMatchRegularExpression($pattern, $result, 'No duplicate msgid');
  77. $pattern = '@\#: \./tests/test_app/templates/Pages/extract\.php:\d+\n';
  78. $pattern .= 'msgid "You deleted %d message."\nmsgid_plural "You deleted %d messages."@';
  79. $this->assertMatchesRegularExpression($pattern, $result);
  80. $pattern = '@\#: \./tests/test_app/templates/Pages/extract\.php:\d+\nmsgid "';
  81. $pattern .= 'Hot features!';
  82. $pattern .= '\\\n - No Configuration: Set-up the database and let the magic begin';
  83. $pattern .= '\\\n - Extremely Simple: Just look at the name...It\'s Cake';
  84. $pattern .= '\\\n - Active, Friendly Community: Join us #cakephp on IRC. We\'d love to help you get started';
  85. $pattern .= '"\nmsgstr ""@';
  86. $this->assertMatchesRegularExpression($pattern, $result);
  87. $this->assertStringContainsString('msgid "double \\"quoted\\""', $result, 'Strings with quotes not handled correctly');
  88. $this->assertStringContainsString("msgid \"single 'quoted'\"", $result, 'Strings with quotes not handled correctly');
  89. $pattern = '@\#: \./tests/test_app/templates/Pages/extract\.php:\d+\n';
  90. $pattern .= 'msgctxt "mail"\n';
  91. $pattern .= 'msgid "letter"@';
  92. $this->assertMatchesRegularExpression($pattern, $result);
  93. $pattern = '@\#: \./tests/test_app/templates/Pages/extract\.php:\d+\n';
  94. $pattern .= 'msgctxt "alphabet"\n';
  95. $pattern .= 'msgid "letter"@';
  96. $this->assertMatchesRegularExpression($pattern, $result);
  97. // extract.php - reading the domain.pot
  98. $result = file_get_contents($this->path . DS . 'domain.pot');
  99. $pattern = '/msgid "You have %d new message."\nmsgid_plural "You have %d new messages."/';
  100. $this->assertDoesNotMatchRegularExpression($pattern, $result);
  101. $pattern = '/msgid "You deleted %d message."\nmsgid_plural "You deleted %d messages."/';
  102. $this->assertDoesNotMatchRegularExpression($pattern, $result);
  103. $pattern = '/msgid "You have %d new message \(domain\)."\nmsgid_plural "You have %d new messages \(domain\)."/';
  104. $this->assertMatchesRegularExpression($pattern, $result);
  105. $pattern = '/msgid "You deleted %d message \(domain\)."\nmsgid_plural "You deleted %d messages \(domain\)."/';
  106. $this->assertMatchesRegularExpression($pattern, $result);
  107. }
  108. /**
  109. * testExecute with no paths
  110. */
  111. public function testExecuteNoPathOption(): void
  112. {
  113. $this->exec(
  114. 'i18n extract ' .
  115. '--merge=no ' .
  116. '--extract-core=no ' .
  117. '--output=' . $this->path . DS,
  118. [
  119. TEST_APP . 'templates' . DS,
  120. 'D',
  121. ]
  122. );
  123. $this->assertExitSuccess();
  124. $this->assertFileExists($this->path . DS . 'default.pot');
  125. }
  126. /**
  127. * testExecute with merging on method
  128. */
  129. public function testExecuteMerge(): void
  130. {
  131. $this->exec(
  132. 'i18n extract ' .
  133. '--merge=yes ' .
  134. '--extract-core=no ' .
  135. '--paths=' . TEST_APP . 'templates' . DS . 'Pages ' .
  136. '--output=' . $this->path . DS
  137. );
  138. $this->assertExitSuccess();
  139. $this->assertFileExists($this->path . DS . 'default.pot');
  140. $this->assertFileDoesNotExist($this->path . DS . 'cake.pot');
  141. $this->assertFileDoesNotExist($this->path . DS . 'domain.pot');
  142. }
  143. /**
  144. * test exclusions
  145. */
  146. public function testExtractWithExclude(): void
  147. {
  148. $this->exec(
  149. 'i18n extract ' .
  150. '--extract-core=no ' .
  151. '--exclude=Pages,Layout ' .
  152. '--paths=' . TEST_APP . 'templates' . DS . ' ' .
  153. '--output=' . $this->path . DS
  154. );
  155. $this->assertExitSuccess();
  156. $this->assertFileExists($this->path . DS . 'default.pot');
  157. $result = file_get_contents($this->path . DS . 'default.pot');
  158. $pattern = '/\#: .*extract\.php:\d+\n/';
  159. $this->assertDoesNotMatchRegularExpression($pattern, $result);
  160. $pattern = '/\#: .*default\.php:\d+\n/';
  161. $this->assertDoesNotMatchRegularExpression($pattern, $result);
  162. }
  163. /**
  164. * testExtractWithoutLocations method
  165. */
  166. public function testExtractWithoutLocations(): void
  167. {
  168. $this->exec(
  169. 'i18n extract ' .
  170. '--extract-core=no ' .
  171. '--no-location=true ' .
  172. '--exclude=Pages,Layout ' .
  173. '--paths=' . TEST_APP . 'templates' . DS . ' ' .
  174. '--output=' . $this->path . DS
  175. );
  176. $this->assertExitSuccess();
  177. $this->assertFileExists($this->path . DS . 'default.pot');
  178. $result = file_get_contents($this->path . DS . 'default.pot');
  179. $pattern = '/\n\#: .*\n/';
  180. $this->assertDoesNotMatchRegularExpression($pattern, $result);
  181. }
  182. /**
  183. * test extract can read more than one path.
  184. */
  185. public function testExtractMultiplePaths(): void
  186. {
  187. $this->exec(
  188. 'i18n extract ' .
  189. '--extract-core=no ' .
  190. '--exclude=Pages,Layout ' .
  191. '--paths=' . TEST_APP . 'templates/Pages,' .
  192. TEST_APP . 'templates/Posts ' .
  193. '--output=' . $this->path . DS
  194. );
  195. $this->assertExitSuccess();
  196. $result = file_get_contents($this->path . DS . 'default.pot');
  197. $pattern = '/msgid "Add User"/';
  198. $this->assertMatchesRegularExpression($pattern, $result);
  199. }
  200. /**
  201. * Tests that it is possible to exclude plugin paths by enabling the param option for the ExtractTask
  202. */
  203. public function testExtractExcludePlugins(): void
  204. {
  205. static::setAppNamespace();
  206. $this->exec(
  207. 'i18n extract ' .
  208. '--extract-core=no ' .
  209. '--exclude-plugins=true ' .
  210. '--paths=' . TEST_APP . 'TestApp/ ' .
  211. '--output=' . $this->path . DS
  212. );
  213. $this->assertExitSuccess();
  214. $result = file_get_contents($this->path . DS . 'default.pot');
  215. $this->assertDoesNotMatchRegularExpression('#TestPlugin#', $result);
  216. }
  217. /**
  218. * Test that is possible to extract messages from a single plugin
  219. */
  220. public function testExtractPlugin(): void
  221. {
  222. Configure::write('Plugins.autoload', ['TestPlugin']);
  223. $this->exec(
  224. 'i18n extract ' .
  225. '--extract-core=no ' .
  226. '--plugin=TestPlugin ' .
  227. '--output=' . $this->path . DS
  228. );
  229. $this->assertExitSuccess();
  230. $result = file_get_contents($this->path . DS . 'default.pot');
  231. $this->assertDoesNotMatchRegularExpression('#Pages#', $result);
  232. $this->assertMatchesRegularExpression('/translate\.php:\d+/', $result);
  233. $this->assertStringContainsString('This is a translatable string', $result);
  234. }
  235. /**
  236. * Test that is possible to extract messages from a vendor prefixed plugin.
  237. */
  238. public function testExtractVendorPrefixedPlugin(): void
  239. {
  240. $this->pluginsToLoad(['Company/TestPluginThree']);
  241. $this->exec(
  242. 'i18n extract ' .
  243. '--extract-core=no ' .
  244. '--plugin=Company/TestPluginThree ' .
  245. '--output=' . $this->path . DS
  246. );
  247. $this->assertExitSuccess();
  248. $result = file_get_contents($this->path . DS . 'company_test_plugin_three.pot');
  249. $this->assertDoesNotMatchRegularExpression('#Pages#', $result);
  250. $this->assertMatchesRegularExpression('/default\.php:\d+/', $result);
  251. $this->assertStringContainsString('A vendor message', $result);
  252. }
  253. /**
  254. * Test that the extract shell overwrites existing files with the overwrite parameter
  255. */
  256. public function testExtractOverwrite(): void
  257. {
  258. file_put_contents($this->path . DS . 'default.pot', 'will be overwritten');
  259. $this->assertFileExists($this->path . DS . 'default.pot');
  260. $original = file_get_contents($this->path . DS . 'default.pot');
  261. $this->exec(
  262. 'i18n extract ' .
  263. '--extract-core=no ' .
  264. '--overwrite ' .
  265. '--paths=' . TEST_APP . 'TestApp/ ' .
  266. '--output=' . $this->path . DS
  267. );
  268. $this->assertExitSuccess();
  269. $result = file_get_contents($this->path . DS . 'default.pot');
  270. $this->assertNotEquals($original, $result);
  271. }
  272. /**
  273. * Test that the extract shell scans the core libs
  274. */
  275. public function testExtractCore(): void
  276. {
  277. $this->exec(
  278. 'i18n extract ' .
  279. '--extract-core=yes ' .
  280. '--paths=' . TEST_APP . 'TestApp/ ' .
  281. '--output=' . $this->path . DS
  282. );
  283. $this->assertExitSuccess();
  284. $this->assertFileExists($this->path . DS . 'cake.pot');
  285. $result = file_get_contents($this->path . DS . 'cake.pot');
  286. $pattern = '/#: Console\/Templates\//';
  287. $this->assertDoesNotMatchRegularExpression($pattern, $result);
  288. $pattern = '/#: Test\//';
  289. $this->assertDoesNotMatchRegularExpression($pattern, $result);
  290. }
  291. /**
  292. * Test when marker-error option is set
  293. * When marker-error is unset, it's already test
  294. * with other functions like testExecute that not detects error because err never called
  295. */
  296. public function testMarkerErrorSets(): void
  297. {
  298. $this->exec(
  299. 'i18n extract ' .
  300. '--marker-error ' .
  301. '--merge=no ' .
  302. '--extract-core=no ' .
  303. '--paths=' . TEST_APP . 'templates/Pages ' .
  304. '--output=' . $this->path . DS
  305. );
  306. $this->assertExitSuccess();
  307. $this->assertErrorContains('Invalid marker content in');
  308. $this->assertErrorContains('extract.php');
  309. }
  310. /**
  311. * test relative-paths option
  312. */
  313. public function testExtractWithRelativePaths(): void
  314. {
  315. $this->exec(
  316. 'i18n extract ' .
  317. '--extract-core=no ' .
  318. '--paths=' . TEST_APP . 'templates ' .
  319. '--output=' . $this->path . DS
  320. );
  321. $this->assertExitSuccess();
  322. $this->assertFileExists($this->path . DS . 'default.pot');
  323. $result = file_get_contents($this->path . DS . 'default.pot');
  324. $expected = '#: ./tests/test_app/templates/Pages/extract.php:';
  325. $this->assertStringContainsString($expected, $result);
  326. }
  327. /**
  328. * test invalid path options
  329. */
  330. public function testExtractWithInvalidPaths(): void
  331. {
  332. $this->exec(
  333. 'i18n extract ' .
  334. '--extract-core=no ' .
  335. '--paths=' . TEST_APP . 'templates,' . TEST_APP . 'unknown ' .
  336. '--output=' . $this->path . DS
  337. );
  338. $this->assertExitSuccess();
  339. $this->assertFileExists($this->path . DS . 'default.pot');
  340. $result = file_get_contents($this->path . DS . 'default.pot');
  341. $expected = '#: ./tests/test_app/templates/Pages/extract.php:';
  342. $this->assertStringContainsString($expected, $result);
  343. }
  344. /**
  345. * Test with associative arrays in App.path.locales and App.path.templates.
  346. */
  347. public function testExtractWithAssociativePaths(): void
  348. {
  349. Configure::write('App.paths', [
  350. 'plugins' => ['customKey' => TEST_APP . 'Plugin' . DS],
  351. 'templates' => ['customKey' => TEST_APP . 'templates' . DS],
  352. 'locales' => ['customKey' => TEST_APP . 'resources' . DS . 'locales' . DS],
  353. ]);
  354. $this->exec(
  355. 'i18n extract ' .
  356. '--merge=no ' .
  357. '--extract-core=no ',
  358. [
  359. // Sending two empty inputs so \Cake\Command\I18nExtractCommand::_getPaths()
  360. // loops through all paths
  361. '',
  362. '',
  363. 'D',
  364. $this->path . DS,
  365. ]
  366. );
  367. $this->assertExitSuccess();
  368. $this->assertFileExists($this->path . DS . 'default.pot');
  369. $result = file_get_contents($this->path . DS . 'default.pot');
  370. $expected = '#: ./tests/test_app/templates/Pages/extract.php:';
  371. $this->assertStringContainsString($expected, $result);
  372. }
  373. }