6a9825e1208f2b1dde2f93e44bb9214e19a61227
[yaffs-website] / web / core / modules / system / tests / src / Kernel / Block / SystemMenuBlockTest.php
1 <?php
2
3 namespace Drupal\Tests\system\Kernel\Block;
4
5 use Drupal\KernelTests\KernelTestBase;
6 use Drupal\system\Entity\Menu;
7 use Drupal\block\Entity\Block;
8 use Drupal\Core\Render\Element;
9 use Drupal\system\Tests\Routing\MockRouteProvider;
10 use Drupal\Tests\Core\Menu\MenuLinkMock;
11 use Drupal\user\Entity\User;
12 use Symfony\Cmf\Component\Routing\RouteObjectInterface;
13 use Symfony\Component\HttpFoundation\Request;
14 use Symfony\Component\Routing\Route;
15 use Symfony\Component\Routing\RouteCollection;
16
17 /**
18  * Tests \Drupal\system\Plugin\Block\SystemMenuBlock.
19  *
20  * @group Block
21  * @todo Expand test coverage to all SystemMenuBlock functionality, including
22  *   block_menu_delete().
23  *
24  * @see \Drupal\system\Plugin\Derivative\SystemMenuBlock
25  * @see \Drupal\system\Plugin\Block\SystemMenuBlock
26  */
27 class SystemMenuBlockTest extends KernelTestBase {
28
29   /**
30    * Modules to enable.
31    *
32    * @var array
33    */
34   public static $modules = [
35     'system',
36     'block',
37     'menu_test',
38     'menu_link_content',
39     'field',
40     'user',
41     'link',
42   ];
43
44   /**
45    * The block under test.
46    *
47    * @var \Drupal\system\Plugin\Block\SystemMenuBlock
48    */
49   protected $block;
50
51   /**
52    * The menu for testing.
53    *
54    * @var \Drupal\system\MenuInterface
55    */
56   protected $menu;
57
58   /**
59    * The menu link tree service.
60    *
61    * @var \Drupal\Core\Menu\MenuLinkTree
62    */
63   protected $linkTree;
64
65   /**
66    * The menu link plugin manager service.
67    *
68    * @var \Drupal\Core\Menu\MenuLinkManagerInterface $menuLinkManager
69    */
70   protected $menuLinkManager;
71
72   /**
73    * The block manager service.
74    *
75    * @var \Drupal\Core\block\BlockManagerInterface
76    */
77   protected $blockManager;
78
79   /**
80    * {@inheritdoc}
81    */
82   protected function setUp() {
83     parent::setUp();
84     $this->installSchema('system', 'sequences');
85     $this->installEntitySchema('user');
86     $this->installEntitySchema('menu_link_content');
87
88     $account = User::create([
89       'name' => $this->randomMachineName(),
90       'status' => 1,
91     ]);
92     $account->save();
93     $this->container->get('current_user')->setAccount($account);
94
95     $this->menuLinkManager = $this->container->get('plugin.manager.menu.link');
96     $this->linkTree = $this->container->get('menu.link_tree');
97     $this->blockManager = $this->container->get('plugin.manager.block');
98
99     $routes = new RouteCollection();
100     $requirements = ['_access' => 'TRUE'];
101     $options = ['_access_checks' => ['access_check.default']];
102     $routes->add('example1', new Route('/example1', [], $requirements, $options));
103     $routes->add('example2', new Route('/example2', [], $requirements, $options));
104     $routes->add('example3', new Route('/example3', [], $requirements, $options));
105     $routes->add('example4', new Route('/example4', [], $requirements, $options));
106     $routes->add('example5', new Route('/example5', [], $requirements, $options));
107     $routes->add('example6', new Route('/example6', [], $requirements, $options));
108     $routes->add('example7', new Route('/example7', [], $requirements, $options));
109     $routes->add('example8', new Route('/example8', [], $requirements, $options));
110
111     $mock_route_provider = new MockRouteProvider($routes);
112     $this->container->set('router.route_provider', $mock_route_provider);
113
114     // Add a new custom menu.
115     $menu_name = 'mock';
116     $label = $this->randomMachineName(16);
117
118     $this->menu = Menu::create([
119       'id' => $menu_name,
120       'label' => $label,
121       'description' => 'Description text',
122     ]);
123     $this->menu->save();
124
125     // This creates a tree with the following structure:
126     // - 1
127     // - 2
128     //   - 3
129     //     - 4
130     // - 5
131     //   - 7
132     // - 6
133     // - 8
134     // With link 6 being the only external link.
135     $links = [
136       1 => MenuLinkMock::create(['id' => 'test.example1', 'route_name' => 'example1', 'title' => 'foo', 'parent' => '', 'weight' => 0]),
137       2 => MenuLinkMock::create(['id' => 'test.example2', 'route_name' => 'example2', 'title' => 'bar', 'parent' => '', 'route_parameters' => ['foo' => 'bar'], 'weight' => 1]),
138       3 => MenuLinkMock::create(['id' => 'test.example3', 'route_name' => 'example3', 'title' => 'baz', 'parent' => 'test.example2', 'weight' => 2]),
139       4 => MenuLinkMock::create(['id' => 'test.example4', 'route_name' => 'example4', 'title' => 'qux', 'parent' => 'test.example3', 'weight' => 3]),
140       5 => MenuLinkMock::create(['id' => 'test.example5', 'route_name' => 'example5', 'title' => 'foofoo', 'parent' => '', 'expanded' => TRUE, 'weight' => 4]),
141       6 => MenuLinkMock::create(['id' => 'test.example6', 'route_name' => '', 'url' => 'https://www.drupal.org/', 'title' => 'barbar', 'parent' => '', 'weight' => 5]),
142       7 => MenuLinkMock::create(['id' => 'test.example7', 'route_name' => 'example7', 'title' => 'bazbaz', 'parent' => 'test.example5', 'weight' => 6]),
143       8 => MenuLinkMock::create(['id' => 'test.example8', 'route_name' => 'example8', 'title' => 'quxqux', 'parent' => '', 'weight' => 7]),
144     ];
145     foreach ($links as $instance) {
146       $this->menuLinkManager->addDefinition($instance->getPluginId(), $instance->getPluginDefinition());
147     }
148   }
149
150   /**
151    * Tests calculation of a system menu block's configuration dependencies.
152    */
153   public function testSystemMenuBlockConfigDependencies() {
154
155     $block = Block::create([
156       'plugin' => 'system_menu_block:' . $this->menu->id(),
157       'region' => 'footer',
158       'id' => 'machinename',
159       'theme' => 'stark',
160     ]);
161
162     $dependencies = $block->calculateDependencies()->getDependencies();
163     $expected = [
164       'config' => [
165         'system.menu.' . $this->menu->id()
166       ],
167       'module' => [
168         'system'
169       ],
170       'theme' => [
171         'stark'
172       ],
173     ];
174     $this->assertIdentical($expected, $dependencies);
175   }
176
177   /**
178    * Tests the config start level and depth.
179    */
180   public function testConfigLevelDepth() {
181     // Helper function to generate a configured block instance.
182     $place_block = function ($level, $depth) {
183       return $this->blockManager->createInstance('system_menu_block:' . $this->menu->id(), [
184         'region' => 'footer',
185         'id' => 'machinename',
186         'theme' => 'stark',
187         'level' => $level,
188         'depth' => $depth,
189       ]);
190     };
191
192     // All the different block instances we're going to test.
193     $blocks = [
194       'all' => $place_block(1, 0),
195       'level_1_only' => $place_block(1, 1),
196       'level_2_only' => $place_block(2, 1),
197       'level_3_only' => $place_block(3, 1),
198       'level_1_and_beyond' => $place_block(1, 0),
199       'level_2_and_beyond' => $place_block(2, 0),
200       'level_3_and_beyond' => $place_block(3, 0),
201     ];
202
203     // Scenario 1: test all block instances when there's no active trail.
204     $no_active_trail_expectations = [];
205     $no_active_trail_expectations['all'] = [
206       'test.example1' => [],
207       'test.example2' => [],
208       'test.example5' => [
209         'test.example7' => [],
210        ],
211       'test.example6' => [],
212       'test.example8' => [],
213     ];
214     $no_active_trail_expectations['level_1_only'] = [
215       'test.example1' => [],
216       'test.example2' => [],
217       'test.example5' => [],
218       'test.example6' => [],
219       'test.example8' => [],
220     ];
221     $no_active_trail_expectations['level_2_only'] = [
222       'test.example7' => [],
223     ];
224     $no_active_trail_expectations['level_3_only'] = [];
225     $no_active_trail_expectations['level_1_and_beyond'] = $no_active_trail_expectations['all'];
226     $no_active_trail_expectations['level_2_and_beyond'] = $no_active_trail_expectations['level_2_only'];
227     $no_active_trail_expectations['level_3_and_beyond'] = [];
228     foreach ($blocks as $id => $block) {
229       $block_build = $block->build();
230       $items = isset($block_build['#items']) ? $block_build['#items'] : [];
231       $this->assertIdentical($no_active_trail_expectations[$id], $this->convertBuiltMenuToIdTree($items), format_string('Menu block %id with no active trail renders the expected tree.', ['%id' => $id]));
232     }
233
234     // Scenario 2: test all block instances when there's an active trail.
235     $route = $this->container->get('router.route_provider')->getRouteByName('example3');
236     $request = new Request();
237     $request->attributes->set(RouteObjectInterface::ROUTE_NAME, 'example3');
238     $request->attributes->set(RouteObjectInterface::ROUTE_OBJECT, $route);
239     $this->container->get('request_stack')->push($request);
240     // \Drupal\Core\Menu\MenuActiveTrail uses the cache collector pattern, which
241     // includes static caching. Since this second scenario simulates a second
242     // request, we must also simulate it for the MenuActiveTrail service, by
243     // clearing the cache collector's static cache.
244     \Drupal::service('menu.active_trail')->clear();
245
246     $active_trail_expectations = [];
247     $active_trail_expectations['all'] = [
248       'test.example1' => [],
249       'test.example2' => [
250         'test.example3' => [
251           'test.example4' => [],
252         ]
253       ],
254       'test.example5' => [
255         'test.example7' => [],
256       ],
257       'test.example6' => [],
258       'test.example8' => [],
259     ];
260     $active_trail_expectations['level_1_only'] = [
261       'test.example1' => [],
262       'test.example2' => [],
263       'test.example5' => [],
264       'test.example6' => [],
265       'test.example8' => [],
266     ];
267     $active_trail_expectations['level_2_only'] = [
268       'test.example3' => [],
269       'test.example7' => [],
270     ];
271     $active_trail_expectations['level_3_only'] = [
272       'test.example4' => [],
273     ];
274     $active_trail_expectations['level_1_and_beyond'] = $active_trail_expectations['all'];
275     $active_trail_expectations['level_2_and_beyond'] = [
276       'test.example3' => [
277         'test.example4' => [],
278       ],
279       'test.example7' => [],
280     ];
281     $active_trail_expectations['level_3_and_beyond'] = $active_trail_expectations['level_3_only'];
282     foreach ($blocks as $id => $block) {
283       $block_build = $block->build();
284       $items = isset($block_build['#items']) ? $block_build['#items'] : [];
285       $this->assertIdentical($active_trail_expectations[$id], $this->convertBuiltMenuToIdTree($items), format_string('Menu block %id with an active trail renders the expected tree.', ['%id' => $id]));
286     }
287   }
288
289   /**
290    * Helper method to allow for easy menu link tree structure assertions.
291    *
292    * Converts the result of MenuLinkTree::build() in a "menu link ID tree".
293    *
294    * @param array $build
295    *   The return value of MenuLinkTree::build()
296    *
297    * @return array
298    *   The "menu link ID tree" representation of the given render array.
299    */
300   protected function convertBuiltMenuToIdTree(array $build) {
301     $level = [];
302     foreach (Element::children($build) as $id) {
303       $level[$id] = [];
304       if (isset($build[$id]['below'])) {
305         $level[$id] = $this->convertBuiltMenuToIdTree($build[$id]['below']);
306       }
307     }
308     return $level;
309   }
310
311 }