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