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