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