View source
<?php
namespace Drupal\Tests\Core\Render;
use Drupal\Component\Utility\Html;
use Drupal\Core\Render\Element;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Render\Markup;
class RendererPlaceholdersTest extends RendererTestBase {
protected function setUp() {
$this->rendererConfig['required_cache_contexts'] = [];
parent::setUp();
}
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 = hash('crc32b', serialize($token_render_array));
return Markup::create('<drupal-render-placeholder callback="Drupal\\Tests\\Core\\Render\\PlaceholdersTest::callback" arguments="' . '0=' . $args[0] . '" token="' . $token . '"></drupal-render-placeholder>');
};
$extract_placeholder_render_array = function ($placeholder_render_array) {
return array_intersect_key($placeholder_render_array, [
'#lazy_builder' => TRUE,
'#cache' => TRUE,
]);
};
$base_element_a1 = [
'#attached' => [
'drupalSettings' => [
'foo' => 'bar',
],
],
'placeholder' => [
'#cache' => [
'contexts' => [],
],
'#create_placeholder' => TRUE,
'#lazy_builder' => [
'Drupal\\Tests\\Core\\Render\\PlaceholdersTest::callback',
$args,
],
],
];
$base_element_a2 = [
'#attached' => [
'drupalSettings' => [
'foo' => 'bar',
],
],
'placeholder' => [
'#cache' => [
'contexts' => [],
'max-age' => 0,
],
'#lazy_builder' => [
'Drupal\\Tests\\Core\\Render\\PlaceholdersTest::callback',
$args,
],
],
];
$base_element_a3 = [
'#attached' => [
'drupalSettings' => [
'foo' => 'bar',
],
],
'placeholder' => [
'#cache' => [
'contexts' => [
'user',
],
],
'#lazy_builder' => [
'Drupal\\Tests\\Core\\Render\\PlaceholdersTest::callback',
$args,
],
],
];
$base_element_a4 = [
'#attached' => [
'drupalSettings' => [
'foo' => 'bar',
],
],
'placeholder' => [
'#cache' => [
'contexts' => [],
'tags' => [
'current-temperature',
],
],
'#lazy_builder' => [
'Drupal\\Tests\\Core\\Render\\PlaceholdersTest::callback',
$args,
],
],
];
$base_element_a5 = [];
$base_element_a6 = [
'#attached' => [
'drupalSettings' => [
'foo' => 'bar',
],
],
'placeholder' => [
'#cache' => [
'contexts' => [],
],
'#lazy_builder' => [
'Drupal\\Tests\\Core\\Render\\PlaceholdersTest::callbackPerUser',
$args,
],
],
];
$base_element_a7 = [
'#attached' => [
'drupalSettings' => [
'foo' => 'bar',
],
],
'placeholder' => [
'#cache' => [
'contexts' => [],
],
'#lazy_builder' => [
'Drupal\\Tests\\Core\\Render\\PlaceholdersTest::callbackTagCurrentTemperature',
$args,
],
],
];
$base_element_b = [
'#markup' => $generate_placeholder_markup(),
'#attached' => [
'drupalSettings' => [
'foo' => 'bar',
],
'placeholders' => [
(string) $generate_placeholder_markup() => [
'#lazy_builder' => [
'Drupal\\Tests\\Core\\Render\\PlaceholdersTest::callback',
$args,
],
],
],
],
];
$keys = [
'placeholder',
'output',
'can',
'be',
'render',
'cached',
'too',
];
$cases = [];
$element_without_cache_keys = $base_element_a1;
$expected_placeholder_render_array = $extract_placeholder_render_array($base_element_a1['placeholder']);
$cases[] = [
$element_without_cache_keys,
$args,
$expected_placeholder_render_array,
FALSE,
[],
[],
[],
];
$element_with_cache_keys = $base_element_a1;
$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' => '<p>This is a rendered placeholder!</p>',
'#attached' => [
'drupalSettings' => [
'dynamic_animal' => $args[0],
],
],
'#cache' => [
'contexts' => [],
'tags' => [],
'max-age' => Cache::PERMANENT,
],
],
];
$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,
[],
[],
[],
];
$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,
[],
[],
[],
];
$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,
[],
[],
[],
];
$element_with_cache_keys = $base_element_a3;
$element_with_cache_keys['placeholder']['#cache']['keys'] = $keys;
$expected_placeholder_render_array['#cache']['keys'] = $keys;
$cid_parts = array_merge($keys, [
'user',
]);
$cases[] = [
$element_with_cache_keys,
$args,
$expected_placeholder_render_array,
$cid_parts,
[],
[],
[
'#markup' => '<p>This is a rendered placeholder!</p>',
'#attached' => [
'drupalSettings' => [
'dynamic_animal' => $args[0],
],
],
'#cache' => [
'contexts' => [
'user',
],
'tags' => [],
'max-age' => Cache::PERMANENT,
],
],
];
$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,
[],
[],
[],
];
$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' => '<p>This is a rendered placeholder!</p>',
'#attached' => [
'drupalSettings' => [
'dynamic_animal' => $args[0],
],
],
'#cache' => [
'contexts' => [],
'tags' => [
'current-temperature',
],
'max-age' => Cache::PERMANENT,
],
],
];
$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',
],
[],
[],
];
$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' => '<p>This is a rendered placeholder!</p>',
'#attached' => [
'drupalSettings' => [
'dynamic_animal' => $args[0],
],
],
'#cache' => [
'contexts' => [
'user',
],
'tags' => [],
'max-age' => Cache::PERMANENT,
],
],
];
$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',
],
[],
];
$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' => '<p>This is a rendered placeholder!</p>',
'#attached' => [
'drupalSettings' => [
'dynamic_animal' => $args[0],
],
],
'#cache' => [
'contexts' => [],
'tags' => [
'current-temperature',
],
'max-age' => Cache::PERMANENT,
],
],
];
$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,
[],
[],
[],
];
$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' => '<p>This is a rendered placeholder!</p>',
'#attached' => [
'drupalSettings' => [
'dynamic_animal' => $args[0],
],
],
'#cache' => [
'contexts' => [],
'tags' => [],
'max-age' => Cache::PERMANENT,
],
],
];
return $cases;
}
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,
];
}
protected function assertPlaceholderRenderCache($cid_parts, array $bubbled_cache_contexts, array $expected_data) {
if ($cid_parts !== FALSE) {
if ($bubbled_cache_contexts) {
$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.');
}
$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.');
}
}
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');
$element['#prefix'] = '<p>#cache disabled</p>';
$output = $this->renderer
->renderRoot($element);
$this
->assertSame('<p>#cache disabled</p><p>This is a rendered placeholder!</p>', (string) $output, 'Output is overridden.');
$this
->assertSame('<p>#cache disabled</p><p>This is a rendered placeholder!</p>', (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);
}
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 = hash('crc32b', serialize($expected_placeholder_render_array));
$placeholder_callback = $expected_placeholder_render_array['#lazy_builder'][0];
$expected_placeholder_markup = '<drupal-render-placeholder callback="' . $placeholder_callback . '" arguments="0=' . $args[0] . '" token="' . $token . '"></drupal-render-placeholder>';
$this
->assertSame($expected_placeholder_markup, Html::normalize($expected_placeholder_markup), 'Placeholder unaltered by Html::normalize() which is used by FilterHtmlCorrector.');
$element['#cache'] = [
'keys' => [
'placeholder_test_GET',
],
];
$element['#prefix'] = '<p>#cache enabled, GET</p>';
$output = $this->renderer
->renderRoot($element);
$this
->assertSame('<p>#cache enabled, GET</p><p>This is a rendered placeholder!</p>', (string) $output, 'Output is overridden.');
$this
->assertTrue(isset($element['#printed']), 'No cache hit');
$this
->assertSame('<p>#cache enabled, GET</p><p>This is a rendered placeholder!</p>', (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);
$cached = $this->memoryCache
->get('placeholder_test_GET');
$has_uncacheable_lazy_builder = !isset($test_element['placeholder']['#cache']['keys']) && isset($test_element['placeholder']['#lazy_builder']);
$edge_case_a6_uncacheable = $has_uncacheable_lazy_builder && $test_element['placeholder']['#lazy_builder'][0] === 'Drupal\\Tests\\Core\\Render\\PlaceholdersTest::callbackPerUser';
$edge_case_a7_uncacheable = $has_uncacheable_lazy_builder && $test_element['placeholder']['#lazy_builder'][0] === 'Drupal\\Tests\\Core\\Render\\PlaceholdersTest::callbackTagCurrentTemperature';
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);
$cached_element = $this->memoryCache
->get('placeholder_test_GET:' . implode(':', $bubbled_cache_contexts))->data;
$expected_element = [
'#markup' => '<p>#cache enabled, GET</p><p>This is a rendered placeholder!</p>',
'#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.');
}
elseif ($edge_case_a7_uncacheable) {
$cached_element = $cached->data;
$expected_element = [
'#markup' => '<p>#cache enabled, GET</p><p>This is a rendered placeholder!</p>',
'#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.');
}
else {
$cached_element = $cached->data;
$expected_element = [
'#markup' => '<p>#cache enabled, GET</p>' . $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.');
}
$element = $test_element;
$element['#cache'] = [
'keys' => [
'placeholder_test_GET',
],
];
$element['#prefix'] = '<p>#cache enabled, GET</p>';
$output = $this->renderer
->renderRoot($element);
$this
->assertSame('<p>#cache enabled, GET</p><p>This is a rendered placeholder!</p>', (string) $output, 'Output is overridden.');
$this
->assertFalse(isset($element['#printed']), 'Cache hit');
$this
->assertSame('<p>#cache enabled, GET</p><p>This is a rendered placeholder!</p>', (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.');
}
public function testCacheableParentWithPostRequest($test_element, $args) {
$this
->setUpUnusedCache();
$this
->setUpRequest('POST');
$element = $test_element;
$element['#cache'] = [
'keys' => [
'placeholder_test_POST',
],
];
$element['#prefix'] = '<p>#cache enabled, POST</p>';
$output = $this->renderer
->renderRoot($element);
$this
->assertSame('<p>#cache enabled, POST</p><p>This is a rendered placeholder!</p>', (string) $output, 'Output is overridden.');
$this
->assertTrue(isset($element['#printed']), 'No cache hit');
$this
->assertSame('<p>#cache enabled, POST</p><p>This is a rendered placeholder!</p>', (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(FALSE, [], []);
}
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('<p>This is a rendered placeholder!</p>', $output, 'The output has been modified by the indirect, recursive placeholder #lazy_builder callback.');
$this
->assertSame((string) $element['#markup'], '<p>This is a rendered placeholder!</p>', '#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.');
}
public function testInvalidLazyBuilder() {
$element = [];
$element['#lazy_builder'] = '\\Drupal\\Tests\\Core\\Render\\PlaceholdersTest::callback';
$this->renderer
->renderRoot($element);
}
public function testInvalidLazyBuilderArguments() {
$element = [];
$element['#lazy_builder'] = [
'\\Drupal\\Tests\\Core\\Render\\PlaceholdersTest::callback',
'arg1',
'arg2',
];
$this->renderer
->renderRoot($element);
}
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('<p>This is a rendered placeholder!</p>', (string) $result);
}
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' => [
'hi!',
],
],
];
$this->renderer
->renderRoot($element);
}
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->renderer
->renderRoot($element);
}
public function testPropertiesPlusBuilder() {
$element = [];
$element['#lazy_builder'] = [
'Drupal\\Tests\\Core\\Render\\RecursivePlaceholdersTest::callback',
[],
];
$element['#llama'] = '#awesome';
$element['#piglet'] = '#cute';
$this->renderer
->renderRoot($element);
}
public function testCreatePlaceholderPropertyWithoutLazyBuilder() {
$element = [];
$element['#create_placeholder'] = TRUE;
$this->renderer
->renderRoot($element);
}
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 = <<<HTML
<details>
<summary>Parent</summary>
<div class="details-wrapper"><details>
<summary>Child</summary>
<div class="details-wrapper">Subchild</div>
</details></div>
</details>
HTML;
$this
->assertSame($expected_output, (string) $output, 'Output is not overridden.');
$this
->assertTrue(isset($element['#printed']), 'No cache hit');
$this
->assertSame($expected_output, (string) $element['#markup'], '#markup is not overridden.');
$expected_js_settings = [
'foo' => 'bar',
'dynamic_animal' => [
$args_1[0] => TRUE,
$args_2[0] => TRUE,
$args_3[0] => TRUE,
],
];
$this
->assertSame($element['#attached']['drupalSettings'], $expected_js_settings, '#attached is modified; both the original JavaScript setting and the ones added by each placeholder #lazy_builder callback exist.');
$cached_element = $this->memoryCache
->get('simpletest:drupal_render:children_placeholders')->data;
$expected_element = [
'#attached' => [
'drupalSettings' => [
'foo' => 'bar',
],
'placeholders' => [
'parent-x-parent' => [
'#lazy_builder' => [
__NAMESPACE__ . '\\PlaceholdersTest::callback',
$args_1,
],
],
'child-x-child' => [
'#lazy_builder' => [
__NAMESPACE__ . '\\PlaceholdersTest::callback',
$args_2,
],
],
'subchild-x-subchild' => [
'#lazy_builder' => [
__NAMESPACE__ . '\\PlaceholdersTest::callback',
$args_3,
],
],
],
],
'#cache' => [
'contexts' => [],
'tags' => [],
'max-age' => Cache::PERMANENT,
],
];
$dom = Html::load($cached_element['#markup']);
$xpath = new \DOMXPath($dom);
$parent = $xpath
->query('//details/summary[text()="Parent"]')->length;
$child = $xpath
->query('//details/div[@class="details-wrapper"]/details/summary[text()="Child"]')->length;
$subchild = $xpath
->query('//details/div[@class="details-wrapper"]/details/div[@class="details-wrapper" and text()="Subchild"]')->length;
$this
->assertTrue($parent && $child && $subchild, 'The correct data is cached: the stored #markup is not affected by placeholder #lazy_builder callbacks.');
unset($cached_element['#markup']);
$this
->assertEquals($cached_element, $expected_element, 'The correct data is cached: the stored #attached properties are not affected by placeholder #lazy_builder callbacks.');
$element = $test_element;
$output = $this->renderer
->renderRoot($element);
$this
->assertSame($expected_output, (string) $output, 'Output is not overridden.');
$this
->assertFalse(isset($element['#printed']), 'Cache hit');
$this
->assertSame($element['#attached']['drupalSettings'], $expected_js_settings, '#attached is modified; both the original JavaScript setting and the ones added by each placeholder #lazy_builder callback exist.');
unset($test_element['#cache']);
$element = $test_element;
$output = $this->renderer
->renderRoot($element);
$this
->assertSame($expected_output, (string) $output, 'Output is not overridden.');
$this
->assertSame($expected_output, (string) $element['#markup'], '#markup is not overridden.');
$this
->assertSame($element['#attached']['drupalSettings'], $expected_js_settings, '#attached is modified; both the original JavaScript setting and the ones added by each #lazy_builder callback exist.');
}
protected function generatePlaceholdersWithChildrenTestElement(array $args_1, array $args_2, array $args_3) {
$test_element = [
'#type' => 'details',
'#cache' => [
'keys' => [
'simpletest',
'drupal_render',
'children_placeholders',
],
],
'#title' => 'Parent',
'#attached' => [
'drupalSettings' => [
'foo' => 'bar',
],
'placeholders' => [
'parent-x-parent' => [
'#lazy_builder' => [
__NAMESPACE__ . '\\PlaceholdersTest::callback',
$args_1,
],
],
],
],
];
$test_element['child'] = [
'#type' => 'details',
'#attached' => [
'placeholders' => [
'child-x-child' => [
'#lazy_builder' => [
__NAMESPACE__ . '\\PlaceholdersTest::callback',
$args_2,
],
],
],
],
'#title' => 'Child',
];
$test_element['child']['subchild'] = [
'#attached' => [
'placeholders' => [
'subchild-x-subchild' => [
'#lazy_builder' => [
__NAMESPACE__ . '\\PlaceholdersTest::callback',
$args_3,
],
],
],
],
'#markup' => 'Subchild',
];
return $test_element;
}
protected function setupThemeManagerForDetails() {
return $this->themeManager
->expects($this
->any())
->method('render')
->willReturnCallback(function ($theme, array $vars) {
$output = <<<'EOS'
<details>
<summary>{{ title }}</summary>
<div class="details-wrapper">{{ children }}</div>
</details>
EOS;
$output = str_replace([
'{{ title }}',
'{{ children }}',
], [
$vars['#title'],
$vars['#children'],
], $output);
return $output;
});
}
}
class RecursivePlaceholdersTest {
public static function callback($animal) {
return [
'another' => [
'#create_placeholder' => TRUE,
'#lazy_builder' => [
'Drupal\\Tests\\Core\\Render\\PlaceholdersTest::callback',
[
$animal,
],
],
],
];
}
}