71c4f55b8d089b51defe53222244b3fa011e2c75
[yaffs-website] / web / core / modules / block / tests / src / Kernel / BlockViewBuilderTest.php
1 <?php
2
3 namespace Drupal\Tests\block\Kernel;
4
5 use Drupal\Component\Utility\Html;
6 use Drupal\Core\Cache\Cache;
7 use Drupal\Core\Language\LanguageInterface;
8 use Drupal\KernelTests\KernelTestBase;
9 use Drupal\block\Entity\Block;
10
11 /**
12  * Tests the block view builder.
13  *
14  * @group block
15  */
16 class BlockViewBuilderTest extends KernelTestBase {
17
18   /**
19    * Modules to install.
20    *
21    * @var array
22    */
23   public static $modules = ['block', 'block_test', 'system', 'user'];
24
25   /**
26    * The block being tested.
27    *
28    * @var \Drupal\block\Entity\BlockInterface
29    */
30   protected $block;
31
32   /**
33    * The block storage.
34    *
35    * @var \Drupal\Core\Config\Entity\ConfigEntityStorageInterface
36    */
37   protected $controller;
38
39   /**
40    * The renderer.
41    *
42    * @var \Drupal\Core\Render\RendererInterface
43    */
44   protected $renderer;
45
46   /**
47    * {@inheritdoc}
48    */
49   protected function setUp() {
50     parent::setUp();
51
52     $this->controller = $this->container
53       ->get('entity_type.manager')
54       ->getStorage('block');
55
56     \Drupal::state()->set('block_test.content', 'Llamas &gt; unicorns!');
57
58     // Create a block with only required values.
59     $this->block = $this->controller->create([
60       'id' => 'test_block',
61       'theme' => 'stark',
62       'plugin' => 'test_cache',
63     ]);
64     $this->block->save();
65
66     $this->container->get('cache.render')->deleteAll();
67
68     $this->renderer = $this->container->get('renderer');
69   }
70
71   /**
72    * Tests the rendering of blocks.
73    */
74   public function testBasicRendering() {
75     \Drupal::state()->set('block_test.content', '');
76
77     $entity = $this->controller->create([
78       'id' => 'test_block1',
79       'theme' => 'stark',
80       'plugin' => 'test_html',
81     ]);
82     $entity->save();
83
84     // Test the rendering of a block.
85     $entity = Block::load('test_block1');
86     $output = entity_view($entity, 'block');
87     $expected = [];
88     $expected[] = '<div id="block-test-block1">';
89     $expected[] = '  ';
90     $expected[] = '    ';
91     $expected[] = '      ';
92     $expected[] = '  </div>';
93     $expected[] = '';
94     $expected_output = implode("\n", $expected);
95     $this->assertEqual($this->renderer->renderRoot($output), $expected_output);
96
97     // Reset the HTML IDs so that the next render is not affected.
98     Html::resetSeenIds();
99
100     // Test the rendering of a block with a given title.
101     $entity = $this->controller->create([
102       'id' => 'test_block2',
103       'theme' => 'stark',
104       'plugin' => 'test_html',
105       'settings' => [
106         'label' => 'Powered by Bananas',
107       ],
108     ]);
109     $entity->save();
110     $output = entity_view($entity, 'block');
111     $expected = [];
112     $expected[] = '<div id="block-test-block2">';
113     $expected[] = '  ';
114     $expected[] = '      <h2>Powered by Bananas</h2>';
115     $expected[] = '    ';
116     $expected[] = '      ';
117     $expected[] = '  </div>';
118     $expected[] = '';
119     $expected_output = implode("\n", $expected);
120     $this->assertEqual($this->renderer->renderRoot($output), $expected_output);
121   }
122
123   /**
124    * Tests block render cache handling.
125    */
126   public function testBlockViewBuilderCache() {
127     // Verify cache handling for a non-empty block.
128     $this->verifyRenderCacheHandling();
129
130     // Create an empty block.
131     $this->block = $this->controller->create([
132       'id' => 'test_block',
133       'theme' => 'stark',
134       'plugin' => 'test_cache',
135     ]);
136     $this->block->save();
137     \Drupal::state()->set('block_test.content', NULL);
138
139     // Verify cache handling for an empty block.
140     $this->verifyRenderCacheHandling();
141   }
142
143   /**
144    * Verifies render cache handling of the block being tested.
145    *
146    * @see ::testBlockViewBuilderCache()
147    */
148   protected function verifyRenderCacheHandling() {
149     // Force a request via GET so we can test the render cache.
150     $request = \Drupal::request();
151     $request_method = $request->server->get('REQUEST_METHOD');
152     $request->setMethod('GET');
153
154     // Test that a cache entry is created.
155     $build = $this->getBlockRenderArray();
156     $cid = 'entity_view:block:test_block:' . implode(':', \Drupal::service('cache_contexts_manager')->convertTokensToKeys(['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme', 'user.permissions'])->getKeys());
157     $this->renderer->renderRoot($build);
158     $this->assertTrue($this->container->get('cache.render')->get($cid), 'The block render element has been cached.');
159
160     // Re-save the block and check that the cache entry has been deleted.
161     $this->block->save();
162     $this->assertFalse($this->container->get('cache.render')->get($cid), 'The block render cache entry has been cleared when the block was saved.');
163
164     // Rebuild the render array (creating a new cache entry in the process) and
165     // delete the block to check the cache entry is deleted.
166     unset($build['#printed']);
167     // Re-add the block because \Drupal\block\BlockViewBuilder::buildBlock()
168     // removes it.
169     $build['#block'] = $this->block;
170
171     $this->renderer->renderRoot($build);
172     $this->assertTrue($this->container->get('cache.render')->get($cid), 'The block render element has been cached.');
173     $this->block->delete();
174     $this->assertFalse($this->container->get('cache.render')->get($cid), 'The block render cache entry has been cleared when the block was deleted.');
175
176     // Restore the previous request method.
177     $request->setMethod($request_method);
178   }
179
180   /**
181    * Tests block view altering.
182    *
183    * @see hook_block_view_alter()
184    * @see hook_block_view_BASE_BLOCK_ID_alter()
185    */
186   public function testBlockViewBuilderViewAlter() {
187     // Establish baseline.
188     $build = $this->getBlockRenderArray();
189     $this->setRawContent((string) $this->renderer->renderRoot($build));
190     $this->assertIdentical(trim((string) $this->cssSelect('div')[0]), 'Llamas > unicorns!');
191
192     // Enable the block view alter hook that adds a foo=bar attribute.
193     \Drupal::state()->set('block_test_view_alter_suffix', TRUE);
194     Cache::invalidateTags($this->block->getCacheTagsToInvalidate());
195     $build = $this->getBlockRenderArray();
196     $this->setRawContent((string) $this->renderer->renderRoot($build));
197     $this->assertIdentical(trim((string) $this->cssSelect('[foo=bar]')[0]), 'Llamas > unicorns!');
198     \Drupal::state()->set('block_test_view_alter_suffix', FALSE);
199
200     \Drupal::state()->set('block_test.content', NULL);
201     Cache::invalidateTags($this->block->getCacheTagsToInvalidate());
202
203     // Advanced: cached block, but an alter hook adds a #pre_render callback to
204     // alter the eventual content.
205     \Drupal::state()->set('block_test_view_alter_append_pre_render_prefix', TRUE);
206     $build = $this->getBlockRenderArray();
207     $this->assertFalse(isset($build['#prefix']), 'The appended #pre_render callback has not yet run before rendering.');
208     $this->assertIdentical((string) $this->renderer->renderRoot($build), 'Hiya!<br>');
209     $this->assertTrue(isset($build['#prefix']) && $build['#prefix'] === 'Hiya!<br>', 'A cached block without content is altered.');
210   }
211
212   /**
213    * Tests block build altering.
214    *
215    * @see hook_block_build_alter()
216    * @see hook_block_build_BASE_BLOCK_ID_alter()
217    */
218   public function testBlockViewBuilderBuildAlter() {
219     // Force a request via GET so we can test the render cache.
220     $request = \Drupal::request();
221     $request_method = $request->server->get('REQUEST_METHOD');
222     $request->setMethod('GET');
223
224     $default_keys = ['entity_view', 'block', 'test_block'];
225     $default_contexts = [];
226     $default_tags = ['block_view', 'config:block.block.test_block'];
227     $default_max_age = Cache::PERMANENT;
228
229     // hook_block_build_alter() adds an additional cache key.
230     $alter_add_key = $this->randomMachineName();
231     \Drupal::state()->set('block_test_block_alter_cache_key', $alter_add_key);
232     $this->assertBlockRenderedWithExpectedCacheability(array_merge($default_keys, [$alter_add_key]), $default_contexts, $default_tags, $default_max_age);
233     \Drupal::state()->set('block_test_block_alter_cache_key', NULL);
234
235     // hook_block_build_alter() adds an additional cache context.
236     $alter_add_context = 'url.query_args:' . $this->randomMachineName();
237     \Drupal::state()->set('block_test_block_alter_cache_context', $alter_add_context);
238     $this->assertBlockRenderedWithExpectedCacheability($default_keys, Cache::mergeContexts($default_contexts, [$alter_add_context]), $default_tags, $default_max_age);
239     \Drupal::state()->set('block_test_block_alter_cache_context', NULL);
240
241     // hook_block_build_alter() adds an additional cache tag.
242     $alter_add_tag = $this->randomMachineName();
243     \Drupal::state()->set('block_test_block_alter_cache_tag', $alter_add_tag);
244     $this->assertBlockRenderedWithExpectedCacheability($default_keys, $default_contexts, Cache::mergeTags($default_tags, [$alter_add_tag]), $default_max_age);
245     \Drupal::state()->set('block_test_block_alter_cache_tag', NULL);
246
247     // hook_block_build_alter() alters the max-age.
248     $alter_max_age = 300;
249     \Drupal::state()->set('block_test_block_alter_cache_max_age', $alter_max_age);
250     $this->assertBlockRenderedWithExpectedCacheability($default_keys, $default_contexts, $default_tags, $alter_max_age);
251     \Drupal::state()->set('block_test_block_alter_cache_max_age', NULL);
252
253     // hook_block_build_alter() alters cache keys, contexts, tags and max-age.
254     \Drupal::state()->set('block_test_block_alter_cache_key', $alter_add_key);
255     \Drupal::state()->set('block_test_block_alter_cache_context', $alter_add_context);
256     \Drupal::state()->set('block_test_block_alter_cache_tag', $alter_add_tag);
257     \Drupal::state()->set('block_test_block_alter_cache_max_age', $alter_max_age);
258     $this->assertBlockRenderedWithExpectedCacheability(array_merge($default_keys, [$alter_add_key]), Cache::mergeContexts($default_contexts, [$alter_add_context]), Cache::mergeTags($default_tags, [$alter_add_tag]), $alter_max_age);
259     \Drupal::state()->set('block_test_block_alter_cache_key', NULL);
260     \Drupal::state()->set('block_test_block_alter_cache_context', NULL);
261     \Drupal::state()->set('block_test_block_alter_cache_tag', NULL);
262     \Drupal::state()->set('block_test_block_alter_cache_max_age', NULL);
263
264     // hook_block_build_alter() sets #create_placeholder.
265     foreach ([TRUE, FALSE] as $value) {
266       \Drupal::state()->set('block_test_block_alter_create_placeholder', $value);
267       $build = $this->getBlockRenderArray();
268       $this->assertTrue(isset($build['#create_placeholder']));
269       $this->assertIdentical($value, $build['#create_placeholder']);
270     }
271     \Drupal::state()->set('block_test_block_alter_create_placeholder', NULL);
272
273     // Restore the previous request method.
274     $request->setMethod($request_method);
275   }
276
277   /**
278    * Asserts that a block is built/rendered/cached with expected cacheability.
279    *
280    * @param string[] $expected_keys
281    *   The expected cache keys.
282    * @param string[] $expected_contexts
283    *   The expected cache contexts.
284    * @param string[] $expected_tags
285    *   The expected cache tags.
286    * @param int $expected_max_age
287    *   The expected max-age.
288    */
289   protected function assertBlockRenderedWithExpectedCacheability(array $expected_keys, array $expected_contexts, array $expected_tags, $expected_max_age) {
290     $required_cache_contexts = ['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme', 'user.permissions'];
291
292     // Check that the expected cacheability metadata is present in:
293     // - the built render array;
294     $this->pass('Built render array');
295     $build = $this->getBlockRenderArray();
296     $this->assertIdentical($expected_keys, $build['#cache']['keys']);
297     $this->assertIdentical($expected_contexts, $build['#cache']['contexts']);
298     $this->assertIdentical($expected_tags, $build['#cache']['tags']);
299     $this->assertIdentical($expected_max_age, $build['#cache']['max-age']);
300     $this->assertFalse(isset($build['#create_placeholder']));
301     // - the rendered render array;
302     $this->pass('Rendered render array');
303     $this->renderer->renderRoot($build);
304     // - the render cache item.
305     $this->pass('Render cache item');
306     $final_cache_contexts = Cache::mergeContexts($expected_contexts, $required_cache_contexts);
307     $cid = implode(':', $expected_keys) . ':' . implode(':', \Drupal::service('cache_contexts_manager')->convertTokensToKeys($final_cache_contexts)->getKeys());
308     $cache_item = $this->container->get('cache.render')->get($cid);
309     $this->assertTrue($cache_item, 'The block render element has been cached with the expected cache ID.');
310     $this->assertIdentical(Cache::mergeTags($expected_tags, ['rendered']), $cache_item->tags);
311     $this->assertIdentical($final_cache_contexts, $cache_item->data['#cache']['contexts']);
312     $this->assertIdentical($expected_tags, $cache_item->data['#cache']['tags']);
313     $this->assertIdentical($expected_max_age, $cache_item->data['#cache']['max-age']);
314
315     $this->container->get('cache.render')->delete($cid);
316   }
317
318   /**
319    * Get a fully built render array for a block.
320    *
321    * @return array
322    *   The render array.
323    */
324   protected function getBlockRenderArray() {
325     return $this->container->get('entity_type.manager')->getViewBuilder('block')->view($this->block, 'block');
326   }
327
328 }