Commit 7543540e authored by alexpott's avatar alexpott

Issue #2375695 by Berdir, EclipseGc, tim.plunkett, Wim Leers, Fabianx,...

Issue #2375695 by Berdir, EclipseGc, tim.plunkett, Wim Leers, Fabianx, dawehner: Condition plugins should provide cache contexts AND cacheability metadata needs to be exposed
parent 05aa5ea1
......@@ -22,7 +22,7 @@ abstract class ContextAwarePluginBase extends PluginBase implements ContextAware
*
* @var \Drupal\Component\Plugin\Context\ContextInterface[]
*/
protected $context;
protected $context = [];
/**
* Overrides \Drupal\Component\Plugin\PluginBase::__construct().
......
......@@ -10,6 +10,7 @@
use Drupal\block\BlockInterface;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Cache\CacheableDependencyInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContextAwarePluginBase;
use Drupal\Component\Utility\Unicode;
......@@ -272,6 +273,16 @@ public function getMachineNameSuggestion() {
return $transliterated;
}
/**
* {@inheritdoc}
*/
public function getCacheMaxAge() {
$max_age = parent::getCacheMaxAge();
// @todo Configurability of this will be removed in
// https://www.drupal.org/node/2458763.
return Cache::mergeMaxAges($max_age, (int) $this->configuration['cache']['max_age']);
}
/**
* Wraps the transliteration service.
*
......@@ -294,25 +305,4 @@ public function setTransliteration(TransliterationInterface $transliteration) {
$this->transliteration = $transliteration;
}
/**
* {@inheritdoc}
*/
public function getCacheContexts() {
return [];
}
/**
* {@inheritdoc}
*/
public function getCacheTags() {
return [];
}
/**
* {@inheritdoc}
*/
public function getCacheMaxAge() {
return (int)$this->configuration['cache']['max_age'];
}
}
......@@ -9,6 +9,7 @@
use Drupal\Component\Plugin\ConfigurablePluginInterface;
use Drupal\Component\Plugin\PluginInspectionInterface;
use Drupal\Core\Cache\CacheableDependencyInterface;
use Drupal\Core\Executable\ExecutableInterface;
use Drupal\Core\Executable\ExecutableManagerInterface;
use Drupal\Core\Plugin\PluginFormInterface;
......@@ -46,7 +47,7 @@
*
* @ingroup plugin_api
*/
interface ConditionInterface extends ExecutableInterface, PluginFormInterface, ConfigurablePluginInterface, PluginInspectionInterface {
interface ConditionInterface extends ExecutableInterface, PluginFormInterface, ConfigurablePluginInterface, PluginInspectionInterface, CacheableDependencyInterface {
/**
* Determines whether condition result will be negated.
......
......@@ -7,6 +7,8 @@
namespace Drupal\Core\Condition;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheableDependencyInterface;
use Drupal\Core\Executable\ExecutableManagerInterface;
use Drupal\Core\Executable\ExecutablePluginBase;
use Drupal\Core\Form\FormStateInterface;
......
......@@ -10,6 +10,8 @@
use Drupal\Component\Plugin\Context\Context as ComponentContext;
use Drupal\Component\Plugin\Exception\ContextException;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Cache\CacheableDependencyInterface;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\TypedData\TypedDataInterface;
use Drupal\Core\TypedData\TypedDataTrait;
......@@ -34,6 +36,21 @@ class Context extends ComponentContext implements ContextInterface {
*/
protected $contextDefinition;
/**
* The cacheability metadata.
*
* @var \Drupal\Core\Cache\CacheableMetadata
*/
protected $cacheabilityMetadata;
/**
* {@inheritdoc}
*/
public function __construct(ContextDefinitionInterface $context_definition) {
parent::__construct($context_definition);
$this->cacheabilityMetadata = new CacheableMetadata();
}
/**
* {@inheritdoc}
*/
......@@ -67,6 +84,11 @@ public function hasContextValue() {
* {@inheritdoc}
*/
public function setContextValue($value) {
// Add the value as a cacheable dependency only if implements the interface
// to prevent it from disabling caching with a max-age 0.
if ($value instanceof CacheableDependencyInterface) {
$this->addCacheableDependency($value);
}
if ($value instanceof TypedDataInterface) {
return $this->setContextData($value);
}
......@@ -120,4 +142,33 @@ public function validate() {
return $this->getContextData()->validate();
}
/**
* {@inheritdoc}
*/
public function addCacheableDependency($dependency) {
$this->cacheabilityMetadata = $this->cacheabilityMetadata->merge(CacheableMetadata::createFromObject($dependency));
return $this;
}
/**
* {@inheritdoc}
*/
public function getCacheContexts() {
return $this->cacheabilityMetadata->getCacheContexts();
}
/**
* {@inheritdoc}
*/
public function getCacheTags() {
return $this->cacheabilityMetadata->getCacheTags();
}
/**
* {@inheritdoc}
*/
public function getCacheMaxAge() {
return $this->cacheabilityMetadata->getCacheMaxAge();
}
}
......@@ -9,6 +9,7 @@
use Drupal\Component\Plugin\Exception\ContextException;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Cache\CacheableDependencyInterface;
use Drupal\Core\Plugin\ContextAwarePluginInterface;
/**
......@@ -84,6 +85,14 @@ public function applyContextMapping(ContextAwarePluginInterface $plugin, $contex
// This assignment has been used, remove it.
unset($mappings[$plugin_context_id]);
// Plugins have their on context objects, only the value is applied.
// They also need to know about the cacheable metadata of where that
// value is coming from, so pass them through to those objects.
$plugin_context = $plugin->getContext($plugin_context_id);
if ($plugin_context instanceof ContextInterface && $contexts[$context_id] instanceof CacheableDependencyInterface) {
$plugin_context->addCacheableDependency($contexts[$context_id]);
}
// Pass the value to the plugin if there is one.
if ($contexts[$context_id]->hasContextValue()) {
$plugin->setContextValue($plugin_context_id, $contexts[$context_id]->getContextValue());
......
......@@ -8,12 +8,13 @@
namespace Drupal\Core\Plugin\Context;
use Drupal\Component\Plugin\Context\ContextInterface as ComponentContextInterface;
use Drupal\Core\Cache\CacheableDependencyInterface;
use Drupal\Core\TypedData\TypedDataInterface;
/**
* Interface for context.
*/
interface ContextInterface extends ComponentContextInterface {
interface ContextInterface extends ComponentContextInterface, CacheableDependencyInterface {
/**
* Gets the context value as typed data object.
......@@ -32,4 +33,22 @@ public function getContextData();
*/
public function setContextData(TypedDataInterface $data);
/**
* Adds a dependency on an object: merges its cacheability metadata.
*
* E.g. when a context depends on some configuration, an entity, or an access
* result, we must make sure their cacheability metadata is present on the
* response. This method makes doing that simple.
*
* @param \Drupal\Core\Cache\CacheableDependencyInterface|mixed $dependency
* The dependency. If the object implements CacheableDependencyInterface,
* then its cacheability metadata will be used. Otherwise, the passed in
* object must be assumed to be uncacheable, so max-age 0 is set.
*
* @return $this
*
* @see \Drupal\Core\Cache\CacheableMetadata::createFromObject()
*/
public function addCacheableDependency($dependency);
}
......@@ -10,6 +10,8 @@
use Drupal\Component\Plugin\ConfigurablePluginInterface;
use Drupal\Component\Plugin\ContextAwarePluginBase as ComponentContextAwarePluginBase;
use Drupal\Component\Plugin\Exception\ContextException;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheableDependencyInterface;
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
use Drupal\Core\Plugin\Context\Context;
use Drupal\Core\StringTranslation\StringTranslationTrait;
......@@ -91,4 +93,53 @@ protected function contextHandler() {
return \Drupal::service('context.handler');
}
/**
* {@inheritdoc}
*/
public function getCacheContexts() {
$cache_contexts = [];
// Applied contexts can affect the cache contexts when this plugin is
// involved in caching, collect and return them.
foreach ($this->getContexts() as $context) {
/** @var $context \Drupal\Core\Cache\CacheableDependencyInterface */
if ($context instanceof CacheableDependencyInterface) {
$cache_contexts = Cache::mergeContexts($cache_contexts, $context->getCacheContexts());
}
}
return $cache_contexts;
}
/**
* {@inheritdoc}
*/
public function getCacheTags() {
$tags = [];
// Applied contexts can affect the cache tags when this plugin is
// involved in caching, collect and return them.
foreach ($this->getContexts() as $context) {
/** @var $context \Drupal\Core\Cache\CacheableDependencyInterface */
if ($context instanceof CacheableDependencyInterface) {
$tags = Cache::mergeTags($tags, $context->getCacheTags());
}
}
return $tags;
}
/**
* {@inheritdoc}
*/
public function getCacheMaxAge() {
$max_age = Cache::PERMANENT;
// Applied contexts can affect the cache max age when this plugin is
// involved in caching, collect and return them.
foreach ($this->getContexts() as $context) {
/** @var $context \Drupal\Core\Cache\CacheableDependencyInterface */
if ($context instanceof CacheableDependencyInterface) {
$max_age = Cache::mergeMaxAges($max_age, $context->getCacheMaxAge());
}
}
return $max_age;
}
}
......@@ -9,6 +9,8 @@
use Drupal\Component\Plugin\Exception\ContextException;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheableDependencyInterface;
use Drupal\Core\Condition\ConditionAccessResolverTrait;
use Drupal\Core\Entity\EntityAccessControlHandler;
use Drupal\Core\Entity\EntityHandlerInterface;
......@@ -87,31 +89,60 @@ protected function checkAccess(EntityInterface $entity, $operation, $langcode, A
else {
$contexts = $entity->getContexts();
$conditions = [];
$missing_context = FALSE;
foreach ($entity->getVisibilityConditions() as $condition_id => $condition) {
if ($condition instanceof ContextAwarePluginInterface) {
try {
$this->contextHandler->applyContextMapping($condition, $contexts);
}
catch (ContextException $e) {
return AccessResult::forbidden()->setCacheMaxAge(0);
$missing_context = TRUE;
}
}
$conditions[$condition_id] = $condition;
}
if ($this->resolveConditions($conditions, 'and') !== FALSE) {
if ($missing_context) {
// If any context is missing then we might be missing cacheable
// metadata, and don't know based on what conditions the block is
// accessible or not. For example, blocks that have a node type
// condition will have a missing context on any non-node route like the
// frontpage.
// @todo Avoid setting max-age 0 for some or all cases, for example by
// treating available contexts without value differently in
// https://www.drupal.org/node/2521956.
$access = AccessResult::forbidden()->setCacheMaxAge(0);
}
elseif ($this->resolveConditions($conditions, 'and') !== FALSE) {
// Delegate to the plugin.
$access = $entity->getPlugin()->access($account, TRUE);
}
else {
$access = AccessResult::forbidden();
}
// This should not be hardcoded to an uncacheable access check result, but
// in order to fix that, we need condition plugins to return cache contexts,
// otherwise it will be impossible to determine by which cache contexts the
// result should be varied.
// @todo Change this to use $access->cacheUntilEntityChanges($entity) once
// https://www.drupal.org/node/2375695 is resolved.
return $access->setCacheMaxAge(0);
$this->mergeCacheabilityFromConditions($access, $conditions);
// Ensure that access is evaluated again when the block changes.
return $access->cacheUntilEntityChanges($entity);
}
}
/**
* Merges cacheable metadata from conditions onto the access result object.
*
* @param \Drupal\Core\Access\AccessResult $access
* The access result object.
* @param \Drupal\Core\Condition\ConditionInterface[] $conditions
* List of visibility conditions.
*/
protected function mergeCacheabilityFromConditions(AccessResult $access, array $conditions) {
foreach ($conditions as $condition) {
if ($condition instanceof CacheableDependencyInterface) {
$access->addCacheTags($condition->getCacheTags());
$access->addCacheContexts($condition->getCacheContexts());
$access->setCacheMaxAge(Cache::mergeMaxAges($access->getCacheMaxAge(), $condition->getCacheMaxAge()));
}
}
}
......
......@@ -7,6 +7,7 @@
namespace Drupal\block;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Plugin\Context\ContextHandlerInterface;
use Drupal\Core\Theme\ThemeManagerInterface;
......@@ -49,7 +50,7 @@ public function __construct(EntityManagerInterface $entity_manager, ThemeManager
/**
* {@inheritdoc}
*/
public function getVisibleBlocksPerRegion(array $contexts) {
public function getVisibleBlocksPerRegion(array $contexts, array &$cacheable_metadata = []) {
$active_theme = $this->themeManager->getActiveTheme();
// Build an array of the region names in the right order.
$empty = array_fill_keys($active_theme->getRegions(), array());
......@@ -57,9 +58,19 @@ public function getVisibleBlocksPerRegion(array $contexts) {
$full = array();
foreach ($this->blockStorage->loadByProperties(array('theme' => $active_theme->getName())) as $block_id => $block) {
/** @var \Drupal\block\BlockInterface $block */
$block->setContexts($contexts);
$access = $block->access('view', NULL, TRUE);
$region = $block->getRegion();
if (!isset($cacheable_metadata[$region])) {
$cacheable_metadata[$region] = CacheableMetadata::createFromObject($access);
}
else {
$cacheable_metadata[$region] = $cacheable_metadata[$region]->merge(CacheableMetadata::createFromObject($access));
}
// Set the contexts on the block before checking access.
if ($block->setContexts($contexts)->access('view')) {
$full[$block->getRegion()][$block_id] = $block;
if ($access->isAllowed()) {
$full[$region][$block_id] = $block;
}
}
......
......@@ -14,11 +14,14 @@ interface BlockRepositoryInterface {
*
* @param \Drupal\Component\Plugin\Context\ContextInterface[] $contexts
* An array of contexts to set on the blocks.
* @param \Drupal\Core\Cache\CacheableMetadata[] $cacheable_metadata
* (optional) List of CacheableMetadata objects, keyed by region. This is
* by reference and is used to pass this information back to the caller.
*
* @return array
* The array is first keyed by region machine name, with the values
* containing an array keyed by block ID, with block entities as the values.
*/
public function getVisibleBlocksPerRegion(array $contexts);
public function getVisibleBlocksPerRegion(array $contexts, array &$cacheable_metadata = []);
}
......@@ -8,6 +8,7 @@
namespace Drupal\block\EventSubscriber;
use Drupal\block\Event\BlockContextEvent;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Plugin\Context\Context;
use Drupal\Core\Plugin\Context\ContextDefinition;
......@@ -48,6 +49,11 @@ public function onBlockActiveContext(BlockContextEvent $event) {
if (isset($info[$type_key]['name'])) {
$context = new Context(new ContextDefinition('language', $info[$type_key]['name']));
$context->setContextValue($this->languageManager->getCurrentLanguage($type_key));
$cacheability = new CacheableMetadata();
$cacheability->setCacheContexts(['languages:' . $type_key]);
$context->addCacheableDependency($cacheability);
$event->setContext('language.' . $type_key, $context);
}
}
......
......@@ -8,6 +8,7 @@
namespace Drupal\block\EventSubscriber;
use Drupal\block\Event\BlockContextEvent;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Plugin\Context\Context;
use Drupal\Core\Plugin\Context\ContextDefinition;
......@@ -56,6 +57,9 @@ public function onBlockActiveContext(BlockContextEvent $event) {
$context = new Context(new ContextDefinition('entity:user', $this->t('Current user')));
$context->setContextValue($current_user);
$cacheability = new CacheableMetadata();
$cacheability->setCacheContexts(['user']);
$context->addCacheableDependency($cacheability);
$event->setContext('user.current_user', $context);
}
......
......@@ -8,6 +8,7 @@
namespace Drupal\block\EventSubscriber;
use Drupal\block\Event\BlockContextEvent;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Plugin\Context\Context;
use Drupal\Core\Plugin\Context\ContextDefinition;
use Drupal\Core\Routing\RouteMatchInterface;
......@@ -39,8 +40,8 @@ public function __construct(RouteMatchInterface $route_match) {
* {@inheritdoc}
*/
public function onBlockActiveContext(BlockContextEvent $event) {
$context = new Context(new ContextDefinition('entity:node', NULL, FALSE));
if (($route_object = $this->routeMatch->getRouteObject()) && ($route_contexts = $route_object->getOption('parameters')) && isset($route_contexts['node'])) {
$context = new Context(new ContextDefinition($route_contexts['node']['type']));
if ($node = $this->routeMatch->getParameter('node')) {
$context->setContextValue($node);
}
......@@ -48,10 +49,12 @@ public function onBlockActiveContext(BlockContextEvent $event) {
}
elseif ($this->routeMatch->getRouteName() == 'node.add') {
$node_type = $this->routeMatch->getParameter('node_type');
$context = new Context(new ContextDefinition('entity:node'));
$context->setContextValue(Node::create(array('type' => $node_type->id())));
$event->setContext('node.node', $context);
}
$cacheability = new CacheableMetadata();
$cacheability->setCacheContexts(['route']);
$context->addCacheableDependency($cacheability);
$event->setContext('node.node', $context);
}
/**
......
......@@ -12,6 +12,7 @@
use Drupal\block\Event\BlockEvents;
use Drupal\Core\Block\MainContentBlockPluginInterface;
use Drupal\Core\Block\MessagesBlockPluginInterface;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Display\PageVariantInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\EntityViewBuilderInterface;
......@@ -130,7 +131,8 @@ public function build() {
];
$contexts = $this->getActiveBlockContexts();
// Load all region content assigned via blocks.
foreach ($this->blockRepository->getVisibleBlocksPerRegion($contexts) as $region => $blocks) {
$cacheable_metadata_list = [];
foreach ($this->blockRepository->getVisibleBlocksPerRegion($contexts, $cacheable_metadata_list) as $region => $blocks) {
/** @var $blocks \Drupal\block\BlockInterface[] */
foreach ($blocks as $key => $block) {
$block_plugin = $block->getPlugin();
......@@ -172,6 +174,17 @@ public function build() {
];
}
// The access results' cacheability is currently added to the top level of the
// render array. This is done to prevent issues with empty regions being
// displayed.
// This would need to be changed to allow caching of block regions, as each
// region must then have the relevant cacheable metadata.
$merged_cacheable_metadata = CacheableMetadata::createFromRenderArray($build);
foreach ($cacheable_metadata_list as $cacheable_metadata) {
$merged_cacheable_metadata = $merged_cacheable_metadata->merge($cacheable_metadata);
}
$merged_cacheable_metadata->applyTo($build);
return $build;
}
......
......@@ -90,6 +90,7 @@ public function testLanguageBlockVisibilityLanguageDelete() {
'langcodes' => array(
'fr' => 'fr',
),
'context_mapping' => ['language' => 'language.language_interface'],
),
),
);
......
......@@ -8,6 +8,7 @@
namespace Drupal\Tests\block\Unit;
use Drupal\block\BlockRepository;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Block\BlockPluginInterface;
use Drupal\Core\Plugin\ContextAwarePluginInterface;
use Drupal\Tests\UnitTestCase;
......@@ -113,18 +114,18 @@ public function testGetVisibleBlocksPerRegion(array $blocks_config, array $expec
public function providerBlocksConfig() {
$blocks_config = array(
'block1' => array(
TRUE, 'top', 0
AccessResult::allowed(), 'top', 0
),
// Test a block without access.
'block2' => array(
FALSE, 'bottom', 0
AccessResult::forbidden(), 'bottom', 0
),
// Test two blocks in the same region with specific weight.
'block3' => array(
TRUE, 'bottom', 5
AccessResult::allowed(), 'bottom', 5
),
'block4' => array(
TRUE, 'bottom', -5
AccessResult::allowed(), 'bottom', -5
),
);
......@@ -151,7 +152,7 @@ public function testGetVisibleBlocksPerRegionWithContext() {
->willReturnSelf();
$block->expects($this->once())
->method('access')
->willReturn(TRUE);
->willReturn(AccessResult::allowed()->addCacheTags(['config:block.block.block_id']));
$block->expects($this->once())
->method('getRegion')
->willReturn('top');
......@@ -163,7 +164,8 @@ public function testGetVisibleBlocksPerRegionWithContext() {
->with(['theme' => $this->theme])
->willReturn($blocks);
$result = [];
foreach ($this->blockRepository->getVisibleBlocksPerRegion($contexts) as $region => $resulting_blocks) {
$cacheable_metadata = [];
foreach ($this->blockRepository->getVisibleBlocksPerRegion($contexts, $cacheable_metadata) as $region => $resulting_blocks) {
$result[$region] = [];
foreach ($resulting_blocks as $plugin_id => $block) {
$result[$region][] = $plugin_id;
......@@ -177,6 +179,10 @@ public function testGetVisibleBlocksPerRegionWithContext() {
'bottom' => [],
];
$this->assertSame($expected, $result);
// Assert that the cacheable metadata from the block access results was
// collected.
$this->assertEquals(['config:block.block.block_id'], $cacheable_metadata['top']->getCacheTags());
}
}
......
......@@ -7,6 +7,8 @@
namespace Drupal\Tests\block\Unit\Plugin\DisplayVariant;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\DependencyInjection\Container;
use Drupal\Tests\UnitTestCase;
/**
......@@ -55,6 +57,18 @@ class BlockPageVariantTest extends UnitTestCase {
* A mocked display variant plugin.
*/
public function setUpDisplayVariant($configuration = array(), $definition = array()) {
$container = new Container();
$cache_context_manager = $this->getMockBuilder('Drupal\Core\Cache\CacheContextsManager')
->disableOriginalConstructor()
->getMock();
$container->set('cache_contexts_manager', $cache_context_manager);
$cache_context_manager->expects($this->any())
->method('validateTokens')
->with([])
->willReturn([]);
\Drupal::setContainer($container);
$this->blockRepository = $this->getMock('Drupal\block\BlockRepositoryInterface');
$this->blockViewBuilder = $this->getMock('Drupal\Core\Entity\EntityViewBuilderInterface');
$this->dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
......@@ -96,7 +110,10 @@ public function providerBuild() {
'#cache' => [
'tags' => [
'config:block_list',
'route',
],
'contexts' => [],
'max-age' => -1,
],
'top' => [
'block1' => [],
......@@ -121,7 +138,10 @@ public function providerBuild() {
'#cache' => [
'tags' => [
'config:block_list',
'route',
],
'contexts' => [],
'max-age' => -1,
],
'top' => [
'block1' => [],
......@@ -152,7 +172,10 @@ public function providerBuild() {
'#cache' => [
'tags' => [
'config:block_list',
'route',
],
'contexts' => [],
'max-age' => -1,
],
'top' => [
'block1' => [],
......@@ -205,7 +228,10 @@ public function testBuild(array $blocks_config, $visible_block_count, array $exp
->will($this->returnValue(array()));
$this->blockRepository->expects($this->once())
->method('getVisibleBlocksPerRegion')
->will($this->returnValue($blocks));
->willReturnCallback(function ($contexts, &$cacheable_metadata) use ($blocks) {
$cacheable_metadata['top'] = (new CacheableMetadata())->addCacheTags(['route']);
return $blocks;
});
$this->assertSame($expected_render_array, $display_variant->build());
}
......@@ -226,6 +252,8 @@ public function testBuildWithoutMainContent() {
'tags' => [
'config:block_list',
],
'contexts' => [],
'max-age' => -1,
],
'content' => [
'system_main' => [],
......
......@@ -8,6 +8,7 @@
namespace Drupal\node\Tests;
use Drupal\block\Entity\Block;
use Drupal\system\Tests\Cache\AssertPageCacheContextsAndTagsTrait;
use Drupal\user\RoleInterface;
/**
......@@ -17,6 +18,8 @@
*/
class NodeBlockFunctionalTest extends NodeTestBase {
use AssertPageCacheContextsAndTagsTrait;
/**
* An administrative user for testing.
*
......@@ -122,6 +125,8 @@ public function testRecentNodeBlock() {
$this->assertText($node3->label(), 'Node found in block.');
$this->assertText($node4->label(), 'Node found in block.');
$this->assertCacheContexts(['languages:language_content', 'languages:language_interface', 'theme', 'user']);
// Enable the "Powered by Drupal" block only on article nodes.
$edit = [
'id' => strtolower($this->randomMachineName()),
......@@ -145,12 +150,16 @@ public function testRecentNodeBlock() {
$this->drupalGet('');
$label = $block->label();
$this->assertNoText($label, 'Block was not displayed on the front page.');
$this->assertCacheContexts(['languages:language_content', 'languages:language_interface', 'theme', 'user', 'route']);
$this->drupalGet('node/add/article');
$this->assertText($label, 'Block was displayed on the node/add/article page.');
$this->assertCacheContexts(['languages:language_content', 'languages:language_interface', 'theme', 'user', 'route']);