diff --git a/core/lib/Drupal/Core/Menu/LocalTaskManager.php b/core/lib/Drupal/Core/Menu/LocalTaskManager.php index 1be9d94..2d2b326 100644 --- a/core/lib/Drupal/Core/Menu/LocalTaskManager.php +++ b/core/lib/Drupal/Core/Menu/LocalTaskManager.php @@ -326,6 +326,9 @@ public function getTasksBuild($current_route_name, CacheableMetadata &$cacheable $route_name = $child->getRouteName(); $route_parameters = $child->getRouteParameters($this->routeMatch); + // Given that the active flag depends on the route we have to add the + // route cache context. + $cacheable_metadata->addCacheContexts(['route']); $active = $this->isRouteActive($current_route_name, $route_name, $route_parameters); // The plugin may have been set active in getLocalTasksForRoute() if diff --git a/core/tests/Drupal/Tests/Core/Menu/LocalTaskDefaultTest.php b/core/tests/Drupal/Tests/Core/Menu/LocalTaskDefaultTest.php index 6e393a8..d5b0533 100644 --- a/core/tests/Drupal/Tests/Core/Menu/LocalTaskDefaultTest.php +++ b/core/tests/Drupal/Tests/Core/Menu/LocalTaskDefaultTest.php @@ -301,6 +301,25 @@ public function testGetOptions() { ), $this->localTaskBase->getOptions($route_match)); } + /** + * @covers ::getCacheableMetadata + */ + public function testGetCacheableMetadataWithoutCacheTags() { + $this->setupLocalTaskDefault(); + + $this->assertEquals([], $this->localTaskBase->getCacheableMetadata()->getCacheTags()); + } + + /** + * @covers ::getCacheableMetadata + */ + public function testGetCacheableMetadataWithCacheTags() { + $this->pluginDefinition['cache_tags'] = ['example']; + $this->setupLocalTaskDefault(); + + $this->assertEquals(['example'], $this->localTaskBase->getCacheableMetadata()->getCacheTags()); + } + } class TestLocalTaskDefault extends LocalTaskDefault { diff --git a/core/tests/Drupal/Tests/Core/Menu/LocalTaskManagerTest.php b/core/tests/Drupal/Tests/Core/Menu/LocalTaskManagerTest.php index 92b2b54..628f4a1 100644 --- a/core/tests/Drupal/Tests/Core/Menu/LocalTaskManagerTest.php +++ b/core/tests/Drupal/Tests/Core/Menu/LocalTaskManagerTest.php @@ -7,10 +7,15 @@ namespace Drupal\Tests\Core\Menu; +use Drupal\Core\Access\AccessResult; use Drupal\Core\Cache\Cache; +use Drupal\Core\Cache\CacheableMetadata; use Drupal\Core\Language\Language; +use Drupal\Core\Menu\LocalTaskInterface; use Drupal\Core\Menu\LocalTaskManager; use Drupal\Tests\UnitTestCase; +use Prophecy\Argument; +use Symfony\Component\HttpFoundation\ParameterBag; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; use Zend\Stdlib\ArrayObject; @@ -85,6 +90,13 @@ class LocalTaskManagerTest extends UnitTestCase { protected $routeMatch; /** + * The mocked account. + * + * @var \Drupal\Core\Session\AccountInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $account; + + /** * {@inheritdoc} */ protected function setUp() { @@ -98,8 +110,10 @@ protected function setUp() { $this->cacheBackend = $this->getMock('Drupal\Core\Cache\CacheBackendInterface'); $this->accessManager = $this->getMock('Drupal\Core\Access\AccessManagerInterface'); $this->routeMatch = $this->getMock('Drupal\Core\Routing\RouteMatchInterface'); + $this->account = $this->getMock('Drupal\Core\Session\AccountInterface'); $this->setupLocalTaskManager(); + $this->setupNullCachebleMetadataValidation(); } /** @@ -249,8 +263,7 @@ protected function setupLocalTaskManager() { ->method('getCurrentLanguage') ->will($this->returnValue(new Language(array('id' => 'en')))); - $account = $this->getMock('Drupal\Core\Session\AccountInterface'); - $this->manager = new LocalTaskManager($this->controllerResolver, $request_stack, $this->routeMatch, $this->routeProvider, $module_handler, $this->cacheBackend, $language_manager, $this->accessManager, $account); + $this->manager = new LocalTaskManager($this->controllerResolver, $request_stack, $this->routeMatch, $this->routeProvider, $module_handler, $this->cacheBackend, $language_manager, $this->accessManager, $this->account); $property = new \ReflectionProperty('Drupal\Core\Menu\LocalTaskManager', 'discovery'); $property->setAccessible(TRUE); @@ -393,5 +406,73 @@ protected function getLocalTasksCache() { return $local_tasks; } + /** + * @covers ::getTasksBuild + */ + public function testGetTasksBuildWithCacheabilityMetadata() { + $definitions = $this->getLocalTaskFixtures(); + + $this->pluginDiscovery->expects($this->once()) + ->method('getDefinitions') + ->will($this->returnValue($definitions)); + + // Setup some cacheablity metadata and ensure its merged together. + $definitions['menu_local_task_test_tasks_settings']['cacheable_metadata'] = (new CacheableMetadata())->setCacheTags(['tag.example1'])->setCacheContexts(['context.example1']); + $definitions['menu_local_task_test_tasks_edit']['cacheable_metadata'] = (new CacheableMetadata())->setCacheTags(['tag.example2'])->setCacheContexts(['context.example2']); + // Test the cacheable metadata of access checking. + $definitions['menu_local_task_test_tasks_view_child1']['access'] = AccessResult::allowed()->addCacheContexts(['user.permissions']); + + $this->setupFactoryAndLocalTaskPlugins($definitions, 'menu_local_task_test_tasks_view'); + $this->setupLocalTaskManager(); + + $this->controllerResolver->expects($this->any()) + ->method('getArguments') + ->willReturn([]); + + $this->routeMatch->expects($this->any()) + ->method('getRouteName') + ->willReturn('menu_local_task_test_tasks_view'); + $this->routeMatch->expects($this->any()) + ->method('getRawParameters') + ->willReturn(new ParameterBag()); + + $cacheable_metadata = new CacheableMetadata(); + $local_tasks = $this->manager->getTasksBuild('menu_local_task_test_tasks_view', $cacheable_metadata); + + // Ensure that all cacheable metadata is merged together. + $this->assertEquals(['tag.example1', 'tag.example2'], $cacheable_metadata->getCacheTags()); + $this->assertEquals(['context.example1', 'context.example2', 'route', 'user.permissions'], $cacheable_metadata->getCacheContexts()); + } + + protected function setupFactoryAndLocalTaskPlugins(array $definitions, $active_plugin_id) { + $map = []; + $access_manager_map = []; + + foreach ($definitions as $plugin_id => $info) { + $info += ['access' => AccessResult::allowed()]; + + $mock = $this->prophesize(LocalTaskInterface::class); + $mock->getRouteName()->willReturn($info['route_name']); + $mock->getTitle()->willReturn($info['title']); + $mock->getRouteParameters(Argument::cetera())->willReturn([]); + $mock->getOptions(Argument::cetera())->willReturn([]); + $mock->getActive()->willReturn($plugin_id === $active_plugin_id); + $mock->getWeight()->willReturn(isset($info['weight']) ? $info['weight'] : 0); + $mock->getCacheableMetadata()->willReturn(isset($info['cacheable_metadata']) ? $info['cacheable_metadata'] : new CacheableMetadata()); + + $access_manager_map[] = [$info['route_name'], [], $this->account, TRUE, $info['access']]; + + $map[] = [$info['id'], [], $mock->reveal()]; + } + + $this->accessManager->expects($this->any()) + ->method('checkNamedRoute') + ->willReturnMap($access_manager_map); + + $this->factory->expects($this->any()) + ->method('createInstance') + ->will($this->returnValueMap($map)); + } + } diff --git a/core/tests/Drupal/Tests/UnitTestCase.php b/core/tests/Drupal/Tests/UnitTestCase.php index 3433c94..3e2c4b6 100644 --- a/core/tests/Drupal/Tests/UnitTestCase.php +++ b/core/tests/Drupal/Tests/UnitTestCase.php @@ -11,7 +11,9 @@ use Drupal\Component\Utility\Random; use Drupal\Component\Utility\SafeMarkup; use Drupal\Core\Cache\CacheTagsInvalidatorInterface; +use Drupal\Core\Cache\Context\CacheContextsManager; use Drupal\Core\DependencyInjection\ContainerBuilder; +use Prophecy\Argument; /** * Provides a base class and helpers for Drupal unit tests. @@ -264,4 +266,13 @@ protected function getClassResolverStub() { return $class_resolver; } + protected function setupNullCachebleMetadataValidation() { + $container = \Drupal::hasContainer() ? \Drupal::getContainer() : new ContainerBuilder(); + + $cache_context_manager = $this->prophesize(CacheContextsManager::class); + + $container->set('cache_contexts_manager', $cache_context_manager->reveal()); + \Drupal::setContainer($container); + } + }