Issue #2273277 by Wim Leers, effulgentsia, Fabianx: Fixed Figure out a solution for the problematic interaction between the render system and the theme system when using #pre_render.

8.0.x
webchick 2014-08-29 16:33:20 -07:00
parent dcc880bc10
commit 8c68491896
46 changed files with 673 additions and 524 deletions

View File

@ -22,6 +22,7 @@ use Drupal\Component\Utility\Tags;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Render\RenderStackFrame;
use Drupal\Core\Site\Settings;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
@ -1169,20 +1170,6 @@ function _drupal_add_css($data = NULL, $options = NULL) {
function drupal_get_css($css = NULL, $skip_alter = FALSE, $theme_add_css = TRUE) {
$theme_info = \Drupal::theme()->getActiveTheme();
// @todo There is probably a better place to add the CSS from themes,
// see https://www.drupal.org/node/2322617.
if ($theme_add_css) {
foreach ($theme_info->getStyleSheets() as $media => $stylesheets) {
foreach ($stylesheets as $stylesheet) {
_drupal_add_css($stylesheet, array(
'group' => CSS_AGGREGATE_THEME,
'every_page' => TRUE,
'media' => $media
));
}
}
}
if (!isset($css)) {
$css = _drupal_add_css();
}
@ -1685,16 +1672,6 @@ function drupal_js_defaults($data = NULL) {
* @see drupal_js_defaults()
*/
function drupal_get_js($scope = 'header', $javascript = NULL, $skip_alter = FALSE, $is_ajax = FALSE, $theme_add_js = TRUE) {
// @todo There is probably a better place to add the JS from themes,
// see https://www.drupal.org/node/2322617.
$active_theme = \Drupal::theme()->getActiveTheme();
if ($theme_add_js) {
// Add libraries used by this theme.
foreach ($active_theme->getLibraries() as $library) {
_drupal_add_library($library);
}
}
if (!isset($javascript)) {
$javascript = _drupal_add_js();
}
@ -1722,7 +1699,7 @@ function drupal_get_js($scope = 'header', $javascript = NULL, $skip_alter = FALS
// Don't add settings if there is no other JavaScript on the page, unless
// this is an AJAX request.
if (!empty($items['settings']) || $is_ajax) {
$theme_key = $active_theme->getName();
$theme_key = \Drupal::theme()->getActiveTheme()->getName();
// Provide the page with information about the theme that's used, so that
// a later AJAX request can be rendered using the same theme.
// @see \Drupal\Core\Theme\AjaxBasePageNegotiator
@ -1847,6 +1824,41 @@ function drupal_merge_attached(array $a, array $b) {
return NestedArray::mergeDeep($a, $b);
}
/**
* Merges sets of cache tags.
*
* The cache tags array is returned in a format that is valid for
* \Drupal\Core\Cache\CacheBackendInterface::set().
*
* When caching elements, it is necessary to collect all cache tags into a
* single array, from both the element itself and all child elements. This
* allows items to be invalidated based on all tags attached to the content
* they're constituted from.
*
* @param array $tags
* The first set of cache tags.
* @param array $other
* The other set of cache tags.
*
* @return array
* The merged set of cache tags.
*/
function drupal_merge_cache_tags(array $tags, array $other) {
foreach ($other as $namespace => $values) {
if (is_array($values)) {
foreach ($values as $value) {
$tags[$namespace][$value] = $value;
}
}
else {
if (!isset($tags[$namespace])) {
$tags[$namespace] = $values;
}
}
}
return $tags;
}
/**
* Adds attachments to a render() structure.
*
@ -2593,6 +2605,14 @@ function drupal_render_page($page) {
* the children is typically inserted into the markup generated by the parent
* array.
*
* An important aspect of rendering is the bubbling of rendering metadata: cache
* tags, attached assets and #post_render_cache metadata all need to be bubbled
* up. That information is needed once the rendering to a HTML string is
* completed: the resulting HTML for the page must know by which cache tags it
* should be invalidated, which (CSS and JavaScript) assets must be loaded, and
* which #post_render_cache callbacks should be executed. A stack data structure
* is used to perform this bubbling.
*
* The process of rendering an element is recursive unless the element defines
* an implemented theme hook in #theme. During each call to drupal_render(), the
* outermost renderable array (also known as an "element") is processed using
@ -2600,6 +2620,8 @@ function drupal_render_page($page) {
* - If this element has already been printed (#printed = TRUE) or the user
* does not have access to it (#access = FALSE), then an empty string is
* returned.
* - If no stack data structure has been created yet, it is done now. Next,
* an empty \Drupal\Core\Render\RenderStackFrame is pushed onto the stack.
* - If this element has #cache defined then the cached markup for this
* element will be returned if it exists in drupal_render()'s cache. To use
* drupal_render() caching, set the element's #cache property to an
@ -2615,6 +2637,12 @@ function drupal_render_page($page) {
* - 'expire': Set to one of the cache lifetime constants.
* - 'bin': Specify a cache bin to cache the element in. Default is
* 'default'.
* When there is a render cache hit, there is no rendering work left to be
* done, so the stack must be updated. The empty (and topmost) frame that
* was just pushed onto the stack is updated with all bubbleable rendering
* metadata from the element retrieved from render cache. Then, this stack
* frame is bubbled: the two topmost frames are popped from the stack, they
* are merged, and the result is pushed back onto the stack.
* - If this element has #type defined and the default attributes for this
* element have not already been merged in (#defaults_loaded = TRUE) then
* the defaults for this type of element, defined in hook_element_info(),
@ -2625,6 +2653,12 @@ function drupal_render_page($page) {
* called sequentially to modify the element before rendering. After all the
* #pre_render functions have been called, #printed is checked a second time
* in case a #pre_render function flags the element as printed.
* If #printed is set, we return early and hence no rendering work is left
* to be done, similarly to a render cache hit. Once again, the empty (and
* topmost) frame that was just pushed onto the stack is updated with all
* bubbleable rendering metadata from the element whose #printed = TRUE.
* Then, this stack frame is bubbled: the two topmost frames are popped from
* the stack, they are merged, and the result is pushed back onto the stack.
* - The child elements of this element are sorted by weight using uasort() in
* \Drupal\Core\Render\Element::children(). Since this is expensive, when
* passing already sorted elements to drupal_render(), for example from a
@ -2684,20 +2718,50 @@ function drupal_render_page($page) {
* attribute as a string and the element itself.
* - If this element has #prefix and/or #suffix defined, they are concatenated
* to #children.
* - The rendering of this element is now complete. The next step will be
* render caching. So this is the perfect time to update the the stack. At
* this point, children of this element (if any), have been rendered also,
* and if there were any, their bubbleable rendering metadata will have been
* bubbled up into the stack frame for the element that is currently being
* rendered. The render cache item for this element must contain the
* bubbleable rendering metadata for this element and all of its children.
* However, right now, the topmost stack frame (the one for this element)
* currently only contains the metadata for the children. Therefore, the
* topmost stack frame is updated with this element's metadata, and then the
* element's metadata is replaced with the metadata in the topmost stack
* frame. This element now contains all bubbleable rendering metadata for
* this element and all its children, so it's now ready for render caching.
* - If this element has #cache defined, the rendered output of this element
* is saved to drupal_render()'s internal cache. This includes the changes
* made by #post_render.
* - If this element (or any of its children) has an array of
* #post_render_cache functions defined, they are called sequentially to
* replace placeholders in the final #markup and extend #attached.
* Placeholders must contain a unique token, to guarantee that e.g. samples
* of placeholders are not replaced also. For this, a special element named
* 'render_cache_placeholder' is provided.
* - If this element has an array of #post_render_cache functions defined, or
* any of its children has (which we would know thanks to the stack having
* been updated just before the render caching step), they are called
* sequentially to replace placeholders in the final #markup and extend
* #attached. Placeholders must contain a unique token, to guarantee that
* e.g. samples of placeholders are not replaced also.
* But, since #post_render_cache callbacks add attach additional assets, the
* correct bubbling of those must once again be taken into account. This
* final stage of rendering should be considered as if it were the parent of
* the current element, because it takes that as its input, and then alters
* its #markup. Hence, just before calling the #post_render_cache callbacks,
* a new empty frame is pushed onto the stack, where all assets #attached
* during the execution of those callbacks will end up in. Then, after the
* execution of those callbacks, we merge that back into the element.
* Note that these callbacks run always: when hitting the render cache, when
* missing, or when render caching is not used at all. This is done to allow
* any Drupal module to customize other render arrays without breaking the
* render cache if it is enabled, and to not require it to use other logic
* when render caching is disabled.
* - Just before finishing the rendering of this element, this element's stack
* frame (the topmost one) is bubbled: the two topmost frames are popped
* from the stack, they are merged and the result is pushed back onto the
* stack.
* So if this element e.g. was a child element, then a new frame was pushed
* onto the stack element at the beginning of rendering this element, it was
* updated when the rendering was completed, and now we merge it with the
* frame for the parent, so that the parent now has the bubbleable rendering
* metadata for its child.
* - #printed is set to TRUE for this element to ensure that it is only
* rendered once.
* - The final value of #children for this element is returned as the rendered
@ -2717,6 +2781,35 @@ function drupal_render_page($page) {
* @see drupal_process_attached()
*/
function drupal_render(&$elements, $is_recursive_call = FALSE) {
static $stack;
$update_stack = function(&$element) use (&$stack) {
// The latest frame represents the bubbleable data for the subtree.
$frame = $stack->top();
// Update the frame, but also update the current element, to ensure it
// contains up-to-date information in case it gets render cached.
$frame->tags = $element['#cache']['tags'] = drupal_merge_cache_tags($element['#cache']['tags'], $frame->tags);
$frame->attached = $element['#attached'] = drupal_merge_attached($element['#attached'], $frame->attached);
$frame->postRenderCache = $element['#post_render_cache'] = NestedArray::mergeDeep($element['#post_render_cache'], $frame->postRenderCache);
};
$bubble_stack = function() use (&$stack) {
// If there's only one frame on the stack, then this is the root call, and
// we can't bubble up further.
if ($stack->count() === 1) {
$stack->pop();
return;
}
// Merge the current and the parent stack frame.
$current = $stack->pop();
$parent = $stack->pop();
$current->tags = drupal_merge_cache_tags($current->tags, $parent->tags);
$current->attached = drupal_merge_attached($current->attached, $parent->attached);
$current->postRenderCache = NestedArray::mergeDeep($current->postRenderCache, $parent->postRenderCache);
$stack->push($current);
};
// Early-return nothing if user does not have access.
if (empty($elements) || (isset($elements['#access']) && !$elements['#access'])) {
return '';
@ -2727,6 +2820,11 @@ function drupal_render(&$elements, $is_recursive_call = FALSE) {
return '';
}
if (!isset($stack)) {
$stack = new \SplStack();
}
$stack->push(new RenderStackFrame());
// Try to fetch the prerendered element from cache, run any #post_render_cache
// callbacks and return the final markup.
if (isset($elements['#cache'])) {
@ -2740,6 +2838,11 @@ function drupal_render(&$elements, $is_recursive_call = FALSE) {
_drupal_render_process_post_render_cache($elements);
}
$elements['#markup'] = SafeMarkup::set($elements['#markup']);
// The render cache item contains all the bubbleable rendering metadata for
// the subtree.
$update_stack($elements);
// Render cache hit, so rendering is finished, all necessary info collected!
$bubble_stack();
return $elements['#markup'];
}
}
@ -2767,8 +2870,18 @@ function drupal_render(&$elements, $is_recursive_call = FALSE) {
}
}
// Defaults for bubbleable rendering metadata.
$elements['#cache']['tags'] = isset($elements['#cache']['tags']) ? $elements['#cache']['tags'] : array();
$elements['#attached'] = isset($elements['#attached']) ? $elements['#attached'] : array();
$elements['#post_render_cache'] = isset($elements['#post_render_cache']) ? $elements['#post_render_cache'] : array();
// Allow #pre_render to abort rendering.
if (!empty($elements['#printed'])) {
// The #printed element contains all the bubbleable rendering metadata for
// the subtree.
$update_stack($elements);
// #printed, so rendering is finished, all necessary info collected!
$bubble_stack();
return '';
}
@ -2777,12 +2890,6 @@ function drupal_render(&$elements, $is_recursive_call = FALSE) {
drupal_process_states($elements);
}
// Add additional libraries, CSS, JavaScript and other custom
// attached data associated with this element.
if (!empty($elements['#attached'])) {
drupal_process_attached($elements);
}
// Get the children of the element, sorted by weight.
$children = Element::children($elements, TRUE);
@ -2872,9 +2979,6 @@ function drupal_render(&$elements, $is_recursive_call = FALSE) {
if (is_string($callable) && strpos($callable, '::') === FALSE) {
$callable = $controller_resolver->getControllerFromDefinition($callable);
}
else {
$callable = $callable;
}
$elements['#children'] = call_user_func($callable, $elements['#children'], $elements);
}
}
@ -2887,20 +2991,8 @@ function drupal_render(&$elements, $is_recursive_call = FALSE) {
$suffix = isset($elements['#suffix']) ? $elements['#suffix'] : '';
$elements['#markup'] = $prefix . $elements['#children'] . $suffix;
// Collect all #post_render_cache callbacks associated with this element when:
// - about to store this element in the render cache, or when;
// - about to apply #post_render_cache callbacks.
if (!$is_recursive_call || isset($elements['#cache'])) {
$post_render_cache = drupal_render_collect_post_render_cache($elements);
if ($post_render_cache) {
$elements['#post_render_cache'] = $post_render_cache;
}
}
// Collect all cache tags. This allows the caller of drupal_render() to also
// access the complete list of cache tags.
if (!$is_recursive_call || isset($elements['#cache'])) {
$elements['#cache']['tags'] = drupal_render_collect_cache_tags($elements);
}
// We've rendered this element (and its subtree!), now update the stack.
$update_stack($elements);
// Cache the processed element if #cache is set.
if (isset($elements['#cache'])) {
@ -2917,9 +3009,23 @@ function drupal_render(&$elements, $is_recursive_call = FALSE) {
// Only the case of a cache hit when #cache is enabled, is not handled here,
// that is handled earlier in drupal_render().
if (!$is_recursive_call) {
// We've already called $update_stack() earlier, which updated both the
// element and current stack frame. However,
// _drupal_render_process_post_render_cache() can both change the element
// further and create and render new child elements, so provide a fresh
// stack frame to collect those additions, merge them back to the element,
// and then update the current frame to match the modified element state.
$stack->push(new RenderStackFrame());
_drupal_render_process_post_render_cache($elements);
$post_render_additions = $stack->pop();
$elements['#cache']['tags'] = drupal_merge_cache_tags($elements['#cache']['tags'], $post_render_additions->tags);
$elements['#attached'] = drupal_merge_attached($elements['#attached'], $post_render_additions->attached);
$elements['#post_render_cache'] = NestedArray::mergeDeep($elements['#post_render_cache'], $post_render_additions->postRenderCache);
}
// Rendering is finished, all necessary info collected!
$bubble_stack();
$elements['#printed'] = TRUE;
$elements['#markup'] = SafeMarkup::set($elements['#markup']);
return $elements['#markup'];
@ -3064,11 +3170,6 @@ function drupal_render_cache_get(array $elements) {
if (!empty($cid) && $cache = \Drupal::cache($bin)->get($cid)) {
$cached_element = $cache->data;
// Add additional libraries, JavaScript, CSS and other data attached
// to this element.
if (isset($cached_element['#attached'])) {
drupal_process_attached($cached_element);
}
// Return the cached element.
return $cached_element;
}
@ -3103,10 +3204,7 @@ function drupal_render_cache_set(&$markup, array $elements) {
$data['#markup'] = $markup;
// Persist attached data associated with this element.
$attached = drupal_render_collect_attached($elements, TRUE);
if ($attached) {
$data['#attached'] = $attached;
}
$data['#attached'] = $elements['#attached'];
// Persist #post_render_cache callbacks associated with this element.
if (isset($elements['#post_render_cache'])) {
@ -3204,161 +3302,9 @@ function _drupal_render_process_post_render_cache(array &$elements) {
$elements = call_user_func_array($callable, array($elements, $context));
}
}
// Make sure that any attachments added in #post_render_cache callbacks are
// also executed.
if (isset($elements['#attached'])) {
drupal_process_attached($elements);
}
}
}
/**
* Collects #post_render_cache for an element and its children into a single
* array.
*
* When caching elements, it is necessary to collect all #post_render_cache
* callbacks into a single array, from both the element itself and all child
* elements. This allows drupal_render() to execute all of them when the element
* is retrieved from the render cache.
*
* Note: the theme system may render child elements directly (e.g. rendering a
* node causes its template to be rendered, which causes the node links to be
* drupal_render()ed). On top of that, the theme system transforms render arrays
* into HTML strings. These two facts combined imply that it is impossible for
* #post_render_cache callbacks to bubble up to the root of the render array.
* Therefore, drupal_render_collect_post_render_cache() must be called *before*
* #theme callbacks, so that it has a chance to examine the full render array.
* In short: in order to examine the full render array for #post_render_cache
* callbacks, it must use post-order tree traversal, whereas drupal_render()
* itself uses pre-order tree traversal.
*
* @param array &$elements
* The element to collect #post_render_cache callbacks for.
* @param array $callbacks
* Internal use only. The #post_render_callbacks array so far.
* @param bool $is_root_element
* Internal use only. Whether the element being processed is the root or not.
*
* @return
* The #post_render_cache array for this element and its descendants.
*
* @see drupal_render()
* @see _drupal_render_process_post_render_cache()
*/
function drupal_render_collect_post_render_cache(array &$elements, array $callbacks = array(), $is_root_element = TRUE) {
// Try to fetch the prerendered element from cache, to determine
// #post_render_cache callbacks for this element and all its children. If we
// don't do this, then the #post_render_cache tokens will be re-generated, but
// they would no longer match the tokens in the render cached markup, causing
// the render cache placeholder markup to be sent to the end user!
$retrieved_from_cache = FALSE;
if (!$is_root_element && isset($elements['#cache'])) {
$cached_element = drupal_render_cache_get($elements);
if ($cached_element !== FALSE && isset($cached_element['#post_render_cache'])) {
$elements['#post_render_cache'] = $cached_element['#post_render_cache'];
$retrieved_from_cache = TRUE;
}
}
// Collect all #post_render_cache callbacks for this element.
if (isset($elements['#post_render_cache'])) {
$callbacks = NestedArray::mergeDeep($callbacks, $elements['#post_render_cache']);
}
// Collect the #post_render_cache callbacks for all child elements, unless
// we've already collected them above by retrieving this element (and its
// children) from the render cache.
if (!$retrieved_from_cache && $children = Element::children($elements)) {
foreach ($children as $child) {
$callbacks = drupal_render_collect_post_render_cache($elements[$child], $callbacks, FALSE);
}
}
return $callbacks;
}
/**
* Collects #attached for an element and its children into a single array.
*
* When caching elements, it is necessary to collect all libraries, JavaScript
* and CSS into a single array, from both the element itself and all child
* elements. This allows drupal_render() to add these back to the page when the
* element is returned from cache.
*
* @param $elements
* The element to collect #attached from.
* @param $return
* Whether to return the attached elements and reset the internal static.
*
* @return
* The #attached array for this element and its descendants.
*/
function drupal_render_collect_attached($elements, $return = FALSE) {
$attached = &drupal_static(__FUNCTION__, array());
// Collect all #attached for this element.
if (isset($elements['#attached'])) {
$attached = drupal_merge_attached($attached, $elements['#attached']);
}
if ($children = Element::children($elements)) {
foreach ($children as $child) {
drupal_render_collect_attached($elements[$child]);
}
}
// If this was the first call to the function, return all attached elements
// and reset the static cache.
if ($return) {
$return = $attached;
$attached = array();
return $return;
}
}
/**
* Collects cache tags for an element and its children into a single array.
*
* The cache tags array is returned in a format that is valid for
* \Drupal\Core\Cache\CacheBackendInterface::set().
*
* When caching elements, it is necessary to collect all cache tags into a
* single array, from both the element itself and all child elements. This
* allows items to be invalidated based on all tags attached to the content
* they're constituted from.
*
* @param array $element
* The element to collect cache tags from.
* @param array $tags
* (optional) An array of already collected cache tags (i.e. from a parent
* element). Defaults to an empty array.
*
* @return array
* The cache tags array for this element and its descendants.
*/
function drupal_render_collect_cache_tags($element, $tags = array()) {
if (isset($element['#cache']['tags'])) {
foreach ($element['#cache']['tags'] as $namespace => $values) {
if (is_array($values)) {
foreach ($values as $value) {
$tags[$namespace][$value] = $value;
}
}
else {
if (!isset($tags[$namespace])) {
$tags[$namespace] = $values;
}
}
}
}
if ($children = Element::children($element)) {
foreach ($children as $child) {
$tags = drupal_render_collect_cache_tags($element[$child], $tags);
}
}
return $tags;
}
/**
* Creates the cache ID for a renderable element.
*

View File

@ -11,6 +11,7 @@
use Drupal\Component\Serialization\Json;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Component\Utility\String;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Config\Config;
use Drupal\Core\Config\StorageException;
@ -1750,6 +1751,14 @@ function template_preprocess_html(&$variables) {
$page->addMetaElement($metatag);
}
}
// Add favicon.
if (theme_get_setting('features.favicon')) {
$url = UrlHelper::stripDangerousProtocols(theme_get_setting('favicon.url'));
$link = new LinkElement($url, 'shortcut icon', ['type' => theme_get_setting('favicon.mimetype')]);
$page->addLinkElement($link);
}
$variables['page_top'][] = array('#markup' => $page->getBodyTop());
$variables['page_bottom'][] = array('#markup' => $page->getBodyBottom());
}

View File

@ -78,7 +78,9 @@ class AjaxResponseRenderer {
* @todo: Remove as part of https://drupal.org/node/2182149
*/
protected function drupalRender(&$elements, $is_recursive_call = FALSE) {
return drupal_render($elements, $is_recursive_call);
$output = drupal_render($elements, $is_recursive_call);
drupal_process_attached($elements);
return $output;
}
/**

View File

@ -150,7 +150,7 @@ class OpenDialogCommand implements CommandInterface {
*/
protected function drupalAttachLibrary($name) {
$attached['#attached']['library'][] = $name;
drupal_render($attached);
drupal_process_attached($attached);
}
}

View File

@ -93,6 +93,7 @@ class DialogController {
}
$content = drupal_render($page_content);
drupal_process_attached($page_content);
$title = isset($page_content['#title']) ? $page_content['#title'] : $this->titleResolver->getTitle($request, $request->attributes->get(RouteObjectInterface::ROUTE_OBJECT));
$response = new AjaxResponse();
// Fetch any modal options passed in from data-dialog-options.

View File

@ -73,6 +73,9 @@ class HtmlControllerBase {
}
$content = $this->drupalRender($page_content);
if (!empty($page_content)) {
drupal_process_attached($page_content);
}
$cache = !empty($page_content['#cache']['tags']) ? array('tags' => $page_content['#cache']['tags']) : array();
$fragment = new HtmlFragment($content, $cache);
@ -85,7 +88,7 @@ class HtmlControllerBase {
}
// Add feed links from the page content.
$attached = drupal_render_collect_attached($page_content, TRUE);
$attached = isset($page_content['#attached']) ? $page_content['#attached'] : array();
if (!empty($attached['drupal_add_feed'])) {
foreach ($attached['drupal_add_feed'] as $feed) {
$feed_link = new FeedLinkElement($feed[1], $this->urlGenerator->generateFromPath($feed[0]));

View File

@ -59,6 +59,14 @@ class DefaultHtmlFragmentRenderer implements HtmlFragmentRendererInterface {
$page->setContent(drupal_render($page_array));
$page->setStatusCode($status_code);
drupal_process_attached($page_array);
if (isset($page_array['page_top'])) {
drupal_process_attached($page_array['page_top']);
}
if (isset($page_array['page_bottom'])) {
drupal_process_attached($page_array['page_bottom']);
}
if ($fragment instanceof CacheableInterface) {
// Collect cache tags for all the content in all the regions on the page.
$tags = $page_array['#cache']['tags'];
@ -102,6 +110,21 @@ class DefaultHtmlFragmentRenderer implements HtmlFragmentRendererInterface {
$page->addLinkElement($link);
}
// Add libraries and CSS used by this theme.
$active_theme = \Drupal::theme()->getActiveTheme();
foreach ($active_theme->getLibraries() as $library) {
$page_array['#attached']['library'][] = $library;
}
foreach ($active_theme->getStyleSheets() as $media => $stylesheets) {
foreach ($stylesheets as $stylesheet) {
$page_array['#attached']['css'][$stylesheet] = array(
'group' => CSS_AGGREGATE_THEME,
'every_page' => TRUE,
'media' => $media
);
}
}
return $page;
}

View File

@ -20,6 +20,15 @@ class DefaultHtmlPageRenderer implements HtmlPageRendererInterface {
'#type' => 'html',
'#page_object' => $page,
);
// drupal_render() will render the 'html' template, which will call
// HtmlPage::getScripts(). But normally we can only run
// drupal_process_attached() after drupal_render(). Hence any assets
// attached to '#type' => 'html' will be lost. This is a work-around for
// that limitation, until the HtmlPage object contains its assets — this is
// an unfortunate intermediate consequence of the way HtmlPage dictates page
// rendering and how that differs from how drupal_render() works.
$render += element_info($render['#type']);
drupal_process_attached($render);
return drupal_render($render);
}
@ -102,6 +111,13 @@ class DefaultHtmlPageRenderer implements HtmlPageRendererInterface {
$page->setBodyTop(drupal_render($page_array['page_top']));
$page->setBodyBottom(drupal_render($page_array['page_bottom']));
$page->setContent(drupal_render($page_array));
drupal_process_attached($page_array);
if (isset($page_array['page_top'])) {
drupal_process_attached($page_array['page_top']);
}
if (isset($page_array['page_bottom'])) {
drupal_process_attached($page_array['page_bottom']);
}
return \Drupal::service('html_page_renderer')->render($page);
}

View File

@ -7,8 +7,6 @@
namespace Drupal\Core\Render\Element;
use Drupal\Component\Utility\UrlHelper;
/**
* Provides a render element for <html>.
*
@ -20,12 +18,8 @@ class Html extends RenderElement {
* {@inheritdoc}
*/
public function getInfo() {
$class = get_class($this);
return array(
'#theme' => 'html',
'#pre_render' => array(
array($class, 'preRenderHtml'),
),
// HTML5 Shiv
'#attached' => array(
'library' => array('core/html5shiv'),
@ -33,35 +27,4 @@ class Html extends RenderElement {
);
}
/**
* #pre_render callback for the html element type.
*
* @param array $element
* A structured array containing the html element type build properties.
*
* @return array
* The processed element.
*/
public static function preRenderHtml($element) {
// Add favicon.
if (static::themeGetSetting('features.favicon')) {
$favicon = static::themeGetSetting('favicon.url');
$type = static::themeGetSetting('favicon.mimetype');
$element['#attached']['drupal_add_html_head_link'][][] = array(
'rel' => 'shortcut icon',
'href' => UrlHelper::stripDangerousProtocols($favicon),
'type' => $type,
);
}
return $element;
}
/**
* Wraps theme_get_setting().
*/
protected static function themeGetSetting($setting_name) {
return theme_get_setting($setting_name);
}
}

View File

@ -56,7 +56,9 @@ class ElementInfoManager extends DefaultPluginManager implements ElementInfoMana
if (!isset($this->elementInfo)) {
$this->elementInfo = $this->buildInfo();
}
return isset($this->elementInfo[$type]) ? $this->elementInfo[$type] : array();
$info = isset($this->elementInfo[$type]) ? $this->elementInfo[$type] : array();
$info['#defaults_loaded'] = TRUE;
return $info;
}
/**

View File

@ -0,0 +1,38 @@
<?php
/**
* @file
* Contains \Drupal\Core\Render\RenderStackFrame.
*/
namespace Drupal\Core\Render;
/**
* Value object used for bubbleable rendering metadata.
*
* @see drupal_render()
*/
class RenderStackFrame {
/**
* Cache tags.
*
* @var array
*/
public $tags = [];
/**
* Attached assets.
*
* @var array
*/
public $attached = [];
/**
* #post_render_cache metadata.
*
* @var array
*/
public $postRenderCache = [];
}

View File

@ -741,7 +741,7 @@ function template_preprocess_comment(&$variables) {
}
if (isset($variables['elements']['signature'])) {
$variables['signature'] = $variables['elements']['signature']['#markup'];
$variables['signature'] = $variables['elements']['signature'];
unset($variables['elements']['signature']);
}
else {

View File

@ -72,7 +72,7 @@ class CommentPostRenderCache {
$form = $this->entityFormBuilder->getForm($comment);
// @todo: This only works as long as assets are still tracked in a global
// static variable, see https://drupal.org/node/2238835
$markup = drupal_render($form, TRUE);
$markup = drupal_render($form);
$callback = 'comment.post_render_cache:renderForm';
$placeholder = drupal_render_cache_generate_placeholder($callback, $context);

View File

@ -138,11 +138,6 @@ class CommentViewBuilder extends EntityViewBuilder {
'#format' => $account->getSignatureFormat(),
'#langcode' => $entity->language()->getId(),
);
// The signature will only be rendered in the theme layer, which means
// its associated cache tags will not bubble up. Work around this for
// now by already rendering the signature here.
// @todo remove this work-around, see https://drupal.org/node/2273277
drupal_render($build[$id]['signature'], TRUE);
}
if (!isset($build[$id]['#attached'])) {

View File

@ -162,20 +162,6 @@ class CommentDefaultFormatter extends FormatterBase implements ContainerFactoryP
if ($this->getSetting('pager_id')) {
$build['pager']['#element'] = $this->getSetting('pager_id');
}
// The viewElements() method of entity field formatters is run
// during the #pre_render phase of rendering an entity. A formatter
// builds the content of the field in preparation for theming.
// All entity cache tags must be available after the #pre_render phase.
// This field formatter is highly exceptional: it renders *another*
// entity and this referenced entity has its own #pre_render
// callbacks. In order collect the cache tags associated with the
// referenced entity it must be passed to drupal_render() so that its
// #pre_render callbacks are invoked and its full build array is
// assembled. Rendering the referenced entity in place here will allow
// its cache tags to be bubbled up and included with those of the
// main entity when cache tags are collected for a renderable array
// in drupal_render().
drupal_render($build, TRUE);
$output['comments'] = $build;
}
}

View File

@ -76,7 +76,8 @@ class CommentCacheTagsTest extends EntityWithUriCacheTagsTestBase {
* Each comment must have a comment body, which always has a text format.
*/
protected function getAdditionalCacheTagsForEntity(EntityInterface $entity) {
return array('filter_format:plain_text');
/** @var \Drupal\comment\CommentInterface $entity */
return array('filter_format:plain_text', 'user:' . $entity->getOwnerId(), 'user_view:1');
}
}

View File

@ -67,7 +67,7 @@ class CommentDefaultFormatterCacheTagsTest extends EntityUnitTestBase {
drupal_render($build);
$expected_cache_tags = array(
'entity_test_view' => TRUE,
'entity_test' => array(1 => $commented_entity->id()),
'entity_test' => array($commented_entity->id()),
);
$this->assertEqual($build['#cache']['tags'], $expected_cache_tags, 'The test entity has the expected cache tags before it has comments.');
@ -102,12 +102,14 @@ class CommentDefaultFormatterCacheTagsTest extends EntityUnitTestBase {
drupal_render($build);
$expected_cache_tags = array(
'entity_test_view' => TRUE,
'entity_test' => array(1 => $commented_entity->id()),
'entity_test' => array($commented_entity->id()),
'comment_view' => TRUE,
'comment' => array(1 => $comment->id()),
'filter_format' => array(
'plain_text' => 'plain_text',
),
'user_view' => TRUE,
'user' => array(2 => 2),
);
$this->assertEqual($build['#cache']['tags'], $expected_cache_tags, 'The test entity has the expected cache tags when it has comments.');
}

View File

@ -95,23 +95,7 @@ class EntityReferenceEntityFormatter extends EntityReferenceFormatterBase {
}
if (!empty($item->target_id)) {
// The viewElements() method of entity field formatters is run
// during the #pre_render phase of rendering an entity. A formatter
// builds the content of the field in preparation for theming.
// All entity cache tags must be available after the #pre_render phase.
// This field formatter is highly exceptional: it renders *another*
// entity and this referenced entity has its own #pre_render
// callbacks. In order collect the cache tags associated with the
// referenced entity it must be passed to drupal_render() so that its
// #pre_render callbacks are invoked and its full build array is
// assembled. Rendering the referenced entity in place here will allow
// its cache tags to be bubbled up and included with those of the
// main entity when cache tags are collected for a renderable array
// in drupal_render().
// @todo remove this work-around, see https://drupal.org/node/2273277
$referenced_entity_build = entity_view($item->entity, $view_mode, $item->getLangcode());
drupal_render($referenced_entity_build, TRUE);
$elements[$delta] = $referenced_entity_build;
$elements[$delta] = entity_view($item->entity, $view_mode, $item->getLangcode());
if (empty($links) && isset($result[$delta][$target_type][$item->target_id]['links'])) {
// Hide the element links.

View File

@ -181,10 +181,11 @@ class EntityReferenceFormatterTest extends EntityUnitTestBase {
</div>
</div>
';
drupal_render($build[0]);
$this->assertEqual($build[0]['#markup'], 'default | ' . $this->referencedEntity->label() . $expected_rendered_name_field . $expected_rendered_body_field, format_string('The markup returned by the @formatter formatter is correct.', array('@formatter' => $formatter)));
$expected_cache_tags = array(
$this->entityType . '_view' => TRUE,
$this->entityType => array($this->referencedEntity->id() => $this->referencedEntity->id()),
$this->entityType => array($this->referencedEntity->id()),
'filter_format' => array('full_html' => 'full_html'),
);
$this->assertEqual($build[0]['#cache']['tags'], $expected_cache_tags, format_string('The @formatter formatter has the expected cache tags.', array('@formatter' => $formatter)));

View File

@ -118,7 +118,8 @@ class DisplayApiTest extends FieldUnitTestBase {
$items = $this->entity->get($this->field_name);
// No display settings: check that default display settings are used.
$this->render($items->view());
$build = $items->view();
$this->render($build);
$settings = \Drupal::service('plugin.manager.field.formatter')->getDefaultSettings('field_test_default');
$setting = $settings['test_formatter_setting'];
$this->assertText($this->label, 'Label was displayed.');
@ -135,7 +136,8 @@ class DisplayApiTest extends FieldUnitTestBase {
'alter' => TRUE,
),
);
$this->render($items->view($display));
$build = $items->view($display);
$this->render($build);
$setting = $display['settings']['test_formatter_setting_multiple'];
$this->assertNoText($this->label, 'Label was not displayed.');
$this->assertText('field_test_entity_display_build_alter', 'Alter fired, display passed.');
@ -155,7 +157,8 @@ class DisplayApiTest extends FieldUnitTestBase {
'alter' => TRUE,
),
);
$this->render($items->view($display));
$build = $items->view($display);
$this->render($build);
$setting = $display['settings']['test_formatter_setting_multiple'];
$this->assertRaw('visually-hidden', 'Label was visually hidden.');
$this->assertText('field_test_entity_display_build_alter', 'Alter fired, display passed.');
@ -174,7 +177,8 @@ class DisplayApiTest extends FieldUnitTestBase {
'test_formatter_setting_additional' => $this->randomMachineName(),
),
);
$this->render($items->view($display));
$build = $items->view($display);
$this->render($build);
$setting = $display['settings']['test_formatter_setting_additional'];
$this->assertNoText($this->label, 'Label was not displayed.');
$this->assertNoText('field_test_entity_display_build_alter', 'Alter not fired.');
@ -184,7 +188,8 @@ class DisplayApiTest extends FieldUnitTestBase {
// View mode: check that display settings specified in the display object
// are used.
$this->render($items->view('teaser'));
$build = $items->view('teaser');
$this->render($build);
$setting = $this->display_options['teaser']['settings']['test_formatter_setting'];
$this->assertText($this->label, 'Label was displayed.');
foreach ($this->values as $delta => $value) {
@ -193,7 +198,8 @@ class DisplayApiTest extends FieldUnitTestBase {
// Unknown view mode: check that display settings for 'default' view mode
// are used.
$this->render($items->view('unknown_view_mode'));
$build = $items->view('unknown_view_mode');
$this->render($build);
$setting = $this->display_options['default']['settings']['test_formatter_setting'];
$this->assertText($this->label, 'Label was displayed.');
foreach ($this->values as $delta => $value) {
@ -210,7 +216,8 @@ class DisplayApiTest extends FieldUnitTestBase {
$setting = $settings['test_formatter_setting'];
foreach ($this->values as $delta => $value) {
$item = $this->entity->{$this->field_name}[$delta];
$this->render($item->view());
$build = $item->view();
$this->render($build);
$this->assertText($setting . '|' . $value['value'], format_string('Value @delta was displayed with expected setting.', array('@delta' => $delta)));
}
@ -224,7 +231,8 @@ class DisplayApiTest extends FieldUnitTestBase {
$setting = $display['settings']['test_formatter_setting_multiple'];
foreach ($this->values as $delta => $value) {
$item = $this->entity->{$this->field_name}[$delta];
$this->render($item->view($display));
$build = $item->view($display);
$this->render($build);
$this->assertText($setting . '|0:' . $value['value'], format_string('Value @delta was displayed with expected setting.', array('@delta' => $delta)));
}
@ -238,7 +246,8 @@ class DisplayApiTest extends FieldUnitTestBase {
$setting = $display['settings']['test_formatter_setting_additional'];
foreach ($this->values as $delta => $value) {
$item = $this->entity->{$this->field_name}[$delta];
$this->render($item->view($display));
$build = $item->view($display);
$this->render($build);
$this->assertText($setting . '|' . $value['value'] . '|' . ($value['value'] + 1), format_string('Value @delta was displayed with expected setting.', array('@delta' => $delta)));
}
@ -247,7 +256,8 @@ class DisplayApiTest extends FieldUnitTestBase {
$setting = $this->display_options['teaser']['settings']['test_formatter_setting'];
foreach ($this->values as $delta => $value) {
$item = $this->entity->{$this->field_name}[$delta];
$this->render($item->view('teaser'));
$build = $item->view('teaser');
$this->render($build);
$this->assertText($setting . '|' . $value['value'], format_string('Value @delta was displayed with expected setting.', array('@delta' => $delta)));
}
@ -256,7 +266,8 @@ class DisplayApiTest extends FieldUnitTestBase {
$setting = $this->display_options['default']['settings']['test_formatter_setting'];
foreach ($this->values as $delta => $value) {
$item = $this->entity->{$this->field_name}[$delta];
$this->render($item->view('unknown_view_mode'));
$build = $item->view('unknown_view_mode');
$this->render($build);
$this->assertText($setting . '|' . $value['value'], format_string('Value @delta was displayed with expected setting.', array('@delta' => $delta)));
}
}
@ -275,7 +286,8 @@ class DisplayApiTest extends FieldUnitTestBase {
);
// $this->entity is set by the setUp() method and by default contains 4
// numeric values. We only want to test the display of this one field.
$this->render($this->entity->get($this->field_name)->view($display));
$build = $this->entity->get($this->field_name)->view($display);
$this->render($build);
// The test field by default contains values, so should not display the
// default "empty" text.
$this->assertNoText($display['settings']['test_empty_string']);
@ -283,7 +295,8 @@ class DisplayApiTest extends FieldUnitTestBase {
// Now remove the values from the test field and retest.
$this->entity->{$this->field_name} = array();
$this->entity->save();
$this->render($this->entity->get($this->field_name)->view($display));
$build = $this->entity->get($this->field_name)->view($display);
$this->render($build);
// This time, as the field values have been removed, we *should* show the
// default "empty" text.
$this->assertText($display['settings']['test_empty_string']);

View File

@ -75,6 +75,7 @@ class FileWidgetAjaxController extends FormAjaxController {
$status_messages = array('#theme' => 'status_messages');
$form['#prefix'] .= drupal_render($status_messages);
$output = drupal_render($form);
drupal_process_attached($form);
$js = _drupal_add_js();
$settings = drupal_merge_js_settings($js['settings']['data']);

View File

@ -247,12 +247,12 @@ class FilterAPITest extends EntityUnitTestBase {
$expected_cache_tags = array(
// The cache tag set by the processed_text element itself.
'filter_format' => array(
'element_test' => 'element_test',
'element_test',
),
// The cache tags set by the filter_test_cache_tags filter.
'foo' => array(
'bar' => 'bar',
'baz' => 'baz',
'bar',
'baz',
),
);
$this->assertEqual($expected_cache_tags, $build['#cache']['tags'], 'Expected cache tags present.');

View File

@ -32,6 +32,7 @@ class LocaleLibraryInfoAlterTest extends WebTestBase {
public function testLibraryInfoAlter() {
$attached['#attached']['library'][] = 'core/jquery.ui.datepicker';
drupal_render($attached);
drupal_process_attached($attached);
$scripts = drupal_get_js();
$this->assertTrue(strpos($scripts, 'locale.datepicker.js'), 'locale.datepicker.js added to scripts.');
}

View File

@ -60,7 +60,7 @@ class NodeCacheTagsTest extends EntityWithUriCacheTagsTestBase {
* Each node must have an author.
*/
protected function getAdditionalCacheTagsForEntity(EntityInterface $node) {
return array('user:' . $node->getOwnerId());
return array('user:' . $node->getOwnerId(), 'user_view:1');
}
}

View File

@ -547,8 +547,9 @@ abstract class KernelTestBase extends UnitTestBase {
* @return string
* The rendered string output (typically HTML).
*/
protected function render(array $elements) {
protected function render(array &$elements) {
$content = drupal_render($elements);
drupal_process_attached($elements);
$this->setRawContent($content);
$this->verbose('<pre style="white-space: pre-wrap">' . String::checkPlain($content));
return $content;

View File

@ -18,6 +18,7 @@ use Drupal\Core\Database\Database;
use Drupal\Core\Database\ConnectionNotDefinedException;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Session\AnonymousUserSession;
use Drupal\Core\Session\UserSession;
@ -341,26 +342,37 @@ abstract class WebTestBase extends TestBase {
* @see drupal_render()
*/
protected function drupalBuildEntityView(EntityInterface $entity, $view_mode = 'full', $langcode = NULL, $reset = FALSE) {
$ensure_fully_built = function(&$elements) use (&$ensure_fully_built) {
// If the default values for this element have not been loaded yet, populate
// them.
if (isset($elements['#type']) && empty($elements['#defaults_loaded'])) {
$elements += element_info($elements['#type']);
}
// Make any final changes to the element before it is rendered. This means
// that the $element or the children can be altered or corrected before the
// element is rendered into the final text.
if (isset($elements['#pre_render'])) {
foreach ($elements['#pre_render'] as $callable) {
$elements = call_user_func($callable, $elements);
}
}
// And recurse.
$children = Element::children($elements, TRUE);
foreach ($children as $key) {
$ensure_fully_built($elements[$key]);
}
};
$render_controller = $this->container->get('entity.manager')->getViewBuilder($entity->getEntityTypeId());
if ($reset) {
$render_controller->resetCache(array($entity->id()));
}
$elements = $render_controller->view($entity, $view_mode, $langcode);
// If the default values for this element have not been loaded yet, populate
// them.
if (isset($elements['#type']) && empty($elements['#defaults_loaded'])) {
$elements += element_info($elements['#type']);
}
$build = $render_controller->view($entity, $view_mode, $langcode);
$ensure_fully_built($build);
// Make any final changes to the element before it is rendered. This means
// that the $element or the children can be altered or corrected before the
// element is rendered into the final text.
if (isset($elements['#pre_render'])) {
foreach ($elements['#pre_render'] as $callable) {
$elements = call_user_func($callable, $elements);
}
}
return $elements;
return $build;
}
/**

View File

@ -110,6 +110,14 @@ class BatchController implements ContainerInjectionInterface {
$page->setBodyBottom(drupal_render($page_array['page_bottom']));
$page->setContent(drupal_render($page_array));
drupal_process_attached($page_array);
if (isset($page_array['page_top'])) {
drupal_process_attached($page_array['page_top']);
}
if (isset($page_array['page_bottom'])) {
drupal_process_attached($page_array['page_bottom']);
}
$page->setStatusCode($status_code);
return $page;

View File

@ -50,6 +50,7 @@ class FrameworkTest extends AjaxTestBase {
),
);
drupal_render($attached);
drupal_process_attached($attached);
$expected_commands[1] = new AddCssCommand(drupal_get_css(_drupal_add_css(), TRUE));
drupal_static_reset('_drupal_add_js');
$attached = array(
@ -60,6 +61,7 @@ class FrameworkTest extends AjaxTestBase {
),
);
drupal_render($attached);
drupal_process_attached($attached);
$expected_commands[2] = new PrependCommand('head', drupal_get_js('header', _drupal_add_js(), TRUE));
drupal_static_reset('_drupal_add_js');
$attached = array(
@ -70,6 +72,7 @@ class FrameworkTest extends AjaxTestBase {
),
);
drupal_render($attached);
drupal_process_attached($attached);
$expected_commands[3] = new AppendCommand('body', drupal_get_js('footer', _drupal_add_js(), TRUE));
$expected_commands[4] = new HtmlCommand('body', 'Hello, world!');

View File

@ -63,7 +63,7 @@ class JavaScriptTest extends DrupalUnitTestBase {
*/
function testAddFile() {
$attached['#attached']['js']['core/misc/collapse.js'] = array();
drupal_render($attached);
$this->render($attached);
$javascript = _drupal_add_js();
$this->assertTrue(array_key_exists('core/misc/collapse.js', $javascript), 'JavaScript files are correctly added.');
}
@ -74,7 +74,7 @@ class JavaScriptTest extends DrupalUnitTestBase {
function testAddSetting() {
// Add a file in order to test default settings.
$attached['#attached']['library'][] = 'core/drupalSettings';
drupal_render($attached);
$this->render($attached);
$javascript = _drupal_add_js();
$last_settings = reset($javascript['settings']['data']);
$this->assertTrue(array_key_exists('currentPath', $last_settings['path']), 'The current path JavaScript setting is set correctly.');
@ -90,7 +90,7 @@ class JavaScriptTest extends DrupalUnitTestBase {
*/
function testAddExternal() {
$attached['#attached']['js']['http://example.com/script.js'] = array('type' => 'external');
drupal_render($attached);
$this->render($attached);
$javascript = _drupal_add_js();
$this->assertTrue(array_key_exists('http://example.com/script.js', $javascript), 'Added an external JavaScript file.');
}
@ -109,7 +109,7 @@ class JavaScriptTest extends DrupalUnitTestBase {
$attached['#attached']['js']['core/misc/collapse.js'] = array(
'attributes' => array('defer' => 'defer'),
);
drupal_render($attached);
$this->render($attached);
$javascript = drupal_get_js();
$expected_1 = '<script src="http://example.com/script.js" defer="defer"></script>';
@ -136,7 +136,7 @@ class JavaScriptTest extends DrupalUnitTestBase {
$attached['#attached']['js']['core/misc/collapse.js'] = array(
'attributes' => array('defer' => 'defer'),
);
drupal_render($attached);
$this->render($attached);
$javascript = drupal_get_js();
$expected_1 = '<script src="http://example.com/script.js" defer="defer"></script>';
@ -152,7 +152,7 @@ class JavaScriptTest extends DrupalUnitTestBase {
function testHeaderSetting() {
$attached = array();
$attached['#attached']['library'][] = 'core/drupalSettings';
drupal_render($attached);
$this->render($attached);
$javascript = drupal_get_js('header');
$this->assertTrue(strpos($javascript, 'basePath') > 0, 'Rendered JavaScript header returns basePath setting.');
@ -211,7 +211,7 @@ class JavaScriptTest extends DrupalUnitTestBase {
'data' => array('commonTestRealWorldAlmostIdentical' => $settings_two),
);
drupal_render($attached);
$this->render($attached);
$javascript = drupal_get_js('header');
// Test whether _drupal_add_js can be used to override a previous setting.
@ -248,7 +248,7 @@ class JavaScriptTest extends DrupalUnitTestBase {
function testReset() {
$attached['#attached']['library'][] = 'core/drupal';
$attached['#attached']['js']['core/misc/collapse.js'] = array();
drupal_render($attached);
$this->render($attached);
drupal_static_reset('_drupal_add_js');
$this->assertEqual(array(), _drupal_add_js(), 'Resetting the JavaScript correctly empties the cache.');
}
@ -264,7 +264,7 @@ class JavaScriptTest extends DrupalUnitTestBase {
'data' => $inline,
'attributes' => array('defer' => 'defer'),
);
drupal_render($attached);
$this->render($attached);
$javascript = _drupal_add_js();
$this->assertTrue(array_key_exists('core/assets/vendor/jquery/jquery.js', $javascript), 'jQuery is added when inline scripts are added.');
$data = end($javascript);
@ -281,7 +281,7 @@ class JavaScriptTest extends DrupalUnitTestBase {
'type' => 'external',
'data' => $external,
);
drupal_render($attached);
$this->render($attached);
$javascript = drupal_get_js();
// Local files have a base_path() prefix, external files should not.
@ -300,7 +300,7 @@ class JavaScriptTest extends DrupalUnitTestBase {
'scope' => 'footer',
'attributes' => array('defer' => 'defer'),
);
drupal_render($attached);
$this->render($attached);
$javascript = drupal_get_js('footer');
$this->assertTrue(strpos($javascript, $inline) > 0, 'Rendered JavaScript footer returns the inline code.');
@ -312,7 +312,7 @@ class JavaScriptTest extends DrupalUnitTestBase {
function testNoCache() {
$attached['#attached']['library'][] = 'core/drupal';
$attached['#attached']['js']['core/misc/collapse.js'] = array('cache' => FALSE);
drupal_render($attached);
$this->render($attached);
$javascript = _drupal_add_js();
$this->assertFalse($javascript['core/misc/collapse.js']['preprocess'], 'Setting cache to FALSE sets proprocess to FALSE when adding JavaScript.');
}
@ -323,7 +323,7 @@ class JavaScriptTest extends DrupalUnitTestBase {
function testDifferentGroup() {
$attached['#attached']['library'][] = 'core/drupal';
$attached['#attached']['js']['core/misc/collapse.js'] = array('group' => JS_THEME);
drupal_render($attached);
$this->render($attached);
$javascript = _drupal_add_js();
$this->assertEqual($javascript['core/misc/collapse.js']['group'], JS_THEME, 'Adding a JavaScript file with a different group caches the given group.');
}
@ -333,7 +333,7 @@ class JavaScriptTest extends DrupalUnitTestBase {
*/
function testDifferentWeight() {
$attached['#attached']['js']['core/misc/collapse.js'] = array('weight' => 2);
drupal_render($attached);
$this->render($attached);
$javascript = _drupal_add_js();
$this->assertEqual($javascript['core/misc/collapse.js']['weight'], 2, 'Adding a JavaScript file with a different weight caches the given weight.');
}
@ -355,7 +355,7 @@ class JavaScriptTest extends DrupalUnitTestBase {
'data' => 'jQuery(function () { });',
'browsers' => array('IE' => FALSE),
);
drupal_render($attached);
$this->render($attached);
$javascript = drupal_get_js();
$expected_1 = "<!--[if lte IE 8]>\n" . '<script src="' . file_create_url('core/misc/collapse.js') . '?' . $default_query_string . '"></script>' . "\n<![endif]-->";
@ -372,7 +372,7 @@ class JavaScriptTest extends DrupalUnitTestBase {
$attached['#attached']['library'][] = 'core/drupal';
$attached['#attached']['js']['core/misc/collapse.js'] = array('version' => 'foo');
$attached['#attached']['js']['core/misc/ajax.js'] = array('version' => 'bar');
drupal_render($attached);
$this->render($attached);
$javascript = drupal_get_js();
$this->assertTrue(strpos($javascript, 'core/misc/collapse.js?v=foo') > 0 && strpos($javascript, 'core/misc/ajax.js?v=bar') > 0 , 'JavaScript version identifiers correctly appended to URLs');
}
@ -393,7 +393,7 @@ class JavaScriptTest extends DrupalUnitTestBase {
$attached['#attached']['js']['core/misc/collapse.js'] = array('every_page' => TRUE);
$attached['#attached']['js']['core/misc/autocomplete.js'] = array();
$attached['#attached']['js']['core/misc/batch.js'] = array('every_page' => TRUE);
drupal_render($attached);
$this->render($attached);
$javascript = drupal_get_js();
$expected = implode("\n", array(
'<script src="' . file_create_url('core/misc/collapse.js') . '?' . $default_query_string . '"></script>',
@ -415,7 +415,7 @@ class JavaScriptTest extends DrupalUnitTestBase {
$attached['#attached']['js']['core/misc/collapse.js'] = array('every_page' => TRUE);
$attached['#attached']['js']['core/misc/autocomplete.js'] = array();
$attached['#attached']['js']['core/misc/batch.js'] = array('every_page' => TRUE);
drupal_render($attached);
$this->render($attached);
$js_items = _drupal_add_js();
$javascript = drupal_get_js();
$expected = implode("\n", array(
@ -438,7 +438,7 @@ class JavaScriptTest extends DrupalUnitTestBase {
$attached['#attached']['library'][] = 'core/drupal';
$attached['#attached']['js']['core/misc/ajax.js'] = array();
$attached['#attached']['js']['core/misc/autocomplete.js'] = array();
drupal_render($attached);
$this->render($attached);
$js_items = _drupal_add_js();
$scripts_html = array(
@ -448,7 +448,7 @@ class JavaScriptTest extends DrupalUnitTestBase {
'core/misc/autocomplete.js' => $js_items['core/misc/autocomplete.js']
)
);
drupal_render($scripts_html);
$this->render($scripts_html);
// Store the expected key for the first item in the cache.
$cache = array_keys(\Drupal::state()->get('system.js_cache_files') ?: array());
@ -462,7 +462,7 @@ class JavaScriptTest extends DrupalUnitTestBase {
$attached['#attached']['js']['some/custom/javascript_file.js'] = array('scope' => 'footer');
$attached['#attached']['js']['core/misc/ajax.js'] = array();
$attached['#attached']['js']['core/misc/autocomplete.js'] = array();
drupal_render($attached);
$this->render($attached);
// Rebuild the cache.
$js_items = _drupal_add_js();
@ -473,7 +473,7 @@ class JavaScriptTest extends DrupalUnitTestBase {
'core/misc/autocomplete.js' => $js_items['core/misc/autocomplete.js']
)
);
drupal_render($scripts_html);
$this->render($scripts_html);
// Compare the expected key for the first file to the current one.
$cache = array_keys(\Drupal::state()->get('system.js_cache_files') ?: array());
@ -528,7 +528,7 @@ class JavaScriptTest extends DrupalUnitTestBase {
$attached['#attached']['js'][] = $shared_options + array(
'data' => '(function($){alert("Weight 0 #3");})(jQuery);',
);
drupal_render($attached);
$this->render($attached);
// Construct the expected result from the regex.
$expected = array(
@ -569,7 +569,7 @@ class JavaScriptTest extends DrupalUnitTestBase {
'every_page' => TRUE,
'weight' => -21,
);
drupal_render($attached);
$this->render($attached);
$javascript = drupal_get_js();
$this->assertTrue(strpos($javascript, 'core/misc/collapse.js') < strpos($javascript, 'core/assets/vendor/jquery/jquery.js'), 'Rendering a JavaScript file above jQuery.');
}
@ -583,7 +583,7 @@ class JavaScriptTest extends DrupalUnitTestBase {
// Add both tableselect.js and simpletest.js, with a larger weight on SimpleTest.
$attached['#attached']['js']['core/misc/tableselect.js'] = array();
$attached['#attached']['js'][drupal_get_path('module', 'simpletest') . '/simpletest.js'] = array('weight' => 9999);
drupal_render($attached);
$this->render($attached);
// Render the JavaScript, testing if simpletest.js was altered to be before
// tableselect.js. See simpletest_js_alter() to see where this alteration
@ -598,7 +598,7 @@ class JavaScriptTest extends DrupalUnitTestBase {
function testLibraryRender() {
$attached = array();
$attached['#attached']['library'][] = 'core/jquery.farbtastic';
drupal_render($attached);
$this->render($attached);
$scripts = drupal_get_js();
$styles = drupal_get_css();
$this->assertTrue(strpos($scripts, 'core/assets/vendor/farbtastic/farbtastic.js'), 'JavaScript of library was added to the page.');
@ -619,7 +619,7 @@ class JavaScriptTest extends DrupalUnitTestBase {
// common_test_library_info_alter() also added a dependency on jQuery Form.
$attached['#attached']['library'][] = 'core/jquery.farbtastic';
drupal_render($attached);
$this->render($attached);
$scripts = drupal_get_js();
$this->assertTrue(strpos($scripts, 'core/assets/vendor/jquery-form/jquery.form.js'), 'Altered library dependencies are added to the page.');
}
@ -647,7 +647,7 @@ class JavaScriptTest extends DrupalUnitTestBase {
drupal_static_reset('drupal_get_library');
$attached['#attached']['library'][] = 'unknown/unknown';
drupal_render($attached);
$this->render($attached);
$scripts = drupal_get_js();
$this->assertTrue(strpos($scripts, 'unknown') === FALSE, 'Unknown library was not added to the page.');
}
@ -657,7 +657,7 @@ class JavaScriptTest extends DrupalUnitTestBase {
*/
function testAttachedLibrary() {
$element['#attached']['library'][] = 'core/jquery.farbtastic';
drupal_render($element);
$this->render($element);
$scripts = drupal_get_js();
$this->assertTrue(strpos($scripts, 'core/assets/vendor/farbtastic/farbtastic.js'), 'The attached_library property adds the additional libraries.');
}

View File

@ -354,20 +354,14 @@ class RenderTest extends DrupalUnitTestBase {
// 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.');
$expected_js = [$parent_js, $child_js, $subchild_js];
$this->assertEqual($element['#attached']['js'], $expected_js, 'The element, child and subchild #attached JavaScript are 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.');
$this->assertEqual($element['#attached']['js'], $expected_js, 'The element, child and subchild #attached JavaScript are included.');
// Restore the previous request method.
\Drupal::request()->setMethod($request_method);
@ -430,11 +424,11 @@ class RenderTest extends DrupalUnitTestBase {
// Test that cache tags are correctly collected from the render element,
// including the ones from its subchild.
$expected_tags = array(
'rendered' => TRUE,
'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.');
$this->assertEqual($expected_tags, $element['#cache']['tags'], 'Cache tags were collected from the element and its subchild.');
// Restore the previous request method.
\Drupal::request()->setMethod($request_method);
@ -453,22 +447,22 @@ class RenderTest extends DrupalUnitTestBase {
);
// #cache disabled.
drupal_static_reset('_drupal_add_js');
$element = $test_element;
$element['#markup'] = '<p>#cache disabled</p>';
$output = drupal_render($element);
$this->assertIdentical($output, '<p>overridden</p>', 'Output is overridden.');
$this->assertIdentical($element['#markup'], '<p>overridden</p>', '#markup is overridden.');
$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.');
$expected_js = [
['type' => 'setting', 'data' => ['foo' => 'bar']],
['type' => 'setting', 'data' => ['common_test' => $context]],
];
$this->assertIdentical($element['#attached']['js'], $expected_js, '#attached is modified; both the original JavaScript setting and the one added by the #post_render_cache callback exist.');
// 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'] = '<p>#cache enabled, GET</p>';
@ -476,9 +470,11 @@ class RenderTest extends DrupalUnitTestBase {
$this->assertIdentical($output, '<p>overridden</p>', 'Output is overridden.');
$this->assertTrue(isset($element['#printed']), 'No cache hit');
$this->assertIdentical($element['#markup'], '<p>overridden</p>', '#markup is overridden.');
$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.');
$expected_js = [
['type' => 'setting', 'data' => ['foo' => 'bar']],
['type' => 'setting', 'data' => ['common_test' => $context]],
];
$this->assertIdentical($element['#attached']['js'], $expected_js, '#attached is modified; both the original JavaScript setting and the one added by the #post_render_cache callback exist.');
// GET request: validate cached data.
$element = array('#cache' => array('cid' => 'post_render_cache_test_GET'));
@ -492,23 +488,23 @@ class RenderTest extends DrupalUnitTestBase {
$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'] = '<p>#cache enabled, GET</p>';
$output = drupal_render($element);
$this->assertIdentical($output, '<p>overridden</p>', 'Output is overridden.');
$this->assertFalse(isset($element['#printed']), 'Cache hit');
$this->assertIdentical($element['#markup'], '<p>overridden</p>', '#markup is overridden.');
$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.');
$expected_js = [
['type' => 'setting', 'data' => ['foo' => 'bar']],
['type' => 'setting', 'data' => ['common_test' => $context]],
];
$this->assertIdentical($element['#attached']['js'], $expected_js, '#attached is modified; both the original JavaScript setting and the one added by the #post_render_cache callback exist.');
// 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'] = '<p>#cache enabled, POST</p>';
@ -516,9 +512,11 @@ class RenderTest extends DrupalUnitTestBase {
$this->assertIdentical($output, '<p>overridden</p>', 'Output is overridden.');
$this->assertTrue(isset($element['#printed']), 'No cache hit');
$this->assertIdentical($element['#markup'], '<p>overridden</p>', '#markup is overridden.');
$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.');
$expected_js = [
['type' => 'setting', 'data' => ['foo' => 'bar']],
['type' => 'setting', 'data' => ['common_test' => $context]],
];
$this->assertIdentical($element['#attached']['js'], $expected_js, '#attached is modified; both the original JavaScript setting and the one added by the #post_render_cache callback exist.');
// POST request: Ensure no data was cached.
$element = array('#cache' => array('cid' => 'post_render_cache_test_POST'));
@ -540,7 +538,6 @@ class RenderTest extends DrupalUnitTestBase {
// 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->randomContextValue());
$context_2 = array('bar' => $this->randomContextValue());
$context_3 = array('baz' => $this->randomContextValue());
@ -579,10 +576,13 @@ class RenderTest extends DrupalUnitTestBase {
$this->assertIdentical($output, '<p>overridden</p>', 'Output is overridden.');
$this->assertTrue(isset($element['#printed']), 'No cache hit');
$this->assertIdentical($element['#markup'], '<p>overridden</p>', '#markup is overridden.');
$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.');
$expected_js = [
['type' => 'setting', 'data' => ['foo' => 'bar']],
['type' => 'setting', 'data' => ['common_test' => $context_1 ]],
['type' => 'setting', 'data' => ['common_test' => $context_2 ]],
['type' => 'setting', 'data' => ['common_test' => $context_3 ]],
];
$this->assertIdentical($element['#attached']['js'], $expected_js, '#attached is modified; both the original JavaScript setting and the ones added by each #post_render_cache callback exist.');
// GET request: validate cached data.
$element = array('#cache' => $element['#cache']);
@ -619,27 +619,20 @@ class RenderTest extends DrupalUnitTestBase {
$this->assertIdentical($cached_element, $expected_element, 'The correct data is cached: the stored #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, '<p>overridden</p>', 'Output is overridden.');
$this->assertFalse(isset($element['#printed']), 'Cache hit');
$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.');
$this->assertIdentical($element['#attached']['js'], $expected_js, '#attached is modified; both the original JavaScript setting and the ones added by each #post_render_cache callback exist.');
// Test case 2.
// Use the exact same element, but now unset #cache.
drupal_static_reset('drupal_add_js');
unset($test_element['#cache']);
$element = $test_element;
$output = drupal_render($element);
$this->assertIdentical($output, '<p>overridden</p>', 'Output is overridden.');
$this->assertIdentical($element['#markup'], '<p>overridden</p>', '#markup is overridden.');
$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.');
$this->assertIdentical($element['#attached']['js'], $expected_js, '#attached is modified; both the original JavaScript setting and the ones added by each #post_render_cache callback exist.');
// Test case 3.
// Create an element with a child and subchild. Each element has the same
@ -649,7 +642,6 @@ class RenderTest extends DrupalUnitTestBase {
// #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');
@ -657,10 +649,7 @@ class RenderTest extends DrupalUnitTestBase {
$this->assertIdentical($output, '<p>overridden</p>', 'Output is overridden.');
$this->assertTrue(isset($element['#printed']), 'No cache hit');
$this->assertIdentical($element['#markup'], '<p>overridden</p>', '#markup is overridden.');
$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.');
$this->assertIdentical($element['#attached']['js'], $expected_js, '#attached is modified; both the original JavaScript setting and the ones added by each #post_render_cache callback exist.');
// GET request: validate cached data for both the parent and child.
$element = $test_element;
@ -725,28 +714,25 @@ class RenderTest extends DrupalUnitTestBase {
$this->assertIdentical($cached_child_element, $expected_child_element, 'The correct data is cached for the child: the stored #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, '<p>overridden</p>', 'Output is overridden.');
$this->assertFalse(isset($element['#printed']), 'Cache hit');
$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.');
$this->assertIdentical($element['#attached']['js'], $expected_js, '#attached is modified; both the original JavaScript setting and the ones added by each #post_render_cache callback exist.');
// 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, '<p>overridden</p>', 'Output is overridden.');
$this->assertFalse(isset($element['#printed']), 'Cache hit');
$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.');
$expected_js = [
['type' => 'setting', 'data' => ['common_test' => $context_2 ]],
['type' => 'setting', 'data' => ['common_test' => $context_3 ]],
];
$this->assertIdentical($element['#attached']['js'], $expected_js, '#attached is modified; both the original JavaScript setting and the ones added by each #post_render_cache callback exist.');
// Restore the previous request method.
\Drupal::request()->setMethod($request_method);
@ -776,27 +762,27 @@ class RenderTest extends DrupalUnitTestBase {
$expected_output = '<foo><bar>' . $context['bar'] . '</bar></foo>';
// #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.');
$expected_js = [
['type' => 'setting', 'data' => ['common_test' => $context]],
];
$this->assertIdentical($element['#attached']['js'], $expected_js, '#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.');
$this->assertIdentical($output, $expected_output, 'Placeholder was replaced in output');
$this->assertIdentical($element['#attached']['js'], $expected_js, '#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'];
@ -815,6 +801,7 @@ class RenderTest extends DrupalUnitTestBase {
// Verify the token is in the cached element.
$expected_element = array(
'#markup' => '<foo><drupal-render-cache-placeholder callback="common_test_post_render_cache_placeholder" token="'. $expected_token . '"></drupal-render-cache-placeholder></foo>',
'#attached' => array(),
'#post_render_cache' => array(
'common_test_post_render_cache_placeholder' => array(
$context
@ -825,77 +812,69 @@ class RenderTest extends DrupalUnitTestBase {
$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.');
$this->assertIdentical($element['#attached']['js'], $expected_js, '#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.
* Tests child element that uses #post_render_cache but that is rendered via a
* template.
*/
function testDrupalRenderChildElementRenderCachePlaceholder() {
$container = array(
'#type' => 'container',
);
$context = array(
'bar' => $this->randomContextValue(),
);
$callback = 'common_test_post_render_cache_placeholder';
$placeholder = drupal_render_cache_generate_placeholder($callback, $context);
$test_element = array(
'#post_render_cache' => array(
$callback => array(
$context
),
),
'#markup' => $placeholder,
'#prefix' => '<foo>',
'#suffix' => '</foo>'
);
$container['test_element'] = $test_element;
$expected_output = '<div><foo><bar>' . $context['bar'] . '</bar></foo></div>' . "\n";
$test_element = [
'#theme' => 'common_test_render_element',
'foo' => [
'#post_render_cache' => [
$callback => [
$context
],
],
'#markup' => $placeholder,
'#prefix' => '<foo>',
'#suffix' => '</foo>'
],
];
$expected_output = '<foo><bar>' . $context['bar'] . '</bar></foo>' . "\n";
// #cache disabled.
drupal_static_reset('_drupal_add_js');
$element = $container;
$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.');
$expected_js = [
['type' => 'setting', 'data' => ['common_test' => $context]],
];
$this->assertIdentical($element['#attached']['js'], $expected_js, '#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 = $test_element;
$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.
$element['foo']['#cache'] = array('cid' => 'render_cache_placeholder_test_child_GET');
// Render, which will use the common-test-render-element.html.twig template.
$output = drupal_render($element);
$this->assertIdentical($output, $expected_output, 'Placeholder was replaced in output');
$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.');
$this->assertIdentical($element['#attached']['js'], $expected_js, '#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'];
$child_tokens = $element['foo']['#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'));
@ -913,6 +892,7 @@ class RenderTest extends DrupalUnitTestBase {
// Verify the token is in the cached element.
$expected_element = array(
'#markup' => '<foo><drupal-render-cache-placeholder callback="common_test_post_render_cache_placeholder" token="'. $expected_token . '"></drupal-render-cache-placeholder></foo>',
'#attached' => array(),
'#post_render_cache' => array(
'common_test_post_render_cache_placeholder' => array(
$context,
@ -937,7 +917,8 @@ class RenderTest extends DrupalUnitTestBase {
$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' => '<div><foo><drupal-render-cache-placeholder callback="common_test_post_render_cache_placeholder" token="'. $expected_token . '"></drupal-render-cache-placeholder></foo></div>' . "\n",
'#markup' => '<foo><drupal-render-cache-placeholder callback="common_test_post_render_cache_placeholder" token="'. $expected_token . '"></drupal-render-cache-placeholder></foo>' . "\n",
'#attached' => array(),
'#post_render_cache' => array(
'common_test_post_render_cache_placeholder' => array(
$context,
@ -945,7 +926,7 @@ class RenderTest extends DrupalUnitTestBase {
),
'#cache' => array('tags' => array('rendered' => TRUE)),
);
$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.');
$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
@ -967,6 +948,7 @@ class RenderTest extends DrupalUnitTestBase {
// Verify the token is in the cached element.
$expected_element = array(
'#markup' => '<foo><drupal-render-cache-placeholder callback="common_test_post_render_cache_placeholder" token="'. $expected_token . '"></drupal-render-cache-placeholder></foo>',
'#attached' => array(),
'#post_render_cache' => array(
'common_test_post_render_cache_placeholder' => array(
$context,
@ -977,32 +959,153 @@ class RenderTest extends DrupalUnitTestBase {
$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 = $test_element;
$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);
// Render, which will use the common-test-render-element.html.twig template.
$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.');
$this->assertIdentical($element['#attached']['js'], $expected_js, '#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 = Json::decode($json);
return $parsed_settings;
/**
* #pre_render callback for testDrupalRenderBubbling().
*/
public static function bubblingPreRender($elements) {
$callback = 'Drupal\system\Tests\Common\RenderTest::bubblingPostRenderCache';
$context = array(
'foo' => 'bar',
'baz' => 'qux',
);
$placeholder = drupal_render_cache_generate_placeholder($callback, $context);
$elements += array(
'child_cache_tag' => array(
'#cache' => array(
'tags' => array('child' => 'cache_tag'),
),
'#markup' => 'Cache tag!',
),
'child_asset' => array(
'#attached' => array(
'js' => array(
array(
'type' => 'setting',
'data' => array('foo' => 'bar'),
)
),
),
'#markup' => 'Asset!',
),
'child_post_render_cache' => array(
'#post_render_cache' => array(
$callback => array(
$context,
),
),
'#markup' => $placeholder,
),
'child_nested_pre_render_uncached' => array(
'#cache' => array('cid' => 'uncached_nested'),
'#pre_render' => array('Drupal\system\Tests\Common\RenderTest::bubblingNestedPreRenderUncached'),
),
'child_nested_pre_render_cached' => array(
'#cache' => array('cid' => 'cached_nested'),
'#pre_render' => array('Drupal\system\Tests\Common\RenderTest::bubblingNestedPreRenderCached'),
),
);
return $elements;
}
/**
* #pre_render callback for testDrupalRenderBubbling().
*/
public static function bubblingNestedPreRenderUncached($elements) {
\Drupal::state()->set('bubbling_nested_pre_render_uncached', TRUE);
$elements['#markup'] = 'Nested!';
return $elements;
}
/**
* #pre_render callback for testDrupalRenderBubbling().
*/
public static function bubblingNestedPreRenderCached($elements) {
\Drupal::state()->set('bubbling_nested_pre_render_cached', TRUE);
return $elements;
}
/**
* #post_render_cache callback for testDrupalRenderBubbling().
*/
public static function bubblingPostRenderCache(array $element, array $context) {
$callback = 'Drupal\system\Tests\Common\RenderTest::bubblingPostRenderCache';
$placeholder = drupal_render_cache_generate_placeholder($callback, $context);
$element['#markup'] = str_replace($placeholder, 'Post-render cache!' . $context['foo'] . $context['baz'], $element['#markup']);
return $element;
}
/**
* Tests bubbling of assets, cache tags and post-render cache callbacks when
* they are added by #pre_render callbacks.
*/
function testDrupalRenderBubbling() {
$verify_result= function ($test_element) {
\Drupal::state()->set('bubbling_nested_pre_render_uncached', FALSE);
\Drupal::state()->set('bubbling_nested_pre_render_cached', FALSE);
\Drupal::cache('render')->set('cached_nested', array('#markup' => 'Cached nested!', '#attached' => array(), '#cache' => array('tags' => array()), '#post_render_cache' => array()));
\Drupal::cache('render')->delete('uncached_nested');
$output = drupal_render($test_element);
// Assert top-level.
$this->assertEqual('Cache tag!Asset!Post-render cache!barquxNested!Cached nested!', trim($output), 'Expected HTML generated.');
$this->assertEqual(array('child' => 'cache_tag'), $test_element['#cache']['tags'], 'Expected cache tags found.');
$expected_attached = array(
'js' => array(
0 => array(
'type' => 'setting',
'data' => array('foo' => 'bar'),
),
),
);
$this->assertEqual($expected_attached, $test_element['#attached'], 'Expected assets found.');
$expected_post_render_cache = array(
'Drupal\\system\\Tests\\Common\\RenderTest::bubblingPostRenderCache' => array(
0 => array (
'foo' => 'bar',
'baz' => 'qux',
),
),
);
$post_render_cache = $test_element['#post_render_cache'];
// We don't care about the exact token.
unset($post_render_cache['Drupal\\system\\Tests\\Common\\RenderTest::bubblingPostRenderCache'][0]['token']);
$this->assertEqual($expected_post_render_cache, $post_render_cache, 'Expected post-render cache data found.');
// Ensure that #pre_render callbacks are only executed if they don't have
// a render cache hit.
$this->assertTrue(\Drupal::state()->get('bubbling_nested_pre_render_uncached'));
$this->assertFalse(\Drupal::state()->get('bubbling_nested_pre_render_cached'));
};
$this->pass('Test <strong>without</strong> theming/Twig.');
$test_element_without_theme = array(
'foo' => array(
'#pre_render' => array(array(get_class($this), 'bubblingPreRender')),
),
);
$verify_result($test_element_without_theme);
$this->pass('Test <strong>with</strong> theming/Twig.');
$test_element_with_theme = array(
'#theme' => 'common_test_render_element',
'foo' => array(
'#pre_render' => array(array(get_class($this), 'bubblingPreRender')),
),
);
$verify_result($test_element_with_theme);
}
/**

View File

@ -46,7 +46,7 @@ abstract class EntityWithUriCacheTagsTestBase extends EntityCacheTagsTestBase {
$cid = 'entity_view:' . $entity_type . ':' . $this->entity->id() . ':' . $view_mode . ':stark:r.anonymous:' . date_default_timezone_get();
$cache_entry = \Drupal::cache('render')->get($cid);
$expected_cache_tags = array_merge(array($view_cache_tag, $cache_tag), $this->getAdditionalCacheTagsForEntity($this->entity), array($render_cache_tag));
$this->assertIdentical($cache_entry->tags, $expected_cache_tags);
$this->verifyRenderCache($cid, $expected_cache_tags);
}
// Verify that after modifying the entity, there is a cache miss.

View File

@ -180,7 +180,9 @@ function ajax_forms_test_advanced_commands_settings_with_merging_callback($form,
),
),
);
// @todo Why is this being tested via an explicit drupal_render() call?
drupal_render($attached);
drupal_process_attached($attached);
$response = new AjaxResponse();
return $response;

View File

@ -73,6 +73,7 @@ class AjaxFormsTestLazyLoadForm extends FormBase {
),
);
drupal_render($attached);
drupal_process_attached($attached);
}
$form_state['rebuild'] = TRUE;
}

View File

@ -33,7 +33,9 @@ function ajax_test_render() {
),
),
);
// @todo Why is this being tested via an explicit drupal_render() call?
drupal_render($attached);
drupal_process_attached($attached);
$response = new AjaxResponse();
return $response;
}
@ -69,7 +71,11 @@ function ajax_test_order() {
),
),
);
// @todo Why is this being tested via an explicit drupal_render() call?
drupal_render($attached);
drupal_process_attached($attached);
return $response;
}

View File

@ -117,6 +117,10 @@ function common_test_theme() {
'variables' => array('foo' => 'foo', 'bar' => 'bar'),
'template' => 'common-test-foo',
),
'common_test_render_element' => array(
'render element' => 'foo',
'template' => 'common-test-render-element',
),
'common_test_empty' => array(
'variables' => array('foo' => 'foo'),
),

View File

@ -0,0 +1,12 @@
{#
/**
* @file
* Default theme implementation for the common test render element.
*
* Available variables:
* - foo: a render array
*
* @ingroup themeable
*/
#}
{{ foo }}

View File

@ -60,18 +60,6 @@ class TextDefaultFormatter extends FormatterBase {
'#format' => $item->format,
'#langcode' => $item->getLangcode(),
);
// The viewElements() method of entity field formatters is run
// during the #pre_render phase of rendering an entity. A formatter
// builds the content of the field in preparation for theming.
// All cache tags must be available after the #pre_render phase. In order
// to collect the cache tags associated with the processed text, it must
// be passed to drupal_render() so that its #pre_render callback is
// invoked and its full build array is assembled. Rendering the processed
// text in place here will allow its cache tags to be bubbled up and
// included with those of the main entity when cache tags are collected
// for a renderable array in drupal_render().
// @todo remove this work-around, see https://drupal.org/node/2273277
drupal_render($elements[$delta], TRUE);
}
return $elements;

View File

@ -2,7 +2,7 @@
/**
* @file
* Contains \Drupal\text\Plugin\field\formatter\TextTrimmedFormatter.
* Contains \Drupal\text\Plugin\field\FieldFormatter\TextTrimmedFormatter.
*/
namespace Drupal\text\Plugin\Field\FieldFormatter;
@ -90,6 +90,16 @@ class TextTrimmedFormatter extends FormatterBase {
protected function viewElementsWithTextProcessing(FieldItemListInterface $items) {
$elements = array();
$render_as_summary = function (&$element) {
// Make sure any default #pre_render callbacks are set on the element,
// because text_pre_render_summary() must run last.
$element += \Drupal::service('element_info')->getInfo($element['#type']);
// Add the #pre_render callback that renders the text into a summary.
$element['#pre_render'][] = '\Drupal\text\Plugin\field\FieldFormatter\TextTrimmedFormatter::preRenderSummary';
// Pass on the trim length to the #pre_render callback via a property.
$element['#text_summary_trim_length'] = $this->getSetting('trim_length');
};
foreach ($items as $delta => $item) {
$elements[$delta] = array(
'#type' => 'processed_text',
@ -98,26 +108,12 @@ class TextTrimmedFormatter extends FormatterBase {
'#langcode' => $item->getLangcode(),
);
// The viewElements() method of entity field formatters is run
// during the #pre_render phase of rendering an entity. A formatter
// builds the content of the field in preparation for theming.
// All cache tags must be available after the #pre_render phase. In order
// to collect the cache tags associated with the processed text, it must
// be passed to drupal_render() so that its #pre_render callback is
// invoked and its full build array is assembled. Rendering the processed
// text in place here will allow its cache tags to be bubbled up and
// included with those of the main entity when cache tags are collected
// for a renderable array in drupal_render().
if ($this->getPluginId() == 'text_summary_or_trimmed' && !empty($item->summary)) {
$elements[$delta]['#text'] = $item->summary;
// @todo remove this work-around, see https://drupal.org/node/2273277
drupal_render($elements[$delta], TRUE);
}
else {
$elements[$delta]['#text'] = $item->value;
// @todo remove this work-around, see https://drupal.org/node/2273277
drupal_render($elements[$delta], TRUE);
$elements[$delta]['#markup'] = text_summary($elements[$delta]['#markup'], $item->format, $this->getSetting('trim_length'));
$render_as_summary($elements[$delta]);
}
}
@ -153,4 +149,27 @@ class TextTrimmedFormatter extends FormatterBase {
return $elements;
}
/**
* Pre-render callback: Renders a processed text element's #markup as a summary.
*
* @param array $element
* A structured array with the following key-value pairs:
* - #markup: the filtered text (as filtered by filter_pre_render_text())
* - #format: containing the machine name of the filter format to be used to
* filter the text. Defaults to the fallback format. See
* filter_fallback_format().
* - #text_summary_trim_length: the desired character length of the summary
* (used by text_summary())
*
* @return array
* The passed-in element with the filtered text in '#markup' trimmed.
*
* @see filter_pre_render_text()
* @see text_summary()
*/
public static function preRenderSummary(array $element) {
$element['#markup'] = text_summary($element['#markup'], $element['#format'], $element['#text_summary_trim_length']);
return $element;
}
}

View File

@ -111,9 +111,10 @@ class TextFormatterTest extends EntityUnitTestBase {
foreach ($formatters as $formatter) {
// Verify the processed text field formatter's render array.
$build = $entity->get('processed_text')->view(array('type' => $formatter));
drupal_render($build[0]);
$this->assertEqual($build[0]['#markup'], "<p>Hello, world!</p>\n");
$expected_cache_tags = array(
'filter_format' => array('my_text_format' => 'my_text_format'),
'filter_format' => array('my_text_format'),
);
$this->assertEqual($build[0]['#cache']['tags'], $expected_cache_tags, format_string('The @formatter formatter has the expected cache tags when formatting a processed text field.', array('@formatter' => $formatter)));

View File

@ -26,6 +26,7 @@ function views_ajax_form_wrapper($form_class, FormStateInterface &$form_state) {
$form = \Drupal::formBuilder()->buildForm($form_class, $form_state);
$output = drupal_render($form);
drupal_process_attached($form);
// These forms have the title built in, so set the title here:
if (empty($form_state['ajax']) && !empty($form_state['title'])) {

View File

@ -156,7 +156,9 @@ class ViewAjaxController implements ContainerInjectionInterface {
* @see https://drupal.org/node/2171071
*/
protected function drupalRender(array $elements) {
return drupal_render($elements);
$output = drupal_render($elements);
drupal_process_attached($elements);
return $output;
}
}

View File

@ -244,7 +244,7 @@ abstract class CachePluginBase extends PluginBase {
$this->storage['head'] = '';
}
$attached = drupal_render_collect_attached($this->storage['output']);
$attached = $this->storage['output']['#attached'];
$this->storage['css'] = $attached['css'];
$this->storage['js'] = $attached['js'];
}

View File

@ -45,7 +45,7 @@ class Rss extends StylePluginBase {
if ($display->hasPath()) {
if (empty($this->preview)) {
$build['#attached']['drupal_add_feed'][] = array($url, $title);
drupal_render($build);
drupal_process_attached($build);
}
}
else {
@ -64,6 +64,7 @@ class Rss extends StylePluginBase {
'href' => $url,
);
$this->view->feed_icon .= drupal_render($feed_icon);
drupal_process_attached($feed_icon);
}
}

View File

@ -135,20 +135,15 @@ class CacheTest extends PluginTestBase {
$output = $view->preview();
drupal_render($output);
unset($view->pre_render_called);
drupal_static_reset('_drupal_add_css');
drupal_static_reset('_drupal_add_js');
$view->destroy();
$view->setDisplay();
$output = $view->preview();
drupal_render($output);
$css = _drupal_add_css();
$css_path = drupal_get_path('module', 'views_test_data') . '/views_cache.test.css';
$js_path = drupal_get_path('module', 'views_test_data') . '/views_cache.test.js';
$js = _drupal_add_js();
$this->assertTrue(isset($css[basename($css_path)]), 'Make sure the css is added for cached views.');
$this->assertTrue(isset($js[$js_path]), 'Make sure the js is added for cached views.');
$this->assertTrue(in_array($css_path, $output['#attached']['css']), 'Make sure the css is added for cached views.');
$this->assertTrue(in_array($js_path, $output['#attached']['js']), 'Make sure the js is added for cached views.');
$this->assertFalse(!empty($view->build_info['pre_render_called']), 'Make sure hook_views_pre_render is not called for the cached view.');
// Now add some css/jss before running the view.
@ -165,22 +160,19 @@ class CacheTest extends PluginTestBase {
),
);
drupal_render($attached);
drupal_process_attached($attached);
$view->destroy();
$output = $view->preview();
drupal_render($output);
drupal_static_reset('_drupal_add_css');
drupal_static_reset('_drupal_add_js');
$this->assertTrue(empty($output['#attached']['css']), 'The view does not have attached CSS.');
$this->assertTrue(empty($output['#attached']['js']), 'The view does not have attached JS.');
$view->destroy();
$output = $view->preview();
drupal_render($output);
$css = _drupal_add_css();
$js = _drupal_add_js();
$this->assertFalse(isset($css['system.maintenance.css']), 'Make sure that unrelated css is not added.');
$this->assertFalse(isset($js[drupal_get_path('module', 'user') . '/user.permissions.js']), 'Make sure that unrelated js is not added.');
$this->assertTrue(empty($output['#attached']['css']), 'The cached view does not have attached CSS.');
$this->assertTrue(empty($output['#attached']['js']), 'The cached view does not have attached JS.');
}
}

View File

@ -54,7 +54,8 @@ class RowEntityTest extends ViewUnitTestBase {
$term->save();
$view = Views::getView('test_entity_row');
$this->render($view->preview());
$build = $view->preview();
$this->render($build);
$this->assertText($term->getName(), 'The rendered entity appears as row in the view.');

View File

@ -86,6 +86,7 @@ class ElementInfoManagerTest extends UnitTestCase {
'#type' => 'page',
'#show_messages' => TRUE,
'#theme' => 'page',
'#defaults_loaded' => TRUE,
),
array('page' => array(
'#show_messages' => TRUE,
@ -96,6 +97,7 @@ class ElementInfoManagerTest extends UnitTestCase {
$data[] = array(
'form',
array(
'#defaults_loaded' => TRUE,
),
array('page' => array(
'#show_messages' => TRUE,
@ -110,6 +112,7 @@ class ElementInfoManagerTest extends UnitTestCase {
'#show_messages' => TRUE,
'#theme' => 'page',
'#number' => 597219,
'#defaults_loaded' => TRUE,
),
array('page' => array(
'#show_messages' => TRUE,
@ -178,6 +181,7 @@ class ElementInfoManagerTest extends UnitTestCase {
'#type' => 'page',
'#show_messages' => TRUE,
'#theme' => 'page',
'#defaults_loaded' => TRUE,
),
);
@ -189,6 +193,7 @@ class ElementInfoManagerTest extends UnitTestCase {
'#theme' => 'page',
'#input' => TRUE,
'#value_callback' => array('TestElementPlugin', 'valueCallback'),
'#defaults_loaded' => TRUE,
),
);
return $data;