CellTest.php 17 KB

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