3 namespace Drupal\Tests\block\Kernel;
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;
12 * Tests the block view builder.
16 class BlockViewBuilderTest extends KernelTestBase {
23 public static $modules = ['block', 'block_test', 'system', 'user'];
26 * The block being tested.
28 * @var \Drupal\block\Entity\BlockInterface
35 * @var \Drupal\Core\Config\Entity\ConfigEntityStorageInterface
37 protected $controller;
42 * @var \Drupal\Core\Render\RendererInterface
49 protected function setUp() {
52 $this->controller = $this->container
53 ->get('entity_type.manager')
54 ->getStorage('block');
56 \Drupal::state()->set('block_test.content', 'Llamas > unicorns!');
58 // Create a block with only required values.
59 $this->block = $this->controller->create([
62 'plugin' => 'test_cache',
66 $this->container->get('cache.render')->deleteAll();
68 $this->renderer = $this->container->get('renderer');
72 * Tests the rendering of blocks.
74 public function testBasicRendering() {
75 \Drupal::state()->set('block_test.content', '');
77 $entity = $this->controller->create([
78 'id' => 'test_block1',
80 'plugin' => 'test_html',
84 // Test the rendering of a block.
85 $entity = Block::load('test_block1');
86 $output = entity_view($entity, 'block');
88 $expected[] = '<div id="block-test-block1">';
92 $expected[] = ' </div>';
94 $expected_output = implode("\n", $expected);
95 $this->assertEqual($this->renderer->renderRoot($output), $expected_output);
97 // Reset the HTML IDs so that the next render is not affected.
100 // Test the rendering of a block with a given title.
101 $entity = $this->controller->create([
102 'id' => 'test_block2',
104 'plugin' => 'test_html',
106 'label' => 'Powered by Bananas',
110 $output = entity_view($entity, 'block');
112 $expected[] = '<div id="block-test-block2">';
114 $expected[] = ' <h2>Powered by Bananas</h2>';
117 $expected[] = ' </div>';
119 $expected_output = implode("\n", $expected);
120 $this->assertEqual($this->renderer->renderRoot($output), $expected_output);
124 * Tests block render cache handling.
126 public function testBlockViewBuilderCache() {
127 // Verify cache handling for a non-empty block.
128 $this->verifyRenderCacheHandling();
130 // Create an empty block.
131 $this->block = $this->controller->create([
132 'id' => 'test_block',
134 'plugin' => 'test_cache',
136 $this->block->save();
137 \Drupal::state()->set('block_test.content', NULL);
139 // Verify cache handling for an empty block.
140 $this->verifyRenderCacheHandling();
144 * Verifies render cache handling of the block being tested.
146 * @see ::testBlockViewBuilderCache()
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');
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.');
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.');
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()
169 $build['#block'] = $this->block;
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.');
176 // Restore the previous request method.
177 $request->setMethod($request_method);
181 * Tests block view altering.
183 * @see hook_block_view_alter()
184 * @see hook_block_view_BASE_BLOCK_ID_alter()
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!');
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);
200 \Drupal::state()->set('block_test.content', NULL);
201 Cache::invalidateTags($this->block->getCacheTagsToInvalidate());
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.');
213 * Tests block build altering.
215 * @see hook_block_build_alter()
216 * @see hook_block_build_BASE_BLOCK_ID_alter()
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');
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;
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);
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);
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);
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);
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);
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']);
271 \Drupal::state()->set('block_test_block_alter_create_placeholder', NULL);
273 // Restore the previous request method.
274 $request->setMethod($request_method);
278 * Asserts that a block is built/rendered/cached with expected cacheability.
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.
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'];
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']);
315 $this->container->get('cache.render')->delete($cid);
319 * Get a fully built render array for a block.
324 protected function getBlockRenderArray() {
325 return $this->container->get('entity_type.manager')->getViewBuilder('block')->view($this->block, 'block');