CellTest.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * CakePHP(tm) : 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(tm) Project
  13. * @since 3.0.0
  14. * @license https://opensource.org/licenses/mit-license.php MIT License
  15. */
  16. namespace Cake\Test\TestCase\View;
  17. use BadMethodCallException;
  18. use Cake\Cache\Cache;
  19. use Cake\Http\Response;
  20. use Cake\Http\ServerRequest;
  21. use Cake\TestSuite\TestCase;
  22. use Cake\View\Cell;
  23. use Cake\View\Exception\MissingCellException;
  24. use Cake\View\Exception\MissingCellTemplateException;
  25. use Cake\View\Exception\MissingTemplateException;
  26. use Cake\View\View;
  27. use TestApp\Controller\CellTraitTestController;
  28. use TestApp\View\CustomJsonView;
  29. /**
  30. * CellTest class.
  31. *
  32. * For testing both View\Cell & Utility\CellTrait
  33. */
  34. class CellTest extends TestCase
  35. {
  36. /**
  37. * @var \Cake\View\View
  38. */
  39. protected $View;
  40. /**
  41. * setUp method
  42. */
  43. public function setUp(): void
  44. {
  45. parent::setUp();
  46. static::setAppNamespace();
  47. $this->loadPlugins(['TestPlugin', 'TestTheme']);
  48. $request = new ServerRequest();
  49. $response = new Response();
  50. $this->View = new View($request, $response);
  51. }
  52. /**
  53. * tearDown method
  54. */
  55. public function tearDown(): void
  56. {
  57. parent::tearDown();
  58. $this->clearPlugins();
  59. unset($this->View);
  60. }
  61. /**
  62. * Tests basic cell rendering.
  63. */
  64. public function testCellRender(): void
  65. {
  66. $cell = $this->View->cell('Articles::teaserList');
  67. $render = "{$cell}";
  68. $this->assertSame('teaser_list', $cell->viewBuilder()->getTemplate());
  69. $this->assertStringContainsString('<h2>Lorem ipsum</h2>', $render);
  70. $this->assertStringContainsString('<h2>Usectetur adipiscing eli</h2>', $render);
  71. $this->assertStringContainsString('<h2>Topis semper blandit eu non</h2>', $render);
  72. $this->assertStringContainsString('<h2>Suspendisse gravida neque</h2>', $render);
  73. $cell = $this->View->cell('Cello');
  74. $this->assertInstanceOf('TestApp\View\Cell\CelloCell', $cell);
  75. $this->assertSame("Cellos\n", $cell->render());
  76. }
  77. /**
  78. * Tests debug output.
  79. */
  80. public function testDebugInfo(): void
  81. {
  82. $cell = $this->View->cell('Articles::teaserList');
  83. $data = $cell->__debugInfo();
  84. $this->assertArrayHasKey('request', $data);
  85. $this->assertArrayHasKey('response', $data);
  86. $this->assertSame('teaserList', $data['action']);
  87. $this->assertEquals([], $data['args']);
  88. }
  89. /**
  90. * Test __toString() hitting an error when rendering views.
  91. */
  92. public function testCellImplictRenderWithError(): void
  93. {
  94. $capture = function ($errno, $msg): void {
  95. restore_error_handler();
  96. $this->assertSame(E_USER_WARNING, $errno);
  97. $this->assertStringContainsString('Could not render cell - Cell template file', $msg);
  98. };
  99. set_error_handler($capture);
  100. $cell = $this->View->cell('Articles::teaserList');
  101. $cell->viewBuilder()->setTemplate('nope');
  102. $result = "{$cell}";
  103. }
  104. /**
  105. * Tests that we are able pass multiple arguments to cell methods.
  106. *
  107. * This test sets its own error handler, as PHPUnit won't convert
  108. * errors into exceptions when the caller is a __toString() method.
  109. */
  110. public function testCellWithArguments(): void
  111. {
  112. $cell = $this->View->cell('Articles::doEcho', ['msg1' => 'dummy', 'msg2' => ' message']);
  113. $render = "{$cell}";
  114. $this->assertStringContainsString('dummy message', $render);
  115. }
  116. /**
  117. * Tests that cell runs default action when none is provided.
  118. */
  119. public function testDefaultCellAction(): void
  120. {
  121. $appCell = $this->View->cell('Articles');
  122. $this->assertSame('display', $appCell->viewBuilder()->getTemplate());
  123. $this->assertStringContainsString('dummy', "{$appCell}");
  124. $pluginCell = $this->View->cell('TestPlugin.Dummy');
  125. $this->assertStringContainsString('dummy', "{$pluginCell}");
  126. $this->assertSame('display', $pluginCell->viewBuilder()->getTemplate());
  127. }
  128. /**
  129. * Tests that cell action setting the templatePath
  130. */
  131. public function testSettingCellTemplatePathFromAction(): void
  132. {
  133. $appCell = $this->View->cell('Articles::customTemplatePath');
  134. $this->assertStringContainsString('Articles subdir custom_template_path template', "{$appCell}");
  135. $this->assertSame('custom_template_path', $appCell->viewBuilder()->getTemplate());
  136. $this->assertSame(Cell::TEMPLATE_FOLDER . '/Articles/Subdir', $appCell->viewBuilder()->getTemplatePath());
  137. }
  138. /**
  139. * Tests that cell action setting the template using the ViewBuilder renders the correct template
  140. */
  141. public function testSettingCellTemplateFromActionViewBuilder(): void
  142. {
  143. $appCell = $this->View->cell('Articles::customTemplateViewBuilder');
  144. $this->assertStringContainsString('This is the alternate template', "{$appCell}");
  145. $this->assertSame('alternate_teaser_list', $appCell->viewBuilder()->getTemplate());
  146. }
  147. /**
  148. * Tests manual render() invocation.
  149. */
  150. public function testCellManualRender(): void
  151. {
  152. /** @var \TestApp\View\Cell\ArticlesCell $cell */
  153. $cell = $this->View->cell('Articles::doEcho', ['msg1' => 'dummy', 'msg2' => ' message']);
  154. $this->assertStringContainsString('dummy message', $cell->render());
  155. $cell->teaserList();
  156. $this->assertStringContainsString('<h2>Lorem ipsum</h2>', $cell->render('teaser_list'));
  157. }
  158. /**
  159. * Tests manual render() invocation with error
  160. */
  161. public function testCellManualRenderError(): void
  162. {
  163. $cell = $this->View->cell('Articles');
  164. $e = null;
  165. try {
  166. $cell->render('fooBar');
  167. } catch (MissingCellTemplateException $e) {
  168. }
  169. $this->assertNotNull($e);
  170. $message = $e->getMessage();
  171. $this->assertStringContainsString(
  172. str_replace('/', DS, 'Cell template file `cell/Articles/foo_bar.php` could not be found.'),
  173. $message
  174. );
  175. $this->assertStringContainsString('The following paths', $message);
  176. $this->assertStringContainsString(ROOT . DS . 'templates', $message);
  177. $this->assertInstanceOf(MissingTemplateException::class, $e->getPrevious());
  178. }
  179. /**
  180. * Test rendering a cell with a theme.
  181. */
  182. public function testCellRenderThemed(): void
  183. {
  184. $this->View->setTheme('TestTheme');
  185. $cell = $this->View->cell('Articles', ['msg' => 'hello world!']);
  186. $this->assertEquals($this->View->getTheme(), $cell->viewBuilder()->getTheme());
  187. $this->assertStringContainsString('Themed cell content.', $cell->render());
  188. }
  189. /**
  190. * Test that a cell can render a plugin view.
  191. */
  192. public function testCellRenderPluginTemplate(): void
  193. {
  194. $cell = $this->View->cell('Articles');
  195. $this->assertStringContainsString(
  196. 'TestPlugin Articles/display',
  197. $cell->render('TestPlugin.display')
  198. );
  199. $cell = $this->View->cell('Articles');
  200. $cell->viewBuilder()->setPlugin('TestPlugin');
  201. $this->assertStringContainsString(
  202. 'TestPlugin Articles/display',
  203. $cell->render('display')
  204. );
  205. }
  206. /**
  207. * Tests that using plugin's cells works.
  208. */
  209. public function testPluginCell(): void
  210. {
  211. $cell = $this->View->cell('TestPlugin.Dummy::echoThis', ['msg' => 'hello world!']);
  212. $this->assertStringContainsString('hello world!', "{$cell}");
  213. }
  214. /**
  215. * Tests that using namespaced cells works.
  216. */
  217. public function testNamespacedCell(): void
  218. {
  219. $cell = $this->View->cell('Admin/Menu');
  220. $this->assertStringContainsString('Admin Menu Cell', $cell->render());
  221. }
  222. /**
  223. * Tests that using namespaced cells in plugins works
  224. */
  225. public function testPluginNamespacedCell(): void
  226. {
  227. $cell = $this->View->cell('TestPlugin.Admin/Menu');
  228. $this->assertStringContainsString('Test Plugin Admin Menu Cell', $cell->render());
  229. }
  230. /**
  231. * Test that plugin cells can render other view templates.
  232. */
  233. public function testPluginCellAlternateTemplate(): void
  234. {
  235. $cell = $this->View->cell('TestPlugin.Dummy::echoThis', ['msg' => 'hello world!']);
  236. $cell->viewBuilder()->setTemplate('../../element/translate');
  237. $this->assertStringContainsString('This is a translatable string', "{$cell}");
  238. }
  239. /**
  240. * Test that plugin cells can render other view templates.
  241. */
  242. public function testPluginCellAlternateTemplateRenderParam(): void
  243. {
  244. $cell = $this->View->cell('TestPlugin.Dummy::echoThis', ['msg' => 'hello world!']);
  245. $result = $cell->render('../../element/translate');
  246. $this->assertStringContainsString('This is a translatable string', $result);
  247. }
  248. /**
  249. * Tests that using an nonexistent cell throws an exception.
  250. */
  251. public function testNonExistentCell(): void
  252. {
  253. $this->expectException(MissingCellException::class);
  254. $cell = $this->View->cell('TestPlugin.Void::echoThis', ['arg1' => 'v1']);
  255. $cell = $this->View->cell('Void::echoThis', ['arg1' => 'v1', 'arg2' => 'v2']);
  256. }
  257. /**
  258. * Tests missing method errors
  259. */
  260. public function testCellMissingMethod(): void
  261. {
  262. $this->expectException(BadMethodCallException::class);
  263. $this->expectExceptionMessage('Class TestApp\View\Cell\ArticlesCell does not have a "nope" method.');
  264. $cell = $this->View->cell('Articles::nope');
  265. $cell->render();
  266. }
  267. /**
  268. * Test that cell options are passed on.
  269. */
  270. public function testCellOptions(): void
  271. {
  272. $cell = $this->View->cell('Articles', [], ['limit' => 10, 'nope' => 'nope']);
  273. $this->assertSame(10, $cell->limit);
  274. $this->assertObjectNotHasAttribute('nope', $cell, 'Not a valid option');
  275. }
  276. /**
  277. * Test that cells get the helper configuration from the view that created them.
  278. */
  279. public function testCellInheritsHelperConfig(): void
  280. {
  281. $request = new ServerRequest();
  282. $response = new Response();
  283. $helpers = ['Url', 'Form', 'Banana'];
  284. $view = new View($request, $response, null, ['helpers' => $helpers]);
  285. $cell = $view->cell('Articles');
  286. $this->assertSame($helpers, $cell->viewBuilder()->getHelpers());
  287. }
  288. /**
  289. * Test that cells the view class name of a custom view passed on.
  290. */
  291. public function testCellInheritsCustomViewClass(): void
  292. {
  293. $request = new ServerRequest();
  294. $response = new Response();
  295. $view = new CustomJsonView($request, $response);
  296. $view->setTheme('Pretty');
  297. $cell = $view->cell('Articles');
  298. $this->assertSame('TestApp\View\CustomJsonView', $cell->viewBuilder()->getClassName());
  299. $this->assertSame('Pretty', $cell->viewBuilder()->getTheme());
  300. }
  301. /**
  302. * Test that cells the view class name of a controller passed on.
  303. */
  304. public function testCellInheritsController(): void
  305. {
  306. $request = new ServerRequest();
  307. $response = new Response();
  308. $controller = new CellTraitTestController($request, $response);
  309. $controller->viewBuilder()->setTheme('Pretty');
  310. $controller->viewBuilder()->setClassName('Json');
  311. $cell = $controller->cell('Articles');
  312. $this->assertSame('Json', $cell->viewBuilder()->getClassName());
  313. $this->assertSame('Pretty', $cell->viewBuilder()->getTheme());
  314. }
  315. /**
  316. * Test cached render.
  317. */
  318. public function testCachedRenderSimple(): void
  319. {
  320. Cache::setConfig('default', ['className' => 'Array']);
  321. $cell = $this->View->cell('Articles', [], ['cache' => true]);
  322. $result = $cell->render();
  323. $expected = "dummy\n";
  324. $this->assertSame($expected, $result);
  325. $result = Cache::read('cell_test_app_view_cell_articles_cell_display_default', 'default');
  326. $this->assertSame($expected, $result);
  327. Cache::drop('default');
  328. }
  329. /**
  330. * Test read cached cell.
  331. */
  332. public function testReadCachedCell(): void
  333. {
  334. Cache::setConfig('default', ['className' => 'Array']);
  335. Cache::write('cell_test_app_view_cell_articles_cell_display_default', 'from cache');
  336. $cell = $this->View->cell('Articles', [], ['cache' => true]);
  337. $result = $cell->render();
  338. $this->assertSame('from cache', $result);
  339. Cache::drop('default');
  340. }
  341. /**
  342. * Test cached render array config
  343. */
  344. public function testCachedRenderArrayConfig(): void
  345. {
  346. Cache::setConfig('cell', ['className' => 'Array']);
  347. Cache::write('my_key', 'from cache', 'cell');
  348. $cell = $this->View->cell('Articles', [], [
  349. 'cache' => ['key' => 'my_key', 'config' => 'cell'],
  350. ]);
  351. $result = $cell->render();
  352. $this->assertSame('from cache', $result);
  353. Cache::drop('cell');
  354. }
  355. /**
  356. * Test cached render when using an action changing the template used
  357. */
  358. public function testCachedRenderSimpleCustomTemplate(): void
  359. {
  360. Cache::setConfig('default', ['className' => 'Array']);
  361. $cell = $this->View->cell('Articles::customTemplateViewBuilder', [], ['cache' => true]);
  362. $result = $cell->render();
  363. $expected = 'This is the alternate template';
  364. $this->assertStringContainsString($expected, $result);
  365. $result = Cache::read('cell_test_app_view_cell_articles_cell_customTemplateViewBuilder_default');
  366. $this->assertStringContainsString($expected, $result);
  367. Cache::drop('default');
  368. }
  369. /**
  370. * Test that when the cell cache is enabled, the cell action is only invoke the first
  371. * time the cell is rendered
  372. */
  373. public function testCachedRenderSimpleCustomTemplateViewBuilder(): void
  374. {
  375. Cache::setConfig('default', ['className' => 'Array']);
  376. $cell = $this->View->cell('Articles::customTemplateViewBuilder', [], ['cache' => ['key' => 'celltest']]);
  377. $result = $cell->render();
  378. $this->assertSame(1, $cell->counter);
  379. $cell->render();
  380. $this->assertSame(1, $cell->counter);
  381. $this->assertStringContainsString('This is the alternate template', $result);
  382. Cache::drop('default');
  383. }
  384. /**
  385. * Test that when the cell cache is enabled, the cell action is only invoke the first
  386. * time the cell is rendered
  387. */
  388. public function testACachedViewCellReRendersWhenGivenADifferentTemplate(): void
  389. {
  390. Cache::setConfig('default', ['className' => 'Array']);
  391. $cell = $this->View->cell('Articles::customTemplateViewBuilder', [], ['cache' => true]);
  392. $result = $cell->render('alternate_teaser_list');
  393. $result2 = $cell->render('not_the_alternate_teaser_list');
  394. $this->assertStringContainsString('This is the alternate template', $result);
  395. $this->assertStringContainsString('This is NOT the alternate template', $result2);
  396. Cache::delete('celltest');
  397. Cache::drop('default');
  398. }
  399. }