MergeTest.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Framework\View\Layout;
  7. use Magento\Framework\App\State;
  8. use Magento\Framework\Phrase;
  9. use Magento\Framework\View\Layout\LayoutCacheKeyInterface;
  10. /**
  11. * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  12. */
  13. class MergeTest extends \PHPUnit\Framework\TestCase
  14. {
  15. /**
  16. * Fixture XML instruction(s) to be used in tests
  17. */
  18. // @codingStandardsIgnoreStart
  19. const FIXTURE_LAYOUT_XML = '<block class="Magento\Framework\View\Element\Template" template="Magento_Framework::fixture_template_one.phtml"/>';
  20. // @codingStandardsIgnoreEnd
  21. /**
  22. * @var \Magento\Framework\View\Model\Layout\Merge
  23. */
  24. protected $_model;
  25. /**
  26. * @var \PHPUnit_Framework_MockObject_MockObject
  27. */
  28. protected $_resource;
  29. /**
  30. * @var \PHPUnit_Framework_MockObject_MockObject
  31. */
  32. protected $_appState;
  33. /**
  34. * @var \PHPUnit_Framework_MockObject_MockObject
  35. */
  36. protected $_cache;
  37. /**
  38. * @var \PHPUnit_Framework_MockObject_MockObject
  39. */
  40. protected $_theme;
  41. /**
  42. * @var \PHPUnit_Framework_MockObject_MockObject
  43. */
  44. protected $scope;
  45. /**
  46. * @var \PHPUnit_Framework_MockObject_MockObject
  47. */
  48. protected $_logger;
  49. /**
  50. * @var \PHPUnit_Framework_MockObject_MockObject
  51. */
  52. protected $_layoutValidator;
  53. /**
  54. * @var \Magento\Framework\View\Page\Config|\PHPUnit_Framework_MockObject_MockObject
  55. */
  56. protected $pageConfig;
  57. /**
  58. * @var LayoutCacheKeyInterface|\PHPUnit_Framework_MockObject_MockObject
  59. */
  60. protected $layoutCacheKeyMock;
  61. protected function setUp()
  62. {
  63. $files = [];
  64. foreach (glob(__DIR__ . '/_mergeFiles/layout/*.xml') as $filename) {
  65. $files[] = new \Magento\Framework\View\File($filename, 'Magento_Widget');
  66. }
  67. $fileSource = $this->getMockForAbstractClass(\Magento\Framework\View\File\CollectorInterface::class);
  68. $fileSource->expects($this->any())->method('getFiles')->will($this->returnValue($files));
  69. $pageLayoutFileSource = $this->getMockForAbstractClass(\Magento\Framework\View\File\CollectorInterface::class);
  70. $pageLayoutFileSource->expects($this->any())->method('getFiles')->willReturn([]);
  71. $design = $this->getMockForAbstractClass(\Magento\Framework\View\DesignInterface::class);
  72. $this->scope = $this->createMock(\Magento\Framework\Url\ScopeInterface::class);
  73. $this->scope->expects($this->any())->method('getId')->will($this->returnValue(20));
  74. $scopeResolver = $this->getMockForAbstractClass(\Magento\Framework\Url\ScopeResolverInterface::class);
  75. $scopeResolver->expects($this->once())->method('getScope')->with(null)->will($this->returnValue($this->scope));
  76. $this->_resource = $this->createMock(\Magento\Widget\Model\ResourceModel\Layout\Update::class);
  77. $this->_appState = $this->createMock(\Magento\Framework\App\State::class);
  78. $this->_logger = $this->createMock(\Psr\Log\LoggerInterface::class);
  79. $this->_layoutValidator = $this->createMock(\Magento\Framework\View\Model\Layout\Update\Validator::class);
  80. $this->_cache = $this->getMockForAbstractClass(\Magento\Framework\Cache\FrontendInterface::class);
  81. $this->_theme = $this->createMock(\Magento\Theme\Model\Theme::class);
  82. $this->_theme->expects($this->any())->method('isPhysical')->will($this->returnValue(true));
  83. $this->_theme->expects($this->any())->method('getArea')->will($this->returnValue('area'));
  84. $this->_theme->expects($this->any())->method('getId')->will($this->returnValue(100));
  85. $objectHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
  86. $this->pageConfig = $this->getMockBuilder(\Magento\Framework\View\Page\Config::class)
  87. ->disableOriginalConstructor()
  88. ->getMock();
  89. $readFactory = $this->createMock(\Magento\Framework\Filesystem\File\ReadFactory::class);
  90. $fileReader = $this->createMock(\Magento\Framework\Filesystem\File\Read::class);
  91. $readFactory->expects($this->any())->method('create')->willReturn($fileReader);
  92. $fileDriver = $objectHelper->getObject(\Magento\Framework\Filesystem\Driver\File::class);
  93. $fileReader->expects($this->any())->method('readAll')->will(
  94. $this->returnCallback(
  95. function ($filename) use ($fileDriver) {
  96. return $fileDriver->fileGetContents(__DIR__ . '/_mergeFiles/layout/' . $filename);
  97. }
  98. )
  99. );
  100. $this->layoutCacheKeyMock = $this->getMockForAbstractClass(LayoutCacheKeyInterface::class);
  101. $this->layoutCacheKeyMock->expects($this->any())
  102. ->method('getCacheKeys')
  103. ->willReturn([]);
  104. $this->_model = $objectHelper->getObject(
  105. \Magento\Framework\View\Model\Layout\Merge::class,
  106. [
  107. 'design' => $design,
  108. 'scopeResolver' => $scopeResolver,
  109. 'fileSource' => $fileSource,
  110. 'pageLayoutFileSource' => $pageLayoutFileSource,
  111. 'resource' => $this->_resource,
  112. 'appState' => $this->_appState,
  113. 'cache' => $this->_cache,
  114. 'theme' => $this->_theme,
  115. 'validator' => $this->_layoutValidator,
  116. 'logger' => $this->_logger,
  117. 'readFactory' => $readFactory,
  118. 'pageConfig' => $this->pageConfig,
  119. 'layoutCacheKey' => $this->layoutCacheKeyMock,
  120. ]
  121. );
  122. }
  123. public function testAddUpdate()
  124. {
  125. $this->assertEmpty($this->_model->asArray());
  126. $this->assertEmpty($this->_model->asString());
  127. $this->_model->addUpdate('test');
  128. $this->assertEquals(['test'], $this->_model->asArray());
  129. $this->assertEquals('test', $this->_model->asString());
  130. }
  131. public function testAddHandle()
  132. {
  133. $this->assertEmpty($this->_model->getHandles());
  134. $this->_model->addHandle('test');
  135. $this->assertEquals(['test'], $this->_model->getHandles());
  136. }
  137. public function testRemoveHandle()
  138. {
  139. $this->_model->addHandle('test');
  140. $this->_model->removeHandle('test');
  141. $this->assertEmpty($this->_model->getHandles());
  142. }
  143. public function testAddPageHandles()
  144. {
  145. /* add a non-page handle to verify that it won't be affected during page handles manipulation */
  146. $nonPageHandles = ['non_page_handle'];
  147. $this->_model->addHandle($nonPageHandles);
  148. $this->assertFalse($this->_model->addPageHandles(['non_existing_handle']));
  149. $this->assertEmpty($this->_model->getPageHandles());
  150. $this->assertEquals($nonPageHandles, $this->_model->getHandles());
  151. /* test that only the first existing handle is taken into account */
  152. $handlesToTry = [
  153. 'default',
  154. 'catalog_category_default',
  155. 'catalog_product_view',
  156. 'catalog_product_view_type_simple',
  157. ];
  158. $expectedPageHandles = [
  159. 'default',
  160. 'catalog_category_default',
  161. 'catalog_product_view',
  162. 'catalog_product_view_type_simple',
  163. ];
  164. $this->assertTrue($this->_model->addPageHandles($handlesToTry));
  165. $this->assertEquals($expectedPageHandles, $this->_model->getPageHandles());
  166. $this->assertEquals(array_merge($nonPageHandles, $expectedPageHandles), $this->_model->getHandles());
  167. /* test that new handles override the previous ones */
  168. $expectedPageHandles = ['default', 'checkout_index_index'];
  169. $this->_model->removeHandle('catalog_category_default');
  170. $this->_model->removeHandle('catalog_product_view');
  171. $this->_model->removeHandle('catalog_product_view_type_simple');
  172. $this->assertTrue($this->_model->addPageHandles(['default', 'checkout_index_index']));
  173. $this->assertEquals($expectedPageHandles, $this->_model->getPageHandles());
  174. $this->assertEquals(array_merge($nonPageHandles, $expectedPageHandles), $this->_model->getHandles());
  175. }
  176. /**
  177. * @dataProvider pageHandleExistsDataProvider
  178. */
  179. public function testPageHandleExists($inputPageHandle, $expectedResult)
  180. {
  181. $this->assertSame($expectedResult, $this->_model->pageHandleExists($inputPageHandle));
  182. }
  183. public function pageHandleExistsDataProvider()
  184. {
  185. return [
  186. 'non-existing handle' => ['non_existing_handle', false],
  187. 'existing page type' => ['default', true],
  188. ];
  189. }
  190. public function testLoadFileSystem()
  191. {
  192. $handles = ['fixture_handle_one', 'fixture_handle_two'];
  193. $this->assertEmpty($this->_model->getHandles());
  194. $this->assertEmpty($this->_model->asString());
  195. $this->_model->load($handles);
  196. $this->assertEquals($handles, $this->_model->getHandles());
  197. $expectedResult = '
  198. <root>
  199. <body>
  200. <block class="Magento\Framework\View\Element\Template"
  201. template="Magento_Framework::fixture_template_one.phtml"/>
  202. </body>
  203. <body>
  204. <block class="Magento\Framework\View\Element\Template"
  205. template="Magento_Framework::fixture_template_two.phtml"/>
  206. </body>
  207. </root>
  208. ';
  209. $actualResult = '<root>' . $this->_model->asString() . '</root>';
  210. $this->assertXmlStringEqualsXmlString($expectedResult, $actualResult);
  211. }
  212. public function testLoadFileSystemWithPageLayout()
  213. {
  214. $handles = ['fixture_handle_with_page_layout'];
  215. $expectedHandles = ['fixture_handle_with_page_layout'];
  216. $expectedResult = '
  217. <root>
  218. <body>
  219. <referenceContainer name="main.container">
  220. <block class="Magento\Framework\View\Element\Template"
  221. template="Magento_Framework::fixture_template_one.phtml"/>
  222. </referenceContainer>
  223. </body>
  224. </root>
  225. ';
  226. $this->assertEmpty($this->_model->getHandles());
  227. $this->assertEmpty($this->_model->asString());
  228. $this->_model->load($handles);
  229. $this->assertEquals($expectedHandles, $this->_model->getHandles());
  230. $actualResult = '<root>' . $this->_model->asString() . '</root>';
  231. $this->assertXmlStringEqualsXmlString($expectedResult, $actualResult);
  232. $this->assertEquals('fixture_handle_page_layout', $this->_model->getPageLayout());
  233. }
  234. public function testLoadCache()
  235. {
  236. $this->_cache->expects($this->at(0))->method('load')
  237. ->with('LAYOUT_area_STORE20_100c6a4ccd050e33acef0553f24ef399961')
  238. ->will($this->returnValue(self::FIXTURE_LAYOUT_XML));
  239. $this->assertEmpty($this->_model->getHandles());
  240. $this->assertEmpty($this->_model->asString());
  241. $handles = ['fixture_handle_one', 'fixture_handle_two'];
  242. $this->_model->load($handles);
  243. $this->assertEquals($handles, $this->_model->getHandles());
  244. $this->assertEquals(self::FIXTURE_LAYOUT_XML, $this->_model->asString());
  245. }
  246. public function testLoadDbApp()
  247. {
  248. $this->_resource->expects(
  249. $this->any()
  250. )->method(
  251. 'fetchUpdatesByHandle'
  252. )->with(
  253. 'fixture_handle',
  254. $this->_theme,
  255. $this->scope
  256. )->will(
  257. $this->returnValue(self::FIXTURE_LAYOUT_XML)
  258. );
  259. $this->assertEmpty($this->_model->getHandles());
  260. $this->assertEmpty($this->_model->asString());
  261. $handles = ['fixture_handle_one'];
  262. $this->_model->load($handles);
  263. $this->assertEquals($handles, $this->_model->getHandles());
  264. $this->assertXmlStringEqualsXmlString(
  265. '<body>' . self::FIXTURE_LAYOUT_XML . '</body>',
  266. $this->_model->asString()
  267. );
  268. }
  269. public function testGetFileLayoutUpdatesXml()
  270. {
  271. $errorString = "Theme layout update file '" . __DIR__ . "/_mergeFiles/layout/file_wrong.xml' is not valid.";
  272. $this->_logger->expects($this->atLeastOnce())->method('info')
  273. ->with($this->stringStartsWith($errorString));
  274. $actualXml = $this->_model->getFileLayoutUpdatesXml();
  275. $this->assertXmlStringEqualsXmlFile(__DIR__ . '/_mergeFiles/merged.xml', $actualXml->asNiceXml());
  276. }
  277. public function testGetContainers()
  278. {
  279. $this->_model->addPageHandles(['default']);
  280. $this->_model->addPageHandles(['catalog_product_view']);
  281. $this->_model->addPageHandles(['catalog_product_view_type_configurable']);
  282. $this->_model->load();
  283. $expected = [
  284. 'content' => 'Main Content Area',
  285. 'product.info.extrahint' => 'Product View Extra Hint',
  286. 'product.info.configurable.extra' => 'Configurable Product Extra Info',
  287. ];
  288. $this->assertEquals($expected, $this->_model->getContainers());
  289. }
  290. public function testGetAllDesignAbstractions()
  291. {
  292. $expected = [
  293. 'customer_account' => [
  294. 'name' => 'customer_account',
  295. 'label' => new Phrase('Customer My Account (All Pages)'),
  296. 'design_abstraction' => 'custom',
  297. ],
  298. 'page_empty' => [
  299. 'name' => 'page_empty',
  300. 'label' => new Phrase('All Empty Layout Pages'),
  301. 'design_abstraction' => 'page_layout',
  302. ],
  303. ];
  304. $this->assertEquals($expected, $this->_model->getAllDesignAbstractions());
  305. }
  306. public function testIsPageLayoutDesignAbstractions()
  307. {
  308. $expected = [
  309. 'customer_account' => [
  310. 'name' => 'customer_account',
  311. 'label' => 'Customer My Account (All Pages)',
  312. 'design_abstraction' => 'custom',
  313. ],
  314. 'page_empty' => [
  315. 'name' => 'page_empty',
  316. 'label' => 'All Empty Layout Pages',
  317. 'design_abstraction' => 'page_layout',
  318. ],
  319. 'empty_data' => [],
  320. ];
  321. $this->assertTrue($this->_model->isPageLayoutDesignAbstraction($expected['page_empty']));
  322. $this->assertFalse($this->_model->isPageLayoutDesignAbstraction($expected['customer_account']));
  323. $this->assertFalse($this->_model->isPageLayoutDesignAbstraction($expected['empty_data']));
  324. }
  325. public function testIsCustomDesignAbstractions()
  326. {
  327. $expected = [
  328. 'customer_account' => [
  329. 'name' => 'customer_account',
  330. 'label' => 'Customer My Account (All Pages)',
  331. 'design_abstraction' => 'custom',
  332. ],
  333. 'page_empty' => [
  334. 'name' => 'page_empty',
  335. 'label' => 'All Empty Layout Pages',
  336. 'design_abstraction' => 'page_layout',
  337. ],
  338. 'empty_data' => [],
  339. ];
  340. $this->assertTrue($this->_model->isCustomerDesignAbstraction($expected['customer_account']));
  341. $this->assertFalse($this->_model->isCustomerDesignAbstraction($expected['page_empty']));
  342. $this->assertFalse($this->_model->isCustomerDesignAbstraction($expected['empty_data']));
  343. }
  344. /**
  345. * @expectedException \Magento\Framework\Exception\LocalizedException
  346. * @expectedExceptionMessage Invalid layout update handle
  347. */
  348. public function testLoadWithInvalidArgumentThrowsException()
  349. {
  350. $this->_model->load(123);
  351. }
  352. /**
  353. * Test loading invalid layout
  354. *
  355. * @expectedException \Exception
  356. * @expectedExceptionMessage Layout is invalid.
  357. */
  358. public function testLoadWithInvalidLayout()
  359. {
  360. $this->_model->addPageHandles(['default']);
  361. $this->_appState->expects($this->once())->method('getMode')->willReturn(State::MODE_DEVELOPER);
  362. $this->_layoutValidator->expects($this->any())
  363. ->method('getMessages')
  364. ->willReturn(['testMessage1', 'testMessage2']);
  365. $this->_layoutValidator->expects($this->any())
  366. ->method('isValid')
  367. ->willThrowException(new \Exception('Layout is invalid.'));
  368. $suffix = md5(implode('|', $this->_model->getHandles()));
  369. $cacheId = "LAYOUT_{$this->_theme->getArea()}_STORE{$this->scope->getId()}_{$this->_theme->getId()}{$suffix}";
  370. $messages = $this->_layoutValidator->getMessages();
  371. // Testing error message is logged with logger
  372. $this->_logger->expects($this->once())->method('info')
  373. ->with(
  374. 'Cache file with merged layout: ' . $cacheId . ' and handles default' . ': ' . array_shift($messages)
  375. );
  376. $this->_model->load();
  377. }
  378. /**
  379. * @expectedException \Magento\Framework\Config\Dom\ValidationException
  380. * @expectedExceptionMessageRegExp /_mergeFiles\/layout\/file_wrong\.xml\' is not valid/
  381. */
  382. public function testLayoutUpdateFileIsNotValid()
  383. {
  384. $this->_appState->expects($this->once())->method('getMode')->willReturn(State::MODE_DEVELOPER);
  385. $this->_model->addPageHandles(['default']);
  386. }
  387. }