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',
26 'layout_builder_test',
32 protected function setUp() {
35 // @todo The Layout Builder UI relies on local tasks; fix in
36 // https://www.drupal.org/project/drupal/issues/2917777.
37 $this->drupalPlaceBlock('local_tasks_block');
40 $this->createContentType(['type' => 'bundle_with_section_field']);
42 'type' => 'bundle_with_section_field',
43 'title' => 'The first node title',
46 'value' => 'The first node body',
51 'type' => 'bundle_with_section_field',
52 'title' => 'The second node title',
55 'value' => 'The second node body',
64 public function testLayoutBuilderUi() {
65 $assert_session = $this->assertSession();
66 $page = $this->getSession()->getPage();
68 $this->drupalLogin($this->drupalCreateUser([
69 'configure any layout',
70 'administer node display',
71 'administer node fields',
74 $this->drupalGet('node/1');
75 $assert_session->pageTextContains('The first node body');
76 $assert_session->pageTextNotContains('Powered by Drupal');
77 $assert_session->linkNotExists('Layout');
79 $field_ui_prefix = 'admin/structure/types/manage/bundle_with_section_field';
81 // From the manage display page, go to manage the layout.
82 $this->drupalGet("$field_ui_prefix/display/default");
83 $assert_session->linkNotExists('Manage layout');
84 $assert_session->fieldDisabled('layout[allow_custom]');
86 $this->drupalPostForm(NULL, ['layout[enabled]' => TRUE], 'Save');
87 $assert_session->linkExists('Manage layout');
88 $this->clickLink('Manage layout');
89 $assert_session->addressEquals("$field_ui_prefix/display-layout/default");
90 // The body field is only present once.
91 $assert_session->elementsCount('css', '.field--name-body', 1);
92 // The extra field is only present once.
93 $this->assertTextAppearsOnce('Placeholder for the "Extra label" field');
95 $assert_session->linkExists('Save Layout');
96 $this->clickLink('Save Layout');
97 $assert_session->addressEquals("$field_ui_prefix/display/default");
99 // Load the default layouts again after saving to confirm fields are only
100 // added on new layouts.
101 $this->drupalGet("$field_ui_prefix/display/default");
102 $assert_session->linkExists('Manage layout');
103 $this->clickLink('Manage layout');
104 $assert_session->addressEquals("$field_ui_prefix/display-layout/default");
105 // The body field is only present once.
106 $assert_session->elementsCount('css', '.field--name-body', 1);
107 // The extra field is only present once.
108 $this->assertTextAppearsOnce('Placeholder for the "Extra label" field');
111 $assert_session->linkExists('Add Block');
112 $this->clickLink('Add Block');
113 $assert_session->linkExists('Powered by Drupal');
114 $this->clickLink('Powered by Drupal');
115 $page->fillField('settings[label]', 'This is the label');
116 $page->checkField('settings[label_display]');
117 $page->pressButton('Add Block');
118 $assert_session->pageTextContains('Powered by Drupal');
119 $assert_session->pageTextContains('This is the label');
120 $assert_session->addressEquals("$field_ui_prefix/display-layout/default");
122 // Save the defaults.
123 $assert_session->linkExists('Save Layout');
124 $this->clickLink('Save Layout');
125 $assert_session->pageTextContains('The layout has been saved.');
126 $assert_session->addressEquals("$field_ui_prefix/display/default");
128 // The node uses the defaults, no overrides available.
129 $this->drupalGet('node/1');
130 $assert_session->pageTextContains('The first node body');
131 $assert_session->pageTextContains('Powered by Drupal');
132 $assert_session->pageTextContains('Extra, Extra read all about it.');
133 $assert_session->pageTextNotContains('Placeholder for the "Extra label" field');
134 $assert_session->linkNotExists('Layout');
137 $this->drupalPostForm("$field_ui_prefix/display/default", ['layout[allow_custom]' => TRUE], 'Save');
138 $this->drupalGet('node/1');
140 // Remove the section from the defaults.
141 $assert_session->linkExists('Layout');
142 $this->clickLink('Layout');
143 $assert_session->pageTextContains('Placeholder for the "Extra label" field');
144 $assert_session->linkExists('Remove section');
145 $this->clickLink('Remove section');
146 $page->pressButton('Remove');
148 // Add a new section.
149 $this->clickLink('Add Section');
150 $assert_session->linkExists('Two column');
151 $this->clickLink('Two column');
152 $assert_session->linkExists('Save Layout');
153 $this->clickLink('Save Layout');
154 $assert_session->pageTextNotContains('The first node body');
155 $assert_session->pageTextNotContains('Powered by Drupal');
156 $assert_session->pageTextNotContains('Extra, Extra read all about it.');
157 $assert_session->pageTextNotContains('Placeholder for the "Extra label" field');
159 // Assert that overrides cannot be turned off while overrides exist.
160 $this->drupalGet("$field_ui_prefix/display/default");
161 $assert_session->checkboxChecked('layout[allow_custom]');
162 $assert_session->fieldDisabled('layout[allow_custom]');
164 // Alter the defaults.
165 $this->drupalGet("$field_ui_prefix/display-layout/default");
166 $assert_session->linkExists('Add Block');
167 $this->clickLink('Add Block');
168 $assert_session->linkExists('Title');
169 $this->clickLink('Title');
170 $page->pressButton('Add Block');
171 // The title field is present.
172 $assert_session->elementExists('css', '.field--name-title');
173 $this->clickLink('Save Layout');
175 // View the other node, which is still using the defaults.
176 $this->drupalGet('node/2');
177 $assert_session->pageTextContains('The second node title');
178 $assert_session->pageTextContains('The second node body');
179 $assert_session->pageTextContains('Powered by Drupal');
180 $assert_session->pageTextContains('Extra, Extra read all about it.');
181 $assert_session->pageTextNotContains('Placeholder for the "Extra label" field');
183 // The overridden node does not pick up the changes to defaults.
184 $this->drupalGet('node/1');
185 $assert_session->elementNotExists('css', '.field--name-title');
186 $assert_session->pageTextNotContains('The first node body');
187 $assert_session->pageTextNotContains('Powered by Drupal');
188 $assert_session->pageTextNotContains('Extra, Extra read all about it.');
189 $assert_session->pageTextNotContains('Placeholder for the "Extra label" field');
190 $assert_session->linkExists('Layout');
192 // Reverting the override returns it to the defaults.
193 $this->clickLink('Layout');
194 $assert_session->linkExists('Add Block');
195 $this->clickLink('Add Block');
196 $assert_session->linkExists('ID');
197 $this->clickLink('ID');
198 $page->pressButton('Add Block');
199 // The title field is present.
200 $assert_session->elementExists('css', '.field--name-nid');
201 $assert_session->pageTextContains('ID');
202 $assert_session->pageTextContains('1');
203 $assert_session->linkExists('Revert to defaults');
204 $this->clickLink('Revert to defaults');
205 $page->pressButton('Revert');
206 $assert_session->pageTextContains('The layout has been reverted back to defaults.');
207 $assert_session->elementExists('css', '.field--name-title');
208 $assert_session->elementNotExists('css', '.field--name-nid');
209 $assert_session->pageTextContains('The first node body');
210 $assert_session->pageTextContains('Powered by Drupal');
211 $assert_session->pageTextContains('Placeholder for the "Extra label" field');
213 // Assert that overrides can be turned off now that all overrides are gone.
214 $this->drupalPostForm("$field_ui_prefix/display/default", ['layout[allow_custom]' => FALSE], 'Save');
215 $this->drupalGet('node/1');
216 $assert_session->linkNotExists('Layout');
220 'new_storage_type' => 'string',
221 'label' => 'My text field',
222 'field_name' => 'my_text',
224 $this->drupalPostForm("$field_ui_prefix/fields/add-field", $edit, 'Save and continue');
225 $page->pressButton('Save field settings');
226 $page->pressButton('Save settings');
227 $this->drupalGet("$field_ui_prefix/display-layout/default");
228 $assert_session->pageTextContains('My text field');
229 $assert_session->elementExists('css', '.field--name-field-my-text');
232 $this->drupalPostForm("$field_ui_prefix/fields/node.bundle_with_section_field.field_my_text/delete", [], 'Delete');
233 $this->drupalGet("$field_ui_prefix/display-layout/default");
234 $assert_session->pageTextNotContains('My text field');
235 $assert_session->elementNotExists('css', '.field--name-field-my-text');
239 * Tests that a non-default view mode works as expected.
241 public function testNonDefaultViewMode() {
242 $assert_session = $this->assertSession();
243 $page = $this->getSession()->getPage();
245 $this->drupalLogin($this->drupalCreateUser([
246 'configure any layout',
247 'administer node display',
250 $field_ui_prefix = 'admin/structure/types/manage/bundle_with_section_field';
251 // Allow overrides for the layout.
252 $this->drupalGet("$field_ui_prefix/display/default");
253 $page->checkField('layout[enabled]');
254 $page->pressButton('Save');
255 $page->checkField('layout[allow_custom]');
256 $page->pressButton('Save');
258 $this->clickLink('Manage layout');
259 // Confirm the body field only is shown once.
260 $assert_session->elementsCount('css', '.field--name-body', 1);
261 $this->clickLink('Cancel Layout');
263 $this->clickLink('Teaser');
264 // Enabling Layout Builder for the default mode does not affect the teaser.
265 $assert_session->addressEquals("$field_ui_prefix/display/teaser");
266 $assert_session->elementNotExists('css', '#layout-builder__layout');
267 $assert_session->checkboxNotChecked('layout[enabled]');
268 $page->checkField('layout[enabled]');
269 $page->pressButton('Save');
270 $assert_session->linkExists('Manage layout');
271 $page->clickLink('Manage layout');
272 // Confirm the body field only is shown once.
273 $assert_session->elementsCount('css', '.field--name-body', 1);
275 // Enable a disabled view mode.
276 $page->clickLink('Cancel Layout');
277 $assert_session->addressEquals("$field_ui_prefix/display/teaser");
278 $page->clickLink('Default');
279 $assert_session->addressEquals("$field_ui_prefix/display");
280 $assert_session->linkNotExists('Full content');
281 $page->checkField('display_modes_custom[full]');
282 $page->pressButton('Save');
284 $assert_session->linkExists('Full content');
285 $page->clickLink('Full content');
286 $assert_session->addressEquals("$field_ui_prefix/display/full");
287 $page->checkField('layout[enabled]');
288 $page->pressButton('Save');
289 $assert_session->linkExists('Manage layout');
290 $page->clickLink('Manage layout');
291 // Confirm the body field only is shown once.
292 $assert_session->elementsCount('css', '.field--name-body', 1);
296 * Tests that component's dependencies are respected during removal.
298 public function testPluginDependencies() {
299 $assert_session = $this->assertSession();
300 $page = $this->getSession()->getPage();
302 $this->container->get('module_installer')->install(['menu_ui']);
303 $this->drupalLogin($this->drupalCreateUser([
304 'configure any layout',
305 'administer node display',
309 // Create a new menu.
310 $this->drupalGet('admin/structure/menu/add');
311 $page->fillField('label', 'My Menu');
312 $page->fillField('id', 'mymenu');
313 $page->pressButton('Save');
314 $this->drupalGet('admin/structure/menu/add');
315 $page->fillField('label', 'My Menu');
316 $page->fillField('id', 'myothermenu');
317 $page->pressButton('Save');
319 $this->drupalPostForm('admin/structure/types/manage/bundle_with_section_field/display', ['layout[enabled]' => TRUE], 'Save');
320 $assert_session->linkExists('Manage layout');
321 $this->clickLink('Manage layout');
322 $assert_session->linkExists('Add Section');
323 $this->clickLink('Add Section');
324 $assert_session->linkExists('Layout plugin (with dependencies)');
325 $this->clickLink('Layout plugin (with dependencies)');
326 $assert_session->elementExists('css', '.layout--layout-test-dependencies-plugin');
327 $assert_session->elementExists('css', '.field--name-body');
328 $assert_session->linkExists('Save Layout');
329 $this->clickLink('Save Layout');
330 $this->drupalPostForm('admin/structure/menu/manage/myothermenu/delete', [], 'Delete');
331 $this->drupalGet('admin/structure/types/manage/bundle_with_section_field/display-layout/default');
332 $assert_session->elementNotExists('css', '.layout--layout-test-dependencies-plugin');
333 $assert_session->elementExists('css', '.field--name-body');
336 $assert_session->linkExists('Add Block');
337 $this->clickLink('Add Block');
338 $assert_session->linkExists('My Menu');
339 $this->clickLink('My Menu');
340 $page->pressButton('Add Block');
342 // Add another block alongside the menu.
343 $assert_session->linkExists('Add Block');
344 $this->clickLink('Add Block');
345 $assert_session->linkExists('Powered by Drupal');
346 $this->clickLink('Powered by Drupal');
347 $page->pressButton('Add Block');
349 // Assert that the blocks are visible, and save the layout.
350 $assert_session->pageTextContains('Powered by Drupal');
351 $assert_session->pageTextContains('My Menu');
352 $assert_session->elementExists('css', '.block.menu--mymenu');
353 $assert_session->linkExists('Save Layout');
354 $this->clickLink('Save Layout');
357 $this->drupalPostForm('admin/structure/menu/manage/mymenu/delete', [], 'Delete');
359 // Ensure that the menu block is gone, but that the other block remains.
360 $this->drupalGet('admin/structure/types/manage/bundle_with_section_field/display-layout/default');
361 $assert_session->pageTextContains('Powered by Drupal');
362 $assert_session->pageTextNotContains('My Menu');
363 $assert_session->elementNotExists('css', '.block.menu--mymenu');
367 * Tests the interaction between full and default view modes.
369 * @see \Drupal\layout_builder\Plugin\SectionStorage\OverridesSectionStorage::getDefaultSectionStorage()
371 public function testLayoutBuilderUiFullViewMode() {
372 $assert_session = $this->assertSession();
373 $page = $this->getSession()->getPage();
375 $this->drupalLogin($this->drupalCreateUser([
376 'configure any layout',
377 'administer node display',
378 'administer node fields',
381 $field_ui_prefix = 'admin/structure/types/manage/bundle_with_section_field';
382 // Allow overrides for the layout.
383 $this->drupalPostForm("$field_ui_prefix/display/default", ['layout[enabled]' => TRUE], 'Save');
384 $this->drupalPostForm("$field_ui_prefix/display/default", ['layout[allow_custom]' => TRUE], 'Save');
386 // Customize the default view mode.
387 $this->drupalGet("$field_ui_prefix/display-layout/default");
388 $this->clickLink('Add Block');
389 $this->clickLink('Powered by Drupal');
390 $page->fillField('settings[label]', 'This is the default view mode');
391 $page->checkField('settings[label_display]');
392 $page->pressButton('Add Block');
393 $assert_session->pageTextContains('This is the default view mode');
394 $this->clickLink('Save Layout');
396 // The default view mode is used for both the node display and layout UI.
397 $this->drupalGet('node/1');
398 $assert_session->pageTextContains('This is the default view mode');
399 $this->drupalGet('node/1/layout');
400 $assert_session->pageTextContains('This is the default view mode');
401 $this->clickLink('Cancel Layout');
403 // Enable the full view mode and customize it.
404 $this->drupalPostForm("$field_ui_prefix/display/default", ['display_modes_custom[full]' => TRUE], 'Save');
405 $this->drupalPostForm("$field_ui_prefix/display/full", ['layout[enabled]' => TRUE], 'Save');
406 $this->drupalGet("$field_ui_prefix/display-layout/full");
407 $this->clickLink('Add Block');
408 $this->clickLink('Powered by Drupal');
409 $page->fillField('settings[label]', 'This is the full view mode');
410 $page->checkField('settings[label_display]');
411 $page->pressButton('Add Block');
412 $assert_session->pageTextContains('This is the full view mode');
413 $this->clickLink('Save Layout');
415 // The full view mode is now used for both the node display and layout UI.
416 $this->drupalGet('node/1');
417 $assert_session->pageTextContains('This is the full view mode');
418 $this->drupalGet('node/1/layout');
419 $assert_session->pageTextContains('This is the full view mode');
420 $this->clickLink('Cancel Layout');
422 // Disable the full view mode, the default should be used again.
423 $this->drupalPostForm("$field_ui_prefix/display/default", ['display_modes_custom[full]' => FALSE], 'Save');
424 $this->drupalGet('node/1');
425 $assert_session->pageTextContains('This is the default view mode');
426 $this->drupalGet('node/1/layout');
427 $assert_session->pageTextContains('This is the default view mode');
428 $this->clickLink('Cancel Layout');
434 public function testLayoutBuilderChooseBlocksAlter() {
435 // See layout_builder_test_plugin_filter_block__layout_builder_alter().
436 $assert_session = $this->assertSession();
438 $this->drupalLogin($this->drupalCreateUser([
439 'configure any layout',
440 'administer node display',
441 'administer node fields',
444 // From the manage display page, go to manage the layout.
445 $this->drupalPostForm('admin/structure/types/manage/bundle_with_section_field/display/default', ['layout[enabled]' => TRUE], 'Save');
446 $assert_session->linkExists('Manage layout');
447 $this->clickLink('Manage layout');
450 $this->clickLink('Add Block');
452 // Verify that blocks not modified are present.
453 $assert_session->linkExists('Powered by Drupal');
454 $assert_session->linkExists('Default revision');
456 // Verify that blocks explicitly removed are not present.
457 $assert_session->linkNotExists('Help');
458 $assert_session->linkNotExists('Sticky at top of lists');
460 // Verify that Changed block is not present on first section.
461 $assert_session->linkNotExists('Changed');
463 // Go back to Manage layout.
464 $this->drupalGet('admin/structure/types/manage/bundle_with_section_field/display/default');
465 $this->clickLink('Manage layout');
467 // Add a new section.
468 $this->clickLink('Add Section', 1);
469 $assert_session->linkExists('Two column');
470 $this->clickLink('Two column');
472 // Add a new block to second section.
473 $this->clickLink('Add Block', 1);
475 // Verify that Changed block is present on second section.
476 $assert_session->linkExists('Changed');
480 * Tests that deleting a View block used in Layout Builder works.
482 public function testDeletedView() {
483 $assert_session = $this->assertSession();
484 $page = $this->getSession()->getPage();
486 $this->drupalLogin($this->drupalCreateUser([
487 'configure any layout',
488 'administer node display',
491 $field_ui_prefix = 'admin/structure/types/manage/bundle_with_section_field';
493 $this->drupalPostForm("$field_ui_prefix/display/default", ['layout[enabled]' => TRUE], 'Save');
494 $this->drupalPostForm("$field_ui_prefix/display/default", ['layout[allow_custom]' => TRUE], 'Save');
495 $this->drupalGet('node/1');
497 $assert_session->linkExists('Layout');
498 $this->clickLink('Layout');
499 $this->clickLink('Add Block');
500 $this->clickLink('Test Block View');
501 $page->pressButton('Add Block');
503 $assert_session->pageTextContains('Test Block View');
504 $assert_session->elementExists('css', '.block-views-blocktest-block-view-block-1');
505 $this->clickLink('Save Layout');
506 $assert_session->pageTextContains('Test Block View');
507 $assert_session->elementExists('css', '.block-views-blocktest-block-view-block-1');
509 View::load('test_block_view')->delete();
510 $this->drupalGet('node/1');
511 // Node can be loaded after deleting the View.
512 $assert_session->pageTextContains(Node::load(1)->getTitle());
513 $assert_session->pageTextNotContains('Test Block View');
517 * Asserts that a text string only appears once on the page.
519 * @param string $needle
520 * The string to look for.
522 protected function assertTextAppearsOnce($needle) {
523 $this->assertEquals(1, substr_count($this->getSession()->getPage()->getContent(), $needle), "'$needle' only appears once on the page.");