From 9309d3d9f549ad09f16de6fe1130f34d55a8e2c4 Mon Sep 17 00:00:00 2001 From: catch <git config --global user.email catch@35733.no-reply.drupal.org> Date: Thu, 12 Mar 2015 10:16:15 +0000 Subject: [PATCH] Issue #2381217 by Wim Leers, dawehner, Fabianx: Views should set cache tags on its render arrays, and bubble the output's cache tags to the cache items written to the Views output cache --- core/lib/Drupal/Core/Render/Renderer.php | 9 +- .../node/src/Tests/Views/FrontPageTest.php | 178 ++++++++++++++- .../AssertPageCacheContextsAndTagsTrait.php | 93 ++++++++ .../Cache/PageCacheTagsIntegrationTest.php | 51 +---- .../Plugin/views/cache/CachePluginBase.php | 22 +- .../views/display/DisplayPluginBase.php | 9 + .../Plugin/views/query/QueryPluginBase.php | 7 + .../views/src/Plugin/views/query/Sql.php | 18 ++ .../src/Tests/AssertViewsCacheTagsTrait.php | 87 ++++++++ core/modules/views/src/Tests/GlossaryTest.php | 18 +- .../views/src/Tests/Plugin/CacheTest.php | 45 +++- .../src/Tests/RenderCacheIntegrationTest.php | 203 ++++++++++++++++++ core/modules/views/src/ViewExecutable.php | 18 ++ .../views.view.entity_test_fields.yml | 74 +++++++ .../test_views/views.view.entity_test_row.yml | 41 ++++ .../test_views/views.view.test_cache.yml | 12 ++ .../Core/Render/RendererBubblingTest.php | 35 ++- .../Render/RendererPostRenderCacheTest.php | 16 +- .../Drupal/Tests/Core/Render/RendererTest.php | 6 +- .../Tests/Core/Render/RendererTestBase.php | 2 + 20 files changed, 849 insertions(+), 95 deletions(-) create mode 100644 core/modules/system/src/Tests/Cache/AssertPageCacheContextsAndTagsTrait.php create mode 100644 core/modules/views/src/Tests/AssertViewsCacheTagsTrait.php create mode 100644 core/modules/views/src/Tests/RenderCacheIntegrationTest.php create mode 100644 core/modules/views/tests/modules/views_test_config/test_views/views.view.entity_test_fields.yml create mode 100644 core/modules/views/tests/modules/views_test_config/test_views/views.view.entity_test_row.yml diff --git a/core/lib/Drupal/Core/Render/Renderer.php b/core/lib/Drupal/Core/Render/Renderer.php index 189c2debf84d..6293e8fd7e54 100644 --- a/core/lib/Drupal/Core/Render/Renderer.php +++ b/core/lib/Drupal/Core/Render/Renderer.php @@ -544,11 +544,6 @@ protected function cacheSet(array &$elements, $pre_bubbling_cid) { $data = $this->getCacheableRenderArray($elements); - // Cache tags are cached, but we also want to associate the "rendered" cache - // tag. This allows us to invalidate the entire render cache, regardless of - // the cache bin. - $data['#cache']['tags'][] = 'rendered'; - $bin = isset($elements['#cache']['bin']) ? $elements['#cache']['bin'] : 'render'; $expire = isset($elements['#cache']['expire']) ? $elements['#cache']['expire'] : Cache::PERMANENT; $cache = $this->cacheFactory->get($bin); @@ -690,7 +685,7 @@ protected function cacheSet(array &$elements, $pre_bubbling_cid) { 'tags' => Cache::mergeTags($stored_cache_tags, $data['#cache']['tags']), ], ]; - $cache->set($pre_bubbling_cid, $redirect_data, $expire, $redirect_data['#cache']['tags']); + $cache->set($pre_bubbling_cid, $redirect_data, $expire, Cache::mergeTags($redirect_data['#cache']['tags'], ['rendered'])); } // Current cache contexts incomplete: this request only uses a subset of @@ -711,7 +706,7 @@ protected function cacheSet(array &$elements, $pre_bubbling_cid) { $data['#cache']['contexts'] = $merged_cache_contexts; } } - $cache->set($cid, $data, $expire, $data['#cache']['tags']); + $cache->set($cid, $data, $expire, Cache::mergeTags($data['#cache']['tags'], ['rendered'])); } /** diff --git a/core/modules/node/src/Tests/Views/FrontPageTest.php b/core/modules/node/src/Tests/Views/FrontPageTest.php index eb970d6bd7c6..c5dee988e6e4 100644 --- a/core/modules/node/src/Tests/Views/FrontPageTest.php +++ b/core/modules/node/src/Tests/Views/FrontPageTest.php @@ -7,6 +7,11 @@ namespace Drupal\node\Tests\Views; +use Drupal\Core\Cache\Cache; +use Drupal\Core\Url; +use Drupal\node\Entity\Node; +use Drupal\system\Tests\Cache\AssertPageCacheContextsAndTagsTrait; +use Drupal\views\Tests\AssertViewsCacheTagsTrait; use Drupal\views\Tests\ViewTestBase; use Drupal\views\ViewExecutable; use Drupal\views\Views; @@ -18,6 +23,14 @@ */ class FrontPageTest extends ViewTestBase { + use AssertPageCacheContextsAndTagsTrait; + use AssertViewsCacheTagsTrait; + + /** + * {@inheritdoc} + */ + protected $dumpHeaders = TRUE; + /** * The entity storage for nodes. * @@ -35,7 +48,8 @@ class FrontPageTest extends ViewTestBase { protected function setUp() { parent::setUp(); - $this->nodeStorage = $this->container->get('entity.manager')->getStorage('node'); + $this->nodeStorage = $this->container->get('entity.manager') + ->getStorage('node'); } /** @@ -173,4 +187,166 @@ public function testAdminFrontPage() { $this->assertPattern('/class=".+view-frontpage/', 'Frontpage view was rendered'); } + /** + * Tests the cache tags when using the "none" cache plugin. + */ + public function testCacheTagsWithCachePluginNone() { + $this->enablePageCaching(); + $this->assertFrontPageViewCacheTags(FALSE); + } + + /** + * Tests the cache tags when using the "tag" cache plugin. + */ + public function testCacheTagsWithCachePluginTag() { + $this->enablePageCaching(); + + $view = Views::getView('frontpage'); + $view->setDisplay('page_1'); + $view->display_handler->overrideOption('cache', [ + 'type' => 'tag', + ]); + $view->save(); + + $this->assertFrontPageViewCacheTags(TRUE); + } + + /** + * Tests the cache tags when using the "time" cache plugin. + */ + public function testCacheTagsWithCachePluginTime() { + $this->enablePageCaching(); + + $view = Views::getView('frontpage'); + $view->setDisplay('page_1'); + $view->display_handler->overrideOption('cache', [ + 'type' => 'time', + 'options' => [ + 'results_lifespan' => 3600, + 'output_lifespan' => 3600, + ], + ]); + $view->save(); + + $this->assertFrontPageViewCacheTags(TRUE); + } + + /** + * Tests the cache tags on the front page. + * + * @param bool $do_assert_views_caches + * Whether to check Views' result & output caches. + */ + protected function assertFrontPageViewCacheTags($do_assert_views_caches) { + $view = Views::getView('frontpage'); + $view->setDisplay('page_1'); + + $cache_contexts = []; + + // Test before there are any nodes. + $empty_node_listing_cache_tags = [ + 'config:views.view.frontpage', + 'node_list', + ]; + $this->assertViewsCacheTags( + $view, + $empty_node_listing_cache_tags, + $do_assert_views_caches, + $empty_node_listing_cache_tags + ); + $this->assertPageCacheContextsAndTags( + Url::fromRoute('view.frontpage.page_1'), + $cache_contexts, + Cache::mergeTags($empty_node_listing_cache_tags, ['rendered']) + ); + + // Create some nodes on the frontpage view. Add more than 10 nodes in order + // to enable paging. + $this->drupalCreateContentType(['type' => 'article']); + for ($i = 0; $i < 15; $i++) { + $node = Node::create([ + 'body' => [ + [ + 'value' => $this->randomMachineName(32), + 'format' => filter_default_format(), + ] + ], + 'type' => 'article', + 'created' => $i, + 'title' => $this->randomMachineName(8), + 'nid' => $i + 1, + ]); + $node->enforceIsNew(TRUE); + $node->save(); + } + $cache_contexts = Cache::mergeContexts($cache_contexts, [ + 'theme', + 'timezone', + 'user.roles' + ]); + + // First page. + $first_page_result_cache_tags = [ + 'config:views.view.frontpage', + 'node_list', + 'node:6', + 'node:7', + 'node:8', + 'node:9', + 'node:10', + 'node:11', + 'node:12', + 'node:13', + 'node:14', + 'node:15', + ]; + $first_page_output_cache_tags = Cache::mergeTags($first_page_result_cache_tags, [ + 'config:filter.format.plain_text', + 'node_view', + 'user_view', + 'user:0', + ]); + $view->setDisplay('page_1'); + $view->setCurrentPage(0); + $this->assertViewsCacheTags( + $view, + $first_page_result_cache_tags, + $do_assert_views_caches, + $first_page_output_cache_tags + ); + $this->assertPageCacheContextsAndTags( + Url::fromRoute('view.frontpage.page_1'), + $cache_contexts, + Cache::mergeTags($first_page_output_cache_tags, ['rendered']) + ); + + // Second page. + $this->assertPageCacheContextsAndTags(Url::fromRoute('view.frontpage.page_1', [], ['query' => ['page' => 1]]), $cache_contexts, [ + // The cache tags for the listed nodes. + 'node:1', + 'node:2', + 'node:3', + 'node:4', + 'node:5', + // The rest. + 'config:filter.format.plain_text', + 'config:views.view.frontpage', + 'node_list', + 'node_view', + 'user_view', + 'user:0', + 'rendered', + ]); + + // Let's update a node title on the first page and ensure that the page + // cache entry invalidates. + $node = Node::load(10); + $title = $node->getTitle() . 'a'; + $node->setTitle($title); + $node->save(); + + $this->drupalGet(Url::fromRoute('view.frontpage.page_1')); + $this->assertText($title); + } + } diff --git a/core/modules/system/src/Tests/Cache/AssertPageCacheContextsAndTagsTrait.php b/core/modules/system/src/Tests/Cache/AssertPageCacheContextsAndTagsTrait.php new file mode 100644 index 000000000000..15234216f836 --- /dev/null +++ b/core/modules/system/src/Tests/Cache/AssertPageCacheContextsAndTagsTrait.php @@ -0,0 +1,93 @@ +<?php + +/** + * @file + * Contains \Drupal\system\Tests\Cache\AssertPageCacheContextsAndTagsTrait. + */ + +namespace Drupal\system\Tests\Cache; + +use Drupal\Core\Url; + +/** + * Provides test assertions for testing page-level cache contexts & tags. + * + * Can be used by test classes that extend \Drupal\simpletest\WebTestBase. + */ +trait AssertPageCacheContextsAndTagsTrait { + + /** + * Enables page caching. + */ + protected function enablePageCaching() { + $config = $this->config('system.performance'); + $config->set('cache.page.use_internal', 1); + $config->set('cache.page.max_age', 300); + $config->save(); + } + + /** + * Asserts page cache miss, then hit for the given URL; checks cache headers. + * + * @param \Drupal\Core\Url $url + * The URL to test. + * @param string[] $expected_contexts + * The expected cache contexts for the given URL. + * @param string[] $expected_tags + * The expected cache tags for the given URL. + */ + protected function assertPageCacheContextsAndTags(Url $url, array $expected_contexts, array $expected_tags) { + $absolute_url = $url->setAbsolute()->toString(); + sort($expected_contexts); + sort($expected_tags); + + $get_cache_header_values = function ($header_name) { + $header_value = $this->drupalGetHeader($header_name); + if (empty($header_value)) { + return []; + } + else { + return explode(' ', $header_value); + } + }; + + // Assert cache miss + expected cache contexts + tags. + $this->drupalGet($absolute_url); + $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS'); + $actual_contexts = $get_cache_header_values('X-Drupal-Cache-Contexts'); + $actual_tags = $get_cache_header_values('X-Drupal-Cache-Tags'); + $this->assertIdentical($actual_contexts, $expected_contexts); + if ($actual_contexts !== $expected_contexts) { + debug(array_diff($actual_contexts, $expected_contexts)); + } + $this->assertIdentical($actual_tags, $expected_tags); + if ($actual_tags !== $expected_tags) { + debug(array_diff($actual_tags, $expected_tags)); + } + + // Assert cache hit + expected cache contexts + tags. + $this->drupalGet($absolute_url); + $actual_contexts = $get_cache_header_values('X-Drupal-Cache-Contexts'); + $actual_tags = $get_cache_header_values('X-Drupal-Cache-Tags'); + $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT'); + $this->assertIdentical($actual_contexts, $expected_contexts); + if ($actual_contexts !== $expected_contexts) { + debug(array_diff($actual_contexts, $expected_contexts)); + } + $this->assertIdentical($actual_tags, $expected_tags); + if ($actual_tags !== $expected_tags) { + debug(array_diff($actual_tags, $expected_tags)); + } + + // Assert page cache item + expected cache tags. + $cid_parts = array($url->setAbsolute()->toString(), 'html'); + $cid = implode(':', $cid_parts); + $cache_entry = \Drupal::cache('render')->get($cid); + sort($cache_entry->tags); + $this->assertEqual($cache_entry->tags, $expected_tags); + if ($cache_entry->tags !== $expected_tags) { + debug(array_diff($cache_entry->tags, $expected_tags)); + } + } + +} diff --git a/core/modules/system/src/Tests/Cache/PageCacheTagsIntegrationTest.php b/core/modules/system/src/Tests/Cache/PageCacheTagsIntegrationTest.php index b56a6ba9e92a..da74be826d68 100644 --- a/core/modules/system/src/Tests/Cache/PageCacheTagsIntegrationTest.php +++ b/core/modules/system/src/Tests/Cache/PageCacheTagsIntegrationTest.php @@ -7,9 +7,7 @@ namespace Drupal\system\Tests\Cache; -use Drupal\Core\Url; use Drupal\simpletest\WebTestBase; -use Drupal\Core\Cache\Cache; /** * Enables the page cache and tests its cache tags in various scenarios. @@ -21,6 +19,8 @@ */ class PageCacheTagsIntegrationTest extends WebTestBase { + use AssertPageCacheContextsAndTagsTrait; + protected $profile = 'standard'; protected $dumpHeaders = TRUE; @@ -31,10 +31,7 @@ class PageCacheTagsIntegrationTest extends WebTestBase { protected function setUp() { parent::setUp(); - $config = $this->config('system.performance'); - $config->set('cache.page.use_internal', 1); - $config->set('cache.page.max_age', 300); - $config->save(); + $this->enablePageCaching(); } /** @@ -128,46 +125,10 @@ function testPageCacheTags() { 'config:system.menu.footer', 'config:system.menu.main', 'config:system.site', + 'comment_list', + 'node_list', + 'config:views.view.comments_recent', )); } - /** - * Asserts page cache miss, then hit for the given URL; checks cache headers. - * - * @param \Drupal\Core\Url $url - * The URL to test. - * @param string[] $expected_contexts - * The expected cache contexts for the given URL. - * @param string[] $expected_tags - * The expected cache tags for the given URL. - */ - protected function assertPageCacheContextsAndTags(Url $url, array $expected_contexts, array $expected_tags) { - $absolute_url = $url->setAbsolute()->toString(); - sort($expected_contexts); - sort($expected_tags); - - // Assert cache miss + expected cache contexts + tags. - $this->drupalGet($absolute_url); - $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS'); - $actual_contexts = explode(' ', $this->drupalGetHeader('X-Drupal-Cache-Contexts')); - $actual_tags = explode(' ', $this->drupalGetHeader('X-Drupal-Cache-Tags')); - $this->assertIdentical($actual_contexts, $expected_contexts); - $this->assertIdentical($actual_tags, $expected_tags); - - // Assert cache hit + expected cache contexts + tags. - $this->drupalGet($absolute_url); - $actual_contexts = explode(' ', $this->drupalGetHeader('X-Drupal-Cache-Contexts')); - $actual_tags = explode(' ', $this->drupalGetHeader('X-Drupal-Cache-Tags')); - $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT'); - $this->assertIdentical($actual_contexts, $expected_contexts); - $this->assertIdentical($actual_tags, $expected_tags); - - // Assert page cache item + expected cache tags. - $cid_parts = array($url->setAbsolute()->toString(), 'html'); - $cid = implode(':', $cid_parts); - $cache_entry = \Drupal::cache('render')->get($cid); - sort($cache_entry->tags); - $this->assertEqual($cache_entry->tags, $expected_tags); - } - } diff --git a/core/modules/views/src/Plugin/views/cache/CachePluginBase.php b/core/modules/views/src/Plugin/views/cache/CachePluginBase.php index f66d3e6f2282..b14c49605803 100644 --- a/core/modules/views/src/Plugin/views/cache/CachePluginBase.php +++ b/core/modules/views/src/Plugin/views/cache/CachePluginBase.php @@ -191,7 +191,7 @@ public function cacheSet($type) { // that is used to render the view for this request and rendering does // not happen twice. $this->storage = $this->view->display_handler->output = $this->renderer->getCacheableRenderArray($output); - \Drupal::cache($this->outputBin)->set($this->generateOutputKey(), $this->storage, $this->cacheSetExpire($type), $this->getCacheTags()); + \Drupal::cache($this->outputBin)->set($this->generateOutputKey(), $this->storage, $this->cacheSetExpire($type), Cache::mergeTags($this->storage['#cache']['tags'], ['rendered'])); break; } } @@ -239,9 +239,6 @@ public function cacheGet($type) { /** * Clear out cached data for a view. - * - * We're just going to nuke anything related to the view, regardless of display, - * to be sure that we catch everything. Maybe that's a bad idea. */ public function cacheFlush() { Cache::invalidateTags($this->view->storage->getCacheTags()); @@ -301,12 +298,18 @@ public function generateResultsKey() { 'langcode' => \Drupal::languageManager()->getCurrentLanguage()->getId(), 'base_url' => $GLOBALS['base_url'], ); - foreach (array('exposed_info', 'page', 'sort', 'order', 'items_per_page', 'offset') as $key) { + foreach (array('exposed_info', 'sort', 'order') as $key) { if ($this->view->getRequest()->query->has($key)) { $key_data[$key] = $this->view->getRequest()->query->get($key); } } + $key_data['pager'] = [ + 'page' => $this->view->getCurrentPage(), + 'items_per_page' => $this->view->getItemsPerPage(), + 'offset' => $this->view->getOffset(), + ]; + $this->resultsKey = $this->view->storage->id() . ':' . $this->displayHandler->display['id'] . ':results:' . hash('sha256', serialize($key_data)); } @@ -343,18 +346,21 @@ public function generateOutputKey() { * @return string[] * An array of cache tags based on the current view. */ - protected function getCacheTags() { + public function getCacheTags() { $tags = $this->view->storage->getCacheTags(); + // The list cache tags for the entity types listed in this view. $entity_information = $this->view->query->getEntityTableInfo(); if (!empty($entity_information)) { // Add the list cache tags for each entity type used by this view. - foreach (array_keys($entity_information) as $entity_type) { - $tags = Cache::mergeTags($tags, \Drupal::entityManager()->getDefinition($entity_type)->getListCacheTags()); + foreach ($entity_information as $table => $metadata) { + $tags = Cache::mergeTags($tags, \Drupal::entityManager()->getDefinition($metadata['entity_type'])->getListCacheTags()); } } + $tags = Cache::mergeTags($tags, $this->view->getQuery()->getCacheTags()); + return $tags; } diff --git a/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php index 4f1d3b3c3212..7cd1041a14b4 100644 --- a/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php +++ b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php @@ -2126,6 +2126,15 @@ public function render() { '#post_render_cache' => &$this->view->element['#post_render_cache'], ); + if (!isset($element['#cache'])) { + $element['#cache'] = []; + } + $element['#cache'] += ['tags' => []]; + + // If the output is a render array, add cache tags, regardless of whether + // caching is enabled or not; cache tags must always be set. + $element['#cache']['tags'] = Cache::mergeTags($element['#cache']['tags'], $this->view->getCacheTags()); + return $element; } diff --git a/core/modules/views/src/Plugin/views/query/QueryPluginBase.php b/core/modules/views/src/Plugin/views/query/QueryPluginBase.php index 1c254dc47810..a206d03225f5 100644 --- a/core/modules/views/src/Plugin/views/query/QueryPluginBase.php +++ b/core/modules/views/src/Plugin/views/query/QueryPluginBase.php @@ -312,6 +312,13 @@ public function getEntityTableInfo() { return $entity_tables; } + /** + * {@inheritdoc} + */ + public function getCacheTags() { + return []; + } + } /** diff --git a/core/modules/views/src/Plugin/views/query/Sql.php b/core/modules/views/src/Plugin/views/query/Sql.php index 47d19799a413..1be46f982e14 100644 --- a/core/modules/views/src/Plugin/views/query/Sql.php +++ b/core/modules/views/src/Plugin/views/query/Sql.php @@ -8,6 +8,7 @@ namespace Drupal\views\Plugin\views\query; use Drupal\Component\Utility\NestedArray; +use Drupal\Core\Cache\Cache; use Drupal\Core\Database\Database; use Drupal\Core\Form\FormStateInterface; use Drupal\views\Plugin\views\display\DisplayPluginBase; @@ -1537,6 +1538,23 @@ function loadEntities(&$results) { } } + /** + * {@inheritdoc} + */ + public function getCacheTags() { + $tags = []; + // Add cache tags for each row, if there is an entity associated with it. + if (!$this->hasAggregate) { + foreach ($this->view->result as $row) { + if ($row->_entity) { + $tags = Cache::mergeTags($row->_entity->getCacheTags(), $tags); + } + } + } + + return $tags; + } + public function addSignature(ViewExecutable $view) { $view->query->addField(NULL, "'" . $view->storage->id() . ':' . $view->current_display . "'", 'view_name'); } diff --git a/core/modules/views/src/Tests/AssertViewsCacheTagsTrait.php b/core/modules/views/src/Tests/AssertViewsCacheTagsTrait.php new file mode 100644 index 000000000000..1617175f0339 --- /dev/null +++ b/core/modules/views/src/Tests/AssertViewsCacheTagsTrait.php @@ -0,0 +1,87 @@ +<?php + +/** + * @file + * Contains \Drupal\views\Tests\AssertViewsCacheTagsTrait. + */ + +namespace Drupal\views\Tests; + +use Drupal\Core\Cache\Cache; +use Drupal\views\ViewExecutable; +use Symfony\Component\HttpFoundation\Request; + +trait AssertViewsCacheTagsTrait { + + + /** + * Asserts a view's result & output cache items' cache tags. + * + * @param \Drupal\views\ViewExecutable $view + * The view to test, must have caching enabled. + * @param null|string[] $expected_results_cache + * NULL when expecting no results cache item, a set of cache tags expected + * to be set on the results cache item otherwise. + * @param bool $views_caching_is_enabled + * Whether to expect an output cache item. If TRUE, the cache tags must + * match those in $expected_render_array_cache_tags. + * @param string[] $expected_render_array_cache_tags + * A set of cache tags expected to be set on the built view's render array. + * + * @return array + * The render array + */ + protected function assertViewsCacheTags(ViewExecutable $view, $expected_results_cache, $views_caching_is_enabled, array $expected_render_array_cache_tags) { + $build = $view->preview(); + + // Ensure the current request is a GET request so that render caching is + // active for direct rendering of views, just like for actual requests. + /** @var \Symfony\Component\HttpFoundation\RequestStack $request_stack */ + $request_stack = \Drupal::service('request_stack'); + $request_stack->push(new Request()); + \Drupal::service('renderer')->renderRoot($build); + $request_stack->pop(); + + // Render array cache tags. + $this->pass('Checking render array cache tags.'); + sort($expected_render_array_cache_tags); + $this->assertEqual($build['#cache']['tags'], $expected_render_array_cache_tags); + + if ($views_caching_is_enabled) { + $this->pass('Checking Views results cache item cache tags.'); + /** @var \Drupal\views\Plugin\views\cache\CachePluginBase $cache_plugin */ + $cache_plugin = $view->display_handler->getPlugin('cache'); + + // Results cache. + $results_cache_item = \Drupal::cache('data')->get($cache_plugin->generateResultsKey()); + if (is_array($expected_results_cache)) { + $this->assertTrue($results_cache_item, 'Results cache item found.'); + if ($results_cache_item) { + sort($expected_results_cache); + $this->assertEqual($results_cache_item->tags, $expected_results_cache); + } + } + else { + $this->assertFalse($results_cache_item, 'Results cache item not found.'); + } + + // Output cache. + $this->pass('Checking Views output cache item cache tags.'); + $output_cache_item = \Drupal::cache('render')->get($cache_plugin->generateOutputKey()); + if ($views_caching_is_enabled === TRUE) { + $this->assertTrue($output_cache_item, 'Output cache item found.'); + if ($output_cache_item) { + $this->assertEqual($output_cache_item->tags, Cache::mergeTags($expected_render_array_cache_tags, ['rendered'])); + } + } + else { + $this->assertFalse($output_cache_item, 'Output cache item not found.'); + } + } + + $view->destroy(); + + return $build; + } + +} diff --git a/core/modules/views/src/Tests/GlossaryTest.php b/core/modules/views/src/Tests/GlossaryTest.php index c9cb84a1a961..553400970170 100644 --- a/core/modules/views/src/Tests/GlossaryTest.php +++ b/core/modules/views/src/Tests/GlossaryTest.php @@ -9,6 +9,7 @@ use Drupal\Component\Utility\Unicode; use Drupal\Core\Url; +use Drupal\system\Tests\Cache\AssertPageCacheContextsAndTagsTrait; use Drupal\views\Views; /** @@ -18,6 +19,9 @@ */ class GlossaryTest extends ViewTestBase { + use AssertPageCacheContextsAndTagsTrait; + use AssertViewsCacheTagsTrait; + /** * Modules to enable. * @@ -39,6 +43,7 @@ public function testGlossaryView() { 'a' => 3, 'l' => 6, ); + $nodes_by_char = []; foreach ($nodes_per_char as $char => $count) { $setting = array( 'type' => $type->id() @@ -46,7 +51,8 @@ public function testGlossaryView() { for ($i = 0; $i < $count; $i++) { $node = $setting; $node['title'] = $char . $this->randomString(3); - $this->drupalCreateNode($node); + $node = $this->drupalCreateNode($node); + $nodes_by_char[$char][] = $node; } } @@ -77,6 +83,16 @@ public function testGlossaryView() { $result_count = trim(str_replace(array('|', '(', ')'), '', (string) $result[0])); $this->assertEqual($result_count, $count, 'The expected number got rendered.'); } + + // Verify cache tags. + $this->enablePageCaching(); + $this->assertPageCacheContextsAndTags(Url::fromRoute('view.glossary.page_1'), [], [ + 'config:views.view.glossary', + 'node:' . $nodes_by_char['a'][0]->id(), 'node:' . $nodes_by_char['a'][1]->id(), 'node:' . $nodes_by_char['a'][2]->id(), + 'node_list', + 'user_list', + 'rendered', + ]); } } diff --git a/core/modules/views/src/Tests/Plugin/CacheTest.php b/core/modules/views/src/Tests/Plugin/CacheTest.php index 913aab21eac6..d730abf89e59 100644 --- a/core/modules/views/src/Tests/Plugin/CacheTest.php +++ b/core/modules/views/src/Tests/Plugin/CacheTest.php @@ -44,7 +44,6 @@ protected function setUp() { * @see views_plugin_cache_time */ public function testTimeResultCaching() { - // Create a basic result which just 2 results. $view = Views::getView('test_cache'); $view->setDisplay(); $view->display_handler->overrideOption('cache', array( @@ -55,6 +54,7 @@ public function testTimeResultCaching() { ) )); + // Test the default (non-paged) display. $this->executeView($view); // Verify the result. $this->assertEqual(5, count($view->result), 'The number of returned rows match.'); @@ -67,7 +67,19 @@ public function testTimeResultCaching() { ); db_insert('views_test_data')->fields($record)->execute(); - // The Result should be the same as before, because of the caching. + // The result should be the same as before, because of the caching. (Note + // that views_test_data records don't have associated cache tags, and hence + // the results cache items aren't invalidated.) + $view->destroy(); + $this->executeView($view); + // Verify the result. + $this->assertEqual(5, count($view->result), 'The number of returned rows match.'); + } + + /** + * Tests result caching with a pager. + */ + public function testTimeResultCachingWithPager() { $view = Views::getView('test_cache'); $view->setDisplay(); $view->display_handler->overrideOption('cache', array( @@ -78,9 +90,31 @@ public function testTimeResultCaching() { ) )); + $mapping = ['views_test_data_name' => 'name']; + + $view->setDisplay('page_1'); + $view->setCurrentPage(0); $this->executeView($view); - // Verify the result. - $this->assertEqual(5, count($view->result), 'The number of returned rows match.'); + $this->assertIdenticalResultset($view, [['name' => 'John'], ['name' => 'George']], $mapping); + $view->destroy(); + + $view->setDisplay('page_1'); + $view->setCurrentPage(1); + $this->executeView($view); + $this->assertIdenticalResultset($view, [['name' => 'Ringo'], ['name' => 'Paul']], $mapping); + $view->destroy(); + + $view->setDisplay('page_1'); + $view->setCurrentPage(0); + $this->executeView($view); + $this->assertIdenticalResultset($view, [['name' => 'John'], ['name' => 'George']], $mapping); + $view->destroy(); + + $view->setDisplay('page_1'); + $view->setCurrentPage(2); + $this->executeView($view); + $this->assertIdenticalResultset($view, [['name' => 'Meredith']], $mapping); + $view->destroy(); } /** @@ -149,7 +183,8 @@ function testHeaderStorage() { drupal_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.'); - $this->assertEqual(['views_test_data:1'], $output['#cache']['tags']); + // Note: views_test_data_views_pre_render() adds some cache tags. + $this->assertEqual(['config:views.view.test_cache_header_storage', 'views_test_data:1'], $output['#cache']['tags']); $this->assertEqual(['views_test_data_post_render_cache' => [['foo' => 'bar']]], $output['#post_render_cache']); $this->assertFalse(!empty($view->build_info['pre_render_called']), 'Make sure hook_views_pre_render is not called for the cached view.'); } diff --git a/core/modules/views/src/Tests/RenderCacheIntegrationTest.php b/core/modules/views/src/Tests/RenderCacheIntegrationTest.php new file mode 100644 index 000000000000..747e170b51c5 --- /dev/null +++ b/core/modules/views/src/Tests/RenderCacheIntegrationTest.php @@ -0,0 +1,203 @@ +<?php + +/** + * @file + * Contains \Drupal\views\Tests\RenderCacheIntegrationTest. + */ + +namespace Drupal\views\Tests; + +use Drupal\Core\Cache\Cache; +use Drupal\entity_test\Entity\EntityTest; +use Drupal\views\Views; + +/** + * Tests the general integration between Views and the render cache. + * + * @group views + */ +class RenderCacheIntegrationTest extends ViewUnitTestBase { + + use AssertViewsCacheTagsTrait; + + /** + * {@inheritdoc} + */ + public static $testViews = ['entity_test_fields', 'entity_test_row']; + + /** + * {@inheritdoc} + */ + public static $modules = ['entity_test', 'user']; + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + + $this->installEntitySchema('entity_test'); + $this->installEntitySchema('user'); + } + + /** + * Tests a field-based view's cache tags when using the "none" cache plugin. + */ + public function testFieldBasedViewCacheTagsWithCachePluginNone() { + $this->assertCacheTagsForFieldBasedView(FALSE); + } + + /** + * Tests a field-based view's cache tags when using the "tag" cache plugin. + */ + public function testFieldBasedViewCacheTagsWithCachePluginTag() { + $view = Views::getview('entity_test_fields'); + $view->getDisplay()->overrideOption('cache', [ + 'type' => 'tag', + ]); + $view->save(); + + $this->assertCacheTagsForFieldBasedView(TRUE); + } + + /** + * Tests a field-based view's cache tags when using the "time" cache plugin. + */ + public function testFieldBasedViewCacheTagsWithCachePluginTime() { + $view = Views::getview('entity_test_fields'); + $view->getDisplay()->overrideOption('cache', [ + 'type' => 'time', + 'options' => [ + 'results_lifespan' => 3600, + 'output_lifespan' => 3600, + ], + ]); + $view->save(); + + $this->assertCacheTagsForFieldBasedView(TRUE); + } + + /** + * Tests cache tags on output & result cache items for a field-based view. + * + * @param bool $do_assert_views_caches + * Whether to check Views' result & output caches. + */ + protected function assertCacheTagsForFieldBasedView($do_assert_views_caches) { + $this->pass('Checking cache tags for field-based view.'); + $view = Views::getview('entity_test_fields'); + + // Empty result (no entities yet). + $base_tags = ['config:views.view.entity_test_fields', 'entity_test_list']; + $this->assertViewsCacheTags($view, $base_tags, $do_assert_views_caches, $base_tags); + + + // Non-empty result (1 entity). + $entities[] = $entity = EntityTest::create(); + $entity->save(); + + $tags_with_entity = Cache::mergeTags($base_tags, $entities[0]->getCacheTags()); + $this->assertViewsCacheTags($view, $tags_with_entity, $do_assert_views_caches, $tags_with_entity); + + + // Paged result (more entities than the items-per-page limit). + for ($i = 0; $i < 5; $i++) { + $entities[] = $entity = EntityTest::create(); + $entity->save(); + } + // Page 1. + $tags_page_1 = Cache::mergeTags($base_tags, $entities[1]->getCacheTags(), $entities[2]->getCacheTags(), $entities[3]->getCacheTags(), $entities[4]->getCacheTags(), $entities[5]->getCacheTags()); + $this->assertViewsCacheTags($view, $tags_page_1, $do_assert_views_caches, $tags_page_1); + $view->destroy(); + // Page 2. + $view->setCurrentPage(1); + $tags_page_2 = Cache::mergeTags($base_tags, $entities[0]->getCacheTags()); + $this->assertViewsCacheTags($view, $tags_page_2, $do_assert_views_caches, $tags_page_2); + $view->destroy(); + + // Ensure that invalidation works on both pages. + $view->setCurrentPage(1); + $entities[0]->name->value = $random_name = $this->randomMachineName(); + $entities[0]->save(); + $build = $this->assertViewsCacheTags($view, $tags_page_2, $do_assert_views_caches, $tags_page_2); + $this->assertTrue(strpos($build['#markup'], $random_name) !== FALSE); + $view->destroy(); + + $view->setCurrentPage(0); + $entities[1]->name->value = $random_name = $this->randomMachineName(); + $entities[1]->save(); + $build = $this->assertViewsCacheTags($view, $tags_page_1, $do_assert_views_caches, $tags_page_1); + $this->assertTrue(strpos($build['#markup'], $random_name) !== FALSE); + } + + /** + * Tests a entity-based view's cache tags when using the "none" cache plugin. + */ + public function testEntityBasedViewCacheTagsWithCachePluginNone() { + $this->assertCacheTagsForEntityBasedView(FALSE); + } + + /** + * Tests a entity-based view's cache tags when using the "tag" cache plugin. + */ + public function testEntityBasedViewCacheTagsWithCachePluginTag() { + $view = Views::getview('entity_test_row'); + $view->getDisplay()->overrideOption('cache', [ + 'type' => 'tag', + ]); + $view->save(); + + $this->assertCacheTagsForEntityBasedView(TRUE); + } + + /** + * Tests a entity-based view's cache tags when using the "time" cache plugin. + */ + public function testEntityBasedViewCacheTagsWithCachePluginTime() { + $view = Views::getview('entity_test_row'); + $view->getDisplay()->overrideOption('cache', [ + 'type' => 'time', + 'options' => [ + 'results_lifespan' => 3600, + 'output_lifespan' => 3600, + ], + ]); + $view->save(); + + $this->assertCacheTagsForEntityBasedView(TRUE); + } + + /** + * Tests cache tags on output & result cache items for an entity-based view. + */ + protected function assertCacheTagsForEntityBasedView($do_assert_views_caches) { + $this->pass('Checking cache tags for entity-based view.'); + $view = Views::getview('entity_test_row'); + + // Empty result (no entities yet). + $base_tags = $base_render_tags = ['config:views.view.entity_test_row', 'entity_test_list']; + $this->assertViewsCacheTags($view, $base_tags, $do_assert_views_caches, $base_tags); + + + // Non-empty result (1 entity). + $entities[] = $entity = EntityTest::create(); + $entity->save(); + + $result_tags_with_entity = Cache::mergeTags($base_tags, $entities[0]->getCacheTags()); + $render_tags_with_entity = Cache::mergeTags($base_render_tags, $entities[0]->getCacheTags(), ['entity_test_view']); + $this->assertViewsCacheTags($view, $result_tags_with_entity, $do_assert_views_caches, $render_tags_with_entity); + + + // Paged result (more entities than the items-per-page limit). + for ($i = 0; $i < 5; $i++) { + $entities[] = $entity = EntityTest::create(); + $entity->save(); + } + + $new_entities_cache_tags = Cache::mergeTags($entities[1]->getCacheTags(), $entities[2]->getCacheTags(), $entities[3]->getCacheTags(), $entities[4]->getCacheTags(), $entities[5]->getCacheTags()); + $result_tags_page_1 = Cache::mergeTags($base_tags, $new_entities_cache_tags); + $render_tags_page_1 = Cache::mergeTags($base_render_tags, $new_entities_cache_tags, ['entity_test_view']); + $this->assertViewsCacheTags($view, $result_tags_page_1, $do_assert_views_caches, $render_tags_page_1); + } + +} diff --git a/core/modules/views/src/ViewExecutable.php b/core/modules/views/src/ViewExecutable.php index 0eb445d4eb65..6350f0d4e73d 100644 --- a/core/modules/views/src/ViewExecutable.php +++ b/core/modules/views/src/ViewExecutable.php @@ -8,6 +8,7 @@ namespace Drupal\views; use Drupal\Component\Utility\String; +use Drupal\Core\Cache\Cache; use Drupal\Core\DependencyInjection\DependencySerializationTrait; use Drupal\Core\Form\FormState; use Drupal\Core\Routing\RouteProviderInterface; @@ -1398,6 +1399,7 @@ public function render($display_id = NULL) { } $this->display_handler->output = $this->display_handler->render(); + if ($cache) { $cache->cacheSet('output'); } @@ -1423,6 +1425,22 @@ public function render($display_id = NULL) { return $this->display_handler->output; } + /** + * Gets the cache tags associated with the executed view. + * + * Note: The cache plugin controls the used tags, so you can override it, if + * needed. + * + * @return string[] + * An array of cache tags. + */ + public function getCacheTags() { + $this->initDisplay(); + /** @var \Drupal\views\Plugin\views\cache\CachePluginBase $cache */ + $cache = $this->display_handler->getPlugin('cache'); + return $cache->getCacheTags(); + } + /** * Builds the render array outline for the given display. * diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.entity_test_fields.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.entity_test_fields.yml new file mode 100644 index 000000000000..8966bb80c458 --- /dev/null +++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.entity_test_fields.yml @@ -0,0 +1,74 @@ +langcode: und +status: true +dependencies: { } +id: entity_test_fields +label: '' +module: views +description: '' +tag: '' +base_table: entity_test +base_field: nid +core: '8' +display: + default: + display_options: + access: + type: none + cache: + type: none + exposed_form: + type: basic + fields: + id: + alter: + alter_text: false + element_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + plugin_id: numeric + entity_type: entity_test + entity_field: id + id: id + table: entity_test + field: id + name: + alter: + alter_text: false + ellipsis: true + html: false + make_link: false + strip_tags: false + trim: false + word_boundary: true + empty_zero: false + field: name + hide_empty: false + id: name + table: entity_test + plugin_id: standard + entity_type: entity_test + entity_field: name + sorts: + id: + table: entity_test + id: id + field: id + plugin_id: standard + entity_type: entity_test + entity_field: id + order: desc + pager: + type: full + options: + items_per_page: 5 + style: + type: default + row: + type: fields + display_plugin: default + display_title: Master + id: default + position: 0 diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.entity_test_row.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.entity_test_row.yml new file mode 100644 index 000000000000..2384fe054766 --- /dev/null +++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.entity_test_row.yml @@ -0,0 +1,41 @@ +langcode: und +status: true +dependencies: { } +id: entity_test_row +label: '' +module: views +description: '' +tag: '' +base_table: entity_test +base_field: nid +core: '8' +display: + default: + display_options: + access: + type: none + cache: + type: none + exposed_form: + type: basic + sorts: + id: + table: entity_test + id: id + field: id + plugin_id: standard + entity_type: entity_test + entity_field: id + order: desc + pager: + type: full + options: + items_per_page: 5 + style: + type: default + row: + type: 'entity:entity_test' + display_plugin: default + display_title: Master + id: default + position: 0 diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_cache.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_cache.yml index 82908d8ca483..6dd5fc522f93 100644 --- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_cache.yml +++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_cache.yml @@ -42,3 +42,15 @@ display: table: views_test_data field: id relationship: none + + page_1: + display_plugin: page + id: page_1 + display_options: + defaults: + pager: false + pager: + type: full + options: + items_per_page: 2 + diff --git a/core/tests/Drupal/Tests/Core/Render/RendererBubblingTest.php b/core/tests/Drupal/Tests/Core/Render/RendererBubblingTest.php index b22ed7ead15a..929a307253f8 100644 --- a/core/tests/Drupal/Tests/Core/Render/RendererBubblingTest.php +++ b/core/tests/Drupal/Tests/Core/Render/RendererBubblingTest.php @@ -113,7 +113,7 @@ public function providerTestContextBubblingEdgeCases() { '#attached' => [], '#cache' => [ 'contexts' => ['foo'], - 'tags' => ['rendered'], + 'tags' => [], ], '#post_render_cache' => [], '#markup' => 'parent', @@ -141,7 +141,7 @@ public function providerTestContextBubblingEdgeCases() { '#attached' => [], '#cache' => [ 'contexts' => [], - 'tags' => ['rendered'], + 'tags' => [], ], '#post_render_cache' => [], '#markup' => 'parent', @@ -164,7 +164,7 @@ public function providerTestContextBubblingEdgeCases() { '#attached' => [], '#cache' => [ 'contexts' => [], - 'tags' => ['rendered'], + 'tags' => [], ], '#post_render_cache' => [], '#markup' => '', @@ -204,7 +204,7 @@ public function providerTestContextBubblingEdgeCases() { '#attached' => [], '#cache' => [ 'contexts' => ['bar', 'baz', 'foo'], - 'tags' => ['rendered'], + 'tags' => [], ], '#post_render_cache' => [], '#markup' => 'parent', @@ -243,17 +243,14 @@ public function providerTestContextBubblingEdgeCases() { // The keys + contexts this redirects to. 'keys' => ['parent'], 'contexts' => ['bar', 'foo'], - // The 'rendered' cache tag is also present for the redirecting cache - // item, to ensure it is considered to be part of the render cache - // and thus invalidated along with everything else. - 'tags' => ['dee', 'fiddle', 'har', 'rendered', 'yar'], + 'tags' => ['dee', 'fiddle', 'har', 'yar'], ], ], 'parent:bar:foo' => [ '#attached' => [], '#cache' => [ 'contexts' => ['bar', 'foo'], - 'tags' => ['dee', 'fiddle', 'har', 'yar', 'rendered'], + 'tags' => ['dee', 'fiddle', 'har', 'yar'], ], '#post_render_cache' => [], '#markup' => 'parent', @@ -334,14 +331,14 @@ public function testConditionalCacheContextBubblingSelfHealing() { '#cache' => [ 'keys' => ['parent'], 'contexts' => ['user.roles'], - 'tags' => ['a', 'b', 'rendered'], + 'tags' => ['a', 'b'], ], ]); $this->assertRenderCacheItem('parent:r.A', [ '#attached' => [], '#cache' => [ 'contexts' => ['user.roles'], - 'tags' => ['a', 'b', 'rendered'], + 'tags' => ['a', 'b'], ], '#post_render_cache' => [], '#markup' => 'parent', @@ -357,14 +354,14 @@ public function testConditionalCacheContextBubblingSelfHealing() { '#cache' => [ 'keys' => ['parent'], 'contexts' => ['foo', 'user.roles'], - 'tags' => ['a', 'b', 'c', 'rendered'], + 'tags' => ['a', 'b', 'c'], ], ]); $this->assertRenderCacheItem('parent:foo:r.B', [ '#attached' => [], '#cache' => [ 'contexts' => ['foo', 'user.roles'], - 'tags' => ['a', 'b', 'c', 'rendered'], + 'tags' => ['a', 'b', 'c'], ], '#post_render_cache' => [], '#markup' => 'parent', @@ -388,14 +385,14 @@ public function testConditionalCacheContextBubblingSelfHealing() { '#cache' => [ 'keys' => ['parent'], 'contexts' => ['foo', 'user.roles'], - 'tags' => ['a', 'b', 'c', 'rendered'], + 'tags' => ['a', 'b', 'c'], ], ]); $this->assertRenderCacheItem('parent:foo:r.A', [ '#attached' => [], '#cache' => [ 'contexts' => ['foo', 'user.roles'], - 'tags' => ['a', 'b', 'rendered'], + 'tags' => ['a', 'b'], ], '#post_render_cache' => [], '#markup' => 'parent', @@ -411,7 +408,7 @@ public function testConditionalCacheContextBubblingSelfHealing() { '#cache' => [ 'keys' => ['parent'], 'contexts' => ['bar', 'foo', 'user.roles'], - 'tags' => ['a', 'b', 'c', 'd', 'rendered'], + 'tags' => ['a', 'b', 'c', 'd'], ], ]; $this->assertRenderCacheItem('parent', $final_parent_cache_item); @@ -419,7 +416,7 @@ public function testConditionalCacheContextBubblingSelfHealing() { '#attached' => [], '#cache' => [ 'contexts' => ['bar', 'foo', 'user.roles'], - 'tags' => ['a', 'b', 'c', 'd', 'rendered'], + 'tags' => ['a', 'b', 'c', 'd'], ], '#post_render_cache' => [], '#markup' => 'parent', @@ -434,7 +431,7 @@ public function testConditionalCacheContextBubblingSelfHealing() { '#attached' => [], '#cache' => [ 'contexts' => ['bar', 'foo', 'user.roles'], - 'tags' => ['a', 'b', 'rendered'], + 'tags' => ['a', 'b'], ], '#post_render_cache' => [], '#markup' => 'parent', @@ -449,7 +446,7 @@ public function testConditionalCacheContextBubblingSelfHealing() { '#attached' => [], '#cache' => [ 'contexts' => ['bar', 'foo', 'user.roles'], - 'tags' => ['a', 'b', 'c', 'rendered'], + 'tags' => ['a', 'b', 'c'], ], '#post_render_cache' => [], '#markup' => 'parent', diff --git a/core/tests/Drupal/Tests/Core/Render/RendererPostRenderCacheTest.php b/core/tests/Drupal/Tests/Core/Render/RendererPostRenderCacheTest.php index 46a49be9f48a..da9f2327748d 100644 --- a/core/tests/Drupal/Tests/Core/Render/RendererPostRenderCacheTest.php +++ b/core/tests/Drupal/Tests/Core/Render/RendererPostRenderCacheTest.php @@ -91,7 +91,7 @@ public function testPostRenderCacheWithColdCache() { '#post_render_cache' => $test_element['#post_render_cache'], '#cache' => [ 'contexts' => [], - 'tags' => ['rendered'], + 'tags' => [], ], ]; $this->assertSame($cached_element, $expected_element, 'The correct data is cached: the stored #markup and #attached properties are not affected by #post_render_cache callbacks.'); @@ -227,7 +227,7 @@ public function testRenderChildrenPostRenderCacheDifferentContexts() { ], '#cache' => [ 'contexts' => [], - 'tags' => ['rendered'], + 'tags' => [], ], ]; @@ -322,7 +322,7 @@ public function testRenderChildrenPostRenderCacheComplex() { ], '#cache' => [ 'contexts' => [], - 'tags' => ['rendered'], + 'tags' => [], ], ]; @@ -348,7 +348,7 @@ public function testRenderChildrenPostRenderCacheComplex() { ], '#cache' => [ 'contexts' => [], - 'tags' => ['rendered'], + 'tags' => [], ], ]; @@ -462,7 +462,7 @@ public function testPlaceholder() { ], '#cache' => [ 'contexts' => [], - 'tags' => ['rendered'], + 'tags' => [], ], ]; $this->assertSame($cached_element, $expected_element, 'The correct data is cached: the stored #markup and #attached properties are not affected by #post_render_cache callbacks.'); @@ -560,7 +560,7 @@ public function testChildElementPlaceholder() { ], '#cache' => [ 'contexts' => [], - 'tags' => ['rendered'], + 'tags' => [], ], ]; $this->assertSame($cached_element, $expected_element, 'The correct data is cached for the child element: the stored #markup and #attached properties are not affected by #post_render_cache callbacks.'); @@ -588,7 +588,7 @@ public function testChildElementPlaceholder() { ], '#cache' => [ 'contexts' => [], - 'tags' => ['rendered'], + 'tags' => [], ], ]; $this->assertSame($cached_element, $expected_element, 'The correct data is cached for the parent element: the stored #markup and #attached properties are not affected by #post_render_cache callbacks.'); @@ -619,7 +619,7 @@ public function testChildElementPlaceholder() { ], '#cache' => [ 'contexts' => [], - 'tags' => ['rendered'], + 'tags' => [], ], ]; $this->assertSame($cached_element, $expected_element, 'The correct data is cached for the child element: the stored #markup and #attached properties are not affected by #post_render_cache callbacks.'); diff --git a/core/tests/Drupal/Tests/Core/Render/RendererTest.php b/core/tests/Drupal/Tests/Core/Render/RendererTest.php index 455834eb2f2c..ac2925c655c1 100644 --- a/core/tests/Drupal/Tests/Core/Render/RendererTest.php +++ b/core/tests/Drupal/Tests/Core/Render/RendererTest.php @@ -7,6 +7,7 @@ namespace Drupal\Tests\Core\Render; +use Drupal\Core\Cache\Cache; use Drupal\Core\Render\Element; use Drupal\Core\Template\Attribute; @@ -567,9 +568,12 @@ public function testRenderCache() { 'render_cache_tag', 'render_cache_tag_child:1', 'render_cache_tag_child:2', - 'rendered', ]; $this->assertEquals($expected_tags, $element['#cache']['tags'], 'Cache tags were collected from the element and its subchild.'); + + // The cache item also has a 'rendered' cache tag. + $cache_item = $this->cacheFactory->get('render')->get('render_cache_test'); + $this->assertSame(Cache::mergeTags($expected_tags, ['rendered']), $cache_item->tags); } } diff --git a/core/tests/Drupal/Tests/Core/Render/RendererTestBase.php b/core/tests/Drupal/Tests/Core/Render/RendererTestBase.php index cf085545cb0c..b4320c955605 100644 --- a/core/tests/Drupal/Tests/Core/Render/RendererTestBase.php +++ b/core/tests/Drupal/Tests/Core/Render/RendererTestBase.php @@ -7,6 +7,7 @@ namespace Drupal\Tests\Core\Render; +use Drupal\Core\Cache\Cache; use Drupal\Core\Cache\MemoryBackend; use Drupal\Core\Render\Element; use Drupal\Core\Render\Renderer; @@ -152,6 +153,7 @@ protected function assertRenderCacheItem($cid, $data) { $this->assertNotFalse($cached, sprintf('Expected cache item "%s" exists.', $cid)); if ($cached !== FALSE) { $this->assertEquals($data, $cached->data, sprintf('Cache item "%s" has the expected data.', $cid)); + $this->assertSame(Cache::mergeTags($data['#cache']['tags'], ['rendered']), $cached->tags, "The cache item's cache tags also has the 'rendered' cache tag."); } } -- GitLab