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