3 namespace Drupal\Tests\Core\Menu;
5 use Drupal\Core\Menu\MenuActiveTrail;
6 use Drupal\Core\Routing\CurrentRouteMatch;
7 use Drupal\Tests\UnitTestCase;
8 use Symfony\Cmf\Component\Routing\RouteObjectInterface;
9 use Symfony\Component\DependencyInjection\Container;
10 use Symfony\Component\HttpFoundation\ParameterBag;
11 use Symfony\Component\HttpFoundation\Request;
12 use Symfony\Component\HttpFoundation\RequestStack;
13 use Symfony\Component\Routing\Route;
16 * Tests the active menu trail service.
20 * @coversDefaultClass \Drupal\Core\Menu\MenuActiveTrail
22 class MenuActiveTrailTest extends UnitTestCase {
25 * The tested active menu trail service.
27 * @var \Drupal\Core\Menu\MenuActiveTrail
29 protected $menuActiveTrail;
32 * The test request stack.
34 * @var \Symfony\Component\HttpFoundation\RequestStack
36 protected $requestStack;
39 * The current route match service.
41 * @var \Drupal\Core\Routing\CurrentRouteMatch
43 protected $currentRouteMatch;
46 * The mocked menu link manager.
48 * @var \Drupal\Core\Menu\MenuLinkManagerInterface|\PHPUnit_Framework_MockObject_MockObject
50 protected $menuLinkManager;
53 * The mocked cache backend.
55 * @var \Drupal\Core\Cache\CacheBackendInterface|\PHPUnit_Framework_MockObject_MockObject
62 * @var \Drupal\Core\Lock\LockBackendInterface|\PHPUnit_Framework_MockObject_MockObject
69 protected function setUp() {
72 $this->requestStack = new RequestStack();
73 $this->currentRouteMatch = new CurrentRouteMatch($this->requestStack);
74 $this->menuLinkManager = $this->getMock('Drupal\Core\Menu\MenuLinkManagerInterface');
75 $this->cache = $this->getMock('\Drupal\Core\Cache\CacheBackendInterface');
76 $this->lock = $this->getMock('\Drupal\Core\Lock\LockBackendInterface');
78 $this->menuActiveTrail = new MenuActiveTrail($this->menuLinkManager, $this->currentRouteMatch, $this->cache, $this->lock);
80 $container = new Container();
81 $container->set('cache_tags.invalidator', $this->getMock('\Drupal\Core\Cache\CacheTagsInvalidatorInterface'));
82 \Drupal::setContainer($container);
86 * Provides test data for all test methods.
89 * Returns a list of test data of which each is an array containing the
91 * - request: A request object.
92 * - links: An array of menu links keyed by ID.
93 * - menu_name: The active menu name.
94 * - expected_link: The expected active link for the given menu.
96 public function provider() {
99 $mock_route = new Route('');
101 $request = new Request();
102 $request->attributes->set(RouteObjectInterface::ROUTE_NAME, 'baby_llama');
103 $request->attributes->set(RouteObjectInterface::ROUTE_OBJECT, $mock_route);
104 $request->attributes->set('_raw_variables', new ParameterBag([]));
106 $link_1 = MenuLinkMock::create(['id' => 'baby_llama_link_1', 'route_name' => 'baby_llama', 'title' => 'Baby llama', 'parent' => 'mama_llama_link']);
107 $link_2 = MenuLinkMock::create(['id' => 'baby_llama_link_2', 'route_name' => 'baby_llama', 'title' => 'Baby llama', 'parent' => 'papa_llama_link']);
109 // @see \Drupal\Core\Menu\MenuLinkManagerInterface::getParentIds()
110 $link_1_parent_ids = ['baby_llama_link_1', 'mama_llama_link', ''];
111 $empty_active_trail = [''];
113 // No active link is returned when zero links match the current route.
114 $data[] = [$request, [], $this->randomMachineName(), NULL, $empty_active_trail];
116 // The first (and only) matching link is returned when one link matches the
118 $data[] = [$request, ['baby_llama_link_1' => $link_1], $this->randomMachineName(), $link_1, $link_1_parent_ids];
120 // The first of multiple matching links is returned when multiple links
121 // match the current route, where "first" is determined by sorting by key.
122 $data[] = [$request, ['baby_llama_link_1' => $link_1, 'baby_llama_link_2' => $link_2], $this->randomMachineName(), $link_1, $link_1_parent_ids];
124 // No active link is returned in case of a 403.
125 $request = new Request();
126 $request->attributes->set('_exception_statuscode', 403);
127 $data[] = [$request, FALSE, $this->randomMachineName(), NULL, $empty_active_trail];
129 // No active link is returned when the route name is missing.
130 $request = new Request();
131 $data[] = [$request, FALSE, $this->randomMachineName(), NULL, $empty_active_trail];
137 * Tests getActiveLink().
139 * @covers ::getActiveLink
140 * @dataProvider provider
142 public function testGetActiveLink(Request $request, $links, $menu_name, $expected_link) {
143 $this->requestStack->push($request);
144 if ($links !== FALSE) {
145 $this->menuLinkManager->expects($this->exactly(2))
146 ->method('loadLinksbyRoute')
148 ->will($this->returnValue($links));
150 // Test with menu name.
151 $this->assertSame($expected_link, $this->menuActiveTrail->getActiveLink($menu_name));
152 // Test without menu name.
153 $this->assertSame($expected_link, $this->menuActiveTrail->getActiveLink());
157 * Tests getActiveTrailIds().
159 * @covers ::getActiveTrailIds
160 * @dataProvider provider
162 public function testGetActiveTrailIds(Request $request, $links, $menu_name, $expected_link, $expected_trail) {
163 $expected_trail_ids = array_combine($expected_trail, $expected_trail);
165 $this->requestStack->push($request);
166 if ($links !== FALSE) {
167 // We expect exactly two calls, one for the first call, and one after the
168 // cache clearing below.
169 $this->menuLinkManager->expects($this->exactly(2))
170 ->method('loadLinksbyRoute')
172 ->will($this->returnValue($links));
173 if ($expected_link !== NULL) {
174 $this->menuLinkManager->expects($this->exactly(2))
175 ->method('getParentIds')
176 ->will($this->returnValueMap([
177 [$expected_link->getPluginId(), $expected_trail_ids],
182 // Call out the same twice in order to ensure that static caching works.
183 $this->assertSame($expected_trail_ids, $this->menuActiveTrail->getActiveTrailIds($menu_name));
184 $this->assertSame($expected_trail_ids, $this->menuActiveTrail->getActiveTrailIds($menu_name));
186 $this->menuActiveTrail->clear();
187 $this->assertSame($expected_trail_ids, $this->menuActiveTrail->getActiveTrailIds($menu_name));
195 public function testGetCid() {
196 $data = $this->provider()[1];
197 /** @var \Symfony\Component\HttpFoundation\Request $request */
199 /** @var \Symfony\Component\Routing\Route $route */
200 $route = $request->attributes->get(RouteObjectInterface::ROUTE_OBJECT);
201 $route->setPath('/test/{b}/{a}');
202 $request->attributes->get('_raw_variables')->add(['b' => 1, 'a' => 0]);
203 $this->requestStack->push($request);
205 $this->menuLinkManager->expects($this->any())
206 ->method('loadLinksbyRoute')
208 ->will($this->returnValue($data[1]));
210 $expected_link = $data[3];
211 $expected_trail = $data[4];
212 $expected_trail_ids = array_combine($expected_trail, $expected_trail);
214 $this->menuLinkManager->expects($this->any())
215 ->method('getParentIds')
216 ->will($this->returnValueMap([
217 [$expected_link->getPluginId(), $expected_trail_ids],
220 $this->assertSame($expected_trail_ids, $this->menuActiveTrail->getActiveTrailIds($data[2]));
222 $this->cache->expects($this->once())
224 // Ensure we normalize the serialized data by sorting them.
225 ->with('active-trail:route:baby_llama:route_parameters:' . serialize(['a' => 0, 'b' => 1]));
226 $this->lock->expects($this->any())
229 $this->menuActiveTrail->destruct();