3 namespace Drupal\Tests\Core\Layout;
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\Tests\UnitTestCase;
16 use org\bovigo\vfs\vfsStream;
17 use Prophecy\Argument;
20 * @coversDefaultClass \Drupal\Core\Layout\LayoutPluginManager
23 class LayoutPluginManagerTest extends UnitTestCase {
28 * @var \Drupal\Core\Extension\ModuleHandlerInterface
30 protected $moduleHandler;
35 * @var \Drupal\Core\Extension\ThemeHandlerInterface
37 protected $themeHandler;
40 * Cache backend instance.
42 * @var \Drupal\Core\Cache\CacheBackendInterface
44 protected $cacheBackend;
47 * The layout plugin manager.
49 * @var \Drupal\Core\Layout\LayoutPluginManagerInterface
51 protected $layoutPluginManager;
56 protected function setUp() {
59 $this->setUpFilesystem();
61 $container = new ContainerBuilder();
62 $container->set('string_translation', $this->getStringTranslationStub());
63 \Drupal::setContainer($container);
65 $this->moduleHandler = $this->prophesize(ModuleHandlerInterface::class);
67 $this->moduleHandler->moduleExists('module_a')->willReturn(TRUE);
68 $this->moduleHandler->moduleExists('theme_a')->willReturn(FALSE);
69 $this->moduleHandler->moduleExists('core')->willReturn(FALSE);
70 $this->moduleHandler->moduleExists('invalid_provider')->willReturn(FALSE);
72 $module_a = new Extension('/', 'module', vfsStream::url('root/modules/module_a/module_a.layouts.yml'));
73 $this->moduleHandler->getModule('module_a')->willReturn($module_a);
74 $this->moduleHandler->getModuleDirectories()->willReturn(['module_a' => vfsStream::url('root/modules/module_a')]);
75 $this->moduleHandler->alter('layout', Argument::type('array'))->shouldBeCalled();
77 $this->themeHandler = $this->prophesize(ThemeHandlerInterface::class);
79 $this->themeHandler->themeExists('theme_a')->willReturn(TRUE);
80 $this->themeHandler->themeExists('core')->willReturn(FALSE);
81 $this->themeHandler->themeExists('invalid_provider')->willReturn(FALSE);
83 $theme_a = new Extension('/', 'theme', vfsStream::url('root/themes/theme_a/theme_a.layouts.yml'));
84 $this->themeHandler->getTheme('theme_a')->willReturn($theme_a);
85 $this->themeHandler->getThemeDirectories()->willReturn(['theme_a' => vfsStream::url('root/themes/theme_a')]);
87 $this->cacheBackend = $this->prophesize(CacheBackendInterface::class);
89 $namespaces = new \ArrayObject(['Drupal\Core' => vfsStream::url('root/core/lib/Drupal/Core')]);
90 $this->layoutPluginManager = new LayoutPluginManager($namespaces, $this->cacheBackend->reveal(), $this->moduleHandler->reveal(), $this->themeHandler->reveal(), $this->getStringTranslationStub());
94 * @covers ::getDefinitions
95 * @covers ::providerExists
97 public function testGetDefinitions() {
99 'module_a_provided_layout',
100 'theme_a_provided_layout',
101 'plugin_provided_layout',
104 $layout_definitions = $this->layoutPluginManager->getDefinitions();
105 $this->assertEquals($expected, array_keys($layout_definitions));
106 $this->assertContainsOnlyInstancesOf(LayoutDefinition::class, $layout_definitions);
110 * @covers ::getDefinition
111 * @covers ::processDefinition
113 public function testGetDefinition() {
114 $theme_a_path = vfsStream::url('root/themes/theme_a');
115 $layout_definition = $this->layoutPluginManager->getDefinition('theme_a_provided_layout');
116 $this->assertSame('theme_a_provided_layout', $layout_definition->id());
117 $this->assertSame('2 column layout', $layout_definition->getLabel());
118 $this->assertSame('Columns: 2', $layout_definition->getCategory());
119 $this->assertSame('twocol', $layout_definition->getTemplate());
120 $this->assertSame("$theme_a_path/templates", $layout_definition->getPath());
121 $this->assertSame('theme_a/twocol', $layout_definition->getLibrary());
122 $this->assertSame('twocol', $layout_definition->getThemeHook());
123 $this->assertSame("$theme_a_path/templates", $layout_definition->getTemplatePath());
124 $this->assertSame('theme_a', $layout_definition->getProvider());
125 $this->assertSame('right', $layout_definition->getDefaultRegion());
126 $this->assertSame(LayoutDefault::class, $layout_definition->getClass());
127 $expected_regions = [
129 'label' => 'Left region',
132 'label' => 'Right region',
135 $this->assertSame($expected_regions, $layout_definition->getRegions());
137 $module_a_path = vfsStream::url('root/modules/module_a');
138 $layout_definition = $this->layoutPluginManager->getDefinition('module_a_provided_layout');
139 $this->assertSame('module_a_provided_layout', $layout_definition->id());
140 $this->assertSame('1 column layout', $layout_definition->getLabel());
141 $this->assertSame('Columns: 1', $layout_definition->getCategory());
142 $this->assertSame(NULL, $layout_definition->getTemplate());
143 $this->assertSame("$module_a_path/layouts", $layout_definition->getPath());
144 $this->assertSame('module_a/onecol', $layout_definition->getLibrary());
145 $this->assertSame('onecol', $layout_definition->getThemeHook());
146 $this->assertSame(NULL, $layout_definition->getTemplatePath());
147 $this->assertSame('module_a', $layout_definition->getProvider());
148 $this->assertSame('top', $layout_definition->getDefaultRegion());
149 $this->assertSame(LayoutDefault::class, $layout_definition->getClass());
150 $expected_regions = [
152 'label' => 'Top region',
155 'label' => 'Bottom region',
158 $this->assertSame($expected_regions, $layout_definition->getRegions());
160 $core_path = '/core/lib/Drupal/Core';
161 $layout_definition = $this->layoutPluginManager->getDefinition('plugin_provided_layout');
162 $this->assertSame('plugin_provided_layout', $layout_definition->id());
163 $this->assertEquals('Layout plugin', $layout_definition->getLabel());
164 $this->assertEquals('Columns: 1', $layout_definition->getCategory());
165 $this->assertSame('plugin-provided-layout', $layout_definition->getTemplate());
166 $this->assertSame($core_path, $layout_definition->getPath());
167 $this->assertSame(NULL, $layout_definition->getLibrary());
168 $this->assertSame('plugin_provided_layout', $layout_definition->getThemeHook());
169 $this->assertSame("$core_path/templates", $layout_definition->getTemplatePath());
170 $this->assertSame('core', $layout_definition->getProvider());
171 $this->assertSame('main', $layout_definition->getDefaultRegion());
172 $this->assertSame('Drupal\Core\Plugin\Layout\TestLayout', $layout_definition->getClass());
173 $expected_regions = [
175 'label' => 'Main Region',
178 $this->assertEquals($expected_regions, $layout_definition->getRegions());
182 * @covers ::processDefinition
184 public function testProcessDefinition() {
185 $this->moduleHandler->alter('layout', Argument::type('array'))->shouldNotBeCalled();
186 $this->setExpectedException(InvalidPluginDefinitionException::class, 'The "module_a_derived_layout:array_based" layout definition must extend ' . LayoutDefinition::class);
187 $module_a_provided_layout = <<<'EOS'
188 module_a_derived_layout:
189 deriver: \Drupal\Tests\Core\Layout\LayoutDeriver
195 'module_a.layouts.yml' => $module_a_provided_layout,
199 $this->layoutPluginManager->getDefinitions();
203 * @covers ::getThemeImplementations
205 public function testGetThemeImplementations() {
206 $core_path = '/core/lib/Drupal/Core';
207 $theme_a_path = vfsStream::url('root/themes/theme_a');
210 'render element' => 'content',
213 'render element' => 'content',
214 'base hook' => 'layout',
215 'template' => 'twocol',
216 'path' => "$theme_a_path/templates",
218 'plugin_provided_layout' => [
219 'render element' => 'content',
220 'base hook' => 'layout',
221 'template' => 'plugin-provided-layout',
222 'path' => "$core_path/templates",
225 $theme_implementations = $this->layoutPluginManager->getThemeImplementations();
226 $this->assertEquals($expected, $theme_implementations);
230 * @covers ::getCategories
232 public function testGetCategories() {
237 $categories = $this->layoutPluginManager->getCategories();
238 $this->assertEquals($expected, $categories);
242 * @covers ::getSortedDefinitions
244 public function testGetSortedDefinitions() {
246 'module_a_provided_layout',
247 'plugin_provided_layout',
248 'theme_a_provided_layout',
251 $layout_definitions = $this->layoutPluginManager->getSortedDefinitions();
252 $this->assertEquals($expected, array_keys($layout_definitions));
253 $this->assertContainsOnlyInstancesOf(LayoutDefinition::class, $layout_definitions);
257 * @covers ::getGroupedDefinitions
259 public function testGetGroupedDefinitions() {
260 $category_expected = [
262 'module_a_provided_layout',
263 'plugin_provided_layout',
266 'theme_a_provided_layout',
270 $definitions = $this->layoutPluginManager->getGroupedDefinitions();
271 $this->assertEquals(array_keys($category_expected), array_keys($definitions));
272 foreach ($category_expected as $category => $expected) {
273 $this->assertArrayHasKey($category, $definitions);
274 $this->assertEquals($expected, array_keys($definitions[$category]));
275 $this->assertContainsOnlyInstancesOf(LayoutDefinition::class, $definitions[$category]);
280 * Sets up the filesystem with YAML files and annotated plugins.
282 protected function setUpFilesystem() {
283 $module_a_provided_layout = <<<'EOS'
284 module_a_provided_layout:
285 label: 1 column layout
286 category: 'Columns: 1'
289 library: module_a/onecol
295 module_a_derived_layout:
296 deriver: \Drupal\Tests\Core\Layout\LayoutDeriver
297 invalid_provider: true
299 $theme_a_provided_layout = <<<'EOS'
300 theme_a_provided_layout:
301 class: '\Drupal\Core\Layout\LayoutDefault'
302 label: 2 column layout
303 category: 'Columns: 2'
306 library: theme_a/twocol
307 default_region: right
314 $plugin_provided_layout = <<<'EOS'
316 namespace Drupal\Core\Plugin\Layout;
317 use Drupal\Core\Layout\LayoutDefault;
320 * id = "plugin_provided_layout",
321 * label = @Translation("Layout plugin"),
322 * category = @Translation("Columns: 1"),
323 * description = @Translation("Test layout"),
324 * path = "core/lib/Drupal/Core",
325 * template = "templates/plugin-provided-layout",
328 * "label" = @Translation("Main Region")
333 class TestLayout extends LayoutDefault {}
335 vfsStream::setup('root');
339 'module_a.layouts.yml' => $module_a_provided_layout,
346 'theme_a.layouts.yml' => $theme_a_provided_layout,
357 'TestLayout.php' => $plugin_provided_layout,
370 * Provides a dynamic layout deriver for the test.
372 class LayoutDeriver extends DeriverBase {
377 public function getDerivativeDefinitions($base_plugin_definition) {
378 if ($base_plugin_definition->get('array_based')) {
379 $this->derivatives['array_based'] = [];
381 if ($base_plugin_definition->get('invalid_provider')) {
382 $this->derivatives['invalid_provider'] = new LayoutDefinition([
383 'id' => 'invalid_provider',
384 'provider' => 'invalid_provider',
387 return $this->derivatives;