diff --git a/src/Plugin/views/cache/SearchApiTagCache.php b/src/Plugin/views/cache/SearchApiTagCache.php index 48e7728f..0f8b5873 100644 --- a/src/Plugin/views/cache/SearchApiTagCache.php +++ b/src/Plugin/views/cache/SearchApiTagCache.php @@ -78,8 +78,13 @@ class SearchApiTagCache extends Tag { */ public function getCacheTags() { $tags = $this->view->storage->getCacheTags(); + // Add the list cache tag of the search index, so that the view will be + // invalidated whenever the index is updated. $tag = 'search_api_list:' . $this->getQuery()->getIndex()->id(); $tags = Cache::mergeTags([$tag], $tags); + // Also add the cache tags of the index itself, so that the view will be + // invalidated if the configuration of the index changes. + $tags = Cache::mergeTags($this->getQuery()->getIndex()->getCacheTagsToInvalidate(), $tags); return $tags; } diff --git a/tests/src/Kernel/Views/ViewsCacheInvalidationTest.php b/tests/src/Kernel/Views/ViewsCacheInvalidationTest.php index d0d29965..6910ff99 100644 --- a/tests/src/Kernel/Views/ViewsCacheInvalidationTest.php +++ b/tests/src/Kernel/Views/ViewsCacheInvalidationTest.php @@ -3,11 +3,12 @@ namespace Drupal\Tests\search_api\Kernel\Views; use Drupal\Core\Cache\Cache; -use Drupal\Core\DependencyInjection\ContainerBuilder; +use Drupal\Core\Datetime\Entity\DateFormat; use Drupal\KernelTests\KernelTestBase; use Drupal\node\Entity\Node; use Drupal\node\Entity\NodeType; use Drupal\search_api\Entity\Index; +use Drupal\Tests\search_api\Kernel\PostRequestIndexingTrait; use Drupal\Tests\user\Traits\UserCreationTrait; use Drupal\views\Tests\AssertViewsCacheTagsTrait; @@ -19,6 +20,7 @@ use Drupal\views\Tests\AssertViewsCacheTagsTrait; class ViewsCacheInvalidationTest extends KernelTestBase { use AssertViewsCacheTagsTrait; + use PostRequestIndexingTrait; use UserCreationTrait; /** @@ -148,6 +150,12 @@ class ViewsCacheInvalidationTest extends KernelTestBase { $this->cacheTagsInvalidator = $this->container->get('cache_tags.invalidator'); $this->currentUser = $this->container->get('current_user'); + DateFormat::create([ + 'id' => 'fallback', + 'label' => 'Fallback', + 'pattern' => 'Y-m-d', + ])->save(); + // Use the test search index from the search_api_test_db module. $this->index = Index::load('test_node_index'); @@ -251,18 +259,58 @@ class ViewsCacheInvalidationTest extends KernelTestBase { $this->assertViewsResult('has-access', ['Angua', 'Cheery', 'Carrot', 'Detritus']); $this->assertCached('has-access'); - // Delete one of the test content entities. This should not affect the - // cached view until the search index is updated. + // Delete one of the test content entities. This takes effect immediately, + // there is no need to wait until the search index is updated. + // @see search_api_entity_delete() $this->nodes['Carrot']->delete(); - $this->assertCached('has-access'); - - $this->index->indexItems(); $this->assertNotCached('has-access'); // The view should no longer include the deleted content now, and the result // should be cached after the view has been displayed. $this->assertViewsResult('has-access', ['Angua', 'Cheery', 'Detritus']); $this->assertCached('has-access'); + + // Update the search index configuration so it will index items immediately + // when they are created or updated. + $this->index->setOption('index_directly', TRUE)->save(); + + // Changing the configuration of the index should invalidate all views that + // show its data. + $this->assertNotCached('no-access'); + $this->assertNotCached('has-access'); + + // Check that the expected results are still returned and are cacheable. + $this->assertViewsResult('no-access', ['Angua', 'Cheery', 'Detritus']); + $this->assertViewsResult('has-access', ['Angua', 'Cheery', 'Detritus']); + $this->assertCached('no-access'); + $this->assertCached('has-access'); + + // Change the configuration of the view. This should also invalidate all + // displays of the view. + $view = $this->getView(); + $view->setItemsPerPage(20); + $view->save(); + $this->assertNotCached('no-access'); + $this->assertNotCached('has-access'); + + // Check that the expected results are still returned and are cacheable. + $this->assertViewsResult('no-access', ['Angua', 'Cheery', 'Detritus']); + $this->assertViewsResult('has-access', ['Angua', 'Cheery', 'Detritus']); + $this->assertCached('no-access'); + $this->assertCached('has-access'); + + // Edit one of the test content entities. Because the search index is being + // updated immediately, the cached views should be cleared without having to + // perform a manual indexing step. + $this->nodes['Angua']->set('title', 'Angua von Überwald')->save(); + $this->assertNotCached('no-access'); + $this->assertNotCached('has-access'); + + // Check that the updated results are shown and are cacheable. + $this->assertViewsResult('no-access', ['Angua', 'Cheery', 'Detritus']); + $this->assertViewsResult('has-access', ['Angua', 'Cheery', 'Detritus']); + $this->assertCached('no-access'); + $this->assertCached('has-access'); } /** @@ -294,16 +342,20 @@ class ViewsCacheInvalidationTest extends KernelTestBase { * The key of the user for which to perform the check. */ protected function doAssertCached($assert_method, $user_key) { - $this->setCurrentUser($user_key); + // Ensure that any post request indexing is done. This is normally handled + // at the end of the request but since we are running a KernelTest we are + // not executing any requests and need to trigger this manually. + $this->triggerPostRequestIndexing(); - // Retrieve the view to render, and apply the required cache contexts that - // are also applied when RendererInterface::renderRoot() is executed. This - // ensures that we pass the same cache information to the render cache as is - // done when actually rendering the HTML root. - $render_array = $this->getRenderableView(); + // Set the user that will be used to check the cache status. + $this->setCurrentUser($user_key); // Retrieve the cached data and perform the assertion. - $cached_data = $this->renderCache->get($render_array); + $view = $this->getView(); + $view->build(); + $cache = $view->getDisplay()->getPlugin('cache'); + $cached_data = $cache->cacheGet('results'); + $this->$assert_method($cached_data); } @@ -361,14 +413,24 @@ class ViewsCacheInvalidationTest extends KernelTestBase { * The render array, or NULL if the view cannot be rendered. */ protected function getRenderableView() { + $render_array = $this->getView()->buildRenderable(); + $render_array['#cache']['contexts'] = Cache::mergeContexts($render_array['#cache']['contexts'], $this->container->getParameter('renderer.config')['required_cache_contexts']); + + return $render_array; + } + + /** + * Returns the test view. + * + * @return \Drupal\views\ViewExecutable + * The view. + */ + protected function getView() { /** @var \Drupal\views\ViewEntityInterface $view */ $view = $this->entityTypeManager->getStorage('view')->load(self::TEST_VIEW_ID); $executable = $this->viewExecutableFactory->get($view); $executable->setDisplay(self::TEST_VIEW_DISPLAY_ID); - $render_array = $executable->buildRenderable(); - $render_array['#cache']['contexts'] = Cache::mergeContexts($render_array['#cache']['contexts'], $this->container->getParameter('renderer.config')['required_cache_contexts']); - - return $render_array; + return $executable; } /**