624d5922144deea55e4a20419c275f111ed8567a
[yaffs-website] / web / core / tests / Drupal / Tests / Core / Menu / LocalTaskManagerTest.php
1 <?php
2
3 namespace Drupal\Tests\Core\Menu;
4
5 use Drupal\Core\Access\AccessResult;
6 use Drupal\Core\Cache\Cache;
7 use Drupal\Core\Cache\CacheableDependencyInterface;
8 use Drupal\Core\Cache\CacheableMetadata;
9 use Drupal\Core\Cache\Context\CacheContextsManager;
10 use Drupal\Core\DependencyInjection\ContainerBuilder;
11 use Drupal\Core\Language\Language;
12 use Drupal\Core\Menu\LocalTaskInterface;
13 use Drupal\Core\Menu\LocalTaskManager;
14 use Drupal\Tests\UnitTestCase;
15 use Prophecy\Argument;
16 use Symfony\Component\HttpFoundation\ParameterBag;
17 use Symfony\Component\HttpFoundation\Request;
18 use Symfony\Component\HttpFoundation\RequestStack;
19
20 /**
21  * @coversDefaultClass \Drupal\Core\Menu\LocalTaskManager
22  * @group Menu
23  */
24 class LocalTaskManagerTest extends UnitTestCase {
25
26   /**
27    * The tested manager.
28    *
29    * @var \Drupal\Core\Menu\LocalTaskManager
30    */
31   protected $manager;
32
33   /**
34    * The mocked controller resolver.
35    *
36    * @var \PHPUnit_Framework_MockObject_MockObject
37    */
38   protected $controllerResolver;
39
40   /**
41    * The test request.
42    *
43    * @var \Symfony\Component\HttpFoundation\Request
44    */
45   protected $request;
46
47   /**
48    * The mocked route provider.
49    *
50    * @var \PHPUnit_Framework_MockObject_MockObject
51    */
52   protected $routeProvider;
53
54   /**
55    * The mocked plugin discovery.
56    *
57    * @var \PHPUnit_Framework_MockObject_MockObject
58    */
59   protected $pluginDiscovery;
60
61   /**
62    * The plugin factory used in the test.
63    *
64    * @var \PHPUnit_Framework_MockObject_MockObject
65    */
66   protected $factory;
67
68   /**
69    * The cache backend used in the test.
70    *
71    * @var \PHPUnit_Framework_MockObject_MockObject
72    */
73   protected $cacheBackend;
74
75   /**
76    * The mocked access manager.
77    *
78    * @var \Drupal\Core\Access\AccessManagerInterface|\PHPUnit_Framework_MockObject_MockObject
79    */
80   protected $accessManager;
81
82   /**
83    * The route match.
84    *
85    * @var \Drupal\Core\Routing\RouteMatchInterface|\PHPUnit_Framework_MockObject_MockObject
86    */
87   protected $routeMatch;
88
89   /**
90    * The mocked account.
91    *
92    * @var \Drupal\Core\Session\AccountInterface|\PHPUnit_Framework_MockObject_MockObject
93    */
94   protected $account;
95
96   /**
97    * {@inheritdoc}
98    */
99   protected function setUp() {
100     parent::setUp();
101
102     $this->controllerResolver = $this->getMock('Drupal\Core\Controller\ControllerResolverInterface');
103     $this->request = new Request();
104     $this->routeProvider = $this->getMock('Drupal\Core\Routing\RouteProviderInterface');
105     $this->pluginDiscovery = $this->getMock('Drupal\Component\Plugin\Discovery\DiscoveryInterface');
106     $this->factory = $this->getMock('Drupal\Component\Plugin\Factory\FactoryInterface');
107     $this->cacheBackend = $this->getMock('Drupal\Core\Cache\CacheBackendInterface');
108     $this->accessManager = $this->getMock('Drupal\Core\Access\AccessManagerInterface');
109     $this->routeMatch = $this->getMock('Drupal\Core\Routing\RouteMatchInterface');
110     $this->account = $this->getMock('Drupal\Core\Session\AccountInterface');
111
112     $this->setupLocalTaskManager();
113     $this->setupNullCacheabilityMetadataValidation();
114   }
115
116   /**
117    * Tests the getLocalTasksForRoute method.
118    *
119    * @see \Drupal\system\Plugin\Type\MenuLocalTaskManager::getLocalTasksForRoute()
120    */
121   public function testGetLocalTasksForRouteSingleLevelTitle() {
122     $definitions = $this->getLocalTaskFixtures();
123
124     $this->pluginDiscovery->expects($this->once())
125       ->method('getDefinitions')
126       ->will($this->returnValue($definitions));
127
128     $mock_plugin = $this->getMock('Drupal\Core\Menu\LocalTaskInterface');
129
130     $this->setupFactory($mock_plugin);
131     $this->setupLocalTaskManager();
132
133     $local_tasks = $this->manager->getLocalTasksForRoute('menu_local_task_test_tasks_view');
134
135     $result = $this->getLocalTasksForRouteResult($mock_plugin);
136
137     $this->assertEquals($result, $local_tasks);
138   }
139
140   /**
141    * Tests the getLocalTasksForRoute method on a child.
142    *
143    * @see \Drupal\system\Plugin\Type\MenuLocalTaskManager::getLocalTasksForRoute()
144    */
145   public function testGetLocalTasksForRouteForChild() {
146     $definitions = $this->getLocalTaskFixtures();
147
148     $this->pluginDiscovery->expects($this->once())
149       ->method('getDefinitions')
150       ->will($this->returnValue($definitions));
151
152     $mock_plugin = $this->getMock('Drupal\Core\Menu\LocalTaskInterface');
153
154     $this->setupFactory($mock_plugin);
155     $this->setupLocalTaskManager();
156
157     $local_tasks = $this->manager->getLocalTasksForRoute('menu_local_task_test_tasks_child1_page');
158
159     $result = $this->getLocalTasksForRouteResult($mock_plugin);
160
161     $this->assertEquals($result, $local_tasks);
162   }
163
164   /**
165    * Tests the cache of the local task manager with an empty initial cache.
166    */
167   public function testGetLocalTaskForRouteWithEmptyCache() {
168     $definitions = $this->getLocalTaskFixtures();
169
170     $this->pluginDiscovery->expects($this->once())
171       ->method('getDefinitions')
172       ->will($this->returnValue($definitions));
173
174     $mock_plugin = $this->getMock('Drupal\Core\Menu\LocalTaskInterface');
175     $this->setupFactory($mock_plugin);
176
177     $this->setupLocalTaskManager();
178
179     $result = $this->getLocalTasksForRouteResult($mock_plugin);
180
181     $this->cacheBackend->expects($this->at(0))
182       ->method('get')
183       ->with('local_task_plugins:en:menu_local_task_test_tasks_view');
184
185     $this->cacheBackend->expects($this->at(1))
186       ->method('get')
187       ->with('local_task_plugins:en');
188
189     $this->cacheBackend->expects($this->at(2))
190       ->method('set')
191       ->with('local_task_plugins:en', $definitions, Cache::PERMANENT);
192
193     $expected_set = $this->getLocalTasksCache();
194
195     $this->cacheBackend->expects($this->at(3))
196       ->method('set')
197       ->with('local_task_plugins:en:menu_local_task_test_tasks_view', $expected_set, Cache::PERMANENT, ['local_task']);
198
199     $local_tasks = $this->manager->getLocalTasksForRoute('menu_local_task_test_tasks_view');
200     $this->assertEquals($result, $local_tasks);
201   }
202
203   /**
204    * Tests the cache of the local task manager with a filled initial cache.
205    */
206   public function testGetLocalTaskForRouteWithFilledCache() {
207     $this->pluginDiscovery->expects($this->never())
208       ->method('getDefinitions');
209
210     $mock_plugin = $this->getMock('Drupal\Core\Menu\LocalTaskInterface');
211     $this->setupFactory($mock_plugin);
212
213     $this->setupLocalTaskManager();
214
215     $result = $this->getLocalTasksCache($mock_plugin);
216
217     $this->cacheBackend->expects($this->at(0))
218       ->method('get')
219       ->with('local_task_plugins:en:menu_local_task_test_tasks_view')
220       ->will($this->returnValue((object) ['data' => $result]));
221
222     $this->cacheBackend->expects($this->never())
223       ->method('set');
224
225     $result = $this->getLocalTasksForRouteResult($mock_plugin);
226     $local_tasks = $this->manager->getLocalTasksForRoute('menu_local_task_test_tasks_view');
227     $this->assertEquals($result, $local_tasks);
228   }
229
230   /**
231    * Tests the getTitle method.
232    *
233    * @see \Drupal\system\Plugin\Type\MenuLocalTaskManager::getTitle()
234    */
235   public function testGetTitle() {
236     $menu_local_task = $this->getMock('Drupal\Core\Menu\LocalTaskInterface');
237     $menu_local_task->expects($this->once())
238       ->method('getTitle');
239
240     $this->controllerResolver->expects($this->once())
241       ->method('getArguments')
242       ->with($this->request, [$menu_local_task, 'getTitle'])
243       ->will($this->returnValue([]));
244
245     $this->manager->getTitle($menu_local_task);
246   }
247
248   /**
249    * Setups the local task manager for the test.
250    */
251   protected function setupLocalTaskManager() {
252     $request_stack = new RequestStack();
253     $request_stack->push($this->request);
254     $module_handler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface');
255     $module_handler->expects($this->any())
256       ->method('getModuleDirectories')
257       ->willReturn([]);
258     $language_manager = $this->getMock('Drupal\Core\Language\LanguageManagerInterface');
259     $language_manager->expects($this->any())
260       ->method('getCurrentLanguage')
261       ->will($this->returnValue(new Language(['id' => 'en'])));
262
263     $this->manager = new LocalTaskManager($this->controllerResolver, $request_stack, $this->routeMatch, $this->routeProvider, $module_handler, $this->cacheBackend, $language_manager, $this->accessManager, $this->account);
264
265     $property = new \ReflectionProperty('Drupal\Core\Menu\LocalTaskManager', 'discovery');
266     $property->setAccessible(TRUE);
267     $property->setValue($this->manager, $this->pluginDiscovery);
268
269     $property = new \ReflectionProperty('Drupal\Core\Menu\LocalTaskManager', 'factory');
270     $property->setAccessible(TRUE);
271     $property->setValue($this->manager, $this->factory);
272
273   }
274
275   /**
276    * Return some local tasks plugin definitions.
277    *
278    * @return array
279    *   An array of plugin definition keyed by plugin ID.
280    */
281   protected function getLocalTaskFixtures() {
282     $definitions = [];
283     $definitions['menu_local_task_test_tasks_settings'] = [
284       'route_name' => 'menu_local_task_test_tasks_settings',
285       'title' => 'Settings',
286       'base_route' => 'menu_local_task_test_tasks_view',
287     ];
288     $definitions['menu_local_task_test_tasks_edit'] = [
289       'route_name' => 'menu_local_task_test_tasks_edit',
290       'title' => 'Settings',
291       'base_route' => 'menu_local_task_test_tasks_view',
292       'weight' => 20,
293     ];
294     // Make this ID different from the route name to catch code that
295     // confuses them.
296     $definitions['menu_local_task_test_tasks_view.tab'] = [
297       'route_name' => 'menu_local_task_test_tasks_view',
298       'title' => 'Settings',
299       'base_route' => 'menu_local_task_test_tasks_view',
300     ];
301
302     $definitions['menu_local_task_test_tasks_view_child1'] = [
303       'route_name' => 'menu_local_task_test_tasks_child1_page',
304       'title' => 'Settings child #1',
305       'parent_id' => 'menu_local_task_test_tasks_view.tab',
306     ];
307     $definitions['menu_local_task_test_tasks_view_child2'] = [
308       'route_name' => 'menu_local_task_test_tasks_child2_page',
309       'title' => 'Settings child #2',
310       'parent_id' => 'menu_local_task_test_tasks_view.tab',
311       'base_route' => 'this_should_be_replaced',
312     ];
313     // Add the ID and defaults from the LocalTaskManager.
314     foreach ($definitions as $id => &$info) {
315       $info['id'] = $id;
316       $info += [
317         'id' => '',
318         'route_name' => '',
319         'route_parameters' => [],
320         'title' => '',
321         'base_route' => '',
322         'parent_id' => NULL,
323         'weight' => 0,
324         'options' => [],
325         'class' => 'Drupal\Core\Menu\LocalTaskDefault',
326       ];
327     }
328     return $definitions;
329   }
330
331   /**
332    * Setups the plugin factory with some local task plugins.
333    *
334    * @param \PHPUnit_Framework_MockObject_MockObject $mock_plugin
335    *   The mock plugin.
336    */
337   protected function setupFactory($mock_plugin) {
338     $map = [];
339     foreach ($this->getLocalTaskFixtures() as $info) {
340       $map[] = [$info['id'], [], $mock_plugin];
341     }
342     $this->factory->expects($this->any())
343       ->method('createInstance')
344       ->will($this->returnValueMap($map));
345   }
346
347   /**
348    * Returns an expected result for getLocalTasksForRoute.
349    *
350    * @param \PHPUnit_Framework_MockObject_MockObject $mock_plugin
351    *   The mock plugin.
352    *
353    * @return array
354    *   The expected result, keyed by local task level.
355    */
356   protected function getLocalTasksForRouteResult($mock_plugin) {
357     $result = [
358       0 => [
359         'menu_local_task_test_tasks_settings' => $mock_plugin,
360         'menu_local_task_test_tasks_view.tab' => $mock_plugin,
361         'menu_local_task_test_tasks_edit' => $mock_plugin,
362       ],
363       1 => [
364         'menu_local_task_test_tasks_view_child1' => $mock_plugin,
365         'menu_local_task_test_tasks_view_child2' => $mock_plugin,
366       ],
367     ];
368     return $result;
369   }
370
371   /**
372    * Returns the cache entry expected when running getLocalTaskForRoute().
373    *
374    * @return array
375    */
376   protected function getLocalTasksCache() {
377     $local_task_fixtures = $this->getLocalTaskFixtures();
378     $local_tasks = [
379       'base_routes' => [
380         'menu_local_task_test_tasks_view' => 'menu_local_task_test_tasks_view',
381       ],
382       'parents' => [
383         'menu_local_task_test_tasks_view.tab' => TRUE,
384       ],
385       'children' => [
386         '> menu_local_task_test_tasks_view' => [
387           'menu_local_task_test_tasks_settings' => $local_task_fixtures['menu_local_task_test_tasks_settings'],
388           'menu_local_task_test_tasks_edit' => $local_task_fixtures['menu_local_task_test_tasks_edit'],
389           'menu_local_task_test_tasks_view.tab' => $local_task_fixtures['menu_local_task_test_tasks_view.tab'],
390         ],
391         'menu_local_task_test_tasks_view.tab' => [
392           // The manager will fill in the base_route before caching.
393           'menu_local_task_test_tasks_view_child1' => ['base_route' => 'menu_local_task_test_tasks_view'] + $local_task_fixtures['menu_local_task_test_tasks_view_child1'],
394           'menu_local_task_test_tasks_view_child2' => ['base_route' => 'menu_local_task_test_tasks_view'] + $local_task_fixtures['menu_local_task_test_tasks_view_child2'],
395         ],
396       ],
397     ];
398     $local_tasks['children']['> menu_local_task_test_tasks_view']['menu_local_task_test_tasks_settings']['weight'] = 0;
399     $local_tasks['children']['> menu_local_task_test_tasks_view']['menu_local_task_test_tasks_edit']['weight'] = 20 + 1e-6;
400     $local_tasks['children']['> menu_local_task_test_tasks_view']['menu_local_task_test_tasks_view.tab']['weight'] = 2e-6;
401     $local_tasks['children']['menu_local_task_test_tasks_view.tab']['menu_local_task_test_tasks_view_child1']['weight'] = 3e-6;
402     $local_tasks['children']['menu_local_task_test_tasks_view.tab']['menu_local_task_test_tasks_view_child2']['weight'] = 4e-6;
403     return $local_tasks;
404   }
405
406   /**
407    * @covers ::getTasksBuild
408    */
409   public function testGetTasksBuildWithCacheabilityMetadata() {
410     $definitions = $this->getLocalTaskFixtures();
411
412     $this->pluginDiscovery->expects($this->once())
413       ->method('getDefinitions')
414       ->will($this->returnValue($definitions));
415
416     // Set up some cacheability metadata and ensure its merged together.
417     $definitions['menu_local_task_test_tasks_settings']['cache_tags'] = ['tag.example1'];
418     $definitions['menu_local_task_test_tasks_settings']['cache_contexts'] = ['context.example1'];
419     $definitions['menu_local_task_test_tasks_edit']['cache_tags'] = ['tag.example2'];
420     $definitions['menu_local_task_test_tasks_edit']['cache_contexts'] = ['context.example2'];
421     // Test the cacheability metadata of access checking.
422     $definitions['menu_local_task_test_tasks_view_child1']['access'] = AccessResult::allowed()->addCacheContexts(['user.permissions']);
423
424     $this->setupFactoryAndLocalTaskPlugins($definitions, 'menu_local_task_test_tasks_view');
425     $this->setupLocalTaskManager();
426
427     $this->controllerResolver->expects($this->any())
428       ->method('getArguments')
429       ->willReturn([]);
430
431     $this->routeMatch->expects($this->any())
432       ->method('getRouteName')
433       ->willReturn('menu_local_task_test_tasks_view');
434     $this->routeMatch->expects($this->any())
435       ->method('getRawParameters')
436       ->willReturn(new ParameterBag());
437
438     $cacheability = new CacheableMetadata();
439     $local_tasks = $this->manager->getTasksBuild('menu_local_task_test_tasks_view', $cacheability);
440
441     // Ensure that all cacheability metadata is merged together.
442     $this->assertEquals(['tag.example1', 'tag.example2'], $cacheability->getCacheTags());
443     $this->assertEquals(['context.example1', 'context.example2', 'route', 'user.permissions'], $cacheability->getCacheContexts());
444   }
445
446   protected function setupFactoryAndLocalTaskPlugins(array $definitions, $active_plugin_id) {
447     $map = [];
448     $access_manager_map = [];
449
450     foreach ($definitions as $plugin_id => $info) {
451       $info += ['access' => AccessResult::allowed()];
452
453       $mock = $this->prophesize(LocalTaskInterface::class);
454       $mock->willImplement(CacheableDependencyInterface::class);
455       $mock->getRouteName()->willReturn($info['route_name']);
456       $mock->getTitle()->willReturn($info['title']);
457       $mock->getRouteParameters(Argument::cetera())->willReturn([]);
458       $mock->getOptions(Argument::cetera())->willReturn([]);
459       $mock->getActive()->willReturn($plugin_id === $active_plugin_id);
460       $mock->getWeight()->willReturn(isset($info['weight']) ? $info['weight'] : 0);
461       $mock->getCacheContexts()->willReturn(isset($info['cache_contexts']) ? $info['cache_contexts'] : []);
462       $mock->getCacheTags()->willReturn(isset($info['cache_tags']) ? $info['cache_tags'] : []);
463       $mock->getCacheMaxAge()->willReturn(isset($info['cache_max_age']) ? $info['cache_max_age'] : Cache::PERMANENT);
464
465       $access_manager_map[] = [$info['route_name'], [], $this->account, TRUE, $info['access']];
466
467       $map[] = [$info['id'], [], $mock->reveal()];
468     }
469
470     $this->accessManager->expects($this->any())
471       ->method('checkNamedRoute')
472       ->willReturnMap($access_manager_map);
473
474     $this->factory->expects($this->any())
475       ->method('createInstance')
476       ->will($this->returnValueMap($map));
477   }
478
479   protected function setupNullCacheabilityMetadataValidation() {
480     $container = \Drupal::hasContainer() ? \Drupal::getContainer() : new ContainerBuilder();
481
482     $cache_context_manager = $this->prophesize(CacheContextsManager::class);
483
484     foreach ([NULL, ['user.permissions'], ['route'], ['route', 'context.example1'], ['context.example1', 'route'], ['context.example1', 'route', 'context.example2'], ['context.example1', 'context.example2', 'route'], ['context.example1', 'context.example2', 'route', 'user.permissions']] as $argument) {
485       $cache_context_manager->assertValidTokens($argument)->willReturn(TRUE);
486     }
487
488     $container->set('cache_contexts_manager', $cache_context_manager->reveal());
489     \Drupal::setContainer($container);
490   }
491
492 }