'drupal_render()', 'description' => 'Performs functional tests on drupal_render().', 'group' => 'Common', ); } function setUp() { parent::setUp(); // There are dependencies in drupal_get_js() on the theme layer so we need // to initialize it. drupal_theme_initialize(); } /** * Tests the output drupal_render() for some elementary input values. */ function testDrupalRenderBasics() { $types = array( array( 'name' => 'null', 'value' => NULL, 'expected' => '', ), array( 'name' => 'no value', 'expected' => '', ), array( 'name' => 'empty string', 'value' => '', 'expected' => '', ), array( 'name' => 'no access', 'value' => array( '#markup' => 'foo', '#access' => FALSE, ), 'expected' => '', ), array( 'name' => 'previously printed', 'value' => array( '#markup' => 'foo', '#printed' => TRUE, ), 'expected' => '', ), array( 'name' => 'printed in prerender', 'value' => array( '#markup' => 'foo', '#pre_render' => array('common_test_drupal_render_printing_pre_render'), ), 'expected' => '', ), // Test that #theme and #theme_wrappers can co-exist on an element. array( 'name' => '#theme and #theme_wrappers basic', 'value' => array( '#theme' => 'common_test_foo', '#foo' => 'foo', '#bar' => 'bar', '#theme_wrappers' => array('container'), '#attributes' => array('class' => 'baz'), ), 'expected' => '
foobar
', ), // Test that #theme_wrappers can disambiguate element attributes shared // with rendering methods that build #children by using the alternate // #theme_wrappers attribute override syntax. array( 'name' => '#theme and #theme_wrappers attribute disambiguation', 'value' => array( '#type' => 'link', '#theme_wrappers' => array( 'container' => array( '#attributes' => array('class' => 'baz'), ), ), '#attributes' => array('id' => 'foo'), '#href' => 'http://drupal.org', '#title' => 'bar', ), 'expected' => '
bar
', ), // Test that #theme_wrappers can disambiguate element attributes when the // "base" attribute is not set for #theme. array( 'name' => '#theme_wrappers attribute disambiguation with undefined #theme attribute', 'value' => array( '#type' => 'link', '#href' => 'http://drupal.org', '#title' => 'foo', '#theme_wrappers' => array( 'container' => array( '#attributes' => array('class' => 'baz'), ), ), ), 'expected' => '
foo
', ), // Two 'container' #theme_wrappers, one using the "base" attributes and // one using an override. array( 'name' => 'Two #theme_wrappers container hooks with different attributes', 'value' => array( '#attributes' => array('class' => 'foo'), '#theme_wrappers' => array( 'container' => array( '#attributes' => array('class' => 'bar'), ), 'container', ), ), 'expected' => '
', ), // Array syntax theme hook suggestion in #theme_wrappers. array( 'name' => '#theme_wrappers implements an array style theme hook suggestion', 'value' => array( '#theme_wrappers' => array(array('container')), '#attributes' => array('class' => 'foo'), ), 'expected' => '
', ), // Test handling of #markup as a fallback for #theme hooks. // Simple #markup with no theme. array( 'name' => 'basic #markup based renderable array', 'value' => array('#markup' => 'foo'), 'expected' => 'foo', ), // Theme suggestion is not implemented, #markup should be rendered. array( 'name' => '#markup fallback for #theme suggestion not implemented', 'value' => array( '#theme' => array('suggestionnotimplemented'), '#markup' => 'foo', ), 'expected' => 'foo', ), // Theme suggestion is not implemented, child #markup should be rendered. array( 'name' => '#markup fallback for child elements, #theme suggestion not implemented', 'value' => array( '#theme' => array('suggestionnotimplemented'), 'child' => array( '#markup' => 'foo', ), ), 'expected' => 'foo', ), // Theme suggestion is implemented but returns empty string, #markup // should not be rendered. array( 'name' => 'Avoid #markup if #theme is implemented but returns an empty string', 'value' => array( '#theme' => array('common_test_empty'), '#markup' => 'foo', ), 'expected' => '', ), // Theme suggestion is implemented but returns empty string, children // should not be rendered. array( 'name' => 'Avoid rendering child elements if #theme is implemented but returns an empty string', 'value' => array( '#theme' => array('common_test_empty'), 'child' => array( '#markup' => 'foo', ), ), 'expected' => '', ), // Test handling of #children and child renderable elements. // #theme is not set, #children is not set and the array has children. array( 'name' => '#theme is not set, #children is not set and array has children', 'value' => array( 'child' => array('#markup' => 'bar'), ), 'expected' => 'bar', ), // #theme is not set, #children is set but empty and the array has // children. array( 'name' => '#theme is not set, #children is an empty string and array has children', 'value' => array( '#children' => '', 'child' => array('#markup' => 'bar'), ), 'expected' => 'bar', ), // #theme is not set, #children is not empty and will be assumed to be the // rendered child elements even though the #markup for 'child' differs. array( 'name' => '#theme is not set, #children is set and array has children', 'value' => array( '#children' => 'foo', 'child' => array('#markup' => 'bar'), ), 'expected' => 'foo', ), // #theme is implemented so the values of both #children and 'child' will // be ignored - it is the responsibility of the theme hook to render these // if appropriate. array( 'name' => '#theme is implemented, #children is set and array has children', 'value' => array( '#theme' => 'common_test_foo', '#children' => 'baz', 'child' => array('#markup' => 'boo'), ), 'expected' => 'foobar', ), // #theme is implemented but #render_children is TRUE. As in the case // where #theme is not set, empty #children means child elements are // rendered recursively. array( 'name' => '#theme is implemented, #render_children is TRUE, #children is empty and array has children', 'value' => array( '#theme' => 'common_test_foo', '#children' => '', '#render_children' => TRUE, 'child' => array( '#markup' => 'boo', ), ), 'expected' => 'boo', ), // #theme is implemented but #render_children is TRUE. As in the case // where #theme is not set, #children will take precedence over 'child'. array( 'name' => '#theme is implemented, #render_children is TRUE, #children is set and array has children', 'value' => array( '#theme' => 'common_test_foo', '#children' => 'baz', '#render_children' => TRUE, 'child' => array( '#markup' => 'boo', ), ), 'expected' => 'baz', ), ); foreach($types as $type) { $this->assertIdentical(drupal_render($type['value']), $type['expected'], '"' . $type['name'] . '" input rendered correctly by drupal_render().'); } } /** * Tests sorting by weight. */ function testDrupalRenderSorting() { $first = $this->randomName(); $second = $this->randomName(); // Build an array with '#weight' set for each element. $elements = array( 'second' => array( '#weight' => 10, '#markup' => $second, ), 'first' => array( '#weight' => 0, '#markup' => $first, ), ); $output = drupal_render($elements); // The lowest weight element should appear last in $output. $this->assertTrue(strpos($output, $second) > strpos($output, $first), 'Elements were sorted correctly by weight.'); // Confirm that the $elements array has '#sorted' set to TRUE. $this->assertTrue($elements['#sorted'], "'#sorted' => TRUE was added to the array"); // Pass $elements through element_children() and ensure it remains // sorted in the correct order. drupal_render() will return an empty string // if used on the same array in the same request. $children = element_children($elements); $this->assertTrue(array_shift($children) == 'first', 'Child found in the correct order.'); $this->assertTrue(array_shift($children) == 'second', 'Child found in the correct order.'); // The same array structure again, but with #sorted set to TRUE. $elements = array( 'second' => array( '#weight' => 10, '#markup' => $second, ), 'first' => array( '#weight' => 0, '#markup' => $first, ), '#sorted' => TRUE, ); $output = drupal_render($elements); // The elements should appear in output in the same order as the array. $this->assertTrue(strpos($output, $second) < strpos($output, $first), 'Elements were not sorted.'); } /** * Tests #attached functionality in children elements. */ function testDrupalRenderChildrenAttached() { // The cache system is turned off for POST requests. $request_method = \Drupal::request()->getMethod(); \Drupal::request()->setMethod('GET'); // Create an element with a child and subchild. Each element loads a // different JavaScript file using #attached. $parent_js = drupal_get_path('module', 'user') . '/user.js'; $child_js = drupal_get_path('module', 'forum') . '/forum.js'; $subchild_js = drupal_get_path('module', 'book') . '/book.js'; $element = array( '#type' => 'details', '#cache' => array( 'keys' => array('simpletest', 'drupal_render', 'children_attached'), ), '#attached' => array('js' => array($parent_js)), '#title' => 'Parent', ); $element['child'] = array( '#type' => 'details', '#attached' => array('js' => array($child_js)), '#title' => 'Child', ); $element['child']['subchild'] = array( '#attached' => array('js' => array($subchild_js)), '#markup' => 'Subchild', ); // Render the element and verify the presence of #attached JavaScript. drupal_render($element); $scripts = drupal_get_js(); $this->assertTrue(strpos($scripts, $parent_js), 'The element #attached JavaScript was included.'); $this->assertTrue(strpos($scripts, $child_js), 'The child #attached JavaScript was included.'); $this->assertTrue(strpos($scripts, $subchild_js), 'The subchild #attached JavaScript was included.'); // Load the element from cache and verify the presence of the #attached // JavaScript. drupal_static_reset('_drupal_add_js'); $element = array('#cache' => array('keys' => array('simpletest', 'drupal_render', 'children_attached'))); $this->assertTrue(strlen(drupal_render($element)) > 0, 'The element was retrieved from cache.'); $scripts = drupal_get_js(); $this->assertTrue(strpos($scripts, $parent_js), 'The element #attached JavaScript was included when loading from cache.'); $this->assertTrue(strpos($scripts, $child_js), 'The child #attached JavaScript was included when loading from cache.'); $this->assertTrue(strpos($scripts, $subchild_js), 'The subchild #attached JavaScript was included when loading from cache.'); // Restore the previous request method. \Drupal::request()->setMethod($request_method); } /** * Tests passing arguments to the theme function. */ function testDrupalRenderThemeArguments() { $element = array( '#theme' => 'common_test_foo', ); // Test that defaults work. $this->assertEqual(drupal_render($element), 'foobar', 'Defaults work'); $element = array( '#theme' => 'common_test_foo', '#foo' => $this->randomName(), '#bar' => $this->randomName(), ); // Tests that passing arguments to the theme function works. $this->assertEqual(drupal_render($element), $element['#foo'] . $element['#bar'], 'Passing arguments to theme functions works'); } /** * Tests caching of an empty render item. */ function testDrupalRenderCache() { // The cache system is turned off for POST requests. $request_method = \Drupal::request()->getMethod(); \Drupal::request()->setMethod('GET'); // Create an empty element. $test_element = array( '#cache' => array( 'cid' => 'render_cache_test', 'tags' => array('render_cache_tag' => TRUE), ), '#markup' => '', 'child' => array( '#cache' => array( 'cid' => 'render_cache_test_child', 'tags' => array('render_cache_tag_child' => array(1, 2)) ), '#markup' => '', ), ); // Render the element and confirm that it goes through the rendering // process (which will set $element['#printed']). $element = $test_element; drupal_render($element); $this->assertTrue(isset($element['#printed']), 'No cache hit'); // Render the element again and confirm that it is retrieved from the cache // instead (so $element['#printed'] will not be set). $element = $test_element; drupal_render($element); $this->assertFalse(isset($element['#printed']), 'Cache hit'); // Test that cache tags are correctly collected from the render element, // including the ones from its subchild. $expected_tags = array( 'render_cache_tag' => TRUE, 'render_cache_tag_child' => array(1 => 1, 2 => 2), ); $actual_tags = drupal_render_collect_cache_tags($test_element); $this->assertEqual($expected_tags, $actual_tags, 'Cache tags were collected from the element and its subchild.'); // Restore the previous request method. \Drupal::request()->setMethod($request_method); } /** * Tests post-render cache callbacks functionality. */ function testDrupalRenderPostRenderCache() { $context = array('foo' => $this->randomString()); $test_element = array(); $test_element['#markup'] = ''; $test_element['#attached']['js'][] = array('type' => 'setting', 'data' => array('foo' => 'bar')); $test_element['#post_render_cache']['common_test_post_render_cache'] = array( $context ); // #cache disabled. drupal_static_reset('_drupal_add_js'); $element = $test_element; $element['#markup'] = '

#cache disabled

'; $output = drupal_render($element); $this->assertIdentical($output, '

overridden

', 'Output is overridden.'); $this->assertIdentical($element['#markup'], '

overridden

', '#markup is overridden.'); $this->assertTrue(!isset($element['#context_test']), '#context_test is not set: impossible to modify $element itself, only possible to modify its #markup and #attached properties.'); $settings = $this->parseDrupalSettings(drupal_get_js()); $this->assertIdentical($settings['foo'], 'bar', 'Original JavaScript setting is added to the page.'); $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' => 'post_render_cache_test_GET'); $element['#markup'] = '

#cache enabled, GET

'; $output = drupal_render($element); $this->assertIdentical($output, '

overridden

', 'Output is overridden.'); $this->assertTrue(isset($element['#printed']), 'No cache hit'); $this->assertIdentical($element['#markup'], '

overridden

', '#markup is overridden.'); $this->assertTrue(!isset($element['#context_test']), '#context_test is not set: impossible to modify $element itself, only possible to modify its #markup and #attached properties.'); $settings = $this->parseDrupalSettings(drupal_get_js()); $this->assertIdentical($settings['foo'], 'bar', 'Original JavaScript setting is added to the page.'); $this->assertIdentical($settings['common_test'], $context, '#attached is modified; JavaScript setting is added to page.'); // GET request: validate cached data. $element = array('#cache' => array('cid' => 'post_render_cache_test_GET')); $cached_element = cache()->get(drupal_render_cid_create($element))->data; $expected_element = array( '#markup' => '

#cache enabled, GET

', '#attached' => $test_element['#attached'], '#post_render_cache' => $test_element['#post_render_cache'], ); $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['#cache'] = array('cid' => 'post_render_cache_test_GET'); $element['#markup'] = '

#cache enabled, GET

'; $output = drupal_render($element); $this->assertIdentical($output, '

overridden

', 'Output is overridden.'); $this->assertFalse(isset($element['#printed']), 'Cache hit'); $this->assertIdentical($element['#markup'], '

overridden

', '#markup is overridden.'); $this->assertTrue(!isset($element['#context_test']), '#context_test is not set: impossible to modify $element itself, only possible to modify its #markup and #attached properties.'); $settings = $this->parseDrupalSettings(drupal_get_js()); $this->assertIdentical($settings['foo'], 'bar', 'Original JavaScript setting is added to the page.'); $this->assertIdentical($settings['common_test'], $context, '#attached is modified; JavaScript setting is added to page.'); // Verify behavior when handling a non-GET request, e.g. a POST request: // also in that case, #post_render_cache callbacks must be called. \Drupal::request()->setMethod('POST'); // POST request: #cache enabled, cache miss. drupal_static_reset('_drupal_add_js'); $element = $test_element; $element['#cache'] = array('cid' => 'post_render_cache_test_POST'); $element['#markup'] = '

#cache enabled, POST

'; $output = drupal_render($element); $this->assertIdentical($output, '

overridden

', 'Output is overridden.'); $this->assertTrue(isset($element['#printed']), 'No cache hit'); $this->assertIdentical($element['#markup'], '

overridden

', '#markup is overridden.'); $this->assertTrue(!isset($element['#context_test']), '#context_test is not set: impossible to modify $element itself, only possible to modify its #markup and #attached properties.'); $settings = $this->parseDrupalSettings(drupal_get_js()); $this->assertIdentical($settings['foo'], 'bar', 'Original JavaScript setting is added to the page.'); $this->assertIdentical($settings['common_test'], $context, '#attached is modified; JavaScript setting is added to page.'); // POST request: Ensure no data was cached. $element = array('#cache' => array('cid' => 'post_render_cache_test_POST')); $cached_element = cache()->get(drupal_render_cid_create($element)); $this->assertFalse($cached_element, 'No data is cached because this is a POST request.'); // Restore the previous request method. \Drupal::request()->setMethod($request_method); } /** * Tests post-render cache callbacks functionality in children elements. */ function testDrupalRenderChildrenPostRenderCache() { // The cache system is turned off for POST requests. $request_method = \Drupal::request()->getMethod(); \Drupal::request()->setMethod('GET'); // Test case 1. // Create an element with a child and subchild. Each element has the same // #post_render_cache callback, but with different contexts. drupal_static_reset('_drupal_add_js'); $context_1 = array('foo' => $this->randomString()); $context_2 = array('bar' => $this->randomString()); $context_3 = array('baz' => $this->randomString()); $test_element = array( '#type' => 'details', '#cache' => array( 'keys' => array('simpletest', 'drupal_render', 'children_post_render_cache'), ), '#post_render_cache' => array( 'common_test_post_render_cache' => array($context_1) ), '#title' => 'Parent', '#attached' => array( 'js' => array( array('type' => 'setting', 'data' => array('foo' => 'bar')) ), ), ); $test_element['child'] = array( '#type' => 'details', '#post_render_cache' => array( 'common_test_post_render_cache' => array($context_2) ), '#title' => 'Child', ); $test_element['child']['subchild'] = array( '#post_render_cache' => array( 'common_test_post_render_cache' => array($context_3) ), '#markup' => 'Subchild', ); $element = $test_element; $output = drupal_render($element); $this->assertIdentical($output, '

overridden

', 'Output is overridden.'); $this->assertTrue(isset($element['#printed']), 'No cache hit'); $this->assertIdentical($element['#markup'], '

overridden

', '#markup is overridden.'); $this->assertTrue(!isset($element['#context_test']), '#context_test is not set: impossible to modify $element itself, only possible to modify its #markup and #attached properties.'); $settings = $this->parseDrupalSettings(drupal_get_js()); $expected_settings = $context_1 + $context_2 + $context_3; $this->assertIdentical($settings['foo'], 'bar', 'Original JavaScript setting is added to the page.'); $this->assertIdentical($settings['common_test'], $expected_settings, '#attached is modified; JavaScript settings for each #post_render_cache callback are added to page.'); // GET request: validate cached data. $element = array('#cache' => $element['#cache']); $cached_element = cache()->get(drupal_render_cid_create($element))->data; $expected_element = array( '#markup' => '
Parent
Child
Subchild
', '#attached' => array( 'js' => array( array('type' => 'setting', 'data' => array('foo' => 'bar')) ), 'library' => array( array('system', 'drupal.collapse'), array('system', 'drupal.collapse'), ), ), '#post_render_cache' => array( 'common_test_post_render_cache' => array( $context_1, $context_2, $context_3, ) ), ); $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; $output = drupal_render($element); $this->assertIdentical($output, '

overridden

', 'Output is overridden.'); $this->assertFalse(isset($element['#printed']), 'Cache hit'); $this->assertTrue(!isset($element['#context_test']), '#context_test is not set: impossible to modify $element itself, only possible to modify its #markup and #attached properties.'); $settings = $this->parseDrupalSettings(drupal_get_js()); $this->assertIdentical($settings['foo'], 'bar', 'Original JavaScript setting is added to the page.'); $this->assertIdentical($settings['common_test'], $expected_settings, '#attached is modified; JavaScript settings for each #post_render_cache callback are added to page.'); // Test case 2. // Create an element with a child and subchild. Each element has the same // #post_render_cache callback, but with different contexts. Both the // parent and the child elements have #cache set. The cached parent element // must contain the pristine child element, i.e. unaffected by its // #post_render_cache callbacks. I.e. the #post_render_cache callbacks may // not yet have run, or otherwise the cached parent element would contain // personalized data, thereby breaking the render cache. drupal_static_reset('_drupal_add_js'); $element = $test_element; $element['#cache']['keys'] = array('simpletest', 'drupal_render', 'children_post_render_cache', 'nested_cache_parent'); $element['child']['#cache']['keys'] = array('simpletest', 'drupal_render', 'children_post_render_cache', 'nested_cache_child'); $output = drupal_render($element); $this->assertIdentical($output, '

overridden

', 'Output is overridden.'); $this->assertTrue(isset($element['#printed']), 'No cache hit'); $this->assertIdentical($element['#markup'], '

overridden

', '#markup is overridden.'); $this->assertTrue(!isset($element['#context_test']), '#context_test is not set: impossible to modify $element itself, only possible to modify its #markup and #attached properties.'); $settings = $this->parseDrupalSettings(drupal_get_js()); $expected_settings = $context_1 + $context_2 + $context_3; $this->assertIdentical($settings['foo'], 'bar', 'Original JavaScript setting is added to the page.'); $this->assertIdentical($settings['common_test'], $expected_settings, '#attached is modified; JavaScript settings for each #post_render_cache callback are added to page.'); // GET request: validate cached data for both the parent and child. $element = $test_element; $element['#cache']['keys'] = array('simpletest', 'drupal_render', 'children_post_render_cache', 'nested_cache_parent'); $element['child']['#cache']['keys'] = array('simpletest', 'drupal_render', 'children_post_render_cache', 'nested_cache_child'); $cached_parent_element = cache()->get(drupal_render_cid_create($element))->data; $cached_child_element = cache()->get(drupal_render_cid_create($element['child']))->data; $expected_parent_element = array( '#markup' => '
Parent
Child
Subchild
', '#attached' => array( 'js' => array( array('type' => 'setting', 'data' => array('foo' => 'bar')) ), 'library' => array( array('system', 'drupal.collapse'), array('system', 'drupal.collapse'), ), ), '#post_render_cache' => array( 'common_test_post_render_cache' => array( $context_1, $context_2, $context_3, ) ), ); $this->assertIdentical($cached_parent_element, $expected_parent_element, 'The correct data is cached for the parent: the stored #markup and #attached properties are not affected by #post_render_cache callbacks.'); $expected_child_element = array( '#markup' => '
Child
Subchild
', '#attached' => array( 'library' => array( array('system', 'drupal.collapse'), ), ), '#post_render_cache' => array( 'common_test_post_render_cache' => array( $context_2, $context_3, ) ), ); $this->assertIdentical($cached_child_element, $expected_child_element, 'The correct data is cached for the child: the stored #markup and #attached properties are not affected by #post_render_cache callbacks.'); // GET request: #cache enabled, cache hit, parent element. drupal_static_reset('_drupal_add_js'); $element = $test_element; $element['#cache']['keys'] = array('simpletest', 'drupal_render', 'children_post_render_cache', 'nested_cache_parent'); $output = drupal_render($element); $this->assertIdentical($output, '

overridden

', 'Output is overridden.'); $this->assertFalse(isset($element['#printed']), 'Cache hit'); $this->assertTrue(!isset($element['#context_test']), '#context_test is not set: impossible to modify $element itself, only possible to modify its #markup and #attached properties.'); $settings = $this->parseDrupalSettings(drupal_get_js()); $this->assertIdentical($settings['foo'], 'bar', 'Original JavaScript setting is added to the page.'); $this->assertIdentical($settings['common_test'], $expected_settings, '#attached is modified; JavaScript settings for each #post_render_cache callback are added to page.'); // GET request: #cache enabled, cache hit, child element. drupal_static_reset('_drupal_add_js'); $element = $test_element; $element['child']['#cache']['keys'] = array('simpletest', 'drupal_render', 'children_post_render_cache', 'nested_cache_child'); $element = $element['child']; $output = drupal_render($element); $this->assertIdentical($output, '

overridden

', 'Output is overridden.'); $this->assertFalse(isset($element['#printed']), 'Cache hit'); $this->assertTrue(!isset($element['#context_test']), '#context_test is not set: impossible to modify $element itself, only possible to modify its #markup and #attached properties.'); $settings = $this->parseDrupalSettings(drupal_get_js()); $expected_settings = $context_2 + $context_3; $this->assertTrue(!isset($settings['foo']), 'Parent JavaScript setting is not added to the page.'); $this->assertIdentical($settings['common_test'], $expected_settings, '#attached is modified; JavaScript settings for each #post_render_cache callback are added to page.'); // Restore the previous request method. \Drupal::request()->setMethod($request_method); } /** * Tests post-render cache-integrated 'render_cache_placeholder' element. */ function testDrupalRenderRenderCachePlaceholder() { $context = array('bar' => $this->randomString()); $test_element = array( '#type' => 'render_cache_placeholder', '#context' => $context, '#callback' => 'common_test_post_render_cache_placeholder', '#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. $element = array('#cache' => array('cid' => 'render_cache_placeholder_test_GET')); $cached_element = cache()->get(drupal_render_cid_create($element))->data; // Parse unique token out of the markup. $dom = filter_dom_load($cached_element['#markup']); $xpath = new \DOMXPath($dom); $nodes = $xpath->query('//*[@token]'); $token = $nodes->item(0)->getAttribute('token'); $expected_element = array( '#markup' => '', '#post_render_cache' => array( 'common_test_post_render_cache_placeholder' => array( $token => $context, ), ), ); $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); } protected function parseDrupalSettings($html) { $startToken = 'drupalSettings = '; $endToken = '}'; $start = strpos($html, $startToken) + strlen($startToken); $end = strrpos($html, $endToken); $json = drupal_substr($html, $start, $end - $start + 1); $parsed_settings = drupal_json_decode($json); return $parsed_settings; } }