3 namespace Drupal\Tests\layout_builder\Functional;
5 use Drupal\node\Entity\Node;
6 use Drupal\Tests\BrowserTestBase;
7 use Drupal\views\Entity\View;
10 * Tests the Layout Builder UI.
12 * @group layout_builder
14 class LayoutBuilderTest extends BrowserTestBase {
19 public static $modules = [
22 'layout_builder_views_test',
27 'layout_builder_test',
33 protected function setUp() {
36 // @todo The Layout Builder UI relies on local tasks; fix in
37 // https://www.drupal.org/project/drupal/issues/2917777.
38 $this->drupalPlaceBlock('local_tasks_block');
41 $this->createContentType(['type' => 'bundle_with_section_field']);
43 'type' => 'bundle_with_section_field',
44 'title' => 'The first node title',
47 'value' => 'The first node body',
52 'type' => 'bundle_with_section_field',
53 'title' => 'The second node title',
56 'value' => 'The second node body',
65 public function testLayoutBuilderUi() {
66 $assert_session = $this->assertSession();
67 $page = $this->getSession()->getPage();
69 $this->drupalLogin($this->drupalCreateUser([
70 'configure any layout',
71 'administer node display',
72 'administer node fields',
75 $this->drupalGet('node/1');
76 $assert_session->pageTextContains('The first node body');
77 $assert_session->pageTextNotContains('Powered by Drupal');
78 $assert_session->linkNotExists('Layout');
80 $field_ui_prefix = 'admin/structure/types/manage/bundle_with_section_field';
82 // From the manage display page, go to manage the layout.
83 $this->drupalGet("$field_ui_prefix/display/default");
84 $assert_session->linkNotExists('Manage layout');
85 $assert_session->fieldDisabled('layout[allow_custom]');
87 $this->drupalPostForm(NULL, ['layout[enabled]' => TRUE], 'Save');
88 $assert_session->linkExists('Manage layout');
89 $this->clickLink('Manage layout');
90 $assert_session->addressEquals("$field_ui_prefix/display-layout/default");
91 // The body field is only present once.
92 $assert_session->elementsCount('css', '.field--name-body', 1);
93 // The extra field is only present once.
94 $assert_session->pageTextContainsOnce('Placeholder for the "Extra label" field');
96 $assert_session->linkExists('Save Layout');
97 $this->clickLink('Save Layout');
98 $assert_session->addressEquals("$field_ui_prefix/display/default");
100 // Load the default layouts again after saving to confirm fields are only
101 // added on new layouts.
102 $this->drupalGet("$field_ui_prefix/display/default");
103 $assert_session->linkExists('Manage layout');
104 $this->clickLink('Manage layout');
105 $assert_session->addressEquals("$field_ui_prefix/display-layout/default");
106 // The body field is only present once.
107 $assert_session->elementsCount('css', '.field--name-body', 1);
108 // The extra field is only present once.
109 $assert_session->pageTextContainsOnce('Placeholder for the "Extra label" field');
112 $assert_session->linkExists('Add Block');
113 $this->clickLink('Add Block');
114 $assert_session->linkExists('Powered by Drupal');
115 $this->clickLink('Powered by Drupal');
116 $page->fillField('settings[label]', 'This is the label');
117 $page->checkField('settings[label_display]');
118 $page->pressButton('Add Block');
119 $assert_session->pageTextContains('Powered by Drupal');
120 $assert_session->pageTextContains('This is the label');
121 $assert_session->addressEquals("$field_ui_prefix/display-layout/default");
123 // Save the defaults.
124 $assert_session->linkExists('Save Layout');
125 $this->clickLink('Save Layout');
126 $assert_session->pageTextContains('The layout has been saved.');
127 $assert_session->addressEquals("$field_ui_prefix/display/default");
129 // The node uses the defaults, no overrides available.
130 $this->drupalGet('node/1');
131 $assert_session->pageTextContains('The first node body');
132 $assert_session->pageTextContains('Powered by Drupal');
133 $assert_session->pageTextContains('Extra, Extra read all about it.');
134 $assert_session->pageTextNotContains('Placeholder for the "Extra label" field');
135 $assert_session->linkNotExists('Layout');
138 $this->drupalPostForm("$field_ui_prefix/display/default", ['layout[allow_custom]' => TRUE], 'Save');
139 $this->drupalGet('node/1');
141 // Remove the section from the defaults.
142 $assert_session->linkExists('Layout');
143 $this->clickLink('Layout');
144 $assert_session->pageTextContains('Placeholder for the "Extra label" field');
145 $assert_session->linkExists('Remove section');
146 $this->clickLink('Remove section');
147 $page->pressButton('Remove');
149 // Add a new section.
150 $this->clickLink('Add Section');
151 $assert_session->linkExists('Two column');
152 $this->clickLink('Two column');
153 $assert_session->linkExists('Save Layout');
154 $this->clickLink('Save Layout');
155 $assert_session->pageTextNotContains('The first node body');
156 $assert_session->pageTextNotContains('Powered by Drupal');
157 $assert_session->pageTextNotContains('Extra, Extra read all about it.');
158 $assert_session->pageTextNotContains('Placeholder for the "Extra label" field');
160 // Assert that overrides cannot be turned off while overrides exist.
161 $this->drupalGet("$field_ui_prefix/display/default");
162 $assert_session->checkboxChecked('layout[allow_custom]');
163 $assert_session->fieldDisabled('layout[allow_custom]');
165 // Alter the defaults.
166 $this->drupalGet("$field_ui_prefix/display-layout/default");
167 $assert_session->linkExists('Add Block');
168 $this->clickLink('Add Block');
169 $assert_session->linkExists('Title');
170 $this->clickLink('Title');
171 $page->pressButton('Add Block');
172 // The title field is present.
173 $assert_session->elementExists('css', '.field--name-title');
174 $this->clickLink('Save Layout');
176 // View the other node, which is still using the defaults.
177 $this->drupalGet('node/2');
178 $assert_session->pageTextContains('The second node title');
179 $assert_session->pageTextContains('The second node body');
180 $assert_session->pageTextContains('Powered by Drupal');
181 $assert_session->pageTextContains('Extra, Extra read all about it.');
182 $assert_session->pageTextNotContains('Placeholder for the "Extra label" field');
184 // The overridden node does not pick up the changes to defaults.
185 $this->drupalGet('node/1');
186 $assert_session->elementNotExists('css', '.field--name-title');
187 $assert_session->pageTextNotContains('The first node body');
188 $assert_session->pageTextNotContains('Powered by Drupal');
189 $assert_session->pageTextNotContains('Extra, Extra read all about it.');
190 $assert_session->pageTextNotContains('Placeholder for the "Extra label" field');
191 $assert_session->linkExists('Layout');
193 // Reverting the override returns it to the defaults.
194 $this->clickLink('Layout');
195 $assert_session->linkExists('Add Block');
196 $this->clickLink('Add Block');
197 $assert_session->linkExists('ID');
198 $this->clickLink('ID');
199 $page->pressButton('Add Block');
200 // The title field is present.
201 $assert_session->elementExists('css', '.field--name-nid');
202 $assert_session->pageTextContains('ID');
203 $assert_session->pageTextContains('1');
204 $assert_session->linkExists('Revert to defaults');
205 $this->clickLink('Revert to defaults');
206 $page->pressButton('Revert');
207 $assert_session->pageTextContains('The layout has been reverted back to defaults.');
208 $assert_session->elementExists('css', '.field--name-title');
209 $assert_session->elementNotExists('css', '.field--name-nid');
210 $assert_session->pageTextContains('The first node body');
211 $assert_session->pageTextContains('Powered by Drupal');
212 $assert_session->pageTextContains('Placeholder for the "Extra label" field');
214 // Assert that overrides can be turned off now that all overrides are gone.
215 $this->drupalPostForm("$field_ui_prefix/display/default", ['layout[allow_custom]' => FALSE], 'Save');
216 $this->drupalGet('node/1');
217 $assert_session->linkNotExists('Layout');
221 'new_storage_type' => 'string',
222 'label' => 'My text field',
223 'field_name' => 'my_text',
225 $this->drupalPostForm("$field_ui_prefix/fields/add-field", $edit, 'Save and continue');
226 $page->pressButton('Save field settings');
227 $page->pressButton('Save settings');
228 $this->drupalGet("$field_ui_prefix/display-layout/default");
229 $assert_session->pageTextContains('My text field');
230 $assert_session->elementExists('css', '.field--name-field-my-text');
233 $this->drupalPostForm("$field_ui_prefix/fields/node.bundle_with_section_field.field_my_text/delete", [], 'Delete');
234 $this->drupalGet("$field_ui_prefix/display-layout/default");
235 $assert_session->pageTextNotContains('My text field');
236 $assert_session->elementNotExists('css', '.field--name-field-my-text');
240 * Tests that a non-default view mode works as expected.
242 public function testNonDefaultViewMode() {
243 $assert_session = $this->assertSession();
244 $page = $this->getSession()->getPage();
246 $this->drupalLogin($this->drupalCreateUser([
247 'configure any layout',
248 'administer node display',
251 $field_ui_prefix = 'admin/structure/types/manage/bundle_with_section_field';
252 // Allow overrides for the layout.
253 $this->drupalGet("$field_ui_prefix/display/default");
254 $page->checkField('layout[enabled]');
255 $page->pressButton('Save');
256 $page->checkField('layout[allow_custom]');
257 $page->pressButton('Save');
259 $this->clickLink('Manage layout');
260 // Confirm the body field only is shown once.
261 $assert_session->elementsCount('css', '.field--name-body', 1);
262 $this->clickLink('Cancel Layout');
264 $this->clickLink('Teaser');
265 // Enabling Layout Builder for the default mode does not affect the teaser.
266 $assert_session->addressEquals("$field_ui_prefix/display/teaser");
267 $assert_session->elementNotExists('css', '#layout-builder__layout');
268 $assert_session->checkboxNotChecked('layout[enabled]');
269 $page->checkField('layout[enabled]');
270 $page->pressButton('Save');
271 $assert_session->linkExists('Manage layout');
272 $page->clickLink('Manage layout');
273 // Confirm the body field only is shown once.
274 $assert_session->elementsCount('css', '.field--name-body', 1);
276 // Enable a disabled view mode.
277 $page->clickLink('Cancel Layout');
278 $assert_session->addressEquals("$field_ui_prefix/display/teaser");
279 $page->clickLink('Default');
280 $assert_session->addressEquals("$field_ui_prefix/display");
281 $assert_session->linkNotExists('Full content');
282 $page->checkField('display_modes_custom[full]');
283 $page->pressButton('Save');
285 $assert_session->linkExists('Full content');
286 $page->clickLink('Full content');
287 $assert_session->addressEquals("$field_ui_prefix/display/full");
288 $page->checkField('layout[enabled]');
289 $page->pressButton('Save');
290 $assert_session->linkExists('Manage layout');
291 $page->clickLink('Manage layout');
292 // Confirm the body field only is shown once.
293 $assert_session->elementsCount('css', '.field--name-body', 1);
297 * Tests that component's dependencies are respected during removal.
299 public function testPluginDependencies() {
300 $assert_session = $this->assertSession();
301 $page = $this->getSession()->getPage();
303 $this->container->get('module_installer')->install(['menu_ui']);
304 $this->drupalLogin($this->drupalCreateUser([
305 'configure any layout',
306 'administer node display',
310 // Create a new menu.
311 $this->drupalGet('admin/structure/menu/add');
312 $page->fillField('label', 'My Menu');
313 $page->fillField('id', 'mymenu');
314 $page->pressButton('Save');
315 $this->drupalGet('admin/structure/menu/add');
316 $page->fillField('label', 'My Menu');
317 $page->fillField('id', 'myothermenu');
318 $page->pressButton('Save');
320 $this->drupalPostForm('admin/structure/types/manage/bundle_with_section_field/display', ['layout[enabled]' => TRUE], 'Save');
321 $assert_session->linkExists('Manage layout');
322 $this->clickLink('Manage layout');
323 $assert_session->linkExists('Add Section');
324 $this->clickLink('Add Section');
325 $assert_session->linkExists('Layout plugin (with dependencies)');
326 $this->clickLink('Layout plugin (with dependencies)');
327 $assert_session->elementExists('css', '.layout--layout-test-dependencies-plugin');
328 $assert_session->elementExists('css', '.field--name-body');
329 $assert_session->linkExists('Save Layout');
330 $this->clickLink('Save Layout');
331 $this->drupalPostForm('admin/structure/menu/manage/myothermenu/delete', [], 'Delete');
332 $this->drupalGet('admin/structure/types/manage/bundle_with_section_field/display-layout/default');
333 $assert_session->elementNotExists('css', '.layout--layout-test-dependencies-plugin');
334 $assert_session->elementExists('css', '.field--name-body');
337 $assert_session->linkExists('Add Block');
338 $this->clickLink('Add Block');
339 $assert_session->linkExists('My Menu');
340 $this->clickLink('My Menu');
341 $page->pressButton('Add Block');
343 // Add another block alongside the menu.
344 $assert_session->linkExists('Add Block');
345 $this->clickLink('Add Block');
346 $assert_session->linkExists('Powered by Drupal');
347 $this->clickLink('Powered by Drupal');
348 $page->pressButton('Add Block');
350 // Assert that the blocks are visible, and save the layout.
351 $assert_session->pageTextContains('Powered by Drupal');
352 $assert_session->pageTextContains('My Menu');
353 $assert_session->elementExists('css', '.block.menu--mymenu');
354 $assert_session->linkExists('Save Layout');
355 $this->clickLink('Save Layout');
358 $this->drupalPostForm('admin/structure/menu/manage/mymenu/delete', [], 'Delete');
360 // Ensure that the menu block is gone, but that the other block remains.
361 $this->drupalGet('admin/structure/types/manage/bundle_with_section_field/display-layout/default');
362 $assert_session->pageTextContains('Powered by Drupal');
363 $assert_session->pageTextNotContains('My Menu');
364 $assert_session->elementNotExists('css', '.block.menu--mymenu');
368 * Tests the interaction between full and default view modes.
370 * @see \Drupal\layout_builder\Plugin\SectionStorage\OverridesSectionStorage::getDefaultSectionStorage()
372 public function testLayoutBuilderUiFullViewMode() {
373 $assert_session = $this->assertSession();
374 $page = $this->getSession()->getPage();
376 $this->drupalLogin($this->drupalCreateUser([
377 'configure any layout',
378 'administer node display',
379 'administer node fields',
382 $field_ui_prefix = 'admin/structure/types/manage/bundle_with_section_field';
383 // Allow overrides for the layout.
384 $this->drupalPostForm("$field_ui_prefix/display/default", ['layout[enabled]' => TRUE], 'Save');
385 $this->drupalPostForm("$field_ui_prefix/display/default", ['layout[allow_custom]' => TRUE], 'Save');
387 // Customize the default view mode.
388 $this->drupalGet("$field_ui_prefix/display-layout/default");
389 $this->clickLink('Add Block');
390 $this->clickLink('Powered by Drupal');
391 $page->fillField('settings[label]', 'This is the default view mode');
392 $page->checkField('settings[label_display]');
393 $page->pressButton('Add Block');
394 $assert_session->pageTextContains('This is the default view mode');
395 $this->clickLink('Save Layout');
397 // The default view mode is used for both the node display and layout UI.
398 $this->drupalGet('node/1');
399 $assert_session->pageTextContains('This is the default view mode');
400 $this->drupalGet('node/1/layout');
401 $assert_session->pageTextContains('This is the default view mode');
402 $this->clickLink('Cancel Layout');
404 // Enable the full view mode and customize it.
405 $this->drupalPostForm("$field_ui_prefix/display/default", ['display_modes_custom[full]' => TRUE], 'Save');
406 $this->drupalPostForm("$field_ui_prefix/display/full", ['layout[enabled]' => TRUE], 'Save');
407 $this->drupalGet("$field_ui_prefix/display-layout/full");
408 $this->clickLink('Add Block');
409 $this->clickLink('Powered by Drupal');
410 $page->fillField('settings[label]', 'This is the full view mode');
411 $page->checkField('settings[label_display]');
412 $page->pressButton('Add Block');
413 $assert_session->pageTextContains('This is the full view mode');
414 $this->clickLink('Save Layout');
416 // The full view mode is now used for both the node display and layout UI.
417 $this->drupalGet('node/1');
418 $assert_session->pageTextContains('This is the full view mode');
419 $this->drupalGet('node/1/layout');
420 $assert_session->pageTextContains('This is the full view mode');
421 $this->clickLink('Cancel Layout');
423 // Disable the full view mode, the default should be used again.
424 $this->drupalPostForm("$field_ui_prefix/display/default", ['display_modes_custom[full]' => FALSE], 'Save');
425 $this->drupalGet('node/1');
426 $assert_session->pageTextContains('This is the default view mode');
427 $this->drupalGet('node/1/layout');
428 $assert_session->pageTextContains('This is the default view mode');
429 $this->clickLink('Cancel Layout');
435 public function testLayoutBuilderChooseBlocksAlter() {
436 // See layout_builder_test_plugin_filter_block__layout_builder_alter().
437 $assert_session = $this->assertSession();
439 $this->drupalLogin($this->drupalCreateUser([
440 'configure any layout',
441 'administer node display',
442 'administer node fields',
445 // From the manage display page, go to manage the layout.
446 $this->drupalPostForm('admin/structure/types/manage/bundle_with_section_field/display/default', ['layout[enabled]' => TRUE], 'Save');
447 $assert_session->linkExists('Manage layout');
448 $this->clickLink('Manage layout');
451 $this->clickLink('Add Block');
453 // Verify that blocks not modified are present.
454 $assert_session->linkExists('Powered by Drupal');
455 $assert_session->linkExists('Default revision');
457 // Verify that blocks explicitly removed are not present.
458 $assert_session->linkNotExists('Help');
459 $assert_session->linkNotExists('Sticky at top of lists');
461 // Verify that Changed block is not present on first section.
462 $assert_session->linkNotExists('Changed');
464 // Go back to Manage layout.
465 $this->drupalGet('admin/structure/types/manage/bundle_with_section_field/display/default');
466 $this->clickLink('Manage layout');
468 // Add a new section.
469 $this->clickLink('Add Section', 1);
470 $assert_session->linkExists('Two column');
471 $this->clickLink('Two column');
473 // Add a new block to second section.
474 $this->clickLink('Add Block', 1);
476 // Verify that Changed block is present on second section.
477 $assert_session->linkExists('Changed');
481 * Tests that deleting a View block used in Layout Builder works.
483 public function testDeletedView() {
484 $assert_session = $this->assertSession();
485 $page = $this->getSession()->getPage();
487 $this->drupalLogin($this->drupalCreateUser([
488 'configure any layout',
489 'administer node display',
492 $field_ui_prefix = 'admin/structure/types/manage/bundle_with_section_field';
494 $this->drupalPostForm("$field_ui_prefix/display/default", ['layout[enabled]' => TRUE], 'Save');
495 $this->drupalPostForm("$field_ui_prefix/display/default", ['layout[allow_custom]' => TRUE], 'Save');
496 $this->drupalGet('node/1');
498 $assert_session->linkExists('Layout');
499 $this->clickLink('Layout');
500 $this->clickLink('Add Block');
501 $this->clickLink('Test Block View');
502 $page->pressButton('Add Block');
504 $assert_session->pageTextContains('Test Block View');
505 $assert_session->elementExists('css', '.block-views-blocktest-block-view-block-1');
506 $this->clickLink('Save Layout');
507 $assert_session->pageTextContains('Test Block View');
508 $assert_session->elementExists('css', '.block-views-blocktest-block-view-block-1');
510 View::load('test_block_view')->delete();
511 $this->drupalGet('node/1');
512 // Node can be loaded after deleting the View.
513 $assert_session->pageTextContains(Node::load(1)->getTitle());
514 $assert_session->pageTextNotContains('Test Block View');
518 * Tests the usage of placeholders for empty blocks.
520 * @see \Drupal\Core\Block\BlockPluginInterface::getPlaceholderString()
521 * @see \Drupal\layout_builder\EventSubscriber\BlockComponentRenderArray::onBuildRender()
523 public function testBlockPlaceholder() {
524 $assert_session = $this->assertSession();
525 $page = $this->getSession()->getPage();
527 $this->drupalLogin($this->drupalCreateUser([
528 'configure any layout',
529 'administer node display',
532 $field_ui_prefix = 'admin/structure/types/manage/bundle_with_section_field';
533 $this->drupalPostForm("$field_ui_prefix/display/default", ['layout[enabled]' => TRUE], 'Save');
535 // Customize the default view mode.
536 $this->drupalGet("$field_ui_prefix/display-layout/default");
538 // Add a block whose content is controlled by state and is empty by default.
539 $this->clickLink('Add Block');
540 $this->clickLink('Test block caching');
541 $page->fillField('settings[label]', 'The block label');
542 $page->pressButton('Add Block');
544 $block_content = 'I am content';
545 $placeholder_content = 'Placeholder for the "The block label" block';
547 // The block placeholder is displayed and there is no content.
548 $assert_session->pageTextContains($placeholder_content);
549 $assert_session->pageTextNotContains($block_content);
551 // Set block content and reload the page.
552 \Drupal::state()->set('block_test.content', $block_content);
553 $this->getSession()->reload();
555 // The block placeholder is no longer displayed and the content is visible.
556 $assert_session->pageTextNotContains($placeholder_content);
557 $assert_session->pageTextContains($block_content);
561 * Tests the Block UI when Layout Builder is installed.
563 public function testBlockUiListing() {
564 $assert_session = $this->assertSession();
565 $page = $this->getSession()->getPage();
567 $this->drupalLogin($this->drupalCreateUser([
571 $this->drupalGet('admin/structure/block');
572 $page->clickLink('Place block');
574 // Ensure that blocks expected to appear are available.
575 $assert_session->pageTextContains('Test HTML block');
576 $assert_session->pageTextContains('Block test');
577 // Ensure that blocks not expected to appear are not available.
578 $assert_session->pageTextNotContains('Body');
579 $assert_session->pageTextNotContains('Content fields');