CellTest.php 16 KB

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