1a49336777ed8f82939303781a21b86d5404d2a2
[yaffs-website] / web / core / modules / system / tests / src / Functional / Menu / LocalTasksTest.php
1 <?php
2
3 namespace Drupal\Tests\system\Functional\Menu;
4
5 use Drupal\Component\Utility\Html;
6 use Drupal\Core\Url;
7 use Drupal\Tests\BrowserTestBase;
8
9 /**
10  * Tests local tasks derived from router and added/altered via hooks.
11  *
12  * @group Menu
13  */
14 class LocalTasksTest extends BrowserTestBase {
15
16   /**
17    * Modules to enable.
18    *
19    * @var string[]
20    */
21   public static $modules = ['block', 'menu_test', 'entity_test', 'node'];
22
23   /**
24    * The local tasks block under testing.
25    *
26    * @var \Drupal\block\Entity\Block
27    */
28   protected $sut;
29
30   /**
31    * {@inheritdoc}
32    */
33   protected function setUp() {
34     parent::setUp();
35
36     $this->sut = $this->drupalPlaceBlock('local_tasks_block', ['id' => 'tabs_block']);
37   }
38
39   /**
40    * Asserts local tasks in the page output.
41    *
42    * @param array $routes
43    *   A list of expected local tasks, prepared as an array of route names and
44    *   their associated route parameters, to assert on the page (in the given
45    *   order).
46    * @param int $level
47    *   (optional) The local tasks level to assert; 0 for primary, 1 for
48    *   secondary. Defaults to 0.
49    */
50   protected function assertLocalTasks(array $routes, $level = 0) {
51     $elements = $this->xpath('//*[contains(@class, :class)]//a', [
52       ':class' => $level == 0 ? 'tabs primary' : 'tabs secondary',
53     ]);
54     $this->assertTrue(count($elements), 'Local tasks found.');
55     foreach ($routes as $index => $route_info) {
56       list($route_name, $route_parameters) = $route_info;
57       $expected = Url::fromRoute($route_name, $route_parameters)->toString();
58       $method = ($elements[$index]->getAttribute('href') == $expected ? 'pass' : 'fail');
59       $this->{$method}(format_string('Task @number href @value equals @expected.', [
60         '@number' => $index + 1,
61         '@value' => $elements[$index]->getAttribute('href'),
62         '@expected' => $expected,
63       ]));
64     }
65   }
66
67   /**
68    * Ensures that some local task appears.
69    *
70    * @param string $title
71    *   The expected title.
72    *
73    * @return bool
74    *   TRUE if the local task exists on the page.
75    */
76   protected function assertLocalTaskAppers($title) {
77     // SimpleXML gives us the unescaped text, not the actual escaped markup,
78     // so use a pattern instead to check the raw content.
79     // This behaviour is a bug in libxml, see
80     // https://bugs.php.net/bug.php?id=49437.
81     return $this->assertPattern('@<a [^>]*>' . preg_quote($title, '@') . '</a>@');
82   }
83
84   /**
85    * Asserts that the local tasks on the specified level are not being printed.
86    *
87    * @param int $level
88    *   (optional) The local tasks level to assert; 0 for primary, 1 for
89    *   secondary. Defaults to 0.
90    */
91   protected function assertNoLocalTasks($level = 0) {
92     $elements = $this->xpath('//*[contains(@class, :class)]//a', [
93       ':class' => $level == 0 ? 'tabs primary' : 'tabs secondary',
94     ]);
95     $this->assertFalse(count($elements), 'Local tasks not found.');
96   }
97
98   /**
99    * Tests the plugin based local tasks.
100    */
101   public function testPluginLocalTask() {
102     // Verify local tasks defined in the hook.
103     $this->drupalGet(Url::fromRoute('menu_test.tasks_default'));
104     $this->assertLocalTasks([
105       ['menu_test.tasks_default', []],
106       ['menu_test.router_test1', ['bar' => 'unsafe']],
107       ['menu_test.router_test1', ['bar' => '1']],
108       ['menu_test.router_test2', ['bar' => '2']],
109     ]);
110
111     // Verify that script tags are escaped on output.
112     $title = Html::escape("Task 1 <script>alert('Welcome to the jungle!')</script>");
113     $this->assertLocalTaskAppers($title);
114     $title = Html::escape("<script>alert('Welcome to the derived jungle!')</script>");
115     $this->assertLocalTaskAppers($title);
116
117     // Verify that local tasks appear as defined in the router.
118     $this->drupalGet(Url::fromRoute('menu_test.local_task_test_tasks_view'));
119     $this->assertLocalTasks([
120       ['menu_test.local_task_test_tasks_view', []],
121       ['menu_test.local_task_test_tasks_edit', []],
122       ['menu_test.local_task_test_tasks_settings', []],
123       ['menu_test.local_task_test_tasks_settings_dynamic', []],
124     ]);
125
126     $title = Html::escape("<script>alert('Welcome to the jungle!')</script>");
127     $this->assertLocalTaskAppers($title);
128
129     // Ensure the view tab is active.
130     $result = $this->xpath('//ul[contains(@class, "tabs")]//li[contains(@class, "active")]/a');
131     $this->assertEqual(1, count($result), 'There is just a single active tab.');
132     $this->assertEqual('View(active tab)', $result[0]->getText(), 'The view tab is active.');
133
134     // Verify that local tasks in the second level appear.
135     $sub_tasks = [
136       ['menu_test.local_task_test_tasks_settings_sub1', []],
137       ['menu_test.local_task_test_tasks_settings_sub2', []],
138       ['menu_test.local_task_test_tasks_settings_sub3', []],
139       ['menu_test.local_task_test_tasks_settings_derived', ['placeholder' => 'derive1']],
140       ['menu_test.local_task_test_tasks_settings_derived', ['placeholder' => 'derive2']],
141     ];
142     $this->drupalGet(Url::fromRoute('menu_test.local_task_test_tasks_settings'));
143     $this->assertLocalTasks($sub_tasks, 1);
144
145     $result = $this->xpath('//ul[contains(@class, "tabs")]//li[contains(@class, "active")]/a');
146     $this->assertEqual(1, count($result), 'There is just a single active tab.');
147     $this->assertEqual('Settings(active tab)', $result[0]->getText(), 'The settings tab is active.');
148
149     $this->drupalGet(Url::fromRoute('menu_test.local_task_test_tasks_settings_sub1'));
150     $this->assertLocalTasks($sub_tasks, 1);
151
152     $result = $this->xpath('//ul[contains(@class, "tabs")]//a[contains(@class, "active")]');
153     $this->assertEqual(2, count($result), 'There are tabs active on both levels.');
154     $this->assertEqual('Settings(active tab)', $result[0]->getText(), 'The settings tab is active.');
155     $this->assertEqual('Dynamic title for TestTasksSettingsSub1(active tab)', $result[1]->getText(), 'The sub1 tab is active.');
156
157     $this->assertCacheTag('kittens:ragdoll');
158     $this->assertCacheTag('kittens:dwarf-cat');
159
160     $this->drupalGet(Url::fromRoute('menu_test.local_task_test_tasks_settings_derived', ['placeholder' => 'derive1']));
161     $this->assertLocalTasks($sub_tasks, 1);
162
163     $result = $this->xpath('//ul[contains(@class, "tabs")]//li[contains(@class, "active")]');
164     $this->assertEqual(2, count($result), 'There are tabs active on both levels.');
165     $this->assertEqual('Settings(active tab)', $result[0]->getText(), 'The settings tab is active.');
166     $this->assertEqual('Derive 1(active tab)', $result[1]->getText(), 'The derive1 tab is active.');
167
168     // Ensures that the local tasks contains the proper 'provider key'
169     $definitions = $this->container->get('plugin.manager.menu.local_task')->getDefinitions();
170     $this->assertEqual($definitions['menu_test.local_task_test_tasks_view']['provider'], 'menu_test');
171     $this->assertEqual($definitions['menu_test.local_task_test_tasks_edit']['provider'], 'menu_test');
172     $this->assertEqual($definitions['menu_test.local_task_test_tasks_settings']['provider'], 'menu_test');
173     $this->assertEqual($definitions['menu_test.local_task_test_tasks_settings_sub1']['provider'], 'menu_test');
174     $this->assertEqual($definitions['menu_test.local_task_test_tasks_settings_sub2']['provider'], 'menu_test');
175     $this->assertEqual($definitions['menu_test.local_task_test_tasks_settings_sub3']['provider'], 'menu_test');
176
177     // Test that we we correctly apply the active class to tabs where one of the
178     // request attributes is upcast to an entity object.
179     $entity = \Drupal::entityManager()->getStorage('entity_test')->create(['bundle' => 'test']);
180     $entity->save();
181
182     $this->drupalGet(Url::fromRoute('menu_test.local_task_test_upcasting_sub1', ['entity_test' => '1']));
183
184     $tasks = [
185       ['menu_test.local_task_test_upcasting_sub1', ['entity_test' => '1']],
186       ['menu_test.local_task_test_upcasting_sub2', ['entity_test' => '1']],
187     ];
188
189     $this->assertLocalTasks($tasks, 0);
190
191     $result = $this->xpath('//ul[contains(@class, "tabs")]//li[contains(@class, "active")]');
192     $this->assertEqual(1, count($result), 'There is one active tab.');
193     $this->assertEqual('upcasting sub1(active tab)', $result[0]->getText(), 'The "upcasting sub1" tab is active.');
194
195     $this->drupalGet(Url::fromRoute('menu_test.local_task_test_upcasting_sub2', ['entity_test' => '1']));
196
197     $tasks = [
198       ['menu_test.local_task_test_upcasting_sub1', ['entity_test' => '1']],
199       ['menu_test.local_task_test_upcasting_sub2', ['entity_test' => '1']],
200     ];
201     $this->assertLocalTasks($tasks, 0);
202
203     $result = $this->xpath('//ul[contains(@class, "tabs")]//li[contains(@class, "active")]');
204     $this->assertEqual(1, count($result), 'There is one active tab.');
205     $this->assertEqual('upcasting sub2(active tab)', $result[0]->getText(), 'The "upcasting sub2" tab is active.');
206   }
207
208   /**
209    * Tests that local task blocks are configurable to show a specific level.
210    */
211   public function testLocalTaskBlock() {
212     // Remove the default block and create a new one.
213     $this->sut->delete();
214
215     $this->sut = $this->drupalPlaceBlock('local_tasks_block', [
216       'id' => 'tabs_block',
217       'primary' => TRUE,
218       'secondary' => FALSE,
219     ]);
220
221     $this->drupalGet(Url::fromRoute('menu_test.local_task_test_tasks_settings'));
222
223     // Verify that local tasks in the first level appear.
224     $this->assertLocalTasks([
225       ['menu_test.local_task_test_tasks_view', []],
226       ['menu_test.local_task_test_tasks_edit', []],
227       ['menu_test.local_task_test_tasks_settings', []],
228     ]);
229
230     // Verify that local tasks in the second level doesn't appear.
231     $this->assertNoLocalTasks(1);
232
233     $this->sut->delete();
234     $this->sut = $this->drupalPlaceBlock('local_tasks_block', [
235       'id' => 'tabs_block',
236       'primary' => FALSE,
237       'secondary' => TRUE,
238     ]);
239
240     $this->drupalGet(Url::fromRoute('menu_test.local_task_test_tasks_settings'));
241
242     // Verify that local tasks in the first level doesn't appear.
243     $this->assertNoLocalTasks(0);
244
245     // Verify that local tasks in the second level appear.
246     $sub_tasks = [
247       ['menu_test.local_task_test_tasks_settings_sub1', []],
248       ['menu_test.local_task_test_tasks_settings_sub2', []],
249       ['menu_test.local_task_test_tasks_settings_sub3', []],
250       ['menu_test.local_task_test_tasks_settings_derived', ['placeholder' => 'derive1']],
251       ['menu_test.local_task_test_tasks_settings_derived', ['placeholder' => 'derive2']],
252     ];
253     $this->assertLocalTasks($sub_tasks, 1);
254   }
255
256   /**
257    * Test that local tasks blocks cache is invalidated correctly.
258    */
259   public function testLocalTaskBlockCache() {
260     $this->drupalLogin($this->rootUser);
261     $this->drupalCreateContentType(['type' => 'page']);
262
263     $this->drupalGet('/admin/structure/types/manage/page');
264
265     // Only the Edit task. The block avoids showing a single tab.
266     $this->assertNoLocalTasks();
267
268     // Field UI adds the usual Manage fields etc tabs.
269     \Drupal::service('module_installer')->install(['field_ui']);
270
271     $this->drupalGet('/admin/structure/types/manage/page');
272
273     $this->assertLocalTasks([
274       ['entity.node_type.edit_form', ['node_type' => 'page']],
275       ['entity.node.field_ui_fields', ['node_type' => 'page']],
276       ['entity.entity_form_display.node.default', ['node_type' => 'page']],
277       ['entity.entity_view_display.node.default', ['node_type' => 'page']],
278     ]);
279   }
280
281 }