Pull merge.
[yaffs-website] / web / core / modules / layout_builder / tests / src / Functional / LayoutSectionTest.php
1 <?php
2
3 namespace Drupal\Tests\layout_builder\Functional;
4
5 use Drupal\language\Entity\ConfigurableLanguage;
6 use Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay;
7 use Drupal\layout_builder\Plugin\SectionStorage\OverridesSectionStorage;
8 use Drupal\layout_builder\Section;
9 use Drupal\layout_builder\SectionComponent;
10 use Drupal\Tests\BrowserTestBase;
11
12 /**
13  * Tests the rendering of a layout section field.
14  *
15  * @group layout_builder
16  */
17 class LayoutSectionTest extends BrowserTestBase {
18
19   /**
20    * {@inheritdoc}
21    */
22   public static $modules = ['field_ui', 'layout_builder', 'node', 'block_test'];
23
24   /**
25    * {@inheritdoc}
26    */
27   protected function setUp() {
28     parent::setUp();
29
30     $this->createContentType([
31       'type' => 'bundle_without_section_field',
32     ]);
33     $this->createContentType([
34       'type' => 'bundle_with_section_field',
35     ]);
36
37     LayoutBuilderEntityViewDisplay::load('node.bundle_with_section_field.default')
38       ->enableLayoutBuilder()
39       ->setOverridable()
40       ->save();
41
42     $this->drupalLogin($this->drupalCreateUser([
43       'configure any layout',
44       'administer node display',
45       'administer node fields',
46       'administer content types',
47     ], 'foobar'));
48   }
49
50   /**
51    * Provides test data for ::testLayoutSectionFormatter().
52    */
53   public function providerTestLayoutSectionFormatter() {
54     $data = [];
55     $data['block_with_global_context'] = [
56       [
57         [
58           'section' => new Section('layout_onecol', [], [
59             'baz' => new SectionComponent('baz', 'content', [
60               'id' => 'test_context_aware',
61               'context_mapping' => [
62                 'user' => '@user.current_user_context:current_user',
63               ],
64             ]),
65           ]),
66         ],
67       ],
68       [
69         '.layout--onecol',
70         '#test_context_aware--username',
71       ],
72       [
73         'foobar',
74       ],
75       'user',
76       'user:2',
77       'UNCACHEABLE',
78     ];
79     $data['block_with_entity_context'] = [
80       [
81         [
82           'section' => new Section('layout_onecol', [], [
83             'baz' => new SectionComponent('baz', 'content', [
84               'id' => 'field_block:node:bundle_with_section_field:body',
85               'context_mapping' => [
86                 'entity' => 'layout_builder.entity',
87               ],
88             ]),
89           ]),
90         ],
91       ],
92       [
93         '.layout--onecol',
94         '.field--name-body',
95       ],
96       [
97         'Body',
98         'The node body',
99       ],
100       '',
101       '',
102       'MISS',
103     ];
104     $data['single_section_single_block'] = [
105       [
106         [
107           'section' => new Section('layout_onecol', [], [
108             'baz' => new SectionComponent('baz', 'content', [
109               'id' => 'system_powered_by_block',
110             ]),
111           ]),
112         ],
113       ],
114       '.layout--onecol',
115       'Powered by',
116       '',
117       '',
118       'MISS',
119     ];
120     $data['multiple_sections'] = [
121       [
122         [
123           'section' => new Section('layout_onecol', [], [
124             'baz' => new SectionComponent('baz', 'content', [
125               'id' => 'system_powered_by_block',
126             ]),
127           ]),
128         ],
129         [
130           'section' => new Section('layout_twocol', [], [
131             'foo' => new SectionComponent('foo', 'first', [
132               'id' => 'test_block_instantiation',
133               'display_message' => 'foo text',
134             ]),
135             'bar' => new SectionComponent('bar', 'second', [
136               'id' => 'test_block_instantiation',
137               'display_message' => 'bar text',
138             ]),
139           ]),
140         ],
141       ],
142       [
143         '.layout--onecol',
144         '.layout--twocol',
145       ],
146       [
147         'Powered by',
148         'foo text',
149         'bar text',
150       ],
151       'user.permissions',
152       '',
153       'MISS',
154     ];
155     return $data;
156   }
157
158   /**
159    * Tests layout_section formatter output.
160    *
161    * @dataProvider providerTestLayoutSectionFormatter
162    */
163   public function testLayoutSectionFormatter($layout_data, $expected_selector, $expected_content, $expected_cache_contexts, $expected_cache_tags, $expected_dynamic_cache) {
164     $node = $this->createSectionNode($layout_data);
165
166     $canonical_url = $node->toUrl('canonical');
167     $this->drupalGet($canonical_url);
168     $this->assertLayoutSection($expected_selector, $expected_content, $expected_cache_contexts, $expected_cache_tags, $expected_dynamic_cache);
169
170     $this->drupalGet($canonical_url->toString() . '/layout');
171     $this->assertLayoutSection($expected_selector, $expected_content, $expected_cache_contexts, $expected_cache_tags, 'UNCACHEABLE');
172   }
173
174   /**
175    * Tests the access checking of the section formatter.
176    */
177   public function testLayoutSectionFormatterAccess() {
178     $node = $this->createSectionNode([
179       [
180         'section' => new Section('layout_onecol', [], [
181           'baz' => new SectionComponent('baz', 'content', [
182             'id' => 'test_access',
183           ]),
184         ]),
185       ],
186     ]);
187
188     // Restrict access to the block.
189     $this->container->get('state')->set('test_block_access', FALSE);
190
191     $this->drupalGet($node->toUrl('canonical'));
192     $this->assertLayoutSection('.layout--onecol', NULL, '', '', 'UNCACHEABLE');
193     // Ensure the block was not rendered.
194     $this->assertSession()->pageTextNotContains('Hello test world');
195
196     // Grant access to the block, and ensure it was rendered.
197     $this->container->get('state')->set('test_block_access', TRUE);
198     $this->drupalGet($node->toUrl('canonical'));
199     $this->assertLayoutSection('.layout--onecol', 'Hello test world', '', '', 'UNCACHEABLE');
200   }
201
202   /**
203    * Tests the multilingual support of the section formatter.
204    */
205   public function testMultilingualLayoutSectionFormatter() {
206     $this->container->get('module_installer')->install(['content_translation']);
207     $this->rebuildContainer();
208
209     ConfigurableLanguage::createFromLangcode('es')->save();
210     $this->container->get('content_translation.manager')->setEnabled('node', 'bundle_with_section_field', TRUE);
211
212     $entity = $this->createSectionNode([
213       [
214         'section' => new Section('layout_onecol', [], [
215           'baz' => new SectionComponent('baz', 'content', [
216             'id' => 'system_powered_by_block',
217           ]),
218         ]),
219       ],
220     ]);
221     $entity->addTranslation('es', [
222       'title' => 'Translated node title',
223       OverridesSectionStorage::FIELD_NAME => [
224         [
225           'section' => new Section('layout_twocol', [], [
226             'foo' => new SectionComponent('foo', 'first', [
227               'id' => 'test_block_instantiation',
228               'display_message' => 'foo text',
229             ]),
230             'bar' => new SectionComponent('bar', 'second', [
231               'id' => 'test_block_instantiation',
232               'display_message' => 'bar text',
233             ]),
234           ]),
235         ],
236       ],
237     ]);
238     $entity->save();
239
240     $this->drupalGet($entity->toUrl('canonical'));
241     $this->assertLayoutSection('.layout--onecol', 'Powered by');
242     $this->drupalGet($entity->toUrl('canonical')->setOption('prefix', 'es/'));
243     $this->assertLayoutSection('.layout--twocol', ['foo text', 'bar text']);
244   }
245
246   /**
247    * Ensures that the entity title is displayed.
248    */
249   public function testLayoutPageTitle() {
250     $this->drupalPlaceBlock('page_title_block');
251     $node = $this->createSectionNode([]);
252
253     $this->drupalGet($node->toUrl('canonical')->toString() . '/layout');
254     $this->assertSession()->titleEquals('Edit layout for The node title | Drupal');
255     $this->assertEquals('Edit layout for The node title', $this->cssSelect('h1.page-title')[0]->getText());
256   }
257
258   /**
259    * Tests that no Layout link shows without a section field.
260    */
261   public function testLayoutUrlNoSectionField() {
262     $node = $this->createNode([
263       'type' => 'bundle_without_section_field',
264       'title' => 'The node title',
265       'body' => [
266         [
267           'value' => 'The node body',
268         ],
269       ],
270     ]);
271     $node->save();
272
273     $this->drupalGet($node->toUrl('canonical')->toString() . '/layout');
274     $this->assertSession()->statusCodeEquals(404);
275   }
276
277   /**
278    * Tests that deleting a field removes it from the layout.
279    */
280   public function testLayoutDeletingField() {
281     $assert_session = $this->assertSession();
282
283     $this->drupalGet('/admin/structure/types/manage/bundle_with_section_field/display-layout/default');
284     $assert_session->statusCodeEquals(200);
285     $assert_session->elementExists('css', '.field--name-body');
286
287     // Delete the field from both bundles.
288     $this->drupalGet('/admin/structure/types/manage/bundle_without_section_field/fields/node.bundle_without_section_field.body/delete');
289     $this->submitForm([], 'Delete');
290     $this->drupalGet('/admin/structure/types/manage/bundle_with_section_field/display-layout/default');
291     $assert_session->statusCodeEquals(200);
292     $assert_session->elementExists('css', '.field--name-body');
293
294     $this->drupalGet('/admin/structure/types/manage/bundle_with_section_field/fields/node.bundle_with_section_field.body/delete');
295     $this->submitForm([], 'Delete');
296     $this->drupalGet('/admin/structure/types/manage/bundle_with_section_field/display-layout/default');
297     $assert_session->statusCodeEquals(200);
298     $assert_session->elementNotExists('css', '.field--name-body');
299   }
300
301   /**
302    * Tests that deleting a bundle removes the layout.
303    */
304   public function testLayoutDeletingBundle() {
305     $assert_session = $this->assertSession();
306
307     $display = LayoutBuilderEntityViewDisplay::load('node.bundle_with_section_field.default');
308     $this->assertInstanceOf(LayoutBuilderEntityViewDisplay::class, $display);
309
310     $this->drupalPostForm('/admin/structure/types/manage/bundle_with_section_field/delete', [], 'Delete');
311     $assert_session->statusCodeEquals(200);
312
313     $display = LayoutBuilderEntityViewDisplay::load('node.bundle_with_section_field.default');
314     $this->assertNull($display);
315   }
316
317   /**
318    * Asserts the output of a layout section.
319    *
320    * @param string|array $expected_selector
321    *   A selector or list of CSS selectors to find.
322    * @param string|array $expected_content
323    *   A string or list of strings to find.
324    * @param string $expected_cache_contexts
325    *   A string of cache contexts to be found in the header.
326    * @param string $expected_cache_tags
327    *   A string of cache tags to be found in the header.
328    * @param string $expected_dynamic_cache
329    *   The expected dynamic cache header. Either 'HIT', 'MISS' or 'UNCACHEABLE'.
330    */
331   protected function assertLayoutSection($expected_selector, $expected_content, $expected_cache_contexts = '', $expected_cache_tags = '', $expected_dynamic_cache = 'MISS') {
332     $assert_session = $this->assertSession();
333     // Find the given selector.
334     foreach ((array) $expected_selector as $selector) {
335       $element = $this->cssSelect($selector);
336       $this->assertNotEmpty($element);
337     }
338
339     // Find the given content.
340     foreach ((array) $expected_content as $content) {
341       $assert_session->pageTextContains($content);
342     }
343     if ($expected_cache_contexts) {
344       $assert_session->responseHeaderContains('X-Drupal-Cache-Contexts', $expected_cache_contexts);
345     }
346     if ($expected_cache_tags) {
347       $assert_session->responseHeaderContains('X-Drupal-Cache-Tags', $expected_cache_tags);
348     }
349     $assert_session->responseHeaderEquals('X-Drupal-Dynamic-Cache', $expected_dynamic_cache);
350   }
351
352   /**
353    * Creates a node with a section field.
354    *
355    * @param array $section_values
356    *   An array of values for a section field.
357    *
358    * @return \Drupal\node\NodeInterface
359    *   The node object.
360    */
361   protected function createSectionNode(array $section_values) {
362     return $this->createNode([
363       'type' => 'bundle_with_section_field',
364       'title' => 'The node title',
365       'body' => [
366         [
367           'value' => 'The node body',
368         ],
369       ],
370       OverridesSectionStorage::FIELD_NAME => $section_values,
371     ]);
372   }
373
374 }