diff --git a/core/modules/system/lib/Drupal/system/Tests/Common/RenderTest.php b/core/modules/system/lib/Drupal/system/Tests/Common/RenderTest.php index 94b2d10..eb70804 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Common/RenderTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Common/RenderTest.php @@ -10,6 +10,7 @@ use Drupal\Component\Utility\Html; use Drupal\Component\Utility\Json; use Drupal\simpletest\DrupalUnitTestBase; +use Drupal\Component\Utility\Crypt; /** * Tests drupal_render(). @@ -542,6 +543,249 @@ function testDrupalRenderPostRenderCache() { } /** + * Tests post-render cache-integrated 'render_cache_placeholder' element. + */ + function testDrupalRenderRenderCachePlaceholder() { + $context = array( + 'bar' => $this->randomContextValue(), + 'token' => Crypt::randomBytesBase64(55), + ); + $callback = 'common_test_post_render_cache_placeholder'; + $test_element = array( + '#post_render_cache' => array( + $callback => array( + $context + ), + ), + '#markup' => drupal_render_cache_generate_placeholder($callback, $context, $context['token']), + '#prefix' => '', + '#suffix' => '' + ); + $expected_output = '' . $context['bar'] . ''; + + // #cache disabled. + drupal_static_reset('_drupal_add_js'); + $element = $test_element; + $output = drupal_render($element); + $this->assertIdentical($output, $expected_output, 'Placeholder was replaced in output'); + $settings = $this->parseDrupalSettings(drupal_get_js()); + $this->assertIdentical($settings['common_test'], $context, '#attached is modified; JavaScript setting is added to page.'); + + // The cache system is turned off for POST requests. + $request_method = \Drupal::request()->getMethod(); + \Drupal::request()->setMethod('GET'); + + // GET request: #cache enabled, cache miss. + drupal_static_reset('_drupal_add_js'); + $element = $test_element; + $element['#cache'] = array('cid' => 'render_cache_placeholder_test_GET'); + $output = drupal_render($element); + $this->assertIdentical($output, $expected_output, 'Placeholder was replaced in output'); + $this->assertTrue(isset($element['#printed']), 'No cache hit'); + $this->assertIdentical($element['#markup'], $expected_output, 'Placeholder was replaced in #markup.'); + $settings = $this->parseDrupalSettings(drupal_get_js()); + $this->assertIdentical($settings['common_test'], $context, '#attached is modified; JavaScript setting is added to page.'); + + // GET request: validate cached data. + $expected_token = $element['#post_render_cache']['common_test_post_render_cache_placeholder'][0]['token']; + $element = array('#cache' => array('cid' => 'render_cache_placeholder_test_GET')); + $cached_element = \Drupal::cache('render')->get(drupal_render_cid_create($element))->data; + // Parse unique token out of the cached markup. + $dom = Html::load($cached_element['#markup']); + $xpath = new \DOMXPath($dom); + $nodes = $xpath->query('//*[@token]'); + $this->assertTrue($nodes->length, 'The token attribute was found in the cached markup'); + $token = ''; + if ($nodes->length) { + $token = $nodes->item(0)->getAttribute('token'); + } + $this->assertIdentical($token, $expected_token, 'The tokens are identical'); + // Verify the token is in the cached element. + $expected_element = array( + '#markup' => '', + '#post_render_cache' => array( + 'common_test_post_render_cache_placeholder' => array( + $context + ), + ), + '#cache' => array('tags' => array()), + ); + $this->assertIdentical($cached_element, $expected_element, 'The correct data is cached: the stored #markup and #attached properties are not affected by #post_render_cache callbacks.'); + + // GET request: #cache enabled, cache hit. + drupal_static_reset('_drupal_add_js'); + $element = $test_element; + $element['#cache'] = array('cid' => 'render_cache_placeholder_test_GET'); + $output = drupal_render($element); + $this->assertIdentical($output, $expected_output, 'Placeholder was replaced in output'); + $this->assertFalse(isset($element['#printed']), 'Cache hit'); + $this->assertIdentical($element['#markup'], $expected_output, 'Placeholder was replaced in #markup.'); + $settings = $this->parseDrupalSettings(drupal_get_js()); + $this->assertIdentical($settings['common_test'], $context, '#attached is modified; JavaScript setting is added to page.'); + + // Restore the previous request method. + \Drupal::request()->setMethod($request_method); + } + + /** + * Tests post-render cache-integrated 'render_cache_placeholder' child + * element. + */ + function testDrupalRenderChildElementRenderCachePlaceholder() { + $container = array( + '#type' => 'container', + ); + $context = array( + 'bar' => $this->randomContextValue(), + 'token' => Crypt::randomBytesBase64(55), + ); + $callback = 'common_test_post_render_cache_placeholder'; + $test_element = array( + '#post_render_cache' => array( + $callback => array( + $context + ), + ), + '#markup' => drupal_render_cache_generate_placeholder($callback, $context, $context['token']), + '#prefix' => '', + '#suffix' => '' + ); + $container['test_element'] = $test_element; + $expected_output = '
' . $context['bar'] . '
' . "\n"; + + // #cache disabled. + drupal_static_reset('_drupal_add_js'); + $element = $container; + $output = drupal_render($element); + $this->assertIdentical($output, $expected_output, 'Placeholder was replaced in output'); + $settings = $this->parseDrupalSettings(drupal_get_js()); + $this->assertIdentical($settings['common_test'], $context, '#attached is modified; JavaScript setting is added to page.'); + + // The cache system is turned off for POST requests. + $request_method = \Drupal::request()->getMethod(); + \Drupal::request()->setMethod('GET'); + + // GET request: #cache enabled, cache miss. + drupal_static_reset('_drupal_add_js'); + $element = $container; + $element['#cache'] = array('cid' => 'render_cache_placeholder_test_GET'); + $element['test_element']['#cache'] = array('cid' => 'render_cache_placeholder_test_child_GET'); + // Simulate element rendering in a template, where sub-items of a renderable + // can be sent to drupal_render() before the parent. + $child = &$element['test_element']; + $element['#children'] = drupal_render($child, TRUE); + // Eventually, drupal_render() gets called on the root element. + $output = drupal_render($element); + $this->assertIdentical($output, $expected_output, 'Placeholder was replaced in output'); + $this->assertTrue(isset($element['#printed']), 'No cache hit'); + $this->assertIdentical($element['#markup'], $expected_output, 'Placeholder was replaced in #markup.'); + $settings = $this->parseDrupalSettings(drupal_get_js()); + $this->assertIdentical($settings['common_test'], $context, '#attached is modified; JavaScript setting is added to page.'); + + // GET request: validate cached data for child element. + $child_tokens = $element['test_element']['#post_render_cache']['common_test_post_render_cache_placeholder'][0]['token']; + $parent_tokens = $element['#post_render_cache']['common_test_post_render_cache_placeholder'][0]['token']; + $expected_token = $child_tokens; + $element = array('#cache' => array('cid' => 'render_cache_placeholder_test_child_GET')); + $cached_element = \Drupal::cache('render')->get(drupal_render_cid_create($element))->data; + // Parse unique token out of the cached markup. + $dom = Html::load($cached_element['#markup']); + $xpath = new \DOMXPath($dom); + $nodes = $xpath->query('//*[@token]'); + $this->assertTrue($nodes->length, 'The token attribute was found in the cached child element markup'); + $token = ''; + if ($nodes->length) { + $token = $nodes->item(0)->getAttribute('token'); + } + debug(array($token, $expected_token,), 'tokens', TRUE); + $this->assertIdentical($token, $expected_token, 'The tokens are identical for the child element'); + // Verify the token is in the cached element. + $expected_element = array( + '#markup' => '', + '#post_render_cache' => array( + 'common_test_post_render_cache_placeholder' => array( + $context, + ), + ), + '#cache' => array('tags' => array()), + ); + debug(array($cached_element, $expected_element,), '', TRUE); + $this->assertIdentical($cached_element, $expected_element, 'The correct data is cached for the child element: the stored #markup and #attached properties are not affected by #post_render_cache callbacks.'); + + // GET request: validate cached data (for the parent/entire render array). + $element = array('#cache' => array('cid' => 'render_cache_placeholder_test_GET')); + $cached_element = \Drupal::cache('render')->get(drupal_render_cid_create($element))->data; + // Parse unique token out of the cached markup. + $dom = Html::load($cached_element['#markup']); + $xpath = new \DOMXPath($dom); + $nodes = $xpath->query('//*[@token]'); + $this->assertTrue($nodes->length, 'The token attribute was found in the cached parent element markup'); + $token = ''; + if ($nodes->length) { + $token = $nodes->item(0)->getAttribute('token'); + } + $this->assertIdentical($token, $expected_token, 'The tokens are identical for the parent element'); + // Verify the token is in the cached element. + $expected_element = array( + '#markup' => '
' . "\n", + '#post_render_cache' => array( + 'common_test_post_render_cache_placeholder' => array( + $context, + ), + ), + '#cache' => array('tags' => array()), + ); + $this->assertIdentical($cached_element, $expected_element, 'The correct data is cached for the parent element: the stored #markup and #attached properties are not affected by #post_render_cache callbacks.'); + + // GET request: validate cached data. + // Check the cache of the child element again after the parent has been + // rendered. + $element = array('#cache' => array('cid' => 'render_cache_placeholder_test_child_GET')); + $cached_element = \Drupal::cache('render')->get(drupal_render_cid_create($element))->data; + // Verify that the child element contains the correct + // render_cache_placeholder markup. + $expected_token = $child_tokens; + $dom = Html::load($cached_element['#markup']); + $xpath = new \DOMXPath($dom); + $nodes = $xpath->query('//*[@token]'); + $this->assertTrue($nodes->length, 'The token attribute was found in the cached child element markup'); + $token = ''; + if ($nodes->length) { + $token = $nodes->item(0)->getAttribute('token'); + } + $this->assertIdentical($token, $expected_token, 'The tokens are identical for the child element'); + // Verify the token is in the cached element. + $expected_element = array( + '#markup' => '', + '#post_render_cache' => array( + 'common_test_post_render_cache_placeholder' => array( + $context, + ), + ), + '#cache' => array('tags' => array()), + ); + $this->assertIdentical($cached_element, $expected_element, 'The correct data is cached for the child element: the stored #markup and #attached properties are not affected by #post_render_cache callbacks.'); + + // GET request: #cache enabled, cache hit. + drupal_static_reset('_drupal_add_js'); + $element = $container; + $element['#cache'] = array('cid' => 'render_cache_placeholder_test_GET'); + // Simulate element rendering in a template, where sub-items of a renderable + // can be sent to drupal_render before the parent. + $child = &$element['test_element']; + $element['#children'] = drupal_render($child, TRUE); + $output = drupal_render($element); + $this->assertIdentical($output, $expected_output, 'Placeholder was replaced in output'); + $this->assertFalse(isset($element['#printed']), 'Cache hit'); + $this->assertIdentical($element['#markup'], $expected_output, 'Placeholder was replaced in #markup.'); + $settings = $this->parseDrupalSettings(drupal_get_js()); + $this->assertIdentical($settings['common_test'], $context, '#attached is modified; JavaScript setting is added to page.'); + + // Restore the previous request method. + \Drupal::request()->setMethod($request_method); + } + + /** * Tests post-render cache callbacks functionality in children elements. */ function testDrupalRenderChildrenPostRenderCache() { diff --git a/core/modules/system/tests/modules/common_test/common_test.module b/core/modules/system/tests/modules/common_test/common_test.module index 1a081df..4982aac 100644 --- a/core/modules/system/tests/modules/common_test/common_test.module +++ b/core/modules/system/tests/modules/common_test/common_test.module @@ -204,8 +204,9 @@ function common_test_post_render_cache(array $element, array $context) { * @return array * A render array. */ -function common_test_post_render_cache_placeholder(array $context) { - $element = array( +function common_test_post_render_cache_placeholder(array $element, array $context) { + $placeholder = drupal_render_cache_generate_placeholder(__FUNCTION__, $context, $context['token']); + $replace_element = array( '#markup' => '' . $context['bar'] . '', '#attached' => array( 'js' => array( @@ -218,6 +219,8 @@ function common_test_post_render_cache_placeholder(array $context) { ), ), ); + $replace = drupal_render($replace_element); + $element['#markup'] = str_replace($placeholder, $replace, $element['#markup']); return $element; }