rendererConfig['required_cache_contexts'] = [];
parent::setUp();
}
/**
* Provides the two classes of placeholders: cacheable and uncacheable.
*
* i.e. with or without #cache[keys].
*
* Also, different types:
* - A) automatically generated placeholder
* - 1) manually triggered (#create_placeholder = TRUE)
* - 2) automatically triggered (based on max-age = 0 at the top level)
* - 3) automatically triggered (based on high cardinality cache contexts at
* the top level)
* - 4) automatically triggered (based on high-invalidation frequency cache
* tags at the top level)
* - 5) automatically triggered (based on max-age = 0 in its subtree, i.e.
* via bubbling)
* - 6) automatically triggered (based on high cardinality cache contexts in
* its subtree, i.e. via bubbling)
* - 7) automatically triggered (based on high-invalidation frequency cache
* tags in its subtree, i.e. via bubbling)
* - B) manually generated placeholder
*
* So, in total 2*8 = 16 permutations. (On one axis: uncacheable vs.
* uncacheable = 2; on the other axis: A1–7 and B = 8.)
*
* @todo Case A5 is not yet supported by core. So that makes for only 14
* permutations currently, instead of 16. That will be done in
* https://www.drupal.org/node/2559847
*
* @return array
*/
public function providerPlaceholders() {
$args = [$this->randomContextValue()];
$generate_placeholder_markup = function($cache_keys = NULL) use ($args) {
$token_render_array = [
'#lazy_builder' => ['Drupal\Tests\Core\Render\PlaceholdersTest::callback', $args],
];
if (is_array($cache_keys)) {
$token_render_array['#cache']['keys'] = $cache_keys;
}
$token = Crypt::hashBase64(serialize($token_render_array));
// \Drupal\Core\Render\Markup::create() is necessary as the render
// system would mangle this markup. As this is exactly what happens at
// runtime this is a valid use-case.
return Markup::create('
This is a rendered placeholder!
', '#attached' => [ 'drupalSettings' => [ 'dynamic_animal' => $args[0], ], ], '#cache' => [ 'contexts' => [], 'tags' => [], 'max-age' => Cache::PERMANENT, ], ], ]; // Case three: render array that has a placeholder that is: // - automatically created, and automatically triggered due to max-age=0 // - uncacheable $element_without_cache_keys = $base_element_a2; $expected_placeholder_render_array = $extract_placeholder_render_array($base_element_a2['placeholder']); $cases[] = [ $element_without_cache_keys, $args, $expected_placeholder_render_array, FALSE, [], [], [], ]; // Case four: render array that has a placeholder that is: // - automatically created, but automatically triggered due to max-age=0 // - cacheable $element_with_cache_keys = $base_element_a2; $element_with_cache_keys['placeholder']['#cache']['keys'] = $keys; $expected_placeholder_render_array['#cache']['keys'] = $keys; $cases[] = [ $element_with_cache_keys, $args, $expected_placeholder_render_array, FALSE, [], [], [], ]; // Case five: render array that has a placeholder that is: // - automatically created, and automatically triggered due to high // cardinality cache contexts // - uncacheable $element_without_cache_keys = $base_element_a3; $expected_placeholder_render_array = $extract_placeholder_render_array($base_element_a3['placeholder']); $cases[] = [ $element_without_cache_keys, $args, $expected_placeholder_render_array, FALSE, [], [], [], ]; // Case six: render array that has a placeholder that is: // - automatically created, and automatically triggered due to high // cardinality cache contexts // - cacheable $element_with_cache_keys = $base_element_a3; $element_with_cache_keys['placeholder']['#cache']['keys'] = $keys; $expected_placeholder_render_array['#cache']['keys'] = $keys; // The CID parts here consist of the cache keys plus the 'user' cache // context, which in this unit test is simply the given cache context token, // see \Drupal\Tests\Core\Render\RendererTestBase::setUp(). $cid_parts = array_merge($keys, ['user']); $cases[] = [ $element_with_cache_keys, $args, $expected_placeholder_render_array, $cid_parts, [], [], [ '#markup' => 'This is a rendered placeholder!
', '#attached' => [ 'drupalSettings' => [ 'dynamic_animal' => $args[0], ], ], '#cache' => [ 'contexts' => ['user'], 'tags' => [], 'max-age' => Cache::PERMANENT, ], ], ]; // Case seven: render array that has a placeholder that is: // - automatically created, and automatically triggered due to high // invalidation frequency cache tags // - uncacheable $element_without_cache_keys = $base_element_a4; $expected_placeholder_render_array = $extract_placeholder_render_array($base_element_a4['placeholder']); $cases[] = [ $element_without_cache_keys, $args, $expected_placeholder_render_array, FALSE, [], [], [], ]; // Case eight: render array that has a placeholder that is: // - automatically created, and automatically triggered due to high // invalidation frequency cache tags // - cacheable $element_with_cache_keys = $base_element_a4; $element_with_cache_keys['placeholder']['#cache']['keys'] = $keys; $expected_placeholder_render_array['#cache']['keys'] = $keys; $cases[] = [ $element_with_cache_keys, $args, $expected_placeholder_render_array, $keys, [], [], [ '#markup' => 'This is a rendered placeholder!
', '#attached' => [ 'drupalSettings' => [ 'dynamic_animal' => $args[0], ], ], '#cache' => [ 'contexts' => [], 'tags' => ['current-temperature'], 'max-age' => Cache::PERMANENT, ], ], ]; // Case nine: render array that DOES NOT have a placeholder that is: // - NOT created, despite max-age=0 that is bubbled // - uncacheable // (because the render element with #lazy_builder does not have #cache[keys] // and hence the max-age=0 bubbles up further) // @todo in https://www.drupal.org/node/2559847 // Case ten: render array that has a placeholder that is: // - automatically created, and automatically triggered due to max-age=0 // that is bubbled // - cacheable // @todo in https://www.drupal.org/node/2559847 // Case eleven: render array that DOES NOT have a placeholder that is: // - NOT created, despite high cardinality cache contexts that are bubbled // - uncacheable $element_without_cache_keys = $base_element_a6; $expected_placeholder_render_array = $extract_placeholder_render_array($base_element_a6['placeholder']); $cases[] = [ $element_without_cache_keys, $args, $expected_placeholder_render_array, FALSE, ['user'], [], [], ]; // Case twelve: render array that has a placeholder that is: // - automatically created, and automatically triggered due to high // cardinality cache contexts that are bubbled // - cacheable $element_with_cache_keys = $base_element_a6; $element_with_cache_keys['placeholder']['#cache']['keys'] = $keys; $expected_placeholder_render_array['#cache']['keys'] = $keys; $cases[] = [ $element_with_cache_keys, $args, $expected_placeholder_render_array, $keys, ['user'], [], [ '#markup' => 'This is a rendered placeholder!
', '#attached' => [ 'drupalSettings' => [ 'dynamic_animal' => $args[0], ], ], '#cache' => [ 'contexts' => ['user'], 'tags' => [], 'max-age' => Cache::PERMANENT, ], ], ]; // Case thirteen: render array that has a placeholder that is: // - automatically created, and automatically triggered due to high // invalidation frequency cache tags that are bubbled // - uncacheable $element_without_cache_keys = $base_element_a7; $expected_placeholder_render_array = $extract_placeholder_render_array($base_element_a7['placeholder']); $cases[] = [ $element_without_cache_keys, $args, $expected_placeholder_render_array, FALSE, [], ['current-temperature'], [], ]; // Case fourteen: render array that has a placeholder that is: // - automatically created, and automatically triggered due to high // invalidation frequency cache tags that are bubbled // - cacheable $element_with_cache_keys = $base_element_a7; $element_with_cache_keys['placeholder']['#cache']['keys'] = $keys; $expected_placeholder_render_array['#cache']['keys'] = $keys; $cases[] = [ $element_with_cache_keys, $args, $expected_placeholder_render_array, $keys, [], [], [ '#markup' => 'This is a rendered placeholder!
', '#attached' => [ 'drupalSettings' => [ 'dynamic_animal' => $args[0], ], ], '#cache' => [ 'contexts' => [], 'tags' => ['current-temperature'], 'max-age' => Cache::PERMANENT, ], ], ]; // Case fifteen: render array that has a placeholder that is: // - manually created // - uncacheable $x = $base_element_b; $expected_placeholder_render_array = $x['#attached']['placeholders'][(string) $generate_placeholder_markup()]; unset($x['#attached']['placeholders'][(string) $generate_placeholder_markup()]['#cache']); $cases[] = [ $x, $args, $expected_placeholder_render_array, FALSE, [], [], [], ]; // Case sixteen: render array that has a placeholder that is: // - manually created // - cacheable $x = $base_element_b; $x['#markup'] = $placeholder_markup = $generate_placeholder_markup($keys); $placeholder_markup = (string) $placeholder_markup; $x['#attached']['placeholders'] = [ $placeholder_markup => [ '#lazy_builder' => ['Drupal\Tests\Core\Render\PlaceholdersTest::callback', $args], '#cache' => ['keys' => $keys], ], ]; $expected_placeholder_render_array = $x['#attached']['placeholders'][$placeholder_markup]; $cases[] = [ $x, $args, $expected_placeholder_render_array, $keys, [], [], [ '#markup' => 'This is a rendered placeholder!
', '#attached' => [ 'drupalSettings' => [ 'dynamic_animal' => $args[0], ], ], '#cache' => [ 'contexts' => [], 'tags' => [], 'max-age' => Cache::PERMANENT, ], ], ]; return $cases; } /** * Generates an element with a placeholder. * * @return array * An array containing: * - A render array containing a placeholder. * - The context used for that #lazy_builder callback. */ protected function generatePlaceholderElement() { $args = [$this->randomContextValue()]; $test_element = []; $test_element['#attached']['drupalSettings']['foo'] = 'bar'; $test_element['placeholder']['#cache']['keys'] = ['placeholder', 'output', 'can', 'be', 'render', 'cached', 'too']; $test_element['placeholder']['#cache']['contexts'] = []; $test_element['placeholder']['#create_placeholder'] = TRUE; $test_element['placeholder']['#lazy_builder'] = ['Drupal\Tests\Core\Render\PlaceholdersTest::callback', $args]; return [$test_element, $args]; } /** * @param false|array $cid_parts * @param array $expected_data * FALSE if no render cache item is expected, a render array with the * expected values if a render cache item is expected. * @param string[] $bubbled_cache_contexts * Additional cache contexts that were bubbled when the placeholder was * rendered. */ protected function assertPlaceholderRenderCache($cid_parts, array $bubbled_cache_contexts, array $expected_data) { if ($cid_parts !== FALSE) { if ($bubbled_cache_contexts) { // Verify render cached placeholder. $cached_element = $this->memoryCache->get(implode(':', $cid_parts))->data; $expected_redirect_element = [ '#cache_redirect' => TRUE, '#cache' => $expected_data['#cache'] + [ 'keys' => $cid_parts, 'bin' => 'render', ], ]; $this->assertEquals($expected_redirect_element, $cached_element, 'The correct cache redirect exists.'); } // Verify render cached placeholder. $cached = $this->memoryCache->get(implode(':', array_merge($cid_parts, $bubbled_cache_contexts))); $cached_element = $cached->data; $this->assertEquals($expected_data, $cached_element, 'The correct data is cached: the stored #markup and #attached properties are not affected by the placeholder being replaced.'); } } /** * @covers ::render * @covers ::doRender * * @dataProvider providerPlaceholders */ public function testUncacheableParent($element, $args, array $expected_placeholder_render_array, $placeholder_cid_parts, array $bubbled_cache_contexts, array $bubbled_cache_tags, array $placeholder_expected_render_cache_array) { if ($placeholder_cid_parts) { $this->setupMemoryCache(); } else { $this->setUpUnusedCache(); } $this->setUpRequest('GET'); // No #cache on parent element. $element['#prefix'] = '#cache disabled
'; $output = $this->renderer->renderRoot($element); $this->assertSame('#cache disabled
This is a rendered placeholder!
', (string) $output, 'Output is overridden.'); $this->assertSame('#cache disabled
This is a rendered placeholder!
', (string) $element['#markup'], '#markup is overridden.'); $expected_js_settings = [ 'foo' => 'bar', 'dynamic_animal' => $args[0], ]; $this->assertSame($element['#attached']['drupalSettings'], $expected_js_settings, '#attached is modified; both the original JavaScript setting and the one added by the placeholder #lazy_builder callback exist.'); $this->assertPlaceholderRenderCache($placeholder_cid_parts, $bubbled_cache_contexts, $placeholder_expected_render_cache_array); } /** * @covers ::render * @covers ::doRender * @covers \Drupal\Core\Render\RenderCache::get * @covers \Drupal\Core\Render\RenderCache::set * @covers \Drupal\Core\Render\RenderCache::createCacheID * * @dataProvider providerPlaceholders */ public function testCacheableParent($test_element, $args, array $expected_placeholder_render_array, $placeholder_cid_parts, array $bubbled_cache_contexts, array $bubbled_cache_tags, array $placeholder_expected_render_cache_array) { $element = $test_element; $this->setupMemoryCache(); $this->setUpRequest('GET'); $token = Crypt::hashBase64(serialize($expected_placeholder_render_array)); $placeholder_callback = $expected_placeholder_render_array['#lazy_builder'][0]; $expected_placeholder_markup = '#cache enabled, GET
'; $output = $this->renderer->renderRoot($element); $this->assertSame('#cache enabled, GET
This is a rendered placeholder!
', (string) $output, 'Output is overridden.'); $this->assertTrue(isset($element['#printed']), 'No cache hit'); $this->assertSame('#cache enabled, GET
This is a rendered placeholder!
', (string) $element['#markup'], '#markup is overridden.'); $expected_js_settings = [ 'foo' => 'bar', 'dynamic_animal' => $args[0], ]; $this->assertSame($element['#attached']['drupalSettings'], $expected_js_settings, '#attached is modified; both the original JavaScript setting and the one added by the placeholder #lazy_builder callback exist.'); $this->assertPlaceholderRenderCache($placeholder_cid_parts, $bubbled_cache_contexts, $placeholder_expected_render_cache_array); // GET request: validate cached data. $cached = $this->memoryCache->get('placeholder_test_GET'); // There are three edge cases, where the shape of the render cache item for // the parent (with CID 'placeholder_test_GET') is vastly different. These // are the cases where: // - the placeholder is uncacheable (because it has no #cache[keys]), and; // - cacheability metadata that meets auto_placeholder_conditions is bubbled $has_uncacheable_lazy_builder = !isset($test_element['placeholder']['#cache']['keys']) && isset($test_element['placeholder']['#lazy_builder']); // Edge cases: always where both bubbling of an auto-placeholdering // condition happens from within a #lazy_builder that is uncacheable. // - uncacheable + A5 (cache max-age) // @todo in https://www.drupal.org/node/2559847 // - uncacheable + A6 (cache context) $edge_case_a6_uncacheable = $has_uncacheable_lazy_builder && $test_element['placeholder']['#lazy_builder'][0] === 'Drupal\Tests\Core\Render\PlaceholdersTest::callbackPerUser'; // - uncacheable + A7 (cache tag) $edge_case_a7_uncacheable = $has_uncacheable_lazy_builder && $test_element['placeholder']['#lazy_builder'][0] === 'Drupal\Tests\Core\Render\PlaceholdersTest::callbackTagCurrentTemperature'; // The redirect-cacheable edge case: a high-cardinality cache context is // bubbled from a #lazy_builder callback for an uncacheable placeholder. The // element containing the uncacheable placeholder has cache keys set, and // due to the bubbled cache contexts it creates a cache redirect. if ($edge_case_a6_uncacheable) { $cached_element = $cached->data; $expected_redirect = [ '#cache_redirect' => TRUE, '#cache' => [ 'keys' => ['placeholder_test_GET'], 'contexts' => ['user'], 'tags' => [], 'max-age' => Cache::PERMANENT, 'bin' => 'render', ], ]; $this->assertEquals($expected_redirect, $cached_element); // Follow the redirect. $cached_element = $this->memoryCache->get('placeholder_test_GET:' . implode(':', $bubbled_cache_contexts))->data; $expected_element = [ '#markup' => '#cache enabled, GET
This is a rendered placeholder!
', '#attached' => [ 'drupalSettings' => [ 'foo' => 'bar', 'dynamic_animal' => $args[0], ], ], '#cache' => [ 'contexts' => $bubbled_cache_contexts, 'tags' => [], 'max-age' => Cache::PERMANENT, ], ]; $this->assertEquals($expected_element, $cached_element, 'The parent is render cached with a redirect in ase a cache context is bubbled from an uncacheable child (no #cache[keys]) with a #lazy_builder.'); } // The normally cacheable edge case: a high-invalidation frequency cache tag // is bubbled from a #lazy_builder callback for an uncacheable placeholder. // The element containing the uncacheable placeholder has cache keys set, // and also has the bubbled cache tags. elseif ($edge_case_a7_uncacheable) { $cached_element = $cached->data; $expected_element = [ '#markup' => '#cache enabled, GET
This is a rendered placeholder!
', '#attached' => [ 'drupalSettings' => [ 'foo' => 'bar', 'dynamic_animal' => $args[0], ], ], '#cache' => [ 'contexts' => [], 'tags' => $bubbled_cache_tags, 'max-age' => Cache::PERMANENT, ], ]; $this->assertEquals($expected_element, $cached_element, 'The correct data is cached: the stored #markup and #attached properties are not affected by placeholder #lazy_builder callbacks.'); } // The regular case. else { $cached_element = $cached->data; $expected_element = [ '#markup' => '#cache enabled, GET
' . $expected_placeholder_markup, '#attached' => [ 'drupalSettings' => [ 'foo' => 'bar', ], 'placeholders' => [ $expected_placeholder_markup => [ '#lazy_builder' => ['Drupal\Tests\Core\Render\PlaceholdersTest::callback', $args], ], ], ], '#cache' => [ 'contexts' => [], 'tags' => $bubbled_cache_tags, 'max-age' => Cache::PERMANENT, ], ]; $expected_element['#attached']['placeholders'][$expected_placeholder_markup] = $expected_placeholder_render_array; $this->assertEquals($expected_element, $cached_element, 'The correct data is cached: the stored #markup and #attached properties are not affected by placeholder #lazy_builder callbacks.'); } // GET request: #cache enabled, cache hit. $element = $test_element; $element['#cache'] = ['keys' => ['placeholder_test_GET']]; $element['#prefix'] = '#cache enabled, GET
'; $output = $this->renderer->renderRoot($element); $this->assertSame('#cache enabled, GET
This is a rendered placeholder!
', (string) $output, 'Output is overridden.'); $this->assertFalse(isset($element['#printed']), 'Cache hit'); $this->assertSame('#cache enabled, GET
This is a rendered placeholder!
', (string) $element['#markup'], '#markup is overridden.'); $expected_js_settings = [ 'foo' => 'bar', 'dynamic_animal' => $args[0], ]; $this->assertSame($element['#attached']['drupalSettings'], $expected_js_settings, '#attached is modified; both the original JavaScript setting and the one added by the placeholder #lazy_builder callback exist.'); } /** * @covers ::render * @covers ::doRender * @covers \Drupal\Core\Render\RenderCache::get * @covers ::replacePlaceholders * * @dataProvider providerPlaceholders */ public function testCacheableParentWithPostRequest($test_element, $args) { $this->setUpUnusedCache(); // Verify behavior when handling a non-GET request, e.g. a POST request: // also in that case, placeholders must be replaced. $this->setUpRequest('POST'); // POST request: #cache enabled, cache miss. $element = $test_element; $element['#cache'] = ['keys' => ['placeholder_test_POST']]; $element['#prefix'] = '#cache enabled, POST
'; $output = $this->renderer->renderRoot($element); $this->assertSame('#cache enabled, POST
This is a rendered placeholder!
', (string) $output, 'Output is overridden.'); $this->assertTrue(isset($element['#printed']), 'No cache hit'); $this->assertSame('#cache enabled, POST
This is a rendered placeholder!
', (string) $element['#markup'], '#markup is overridden.'); $expected_js_settings = [ 'foo' => 'bar', 'dynamic_animal' => $args[0], ]; $this->assertSame($element['#attached']['drupalSettings'], $expected_js_settings, '#attached is modified; both the original JavaScript setting and the one added by the placeholder #lazy_builder callback exist.'); // Even when the child element's placeholder is cacheable, it should not // generate a render cache item. $this->assertPlaceholderRenderCache(FALSE, [], []); } /** * @covers ::render * @covers ::doRender * @covers \Drupal\Core\Render\RenderCache::get * @covers \Drupal\Core\Render\PlaceholderingRenderCache::get * @covers \Drupal\Core\Render\PlaceholderingRenderCache::set * @covers ::replacePlaceholders * * @dataProvider providerPlaceholders */ public function testPlaceholderingDisabledForPostRequests($test_element, $args) { $this->setUpUnusedCache(); $this->setUpRequest('POST'); $element = $test_element; // Render without replacing placeholders, to allow this test to see which // #attached[placeholders] there are, if any. $this->renderer->executeInRenderContext(new RenderContext(), function () use (&$element) { return $this->renderer->render($element); }); // Only test cases where the placeholders have been specified manually are // allowed to have placeholders. This means that of the different situations // listed in providerPlaceholders(), only type B can have attached // placeholders. Everything else, whether: // 1. manual placeholdering // 2. automatic placeholdering via already-present cacheability metadata // 3. automatic placeholdering via bubbled cacheability metadata // All three of those should NOT result in placeholders. if (!isset($test_element['#attached']['placeholders'])) { $this->assertFalse(isset($element['#attached']['placeholders']), 'No placeholders created.'); } } /** * Tests a placeholder that adds another placeholder. * * E.g. when rendering a node in a placeholder the rendering of that node * needs a placeholder of its own to be executed (to render the node links). * * @covers ::render * @covers ::doRender * @covers ::replacePlaceholders */ public function testRecursivePlaceholder() { $args = [$this->randomContextValue()]; $element = []; $element['#create_placeholder'] = TRUE; $element['#lazy_builder'] = ['Drupal\Tests\Core\Render\RecursivePlaceholdersTest::callback', $args]; $output = $this->renderer->renderRoot($element); $this->assertEquals('This is a rendered placeholder!
', $output, 'The output has been modified by the indirect, recursive placeholder #lazy_builder callback.'); $this->assertSame((string) $element['#markup'], 'This is a rendered placeholder!
', '#markup is overridden by the indirect, recursive placeholder #lazy_builder callback.'); $expected_js_settings = [ 'dynamic_animal' => $args[0], ]; $this->assertSame($element['#attached']['drupalSettings'], $expected_js_settings, '#attached is modified by the indirect, recursive placeholder #lazy_builder callback.'); } /** * @covers ::render * @covers ::doRender */ public function testInvalidLazyBuilder() { $element = []; $element['#lazy_builder'] = '\Drupal\Tests\Core\Render\PlaceholdersTest::callback'; $this->setExpectedException(\DomainException::class, 'The #lazy_builder property must have an array as a value.'); $this->renderer->renderRoot($element); } /** * @covers ::render * @covers ::doRender */ public function testInvalidLazyBuilderArguments() { $element = []; $element['#lazy_builder'] = ['\Drupal\Tests\Core\Render\PlaceholdersTest::callback', 'arg1', 'arg2']; $this->setExpectedException(\DomainException::class, 'The #lazy_builder property must have an array as a value, containing two values: the callback, and the arguments for the callback.'); $this->renderer->renderRoot($element); } /** * @covers ::render * @covers ::doRender * * @see testNonScalarLazybuilderCallbackContext */ public function testScalarLazybuilderCallbackContext() { $element = []; $element['#lazy_builder'] = ['\Drupal\Tests\Core\Render\PlaceholdersTest::callback', [ 'string' => 'foo', 'bool' => TRUE, 'int' => 1337, 'float' => 3.14, 'null' => NULL, ]]; $result = $this->renderer->renderRoot($element); $this->assertInstanceOf('\Drupal\Core\Render\Markup', $result); $this->assertEquals('This is a rendered placeholder!
', (string) $result); } /** * @covers ::render * @covers ::doRender */ public function testNonScalarLazybuilderCallbackContext() { $element = []; $element['#lazy_builder'] = ['\Drupal\Tests\Core\Render\PlaceholdersTest::callback', [ 'string' => 'foo', 'bool' => TRUE, 'int' => 1337, 'float' => 3.14, 'null' => NULL, // array is not one of the scalar types. 'array' => ['hi!'], ]]; $this->setExpectedException(\DomainException::class, "A #lazy_builder callback's context may only contain scalar values or NULL."); $this->renderer->renderRoot($element); } /** * @covers ::render * @covers ::doRender */ public function testChildrenPlusBuilder() { $element = []; $element['#lazy_builder'] = ['Drupal\Tests\Core\Render\RecursivePlaceholdersTest::callback', []]; $element['child_a']['#markup'] = 'Oh hai!'; $element['child_b']['#markup'] = 'kthxbai'; $this->setExpectedException(\DomainException::class, 'When a #lazy_builder callback is specified, no children can exist; all children must be generated by the #lazy_builder callback. You specified the following children: child_a, child_b.'); $this->renderer->renderRoot($element); } /** * @covers ::render * @covers ::doRender */ public function testPropertiesPlusBuilder() { $element = []; $element['#lazy_builder'] = ['Drupal\Tests\Core\Render\RecursivePlaceholdersTest::callback', []]; $element['#llama'] = '#awesome'; $element['#piglet'] = '#cute'; $this->setExpectedException(\DomainException::class, 'When a #lazy_builder callback is specified, no properties can exist; all properties must be generated by the #lazy_builder callback. You specified the following properties: #llama, #piglet.'); $this->renderer->renderRoot($element); } /** * @covers ::render * @covers ::doRender */ public function testCreatePlaceholderPropertyWithoutLazyBuilder() { $element = []; $element['#create_placeholder'] = TRUE; $this->setExpectedException(\LogicException::class, 'When #create_placeholder is set, a #lazy_builder callback must be present as well.'); $this->renderer->renderRoot($element); } /** * Create an element with a child and subchild. Each element has the same * #lazy_builder callback, but with different contexts. They don't modify * markup, only attach additional drupalSettings. * * @covers ::render * @covers ::doRender * @covers \Drupal\Core\Render\RenderCache::get * @covers ::replacePlaceholders */ public function testRenderChildrenPlaceholdersDifferentArguments() { $this->setUpRequest(); $this->setupMemoryCache(); $this->cacheContextsManager->expects($this->any()) ->method('convertTokensToKeys') ->willReturnArgument(0); $this->controllerResolver->expects($this->any()) ->method('getControllerFromDefinition') ->willReturnArgument(0); $this->setupThemeManagerForDetails(); $args_1 = ['foo', TRUE]; $args_2 = ['bar', TRUE]; $args_3 = ['baz', TRUE]; $test_element = $this->generatePlaceholdersWithChildrenTestElement($args_1, $args_2, $args_3); $element = $test_element; $output = $this->renderer->renderRoot($element); $expected_output = <<