Issue #2450993 by Wim Leers, Fabianx, Crell, dawehner, effulgentsia: Rendered Cache Metadata created during the main controller request gets lost
parent
2dbda2635a
commit
ebb21d2804
|
@ -1392,6 +1392,11 @@ services:
|
|||
renderer:
|
||||
class: Drupal\Core\Render\Renderer
|
||||
arguments: ['@controller_resolver', '@theme.manager', '@plugin.manager.element_info', '@render_cache', '%renderer.config%']
|
||||
early_rendering_controller_wrapper_subscriber:
|
||||
class: Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber
|
||||
arguments: ['@controller_resolver', '@renderer']
|
||||
tags:
|
||||
- { name: event_subscriber }
|
||||
email.validator:
|
||||
class: Egulias\EmailValidator\EmailValidator
|
||||
|
||||
|
|
|
@ -0,0 +1,166 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\EventSubscriber;
|
||||
|
||||
use Drupal\Core\Cache\CacheableDependencyInterface;
|
||||
use Drupal\Core\Cache\CacheableResponseInterface;
|
||||
use Drupal\Core\Controller\ControllerResolverInterface;
|
||||
use Drupal\Core\Render\AttachmentsInterface;
|
||||
use Drupal\Core\Render\BubbleableMetadata;
|
||||
use Drupal\Core\Render\RenderContext;
|
||||
use Drupal\Core\Render\RendererInterface;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
|
||||
use Symfony\Component\HttpKernel\KernelEvents;
|
||||
|
||||
/**
|
||||
* Subscriber that wraps controllers, to handle early rendering.
|
||||
*
|
||||
* When controllers call drupal_render() (RendererInterface::render()) outside
|
||||
* of a render context, we call that "early rendering". Controllers should
|
||||
* return only render arrays, but we cannot prevent controllers from doing early
|
||||
* rendering. The problem with early rendering is that the bubbleable metadata
|
||||
* (cacheability & attachments) are lost.
|
||||
*
|
||||
* This can lead to broken pages (missing assets), stale pages (missing cache
|
||||
* tags causing a page not to be invalidated) or even security problems (missing
|
||||
* cache contexts causing a cached page not to be varied sufficiently).
|
||||
*
|
||||
* This event subscriber wraps all controller executions in a closure that sets
|
||||
* up a render context. Consequently, any early rendering will have their
|
||||
* bubbleable metadata (assets & cacheability) stored on that render context.
|
||||
*
|
||||
* If the render context is empty, then the controller either did not do any
|
||||
* rendering at all, or used the RendererInterface::renderRoot() or
|
||||
* ::renderPlain() methods. In that case, no bubbleable metadata is lost.
|
||||
*
|
||||
* If the render context is not empty, then the controller did use
|
||||
* drupal_render(), and bubbleable metadata was collected. This bubbleable
|
||||
* metadata is then merged onto the render array.
|
||||
*
|
||||
* In other words: this just exists to ease the transition to Drupal 8: it
|
||||
* allows controllers that return render arrays (the majority) to still do early
|
||||
* rendering. But controllers that return responses are already expected to do
|
||||
* the right thing: if early rendering is detected in such a case, an exception
|
||||
* is thrown.
|
||||
*
|
||||
* @see \Drupal\Core\Render\RendererInterface
|
||||
* @see \Drupal\Core\Render\Renderer
|
||||
*
|
||||
* @todo Remove in Drupal 9.0.0, by disallowing early rendering.
|
||||
*/
|
||||
class EarlyRenderingControllerWrapperSubscriber implements EventSubscriberInterface {
|
||||
|
||||
/**
|
||||
* The controller resolver.
|
||||
*
|
||||
* @var \Drupal\Core\Controller\ControllerResolverInterface
|
||||
*/
|
||||
protected $controllerResolver;
|
||||
|
||||
/**
|
||||
* The renderer.
|
||||
*
|
||||
* @var \Drupal\Core\Render\RendererInterface
|
||||
*/
|
||||
protected $renderer;
|
||||
|
||||
/**
|
||||
* Constructs a new EarlyRenderingControllerWrapperSubscriber instance.
|
||||
*
|
||||
* @param \Drupal\Core\Controller\ControllerResolverInterface $controller_resolver
|
||||
* The controller resolver.
|
||||
* @param \Drupal\Core\Render\RendererInterface $renderer
|
||||
* The renderer.
|
||||
*/
|
||||
public function __construct(ControllerResolverInterface $controller_resolver, RendererInterface $renderer) {
|
||||
$this->controllerResolver = $controller_resolver;
|
||||
$this->renderer = $renderer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures bubbleable metadata from early rendering is not lost.
|
||||
*
|
||||
* @param \Symfony\Component\HttpKernel\Event\FilterControllerEvent $event
|
||||
* The controller event.
|
||||
*/
|
||||
public function onController(FilterControllerEvent $event) {
|
||||
$controller = $event->getController();
|
||||
|
||||
// See \Symfony\Component\HttpKernel\HttpKernel::handleRaw().
|
||||
$arguments = $this->controllerResolver->getArguments($event->getRequest(), $controller);
|
||||
|
||||
$event->setController(function() use ($controller, $arguments) {
|
||||
return $this->wrapControllerExecutionInRenderContext($controller, $arguments);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps a controller execution in a render context.
|
||||
*
|
||||
* @param callable $controller
|
||||
* The controller to execute.
|
||||
* @param array $arguments
|
||||
* The arguments to pass to the controller.
|
||||
*
|
||||
* @return mixed
|
||||
* The return value of the controller.
|
||||
*
|
||||
* @throws \LogicException
|
||||
* When early rendering has occurred in a controller that returned a
|
||||
* Response or domain object that cares about attachments or cacheability.
|
||||
*
|
||||
* @see \Symfony\Component\HttpKernel\HttpKernel::handleRaw()
|
||||
*/
|
||||
protected function wrapControllerExecutionInRenderContext($controller, array $arguments) {
|
||||
$context = new RenderContext();
|
||||
|
||||
$response = $this->renderer->executeInRenderContext($context, function() use ($controller, $arguments) {
|
||||
// Now call the actual controller, just like HttpKernel does.
|
||||
return call_user_func_array($controller, $arguments);
|
||||
});
|
||||
|
||||
// If early rendering happened, i.e. if code in the controller called
|
||||
// drupal_render() outside of a render context, then the bubbleable metadata
|
||||
// for that is stored in the current render context.
|
||||
if (!$context->isEmpty()) {
|
||||
// If a render array is returned by the controller, merge the "lost"
|
||||
// bubbleable metadata.
|
||||
if (is_array($response)) {
|
||||
$early_rendering_bubbleable_metadata = $context->pop();
|
||||
BubbleableMetadata::createFromRenderArray($response)
|
||||
->merge($early_rendering_bubbleable_metadata)
|
||||
->applyTo($response);
|
||||
}
|
||||
// If a Response or domain object is returned, and it cares about
|
||||
// attachments or cacheability, then throw an exception: early rendering
|
||||
// is not permitted in that case. It is the developer's responsibility
|
||||
// to not use early rendering.
|
||||
elseif ($response instanceof AttachmentsInterface || $response instanceof CacheableResponseInterface || $response instanceof CacheableDependencyInterface) {
|
||||
throw new \LogicException(sprintf('The controller result claims to be providing relevant cache metadata, but leaked metadata was detected. Please ensure you are not rendering content too early. Returned object class: %s.', get_class($response)));
|
||||
}
|
||||
else {
|
||||
// A Response or domain object is returned that does not care about
|
||||
// attachments nor cacheability. E.g. a RedirectResponse. It is safe to
|
||||
// discard any early rendering metadata.
|
||||
}
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getSubscribedEvents() {
|
||||
$events[KernelEvents::CONTROLLER][] = ['onController'];
|
||||
|
||||
return $events;
|
||||
}
|
||||
|
||||
}
|
|
@ -14,6 +14,7 @@ use Drupal\Core\Extension\ModuleHandlerInterface;
|
|||
use Drupal\Core\Render\HtmlResponse;
|
||||
use Drupal\Core\Render\PageDisplayVariantSelectionEvent;
|
||||
use Drupal\Core\Render\RenderCacheInterface;
|
||||
use Drupal\Core\Render\RenderContext;
|
||||
use Drupal\Core\Render\RendererInterface;
|
||||
use Drupal\Core\Render\RenderEvents;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
|
@ -181,7 +182,9 @@ class HtmlRenderer implements MainContentRendererInterface {
|
|||
// ::renderResponse().
|
||||
// @todo Remove this once https://www.drupal.org/node/2359901 lands.
|
||||
if (!empty($main_content)) {
|
||||
$this->renderer->render($main_content, FALSE);
|
||||
$this->renderer->executeInRenderContext(new RenderContext(), function() use (&$main_content) {
|
||||
return $this->renderer->render($main_content, FALSE);
|
||||
});
|
||||
$main_content = $this->renderCache->getCacheableRenderArray($main_content) + [
|
||||
'#title' => isset($main_content['#title']) ? $main_content['#title'] : NULL
|
||||
];
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Render\RenderContext.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Render;
|
||||
|
||||
/**
|
||||
* The render context: a stack containing bubbleable rendering metadata.
|
||||
*
|
||||
* A stack of \Drupal\Core\Render\BubbleableMetadata objects.
|
||||
*
|
||||
* @see \Drupal\Core\Render\RendererInterface
|
||||
* @see \Drupal\Core\Render\Renderer
|
||||
* @see \Drupal\Core\Render\BubbleableMetadata
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class RenderContext extends \SplStack {
|
||||
|
||||
/**
|
||||
* Updates the current frame of the stack.
|
||||
*
|
||||
* @param array &$element
|
||||
* The element of the render array that has just been rendered. The stack
|
||||
* frame for this element will be updated with the bubbleable rendering
|
||||
* metadata of this element.
|
||||
*/
|
||||
public function update(&$element) {
|
||||
// The latest frame represents the bubbleable metadata for the subtree.
|
||||
$frame = $this->pop();
|
||||
// Update the frame, but also update the current element, to ensure it
|
||||
// contains up-to-date information in case it gets render cached.
|
||||
$updated_frame = BubbleableMetadata::createFromRenderArray($element)->merge($frame);
|
||||
$updated_frame->applyTo($element);
|
||||
$this->push($updated_frame);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bubbles the stack.
|
||||
*
|
||||
* Whenever another level in the render array has been rendered, the stack
|
||||
* must be bubbled, to merge its rendering metadata with that of the parent
|
||||
* element.
|
||||
*/
|
||||
public function bubble() {
|
||||
// If there's only one frame on the stack, then this is the root call, and
|
||||
// we can't bubble up further. ::renderRoot() will reset the stack, but we
|
||||
// must not reset it here to allow users of ::executeInRenderContext() to
|
||||
// access the stack directly.
|
||||
if ($this->count() === 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Merge the current and the parent stack frame.
|
||||
$current = $this->pop();
|
||||
$parent = $this->pop();
|
||||
$this->push($current->merge($parent));
|
||||
}
|
||||
|
||||
}
|
|
@ -58,11 +58,24 @@ class Renderer implements RendererInterface {
|
|||
protected $rendererConfig;
|
||||
|
||||
/**
|
||||
* The stack containing bubbleable rendering metadata.
|
||||
* The render context.
|
||||
*
|
||||
* @var \SplStack|null
|
||||
* This must be static as long as some controllers rebuild the container
|
||||
* during a request. This causes multiple renderer instances to co-exist
|
||||
* simultaneously, render state getting lost, and therefore causing pages to
|
||||
* fail to render correctly. As soon as it is guaranteed that during a request
|
||||
* the same container is used, it no longer needs to be static.
|
||||
*
|
||||
* @var \Drupal\Core\Render\RenderContext|null
|
||||
*/
|
||||
protected static $stack;
|
||||
protected static $context;
|
||||
|
||||
/**
|
||||
* Whether we're currently in a ::renderRoot() call.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $isRenderingRoot = FALSE;
|
||||
|
||||
/**
|
||||
* Constructs a new Renderer.
|
||||
|
@ -90,18 +103,29 @@ class Renderer implements RendererInterface {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function renderRoot(&$elements) {
|
||||
return $this->render($elements, TRUE);
|
||||
// Disallow calling ::renderRoot() from within another ::renderRoot() call.
|
||||
if ($this->isRenderingRoot) {
|
||||
$this->isRenderingRoot = FALSE;
|
||||
throw new \LogicException('A stray renderRoot() invocation is causing bubbling of attached assets to break.');
|
||||
}
|
||||
|
||||
// Render in its own render context.
|
||||
$this->isRenderingRoot = TRUE;
|
||||
$output = $this->executeInRenderContext(new RenderContext(), function () use (&$elements) {
|
||||
return $this->render($elements, TRUE);
|
||||
});
|
||||
$this->isRenderingRoot = FALSE;
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function renderPlain(&$elements) {
|
||||
$current_stack = static::$stack;
|
||||
$this->resetStack();
|
||||
$output = $this->renderRoot($elements);
|
||||
static::$stack = $current_stack;
|
||||
return $output;
|
||||
return $this->executeInRenderContext(new RenderContext(), function () use (&$elements) {
|
||||
return $this->render($elements, TRUE);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -151,16 +175,17 @@ class Renderer implements RendererInterface {
|
|||
// possible that any of them throw an exception that will cause a different
|
||||
// page to be rendered (e.g. throwing
|
||||
// \Symfony\Component\HttpKernel\Exception\NotFoundHttpException will cause
|
||||
// the 404 page to be rendered). That page might also use Renderer::render()
|
||||
// but if exceptions aren't caught here, the stack will be left in an
|
||||
// inconsistent state.
|
||||
// Hence, catch all exceptions and reset the stack and re-throw them.
|
||||
// the 404 page to be rendered). That page might also use
|
||||
// Renderer::renderRoot() but if exceptions aren't caught here, it will be
|
||||
// impossible to call Renderer::renderRoot() again.
|
||||
// Hence, catch all exceptions, reset the isRenderingRoot property and
|
||||
// re-throw exceptions.
|
||||
try {
|
||||
return $this->doRender($elements, $is_root_call);
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
// Reset stack and re-throw exception.
|
||||
$this->resetStack();
|
||||
// Mark the ::rootRender() call finished due to this exception & re-throw.
|
||||
$this->isRenderingRoot = FALSE;
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
@ -200,10 +225,10 @@ class Renderer implements RendererInterface {
|
|||
return '';
|
||||
}
|
||||
|
||||
if (!isset(static::$stack)) {
|
||||
static::$stack = new \SplStack();
|
||||
if (!isset(static::$context)) {
|
||||
throw new \LogicException("Render context is empty, because render() was called outside of a renderRoot() or renderPlain() call. Use renderPlain()/renderRoot() or #lazy_builder/#pre_render instead.");
|
||||
}
|
||||
static::$stack->push(new BubbleableMetadata());
|
||||
static::$context->push(new BubbleableMetadata());
|
||||
|
||||
// Set the bubbleable rendering metadata that has configurable defaults, if:
|
||||
// - this is the root call, to ensure that the final render array definitely
|
||||
|
@ -244,10 +269,10 @@ class Renderer implements RendererInterface {
|
|||
}
|
||||
// The render cache item contains all the bubbleable rendering metadata
|
||||
// for the subtree.
|
||||
$this->updateStack($elements);
|
||||
static::$context->update($elements);
|
||||
// Render cache hit, so rendering is finished, all necessary info
|
||||
// collected!
|
||||
$this->bubbleStack();
|
||||
static::$context->bubble();
|
||||
return $elements['#markup'];
|
||||
}
|
||||
}
|
||||
|
@ -345,9 +370,9 @@ class Renderer implements RendererInterface {
|
|||
if (!empty($elements['#printed'])) {
|
||||
// The #printed element contains all the bubbleable rendering metadata for
|
||||
// the subtree.
|
||||
$this->updateStack($elements);
|
||||
static::$context->update($elements);
|
||||
// #printed, so rendering is finished, all necessary info collected!
|
||||
$this->bubbleStack();
|
||||
static::$context->bubble();
|
||||
return '';
|
||||
}
|
||||
|
||||
|
@ -473,8 +498,8 @@ class Renderer implements RendererInterface {
|
|||
|
||||
$elements['#markup'] = $prefix . $elements['#children'] . $suffix;
|
||||
|
||||
// We've rendered this element (and its subtree!), now update the stack.
|
||||
$this->updateStack($elements);
|
||||
// We've rendered this element (and its subtree!), now update the context.
|
||||
static::$context->update($elements);
|
||||
|
||||
// Cache the processed element if both $pre_bubbling_elements and $elements
|
||||
// have the metadata necessary to generate a cache ID.
|
||||
|
@ -496,13 +521,14 @@ class Renderer implements RendererInterface {
|
|||
// that is handled earlier in Renderer::render().
|
||||
if ($is_root_call) {
|
||||
$this->replacePlaceholders($elements);
|
||||
if (static::$stack->count() !== 1) {
|
||||
// @todo remove as part of https://www.drupal.org/node/2511330.
|
||||
if (static::$context->count() !== 1) {
|
||||
throw new \LogicException('A stray drupal_render() invocation with $is_root_call = TRUE is causing bubbling of attached assets to break.');
|
||||
}
|
||||
}
|
||||
|
||||
// Rendering is finished, all necessary info collected!
|
||||
$this->bubbleStack();
|
||||
static::$context->bubble();
|
||||
|
||||
$elements['#printed'] = TRUE;
|
||||
$elements['#markup'] = SafeMarkup::set($elements['#markup']);
|
||||
|
@ -510,52 +536,24 @@ class Renderer implements RendererInterface {
|
|||
}
|
||||
|
||||
/**
|
||||
* Resets the renderer service's internal stack (used for bubbling metadata).
|
||||
*
|
||||
* Only necessary in very rare/advanced situations, such as when rendering an
|
||||
* error page if an exception occurred *during* rendering.
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function resetStack() {
|
||||
static::$stack = NULL;
|
||||
}
|
||||
public function executeInRenderContext(RenderContext $context, callable $callable) {
|
||||
// Store the current render context.
|
||||
$current_context = static::$context;
|
||||
|
||||
/**
|
||||
* Updates the stack.
|
||||
*
|
||||
* @param array &$element
|
||||
* The element of the render array that has just been rendered. The stack
|
||||
* frame for this element will be updated with the bubbleable rendering
|
||||
* metadata of this element.
|
||||
*/
|
||||
protected function updateStack(&$element) {
|
||||
// The latest frame represents the bubbleable metadata for the subtree.
|
||||
$frame = static::$stack->pop();
|
||||
// Update the frame, but also update the current element, to ensure it
|
||||
// contains up-to-date information in case it gets render cached.
|
||||
$updated_frame = BubbleableMetadata::createFromRenderArray($element)->merge($frame);
|
||||
$updated_frame->applyTo($element);
|
||||
static::$stack->push($updated_frame);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bubbles the stack.
|
||||
*
|
||||
* Whenever another level in the render array has been rendered, the stack
|
||||
* must be bubbled, to merge its rendering metadata with that of the parent
|
||||
* element.
|
||||
*/
|
||||
protected function bubbleStack() {
|
||||
// If there's only one frame on the stack, then this is the root call, and
|
||||
// we can't bubble up further. Reset the stack for the next root call.
|
||||
if (static::$stack->count() === 1) {
|
||||
$this->resetStack();
|
||||
return;
|
||||
// Set the provided context and call the callable, it will use that context.
|
||||
static::$context = $context;
|
||||
$result = $callable();
|
||||
// @todo Convert to an assertion in https://www.drupal.org/node/2408013
|
||||
if (static::$context->count() > 1) {
|
||||
throw new \LogicException('Bubbling failed.');
|
||||
}
|
||||
|
||||
// Merge the current and the parent stack frame.
|
||||
$current = static::$stack->pop();
|
||||
$parent = static::$stack->pop();
|
||||
static::$stack->push($current->merge($parent));
|
||||
// Restore the original render context.
|
||||
static::$context = $current_context;
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -22,6 +22,8 @@ interface RendererInterface {
|
|||
* - system internals that are responsible for rendering the final HTML
|
||||
* - render arrays for non-HTML responses, such as feeds
|
||||
*
|
||||
* (Cannot be executed within another render context.)
|
||||
*
|
||||
* @param array $elements
|
||||
* The structured array describing the data to be rendered.
|
||||
*
|
||||
|
@ -29,6 +31,9 @@ interface RendererInterface {
|
|||
* The rendered HTML.
|
||||
*
|
||||
* @see ::render()
|
||||
*
|
||||
* @throws \LogicException
|
||||
* When called from inside another renderRoot() call.
|
||||
*/
|
||||
public function renderRoot(&$elements);
|
||||
|
||||
|
@ -45,9 +50,11 @@ interface RendererInterface {
|
|||
* ::renderRoot() call, but that is generally highly problematic (and hence an
|
||||
* exception is thrown when a ::renderRoot() call happens within another
|
||||
* ::renderRoot() call). However, in this case, we only care about the output,
|
||||
* not about the bubbling. Hence this uses a separate render stack, to not
|
||||
* not about the bubbling. Hence this uses a separate render context, to not
|
||||
* affect the parent ::renderRoot() call.
|
||||
*
|
||||
* (Can be executed within another render context: it runs in isolation.)
|
||||
*
|
||||
* @param array $elements
|
||||
* The structured array describing the data to be rendered.
|
||||
*
|
||||
|
@ -88,8 +95,8 @@ interface RendererInterface {
|
|||
* or configuration that can affect that rendering changes.
|
||||
* - Placeholders, with associated self-contained placeholder render arrays,
|
||||
* for executing code to handle dynamic requirements that cannot be cached.
|
||||
* A stack of \Drupal\Core\Render\BubbleableMetadata objects can be used to
|
||||
* perform this bubbling.
|
||||
* A render context (\Drupal\Core\Render\RenderContext) can be used to perform
|
||||
* bubbling; it is a stack of \Drupal\Core\Render\BubbleableMetadata objects.
|
||||
*
|
||||
* Additionally, whether retrieving from cache or not, it is important to
|
||||
* know all of the assets (CSS and JavaScript) required by the rendered HTML,
|
||||
|
@ -103,9 +110,9 @@ interface RendererInterface {
|
|||
* - 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,
|
||||
* - If no render context is set yet, an exception is thrown. Otherwise,
|
||||
* an empty \Drupal\Core\Render\BubbleableMetadata is pushed onto the
|
||||
* stack.
|
||||
* render context.
|
||||
* - If this element has #cache defined then the cached markup for this
|
||||
* element will be returned if it exists in Renderer::render()'s cache. To
|
||||
* use Renderer::render() caching, set the element's #cache property to an
|
||||
|
@ -299,13 +306,12 @@ interface RendererInterface {
|
|||
* The rendered HTML.
|
||||
*
|
||||
* @throws \LogicException
|
||||
* If a root call to ::render() does not result in an empty stack, this
|
||||
* indicates an erroneous ::render() root call (a root call within a
|
||||
* root call, which makes no sense). Therefore, a logic exception is thrown.
|
||||
* When called outside of a render context. (i.e. outside of a renderRoot(),
|
||||
* renderPlain() or executeInRenderContext() call.)
|
||||
* @throws \Exception
|
||||
* If a #pre_render callback throws an exception, it is caught to reset the
|
||||
* stack used for bubbling rendering metadata, and then the exception is re-
|
||||
* thrown.
|
||||
* If a #pre_render callback throws an exception, it is caught to mark the
|
||||
* renderer as no longer being in a root render call, if any. Then the
|
||||
* exception is rethrown.
|
||||
*
|
||||
* @see \Drupal\Core\Render\ElementInfoManagerInterface::getInfo()
|
||||
* @see \Drupal\Core\Theme\ThemeManagerInterface::render()
|
||||
|
@ -315,6 +321,37 @@ interface RendererInterface {
|
|||
*/
|
||||
public function render(&$elements, $is_root_call = FALSE);
|
||||
|
||||
/**
|
||||
* Executes a callable within a render context.
|
||||
*
|
||||
* Only for very advanced use cases. Prefer using ::renderRoot() and
|
||||
* ::renderPlain() instead.
|
||||
*
|
||||
* All rendering must happen within a render context. Within a render context,
|
||||
* all bubbleable metadata is bubbled and hence tracked. Outside of a render
|
||||
* context, it would be lost. This could lead to missing assets, incorrect
|
||||
* cache variations (and thus security issues), insufficient cache
|
||||
* invalidations, and so on.
|
||||
*
|
||||
* Any and all rendering must therefore happen within a render context, and it
|
||||
* is this method that provides that.
|
||||
*
|
||||
* @see \Drupal\Core\Render\BubbleableMetadata
|
||||
*
|
||||
* @param \Drupal\Core\Render\RenderContext $context
|
||||
* The render context to execute the callable within.
|
||||
* @param callable $callable
|
||||
* The callable to execute.
|
||||
* @return mixed
|
||||
* The callable's return value.
|
||||
*
|
||||
* @see \Drupal\Core\Render\RenderContext
|
||||
*
|
||||
* @throws \LogicException
|
||||
* In case bubbling has failed, can only happen in case of broken code.
|
||||
*/
|
||||
public function executeInRenderContext(RenderContext $context, callable $callable);
|
||||
|
||||
/**
|
||||
* Merges the bubbleable rendering metadata o/t 2nd render array with the 1st.
|
||||
*
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
namespace Drupal\aggregator\Tests\Views;
|
||||
|
||||
use Drupal\Core\Render\RenderContext;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\views\Views;
|
||||
use Drupal\views\Tests\ViewTestData;
|
||||
|
@ -66,6 +67,9 @@ class IntegrationTest extends ViewUnitTestBase {
|
|||
* Tests basic aggregator_item view.
|
||||
*/
|
||||
public function testAggregatorItemView() {
|
||||
/** @var \Drupal\Core\Render\RendererInterface $renderer */
|
||||
$renderer = \Drupal::service('renderer');
|
||||
|
||||
$feed = $this->feedStorage->create(array(
|
||||
'title' => $this->randomMachineName(),
|
||||
'url' => 'https://www.drupal.org/',
|
||||
|
@ -112,13 +116,22 @@ class IntegrationTest extends ViewUnitTestBase {
|
|||
foreach ($view->result as $row) {
|
||||
$iid = $view->field['iid']->getValue($row);
|
||||
$expected_link = \Drupal::l($items[$iid]->getTitle(), Url::fromUri($items[$iid]->getLink(), ['absolute' => TRUE]));
|
||||
$this->assertEqual($view->field['title']->advancedRender($row), $expected_link, 'Ensure the right link is generated');
|
||||
$output = $renderer->executeInRenderContext(new RenderContext(), function () use ($view, $row) {
|
||||
return $view->field['title']->advancedRender($row);
|
||||
});
|
||||
$this->assertEqual($output, $expected_link, 'Ensure the right link is generated');
|
||||
|
||||
$expected_author = aggregator_filter_xss($items[$iid]->getAuthor());
|
||||
$this->assertEqual($view->field['author']->advancedRender($row), $expected_author, 'Ensure the author got filtered');
|
||||
$output = $renderer->executeInRenderContext(new RenderContext(), function () use ($view, $row) {
|
||||
return $view->field['author']->advancedRender($row);
|
||||
});
|
||||
$this->assertEqual($output, $expected_author, 'Ensure the author got filtered');
|
||||
|
||||
$expected_description = aggregator_filter_xss($items[$iid]->getDescription());
|
||||
$this->assertEqual($view->field['description']->advancedRender($row), $expected_description, 'Ensure the author got filtered');
|
||||
$output = $renderer->executeInRenderContext(new RenderContext(), function () use ($view, $row) {
|
||||
return $view->field['description']->advancedRender($row);
|
||||
});
|
||||
$this->assertEqual($output, $expected_description, 'Ensure the author got filtered');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
namespace Drupal\comment\Tests\Views;
|
||||
|
||||
use Drupal\comment\Entity\Comment;
|
||||
use Drupal\Core\Render\RenderContext;
|
||||
use Drupal\Core\Session\AnonymousUserSession;
|
||||
use Drupal\user\RoleInterface;
|
||||
use Drupal\views\Views;
|
||||
|
@ -58,6 +59,8 @@ class CommentFieldNameTest extends CommentTestBase {
|
|||
* Test comment field name.
|
||||
*/
|
||||
public function testCommentFieldName() {
|
||||
/** @var \Drupal\Core\Render\RendererInterface $renderer */
|
||||
$renderer = \Drupal::service('renderer');
|
||||
$view = Views::getView('test_comment_field_name');
|
||||
$this->executeView($view);
|
||||
|
||||
|
@ -85,8 +88,14 @@ class CommentFieldNameTest extends CommentTestBase {
|
|||
$view = Views::getView('test_comment_field_name');
|
||||
$this->executeView($view);
|
||||
// Test that data rendered.
|
||||
$this->assertIdentical($this->comment->getFieldName(), $view->field['field_name']->advancedRender($view->result[0]));
|
||||
$this->assertIdentical($this->customComment->getFieldName(), $view->field['field_name']->advancedRender($view->result[1]));
|
||||
$output = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['field_name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($this->comment->getFieldName(), $output);
|
||||
$output = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['field_name']->advancedRender($view->result[1]);
|
||||
});
|
||||
$this->assertIdentical($this->customComment->getFieldName(), $output);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
namespace Drupal\file\Tests\Views;
|
||||
|
||||
use Drupal\Core\Render\RenderContext;
|
||||
use Drupal\file\Entity\File;
|
||||
use Drupal\views\Views;
|
||||
use Drupal\views\Tests\ViewUnitTestBase;
|
||||
|
@ -69,17 +70,22 @@ class ExtensionViewsFieldTest extends ViewUnitTestBase {
|
|||
* Tests file extension views field handler extension_detect_tar option.
|
||||
*/
|
||||
public function testFileExtensionTarOption() {
|
||||
/** @var \Drupal\Core\Render\RendererInterface $renderer */
|
||||
$renderer = \Drupal::service('renderer');
|
||||
|
||||
$view = Views::getView('file_extension_view');
|
||||
$view->setDisplay();
|
||||
$this->executeView($view);
|
||||
|
||||
// Test without the tar option.
|
||||
$this->assertEqual($view->field['extension']->advancedRender($view->result[0]), 'png');
|
||||
$this->assertEqual($view->field['extension']->advancedRender($view->result[1]), 'tar');
|
||||
$this->assertEqual($view->field['extension']->advancedRender($view->result[2]), 'gz');
|
||||
$this->assertEqual($view->field['extension']->advancedRender($view->result[3]), '');
|
||||
// Test with the tar option.
|
||||
$renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
$this->assertEqual($view->field['extension']->advancedRender($view->result[0]), 'png');
|
||||
$this->assertEqual($view->field['extension']->advancedRender($view->result[1]), 'tar');
|
||||
$this->assertEqual($view->field['extension']->advancedRender($view->result[2]), 'gz');
|
||||
$this->assertEqual($view->field['extension']->advancedRender($view->result[3]), '');
|
||||
});
|
||||
|
||||
// Test with the tar option.
|
||||
$view = Views::getView('file_extension_view');
|
||||
$view->setDisplay();
|
||||
$view->initHandlers();
|
||||
|
@ -87,10 +93,12 @@ class ExtensionViewsFieldTest extends ViewUnitTestBase {
|
|||
$view->field['extension']->options['settings']['extension_detect_tar'] = TRUE;
|
||||
$this->executeView($view);
|
||||
|
||||
$this->assertEqual($view->field['extension']->advancedRender($view->result[0]), 'png');
|
||||
$this->assertEqual($view->field['extension']->advancedRender($view->result[1]), 'tar');
|
||||
$this->assertEqual($view->field['extension']->advancedRender($view->result[2]), 'tar.gz');
|
||||
$this->assertEqual($view->field['extension']->advancedRender($view->result[3]), '');
|
||||
$renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
$this->assertEqual($view->field['extension']->advancedRender($view->result[0]), 'png');
|
||||
$this->assertEqual($view->field['extension']->advancedRender($view->result[1]), 'tar');
|
||||
$this->assertEqual($view->field['extension']->advancedRender($view->result[2]), 'tar.gz');
|
||||
$this->assertEqual($view->field['extension']->advancedRender($view->result[3]), '');
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ namespace Drupal\filter\Tests;
|
|||
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Core\Render\RenderContext;
|
||||
use Drupal\editor\EditorXssFilter\Standard;
|
||||
use Drupal\filter\Entity\FilterFormat;
|
||||
use Drupal\filter\FilterPluginCollection;
|
||||
|
@ -101,10 +102,14 @@ class FilterUnitTest extends KernelTestBase {
|
|||
* Tests the caption filter.
|
||||
*/
|
||||
function testCaptionFilter() {
|
||||
/** @var \Drupal\Core\Render\RendererInterface $renderer */
|
||||
$renderer = \Drupal::service('renderer');
|
||||
$filter = $this->filters['filter_caption'];
|
||||
|
||||
$test = function($input) use ($filter) {
|
||||
return $filter->process($input, 'und');
|
||||
$test = function($input) use ($filter, $renderer) {
|
||||
return $renderer->executeInRenderContext(new RenderContext(), function () use ($input, $filter) {
|
||||
return $filter->process($input, 'und');
|
||||
});
|
||||
};
|
||||
|
||||
$attached_library = array(
|
||||
|
@ -190,12 +195,14 @@ class FilterUnitTest extends KernelTestBase {
|
|||
'filter_html_nofollow' => 0,
|
||||
)
|
||||
));
|
||||
$test_with_html_filter = function ($input) use ($filter, $html_filter) {
|
||||
// 1. Apply HTML filter's processing step.
|
||||
$output = $html_filter->process($input, 'und');
|
||||
// 2. Apply caption filter's processing step.
|
||||
$output = $filter->process($output, 'und');
|
||||
return $output->getProcessedText();
|
||||
$test_with_html_filter = function ($input) use ($filter, $html_filter, $renderer) {
|
||||
return $renderer->executeInRenderContext(new RenderContext(), function () use ($input, $filter, $html_filter) {
|
||||
// 1. Apply HTML filter's processing step.
|
||||
$output = $html_filter->process($input, 'und');
|
||||
// 2. Apply caption filter's processing step.
|
||||
$output = $filter->process($output, 'und');
|
||||
return $output->getProcessedText();
|
||||
});
|
||||
};
|
||||
// Editor XSS filter.
|
||||
$test_editor_xss_filter = function ($input) {
|
||||
|
@ -252,11 +259,15 @@ class FilterUnitTest extends KernelTestBase {
|
|||
* Tests the combination of the align and caption filters.
|
||||
*/
|
||||
function testAlignAndCaptionFilters() {
|
||||
/** @var \Drupal\Core\Render\RendererInterface $renderer */
|
||||
$renderer = \Drupal::service('renderer');
|
||||
$align_filter = $this->filters['filter_align'];
|
||||
$caption_filter = $this->filters['filter_caption'];
|
||||
|
||||
$test = function($input) use ($align_filter, $caption_filter) {
|
||||
return $caption_filter->process($align_filter->process($input, 'und'), 'und');
|
||||
$test = function($input) use ($align_filter, $caption_filter, $renderer) {
|
||||
return $renderer->executeInRenderContext(new RenderContext(), function () use ($input, $align_filter, $caption_filter) {
|
||||
return $caption_filter->process($align_filter->process($input, 'und'), 'und');
|
||||
});
|
||||
};
|
||||
|
||||
$attached_library = array(
|
||||
|
|
|
@ -188,17 +188,20 @@ class NodeController extends ControllerBase implements ContainerInjectionInterfa
|
|||
}
|
||||
|
||||
$row = [];
|
||||
$row[] = [
|
||||
$column = [
|
||||
'data' => [
|
||||
'#type' => 'inline_template',
|
||||
'#template' => '{% trans %}{{ date }} by {{ username }}{% endtrans %}{% if message %}<p class="revision-log">{{ message }}</p>{% endif %}',
|
||||
'#context' => [
|
||||
'date' => $link,
|
||||
'username' => $this->renderer->render($username),
|
||||
'username' => $this->renderer->renderPlain($username),
|
||||
'message' => Xss::filter($revision->revision_log->value),
|
||||
],
|
||||
],
|
||||
];
|
||||
// @todo Simplify once https://www.drupal.org/node/2334319 lands.
|
||||
$this->renderer->addCacheableDependency($column['data'], $username);
|
||||
$row[] = $column;
|
||||
|
||||
if ($vid == $node->getRevisionId()) {
|
||||
$row[0]['class'] = ['revision-current'];
|
||||
|
|
|
@ -95,7 +95,7 @@ class NodeRevisionsUiTest extends NodeTestBase {
|
|||
'#theme' => 'username',
|
||||
'#account' => $this->editor,
|
||||
];
|
||||
$editor = \Drupal::service('renderer')->render($username);
|
||||
$editor = \Drupal::service('renderer')->renderPlain($username);
|
||||
|
||||
// Get original node.
|
||||
$nodes[] = clone $node;
|
||||
|
|
|
@ -10,9 +10,14 @@ namespace Drupal\rest\Plugin\views\display;
|
|||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Core\Cache\CacheableMetadata;
|
||||
use Drupal\Core\Cache\CacheableResponse;
|
||||
use Drupal\Core\Render\RenderContext;
|
||||
use Drupal\Core\Render\RendererInterface;
|
||||
use Drupal\Core\Routing\RouteProviderInterface;
|
||||
use Drupal\Core\State\StateInterface;
|
||||
use Drupal\views\Plugin\views\display\ResponseDisplayPluginInterface;
|
||||
use Drupal\views\ViewExecutable;
|
||||
use Drupal\views\Plugin\views\display\PathPluginBase;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
|
||||
/**
|
||||
|
@ -70,6 +75,48 @@ class RestExport extends PathPluginBase implements ResponseDisplayPluginInterfac
|
|||
*/
|
||||
protected $mimeType;
|
||||
|
||||
/**
|
||||
* The renderer.
|
||||
*
|
||||
* @var \Drupal\Core\Render\RendererInterface
|
||||
*/
|
||||
protected $renderer;
|
||||
|
||||
/**
|
||||
* Constructs a RestExport object.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin_id for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\Core\Routing\RouteProviderInterface $route_provider
|
||||
* The route provider.
|
||||
* @param \Drupal\Core\State\StateInterface $state
|
||||
* The state key value store.
|
||||
* @param \Drupal\Core\Render\RendererInterface $renderer
|
||||
* The renderer.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, RouteProviderInterface $route_provider, StateInterface $state, RendererInterface $renderer) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition, $route_provider, $state);
|
||||
|
||||
$this->renderer = $renderer;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('router.route_provider'),
|
||||
$container->get('state'),
|
||||
$container->get('renderer')
|
||||
);
|
||||
}
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
@ -263,7 +310,9 @@ class RestExport extends PathPluginBase implements ResponseDisplayPluginInterfac
|
|||
*/
|
||||
public function render() {
|
||||
$build = array();
|
||||
$build['#markup'] = $this->view->style_plugin->render();
|
||||
$build['#markup'] = $this->renderer->executeInRenderContext(new RenderContext(), function() {
|
||||
return $this->view->style_plugin->render();
|
||||
});
|
||||
|
||||
$this->view->element['#content_type'] = $this->getMimeType();
|
||||
$this->view->element['#cache_properties'][] = '#content_type';
|
||||
|
|
|
@ -10,6 +10,7 @@ namespace Drupal\simpletest;
|
|||
use Drupal\Component\Serialization\Json;
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Component\Utility\Xss;
|
||||
use Drupal\Core\Render\RenderContext;
|
||||
use Symfony\Component\CssSelector\CssSelector;
|
||||
|
||||
/**
|
||||
|
@ -808,7 +809,12 @@ trait AssertContentTrait {
|
|||
* TRUE on pass, FALSE on fail.
|
||||
*/
|
||||
protected function assertThemeOutput($callback, array $variables = array(), $expected = '', $message = '', $group = 'Other') {
|
||||
$output = \Drupal::theme()->render($callback, $variables);
|
||||
/** @var \Drupal\Core\Render\RendererInterface $renderer */
|
||||
$renderer = \Drupal::service('renderer');
|
||||
|
||||
$output = $renderer->executeInRenderContext(new RenderContext(), function() use ($callback, $variables) {
|
||||
return \Drupal::theme()->render($callback, $variables);
|
||||
});
|
||||
$this->verbose(
|
||||
'<hr />' . 'Result:' . '<pre>' . SafeMarkup::checkPlain(var_export($output, TRUE)) . '</pre>'
|
||||
. '<hr />' . 'Expected:' . '<pre>' . SafeMarkup::checkPlain(var_export($expected, TRUE)) . '</pre>'
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Common\EarlyRenderingControllerTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Common;
|
||||
|
||||
use Drupal\Core\Render\Element;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
/**
|
||||
* Verifies that bubbleable metadata of early rendering is not lost.
|
||||
*
|
||||
* @group Common
|
||||
*/
|
||||
class EarlyRenderingControllerTest extends WebTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $dumpHeaders = TRUE;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['system', 'early_rendering_controller_test'];
|
||||
|
||||
/**
|
||||
* Tests theme preprocess functions being able to attach assets.
|
||||
*/
|
||||
function testEarlyRendering() {
|
||||
// Render array: non-early & early.
|
||||
$this->drupalGet(Url::fromRoute('early_rendering_controller_test.render_array'));
|
||||
$this->assertResponse(200);
|
||||
$this->assertRaw('Hello world!');
|
||||
$this->assertCacheTag('foo');
|
||||
$this->drupalGet(Url::fromRoute('early_rendering_controller_test.render_array.early'));
|
||||
$this->assertResponse(200);
|
||||
$this->assertRaw('Hello world!');
|
||||
$this->assertCacheTag('foo');
|
||||
|
||||
// Basic Response object: non-early & early.
|
||||
$this->drupalGet(Url::fromRoute('early_rendering_controller_test.response'));
|
||||
$this->assertResponse(200);
|
||||
$this->assertRaw('Hello world!');
|
||||
$this->assertNoCacheTag('foo');
|
||||
$this->drupalGet(Url::fromRoute('early_rendering_controller_test.response.early'));
|
||||
$this->assertResponse(200);
|
||||
$this->assertRaw('Hello world!');
|
||||
$this->assertNoCacheTag('foo');
|
||||
|
||||
// Response object with attachments: non-early & early.
|
||||
$this->drupalGet(Url::fromRoute('early_rendering_controller_test.response-with-attachments'));
|
||||
$this->assertResponse(200);
|
||||
$this->assertRaw('Hello world!');
|
||||
$this->assertNoCacheTag('foo');
|
||||
$this->drupalGet(Url::fromRoute('early_rendering_controller_test.response-with-attachments.early'));
|
||||
$this->assertResponse(500);
|
||||
$this->assertRaw('The controller result claims to be providing relevant cache metadata, but leaked metadata was detected. Please ensure you are not rendering content too early. Returned object class: Drupal\early_rendering_controller_test\AttachmentsTestResponse.');
|
||||
|
||||
// Cacheable Response object: non-early & early.
|
||||
$this->drupalGet(Url::fromRoute('early_rendering_controller_test.cacheable-response'));
|
||||
$this->assertResponse(200);
|
||||
$this->assertRaw('Hello world!');
|
||||
$this->assertNoCacheTag('foo');
|
||||
$this->drupalGet(Url::fromRoute('early_rendering_controller_test.cacheable-response.early'));
|
||||
$this->assertResponse(500);
|
||||
$this->assertRaw('The controller result claims to be providing relevant cache metadata, but leaked metadata was detected. Please ensure you are not rendering content too early. Returned object class: Drupal\early_rendering_controller_test\CacheableTestResponse.');
|
||||
|
||||
// Basic domain object: non-early & early.
|
||||
$this->drupalGet(Url::fromRoute('early_rendering_controller_test.domain-object'));
|
||||
$this->assertResponse(200);
|
||||
$this->assertRaw('TestDomainObject');
|
||||
$this->assertNoCacheTag('foo');
|
||||
$this->drupalGet(Url::fromRoute('early_rendering_controller_test.domain-object.early'));
|
||||
$this->assertResponse(200);
|
||||
$this->assertRaw('TestDomainObject');
|
||||
$this->assertNoCacheTag('foo');
|
||||
|
||||
// Basic domain object with attachments: non-early & early.
|
||||
$this->drupalGet(Url::fromRoute('early_rendering_controller_test.domain-object-with-attachments'));
|
||||
$this->assertResponse(200);
|
||||
$this->assertRaw('AttachmentsTestDomainObject');
|
||||
$this->assertNoCacheTag('foo');
|
||||
$this->drupalGet(Url::fromRoute('early_rendering_controller_test.domain-object-with-attachments.early'));
|
||||
$this->assertResponse(500);
|
||||
$this->assertRaw('The controller result claims to be providing relevant cache metadata, but leaked metadata was detected. Please ensure you are not rendering content too early. Returned object class: Drupal\early_rendering_controller_test\AttachmentsTestDomainObject.');
|
||||
|
||||
// Cacheable Response object: non-early & early.
|
||||
$this->drupalGet(Url::fromRoute('early_rendering_controller_test.cacheable-domain-object'));
|
||||
$this->assertResponse(200);
|
||||
$this->assertRaw('CacheableTestDomainObject');
|
||||
$this->assertNoCacheTag('foo');
|
||||
$this->drupalGet(Url::fromRoute('early_rendering_controller_test.cacheable-domain-object.early'));
|
||||
$this->assertResponse(500);
|
||||
$this->assertRaw('The controller result claims to be providing relevant cache metadata, but leaked metadata was detected. Please ensure you are not rendering content too early. Returned object class: Drupal\early_rendering_controller_test\CacheableTestDomainObject.');
|
||||
|
||||
// The exceptions are expected. Do not interpret them as a test failure.
|
||||
// Not using File API; a potential error must trigger a PHP warning.
|
||||
unlink(\Drupal::root() . '/' . $this->siteDirectory . '/error.log');
|
||||
}
|
||||
|
||||
}
|
|
@ -10,6 +10,7 @@ namespace Drupal\system\Tests\Common;
|
|||
use Drupal\Component\Utility\UrlHelper;
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Language\Language;
|
||||
use Drupal\Core\Render\RenderContext;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
|
@ -168,9 +169,11 @@ class UrlTest extends WebTestBase {
|
|||
$l = \Drupal::l('foo', Url::fromUri('https://www.drupal.org'));
|
||||
|
||||
// Test a renderable array passed to _l().
|
||||
$renderable_text = array('#markup' => 'foo');
|
||||
$l_renderable_text = \Drupal::l($renderable_text, Url::fromUri('https://www.drupal.org'));
|
||||
$this->assertEqual($l_renderable_text, $l);
|
||||
$renderer->executeInRenderContext(new RenderContext(), function() use ($renderer, $l) {
|
||||
$renderable_text = array('#markup' => 'foo');
|
||||
$l_renderable_text = \Drupal::l($renderable_text, Url::fromUri('https://www.drupal.org'));
|
||||
$this->assertEqual($l_renderable_text, $l);
|
||||
});
|
||||
|
||||
// Test a themed link with plain text 'text'.
|
||||
$type_link_plain_array = array(
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
name: 'Early rendering controller test'
|
||||
type: module
|
||||
description: 'Support module for EarlyRenderingControllerTest.'
|
||||
package: Testing
|
||||
version: VERSION
|
||||
core: 8.x
|
|
@ -0,0 +1,97 @@
|
|||
# Controller returning a render array.
|
||||
early_rendering_controller_test.render_array:
|
||||
path: '/early-rendering-controller-test/render-array'
|
||||
defaults:
|
||||
_controller: '\Drupal\early_rendering_controller_test\EarlyRenderingTestController::renderArray'
|
||||
requirements:
|
||||
_access: 'TRUE'
|
||||
early_rendering_controller_test.render_array.early:
|
||||
path: '/early-rendering-controller-test/render-array/early'
|
||||
defaults:
|
||||
_controller: '\Drupal\early_rendering_controller_test\EarlyRenderingTestController::renderArrayEarly'
|
||||
requirements:
|
||||
_access: 'TRUE'
|
||||
|
||||
# Controller returning a basic Response object.
|
||||
early_rendering_controller_test.response:
|
||||
path: '/early-rendering-controller-test/response'
|
||||
defaults:
|
||||
_controller: '\Drupal\early_rendering_controller_test\EarlyRenderingTestController::response'
|
||||
requirements:
|
||||
_access: 'TRUE'
|
||||
early_rendering_controller_test.response.early:
|
||||
path: '/early-rendering-controller-test/response/early'
|
||||
defaults:
|
||||
_controller: '\Drupal\early_rendering_controller_test\EarlyRenderingTestController::responseEarly'
|
||||
requirements:
|
||||
_access: 'TRUE'
|
||||
|
||||
# Controller returning a Response object with attachments.
|
||||
early_rendering_controller_test.response-with-attachments:
|
||||
path: '/early-rendering-controller-test/response-with-attachments'
|
||||
defaults:
|
||||
_controller: '\Drupal\early_rendering_controller_test\EarlyRenderingTestController::responseWithAttachments'
|
||||
requirements:
|
||||
_access: 'TRUE'
|
||||
early_rendering_controller_test.response-with-attachments.early:
|
||||
path: '/early-rendering-controller-test/response-with-attachments/early'
|
||||
defaults:
|
||||
_controller: '\Drupal\early_rendering_controller_test\EarlyRenderingTestController::responseWithAttachmentsEarly'
|
||||
requirements:
|
||||
_access: 'TRUE'
|
||||
|
||||
# Controller returning a cacheable Response object.
|
||||
early_rendering_controller_test.cacheable-response:
|
||||
path: '/early-rendering-controller-test/cacheable-response'
|
||||
defaults:
|
||||
_controller: '\Drupal\early_rendering_controller_test\EarlyRenderingTestController::cacheableResponse'
|
||||
requirements:
|
||||
_access: 'TRUE'
|
||||
early_rendering_controller_test.cacheable-response.early:
|
||||
path: '/early-rendering-controller-test/cacheable-response/early'
|
||||
defaults:
|
||||
_controller: '\Drupal\early_rendering_controller_test\EarlyRenderingTestController::cacheableResponseEarly'
|
||||
requirements:
|
||||
_access: 'TRUE'
|
||||
|
||||
# Controller returning a basic domain object.
|
||||
early_rendering_controller_test.domain-object:
|
||||
path: '/early-rendering-controller-test/domain-object'
|
||||
defaults:
|
||||
_controller: '\Drupal\early_rendering_controller_test\EarlyRenderingTestController::domainObject'
|
||||
requirements:
|
||||
_access: 'TRUE'
|
||||
early_rendering_controller_test.domain-object.early:
|
||||
path: '/early-rendering-controller-test/domain-object/early'
|
||||
defaults:
|
||||
_controller: '\Drupal\early_rendering_controller_test\EarlyRenderingTestController::domainObjectEarly'
|
||||
requirements:
|
||||
_access: 'TRUE'
|
||||
|
||||
# Controller returning a domain object with attachments.
|
||||
early_rendering_controller_test.domain-object-with-attachments:
|
||||
path: '/early-rendering-controller-test/domain-object-with-attachments'
|
||||
defaults:
|
||||
_controller: '\Drupal\early_rendering_controller_test\EarlyRenderingTestController::domainObjectWithAttachments'
|
||||
requirements:
|
||||
_access: 'TRUE'
|
||||
early_rendering_controller_test.domain-object-with-attachments.early:
|
||||
path: '/early-rendering-controller-test/domain-object-with-attachments/early'
|
||||
defaults:
|
||||
_controller: '\Drupal\early_rendering_controller_test\EarlyRenderingTestController::domainObjectWithAttachmentsEarly'
|
||||
requirements:
|
||||
_access: 'TRUE'
|
||||
|
||||
# Controller returning a cacheable domain object.
|
||||
early_rendering_controller_test.cacheable-domain-object:
|
||||
path: '/early-rendering-controller-test/cacheable-domain-object'
|
||||
defaults:
|
||||
_controller: '\Drupal\early_rendering_controller_test\EarlyRenderingTestController::cacheableDomainObject'
|
||||
requirements:
|
||||
_access: 'TRUE'
|
||||
early_rendering_controller_test.cacheable-domain-object.early:
|
||||
path: '/early-rendering-controller-test/cacheable-domain-object/early'
|
||||
defaults:
|
||||
_controller: '\Drupal\early_rendering_controller_test\EarlyRenderingTestController::cacheableDomainObjectEarly'
|
||||
requirements:
|
||||
_access: 'TRUE'
|
|
@ -0,0 +1,5 @@
|
|||
services:
|
||||
test_domain_object.view_subscriber:
|
||||
class: Drupal\early_rendering_controller_test\TestDomainObjectViewSubscriber
|
||||
tags:
|
||||
- { name: event_subscriber }
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\early_rendering_controller_test\AttachmentsTestDomainObject.
|
||||
*/
|
||||
|
||||
namespace Drupal\early_rendering_controller_test;
|
||||
|
||||
use Drupal\Core\Render\AttachmentsInterface;
|
||||
use Drupal\Core\Render\AttachmentsTrait;
|
||||
|
||||
class AttachmentsTestDomainObject extends TestDomainObject implements AttachmentsInterface {
|
||||
|
||||
use AttachmentsTrait;
|
||||
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\early_rendering_controller_test\AttachmentsTestResponse.
|
||||
*/
|
||||
|
||||
namespace Drupal\early_rendering_controller_test;
|
||||
|
||||
use Drupal\Core\Render\AttachmentsInterface;
|
||||
use Drupal\Core\Render\AttachmentsTrait;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class AttachmentsTestResponse extends Response implements AttachmentsInterface {
|
||||
|
||||
use AttachmentsTrait;
|
||||
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\early_rendering_controller_test\AttachmentsTestDomainObject.
|
||||
*/
|
||||
|
||||
namespace Drupal\early_rendering_controller_test;
|
||||
|
||||
use Drupal\Core\Cache\CacheableDependencyInterface;
|
||||
|
||||
class CacheableTestDomainObject extends TestDomainObject implements CacheableDependencyInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheContexts() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheTags() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheMaxAge() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\early_rendering_controller_test\CacheableTestResponse.
|
||||
*/
|
||||
|
||||
namespace Drupal\early_rendering_controller_test;
|
||||
|
||||
use Drupal\Core\Cache\CacheableResponseInterface;
|
||||
use Drupal\Core\Cache\CacheableResponseTrait;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class CacheableTestResponse extends Response implements CacheableResponseInterface {
|
||||
|
||||
use CacheableResponseTrait;
|
||||
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\early_rendering_controller_test\EarlyRenderingTestController.
|
||||
*/
|
||||
|
||||
namespace Drupal\early_rendering_controller_test;
|
||||
|
||||
use Drupal\Core\Controller\ControllerBase;
|
||||
use Drupal\Core\Render\RendererInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* Controller routines for early_rendering_test routes.
|
||||
*
|
||||
* The methods on this controller each correspond to a route for this module,
|
||||
* each of which exist solely for test cases in EarlyRenderingControllerTest;
|
||||
* see that test for documentation.
|
||||
*
|
||||
* @see core/modules/early_rendering_controller_test/early_rendering_controller_test.routing.yml
|
||||
* @see \Drupal\system\Tests\Common\EarlyRenderingControllerTest::testEarlyRendering()
|
||||
*/
|
||||
class EarlyRenderingTestController extends ControllerBase {
|
||||
|
||||
/**
|
||||
* The renderer.
|
||||
*
|
||||
* @var \Drupal\Core\Render\RendererInterface
|
||||
*/
|
||||
protected $renderer;
|
||||
|
||||
/**
|
||||
* Constructs a EarlyRenderingTestController.
|
||||
*
|
||||
* @param \Drupal\Core\Render\RendererInterface $renderer
|
||||
*/
|
||||
public function __construct(RendererInterface $renderer) {
|
||||
$this->renderer = $renderer;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('renderer')
|
||||
);
|
||||
}
|
||||
|
||||
protected function earlyRenderContent() {
|
||||
return [
|
||||
'#markup' => 'Hello world!',
|
||||
'#cache' => [
|
||||
'tags' => [
|
||||
'foo',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function renderArray() {
|
||||
return [
|
||||
'#pre_render' => [function () {
|
||||
$elements = $this->earlyRenderContent();
|
||||
return $elements;
|
||||
}],
|
||||
];
|
||||
}
|
||||
|
||||
public function renderArrayEarly() {
|
||||
$render_array = $this->earlyRenderContent();
|
||||
return [
|
||||
'#markup' => $this->renderer->render($render_array),
|
||||
];
|
||||
}
|
||||
|
||||
public function response() {
|
||||
return new Response('Hello world!');
|
||||
}
|
||||
|
||||
public function responseEarly() {
|
||||
$render_array = $this->earlyRenderContent();
|
||||
return new Response($this->renderer->render($render_array));
|
||||
}
|
||||
|
||||
public function responseWithAttachments() {
|
||||
return new AttachmentsTestResponse('Hello world!');
|
||||
}
|
||||
|
||||
public function responseWithAttachmentsEarly() {
|
||||
$render_array = $this->earlyRenderContent();
|
||||
return new AttachmentsTestResponse($this->renderer->render($render_array));
|
||||
}
|
||||
|
||||
public function cacheableResponse() {
|
||||
return new CacheableTestResponse('Hello world!');
|
||||
}
|
||||
|
||||
public function cacheableResponseEarly() {
|
||||
$render_array = $this->earlyRenderContent();
|
||||
return new CacheableTestResponse($this->renderer->render($render_array));
|
||||
}
|
||||
|
||||
public function domainObject() {
|
||||
return new TestDomainObject();
|
||||
}
|
||||
|
||||
public function domainObjectEarly() {
|
||||
$render_array = $this->earlyRenderContent();
|
||||
$this->renderer->render($render_array);
|
||||
return new TestDomainObject();
|
||||
}
|
||||
|
||||
public function domainObjectWithAttachments() {
|
||||
return new AttachmentsTestDomainObject();
|
||||
}
|
||||
|
||||
public function domainObjectWithAttachmentsEarly() {
|
||||
$render_array = $this->earlyRenderContent();
|
||||
$this->renderer->render($render_array);
|
||||
return new AttachmentsTestDomainObject();
|
||||
}
|
||||
|
||||
public function cacheableDomainObject() {
|
||||
return new CacheableTestDomainObject();
|
||||
}
|
||||
|
||||
public function cacheableDomainObjectEarly() {
|
||||
$render_array = $this->earlyRenderContent();
|
||||
$this->renderer->render($render_array);
|
||||
return new CacheableTestDomainObject();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\early_rendering_controller_test\TestDomainObject.
|
||||
*/
|
||||
|
||||
namespace Drupal\early_rendering_controller_test;
|
||||
|
||||
class TestDomainObject { }
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\early_rendering_controller_test\TestDomainObjectViewSubscriber.
|
||||
*/
|
||||
|
||||
namespace Drupal\early_rendering_controller_test;
|
||||
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
|
||||
use Symfony\Component\HttpKernel\KernelEvents;
|
||||
|
||||
/**
|
||||
* View subscriber for turning TestDomainObject objects into Response objects.
|
||||
*/
|
||||
class TestDomainObjectViewSubscriber implements EventSubscriberInterface {
|
||||
|
||||
/**
|
||||
* Sets a response given a TestDomainObject instance.
|
||||
*
|
||||
* @param \Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent $event
|
||||
* The event to process.
|
||||
*/
|
||||
public function onViewTestDomainObject(GetResponseForControllerResultEvent $event) {
|
||||
$result = $event->getControllerResult();
|
||||
|
||||
if ($result instanceof TestDomainObject) {
|
||||
if ($result instanceof AttachmentsTestDomainObject) {
|
||||
$event->setResponse(new AttachmentsTestResponse('AttachmentsTestDomainObject'));
|
||||
}
|
||||
elseif ($result instanceof CacheableTestDomainObject) {
|
||||
$event->setResponse(new CacheableTestResponse('CacheableTestDomainObject'));
|
||||
}
|
||||
else {
|
||||
$event->setResponse(new Response('TestDomainObject'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
static function getSubscribedEvents() {
|
||||
$events[KernelEvents::VIEW][] = ['onViewTestDomainObject'];
|
||||
|
||||
return $events;
|
||||
}
|
||||
|
||||
}
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
namespace Drupal\taxonomy\Tests\Views;
|
||||
|
||||
use Drupal\Core\Render\RenderContext;
|
||||
use Drupal\views\Views;
|
||||
|
||||
/**
|
||||
|
@ -24,10 +25,15 @@ class TaxonomyFieldTidTest extends TaxonomyTestBase {
|
|||
public static $testViews = array('test_taxonomy_tid_field');
|
||||
|
||||
function testViewsHandlerTidField() {
|
||||
/** @var \Drupal\Core\Render\RendererInterface $renderer */
|
||||
$renderer = \Drupal::service('renderer');
|
||||
|
||||
$view = Views::getView('test_taxonomy_tid_field');
|
||||
$this->executeView($view);
|
||||
|
||||
$actual = $view->field['name']->advancedRender($view->result[0]);
|
||||
$actual = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$expected = \Drupal::l($this->term1->label(), $this->term1->urlInfo());
|
||||
|
||||
$this->assertEqual($expected, $actual);
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
namespace Drupal\user\Tests\Views;
|
||||
|
||||
use Drupal\Core\Render\RenderContext;
|
||||
use Drupal\views\Views;
|
||||
|
||||
/**
|
||||
|
@ -25,6 +26,9 @@ class HandlerFieldUserNameTest extends UserTestBase {
|
|||
public static $testViews = array('test_views_handler_field_user_name');
|
||||
|
||||
public function testUserName() {
|
||||
/** @var \Drupal\Core\Render\RendererInterface $renderer */
|
||||
$renderer = \Drupal::service('renderer');
|
||||
|
||||
$this->drupalLogin($this->drupalCreateUser(array('access user profiles')));
|
||||
|
||||
// Set defaults.
|
||||
|
@ -37,13 +41,17 @@ class HandlerFieldUserNameTest extends UserTestBase {
|
|||
$this->executeView($view);
|
||||
|
||||
$anon_name = $this->config('user.settings')->get('anonymous');
|
||||
$render = $view->field['name']->advancedRender($view->result[0]);
|
||||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertTrue(strpos($render, $anon_name) !== FALSE, 'For user 0 it should use the default anonymous name by default.');
|
||||
|
||||
$username = $this->randomMachineName();
|
||||
$view->result[0]->_entity->setUsername($username);
|
||||
$view->result[0]->_entity->uid->value = 1;
|
||||
$render = $view->field['name']->advancedRender($view->result[0]);
|
||||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertTrue(strpos($render, $username) !== FALSE, 'If link to user is checked the username should be part of the output.');
|
||||
$this->assertTrue(strpos($render, 'user/1') !== FALSE, 'If link to user is checked the link to the user should appear as well.');
|
||||
|
||||
|
@ -52,7 +60,9 @@ class HandlerFieldUserNameTest extends UserTestBase {
|
|||
$username = $this->randomMachineName();
|
||||
$view->result[0]->_entity->setUsername($username);
|
||||
$view->result[0]->_entity->uid->value = 1;
|
||||
$render = $view->field['name']->advancedRender($view->result[0]);
|
||||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($render, $username, 'If the user is not linked the username should be printed out for a normal user.');
|
||||
|
||||
}
|
||||
|
@ -61,13 +71,18 @@ class HandlerFieldUserNameTest extends UserTestBase {
|
|||
* Tests that the field handler works when no additional fields are added.
|
||||
*/
|
||||
public function testNoAdditionalFields() {
|
||||
/** @var \Drupal\Core\Render\RendererInterface $renderer */
|
||||
$renderer = \Drupal::service('renderer');
|
||||
|
||||
$view = Views::getView('test_views_handler_field_user_name');
|
||||
$this->executeView($view);
|
||||
|
||||
$username = $this->randomMachineName();
|
||||
$view->result[0]->_entity->setUsername($username);
|
||||
$view->result[0]->_entity->uid->value = 1;
|
||||
$render = $view->field['name']->advancedRender($view->result[0]);
|
||||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertTrue(strpos($render, $username) !== FALSE, 'If link to user is checked the username should be part of the output.');
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
namespace Drupal\views\Tests\Handler;
|
||||
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
use Drupal\Core\Render\RenderContext;
|
||||
use Drupal\views\Views;
|
||||
|
||||
/**
|
||||
|
@ -67,6 +68,9 @@ class FieldGroupRowsTest extends HandlerTestBase {
|
|||
* Testing the "Grouped rows" functionality.
|
||||
*/
|
||||
public function testGroupRows() {
|
||||
/** @var \Drupal\Core\Render\RendererInterface $renderer */
|
||||
$renderer = \Drupal::service('renderer');
|
||||
|
||||
$edit = array(
|
||||
'title' => $this->randomMachineName(),
|
||||
$this->fieldName => array('a', 'b', 'c'),
|
||||
|
@ -77,7 +81,10 @@ class FieldGroupRowsTest extends HandlerTestBase {
|
|||
|
||||
// Test grouped rows.
|
||||
$this->executeView($view);
|
||||
$this->assertEqual($view->field[$this->fieldName]->advancedRender($view->result[0]), 'a, b, c');
|
||||
$output = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field[$this->fieldName]->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertEqual($output, 'a, b, c');
|
||||
|
||||
// Change the group_rows checkbox to false.
|
||||
$view = Views::getView('test_group_rows');
|
||||
|
@ -88,11 +95,20 @@ class FieldGroupRowsTest extends HandlerTestBase {
|
|||
$view->render();
|
||||
|
||||
$view->row_index = 0;
|
||||
$this->assertEqual($view->field[$this->fieldName]->advancedRender($view->result[0]), 'a');
|
||||
$output = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field[$this->fieldName]->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertEqual($output, 'a');
|
||||
$view->row_index = 1;
|
||||
$this->assertEqual($view->field[$this->fieldName]->advancedRender($view->result[1]), 'b');
|
||||
$output = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field[$this->fieldName]->advancedRender($view->result[1]);
|
||||
});
|
||||
$this->assertEqual($output, 'b');
|
||||
$view->row_index = 2;
|
||||
$this->assertEqual($view->field[$this->fieldName]->advancedRender($view->result[2]), 'c');
|
||||
$output = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field[$this->fieldName]->advancedRender($view->result[2]);
|
||||
});
|
||||
$this->assertEqual($output, 'c');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
namespace Drupal\views\Tests\Handler;
|
||||
|
||||
use Drupal\Core\Render\RenderContext;
|
||||
use Drupal\views\Tests\ViewUnitTestBase;
|
||||
use Drupal\views\Plugin\views\field\FieldPluginBase;
|
||||
use Drupal\views\Views;
|
||||
|
@ -52,12 +53,18 @@ class FieldUnitTest extends ViewUnitTestBase {
|
|||
* Tests that the render function is called.
|
||||
*/
|
||||
public function testRender() {
|
||||
/** @var \Drupal\Core\Render\RendererInterface $renderer */
|
||||
$renderer = \Drupal::service('renderer');
|
||||
|
||||
$view = Views::getView('test_field_tokens');
|
||||
$this->executeView($view);
|
||||
|
||||
$random_text = $this->randomMachineName();
|
||||
$view->field['job']->setTestValue($random_text);
|
||||
$this->assertEqual($view->field['job']->theme($view->result[0]), $random_text, 'Make sure the render method rendered the manual set value.');
|
||||
$output = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['job']->theme($view->result[0]);
|
||||
});
|
||||
$this->assertEqual($output, $random_text, 'Make sure the render method rendered the manual set value.');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -141,6 +148,9 @@ class FieldUnitTest extends ViewUnitTestBase {
|
|||
* Tests general rewriting of the output.
|
||||
*/
|
||||
public function testRewrite() {
|
||||
/** @var \Drupal\Core\Render\RendererInterface $renderer */
|
||||
$renderer = \Drupal::service('renderer');
|
||||
|
||||
$view = Views::getView('test_view');
|
||||
$view->initHandlers();
|
||||
$this->executeView($view);
|
||||
|
@ -149,11 +159,15 @@ class FieldUnitTest extends ViewUnitTestBase {
|
|||
|
||||
// Don't check the rewrite checkbox, so the text shouldn't appear.
|
||||
$id_field->options['alter']['text'] = $random_text = $this->randomMachineName();
|
||||
$output = $id_field->theme($row);
|
||||
$output = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
|
||||
return $id_field->theme($row);
|
||||
});
|
||||
$this->assertNotSubString($output, $random_text);
|
||||
|
||||
$id_field->options['alter']['alter_text'] = TRUE;
|
||||
$output = $id_field->theme($row);
|
||||
$output = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
|
||||
return $id_field->theme($row);
|
||||
});
|
||||
$this->assertSubString($output, $random_text);
|
||||
}
|
||||
|
||||
|
@ -161,6 +175,9 @@ class FieldUnitTest extends ViewUnitTestBase {
|
|||
* Tests the field tokens, row level and field level.
|
||||
*/
|
||||
public function testFieldTokens() {
|
||||
/** @var \Drupal\Core\Render\RendererInterface $renderer */
|
||||
$renderer = \Drupal::service('renderer');
|
||||
|
||||
$view = Views::getView('test_field_tokens');
|
||||
$this->executeView($view);
|
||||
$name_field_0 = $view->field['name'];
|
||||
|
@ -182,19 +199,25 @@ class FieldUnitTest extends ViewUnitTestBase {
|
|||
$expected_output_1 = "$row->views_test_data_name $row->views_test_data_name";
|
||||
$expected_output_2 = "$row->views_test_data_name $row->views_test_data_name $row->views_test_data_name";
|
||||
|
||||
$output = $name_field_0->advancedRender($row);
|
||||
$output = $renderer->executeInRenderContext(new RenderContext(), function () use ($name_field_0, $row) {
|
||||
return $name_field_0->advancedRender($row);
|
||||
});
|
||||
$this->assertEqual($output, $expected_output_0, format_string('Test token replacement: "!token" gave "!output"', [
|
||||
'!token' => $name_field_0->options['alter']['text'],
|
||||
'!output' => $output,
|
||||
]));
|
||||
|
||||
$output = $name_field_1->advancedRender($row);
|
||||
$output = $renderer->executeInRenderContext(new RenderContext(), function () use ($name_field_1, $row) {
|
||||
return $name_field_1->advancedRender($row);
|
||||
});
|
||||
$this->assertEqual($output, $expected_output_1, format_string('Test token replacement: "!token" gave "!output"', [
|
||||
'!token' => $name_field_1->options['alter']['text'],
|
||||
'!output' => $output,
|
||||
]));
|
||||
|
||||
$output = $name_field_2->advancedRender($row);
|
||||
$output = $renderer->executeInRenderContext(new RenderContext(), function () use ($name_field_2, $row) {
|
||||
return $name_field_2->advancedRender($row);
|
||||
});
|
||||
$this->assertEqual($output, $expected_output_2, format_string('Test token replacement: "!token" gave "!output"', [
|
||||
'!token' => $name_field_2->options['alter']['text'],
|
||||
'!output' => $output,
|
||||
|
@ -207,7 +230,9 @@ class FieldUnitTest extends ViewUnitTestBase {
|
|||
|
||||
$random_text = $this->randomMachineName();
|
||||
$job_field->setTestValue($random_text);
|
||||
$output = $job_field->advancedRender($row);
|
||||
$output = $renderer->executeInRenderContext(new RenderContext(), function () use ($job_field, $row) {
|
||||
return $job_field->advancedRender($row);
|
||||
});
|
||||
$this->assertSubString($output, $random_text, format_string('Make sure the self token (!token => !value) appears in the output (!output)', [
|
||||
'!value' => $random_text,
|
||||
'!output' => $output,
|
||||
|
@ -219,7 +244,9 @@ class FieldUnitTest extends ViewUnitTestBase {
|
|||
$job_field->options['alter']['text'] = $old_token;
|
||||
$random_text = $this->randomMachineName();
|
||||
$job_field->setTestValue($random_text);
|
||||
$output = $job_field->advancedRender($row);
|
||||
$output = $renderer->executeInRenderContext(new RenderContext(), function () use ($job_field, $row) {
|
||||
return $job_field->advancedRender($row);
|
||||
});
|
||||
$this->assertSubString($output, $old_token, format_string('Make sure the old token style (!token => !value) is not changed in the output (!output)', [
|
||||
'!value' => $random_text,
|
||||
'!output' => $output,
|
||||
|
@ -268,6 +295,9 @@ class FieldUnitTest extends ViewUnitTestBase {
|
|||
* This tests alters the result to get easier and less coupled results.
|
||||
*/
|
||||
function _testHideIfEmpty() {
|
||||
/** @var \Drupal\Core\Render\RendererInterface $renderer */
|
||||
$renderer = \Drupal::service('renderer');
|
||||
|
||||
$view = Views::getView('test_view');
|
||||
$view->initDisplay();
|
||||
$this->executeView($view);
|
||||
|
@ -284,22 +314,30 @@ class FieldUnitTest extends ViewUnitTestBase {
|
|||
|
||||
// Test a valid string.
|
||||
$view->result[0]->{$column_map_reversed['name']} = $random_name;
|
||||
$render = $view->field['name']->advancedRender($view->result[0]);
|
||||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($render, $random_name, 'By default, a string should not be treated as empty.');
|
||||
|
||||
// Test an empty string.
|
||||
$view->result[0]->{$column_map_reversed['name']} = "";
|
||||
$render = $view->field['name']->advancedRender($view->result[0]);
|
||||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($render, "", 'By default, "" should not be treated as empty.');
|
||||
|
||||
// Test zero as an integer.
|
||||
$view->result[0]->{$column_map_reversed['name']} = 0;
|
||||
$render = $view->field['name']->advancedRender($view->result[0]);
|
||||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($render, '0', 'By default, 0 should not be treated as empty.');
|
||||
|
||||
// Test zero as a string.
|
||||
$view->result[0]->{$column_map_reversed['name']} = "0";
|
||||
$render = $view->field['name']->advancedRender($view->result[0]);
|
||||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($render, "0", 'By default, "0" should not be treated as empty.');
|
||||
|
||||
// Test when results are not rewritten and non-zero empty values are hidden.
|
||||
|
@ -309,22 +347,30 @@ class FieldUnitTest extends ViewUnitTestBase {
|
|||
|
||||
// Test a valid string.
|
||||
$view->result[0]->{$column_map_reversed['name']} = $random_name;
|
||||
$render = $view->field['name']->advancedRender($view->result[0]);
|
||||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($render, $random_name, 'If hide_empty is checked, a string should not be treated as empty.');
|
||||
|
||||
// Test an empty string.
|
||||
$view->result[0]->{$column_map_reversed['name']} = "";
|
||||
$render = $view->field['name']->advancedRender($view->result[0]);
|
||||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($render, "", 'If hide_empty is checked, "" should be treated as empty.');
|
||||
|
||||
// Test zero as an integer.
|
||||
$view->result[0]->{$column_map_reversed['name']} = 0;
|
||||
$render = $view->field['name']->advancedRender($view->result[0]);
|
||||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($render, '0', 'If hide_empty is checked, but not empty_zero, 0 should not be treated as empty.');
|
||||
|
||||
// Test zero as a string.
|
||||
$view->result[0]->{$column_map_reversed['name']} = "0";
|
||||
$render = $view->field['name']->advancedRender($view->result[0]);
|
||||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($render, "0", 'If hide_empty is checked, but not empty_zero, "0" should not be treated as empty.');
|
||||
|
||||
// Test when results are not rewritten and all empty values are hidden.
|
||||
|
@ -334,12 +380,16 @@ class FieldUnitTest extends ViewUnitTestBase {
|
|||
|
||||
// Test zero as an integer.
|
||||
$view->result[0]->{$column_map_reversed['name']} = 0;
|
||||
$render = $view->field['name']->advancedRender($view->result[0]);
|
||||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($render, "", 'If hide_empty and empty_zero are checked, 0 should be treated as empty.');
|
||||
|
||||
// Test zero as a string.
|
||||
$view->result[0]->{$column_map_reversed['name']} = "0";
|
||||
$render = $view->field['name']->advancedRender($view->result[0]);
|
||||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($render, "", 'If hide_empty and empty_zero are checked, "0" should be treated as empty.');
|
||||
|
||||
// Test when results are rewritten to a valid string and non-zero empty
|
||||
|
@ -352,22 +402,30 @@ class FieldUnitTest extends ViewUnitTestBase {
|
|||
|
||||
// Test a valid string.
|
||||
$view->result[0]->{$column_map_reversed['name']} = $random_value;
|
||||
$render = $view->field['name']->advancedRender($view->result[0]);
|
||||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($render, $random_name, 'If the rewritten string is not empty, it should not be treated as empty.');
|
||||
|
||||
// Test an empty string.
|
||||
$view->result[0]->{$column_map_reversed['name']} = "";
|
||||
$render = $view->field['name']->advancedRender($view->result[0]);
|
||||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($render, $random_name, 'If the rewritten string is not empty, "" should not be treated as empty.');
|
||||
|
||||
// Test zero as an integer.
|
||||
$view->result[0]->{$column_map_reversed['name']} = 0;
|
||||
$render = $view->field['name']->advancedRender($view->result[0]);
|
||||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($render, $random_name, 'If the rewritten string is not empty, 0 should not be treated as empty.');
|
||||
|
||||
// Test zero as a string.
|
||||
$view->result[0]->{$column_map_reversed['name']} = "0";
|
||||
$render = $view->field['name']->advancedRender($view->result[0]);
|
||||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($render, $random_name, 'If the rewritten string is not empty, "0" should not be treated as empty.');
|
||||
|
||||
// Test when results are rewritten to an empty string and non-zero empty results are hidden.
|
||||
|
@ -379,22 +437,30 @@ class FieldUnitTest extends ViewUnitTestBase {
|
|||
|
||||
// Test a valid string.
|
||||
$view->result[0]->{$column_map_reversed['name']} = $random_name;
|
||||
$render = $view->field['name']->advancedRender($view->result[0]);
|
||||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($render, $random_name, 'If the rewritten string is empty, it should not be treated as empty.');
|
||||
|
||||
// Test an empty string.
|
||||
$view->result[0]->{$column_map_reversed['name']} = "";
|
||||
$render = $view->field['name']->advancedRender($view->result[0]);
|
||||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($render, "", 'If the rewritten string is empty, "" should be treated as empty.');
|
||||
|
||||
// Test zero as an integer.
|
||||
$view->result[0]->{$column_map_reversed['name']} = 0;
|
||||
$render = $view->field['name']->advancedRender($view->result[0]);
|
||||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($render, '0', 'If the rewritten string is empty, 0 should not be treated as empty.');
|
||||
|
||||
// Test zero as a string.
|
||||
$view->result[0]->{$column_map_reversed['name']} = "0";
|
||||
$render = $view->field['name']->advancedRender($view->result[0]);
|
||||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($render, "0", 'If the rewritten string is empty, "0" should not be treated as empty.');
|
||||
|
||||
// Test when results are rewritten to zero as a string and non-zero empty
|
||||
|
@ -407,22 +473,30 @@ class FieldUnitTest extends ViewUnitTestBase {
|
|||
|
||||
// Test a valid string.
|
||||
$view->result[0]->{$column_map_reversed['name']} = $random_name;
|
||||
$render = $view->field['name']->advancedRender($view->result[0]);
|
||||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($render, "0", 'If the rewritten string is zero and empty_zero is not checked, the string rewritten as 0 should not be treated as empty.');
|
||||
|
||||
// Test an empty string.
|
||||
$view->result[0]->{$column_map_reversed['name']} = "";
|
||||
$render = $view->field['name']->advancedRender($view->result[0]);
|
||||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($render, "0", 'If the rewritten string is zero and empty_zero is not checked, "" rewritten as 0 should not be treated as empty.');
|
||||
|
||||
// Test zero as an integer.
|
||||
$view->result[0]->{$column_map_reversed['name']} = 0;
|
||||
$render = $view->field['name']->advancedRender($view->result[0]);
|
||||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($render, "0", 'If the rewritten string is zero and empty_zero is not checked, 0 should not be treated as empty.');
|
||||
|
||||
// Test zero as a string.
|
||||
$view->result[0]->{$column_map_reversed['name']} = "0";
|
||||
$render = $view->field['name']->advancedRender($view->result[0]);
|
||||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($render, "0", 'If the rewritten string is zero and empty_zero is not checked, "0" should not be treated as empty.');
|
||||
|
||||
// Test when results are rewritten to a valid string and non-zero empty
|
||||
|
@ -435,22 +509,30 @@ class FieldUnitTest extends ViewUnitTestBase {
|
|||
|
||||
// Test a valid string.
|
||||
$view->result[0]->{$column_map_reversed['name']} = $random_name;
|
||||
$render = $view->field['name']->advancedRender($view->result[0]);
|
||||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($render, $random_value, 'If the original and rewritten strings are valid, it should not be treated as empty.');
|
||||
|
||||
// Test an empty string.
|
||||
$view->result[0]->{$column_map_reversed['name']} = "";
|
||||
$render = $view->field['name']->advancedRender($view->result[0]);
|
||||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($render, "", 'If either the original or rewritten string is invalid, "" should be treated as empty.');
|
||||
|
||||
// Test zero as an integer.
|
||||
$view->result[0]->{$column_map_reversed['name']} = 0;
|
||||
$render = $view->field['name']->advancedRender($view->result[0]);
|
||||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($render, $random_value, 'If the original and rewritten strings are valid, 0 should not be treated as empty.');
|
||||
|
||||
// Test zero as a string.
|
||||
$view->result[0]->{$column_map_reversed['name']} = "0";
|
||||
$render = $view->field['name']->advancedRender($view->result[0]);
|
||||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($render, $random_value, 'If the original and rewritten strings are valid, "0" should not be treated as empty.');
|
||||
|
||||
// Test when results are rewritten to zero as a string and all empty
|
||||
|
@ -463,22 +545,30 @@ class FieldUnitTest extends ViewUnitTestBase {
|
|||
|
||||
// Test a valid string.
|
||||
$view->result[0]->{$column_map_reversed['name']} = $random_name;
|
||||
$render = $view->field['name']->advancedRender($view->result[0]);
|
||||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($render, "", 'If the rewritten string is zero, it should be treated as empty.');
|
||||
|
||||
// Test an empty string.
|
||||
$view->result[0]->{$column_map_reversed['name']} = "";
|
||||
$render = $view->field['name']->advancedRender($view->result[0]);
|
||||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($render, "", 'If the rewritten string is zero, "" should be treated as empty.');
|
||||
|
||||
// Test zero as an integer.
|
||||
$view->result[0]->{$column_map_reversed['name']} = 0;
|
||||
$render = $view->field['name']->advancedRender($view->result[0]);
|
||||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($render, "", 'If the rewritten string is zero, 0 should not be treated as empty.');
|
||||
|
||||
// Test zero as a string.
|
||||
$view->result[0]->{$column_map_reversed['name']} = "0";
|
||||
$render = $view->field['name']->advancedRender($view->result[0]);
|
||||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($render, "", 'If the rewritten string is zero, "0" should not be treated as empty.');
|
||||
}
|
||||
|
||||
|
@ -486,6 +576,9 @@ class FieldUnitTest extends ViewUnitTestBase {
|
|||
* Tests the usage of the empty text.
|
||||
*/
|
||||
function _testEmptyText() {
|
||||
/** @var \Drupal\Core\Render\RendererInterface $renderer */
|
||||
$renderer = \Drupal::service('renderer');
|
||||
|
||||
$view = Views::getView('test_view');
|
||||
$view->initDisplay();
|
||||
$this->executeView($view);
|
||||
|
@ -495,27 +588,37 @@ class FieldUnitTest extends ViewUnitTestBase {
|
|||
|
||||
$empty_text = $view->field['name']->options['empty'] = $this->randomMachineName();
|
||||
$view->result[0]->{$column_map_reversed['name']} = "";
|
||||
$render = $view->field['name']->advancedRender($view->result[0]);
|
||||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($render, $empty_text, 'If a field is empty, the empty text should be used for the output.');
|
||||
|
||||
$view->result[0]->{$column_map_reversed['name']} = "0";
|
||||
$render = $view->field['name']->advancedRender($view->result[0]);
|
||||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($render, "0", 'If a field is 0 and empty_zero is not checked, the empty text should not be used for the output.');
|
||||
|
||||
$view->result[0]->{$column_map_reversed['name']} = "0";
|
||||
$view->field['name']->options['empty_zero'] = TRUE;
|
||||
$render = $view->field['name']->advancedRender($view->result[0]);
|
||||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($render, $empty_text, 'If a field is 0 and empty_zero is checked, the empty text should be used for the output.');
|
||||
|
||||
$view->result[0]->{$column_map_reversed['name']} = "";
|
||||
$view->field['name']->options['alter']['alter_text'] = TRUE;
|
||||
$alter_text = $view->field['name']->options['alter']['text'] = $this->randomMachineName();
|
||||
$view->field['name']->options['hide_alter_empty'] = FALSE;
|
||||
$render = $view->field['name']->advancedRender($view->result[0]);
|
||||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($render, $alter_text, 'If a field is empty, some rewrite text exists, but hide_alter_empty is not checked, render the rewrite text.');
|
||||
|
||||
$view->field['name']->options['hide_alter_empty'] = TRUE;
|
||||
$render = $view->field['name']->advancedRender($view->result[0]);
|
||||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($render, $empty_text, 'If a field is empty, some rewrite text exists, and hide_alter_empty is checked, use the empty text.');
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ namespace Drupal\views\Tests\Handler;
|
|||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
use Drupal\Component\Utility\UrlHelper;
|
||||
use Drupal\Core\Render\RenderContext;
|
||||
use Drupal\system\Tests\Cache\AssertPageCacheContextsAndTagsTrait;
|
||||
use Drupal\views\Views;
|
||||
|
||||
|
@ -197,6 +198,9 @@ class FieldWebTest extends HandlerTestBase {
|
|||
* Tests rewriting the output to a link.
|
||||
*/
|
||||
public function testAlterUrl() {
|
||||
/** @var \Drupal\Core\Render\RendererInterface $renderer */
|
||||
$renderer = \Drupal::service('renderer');
|
||||
|
||||
$view = Views::getView('test_view');
|
||||
$view->setDisplay();
|
||||
$view->initHandlers();
|
||||
|
@ -211,13 +215,17 @@ class FieldWebTest extends HandlerTestBase {
|
|||
// Tests that the suffix/prefix appears on the output.
|
||||
$id_field->options['alter']['prefix'] = $prefix = $this->randomMachineName();
|
||||
$id_field->options['alter']['suffix'] = $suffix = $this->randomMachineName();
|
||||
$output = $id_field->theme($row);
|
||||
$output = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
|
||||
return $id_field->theme($row);
|
||||
});
|
||||
$this->assertSubString($output, $prefix);
|
||||
$this->assertSubString($output, $suffix);
|
||||
unset($id_field->options['alter']['prefix']);
|
||||
unset($id_field->options['alter']['suffix']);
|
||||
|
||||
$output = $id_field->theme($row);
|
||||
$output = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
|
||||
return $id_field->theme($row);
|
||||
});
|
||||
$this->assertSubString($output, $path, 'Make sure that the path is part of the output');
|
||||
|
||||
// Some generic test code adapted from the UrlTest class, which tests
|
||||
|
@ -228,44 +236,60 @@ class FieldWebTest extends HandlerTestBase {
|
|||
|
||||
$expected_result = \Drupal::url('entity.node.canonical', ['node' => '123'], ['absolute' => $absolute]);
|
||||
$alter['absolute'] = $absolute;
|
||||
$result = $id_field->theme($row);
|
||||
$result = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
|
||||
return $id_field->theme($row);
|
||||
});
|
||||
$this->assertSubString($result, $expected_result);
|
||||
|
||||
$expected_result = \Drupal::url('entity.node.canonical', ['node' => '123'], ['fragment' => 'foo', 'absolute' => $absolute]);
|
||||
$alter['path'] = 'node/123#foo';
|
||||
$result = $id_field->theme($row);
|
||||
$result = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
|
||||
return $id_field->theme($row);
|
||||
});
|
||||
$this->assertSubString($result, $expected_result);
|
||||
|
||||
$expected_result = \Drupal::url('entity.node.canonical', ['node' => '123'], ['query' => ['foo' => NULL], 'absolute' => $absolute]);
|
||||
$alter['path'] = 'node/123?foo';
|
||||
$result = $id_field->theme($row);
|
||||
$result = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
|
||||
return $id_field->theme($row);
|
||||
});
|
||||
$this->assertSubString($result, $expected_result);
|
||||
|
||||
$expected_result = \Drupal::url('entity.node.canonical', ['node' => '123'], ['query' => ['foo' => 'bar', 'bar' => 'baz'], 'absolute' => $absolute]);
|
||||
$alter['path'] = 'node/123?foo=bar&bar=baz';
|
||||
$result = $id_field->theme($row);
|
||||
$result = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
|
||||
return $id_field->theme($row);
|
||||
});
|
||||
$this->assertSubString(Html::decodeEntities($result), Html::decodeEntities($expected_result));
|
||||
|
||||
// @todo The route-based URL generator strips out NULL attributes.
|
||||
// $expected_result = \Drupal::url('entity.node.canonical', ['node' => '123'], ['query' => ['foo' => NULL], 'fragment' => 'bar', 'absolute' => $absolute]);
|
||||
$expected_result = \Drupal::urlGenerator()->generateFromPath('node/123', array('query' => array('foo' => NULL), 'fragment' => 'bar', 'absolute' => $absolute));
|
||||
$alter['path'] = 'node/123?foo#bar';
|
||||
$result = $id_field->theme($row);
|
||||
$result = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
|
||||
return $id_field->theme($row);
|
||||
});
|
||||
$this->assertSubString(Html::decodeEntities($result), Html::decodeEntities($expected_result));
|
||||
|
||||
$expected_result = \Drupal::url('<front>', [], ['absolute' => $absolute]);
|
||||
$alter['path'] = '<front>';
|
||||
$result = $id_field->theme($row);
|
||||
$result = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
|
||||
return $id_field->theme($row);
|
||||
});
|
||||
$this->assertSubString($result, $expected_result);
|
||||
}
|
||||
|
||||
// Tests the replace spaces with dashes feature.
|
||||
$id_field->options['alter']['replace_spaces'] = TRUE;
|
||||
$id_field->options['alter']['path'] = $path = $this->randomMachineName() . ' ' . $this->randomMachineName();
|
||||
$output = $id_field->theme($row);
|
||||
$output = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
|
||||
return $id_field->theme($row);
|
||||
});
|
||||
$this->assertSubString($output, str_replace(' ', '-', $path));
|
||||
$id_field->options['alter']['replace_spaces'] = FALSE;
|
||||
$output = $id_field->theme($row);
|
||||
$output = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
|
||||
return $id_field->theme($row);
|
||||
});
|
||||
// The url has a space in it, so to check we have to decode the url output.
|
||||
$this->assertSubString(urldecode($output), $path);
|
||||
|
||||
|
@ -273,44 +297,60 @@ class FieldWebTest extends HandlerTestBase {
|
|||
// Switch on the external flag should output an external url as well.
|
||||
$id_field->options['alter']['external'] = TRUE;
|
||||
$id_field->options['alter']['path'] = $path = 'www.drupal.org';
|
||||
$output = $id_field->theme($row);
|
||||
$output = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
|
||||
return $id_field->theme($row);
|
||||
});
|
||||
$this->assertSubString($output, 'http://www.drupal.org');
|
||||
|
||||
// Setup a not external url, which shouldn't lead to an external url.
|
||||
$id_field->options['alter']['external'] = FALSE;
|
||||
$id_field->options['alter']['path'] = $path = 'www.drupal.org';
|
||||
$output = $id_field->theme($row);
|
||||
$output = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
|
||||
return $id_field->theme($row);
|
||||
});
|
||||
$this->assertNotSubString($output, 'http://www.drupal.org');
|
||||
|
||||
// Tests the transforming of the case setting.
|
||||
$id_field->options['alter']['path'] = $path = $this->randomMachineName();
|
||||
$id_field->options['alter']['path_case'] = 'none';
|
||||
$output = $id_field->theme($row);
|
||||
$output = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
|
||||
return $id_field->theme($row);
|
||||
});
|
||||
$this->assertSubString($output, $path);
|
||||
|
||||
// Switch to uppercase and lowercase.
|
||||
$id_field->options['alter']['path_case'] = 'upper';
|
||||
$output = $id_field->theme($row);
|
||||
$output = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
|
||||
return $id_field->theme($row);
|
||||
});
|
||||
$this->assertSubString($output, strtoupper($path));
|
||||
$id_field->options['alter']['path_case'] = 'lower';
|
||||
$output = $id_field->theme($row);
|
||||
$output = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
|
||||
return $id_field->theme($row);
|
||||
});
|
||||
$this->assertSubString($output, strtolower($path));
|
||||
|
||||
// Switch to ucfirst and ucwords.
|
||||
$id_field->options['alter']['path_case'] = 'ucfirst';
|
||||
$id_field->options['alter']['path'] = 'drupal has a great community';
|
||||
$output = $id_field->theme($row);
|
||||
$output = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
|
||||
return $id_field->theme($row);
|
||||
});
|
||||
$this->assertSubString($output, UrlHelper::encodePath('Drupal has a great community'));
|
||||
|
||||
$id_field->options['alter']['path_case'] = 'ucwords';
|
||||
$output = $id_field->theme($row);
|
||||
$output = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
|
||||
return $id_field->theme($row);
|
||||
});
|
||||
$this->assertSubString($output, UrlHelper::encodePath('Drupal Has A Great Community'));
|
||||
unset($id_field->options['alter']['path_case']);
|
||||
|
||||
// Tests the linkclass setting and see whether it actually exists in the
|
||||
// output.
|
||||
$id_field->options['alter']['link_class'] = $class = $this->randomMachineName();
|
||||
$output = $id_field->theme($row);
|
||||
$output = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
|
||||
return $id_field->theme($row);
|
||||
});
|
||||
$elements = $this->xpathContent($output, '//a[contains(@class, :class)]', array(':class' => $class));
|
||||
$this->assertTrue($elements);
|
||||
// @fixme link_class, alt, rel cannot be unset, which should be fixed.
|
||||
|
@ -318,21 +358,27 @@ class FieldWebTest extends HandlerTestBase {
|
|||
|
||||
// Tests the alt setting.
|
||||
$id_field->options['alter']['alt'] = $rel = $this->randomMachineName();
|
||||
$output = $id_field->theme($row);
|
||||
$output = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
|
||||
return $id_field->theme($row);
|
||||
});
|
||||
$elements = $this->xpathContent($output, '//a[contains(@title, :alt)]', array(':alt' => $rel));
|
||||
$this->assertTrue($elements);
|
||||
$id_field->options['alter']['alt'] = '';
|
||||
|
||||
// Tests the rel setting.
|
||||
$id_field->options['alter']['rel'] = $rel = $this->randomMachineName();
|
||||
$output = $id_field->theme($row);
|
||||
$output = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
|
||||
return $id_field->theme($row);
|
||||
});
|
||||
$elements = $this->xpathContent($output, '//a[contains(@rel, :rel)]', array(':rel' => $rel));
|
||||
$this->assertTrue($elements);
|
||||
$id_field->options['alter']['rel'] = '';
|
||||
|
||||
// Tests the target setting.
|
||||
$id_field->options['alter']['target'] = $target = $this->randomMachineName();
|
||||
$output = $id_field->theme($row);
|
||||
$output = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
|
||||
return $id_field->theme($row);
|
||||
});
|
||||
$elements = $this->xpathContent($output, '//a[contains(@target, :target)]', array(':target' => $target));
|
||||
$this->assertTrue($elements);
|
||||
unset($id_field->options['alter']['target']);
|
||||
|
@ -453,6 +499,9 @@ class FieldWebTest extends HandlerTestBase {
|
|||
* Tests trimming/read-more/ellipses.
|
||||
*/
|
||||
public function testTextRendering() {
|
||||
/** @var \Drupal\Core\Render\RendererInterface $renderer */
|
||||
$renderer = \Drupal::service('renderer');
|
||||
|
||||
$view = Views::getView('test_field_output');
|
||||
$view->initHandlers();
|
||||
$name_field = $view->field['name'];
|
||||
|
@ -465,18 +514,24 @@ class FieldWebTest extends HandlerTestBase {
|
|||
$row = $view->result[0];
|
||||
|
||||
$name_field->options['alter']['strip_tags'] = TRUE;
|
||||
$output = $name_field->advancedRender($row);
|
||||
$output = $renderer->executeInRenderContext(new RenderContext(), function () use ($name_field, $row) {
|
||||
return $name_field->advancedRender($row);
|
||||
});
|
||||
$this->assertSubString($output, $random_text, 'Find text without html if stripping of views field output is enabled.');
|
||||
$this->assertNotSubString($output, $html_text, 'Find no text with the html if stripping of views field output is enabled.');
|
||||
|
||||
// Tests preserving of html tags.
|
||||
$name_field->options['alter']['preserve_tags'] = '<div>';
|
||||
$output = $name_field->advancedRender($row);
|
||||
$output = $renderer->executeInRenderContext(new RenderContext(), function () use ($name_field, $row) {
|
||||
return $name_field->advancedRender($row);
|
||||
});
|
||||
$this->assertSubString($output, $random_text, 'Find text without html if stripping of views field output is enabled but a div is allowed.');
|
||||
$this->assertSubString($output, $html_text, 'Find text with the html if stripping of views field output is enabled but a div is allowed.');
|
||||
|
||||
$name_field->options['alter']['strip_tags'] = FALSE;
|
||||
$output = $name_field->advancedRender($row);
|
||||
$output = $renderer->executeInRenderContext(new RenderContext(), function () use ($name_field, $row) {
|
||||
return $name_field->advancedRender($row);
|
||||
});
|
||||
$this->assertSubString($output, $random_text, 'Find text without html if stripping of views field output is disabled.');
|
||||
$this->assertSubString($output, $html_text, 'Find text with the html if stripping of views field output is disabled.');
|
||||
|
||||
|
@ -485,13 +540,17 @@ class FieldWebTest extends HandlerTestBase {
|
|||
$views_test_data_name = $row->views_test_data_name;
|
||||
$row->views_test_data_name = ' ' . $views_test_data_name . ' ';
|
||||
$name_field->options['alter']['trim_whitespace'] = TRUE;
|
||||
$output = $name_field->advancedRender($row);
|
||||
$output = $renderer->executeInRenderContext(new RenderContext(), function () use ($name_field, $row) {
|
||||
return $name_field->advancedRender($row);
|
||||
});
|
||||
|
||||
$this->assertSubString($output, $views_test_data_name, 'Make sure the trimmed text can be found if trimming is enabled.');
|
||||
$this->assertNotSubString($output, $row->views_test_data_name, 'Make sure the untrimmed text can be found if trimming is enabled.');
|
||||
|
||||
$name_field->options['alter']['trim_whitespace'] = FALSE;
|
||||
$output = $name_field->advancedRender($row);
|
||||
$output = $renderer->executeInRenderContext(new RenderContext(), function () use ($name_field, $row) {
|
||||
return $name_field->advancedRender($row);
|
||||
});
|
||||
$this->assertSubString($output, $views_test_data_name, 'Make sure the trimmed text can be found if trimming is disabled.');
|
||||
$this->assertSubString($output, $row->views_test_data_name, 'Make sure the untrimmed text can be found if trimming is disabled.');
|
||||
|
||||
|
@ -504,12 +563,16 @@ class FieldWebTest extends HandlerTestBase {
|
|||
$name_field->options['alter']['max_length'] = 5;
|
||||
$trimmed_name = Unicode::substr($row->views_test_data_name, 0, 5);
|
||||
|
||||
$output = $name_field->advancedRender($row);
|
||||
$output = $renderer->executeInRenderContext(new RenderContext(), function () use ($name_field, $row) {
|
||||
return $name_field->advancedRender($row);
|
||||
});
|
||||
$this->assertSubString($output, $trimmed_name, format_string('Make sure the trimmed output (!trimmed) appears in the rendered output (!output).', array('!trimmed' => $trimmed_name, '!output' => $output)));
|
||||
$this->assertNotSubString($output, $row->views_test_data_name, format_string("Make sure the untrimmed value (!untrimmed) shouldn't appear in the rendered output (!output).", array('!untrimmed' => $row->views_test_data_name, '!output' => $output)));
|
||||
|
||||
$name_field->options['alter']['max_length'] = 9;
|
||||
$output = $name_field->advancedRender($row);
|
||||
$output = $renderer->executeInRenderContext(new RenderContext(), function () use ($name_field, $row) {
|
||||
return $name_field->advancedRender($row);
|
||||
});
|
||||
$this->assertSubString($output, $trimmed_name, format_string('Make sure the untrimmed (!untrimmed) output appears in the rendered output (!output).', array('!trimmed' => $trimmed_name, '!output' => $output)));
|
||||
|
||||
// Take word_boundary into account for the tests.
|
||||
|
@ -549,7 +612,9 @@ class FieldWebTest extends HandlerTestBase {
|
|||
|
||||
foreach ($tuples as $tuple) {
|
||||
$row->views_test_data_name = $tuple['value'];
|
||||
$output = $name_field->advancedRender($row);
|
||||
$output = $renderer->executeInRenderContext(new RenderContext(), function () use ($name_field, $row) {
|
||||
return $name_field->advancedRender($row);
|
||||
});
|
||||
|
||||
if ($tuple['trimmed']) {
|
||||
$this->assertNotSubString($output, $tuple['value'], format_string('The untrimmed value (!untrimmed) should not appear in the trimmed output (!output).', array('!untrimmed' => $tuple['value'], '!output' => $output)));
|
||||
|
@ -566,22 +631,30 @@ class FieldWebTest extends HandlerTestBase {
|
|||
$name_field->options['alter']['more_link_text'] = $more_text = $this->randomMachineName();
|
||||
$name_field->options['alter']['more_link_path'] = $more_path = $this->randomMachineName();
|
||||
|
||||
$output = $name_field->advancedRender($row);
|
||||
$output = $renderer->executeInRenderContext(new RenderContext(), function () use ($name_field, $row) {
|
||||
return $name_field->advancedRender($row);
|
||||
});
|
||||
$this->assertSubString($output, $more_text, 'Make sure a read more text is displayed if the output got trimmed');
|
||||
$this->assertTrue($this->xpathContent($output, '//a[contains(@href, :path)]', array(':path' => $more_path)), 'Make sure the read more link points to the right destination.');
|
||||
|
||||
$name_field->options['alter']['more_link'] = FALSE;
|
||||
$output = $name_field->advancedRender($row);
|
||||
$output = $renderer->executeInRenderContext(new RenderContext(), function () use ($name_field, $row) {
|
||||
return $name_field->advancedRender($row);
|
||||
});
|
||||
$this->assertNotSubString($output, $more_text, 'Make sure no read more text appears.');
|
||||
$this->assertFalse($this->xpathContent($output, '//a[contains(@href, :path)]', array(':path' => $more_path)), 'Make sure no read more link appears.');
|
||||
|
||||
// Check for the ellipses.
|
||||
$row->views_test_data_name = $this->randomMachineName(8);
|
||||
$name_field->options['alter']['max_length'] = 5;
|
||||
$output = $name_field->advancedRender($row);
|
||||
$output = $renderer->executeInRenderContext(new RenderContext(), function () use ($name_field, $row) {
|
||||
return $name_field->advancedRender($row);
|
||||
});
|
||||
$this->assertSubString($output, '…', 'An ellipsis should appear if the output is trimmed');
|
||||
$name_field->options['alter']['max_length'] = 10;
|
||||
$output = $name_field->advancedRender($row);
|
||||
$output = $renderer->executeInRenderContext(new RenderContext(), function () use ($name_field, $row) {
|
||||
return $name_field->advancedRender($row);
|
||||
});
|
||||
$this->assertNotSubString($output, '…', 'No ellipsis should appear if the output is not trimmed');
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
namespace Drupal\views\Tests\Plugin;
|
||||
|
||||
use Drupal\Core\Render\RenderContext;
|
||||
use Drupal\node\Entity\Node;
|
||||
use Drupal\views\Tests\ViewUnitTestBase;
|
||||
use Drupal\views\Views;
|
||||
|
@ -282,14 +283,18 @@ class CacheTest extends ViewUnitTestBase {
|
|||
$output = $view->buildRenderable();
|
||||
/** @var \Drupal\Core\Render\RendererInterface $renderer */
|
||||
$renderer = \Drupal::service('renderer');
|
||||
$renderer->render($output);
|
||||
$renderer->executeInRenderContext(new RenderContext(), function () use (&$output, $renderer) {
|
||||
return $renderer->render($output);
|
||||
});
|
||||
|
||||
unset($view->pre_render_called);
|
||||
$view->destroy();
|
||||
|
||||
$view->setDisplay();
|
||||
$output = $view->buildRenderable();
|
||||
$renderer->render($output);
|
||||
$renderer->executeInRenderContext(new RenderContext(), function () use (&$output, $renderer) {
|
||||
return $renderer->render($output);
|
||||
});
|
||||
|
||||
$this->assertTrue(in_array('views_test_data/test', $output['#attached']['library']), 'Make sure libraries are added for cached views.');
|
||||
$this->assertEqual(['foo' => 'bar'], $output['#attached']['drupalSettings'], 'Make sure drupalSettings are added for cached views.');
|
||||
|
|
Loading…
Reference in New Issue