CellTest.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510
  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(
  191. str_replace('/', DS, "Cell template file `cell/Articles/foo_bar.php` could not be found."),
  192. $message
  193. );
  194. $this->assertStringContainsString('The following paths', $message);
  195. $this->assertStringContainsString(ROOT . DS . 'templates', $message);
  196. $this->assertInstanceOf(MissingTemplateException::class, $e->getPrevious());
  197. }
  198. /**
  199. * Test rendering a cell with a theme.
  200. *
  201. * @return void
  202. */
  203. public function testCellRenderThemed()
  204. {
  205. $this->View->setTheme('TestTheme');
  206. $cell = $this->View->cell('Articles', ['msg' => 'hello world!']);
  207. $this->assertEquals($this->View->getTheme(), $cell->viewBuilder()->getTheme());
  208. $this->assertStringContainsString('Themed cell content.', $cell->render());
  209. }
  210. /**
  211. * Test that a cell can render a plugin view.
  212. *
  213. * @return void
  214. */
  215. public function testCellRenderPluginTemplate()
  216. {
  217. $cell = $this->View->cell('Articles');
  218. $this->assertStringContainsString(
  219. 'TestPlugin Articles/display',
  220. $cell->render('TestPlugin.display')
  221. );
  222. $cell = $this->View->cell('Articles');
  223. $cell->viewBuilder()->setPlugin('TestPlugin');
  224. $this->assertStringContainsString(
  225. 'TestPlugin Articles/display',
  226. $cell->render('display')
  227. );
  228. }
  229. /**
  230. * Tests that using plugin's cells works.
  231. *
  232. * @return void
  233. */
  234. public function testPluginCell()
  235. {
  236. $cell = $this->View->cell('TestPlugin.Dummy::echoThis', ['msg' => 'hello world!']);
  237. $this->assertStringContainsString('hello world!', "{$cell}");
  238. }
  239. /**
  240. * Tests that using namespaced cells works.
  241. *
  242. * @return void
  243. */
  244. public function testNamespacedCell()
  245. {
  246. $cell = $this->View->cell('Admin/Menu');
  247. $this->assertStringContainsString('Admin Menu Cell', $cell->render());
  248. }
  249. /**
  250. * Tests that using namespaced cells in plugins works
  251. *
  252. * @return void
  253. */
  254. public function testPluginNamespacedCell()
  255. {
  256. $cell = $this->View->cell('TestPlugin.Admin/Menu');
  257. $this->assertStringContainsString('Test Plugin Admin Menu Cell', $cell->render());
  258. }
  259. /**
  260. * Test that plugin cells can render other view templates.
  261. *
  262. * @return void
  263. */
  264. public function testPluginCellAlternateTemplate()
  265. {
  266. $cell = $this->View->cell('TestPlugin.Dummy::echoThis', ['msg' => 'hello world!']);
  267. $cell->viewBuilder()->setTemplate('../../element/translate');
  268. $this->assertStringContainsString('This is a translatable string', "{$cell}");
  269. }
  270. /**
  271. * Test that plugin cells can render other view templates.
  272. *
  273. * @return void
  274. */
  275. public function testPluginCellAlternateTemplateRenderParam()
  276. {
  277. $cell = $this->View->cell('TestPlugin.Dummy::echoThis', ['msg' => 'hello world!']);
  278. $result = $cell->render('../../element/translate');
  279. $this->assertStringContainsString('This is a translatable string', $result);
  280. }
  281. /**
  282. * Tests that using an non-existent cell throws an exception.
  283. *
  284. * @return void
  285. */
  286. public function testNonExistentCell()
  287. {
  288. $this->expectException(\Cake\View\Exception\MissingCellException::class);
  289. $cell = $this->View->cell('TestPlugin.Void::echoThis', ['arg1' => 'v1']);
  290. $cell = $this->View->cell('Void::echoThis', ['arg1' => 'v1', 'arg2' => 'v2']);
  291. }
  292. /**
  293. * Tests missing method errors
  294. *
  295. * @return void
  296. */
  297. public function testCellMissingMethod()
  298. {
  299. $this->expectException(\BadMethodCallException::class);
  300. $this->expectExceptionMessage('Class TestApp\View\Cell\ArticlesCell does not have a "nope" method.');
  301. $cell = $this->View->cell('Articles::nope');
  302. $cell->render();
  303. }
  304. /**
  305. * Test that cell options are passed on.
  306. *
  307. * @return void
  308. */
  309. public function testCellOptions()
  310. {
  311. $cell = $this->View->cell('Articles', [], ['limit' => 10, 'nope' => 'nope']);
  312. $this->assertEquals(10, $cell->limit);
  313. $this->assertObjectNotHasAttribute('nope', $cell, 'Not a valid option');
  314. }
  315. /**
  316. * Test that cells get the helper configuration from the view that created them.
  317. *
  318. * @return void
  319. */
  320. public function testCellInheritsHelperConfig()
  321. {
  322. $request = new ServerRequest();
  323. $response = new Response();
  324. $helpers = ['Url', 'Form', 'Banana'];
  325. $view = new View($request, $response, null, ['helpers' => $helpers]);
  326. $cell = $view->cell('Articles');
  327. $this->assertSame($helpers, $cell->viewBuilder()->getHelpers());
  328. }
  329. /**
  330. * Test that cells the view class name of a custom view passed on.
  331. *
  332. * @return void
  333. */
  334. public function testCellInheritsCustomViewClass()
  335. {
  336. $request = new ServerRequest();
  337. $response = new Response();
  338. $view = new CustomJsonView($request, $response);
  339. $view->setTheme('Pretty');
  340. $cell = $view->cell('Articles');
  341. $this->assertSame('TestApp\View\CustomJsonView', $cell->viewBuilder()->getClassName());
  342. $this->assertSame('Pretty', $cell->viewBuilder()->getTheme());
  343. }
  344. /**
  345. * Test that cells the view class name of a controller passed on.
  346. *
  347. * @return void
  348. */
  349. public function testCellInheritsController()
  350. {
  351. $request = new ServerRequest();
  352. $response = new Response();
  353. $controller = new CellTraitTestController($request, $response);
  354. $controller->viewBuilder()->setTheme('Pretty');
  355. $controller->viewBuilder()->setClassName('Json');
  356. $cell = $controller->cell('Articles');
  357. $this->assertSame('Json', $cell->viewBuilder()->getClassName());
  358. $this->assertSame('Pretty', $cell->viewBuilder()->getTheme());
  359. }
  360. /**
  361. * Test cached render.
  362. *
  363. * @return void
  364. */
  365. public function testCachedRenderSimple()
  366. {
  367. Cache::setConfig('default', ['className' => 'Array']);
  368. $cell = $this->View->cell('Articles', [], ['cache' => true]);
  369. $result = $cell->render();
  370. $expected = "dummy\n";
  371. $this->assertEquals($expected, $result);
  372. $result = Cache::read('cell_test_app_view_cell_articles_cell_display_default', 'default');
  373. $this->assertEquals($expected, $result);
  374. Cache::drop('default');
  375. }
  376. /**
  377. * Test read cached cell.
  378. *
  379. * @return void
  380. */
  381. public function testReadCachedCell()
  382. {
  383. Cache::setConfig('default', ['className' => 'Array']);
  384. Cache::write('cell_test_app_view_cell_articles_cell_display_default', 'from cache');
  385. $cell = $this->View->cell('Articles', [], ['cache' => true]);
  386. $result = $cell->render();
  387. $this->assertEquals('from cache', $result);
  388. Cache::drop('default');
  389. }
  390. /**
  391. * Test cached render array config
  392. *
  393. * @return void
  394. */
  395. public function testCachedRenderArrayConfig()
  396. {
  397. Cache::setConfig('cell', ['className' => 'Array']);
  398. Cache::write('my_key', 'from cache', 'cell');
  399. $cell = $this->View->cell('Articles', [], [
  400. 'cache' => ['key' => 'my_key', 'config' => 'cell'],
  401. ]);
  402. $result = $cell->render();
  403. $this->assertEquals('from cache', $result);
  404. Cache::drop('cell');
  405. }
  406. /**
  407. * Test cached render when using an action changing the template used
  408. *
  409. * @return void
  410. */
  411. public function testCachedRenderSimpleCustomTemplate()
  412. {
  413. Cache::setConfig('default', ['className' => 'Array']);
  414. $cell = $this->View->cell('Articles::customTemplateViewBuilder', [], ['cache' => true]);
  415. $result = $cell->render();
  416. $expected = 'This is the alternate template';
  417. $this->assertStringContainsString($expected, $result);
  418. $result = Cache::read('cell_test_app_view_cell_articles_cell_customTemplateViewBuilder_default');
  419. $this->assertStringContainsString($expected, $result);
  420. Cache::drop('default');
  421. }
  422. /**
  423. * Test that when the cell cache is enabled, the cell action is only invoke the first
  424. * time the cell is rendered
  425. *
  426. * @return void
  427. */
  428. public function testCachedRenderSimpleCustomTemplateViewBuilder()
  429. {
  430. Cache::setConfig('default', ['className' => 'Array']);
  431. $cell = $this->View->cell('Articles::customTemplateViewBuilder', [], ['cache' => ['key' => 'celltest']]);
  432. $result = $cell->render();
  433. $this->assertEquals(1, $cell->counter);
  434. $cell->render();
  435. $this->assertEquals(1, $cell->counter);
  436. $this->assertStringContainsString('This is the alternate template', $result);
  437. Cache::drop('default');
  438. }
  439. /**
  440. * Test that when the cell cache is enabled, the cell action is only invoke the first
  441. * time the cell is rendered
  442. *
  443. * @return void
  444. */
  445. public function testACachedViewCellReRendersWhenGivenADifferentTemplate()
  446. {
  447. Cache::setConfig('default', ['className' => 'Array']);
  448. $cell = $this->View->cell('Articles::customTemplateViewBuilder', [], ['cache' => true]);
  449. $result = $cell->render('alternate_teaser_list');
  450. $result2 = $cell->render('not_the_alternate_teaser_list');
  451. $this->assertStringContainsString('This is the alternate template', $result);
  452. $this->assertStringContainsString('This is NOT the alternate template', $result2);
  453. Cache::delete('celltest');
  454. Cache::drop('default');
  455. }
  456. }