3 namespace Drupal\toolbar\Tests;
5 use Drupal\Core\Language\LanguageInterface;
7 use Drupal\language\Entity\ConfigurableLanguage;
8 use Drupal\simpletest\WebTestBase;
9 use Drupal\user\RoleInterface;
12 * Tests the caching of the admin menu subtree items.
14 * The cache of the admin menu subtree items will be invalidated if the
15 * following hooks are invoked.
17 * toolbar_modules_enabled()
18 * toolbar_modules_disabled()
19 * toolbar_menu_link_update()
20 * toolbar_user_update()
21 * toolbar_user_role_update()
23 * Each hook invocation is simulated and then the previous hash of the admin
24 * menu subtrees is compared to the new hash.
28 class ToolbarAdminMenuTest extends WebTestBase {
31 * A user with permission to access the administrative toolbar.
33 * @var \Drupal\user\UserInterface
38 * A second user with permission to access the administrative toolbar.
40 * @var \Drupal\user\UserInterface
42 protected $adminUser2;
45 * The current admin menu subtrees hash for adminUser.
56 public static $modules = ['node', 'block', 'menu_ui', 'user', 'taxonomy', 'toolbar', 'language', 'test_page_test', 'locale'];
58 protected function setUp() {
63 'access administration pages',
64 'administer site configuration',
68 'access content overview',
72 'administer permissions',
74 'access user profiles',
75 'administer taxonomy',
76 'administer languages',
77 'translate interface',
80 // Create an administrative user and log it in.
81 $this->adminUser = $this->drupalCreateUser($perms);
82 $this->adminUser2 = $this->drupalCreateUser($perms);
84 $this->drupalLogin($this->adminUser);
86 $this->drupalGet('test-page');
87 $this->assertResponse(200);
89 // Assert that the toolbar is present in the HTML.
90 $this->assertRaw('id="toolbar-administration"');
92 // Store the adminUser admin menu subtrees hash for comparison later.
93 $this->hash = $this->getSubtreesHash();
97 * Tests the toolbar_modules_installed() and toolbar_modules_uninstalled() hook
100 public function testModuleStatusChangeSubtreesHashCacheClear() {
101 // Uninstall a module.
103 $edit['uninstall[taxonomy]'] = TRUE;
104 $this->drupalPostForm('admin/modules/uninstall', $edit, t('Uninstall'));
105 // Confirm the uninstall form.
106 $this->drupalPostForm(NULL, [], t('Uninstall'));
107 $this->rebuildContainer();
109 // Assert that the subtrees hash has been altered because the subtrees
110 // structure changed.
111 $this->assertDifferentHash();
115 $edit['modules[taxonomy][enable]'] = TRUE;
116 $this->drupalPostForm('admin/modules', $edit, t('Install'));
117 $this->rebuildContainer();
119 // Assert that the subtrees hash has been altered because the subtrees
120 // structure changed.
121 $this->assertDifferentHash();
125 * Tests toolbar cache tags implementation.
127 public function testMenuLinkUpdateSubtreesHashCacheClear() {
128 // The ID of a (any) admin menu link.
129 $admin_menu_link_id = 'system.admin_config_development';
133 $edit['enabled'] = FALSE;
134 $this->drupalPostForm("admin/structure/menu/link/" . $admin_menu_link_id . "/edit", $edit, t('Save'));
135 $this->assertResponse(200);
136 $this->assertText('The menu link has been saved.');
138 // Assert that the subtrees hash has been altered because the subtrees
139 // structure changed.
140 $this->assertDifferentHash();
144 * Exercises the toolbar_user_role_update() and toolbar_user_update() hook
147 public function testUserRoleUpdateSubtreesHashCacheClear() {
148 // Find the new role ID.
149 $all_rids = $this->adminUser->getRoles();
150 unset($all_rids[array_search(RoleInterface::AUTHENTICATED_ID, $all_rids)]);
151 $rid = reset($all_rids);
154 $edit[$rid . '[administer taxonomy]'] = FALSE;
155 $this->drupalPostForm('admin/people/permissions', $edit, t('Save permissions'));
157 // Assert that the subtrees hash has been altered because the subtrees
158 // structure changed.
159 $this->assertDifferentHash();
161 // Test that assigning a user an extra role only affects that single user.
162 // Get the hash for a second user.
163 $this->drupalLogin($this->adminUser2);
164 $this->drupalGet('test-page');
165 $this->assertResponse(200);
167 // Assert that the toolbar is present in the HTML.
168 $this->assertRaw('id="toolbar-administration"');
170 $admin_user_2_hash = $this->getSubtreesHash();
172 // Log in the first admin user again.
173 $this->drupalLogin($this->adminUser);
174 $this->drupalGet('test-page');
175 $this->assertResponse(200);
177 // Assert that the toolbar is present in the HTML.
178 $this->assertRaw('id="toolbar-administration"');
180 $this->hash = $this->getSubtreesHash();
182 $rid = $this->drupalCreateRole(['administer content types']);
184 // Assign the role to the user.
185 $this->drupalPostForm('user/' . $this->adminUser->id() . '/edit', ["roles[$rid]" => $rid], t('Save'));
186 $this->assertText(t('The changes have been saved.'));
188 // Assert that the subtrees hash has been altered because the subtrees
189 // structure changed.
190 $this->assertDifferentHash();
192 // Log in the second user again and assert that their subtrees hash did not
194 $this->drupalLogin($this->adminUser2);
196 // Request a new page to refresh the drupalSettings object.
197 $this->drupalGet('test-page');
198 $this->assertResponse(200);
199 $new_subtree_hash = $this->getSubtreesHash();
201 // Assert that the old admin menu subtree hash and the new admin menu
202 // subtree hash are the same.
203 $this->assertTrue($new_subtree_hash, 'A valid hash value for the admin menu subtrees was created.');
204 $this->assertEqual($admin_user_2_hash, $new_subtree_hash, 'The user-specific subtree menu hash has not been updated.');
208 * Tests that changes to a user account by another user clears the changed
209 * account's toolbar cached, not the user's who took the action.
211 public function testNonCurrentUserAccountUpdates() {
212 $admin_user_id = $this->adminUser->id();
213 $this->hash = $this->getSubtreesHash();
215 // adminUser2 will add a role to adminUser.
216 $this->drupalLogin($this->adminUser2);
217 $rid = $this->drupalCreateRole(['administer content types']);
219 // Get the subtree hash for adminUser2 to check later that it has not
220 // changed. Request a new page to refresh the drupalSettings object.
221 $this->drupalGet('test-page');
222 $this->assertResponse(200);
223 $admin_user_2_hash = $this->getSubtreesHash();
225 // Assign the role to the user.
226 $this->drupalPostForm('user/' . $admin_user_id . '/edit', ["roles[$rid]" => $rid], t('Save'));
227 $this->assertText(t('The changes have been saved.'));
229 // Log in adminUser and assert that the subtrees hash has changed.
230 $this->drupalLogin($this->adminUser);
231 $this->assertDifferentHash();
233 // Log in adminUser2 to check that its subtrees hash has not changed.
234 $this->drupalLogin($this->adminUser2);
235 $new_subtree_hash = $this->getSubtreesHash();
237 // Assert that the old adminUser subtree hash and the new adminUser
238 // subtree hash are the same.
239 $this->assertTrue($new_subtree_hash, 'A valid hash value for the admin menu subtrees was created.');
240 $this->assertEqual($admin_user_2_hash, $new_subtree_hash, 'The user-specific subtree menu hash has not been updated.');
244 * Tests that toolbar cache is cleared when string translations are made.
246 public function testLocaleTranslationSubtreesHashCacheClear() {
247 $admin_user = $this->adminUser;
248 // User to translate and delete string.
249 $translate_user = $this->drupalCreateUser(['translate interface', 'access administration pages']);
251 // Create a new language with the langcode 'xx'.
253 // The English name for the language. This will be translated.
254 $name = $this->randomMachineName(16);
255 // This will be the translation of $name.
256 $translation = $this->randomMachineName(16);
258 // Add custom language.
259 $this->drupalLogin($admin_user);
261 'predefined_langcode' => 'custom',
262 'langcode' => $langcode,
264 'direction' => LanguageInterface::DIRECTION_LTR,
266 $this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add custom language'));
267 t($name, [], ['langcode' => $langcode]);
268 // Reset locale cache.
269 $this->container->get('string_translation')->reset();
270 $this->assertRaw('"edit-languages-' . $langcode . '-weight"', 'Language code found.');
271 $this->assertText(t($name), 'Test language added.');
273 // Have the adminUser request a page in the new language.
274 $this->drupalGet($langcode . '/test-page');
275 $this->assertResponse(200);
277 // Get a baseline hash for the admin menu subtrees before translating one
278 // of the menu link items.
279 $original_subtree_hash = $this->getSubtreesHash();
280 $this->assertTrue($original_subtree_hash, 'A valid hash value for the admin menu subtrees was created.');
281 $this->drupalLogout();
283 // Translate the string 'Search and metadata' in the xx language. This
284 // string appears in a link in the admin menu subtrees. Changing the string
285 // should create a new menu hash if the toolbar subtrees cache is correctly
287 $this->drupalLogin($translate_user);
289 'string' => 'Search and metadata',
290 'langcode' => $langcode,
291 'translation' => 'untranslated',
293 $this->drupalPostForm('admin/config/regional/translate', $search, t('Filter'));
294 $this->assertNoText(t('No strings available'));
295 $this->assertText($name, 'Search found the string as untranslated.');
297 // Assume this is the only result.
298 // Translate the string to a random string.
299 $textarea = current($this->xpath('//textarea'));
300 $lid = (string) $textarea[0]['name'];
302 $lid => $translation,
304 $this->drupalPostForm('admin/config/regional/translate', $edit, t('Save translations'));
305 $this->assertText(t('The strings have been saved.'), 'The strings have been saved.');
306 $this->assertUrl(\Drupal::url('locale.translate_page', [], ['absolute' => TRUE]), [], 'Correct page redirection.');
307 $this->drupalLogout();
309 // Log in the adminUser. Check the admin menu subtrees hash now that one
310 // of the link items in the Structure tree (Menus) has had its text
312 $this->drupalLogin($admin_user);
313 // Have the adminUser request a page in the new language.
314 $this->drupalGet($langcode . '/test-page');
315 $this->assertResponse(200);
316 $new_subtree_hash = $this->getSubtreesHash();
318 // Assert that the old admin menu subtrees hash and the new admin menu
319 // subtrees hash are different.
320 $this->assertTrue($new_subtree_hash, 'A valid hash value for the admin menu subtrees was created.');
321 $this->assertNotEqual($original_subtree_hash, $new_subtree_hash, 'The user-specific subtree menu hash has been updated.');
325 * Tests that the 'toolbar/subtrees/{hash}' is reachable and correct.
327 public function testSubtreesJsonRequest() {
328 $admin_user = $this->adminUser;
329 $this->drupalLogin($admin_user);
330 // Request a new page to refresh the drupalSettings object.
331 $subtrees_hash = $this->getSubtreesHash();
333 $ajax_result = $this->drupalGetAjax('toolbar/subtrees/' . $subtrees_hash);
334 $this->assertResponse('200');
335 $this->assertEqual($ajax_result[0]['command'], 'setToolbarSubtrees', 'Subtrees response uses the correct command.');
336 $this->assertEqual(array_keys($ajax_result[0]['subtrees']), ['system-admin_content', 'system-admin_structure', 'system-themes_page', 'system-modules_list', 'system-admin_config', 'entity-user-collection', 'front'], 'Correct subtrees returned.');
340 * Test that subtrees hashes vary by the language of the page.
342 public function testLanguageSwitching() {
343 // Create a new language with the langcode 'xx'.
345 $language = ConfigurableLanguage::createFromLangcode($langcode);
347 // The language path processor is just registered for more than one
348 // configured language, so rebuild the container now that we are
350 $this->rebuildContainer();
352 // Get a page with the new language langcode in the URL.
353 $this->drupalGet('test-page', ['language' => $language]);
354 // Assert different hash.
355 $new_subtree_hash = $this->getSubtreesHash();
357 // Assert that the old admin menu subtree hash and the new admin menu
358 // subtree hash are different.
359 $this->assertTrue($new_subtree_hash, 'A valid hash value for the admin menu subtrees was created.');
360 $this->assertNotEqual($this->hash, $new_subtree_hash, 'The user-specific subtree menu hash has been updated.');
364 * Test that back to site link exists on admin pages, not on content pages.
366 public function testBackToSiteLink() {
367 // Back to site link should exist in the markup.
368 $this->drupalGet('test-page');
369 $back_link = $this->cssSelect('.home-toolbar-tab');
370 $this->assertTrue($back_link);
374 * Tests that external links added to the menu appear in the toolbar.
376 public function testExternalLink() {
378 'title[0][value]' => 'External URL',
379 'link[0][uri]' => 'http://example.org',
380 'menu_parent' => 'admin:system.admin',
381 'description[0][value]' => 'External URL & escaped',
383 $this->drupalPostForm('admin/structure/menu/manage/admin/add', $edit, 'Save');
385 // Assert that the new menu link is shown on the menu link listing.
386 $this->drupalGet('admin/structure/menu/manage/admin');
387 $this->assertText('External URL');
389 // Assert that the new menu link is shown in the toolbar on a regular page.
390 $this->drupalGet(Url::fromRoute('<front>'));
391 $this->assertText('External URL');
392 // Ensure the description is escaped as expected.
393 $this->assertRaw('title="External URL & escaped"');
397 * Get the hash value from the admin menu subtrees route path.
400 * The hash value from the admin menu subtrees route path.
402 private function getSubtreesHash() {
403 $settings = $this->getDrupalSettings();
404 // The toolbar module defines a route '/toolbar/subtrees/{hash}' that
405 // returns JSON for the rendered subtrees. This hash is provided to the
406 // client in drupalSettings.
407 return $settings['toolbar']['subtreesHash'];
411 * Asserts the subtrees hash on a fresh page GET is different from the hash
412 * from the previous page GET.
414 private function assertDifferentHash() {
415 // Request a new page to refresh the drupalSettings object.
416 $this->drupalGet('test-page');
417 $this->assertResponse(200);
418 $new_subtree_hash = $this->getSubtreesHash();
420 // Assert that the old admin menu subtree hash and the new admin menu
421 // subtree hash are different.
422 $this->assertTrue($new_subtree_hash, 'A valid hash value for the admin menu subtrees was created.');
423 $this->assertNotEqual($this->hash, $new_subtree_hash, 'The user-specific subtree menu hash has been updated.');
425 // Save the new subtree hash as the original.
426 $this->hash = $new_subtree_hash;