3 namespace Drupal\Tests\layout_builder\FunctionalJavascript;
5 use Drupal\node\Entity\Node;
8 * Tests that the inline block feature works correctly.
10 * @group layout_builder
12 class InlineBlockTest extends InlineBlockTestBase {
15 * Tests adding and editing of inline blocks.
17 public function testInlineBlocks() {
18 $assert_session = $this->assertSession();
19 $page = $this->getSession()->getPage();
21 $this->drupalLogin($this->drupalCreateUser([
22 'access contextual links',
23 'configure any layout',
24 'administer node display',
25 'administer node fields',
28 // Enable layout builder.
29 $this->drupalPostForm(
30 static::FIELD_UI_PREFIX . '/display/default',
31 ['layout[enabled]' => TRUE],
34 $this->clickLink('Manage layout');
35 $assert_session->addressEquals(static::FIELD_UI_PREFIX . '/display-layout/default');
36 // Add a basic block with the body field set.
37 $this->addInlineBlockToLayout('Block title', 'The DEFAULT block body');
38 $this->assertSaveLayout();
40 $this->drupalGet('node/1');
41 $assert_session->pageTextContains('The DEFAULT block body');
42 $this->drupalGet('node/2');
43 $assert_session->pageTextContains('The DEFAULT block body');
46 $this->drupalPostForm(static::FIELD_UI_PREFIX . '/display/default', ['layout[allow_custom]' => TRUE], 'Save');
47 $this->drupalGet('node/1/layout');
49 // Confirm the block can be edited.
50 $this->drupalGet('node/1/layout');
51 $this->configureInlineBlock('The DEFAULT block body', 'The NEW block body!');
52 $this->assertSaveLayout();
53 $this->drupalGet('node/1');
54 $assert_session->pageTextContains('The NEW block body');
55 $assert_session->pageTextNotContains('The DEFAULT block body');
56 $this->drupalGet('node/2');
57 // Node 2 should use default layout.
58 $assert_session->pageTextContains('The DEFAULT block body');
59 $assert_session->pageTextNotContains('The NEW block body');
61 // Add a basic block with the body field set.
62 $this->drupalGet('node/1/layout');
63 $this->addInlineBlockToLayout('2nd Block title', 'The 2nd block body');
64 $this->assertSaveLayout();
65 $this->drupalGet('node/1');
66 $assert_session->pageTextContains('The NEW block body!');
67 $assert_session->pageTextContains('The 2nd block body');
68 $this->drupalGet('node/2');
69 // Node 2 should use default layout.
70 $assert_session->pageTextContains('The DEFAULT block body');
71 $assert_session->pageTextNotContains('The NEW block body');
72 $assert_session->pageTextNotContains('The 2nd block body');
74 // Confirm the block can be edited.
75 $this->drupalGet('node/1/layout');
76 /* @var \Behat\Mink\Element\NodeElement $inline_block_2 */
77 $inline_block_2 = $page->findAll('css', static::INLINE_BLOCK_LOCATOR)[1];
78 $uuid = $inline_block_2->getAttribute('data-layout-block-uuid');
79 $block_css_locator = static::INLINE_BLOCK_LOCATOR . "[data-layout-block-uuid=\"$uuid\"]";
80 $this->configureInlineBlock('The 2nd block body', 'The 2nd NEW block body!', $block_css_locator);
81 $this->assertSaveLayout();
82 $this->drupalGet('node/1');
83 $assert_session->pageTextContains('The NEW block body!');
84 $assert_session->pageTextContains('The 2nd NEW block body!');
85 $this->drupalGet('node/2');
86 // Node 2 should use default layout.
87 $assert_session->pageTextContains('The DEFAULT block body');
88 $assert_session->pageTextNotContains('The NEW block body!');
89 $assert_session->pageTextNotContains('The 2nd NEW block body!');
91 // The default layout entity block should be changed.
92 $this->drupalGet(static::FIELD_UI_PREFIX . '/display-layout/default');
93 $assert_session->pageTextContains('The DEFAULT block body');
94 // Confirm default layout still only has 1 entity block.
95 $assert_session->elementsCount('css', static::INLINE_BLOCK_LOCATOR, 1);
99 * Tests adding a new entity block and then not saving the layout.
101 * @dataProvider layoutNoSaveProvider
103 public function testNoLayoutSave($operation, $no_save_link_text, $confirm_button_text) {
105 $this->drupalLogin($this->drupalCreateUser([
106 'access contextual links',
107 'configure any layout',
108 'administer node display',
110 $assert_session = $this->assertSession();
111 $page = $this->getSession()->getPage();
112 $this->assertEmpty($this->blockStorage->loadMultiple(), 'No entity blocks exist');
113 // Enable layout builder and overrides.
114 $this->drupalPostForm(
115 static::FIELD_UI_PREFIX . '/display/default',
116 ['layout[enabled]' => TRUE, 'layout[allow_custom]' => TRUE],
120 $this->drupalGet('node/1/layout');
121 $this->addInlineBlockToLayout('Block title', 'The block body');
122 $this->clickLink($no_save_link_text);
123 if ($confirm_button_text) {
124 $page->pressButton($confirm_button_text);
126 $this->drupalGet('node/1');
127 $this->assertEmpty($this->blockStorage->loadMultiple(), 'No entity blocks were created when layout is canceled.');
128 $assert_session->pageTextNotContains('The block body');
130 $this->drupalGet('node/1/layout');
132 $this->addInlineBlockToLayout('Block title', 'The block body');
133 $this->assertSaveLayout();
134 $this->drupalGet('node/1');
135 $assert_session->pageTextContains('The block body');
136 $blocks = $this->blockStorage->loadMultiple();
137 $this->assertEquals(count($blocks), 1);
138 /* @var \Drupal\Core\Entity\ContentEntityBase $block */
139 $block = array_pop($blocks);
140 $revision_id = $block->getRevisionId();
142 // Confirm the block can be edited.
143 $this->drupalGet('node/1/layout');
144 $this->configureInlineBlock('The block body', 'The block updated body');
146 $this->clickLink($no_save_link_text);
147 if ($confirm_button_text) {
148 $page->pressButton($confirm_button_text);
150 $this->drupalGet('node/1');
152 $blocks = $this->blockStorage->loadMultiple();
153 // When reverting or canceling the update block should not be on the page.
154 $assert_session->pageTextNotContains('The block updated body');
155 if ($operation === 'cancel') {
156 // When canceling the original block body should appear.
157 $assert_session->pageTextContains('The block body');
159 $this->assertEquals(count($blocks), 1);
160 $block = array_pop($blocks);
161 $this->assertEquals($block->getRevisionId(), $revision_id);
162 $this->assertEquals($block->get('body')->getValue()[0]['value'], 'The block body');
165 // The block should not be visible.
166 // Blocks are currently only deleted when the parent entity is deleted.
167 $assert_session->pageTextNotContains('The block body');
172 * Provides test data for ::testNoLayoutSave().
174 public function layoutNoSaveProvider() {
183 'Revert to defaults',
190 * Tests entity blocks revisioning.
192 public function testInlineBlocksRevisioning() {
193 $assert_session = $this->assertSession();
194 $page = $this->getSession()->getPage();
196 $this->drupalLogin($this->drupalCreateUser([
197 'access contextual links',
198 'configure any layout',
199 'administer node display',
200 'administer node fields',
202 'bypass node access',
204 // Enable layout builder and overrides.
205 $this->drupalPostForm(
206 static::FIELD_UI_PREFIX . '/display/default',
207 ['layout[enabled]' => TRUE, 'layout[allow_custom]' => TRUE],
210 $this->drupalGet('node/1/layout');
212 // Add an inline block.
213 $this->addInlineBlockToLayout('Block title', 'The DEFAULT block body');
214 $this->assertSaveLayout();
215 $this->drupalGet('node/1');
217 $assert_session->pageTextContains('The DEFAULT block body');
219 /** @var \Drupal\node\NodeStorageInterface $node_storage */
220 $node_storage = $this->container->get('entity_type.manager')->getStorage('node');
221 $original_revision_id = $node_storage->getLatestRevisionId(1);
223 // Create a new revision.
224 $this->drupalGet('node/1/edit');
225 $page->findField('title[0][value]')->setValue('Node updated');
226 $page->pressButton('Save');
228 $this->drupalGet('node/1');
229 $assert_session->pageTextContains('The DEFAULT block body');
231 $assert_session->linkExists('Revisions');
234 $this->drupalGet('node/1/layout');
235 $this->configureInlineBlock('The DEFAULT block body', 'The NEW block body');
236 $this->assertSaveLayout();
237 $this->drupalGet('node/1');
238 $assert_session->pageTextContains('The NEW block body');
239 $assert_session->pageTextNotContains('The DEFAULT block body');
241 $revision_url = "node/1/revisions/$original_revision_id";
243 // Ensure viewing the previous revision shows the previous block revision.
244 $this->drupalGet("$revision_url/view");
245 $assert_session->pageTextContains('The DEFAULT block body');
246 $assert_session->pageTextNotContains('The NEW block body');
248 // Revert to first revision.
249 $revision_url = "$revision_url/revert";
250 $this->drupalGet($revision_url);
251 $page->pressButton('Revert');
253 $this->drupalGet('node/1');
254 $assert_session->pageTextContains('The DEFAULT block body');
255 $assert_session->pageTextNotContains('The NEW block body');
259 * Tests that entity blocks deleted correctly.
261 public function testDeletion() {
262 /** @var \Drupal\Core\Cron $cron */
263 $cron = \Drupal::service('cron');
264 /** @var \Drupal\layout_builder\InlineBlockUsage $usage */
265 $usage = \Drupal::service('inline_block.usage');
266 $this->drupalLogin($this->drupalCreateUser([
267 'administer content types',
268 'access contextual links',
269 'configure any layout',
270 'administer node display',
271 'administer node fields',
273 'bypass node access',
275 $assert_session = $this->assertSession();
276 $page = $this->getSession()->getPage();
278 // Enable layout builder.
279 $this->drupalPostForm(
280 static::FIELD_UI_PREFIX . '/display/default',
281 ['layout[enabled]' => TRUE],
284 // Add a block to default layout.
285 $this->drupalGet(static::FIELD_UI_PREFIX . '/display/default');
286 $this->clickLink('Manage layout');
287 $assert_session->addressEquals(static::FIELD_UI_PREFIX . '/display-layout/default');
288 $this->addInlineBlockToLayout('Block title', 'The DEFAULT block body');
289 $this->assertSaveLayout();
291 $this->assertCount(1, $this->blockStorage->loadMultiple());
292 $default_block_id = $this->getLatestBlockEntityId();
294 // Ensure the block shows up on node pages.
295 $this->drupalGet('node/1');
296 $assert_session->pageTextContains('The DEFAULT block body');
297 $this->drupalGet('node/2');
298 $assert_session->pageTextContains('The DEFAULT block body');
301 $this->drupalPostForm(static::FIELD_UI_PREFIX . '/display/default', ['layout[allow_custom]' => TRUE], 'Save');
303 // Ensure we have 2 copies of the block in node overrides.
304 $this->drupalGet('node/1/layout');
305 $this->assertSaveLayout();
306 $node_1_block_id = $this->getLatestBlockEntityId();
308 $this->drupalGet('node/2/layout');
309 $this->assertSaveLayout();
310 $node_2_block_id = $this->getLatestBlockEntityId();
311 $this->assertCount(3, $this->blockStorage->loadMultiple());
313 $this->drupalGet(static::FIELD_UI_PREFIX . '/display/default');
314 $this->clickLink('Manage layout');
315 $assert_session->addressEquals(static::FIELD_UI_PREFIX . '/display-layout/default');
317 $this->assertNotEmpty($this->blockStorage->load($default_block_id));
318 $this->assertNotEmpty($usage->getUsage($default_block_id));
319 // Remove block from default.
320 $this->removeInlineBlockFromLayout();
321 $this->assertSaveLayout();
322 // Ensure the block in the default was deleted.
323 $this->blockStorage->resetCache([$default_block_id]);
324 $this->assertEmpty($this->blockStorage->load($default_block_id));
325 // Ensure other blocks still exist.
326 $this->assertCount(2, $this->blockStorage->loadMultiple());
327 $this->assertEmpty($usage->getUsage($default_block_id));
329 $this->drupalGet('node/1/layout');
330 $assert_session->pageTextContains('The DEFAULT block body');
332 $this->removeInlineBlockFromLayout();
333 $this->assertSaveLayout();
335 // Ensure entity block is not deleted because it is needed in revision.
336 $this->assertNotEmpty($this->blockStorage->load($node_1_block_id));
337 $this->assertCount(2, $this->blockStorage->loadMultiple());
339 $this->assertNotEmpty($usage->getUsage($node_1_block_id));
340 // Ensure entity block is deleted when node is deleted.
341 $this->drupalGet('node/1/delete');
342 $page->pressButton('Delete');
343 $this->assertEmpty(Node::load(1));
345 $this->assertEmpty($this->blockStorage->load($node_1_block_id));
346 $this->assertEmpty($usage->getUsage($node_1_block_id));
347 $this->assertCount(1, $this->blockStorage->loadMultiple());
349 // Add another block to the default.
350 $this->drupalGet(static::FIELD_UI_PREFIX . '/display/default');
351 $this->clickLink('Manage layout');
352 $assert_session->addressEquals(static::FIELD_UI_PREFIX . '/display-layout/default');
353 $this->addInlineBlockToLayout('Title 2', 'Body 2');
354 $this->assertSaveLayout();
356 $default_block2_id = $this->getLatestBlockEntityId();
357 $this->assertCount(2, $this->blockStorage->loadMultiple());
359 // Delete the other node so bundle can be deleted.
360 $this->assertNotEmpty($usage->getUsage($node_2_block_id));
361 $this->drupalGet('node/2/delete');
362 $page->pressButton('Delete');
363 $this->assertEmpty(Node::load(2));
365 // Ensure entity block was deleted.
366 $this->assertEmpty($this->blockStorage->load($node_2_block_id));
367 $this->assertEmpty($usage->getUsage($node_2_block_id));
368 $this->assertCount(1, $this->blockStorage->loadMultiple());
370 // Delete the bundle which has the default layout.
371 $this->assertNotEmpty($usage->getUsage($default_block2_id));
372 $this->drupalGet(static::FIELD_UI_PREFIX . '/delete');
373 $page->pressButton('Delete');
376 // Ensure the entity block in default is deleted when bundle is deleted.
377 $this->assertEmpty($this->blockStorage->load($default_block2_id));
378 $this->assertEmpty($usage->getUsage($default_block2_id));
379 $this->assertCount(0, $this->blockStorage->loadMultiple());
383 * Tests access to the block edit form of inline blocks.
385 * This module does not provide links to these forms but in case the paths are
386 * accessed directly they should accessible by users with the
387 * 'configure any layout' permission.
389 * @see layout_builder_block_content_access()
391 public function testAccess() {
392 $this->drupalLogin($this->drupalCreateUser([
393 'access contextual links',
394 'configure any layout',
395 'administer node display',
396 'administer node fields',
398 $assert_session = $this->assertSession();
400 // Enable layout builder and overrides.
401 $this->drupalPostForm(
402 static::FIELD_UI_PREFIX . '/display/default',
403 ['layout[enabled]' => TRUE, 'layout[allow_custom]' => TRUE],
407 // Ensure we have 2 copies of the block in node overrides.
408 $this->drupalGet('node/1/layout');
409 $this->addInlineBlockToLayout('Block title', 'Block body');
410 $this->assertSaveLayout();
411 $node_1_block_id = $this->getLatestBlockEntityId();
413 $this->drupalGet("block/$node_1_block_id");
414 $assert_session->pageTextNotContains('You are not authorized to access this page');
416 $this->drupalLogout();
417 $this->drupalLogin($this->drupalCreateUser([
421 $this->drupalGet("block/$node_1_block_id");
422 $assert_session->pageTextContains('You are not authorized to access this page');
424 $this->drupalLogin($this->drupalCreateUser([
425 'configure any layout',
427 $this->drupalGet("block/$node_1_block_id");
428 $assert_session->pageTextNotContains('You are not authorized to access this page');
432 * Tests the workflow for adding an inline block depending on number of types.
434 * @throws \Behat\Mink\Exception\ElementNotFoundException
435 * @throws \Behat\Mink\Exception\ExpectationException
437 public function testAddWorkFlow() {
438 $assert_session = $this->assertSession();
439 $page = $this->getSession()->getPage();
440 $type_storage = $this->container->get('entity_type.manager')->getStorage('block_content_type');
441 foreach ($type_storage->loadByProperties() as $type) {
445 $this->drupalLogin($this->drupalCreateUser([
446 'access contextual links',
447 'configure any layout',
448 'administer node display',
449 'administer node fields',
452 // Enable layout builder and overrides.
453 $this->drupalPostForm(
454 static::FIELD_UI_PREFIX . '/display/default',
455 ['layout[enabled]' => TRUE, 'layout[allow_custom]' => TRUE],
459 $layout_default_path = 'admin/structure/types/manage/bundle_with_section_field/display-layout/default';
460 $this->drupalGet($layout_default_path);
461 // Add a basic block with the body field set.
462 $page->clickLink('Add Block');
463 $assert_session->assertWaitOnAjaxRequest();
464 // Confirm that with no block content types the link does not appear.
465 $assert_session->linkNotExists('Create custom block');
467 $this->createBlockContentType('basic', 'Basic block');
469 $this->drupalGet($layout_default_path);
470 // Add a basic block with the body field set.
471 $page->clickLink('Add Block');
472 $assert_session->assertWaitOnAjaxRequest();
473 // Confirm with only 1 type the "Create custom block" link goes directly t
475 $assert_session->linkNotExists('Basic block');
476 $this->clickLink('Create custom block');
477 $assert_session->assertWaitOnAjaxRequest();
478 $assert_session->fieldExists('Title');
480 $this->createBlockContentType('advanced', 'Advanced block');
482 $this->drupalGet($layout_default_path);
483 // Add a basic block with the body field set.
484 $page->clickLink('Add Block');
485 // Confirm that, when more than 1 type exists, "Create custom block" shows a
486 // list of block types.
487 $assert_session->assertWaitOnAjaxRequest();
488 $assert_session->linkNotExists('Basic block');
489 $assert_session->linkNotExists('Advanced block');
490 $this->clickLink('Create custom block');
491 $assert_session->assertWaitOnAjaxRequest();
492 $assert_session->fieldNotExists('Title');
493 $assert_session->linkExists('Basic block');
494 $assert_session->linkExists('Advanced block');
496 $this->clickLink('Advanced block');
497 $assert_session->assertWaitOnAjaxRequest();
498 $assert_session->fieldExists('Title');