Pull merge.
[yaffs-website] / web / core / tests / Drupal / Tests / Core / Layout / LayoutPluginManagerTest.php
1 <?php
2
3 namespace Drupal\Tests\Core\Layout;
4
5 use Drupal\Component\Plugin\Derivative\DeriverBase;
6 use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException;
7 use Drupal\Core\Cache\CacheBackendInterface;
8 use Drupal\Core\DependencyInjection\ContainerBuilder;
9 use Drupal\Core\Extension\Extension;
10 use Drupal\Core\Extension\ModuleHandlerInterface;
11 use Drupal\Core\Extension\ThemeHandlerInterface;
12 use Drupal\Core\Layout\LayoutDefault;
13 use Drupal\Core\Layout\LayoutDefinition;
14 use Drupal\Core\Layout\LayoutPluginManager;
15 use Drupal\Core\StringTranslation\TranslatableMarkup;
16 use Drupal\Tests\UnitTestCase;
17 use org\bovigo\vfs\vfsStream;
18 use Prophecy\Argument;
19
20 /**
21  * @coversDefaultClass \Drupal\Core\Layout\LayoutPluginManager
22  * @group Layout
23  */
24 class LayoutPluginManagerTest extends UnitTestCase {
25
26   /**
27    * The module handler.
28    *
29    * @var \Drupal\Core\Extension\ModuleHandlerInterface
30    */
31   protected $moduleHandler;
32
33   /**
34    * The theme handler.
35    *
36    * @var \Drupal\Core\Extension\ThemeHandlerInterface
37    */
38   protected $themeHandler;
39
40   /**
41    * Cache backend instance.
42    *
43    * @var \Drupal\Core\Cache\CacheBackendInterface
44    */
45   protected $cacheBackend;
46
47   /**
48    * The layout plugin manager.
49    *
50    * @var \Drupal\Core\Layout\LayoutPluginManagerInterface
51    */
52   protected $layoutPluginManager;
53
54   /**
55    * {@inheritdoc}
56    */
57   protected function setUp() {
58     parent::setUp();
59
60     $this->setUpFilesystem();
61
62     $container = new ContainerBuilder();
63     $container->set('string_translation', $this->getStringTranslationStub());
64     \Drupal::setContainer($container);
65
66     $this->moduleHandler = $this->prophesize(ModuleHandlerInterface::class);
67
68     $this->moduleHandler->moduleExists('module_a')->willReturn(TRUE);
69     $this->moduleHandler->moduleExists('theme_a')->willReturn(FALSE);
70     $this->moduleHandler->moduleExists('core')->willReturn(FALSE);
71     $this->moduleHandler->moduleExists('invalid_provider')->willReturn(FALSE);
72
73     $module_a = new Extension('/', 'module', vfsStream::url('root/modules/module_a/module_a.layouts.yml'));
74     $this->moduleHandler->getModule('module_a')->willReturn($module_a);
75     $this->moduleHandler->getModuleDirectories()->willReturn(['module_a' => vfsStream::url('root/modules/module_a')]);
76     $this->moduleHandler->alter('layout', Argument::type('array'))->shouldBeCalled();
77
78     $this->themeHandler = $this->prophesize(ThemeHandlerInterface::class);
79
80     $this->themeHandler->themeExists('theme_a')->willReturn(TRUE);
81     $this->themeHandler->themeExists('core')->willReturn(FALSE);
82     $this->themeHandler->themeExists('invalid_provider')->willReturn(FALSE);
83
84     $theme_a = new Extension('/', 'theme', vfsStream::url('root/themes/theme_a/theme_a.layouts.yml'));
85     $this->themeHandler->getTheme('theme_a')->willReturn($theme_a);
86     $this->themeHandler->getThemeDirectories()->willReturn(['theme_a' => vfsStream::url('root/themes/theme_a')]);
87
88     $this->cacheBackend = $this->prophesize(CacheBackendInterface::class);
89
90     $namespaces = new \ArrayObject(['Drupal\Core' => vfsStream::url('root/core/lib/Drupal/Core')]);
91     $this->layoutPluginManager = new LayoutPluginManager($namespaces, $this->cacheBackend->reveal(), $this->moduleHandler->reveal(), $this->themeHandler->reveal(), $this->getStringTranslationStub());
92   }
93
94   /**
95    * @covers ::getDefinitions
96    * @covers ::providerExists
97    */
98   public function testGetDefinitions() {
99     $expected = [
100       'module_a_provided_layout',
101       'theme_a_provided_layout',
102       'plugin_provided_layout',
103     ];
104
105     $layout_definitions = $this->layoutPluginManager->getDefinitions();
106     $this->assertEquals($expected, array_keys($layout_definitions));
107     $this->assertContainsOnlyInstancesOf(LayoutDefinition::class, $layout_definitions);
108   }
109
110   /**
111    * @covers ::getDefinition
112    * @covers ::processDefinition
113    */
114   public function testGetDefinition() {
115     $theme_a_path = vfsStream::url('root/themes/theme_a');
116     $layout_definition = $this->layoutPluginManager->getDefinition('theme_a_provided_layout');
117     $this->assertSame('theme_a_provided_layout', $layout_definition->id());
118     $this->assertSame('2 column layout', (string) $layout_definition->getLabel());
119     $this->assertSame('Columns: 2', (string) $layout_definition->getCategory());
120     $this->assertSame('A theme provided layout', (string) $layout_definition->getDescription());
121     $this->assertTrue($layout_definition->getLabel() instanceof TranslatableMarkup);
122     $this->assertTrue($layout_definition->getCategory() instanceof TranslatableMarkup);
123     $this->assertTrue($layout_definition->getDescription() instanceof TranslatableMarkup);
124     $this->assertSame('twocol', $layout_definition->getTemplate());
125     $this->assertSame("$theme_a_path/templates", $layout_definition->getPath());
126     $this->assertSame('theme_a/twocol', $layout_definition->getLibrary());
127     $this->assertSame('twocol', $layout_definition->getThemeHook());
128     $this->assertSame("$theme_a_path/templates", $layout_definition->getTemplatePath());
129     $this->assertSame('theme_a', $layout_definition->getProvider());
130     $this->assertSame('right', $layout_definition->getDefaultRegion());
131     $this->assertSame(LayoutDefault::class, $layout_definition->getClass());
132     $expected_regions = [
133       'left' => [
134         'label' => new TranslatableMarkup('Left region', [], ['context' => 'layout_region']),
135       ],
136       'right' => [
137         'label' => new TranslatableMarkup('Right region', [], ['context' => 'layout_region']),
138       ],
139     ];
140     $regions = $layout_definition->getRegions();
141     $this->assertEquals($expected_regions, $regions);
142     $this->assertTrue($regions['left']['label'] instanceof TranslatableMarkup);
143     $this->assertTrue($regions['right']['label'] instanceof TranslatableMarkup);
144
145     $module_a_path = vfsStream::url('root/modules/module_a');
146     $layout_definition = $this->layoutPluginManager->getDefinition('module_a_provided_layout');
147     $this->assertSame('module_a_provided_layout', $layout_definition->id());
148     $this->assertSame('1 column layout', (string) $layout_definition->getLabel());
149     $this->assertSame('Columns: 1', (string) $layout_definition->getCategory());
150     $this->assertSame('A module provided layout', (string) $layout_definition->getDescription());
151     $this->assertTrue($layout_definition->getLabel() instanceof TranslatableMarkup);
152     $this->assertTrue($layout_definition->getCategory() instanceof TranslatableMarkup);
153     $this->assertTrue($layout_definition->getDescription() instanceof TranslatableMarkup);
154     $this->assertSame(NULL, $layout_definition->getTemplate());
155     $this->assertSame("$module_a_path/layouts", $layout_definition->getPath());
156     $this->assertSame('module_a/onecol', $layout_definition->getLibrary());
157     $this->assertSame('onecol', $layout_definition->getThemeHook());
158     $this->assertSame(NULL, $layout_definition->getTemplatePath());
159     $this->assertSame('module_a', $layout_definition->getProvider());
160     $this->assertSame('top', $layout_definition->getDefaultRegion());
161     $this->assertSame(LayoutDefault::class, $layout_definition->getClass());
162     $expected_regions = [
163       'top' => [
164         'label' => new TranslatableMarkup('Top region', [], ['context' => 'layout_region']),
165       ],
166       'bottom' => [
167         'label' => new TranslatableMarkup('Bottom region', [], ['context' => 'layout_region']),
168       ],
169     ];
170     $regions = $layout_definition->getRegions();
171     $this->assertEquals($expected_regions, $regions);
172     $this->assertTrue($regions['top']['label'] instanceof TranslatableMarkup);
173     $this->assertTrue($regions['bottom']['label'] instanceof TranslatableMarkup);
174
175     $core_path = '/core/lib/Drupal/Core';
176     $layout_definition = $this->layoutPluginManager->getDefinition('plugin_provided_layout');
177     $this->assertSame('plugin_provided_layout', $layout_definition->id());
178     $this->assertEquals('Layout plugin', $layout_definition->getLabel());
179     $this->assertEquals('Columns: 1', $layout_definition->getCategory());
180     $this->assertEquals('Test layout', $layout_definition->getDescription());
181     $this->assertTrue($layout_definition->getLabel() instanceof TranslatableMarkup);
182     $this->assertTrue($layout_definition->getCategory() instanceof TranslatableMarkup);
183     $this->assertTrue($layout_definition->getDescription() instanceof TranslatableMarkup);
184     $this->assertSame('plugin-provided-layout', $layout_definition->getTemplate());
185     $this->assertSame($core_path, $layout_definition->getPath());
186     $this->assertSame(NULL, $layout_definition->getLibrary());
187     $this->assertSame('plugin_provided_layout', $layout_definition->getThemeHook());
188     $this->assertSame("$core_path/templates", $layout_definition->getTemplatePath());
189     $this->assertSame('core', $layout_definition->getProvider());
190     $this->assertSame('main', $layout_definition->getDefaultRegion());
191     $this->assertSame('Drupal\Core\Plugin\Layout\TestLayout', $layout_definition->getClass());
192     $expected_regions = [
193       'main' => [
194         'label' => new TranslatableMarkup('Main Region', [], ['context' => 'layout_region']),
195       ],
196     ];
197     $regions = $layout_definition->getRegions();
198     $this->assertEquals($expected_regions, $regions);
199     $this->assertTrue($regions['main']['label'] instanceof TranslatableMarkup);
200   }
201
202   /**
203    * @covers ::processDefinition
204    */
205   public function testProcessDefinition() {
206     $this->moduleHandler->alter('layout', Argument::type('array'))->shouldNotBeCalled();
207     $this->setExpectedException(InvalidPluginDefinitionException::class, 'The "module_a_derived_layout:array_based" layout definition must extend ' . LayoutDefinition::class);
208     $module_a_provided_layout = <<<'EOS'
209 module_a_derived_layout:
210   deriver: \Drupal\Tests\Core\Layout\LayoutDeriver
211   array_based: true
212 EOS;
213     vfsStream::create([
214       'modules' => [
215         'module_a' => [
216           'module_a.layouts.yml' => $module_a_provided_layout,
217         ],
218       ],
219     ]);
220     $this->layoutPluginManager->getDefinitions();
221   }
222
223   /**
224    * @covers ::getThemeImplementations
225    */
226   public function testGetThemeImplementations() {
227     $core_path = '/core/lib/Drupal/Core';
228     $theme_a_path = vfsStream::url('root/themes/theme_a');
229     $expected = [
230       'layout' => [
231         'render element' => 'content',
232       ],
233       'twocol' => [
234         'render element' => 'content',
235         'base hook' => 'layout',
236         'template' => 'twocol',
237         'path' => "$theme_a_path/templates",
238       ],
239       'plugin_provided_layout' => [
240         'render element' => 'content',
241         'base hook' => 'layout',
242         'template' => 'plugin-provided-layout',
243         'path' => "$core_path/templates",
244       ],
245     ];
246     $theme_implementations = $this->layoutPluginManager->getThemeImplementations();
247     $this->assertEquals($expected, $theme_implementations);
248   }
249
250   /**
251    * @covers ::getCategories
252    */
253   public function testGetCategories() {
254     $expected = [
255       'Columns: 1',
256       'Columns: 2',
257     ];
258     $categories = $this->layoutPluginManager->getCategories();
259     $this->assertEquals($expected, $categories);
260   }
261
262   /**
263    * @covers ::getSortedDefinitions
264    */
265   public function testGetSortedDefinitions() {
266     $expected = [
267       'module_a_provided_layout',
268       'plugin_provided_layout',
269       'theme_a_provided_layout',
270     ];
271
272     $layout_definitions = $this->layoutPluginManager->getSortedDefinitions();
273     $this->assertEquals($expected, array_keys($layout_definitions));
274     $this->assertContainsOnlyInstancesOf(LayoutDefinition::class, $layout_definitions);
275   }
276
277   /**
278    * @covers ::getGroupedDefinitions
279    */
280   public function testGetGroupedDefinitions() {
281     $category_expected = [
282       'Columns: 1' => [
283         'module_a_provided_layout',
284         'plugin_provided_layout',
285       ],
286       'Columns: 2' => [
287         'theme_a_provided_layout',
288       ],
289     ];
290
291     $definitions = $this->layoutPluginManager->getGroupedDefinitions();
292     $this->assertEquals(array_keys($category_expected), array_keys($definitions));
293     foreach ($category_expected as $category => $expected) {
294       $this->assertArrayHasKey($category, $definitions);
295       $this->assertEquals($expected, array_keys($definitions[$category]));
296       $this->assertContainsOnlyInstancesOf(LayoutDefinition::class, $definitions[$category]);
297     }
298   }
299
300   /**
301    * Sets up the filesystem with YAML files and annotated plugins.
302    */
303   protected function setUpFilesystem() {
304     $module_a_provided_layout = <<<'EOS'
305 module_a_provided_layout:
306   label: 1 column layout
307   category: 'Columns: 1'
308   description: 'A module provided layout'
309   theme_hook: onecol
310   path: layouts
311   library: module_a/onecol
312   regions:
313     top:
314       label: Top region
315     bottom:
316       label: Bottom region
317 module_a_derived_layout:
318   deriver: \Drupal\Tests\Core\Layout\LayoutDeriver
319   invalid_provider: true
320 EOS;
321     $theme_a_provided_layout = <<<'EOS'
322 theme_a_provided_layout:
323   class: '\Drupal\Core\Layout\LayoutDefault'
324   label: 2 column layout
325   category: 'Columns: 2'
326   description: 'A theme provided layout'
327   template: twocol
328   path: templates
329   library: theme_a/twocol
330   default_region: right
331   regions:
332     left:
333       label: Left region
334     right:
335       label: Right region
336 EOS;
337     $plugin_provided_layout = <<<'EOS'
338 <?php
339 namespace Drupal\Core\Plugin\Layout;
340 use Drupal\Core\Layout\LayoutDefault;
341 /**
342  * @Layout(
343  *   id = "plugin_provided_layout",
344  *   label = @Translation("Layout plugin"),
345  *   category = @Translation("Columns: 1"),
346  *   description = @Translation("Test layout"),
347  *   path = "core/lib/Drupal/Core",
348  *   template = "templates/plugin-provided-layout",
349  *   regions = {
350  *     "main" = {
351  *       "label" = @Translation("Main Region", context = "layout_region")
352  *     }
353  *   }
354  * )
355  */
356 class TestLayout extends LayoutDefault {}
357 EOS;
358     vfsStream::setup('root');
359     vfsStream::create([
360       'modules' => [
361         'module_a' => [
362           'module_a.layouts.yml' => $module_a_provided_layout,
363         ],
364       ],
365     ]);
366     vfsStream::create([
367       'themes' => [
368         'theme_a' => [
369           'theme_a.layouts.yml' => $theme_a_provided_layout,
370         ],
371       ],
372     ]);
373     vfsStream::create([
374       'core' => [
375         'lib' => [
376           'Drupal' => [
377             'Core' => [
378               'Plugin' => [
379                 'Layout' => [
380                   'TestLayout.php' => $plugin_provided_layout,
381                 ],
382               ],
383             ],
384           ],
385         ],
386       ],
387     ]);
388   }
389
390 }
391
392 /**
393  * Provides a dynamic layout deriver for the test.
394  */
395 class LayoutDeriver extends DeriverBase {
396
397   /**
398    * {@inheritdoc}
399    */
400   public function getDerivativeDefinitions($base_plugin_definition) {
401     if ($base_plugin_definition->get('array_based')) {
402       $this->derivatives['array_based'] = [];
403     }
404     if ($base_plugin_definition->get('invalid_provider')) {
405       $this->derivatives['invalid_provider'] = new LayoutDefinition([
406         'id' => 'invalid_provider',
407         'provider' => 'invalid_provider',
408       ]);
409     }
410     return $this->derivatives;
411   }
412
413 }