Initial Drupal 11 with DDEV setup
This commit is contained in:
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\toolbar_disable_user_toolbar\Hook;
|
||||
|
||||
use Drupal\Core\Hook\Attribute\Hook;
|
||||
|
||||
/**
|
||||
* Hook implementations for toolbar_disable_user_toolbar.
|
||||
*/
|
||||
class ToolbarDisableUserToolbarHooks {
|
||||
|
||||
/**
|
||||
* Implements hook_toolbar_alter().
|
||||
*/
|
||||
#[Hook('toolbar_alter')]
|
||||
public function toolbarAlter(&$items): void {
|
||||
unset($items['user']);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
name: 'Disable user toolbar'
|
||||
type: module
|
||||
description: 'Support module for testing toolbar without user toolbar'
|
||||
package: Testing
|
||||
version: VERSION
|
||||
@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\toolbar_test\Hook;
|
||||
|
||||
use Drupal\Core\Link;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\Core\Hook\Attribute\Hook;
|
||||
|
||||
/**
|
||||
* Hook implementations for toolbar_test.
|
||||
*/
|
||||
class ToolbarTestHooks {
|
||||
|
||||
/**
|
||||
* Implements hook_toolbar().
|
||||
*/
|
||||
#[Hook('toolbar')]
|
||||
public function toolbar(): array {
|
||||
$items['testing'] = [
|
||||
'#type' => 'toolbar_item',
|
||||
'tab' => [
|
||||
'#type' => 'link',
|
||||
'#title' => 'Test tab',
|
||||
'#url' => Url::fromRoute('<front>'),
|
||||
'#options' => [
|
||||
'attributes' => [
|
||||
'id' => 'toolbar-tab-testing',
|
||||
'title' => 'Test tab',
|
||||
],
|
||||
],
|
||||
],
|
||||
'tray' => [
|
||||
'#heading' => 'Test tray',
|
||||
'#wrapper_attributes' => [
|
||||
'id' => 'toolbar-tray-testing',
|
||||
],
|
||||
'content' => [
|
||||
'#theme' => 'item_list',
|
||||
'#items' => [
|
||||
Link::fromTextAndUrl('link 1', Url::fromRoute('<front>', [], [
|
||||
'attributes' => [
|
||||
'title' => 'Test link 1 title',
|
||||
],
|
||||
]))->toRenderable(),
|
||||
Link::fromTextAndUrl('link 2', Url::fromRoute('<front>', [], [
|
||||
'attributes' => [
|
||||
'title' => 'Test link 2 title',
|
||||
],
|
||||
]))->toRenderable(),
|
||||
Link::fromTextAndUrl('link 3', Url::fromRoute('<front>', [], [
|
||||
'attributes' => [
|
||||
'title' => 'Test link 3 title',
|
||||
],
|
||||
]))->toRenderable(),
|
||||
],
|
||||
'#attributes' => [
|
||||
'class' => [
|
||||
'toolbar-menu',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'#weight' => 50,
|
||||
];
|
||||
$items['empty'] = ['#type' => 'toolbar_item'];
|
||||
return $items;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
name: 'Toolbar module API tests'
|
||||
type: module
|
||||
description: 'Support module for toolbar testing.'
|
||||
package: Testing
|
||||
version: VERSION
|
||||
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* A dummy module to test API interaction with the Toolbar module.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Implements hook_preprocess_HOOK().
|
||||
*/
|
||||
function toolbar_test_preprocess_menu(&$variables): void {
|
||||
// All the standard hook_theme variables should be populated when the
|
||||
// Toolbar module is rendering a menu.
|
||||
foreach (['menu_name', 'items', 'attributes'] as $variable) {
|
||||
$variables[$variable];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\toolbar\Functional;
|
||||
|
||||
use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
|
||||
|
||||
/**
|
||||
* Generic module test for toolbar.
|
||||
*
|
||||
* @group toolbar
|
||||
*/
|
||||
class GenericTest extends GenericModuleTestBase {}
|
||||
@ -0,0 +1,490 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\toolbar\Functional;
|
||||
|
||||
use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\user\Entity\Role;
|
||||
use Drupal\user\RoleInterface;
|
||||
|
||||
/**
|
||||
* Tests the caching of the admin menu subtree items.
|
||||
*
|
||||
* The cache of the admin menu subtree items will be invalidated if the
|
||||
* following hooks are invoked.
|
||||
*
|
||||
* toolbar_modules_enabled()
|
||||
* toolbar_modules_disabled()
|
||||
* toolbar_menu_link_update()
|
||||
* toolbar_user_update()
|
||||
* toolbar_user_role_update()
|
||||
*
|
||||
* Each hook invocation is simulated and then the previous hash of the admin
|
||||
* menu subtrees is compared to the new hash.
|
||||
*
|
||||
* @group toolbar
|
||||
*/
|
||||
class ToolbarAdminMenuTest extends BrowserTestBase {
|
||||
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* A user with permission to access the administrative toolbar.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $adminUser;
|
||||
|
||||
/**
|
||||
* A second user with permission to access the administrative toolbar.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $adminUser2;
|
||||
|
||||
/**
|
||||
* The current admin menu subtrees hash for adminUser.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $hash;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'node',
|
||||
'block',
|
||||
'menu_ui',
|
||||
'user',
|
||||
'taxonomy',
|
||||
'toolbar',
|
||||
'language',
|
||||
'test_page_test',
|
||||
'locale',
|
||||
'search',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$perms = [
|
||||
'access toolbar',
|
||||
'access administration pages',
|
||||
'administer site configuration',
|
||||
'bypass node access',
|
||||
'administer themes',
|
||||
'administer nodes',
|
||||
'access content overview',
|
||||
'administer blocks',
|
||||
'administer menu',
|
||||
'administer modules',
|
||||
'administer permissions',
|
||||
'administer users',
|
||||
'access user profiles',
|
||||
'administer taxonomy',
|
||||
'administer languages',
|
||||
'translate interface',
|
||||
'administer search',
|
||||
];
|
||||
|
||||
// Create an administrative user and log it in.
|
||||
$this->adminUser = $this->drupalCreateUser($perms);
|
||||
$this->adminUser2 = $this->drupalCreateUser($perms);
|
||||
|
||||
$this->drupalLogin($this->adminUser);
|
||||
|
||||
$this->drupalGet('test-page');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
|
||||
// Assert that the toolbar is present in the HTML.
|
||||
$this->assertSession()->responseContains('id="toolbar-administration"');
|
||||
|
||||
// Store the adminUser admin menu subtrees hash for comparison later.
|
||||
$this->hash = $this->getSubtreesHash();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests Toolbar's responses to installing and uninstalling modules.
|
||||
*
|
||||
* @see toolbar_modules_installed()
|
||||
* @see toolbar_modules_uninstalled()
|
||||
*/
|
||||
public function testModuleStatusChangeSubtreesHashCacheClear(): void {
|
||||
// Use an admin role to ensure the user has all available permissions. This
|
||||
// results in the admin menu links changing as the taxonomy module is
|
||||
// installed and uninstalled because the role will always have the
|
||||
// 'administer taxonomy' permission if it exists.
|
||||
$role = Role::load($this->createRole([]));
|
||||
$role->setIsAdmin(TRUE);
|
||||
$role->save();
|
||||
$this->adminUser->addRole($role->id())->save();
|
||||
|
||||
// Uninstall a module.
|
||||
$edit = [];
|
||||
$edit['uninstall[taxonomy]'] = TRUE;
|
||||
$this->drupalGet('admin/modules/uninstall');
|
||||
$this->submitForm($edit, 'Uninstall');
|
||||
// Confirm the uninstall form.
|
||||
$this->submitForm([], 'Uninstall');
|
||||
$this->rebuildContainer();
|
||||
|
||||
// Assert that the subtrees hash has been altered because the subtrees
|
||||
// structure changed.
|
||||
$this->assertDifferentHash();
|
||||
|
||||
// Enable a module.
|
||||
$edit = [];
|
||||
$edit['modules[taxonomy][enable]'] = TRUE;
|
||||
$this->drupalGet('admin/modules');
|
||||
$this->submitForm($edit, 'Install');
|
||||
$this->rebuildContainer();
|
||||
|
||||
// Assert that the subtrees hash has been altered because the subtrees
|
||||
// structure changed.
|
||||
$this->assertDifferentHash();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests toolbar cache tags implementation.
|
||||
*/
|
||||
public function testMenuLinkUpdateSubtreesHashCacheClear(): void {
|
||||
// The ID of (any) admin menu link.
|
||||
$admin_menu_link_id = 'system.admin_config_development';
|
||||
|
||||
// Disable the link.
|
||||
$edit = [];
|
||||
$edit['enabled'] = FALSE;
|
||||
$this->drupalGet("admin/structure/menu/link/" . $admin_menu_link_id . "/edit");
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextContains('The menu link has been saved.');
|
||||
|
||||
// Assert that the subtrees hash has been altered because the subtrees
|
||||
// structure changed.
|
||||
$this->assertDifferentHash();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests Toolbar's responses to user data updates.
|
||||
*
|
||||
* @see toolbar_user_role_update()
|
||||
* @see toolbar_user_update()
|
||||
*/
|
||||
public function testUserRoleUpdateSubtreesHashCacheClear(): void {
|
||||
// Find the new role ID.
|
||||
$all_rids = $this->adminUser->getRoles();
|
||||
unset($all_rids[array_search(RoleInterface::AUTHENTICATED_ID, $all_rids)]);
|
||||
$rid = reset($all_rids);
|
||||
|
||||
$edit = [];
|
||||
$edit[$rid . '[administer taxonomy]'] = FALSE;
|
||||
$this->drupalGet('admin/people/permissions');
|
||||
$this->submitForm($edit, 'Save permissions');
|
||||
|
||||
// Assert that the subtrees hash has been altered because the subtrees
|
||||
// structure changed.
|
||||
$this->assertDifferentHash();
|
||||
|
||||
// Test that assigning a user an extra role only affects that single user.
|
||||
// Get the hash for a second user.
|
||||
$this->drupalLogin($this->adminUser2);
|
||||
$this->drupalGet('test-page');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
|
||||
// Assert that the toolbar is present in the HTML.
|
||||
$this->assertSession()->responseContains('id="toolbar-administration"');
|
||||
|
||||
$admin_user_2_hash = $this->getSubtreesHash();
|
||||
|
||||
// Log in the first admin user again.
|
||||
$this->drupalLogin($this->adminUser);
|
||||
$this->drupalGet('test-page');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
|
||||
// Assert that the toolbar is present in the HTML.
|
||||
$this->assertSession()->responseContains('id="toolbar-administration"');
|
||||
|
||||
$this->hash = $this->getSubtreesHash();
|
||||
|
||||
$rid = $this->drupalCreateRole(['administer content types']);
|
||||
|
||||
// Assign the role to the user.
|
||||
$this->drupalGet('user/' . $this->adminUser->id() . '/edit');
|
||||
$this->submitForm(["roles[{$rid}]" => $rid], 'Save');
|
||||
$this->assertSession()->pageTextContains('The changes have been saved.');
|
||||
|
||||
// Assert that the subtrees hash has been altered because the subtrees
|
||||
// structure changed.
|
||||
$this->assertDifferentHash();
|
||||
|
||||
// Log in the second user again and assert that their subtrees hash did not
|
||||
// change.
|
||||
$this->drupalLogin($this->adminUser2);
|
||||
|
||||
// Request a new page to refresh the drupalSettings object.
|
||||
$this->drupalGet('test-page');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$new_subtree_hash = $this->getSubtreesHash();
|
||||
|
||||
// Assert that the old admin menu subtree hash and the new admin menu
|
||||
// subtree hash are the same.
|
||||
$this->assertNotEmpty($new_subtree_hash, 'A valid hash value for the admin menu subtrees was created.');
|
||||
$this->assertEquals($admin_user_2_hash, $new_subtree_hash, 'The user-specific subtree menu hash has not been updated.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests cache invalidation when one user modifies another user.
|
||||
*/
|
||||
public function testNonCurrentUserAccountUpdates(): void {
|
||||
$admin_user_id = $this->adminUser->id();
|
||||
$this->hash = $this->getSubtreesHash();
|
||||
|
||||
// adminUser2 will add a role to adminUser.
|
||||
$this->drupalLogin($this->adminUser2);
|
||||
$rid = $this->drupalCreateRole(['administer content types']);
|
||||
|
||||
// Get the subtree hash for adminUser2 to check later that it has not
|
||||
// changed. Request a new page to refresh the drupalSettings object.
|
||||
$this->drupalGet('test-page');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$admin_user_2_hash = $this->getSubtreesHash();
|
||||
|
||||
// Assign the role to the user.
|
||||
$this->drupalGet('user/' . $admin_user_id . '/edit');
|
||||
$this->submitForm(["roles[{$rid}]" => $rid], 'Save');
|
||||
$this->assertSession()->pageTextContains('The changes have been saved.');
|
||||
|
||||
// Log in adminUser and assert that the subtrees hash has changed.
|
||||
$this->drupalLogin($this->adminUser);
|
||||
$this->assertDifferentHash();
|
||||
|
||||
// Log in adminUser2 to check that its subtrees hash has not changed.
|
||||
$this->drupalLogin($this->adminUser2);
|
||||
$new_subtree_hash = $this->getSubtreesHash();
|
||||
|
||||
// Assert that the old adminUser subtree hash and the new adminUser
|
||||
// subtree hash are the same.
|
||||
$this->assertNotEmpty($new_subtree_hash, 'A valid hash value for the admin menu subtrees was created.');
|
||||
$this->assertEquals($new_subtree_hash, $admin_user_2_hash, 'The user-specific subtree menu hash has not been updated.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that toolbar cache is cleared when string translations are made.
|
||||
*/
|
||||
public function testLocaleTranslationSubtreesHashCacheClear(): void {
|
||||
$admin_user = $this->adminUser;
|
||||
// User to translate and delete string.
|
||||
$translate_user = $this->drupalCreateUser([
|
||||
'translate interface',
|
||||
'access administration pages',
|
||||
]);
|
||||
|
||||
// Create a new language with the langcode 'xx'.
|
||||
$langcode = 'xx';
|
||||
// The English name for the language. This will be translated.
|
||||
$name = $this->randomMachineName(16);
|
||||
// This will be the translation of $name.
|
||||
$translation = $this->randomMachineName(16);
|
||||
|
||||
// Add custom language.
|
||||
$this->drupalLogin($admin_user);
|
||||
$edit = [
|
||||
'predefined_langcode' => 'custom',
|
||||
'langcode' => $langcode,
|
||||
'label' => $name,
|
||||
'direction' => LanguageInterface::DIRECTION_LTR,
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/language/add');
|
||||
$this->submitForm($edit, 'Add custom language');
|
||||
// phpcs:ignore Drupal.Semantics.FunctionT.NotLiteralString
|
||||
$this->t($name, [], ['langcode' => $langcode]);
|
||||
// Reset locale cache.
|
||||
$this->container->get('string_translation')->reset();
|
||||
$this->assertSession()->responseContains('"edit-languages-' . $langcode . '-weight"');
|
||||
// Verify that the test language was added.
|
||||
$this->assertSession()->pageTextContains($name);
|
||||
|
||||
// Have the adminUser request a page in the new language.
|
||||
$this->drupalGet($langcode . '/test-page');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
|
||||
// Get a baseline hash for the admin menu subtrees before translating one
|
||||
// of the menu link items.
|
||||
$original_subtree_hash = $this->getSubtreesHash();
|
||||
$this->assertNotEmpty($original_subtree_hash, 'A valid hash value for the admin menu subtrees was created.');
|
||||
$this->drupalLogout();
|
||||
|
||||
// Translate the string 'Search and metadata' in the xx language. This
|
||||
// string appears in a link in the admin menu subtrees. Changing the string
|
||||
// should create a new menu hash if the toolbar subtrees cache is correctly
|
||||
// invalidated.
|
||||
$this->drupalLogin($translate_user);
|
||||
// We need to visit the page to get the string to be translated.
|
||||
$this->drupalGet($langcode . '/admin/config');
|
||||
$search = [
|
||||
'string' => 'Search and metadata',
|
||||
'langcode' => $langcode,
|
||||
'translation' => 'untranslated',
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate');
|
||||
$this->submitForm($search, 'Filter');
|
||||
$this->assertSession()->pageTextNotContains('No strings available');
|
||||
// Verify that search found the string as untranslated.
|
||||
$this->assertSession()->pageTextContains($name);
|
||||
|
||||
// Assume this is the only result.
|
||||
// Translate the string to a random string.
|
||||
$textarea = $this->assertSession()->elementExists('xpath', '//textarea');
|
||||
$lid = (string) $textarea->getAttribute('name');
|
||||
$edit = [
|
||||
$lid => $translation,
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate');
|
||||
$this->submitForm($edit, 'Save translations');
|
||||
$this->assertSession()->pageTextContains('The strings have been saved.');
|
||||
// Verify that the user is redirected to the correct page.
|
||||
$this->assertSession()->addressEquals(Url::fromRoute('locale.translate_page'));
|
||||
$this->drupalLogout();
|
||||
|
||||
// Log in the adminUser. Check the admin menu subtrees hash now that one
|
||||
// of the link items in the Structure tree (Menus) has had its text
|
||||
// translated.
|
||||
$this->drupalLogin($admin_user);
|
||||
$this->drupalGet('admin/config');
|
||||
// Have the adminUser request a page in the new language.
|
||||
$this->drupalGet($langcode . '/test-page');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$new_subtree_hash = $this->getSubtreesHash();
|
||||
|
||||
// Assert that the old admin menu subtrees hash and the new admin menu
|
||||
// subtrees hash are different.
|
||||
$this->assertNotEmpty($new_subtree_hash, 'A valid hash value for the admin menu subtrees was created.');
|
||||
$this->assertNotEquals($original_subtree_hash, $new_subtree_hash, 'The user-specific subtree menu hash has been updated.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the 'toolbar/subtrees/{hash}' is reachable and correct.
|
||||
*/
|
||||
public function testSubtreesJsonRequest(): void {
|
||||
$admin_user = $this->adminUser;
|
||||
$this->drupalLogin($admin_user);
|
||||
// Request a new page to refresh the drupalSettings object.
|
||||
$subtrees_hash = $this->getSubtreesHash();
|
||||
|
||||
$this->drupalGet('toolbar/subtrees/' . $subtrees_hash, ['query' => [MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_ajax']], ['X-Requested-With' => 'XMLHttpRequest']);
|
||||
$ajax_result = json_decode($this->getSession()->getPage()->getContent(), TRUE);
|
||||
$this->assertEquals('setToolbarSubtrees', $ajax_result[0]['command'], 'Subtrees response uses the correct command.');
|
||||
$this->assertEquals(['system-admin_content', 'system-admin_structure', 'system-themes_page', 'system-modules_list', 'system-admin_config', 'entity-user-collection', 'front'], array_keys($ajax_result[0]['subtrees']), 'Correct subtrees returned.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that subtrees hashes vary by the language of the page.
|
||||
*/
|
||||
public function testLanguageSwitching(): void {
|
||||
// Create a new language with the langcode 'xx'.
|
||||
$langcode = 'xx';
|
||||
$language = ConfigurableLanguage::createFromLangcode($langcode);
|
||||
$language->save();
|
||||
// The language path processor is just registered for more than one
|
||||
// configured language, so rebuild the container now that we are
|
||||
// multilingual.
|
||||
$this->rebuildContainer();
|
||||
|
||||
// Get a page with the new language langcode in the URL.
|
||||
$this->drupalGet('test-page', ['language' => $language]);
|
||||
// Assert different hash.
|
||||
$new_subtree_hash = $this->getSubtreesHash();
|
||||
|
||||
// Assert that the old admin menu subtree hash and the new admin menu
|
||||
// subtree hash are different.
|
||||
$this->assertNotEmpty($new_subtree_hash, 'A valid hash value for the admin menu subtrees was created.');
|
||||
$this->assertNotEquals($this->hash, $new_subtree_hash, 'The user-specific subtree menu hash has been updated.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that back to site link exists on admin pages, not on content pages.
|
||||
*/
|
||||
public function testBackToSiteLink(): void {
|
||||
// Back to site link should exist in the markup.
|
||||
$this->drupalGet('test-page');
|
||||
$back_link = $this->cssSelect('.home-toolbar-tab');
|
||||
$this->assertNotEmpty($back_link);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that external links added to the menu appear in the toolbar.
|
||||
*/
|
||||
public function testExternalLink(): void {
|
||||
$edit = [
|
||||
'title[0][value]' => 'External URL',
|
||||
'link[0][uri]' => 'http://example.org',
|
||||
'menu_parent' => 'admin:system.admin',
|
||||
'description[0][value]' => 'External URL & escaped',
|
||||
];
|
||||
$this->drupalGet('admin/structure/menu/manage/admin/add');
|
||||
$this->submitForm($edit, 'Save');
|
||||
|
||||
// Assert that the new menu link is shown on the menu link listing.
|
||||
$this->drupalGet('admin/structure/menu/manage/admin');
|
||||
$this->assertSession()->pageTextContains('External URL');
|
||||
|
||||
// Assert that the new menu link is shown in the toolbar on a regular page.
|
||||
$this->drupalGet(Url::fromRoute('<front>'));
|
||||
$this->assertSession()->pageTextContains('External URL');
|
||||
// Ensure the description is escaped as expected.
|
||||
$this->assertSession()->responseContains('title="External URL & escaped"');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the hash value from the admin menu subtrees route path.
|
||||
*
|
||||
* @return string
|
||||
* The hash value from the admin menu subtrees route path.
|
||||
*/
|
||||
private function getSubtreesHash() {
|
||||
$settings = $this->getDrupalSettings();
|
||||
// The toolbar module defines a route '/toolbar/subtrees/{hash}' that
|
||||
// returns JSON for the rendered subtrees. This hash is provided to the
|
||||
// client in drupalSettings.
|
||||
return $settings['toolbar']['subtreesHash'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the subtree hash of the current page with that of the previous page.
|
||||
*
|
||||
* Asserts that the subtrees hash on a fresh page GET is different from the
|
||||
* subtree hash from the previous page GET.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
private function assertDifferentHash(): void {
|
||||
// Request a new page to refresh the drupalSettings object.
|
||||
$this->drupalGet('test-page');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$new_subtree_hash = $this->getSubtreesHash();
|
||||
|
||||
// Assert that the old admin menu subtree hash and the new admin menu
|
||||
// subtree hash are different.
|
||||
$this->assertNotEmpty($new_subtree_hash, 'A valid hash value for the admin menu subtrees was created.');
|
||||
$this->assertNotEquals($this->hash, $new_subtree_hash, 'The user-specific subtree menu hash has been updated.');
|
||||
|
||||
// Save the new subtree hash as the original.
|
||||
$this->hash = $new_subtree_hash;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,150 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\toolbar\Functional;
|
||||
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
|
||||
use Drupal\Tests\system\Functional\Cache\AssertPageCacheContextsAndTagsTrait;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests the cache contexts for toolbar.
|
||||
*
|
||||
* @group toolbar
|
||||
*/
|
||||
class ToolbarCacheContextsTest extends BrowserTestBase {
|
||||
|
||||
use AssertPageCacheContextsAndTagsTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['toolbar', 'test_page_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* An authenticated user to use for testing.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $adminUser;
|
||||
|
||||
/**
|
||||
* An authenticated user to use for testing.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $adminUser2;
|
||||
|
||||
/**
|
||||
* A list of default permissions for test users.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $perms = [
|
||||
'access toolbar',
|
||||
'access administration pages',
|
||||
'administer site configuration',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->adminUser = $this->drupalCreateUser($this->perms);
|
||||
$this->adminUser2 = $this->drupalCreateUser($this->perms);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests toolbar cache integration.
|
||||
*/
|
||||
public function testCacheIntegration(): void {
|
||||
$this->installExtraModules(['csrf_test', 'dynamic_page_cache']);
|
||||
$this->drupalLogin($this->adminUser);
|
||||
$this->drupalGet('test-page');
|
||||
$this->assertSession()->responseHeaderEquals('X-Drupal-Dynamic-Cache', 'MISS');
|
||||
$this->assertCacheContexts(['session', 'user', 'url.query_args:' . MainContentViewSubscriber::WRAPPER_FORMAT], 'Expected cache contexts found with CSRF token link.');
|
||||
$this->drupalGet('test-page');
|
||||
$this->assertSession()->responseHeaderEquals('X-Drupal-Dynamic-Cache', 'HIT');
|
||||
$this->assertCacheContexts(['session', 'user', 'url.query_args:' . MainContentViewSubscriber::WRAPPER_FORMAT], 'Expected cache contexts found with CSRF token link.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests toolbar cache contexts.
|
||||
*/
|
||||
public function testToolbarCacheContextsCaller(): void {
|
||||
// Test with default combination and permission to see toolbar.
|
||||
$this->assertToolbarCacheContexts(['user', 'session'], 'Expected cache contexts found for default combination and permission to see toolbar.');
|
||||
|
||||
// Test without user toolbar tab. User module is a required module so we
|
||||
// have to manually remove the user toolbar tab.
|
||||
$this->installExtraModules(['toolbar_disable_user_toolbar']);
|
||||
$this->assertToolbarCacheContexts(['user.permissions'], 'Expected cache contexts found without user toolbar tab.');
|
||||
|
||||
// Test with the toolbar and contextual enabled.
|
||||
$this->installExtraModules(['contextual']);
|
||||
$this->adminUser2 = $this->drupalCreateUser(array_merge($this->perms, ['access contextual links']));
|
||||
$this->assertToolbarCacheContexts(['user.permissions'], 'Expected cache contexts found with contextual module enabled.');
|
||||
\Drupal::service('module_installer')->uninstall(['contextual']);
|
||||
|
||||
// Test with the comment module enabled.
|
||||
$this->installExtraModules(['comment']);
|
||||
$this->adminUser2 = $this->drupalCreateUser(array_merge($this->perms, ['access comments']));
|
||||
$this->assertToolbarCacheContexts(['user.permissions'], 'Expected cache contexts found with comment module enabled.');
|
||||
\Drupal::service('module_installer')->uninstall(['comment']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that cache contexts are applied for both users.
|
||||
*
|
||||
* @param string[] $cache_contexts
|
||||
* Expected cache contexts for both users.
|
||||
* @param string $message
|
||||
* (optional) A verbose message to output.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function assertToolbarCacheContexts(array $cache_contexts, ?string $message = NULL): void {
|
||||
// Default cache contexts that should exist on all test cases.
|
||||
$default_cache_contexts = [
|
||||
'languages:language_interface',
|
||||
'theme',
|
||||
'url.query_args:' . MainContentViewSubscriber::WRAPPER_FORMAT,
|
||||
];
|
||||
$cache_contexts = Cache::mergeContexts($default_cache_contexts, $cache_contexts);
|
||||
|
||||
// Assert contexts for user1 which has only default permissions.
|
||||
$this->drupalLogin($this->adminUser);
|
||||
$this->drupalGet('test-page');
|
||||
$this->assertCacheContexts($cache_contexts, $message);
|
||||
$this->drupalLogout();
|
||||
|
||||
// Assert contexts for user2 which has some additional permissions.
|
||||
$this->drupalLogin($this->adminUser2);
|
||||
$this->drupalGet('test-page');
|
||||
$this->assertCacheContexts($cache_contexts, $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs a given list of modules and rebuilds the cache.
|
||||
*
|
||||
* @param string[] $module_list
|
||||
* An array of module names.
|
||||
*/
|
||||
protected function installExtraModules(array $module_list): void {
|
||||
\Drupal::service('module_installer')->install($module_list);
|
||||
|
||||
// Installing modules updates the container and needs a router rebuild.
|
||||
$this->container = \Drupal::getContainer();
|
||||
$this->container->get('router.builder')->rebuildIfNeeded();
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\toolbar\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests the implementation of hook_toolbar() by a module.
|
||||
*
|
||||
* @group toolbar
|
||||
*/
|
||||
class ToolbarHookToolbarTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* A user with permission to access the administrative toolbar.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $adminUser;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['toolbar', 'toolbar_test', 'test_page_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Create an administrative user and log it in.
|
||||
$this->adminUser = $this->drupalCreateUser(['access toolbar']);
|
||||
$this->drupalLogin($this->adminUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests for a tab and tray provided by a module implementing hook_toolbar().
|
||||
*/
|
||||
public function testHookToolbar(): void {
|
||||
$this->drupalGet('test-page');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
|
||||
// Assert that the toolbar is present in the HTML.
|
||||
$this->assertSession()->responseContains('id="toolbar-administration"');
|
||||
|
||||
// Assert that the tab registered by toolbar_test is present.
|
||||
$this->assertSession()->responseContains('id="toolbar-tab-testing"');
|
||||
|
||||
// Assert that the tab item descriptions are present.
|
||||
$this->assertSession()->responseContains('title="Test tab"');
|
||||
|
||||
// Assert that the tray registered by toolbar_test is present.
|
||||
$this->assertSession()->responseContains('id="toolbar-tray-testing"');
|
||||
|
||||
// Assert that tray item descriptions are present.
|
||||
$this->assertSession()->responseContains('title="Test link 1 title"');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\toolbar\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests that the toolbar icon class remains for translated menu items.
|
||||
*
|
||||
* @group toolbar
|
||||
*/
|
||||
class ToolbarMenuTranslationTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* A user with permission to access the administrative toolbar.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $adminUser;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'toolbar',
|
||||
'toolbar_test',
|
||||
'locale',
|
||||
'locale_test',
|
||||
'block',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Create an administrative user and log it in.
|
||||
$this->adminUser = $this->drupalCreateUser([
|
||||
'access toolbar',
|
||||
'translate interface',
|
||||
'administer languages',
|
||||
'access administration pages',
|
||||
'administer blocks',
|
||||
]);
|
||||
$this->drupalLogin($this->adminUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that toolbar classes don't change when adding a translation.
|
||||
*/
|
||||
public function testToolbarClasses(): void {
|
||||
$langcode = 'es';
|
||||
|
||||
// Add Spanish.
|
||||
$edit['predefined_langcode'] = $langcode;
|
||||
$this->drupalGet('admin/config/regional/language/add');
|
||||
$this->submitForm($edit, 'Add language');
|
||||
|
||||
// The menu item 'Structure' in the toolbar will be translated.
|
||||
$menu_item = 'Structure';
|
||||
|
||||
// Visit a page that has the string on it so it can be translated.
|
||||
$this->drupalGet($langcode . '/admin/structure');
|
||||
|
||||
// Search for the menu item.
|
||||
$search = [
|
||||
'string' => $menu_item,
|
||||
'langcode' => $langcode,
|
||||
'translation' => 'untranslated',
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate');
|
||||
$this->submitForm($search, 'Filter');
|
||||
// Make sure will be able to translate the menu item.
|
||||
$this->assertSession()->pageTextNotContains('No strings available.');
|
||||
|
||||
// Check that the class is on the item before we translate it.
|
||||
$this->assertSession()->elementsCount('xpath', '//a[contains(@class, "icon-system-admin-structure")]', 1);
|
||||
|
||||
// Translate the menu item.
|
||||
$menu_item_translated = $this->randomMachineName();
|
||||
$textarea = $this->assertSession()->elementExists('xpath', '//textarea');
|
||||
$lid = (string) $textarea->getAttribute('name');
|
||||
$edit = [
|
||||
$lid => $menu_item_translated,
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate');
|
||||
$this->submitForm($edit, 'Save translations');
|
||||
|
||||
// Search for the translated menu item.
|
||||
$search = [
|
||||
'string' => $menu_item,
|
||||
'langcode' => $langcode,
|
||||
'translation' => 'translated',
|
||||
];
|
||||
$this->drupalGet('admin/config/regional/translate');
|
||||
$this->submitForm($search, 'Filter');
|
||||
// Make sure the menu item string was translated.
|
||||
$this->assertSession()->pageTextContains($menu_item_translated);
|
||||
|
||||
// Go to another page in the custom language and make sure the menu item
|
||||
// was translated.
|
||||
$this->drupalGet($langcode . '/admin/structure');
|
||||
$this->assertSession()->pageTextContains($menu_item_translated);
|
||||
|
||||
// Toolbar icons are included based on the presence of a specific class on
|
||||
// the menu item. Ensure that class also exists for a translated menu item.
|
||||
$this->assertSession()->elementsCount('xpath', '//a[contains(@class, "icon-system-admin-structure")]', 1);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\toolbar\FunctionalJavascript;
|
||||
|
||||
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
|
||||
|
||||
/**
|
||||
* Tests that the active trail is maintained in the toolbar.
|
||||
*
|
||||
* @group toolbar
|
||||
*/
|
||||
class ToolbarActiveTrailTest extends WebDriverTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['toolbar', 'node', 'field_ui'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->drupalCreateContentType(['type' => 'article', 'name' => 'Article']);
|
||||
$this->drupalLogin($this->drupalCreateUser([
|
||||
'access administration pages',
|
||||
'administer content types',
|
||||
'administer node fields',
|
||||
'access toolbar',
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the active trail is maintained even when traversed deeper.
|
||||
*
|
||||
* @param string $orientation
|
||||
* The toolbar orientation.
|
||||
*
|
||||
* @testWith ["vertical"]
|
||||
* ["horizontal"]
|
||||
*
|
||||
* @throws \Behat\Mink\Exception\ElementNotFoundException
|
||||
*/
|
||||
public function testToolbarActiveTrail(string $orientation): void {
|
||||
$page = $this->getSession()->getPage();
|
||||
$assert_session = $this->assertSession();
|
||||
|
||||
$this->drupalGet('<front>');
|
||||
$this->assertNotEmpty($this->assertSession()->waitForElement('css', 'body.toolbar-horizontal'));
|
||||
$this->assertNotEmpty($this->assertSession()->waitForElementVisible('css', '.toolbar-tray'));
|
||||
$this->assertSession()->waitForElementRemoved('css', '.toolbar-loading');
|
||||
$this->assertNotEmpty($assert_session->waitForElementVisible('css', '#toolbar-item-administration.is-active'));
|
||||
|
||||
// If testing for vertical orientation of the toolbar then switch to it.
|
||||
if ($orientation === 'vertical') {
|
||||
$page->pressButton('Vertical orientation');
|
||||
}
|
||||
|
||||
// Traverse deeper.
|
||||
$this->clickLink('Structure');
|
||||
$this->clickLink('Content types');
|
||||
$this->clickLink('Manage fields');
|
||||
$this->clickLink('Edit');
|
||||
|
||||
if ($orientation === 'vertical') {
|
||||
// Assert that menu-item--active-trail was maintained.
|
||||
$this->assertNotNull($assert_session->waitForElementVisible('css', '.menu-item--active-trail a:contains("Structure")'));
|
||||
$this->assertNotNull($assert_session->waitForElementVisible('css', '.menu-item--active-trail a:contains("Content types")'));
|
||||
// Change orientation and check focus is maintained.
|
||||
$page->pressButton('Horizontal orientation');
|
||||
$this->assertNotNull($assert_session->waitForElementVisible('css', '#toolbar-link-system-admin_structure.is-active'));
|
||||
}
|
||||
else {
|
||||
// Assert that is-active was maintained.
|
||||
$this->assertNotNull($assert_session->waitForElementVisible('css', '#toolbar-link-system-admin_structure.is-active'));
|
||||
// Change orientation and check focus is maintained.
|
||||
$page->pressButton('Vertical orientation');
|
||||
// Introduce a delay to let the focus load.
|
||||
$this->getSession()->wait(150);
|
||||
$this->assertNotNull($assert_session->waitForElementVisible('css', '.menu-item--active-trail a:contains("Structure")'));
|
||||
$this->assertNotNull($assert_session->waitForElementVisible('css', '.menu-item--active-trail a:contains("Content types")'));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,130 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\toolbar\FunctionalJavascript;
|
||||
|
||||
use Behat\Mink\Element\NodeElement;
|
||||
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
|
||||
|
||||
/**
|
||||
* Tests the JavaScript functionality of the toolbar.
|
||||
*
|
||||
* @group toolbar
|
||||
*/
|
||||
class ToolbarIntegrationTest extends WebDriverTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['toolbar', 'node'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Tests if the toolbar can be toggled with JavaScript.
|
||||
*/
|
||||
public function testToolbarToggling(): void {
|
||||
$admin_user = $this->drupalCreateUser([
|
||||
'access toolbar',
|
||||
'administer site configuration',
|
||||
'access content overview',
|
||||
]);
|
||||
$this->drupalLogin($admin_user);
|
||||
|
||||
// Set size for horizontal toolbar.
|
||||
$this->getSession()->resizeWindow(1200, 600);
|
||||
$this->drupalGet('<front>');
|
||||
$this->assertNotEmpty($this->assertSession()->waitForElement('css', 'body.toolbar-horizontal'));
|
||||
$this->assertNotEmpty($this->assertSession()->waitForElementVisible('css', '.toolbar-tray'));
|
||||
|
||||
$page = $this->getSession()->getPage();
|
||||
|
||||
// Test that it is possible to toggle the toolbar tray.
|
||||
$content_link = $page->findLink('Content');
|
||||
$manage_link = $page->find('css', '#toolbar-item-administration');
|
||||
|
||||
// Start with open tray.
|
||||
$this->waitAndAssertAriaPressedState($manage_link, TRUE);
|
||||
$this->assertTrue($content_link->isVisible(), 'Toolbar tray is open by default.');
|
||||
|
||||
// Click to close.
|
||||
$manage_link->click();
|
||||
$this->waitAndAssertAriaPressedState($manage_link, FALSE);
|
||||
$this->assertFalse($content_link->isVisible(), 'Toolbar tray is closed after clicking the "Manage" link.');
|
||||
|
||||
// Click to open.
|
||||
$manage_link->click();
|
||||
$this->waitAndAssertAriaPressedState($manage_link, TRUE);
|
||||
$this->assertTrue($content_link->isVisible(), 'Toolbar tray is visible again after clicking the "Manage" button a second time.');
|
||||
|
||||
// Test toggling the toolbar tray between horizontal and vertical.
|
||||
$tray = $page->findById('toolbar-item-administration-tray');
|
||||
$this->assertFalse($tray->hasClass('toolbar-tray-vertical'), 'Toolbar tray is not vertically oriented by default.');
|
||||
$page->pressButton('Vertical orientation');
|
||||
$this->assertTrue($tray->hasClass('toolbar-tray-vertical'), 'After toggling the orientation the toolbar tray is now displayed vertically.');
|
||||
|
||||
$page->pressButton('Horizontal orientation');
|
||||
$this->assertTrue($tray->hasClass('toolbar-tray-horizontal'), 'After toggling the orientation a second time the toolbar tray is displayed horizontally again.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the orientation toggle is not shown for empty toolbar items.
|
||||
*/
|
||||
public function testEmptyTray(): void {
|
||||
// Granting access to the toolbar but not any administrative menu links will
|
||||
// result in an empty toolbar tray for the "Manage" toolbar item.
|
||||
$admin_user = $this->drupalCreateUser([
|
||||
'access toolbar',
|
||||
]);
|
||||
$this->drupalLogin($admin_user);
|
||||
|
||||
// Set size for horizontal toolbar.
|
||||
$this->getSession()->resizeWindow(1200, 600);
|
||||
$this->drupalGet('<front>');
|
||||
$this->assertNotEmpty($this->assertSession()->waitForElement('css', 'body.toolbar-horizontal'));
|
||||
$this->assertNotEmpty($this->assertSession()->waitForElementVisible('css', '.toolbar-tray'));
|
||||
|
||||
// Test that the orientation toggle does not appear.
|
||||
$page = $this->getSession()->getPage();
|
||||
$tray = $page->findById('toolbar-item-administration-tray');
|
||||
$this->assertTrue($tray->hasClass('toolbar-tray-horizontal'), 'Toolbar tray is horizontally oriented by default.');
|
||||
$this->assertSession()->elementNotExists('css', '#toolbar-item-administration-tray .toolbar-menu');
|
||||
$this->assertSession()->elementNotExists('css', '#toolbar-item-administration-tray .toolbar-toggle-orientation');
|
||||
$button = $page->findButton('Vertical orientation');
|
||||
$this->assertFalse($button->isVisible(), 'Orientation toggle from other tray is not visible');
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that an element's `aria-pressed` attribute matches expected state.
|
||||
*
|
||||
* Uses `waitFor()` to pause until either the condition is met or the timeout
|
||||
* of `1` second has passed.
|
||||
*
|
||||
* @param \Behat\Mink\Element\NodeElement $element
|
||||
* The element to be tested.
|
||||
* @param bool $expected
|
||||
* The expected value of `aria-pressed`, as a boolean.
|
||||
*
|
||||
* @throws ExpectationFailedException
|
||||
*/
|
||||
private function waitAndAssertAriaPressedState(NodeElement $element, bool $expected): void {
|
||||
$this->assertTrue(
|
||||
$this
|
||||
->getSession()
|
||||
->getPage()
|
||||
->waitFor(1, function () use ($element, $expected): bool {
|
||||
// Get boolean representation of `aria-pressed`.
|
||||
// TRUE if `aria-pressed="true"`, FALSE otherwise.
|
||||
$actual = $element->getAttribute('aria-pressed') == 'true';
|
||||
|
||||
// Exit `waitFor()` when $actual == $expected.
|
||||
return $actual == $expected;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\toolbar\FunctionalJavascript;
|
||||
|
||||
use Drupal\Component\Serialization\Json;
|
||||
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
|
||||
|
||||
/**
|
||||
* Tests the sessionStorage state set by the toolbar.
|
||||
*
|
||||
* @group toolbar
|
||||
*/
|
||||
class ToolbarStoredStateTest extends WebDriverTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['toolbar', 'node'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Tests different states of the toolbar.
|
||||
*/
|
||||
public function testToolbarStoredState(): void {
|
||||
$admin_user = $this->drupalCreateUser([
|
||||
'access toolbar',
|
||||
'administer site configuration',
|
||||
'access content overview',
|
||||
]);
|
||||
$this->drupalLogin($admin_user);
|
||||
$page = $this->getSession()->getPage();
|
||||
$assert_session = $this->assertSession();
|
||||
|
||||
$this->drupalGet('<front>');
|
||||
$this->assertNotEmpty($this->assertSession()->waitForElement('css', 'body.toolbar-horizontal'));
|
||||
$this->assertNotEmpty($this->assertSession()->waitForElementVisible('css', '.toolbar-tray'));
|
||||
$this->assertSession()->waitForElementRemoved('css', '.toolbar-loading');
|
||||
|
||||
$page->clickLink('toolbar-item-user');
|
||||
$this->assertNotEmpty($assert_session->waitForElementVisible('css', '#toolbar-item-user.is-active'));
|
||||
|
||||
// Expected state values with the user tray open with horizontal
|
||||
// orientation.
|
||||
$expected = [
|
||||
'orientation' => 'horizontal',
|
||||
'hasActiveTab' => TRUE,
|
||||
'activeTabId' => 'toolbar-item-user',
|
||||
'activeTray' => 'toolbar-item-user-tray',
|
||||
'isOriented' => TRUE,
|
||||
'isFixed' => TRUE,
|
||||
];
|
||||
$toolbar_stored_state = JSON::decode(
|
||||
$this->getSession()->evaluateScript("sessionStorage.getItem('Drupal.toolbar.toolbarState')")
|
||||
);
|
||||
|
||||
// The userButtonMinWidth property will differ depending on the length of
|
||||
// the test-generated username, so it is checked differently and the value
|
||||
// is copied to the expected value array.
|
||||
$this->assertNotNull($toolbar_stored_state['userButtonMinWidth']);
|
||||
$this->assertIsNumeric($toolbar_stored_state['userButtonMinWidth']);
|
||||
$this->assertGreaterThan(60, $toolbar_stored_state['userButtonMinWidth']);
|
||||
$expected['userButtonMinWidth'] = $toolbar_stored_state['userButtonMinWidth'];
|
||||
|
||||
$this->assertSame($expected, $toolbar_stored_state);
|
||||
|
||||
$page->clickLink('toolbar-item-user');
|
||||
$assert_session->assertNoElementAfterWait('css', '#toolbar-item-user.is-active');
|
||||
|
||||
// Update expected state values to reflect no tray being open.
|
||||
$expected['hasActiveTab'] = FALSE;
|
||||
$expected['activeTabId'] = NULL;
|
||||
unset($expected['activeTray']);
|
||||
$toolbar_stored_state = JSON::decode(
|
||||
$this->getSession()->evaluateScript("sessionStorage.getItem('Drupal.toolbar.toolbarState')")
|
||||
);
|
||||
$this->assertSame($expected, $toolbar_stored_state);
|
||||
|
||||
$page->clickLink('toolbar-item-administration');
|
||||
$orientation_toggle = $assert_session->waitForElementVisible('css', '[title="Vertical orientation"]');
|
||||
$orientation_toggle->click();
|
||||
$assert_session->waitForElementVisible('css', 'body.toolbar-vertical');
|
||||
|
||||
// Update expected state values to reflect the administration tray being
|
||||
// open with vertical orientation.
|
||||
$expected['orientation'] = 'vertical';
|
||||
$expected['hasActiveTab'] = TRUE;
|
||||
$expected['activeTabId'] = 'toolbar-item-administration';
|
||||
$expected['activeTray'] = 'toolbar-item-administration-tray';
|
||||
$toolbar_stored_state = JSON::decode(
|
||||
$this->getSession()->evaluateScript("sessionStorage.getItem('Drupal.toolbar.toolbarState')")
|
||||
);
|
||||
$this->assertSame($expected, $toolbar_stored_state);
|
||||
|
||||
$this->getSession()->resizeWindow(600, 600);
|
||||
$this->getSession()->wait(1000, "JSON.parse(sessionStorage.getItem('Drupal.toolbar.toolbarState')).isFixed == false");
|
||||
|
||||
// Update expected state values to reflect the viewport being at a width
|
||||
// that is narrow enough that the toolbar isn't fixed.
|
||||
$expected['isFixed'] = FALSE;
|
||||
$toolbar_stored_state = JSON::decode(
|
||||
$this->getSession()->evaluateScript("sessionStorage.getItem('Drupal.toolbar.toolbarState')")
|
||||
);
|
||||
$this->assertSame($expected, $toolbar_stored_state);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,272 @@
|
||||
/**
|
||||
* @file
|
||||
* Tests of the existing Toolbar JS Api.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
'@tags': ['core'],
|
||||
before(browser) {
|
||||
browser
|
||||
.drupalInstall()
|
||||
.drupalInstallModule('toolbar', true)
|
||||
.drupalCreateUser({
|
||||
name: 'user',
|
||||
password: '123',
|
||||
permissions: [
|
||||
'access site reports',
|
||||
'access toolbar',
|
||||
'administer menu',
|
||||
'administer modules',
|
||||
'administer site configuration',
|
||||
'administer account settings',
|
||||
'administer software updates',
|
||||
'access content',
|
||||
'administer permissions',
|
||||
'administer users',
|
||||
],
|
||||
})
|
||||
.drupalLogin({ name: 'user', password: '123' });
|
||||
},
|
||||
beforeEach(browser) {
|
||||
// Set the resolution to the default desktop resolution. Ensure the default
|
||||
// toolbar is horizontal in headless mode.
|
||||
browser
|
||||
.setWindowSize(1920, 1080)
|
||||
// To clear active tab/tray from previous tests
|
||||
.execute(function () {
|
||||
localStorage.clear();
|
||||
// Clear escapeAdmin URL values.
|
||||
sessionStorage.clear();
|
||||
})
|
||||
.drupalRelativeURL('/')
|
||||
.waitForElementPresent('#toolbar-administration', 50000, 1000, false);
|
||||
},
|
||||
after(browser) {
|
||||
browser.drupalUninstall();
|
||||
},
|
||||
'Drupal.Toolbar.models': (browser) => {
|
||||
browser
|
||||
.waitForElementPresent(
|
||||
'#toolbar-item-administration-tray',
|
||||
50000,
|
||||
1000,
|
||||
false,
|
||||
)
|
||||
.execute(
|
||||
function () {
|
||||
const toReturn = {};
|
||||
const { models } = Drupal.toolbar;
|
||||
toReturn.hasMenuModel = models.hasOwnProperty('menuModel');
|
||||
toReturn.menuModelType = typeof models.menuModel === 'object';
|
||||
toReturn.hasToolbarModel = models.hasOwnProperty('toolbarModel');
|
||||
toReturn.toolbarModelType = typeof models.toolbarModel === 'object';
|
||||
toReturn.toolbarModelActiveTab =
|
||||
models.toolbarModel.get('activeTab').id ===
|
||||
'toolbar-item-administration';
|
||||
toReturn.toolbarModelActiveTray =
|
||||
models.toolbarModel.get('activeTray').id ===
|
||||
'toolbar-item-administration-tray';
|
||||
toReturn.toolbarModelIsOriented =
|
||||
models.toolbarModel.get('isOriented') === true;
|
||||
toReturn.toolbarModelIsFixed =
|
||||
models.toolbarModel.get('isFixed') === true;
|
||||
toReturn.toolbarModelAreSubtreesLoaded =
|
||||
models.toolbarModel.get('areSubtreesLoaded') === false;
|
||||
toReturn.toolbarModelIsViewportOverflowConstrained =
|
||||
models.toolbarModel.get('isViewportOverflowConstrained') === false;
|
||||
toReturn.toolbarModelOrientation =
|
||||
models.toolbarModel.get('orientation') === 'horizontal';
|
||||
toReturn.toolbarModelLocked =
|
||||
models.toolbarModel.get('locked') === null;
|
||||
toReturn.toolbarModelIsTrayToggleVisible =
|
||||
models.toolbarModel.get('isTrayToggleVisible') === true;
|
||||
toReturn.toolbarModelHeight =
|
||||
models.toolbarModel.get('height') === 79;
|
||||
toReturn.toolbarModelOffsetsBottom =
|
||||
models.toolbarModel.get('offsets').bottom === 0;
|
||||
toReturn.toolbarModelOffsetsLeft =
|
||||
models.toolbarModel.get('offsets').left === 0;
|
||||
toReturn.toolbarModelOffsetsRight =
|
||||
models.toolbarModel.get('offsets').right === 0;
|
||||
toReturn.toolbarModelOffsetsTop =
|
||||
models.toolbarModel.get('offsets').top === 80;
|
||||
toReturn.toolbarModelSubtrees =
|
||||
models.menuModel.get('subtrees') === null;
|
||||
return toReturn;
|
||||
},
|
||||
[],
|
||||
(result) => {
|
||||
const expectedTrue = {
|
||||
hasMenuModel: 'has menu model',
|
||||
menuModelType: 'menu model is an object',
|
||||
hasToolbarModel: 'has toolbar model',
|
||||
toolbarModelType: 'toolbar model is an object',
|
||||
toolbarModelActiveTab: 'get("activeTab") has expected result',
|
||||
toolbarModelActiveTray: 'get("activeTray") has expected result',
|
||||
toolbarModelIsOriented: 'get("isOriented") has expected result',
|
||||
toolbarModelIsFixed: 'get("isFixed") has expected result',
|
||||
toolbarModelAreSubtreesLoaded:
|
||||
'get("areSubtreesLoaded") has expected result',
|
||||
toolbarModelIsViewportOverflowConstrained:
|
||||
'get("isViewportOverflowConstrained") has expected result',
|
||||
toolbarModelOrientation: 'get("orientation") has expected result',
|
||||
toolbarModelLocked: 'get("locked") has expected result',
|
||||
toolbarModelIsTrayToggleVisible:
|
||||
'get("isTrayToggleVisible") has expected result',
|
||||
toolbarModelHeight: 'get("height") has expected result',
|
||||
toolbarModelOffsetsBottom:
|
||||
'get("offsets") bottom has expected result',
|
||||
toolbarModelOffsetsLeft: 'get("offsets") left has expected result',
|
||||
toolbarModelOffsetsRight:
|
||||
'get("offsets") right has expected result',
|
||||
toolbarModelOffsetsTop: 'get("offsets") top has expected result',
|
||||
toolbarModelSubtrees: 'get("subtrees") has expected result',
|
||||
};
|
||||
browser.assert.deepEqual(
|
||||
Object.keys(expectedTrue).sort(),
|
||||
Object.keys(result.value).sort(),
|
||||
'Keys to check match',
|
||||
);
|
||||
Object.keys(expectedTrue).forEach((property) => {
|
||||
browser.assert.equal(
|
||||
result.value[property],
|
||||
true,
|
||||
expectedTrue[property],
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
},
|
||||
'Change tab': (browser) => {
|
||||
browser.execute(
|
||||
function () {
|
||||
const toReturn = {};
|
||||
const { models } = Drupal.toolbar;
|
||||
toReturn.hasMenuModel = models.hasOwnProperty('menuModel');
|
||||
toReturn.menuModelType = typeof models.menuModel === 'object';
|
||||
toReturn.hasToolbarModel = models.hasOwnProperty('toolbarModel');
|
||||
toReturn.toolbarModelType = typeof models.toolbarModel === 'object';
|
||||
|
||||
const tab = document.querySelector('#toolbar-item-user');
|
||||
tab.dispatchEvent(new MouseEvent('click', { bubbles: true }));
|
||||
|
||||
toReturn.toolbarModelChangedTab =
|
||||
models.toolbarModel.get('activeTab').id === 'toolbar-item-user';
|
||||
toReturn.toolbarModelChangedTray =
|
||||
models.toolbarModel.get('activeTray').id === 'toolbar-item-user-tray';
|
||||
return toReturn;
|
||||
},
|
||||
[],
|
||||
(result) => {
|
||||
const expectedTrue = {
|
||||
hasMenuModel: 'has menu model',
|
||||
menuModelType: 'menu model is an object',
|
||||
hasToolbarModel: 'has toolbar model',
|
||||
toolbarModelType: 'toolbar model is an object',
|
||||
toolbarModelChangedTab: 'get("activeTab") has expected result',
|
||||
toolbarModelChangedTray: 'get("activeTray") has expected result',
|
||||
};
|
||||
browser.assert.deepEqual(
|
||||
Object.keys(expectedTrue).sort(),
|
||||
Object.keys(result.value).sort(),
|
||||
'Keys to check match',
|
||||
);
|
||||
Object.keys(expectedTrue).forEach((property) => {
|
||||
browser.assert.equal(
|
||||
result.value[property],
|
||||
true,
|
||||
expectedTrue[property],
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
},
|
||||
'Change orientation': (browser) => {
|
||||
browser.executeAsync(
|
||||
function (done) {
|
||||
const toReturn = {};
|
||||
const { models } = Drupal.toolbar;
|
||||
|
||||
const orientationToggle = document.querySelector(
|
||||
'#toolbar-item-administration-tray .toolbar-toggle-orientation button',
|
||||
);
|
||||
toReturn.toolbarOrientation =
|
||||
models.toolbarModel.get('orientation') === 'horizontal';
|
||||
orientationToggle.dispatchEvent(
|
||||
new MouseEvent('click', { bubbles: true }),
|
||||
);
|
||||
setTimeout(() => {
|
||||
toReturn.toolbarChangeOrientation =
|
||||
models.toolbarModel.get('orientation') === 'vertical';
|
||||
done(toReturn);
|
||||
}, 100);
|
||||
},
|
||||
[],
|
||||
(result) => {
|
||||
const expectedTrue = {
|
||||
toolbarOrientation: 'get("orientation") has expected result',
|
||||
toolbarChangeOrientation: 'changing orientation has expected result',
|
||||
};
|
||||
browser.assert.deepEqual(
|
||||
Object.keys(expectedTrue).sort(),
|
||||
Object.keys(result.value).sort(),
|
||||
'Keys to check match',
|
||||
);
|
||||
Object.keys(expectedTrue).forEach((property) => {
|
||||
browser.assert.equal(
|
||||
result.value[property],
|
||||
true,
|
||||
expectedTrue[property],
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
},
|
||||
'Open submenu': (browser) => {
|
||||
browser.executeAsync(
|
||||
function (done) {
|
||||
const toReturn = {};
|
||||
const { models } = Drupal.toolbar;
|
||||
Drupal.toolbar.models.toolbarModel.set('orientation', 'vertical');
|
||||
toReturn.toolbarOrientation =
|
||||
models.toolbarModel.get('orientation') === 'vertical';
|
||||
const manageTab = document.querySelector(
|
||||
'#toolbar-item-administration',
|
||||
);
|
||||
Drupal.toolbar.models.toolbarModel.set('activeTab', manageTab);
|
||||
const menuDropdown = document.querySelector(
|
||||
'#toolbar-item-administration-tray button',
|
||||
);
|
||||
menuDropdown.dispatchEvent(new MouseEvent('click', { bubbles: true }));
|
||||
|
||||
setTimeout(() => {
|
||||
const statReportElement = document.querySelector(
|
||||
'#toolbar-link-system-status',
|
||||
);
|
||||
toReturn.submenuItem =
|
||||
statReportElement.textContent === 'Status report';
|
||||
done(toReturn);
|
||||
}, 100);
|
||||
},
|
||||
[],
|
||||
(result) => {
|
||||
const expectedTrue = {
|
||||
toolbarOrientation: 'get("orientation") has expected result',
|
||||
submenuItem: 'opening submenu has expected result',
|
||||
};
|
||||
browser.assert.deepEqual(
|
||||
Object.keys(expectedTrue).sort(),
|
||||
Object.keys(result.value).sort(),
|
||||
'Keys to check match',
|
||||
);
|
||||
Object.keys(expectedTrue).forEach((property) => {
|
||||
browser.assert.equal(
|
||||
result.value[property],
|
||||
true,
|
||||
expectedTrue[property],
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,351 @@
|
||||
/**
|
||||
* @file
|
||||
* Test the expected toolbar functionality.
|
||||
*/
|
||||
|
||||
const itemAdministration = '#toolbar-item-administration';
|
||||
const itemAdministrationTray = '#toolbar-item-administration-tray';
|
||||
const adminOrientationButton = `${itemAdministrationTray} .toolbar-toggle-orientation button`;
|
||||
const itemUser = '#toolbar-item-user';
|
||||
const itemUserTray = '#toolbar-item-user-tray';
|
||||
const userOrientationBtn = `${itemUserTray} .toolbar-toggle-orientation button`;
|
||||
|
||||
module.exports = {
|
||||
'@tags': ['core'],
|
||||
before(browser) {
|
||||
browser.drupalInstall({
|
||||
setupFile:
|
||||
'core/modules/toolbar/tests/src/Nightwatch/ToolbarTestSetup.php',
|
||||
});
|
||||
},
|
||||
beforeEach(browser) {
|
||||
// Set the resolution to the default desktop resolution. Ensure the default
|
||||
// toolbar is horizontal in headless mode.
|
||||
browser
|
||||
.setWindowSize(1920, 1080)
|
||||
// To clear active tab/tray from previous tests
|
||||
.execute(function () {
|
||||
localStorage.clear();
|
||||
// Clear escapeAdmin URL values.
|
||||
sessionStorage.clear();
|
||||
})
|
||||
.drupalRelativeURL('/')
|
||||
.waitForElementPresent('#toolbar-administration');
|
||||
},
|
||||
after(browser) {
|
||||
browser.drupalUninstall();
|
||||
},
|
||||
'Change tab': (browser) => {
|
||||
browser.waitForElementPresent(itemUserTray);
|
||||
browser.assert.not.hasClass(itemUser, 'is-active');
|
||||
browser.assert.not.hasClass(itemUserTray, 'is-active');
|
||||
browser.click(itemUser);
|
||||
browser.assert.hasClass(itemUser, 'is-active');
|
||||
browser.assert.hasClass(itemUserTray, 'is-active');
|
||||
},
|
||||
'Change orientation': (browser) => {
|
||||
browser.waitForElementPresent(adminOrientationButton);
|
||||
browser.assert.hasClass(
|
||||
itemAdministrationTray,
|
||||
'is-active toolbar-tray-horizontal',
|
||||
);
|
||||
browser.click(adminOrientationButton);
|
||||
browser.assert.hasClass(
|
||||
itemAdministrationTray,
|
||||
'is-active toolbar-tray-vertical',
|
||||
);
|
||||
browser.click(adminOrientationButton);
|
||||
browser.assert.hasClass(
|
||||
itemAdministrationTray,
|
||||
'is-active toolbar-tray-horizontal',
|
||||
);
|
||||
},
|
||||
'Toggle tray': (browser) => {
|
||||
browser.waitForElementPresent(itemUserTray);
|
||||
browser.click(itemUser);
|
||||
browser.assert.hasClass(itemUserTray, 'is-active');
|
||||
browser.click(itemUser);
|
||||
browser.assert.not.hasClass(itemUserTray, 'is-active');
|
||||
browser.click(itemUser);
|
||||
browser.assert.hasClass(itemUserTray, 'is-active');
|
||||
},
|
||||
'Toggle submenu and sub-submenu': (browser) => {
|
||||
browser.waitForElementPresent(adminOrientationButton);
|
||||
browser.assert.hasClass(
|
||||
itemAdministrationTray,
|
||||
'is-active toolbar-tray-horizontal',
|
||||
);
|
||||
browser.click(adminOrientationButton);
|
||||
browser.assert.hasClass(
|
||||
itemAdministrationTray,
|
||||
'is-active toolbar-tray-vertical',
|
||||
);
|
||||
browser.waitForElementPresent(
|
||||
'#toolbar-item-administration-tray li:nth-child(2) button',
|
||||
);
|
||||
browser.assert.not.hasClass(
|
||||
'#toolbar-item-administration-tray li:nth-child(2)',
|
||||
'open',
|
||||
);
|
||||
browser.assert.not.hasClass(
|
||||
'#toolbar-item-administration-tray li:nth-child(2) button',
|
||||
'open',
|
||||
);
|
||||
browser.click('#toolbar-item-administration-tray li:nth-child(2) button');
|
||||
browser.assert.hasClass(
|
||||
'#toolbar-item-administration-tray li:nth-child(2)',
|
||||
'open',
|
||||
);
|
||||
browser.assert.hasClass(
|
||||
'#toolbar-item-administration-tray li:nth-child(2) button',
|
||||
'open',
|
||||
);
|
||||
browser.expect
|
||||
.element('#toolbar-link-user-admin_index')
|
||||
.text.to.equal('People');
|
||||
browser.expect
|
||||
.element('#toolbar-link-system-admin_config_system')
|
||||
.text.to.equal('System');
|
||||
// Check sub-submenu.
|
||||
browser.waitForElementPresent(
|
||||
'#toolbar-item-administration-tray li.menu-item.level-2',
|
||||
);
|
||||
browser.assert.not.hasClass(
|
||||
'#toolbar-item-administration-tray li.menu-item.level-2',
|
||||
'open',
|
||||
);
|
||||
browser.assert.not.hasClass(
|
||||
'#toolbar-item-administration-tray li.menu-item.level-2 button',
|
||||
'open',
|
||||
);
|
||||
browser.click(
|
||||
'#toolbar-item-administration-tray li.menu-item.level-2 button',
|
||||
);
|
||||
browser.assert.hasClass(
|
||||
'#toolbar-item-administration-tray li.menu-item.level-2',
|
||||
'open',
|
||||
);
|
||||
browser.assert.hasClass(
|
||||
'#toolbar-item-administration-tray li.menu-item.level-2 button',
|
||||
'open',
|
||||
);
|
||||
browser.expect
|
||||
.element('#toolbar-link-entity-user-admin_form')
|
||||
.text.to.equal('Account settings');
|
||||
},
|
||||
'Narrow toolbar width breakpoint': (browser) => {
|
||||
browser.waitForElementPresent(adminOrientationButton);
|
||||
browser.assert.hasClass(
|
||||
itemAdministrationTray,
|
||||
'is-active toolbar-tray-horizontal',
|
||||
);
|
||||
browser.assert.hasClass('#toolbar-administration', 'toolbar-oriented');
|
||||
browser.window.setSize(263, 900);
|
||||
browser.assert.hasClass(
|
||||
itemAdministrationTray,
|
||||
'is-active toolbar-tray-vertical',
|
||||
);
|
||||
browser.assert.not.hasClass(itemAdministration, 'toolbar-oriented');
|
||||
},
|
||||
'Standard width toolbar breakpoint': (browser) => {
|
||||
browser.window.setSize(1000, 900);
|
||||
browser.waitForElementPresent(adminOrientationButton);
|
||||
browser.assert.hasClass('body', 'toolbar-fixed');
|
||||
browser.window.setSize(609, 900);
|
||||
browser.assert.hasClass(
|
||||
itemAdministrationTray,
|
||||
'is-active toolbar-tray-vertical',
|
||||
);
|
||||
browser.assert.not.hasClass('body', 'toolbar-fixed');
|
||||
},
|
||||
'Wide toolbar breakpoint': (browser) => {
|
||||
browser.waitForElementPresent(adminOrientationButton);
|
||||
browser.window.setSize(975, 900);
|
||||
browser.assert.hasClass(
|
||||
itemAdministrationTray,
|
||||
'is-active toolbar-tray-vertical',
|
||||
);
|
||||
},
|
||||
'Back to site link': (browser) => {
|
||||
const escapeSelector = '[data-toolbar-escape-admin]';
|
||||
browser.drupalRelativeURL('/user');
|
||||
browser.drupalRelativeURL('/admin');
|
||||
// Don't check the visibility as stark doesn't add the .path-admin class
|
||||
// to the <body> required to display the button.
|
||||
browser.assert.attributeContains(escapeSelector, 'href', '/user/login');
|
||||
},
|
||||
'Aural view test: tray orientation': (browser) => {
|
||||
browser.waitForElementPresent(
|
||||
'#toolbar-item-administration-tray .toolbar-toggle-orientation button',
|
||||
);
|
||||
browser.executeAsync(
|
||||
function (done) {
|
||||
Drupal.announce = done;
|
||||
|
||||
const orientationButton = document.querySelector(
|
||||
'#toolbar-item-administration-tray .toolbar-toggle-orientation button',
|
||||
);
|
||||
orientationButton.dispatchEvent(
|
||||
new MouseEvent('click', { bubbles: true }),
|
||||
);
|
||||
},
|
||||
(result) => {
|
||||
browser.assert.equal(
|
||||
result.value,
|
||||
'Tray orientation changed to vertical.',
|
||||
);
|
||||
},
|
||||
);
|
||||
browser.executeAsync(
|
||||
function (done) {
|
||||
Drupal.announce = done;
|
||||
|
||||
const orientationButton = document.querySelector(
|
||||
'#toolbar-item-administration-tray .toolbar-toggle-orientation button',
|
||||
);
|
||||
orientationButton.dispatchEvent(
|
||||
new MouseEvent('click', { bubbles: true }),
|
||||
);
|
||||
},
|
||||
(result) => {
|
||||
browser.assert.equal(
|
||||
result.value,
|
||||
'Tray orientation changed to horizontal.',
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
'Aural view test: tray toggle': (browser) => {
|
||||
browser.executeAsync(
|
||||
function (done) {
|
||||
Drupal.announce = done;
|
||||
const $adminButton = jQuery('#toolbar-item-administration');
|
||||
$adminButton.trigger('click');
|
||||
},
|
||||
(result) => {
|
||||
browser.assert.equal(
|
||||
result.value,
|
||||
'Tray "Administration menu" closed.',
|
||||
);
|
||||
},
|
||||
);
|
||||
browser.executeAsync(
|
||||
function (done) {
|
||||
Drupal.announce = done;
|
||||
const $adminButton = jQuery('#toolbar-item-administration');
|
||||
$adminButton.trigger('click');
|
||||
},
|
||||
(result) => {
|
||||
browser.assert.equal(
|
||||
result.value,
|
||||
'Tray "Administration menu" opened.',
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
'Toolbar event: drupalToolbarOrientationChange': (browser) => {
|
||||
browser.executeAsync(
|
||||
function (done) {
|
||||
jQuery(document).on(
|
||||
'drupalToolbarOrientationChange',
|
||||
function (event, orientation) {
|
||||
done(orientation);
|
||||
},
|
||||
);
|
||||
const orientationButton = document.querySelector(
|
||||
'#toolbar-item-administration-tray .toolbar-toggle-orientation button',
|
||||
);
|
||||
orientationButton.dispatchEvent(
|
||||
new MouseEvent('click', { bubbles: true }),
|
||||
);
|
||||
},
|
||||
(result) => {
|
||||
browser.assert.equal(result.value, 'vertical');
|
||||
},
|
||||
);
|
||||
},
|
||||
'Toolbar event: drupalToolbarTabChange': (browser) => {
|
||||
browser.executeAsync(
|
||||
function (done) {
|
||||
jQuery(document).on('drupalToolbarTabChange', function (event, tab) {
|
||||
done(tab.id);
|
||||
});
|
||||
jQuery('#toolbar-item-user').trigger('click');
|
||||
},
|
||||
(result) => {
|
||||
browser.assert.equal(result.value, 'toolbar-item-user');
|
||||
},
|
||||
);
|
||||
},
|
||||
'Toolbar event: drupalToolbarTrayChange': (browser) => {
|
||||
browser.executeAsync(
|
||||
function (done) {
|
||||
const $adminButton = jQuery('#toolbar-item-administration');
|
||||
// Hide the admin menu first, this event is not firing reliably
|
||||
// otherwise.
|
||||
$adminButton.trigger('click');
|
||||
jQuery(document).on('drupalToolbarTrayChange', function (event, tray) {
|
||||
done(tray.id);
|
||||
});
|
||||
$adminButton.trigger('click');
|
||||
},
|
||||
(result) => {
|
||||
browser.assert.equal(result.value, 'toolbar-item-administration-tray');
|
||||
},
|
||||
);
|
||||
},
|
||||
'Locked toolbar vertical wide viewport': (browser) => {
|
||||
browser.window.setSize(1000, 900);
|
||||
browser.waitForElementPresent(adminOrientationButton);
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
browser.expect.element(adminOrientationButton).to.be.visible;
|
||||
browser.window.setSize(975, 900);
|
||||
browser.assert.hasClass(
|
||||
itemAdministrationTray,
|
||||
'is-active toolbar-tray-vertical',
|
||||
);
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
browser.expect.element(adminOrientationButton).to.not.be.visible;
|
||||
},
|
||||
'Settings are retained on refresh': (browser) => {
|
||||
browser.waitForElementPresent(itemUser);
|
||||
// Set user as active tab.
|
||||
browser.assert.not.hasClass(itemUser, 'is-active');
|
||||
browser.assert.not.hasClass(itemUserTray, 'is-active');
|
||||
browser.click(itemUser);
|
||||
// Check tab and tray are open.
|
||||
browser.assert.hasClass(itemUser, 'is-active');
|
||||
browser.assert.hasClass(itemUserTray, 'is-active');
|
||||
// Set orientation to vertical.
|
||||
browser.waitForElementPresent(userOrientationBtn);
|
||||
browser.assert.hasClass(itemUserTray, 'is-active toolbar-tray-horizontal');
|
||||
browser.click(userOrientationBtn);
|
||||
browser.assert.hasClass(itemUserTray, 'is-active toolbar-tray-vertical');
|
||||
browser.refresh();
|
||||
// Check user tab is active.
|
||||
browser.assert.hasClass(itemUser, 'is-active');
|
||||
// Check tray is active and orientation is vertical.
|
||||
browser.assert.hasClass(itemUserTray, 'is-active toolbar-tray-vertical');
|
||||
},
|
||||
'Check toolbar overlap with page content': (browser) => {
|
||||
browser.assert.hasClass('body', 'toolbar-horizontal');
|
||||
browser.execute(
|
||||
() => {
|
||||
const toolbar = document.querySelector('#toolbar-administration');
|
||||
const nextElement = toolbar.nextElementSibling.getBoundingClientRect();
|
||||
const tray = document
|
||||
.querySelector('#toolbar-item-administration-tray')
|
||||
.getBoundingClientRect();
|
||||
// Page content should start after the toolbar height to not overlap.
|
||||
return nextElement.top > tray.top + tray.height;
|
||||
},
|
||||
(result) => {
|
||||
browser.assert.equal(
|
||||
result.value,
|
||||
true,
|
||||
'Toolbar and page content do not overlap',
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\toolbar\Nightwatch;
|
||||
|
||||
use Drupal\Core\Extension\ModuleInstallerInterface;
|
||||
use Drupal\TestSite\TestSetupInterface;
|
||||
use Drupal\user\Entity\Role;
|
||||
use Drupal\user\RoleInterface;
|
||||
|
||||
/**
|
||||
* Sets up the site for testing the toolbar module.
|
||||
*/
|
||||
class ToolbarTestSetup implements TestSetupInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setup(): void {
|
||||
$module_installer = \Drupal::service('module_installer');
|
||||
assert($module_installer instanceof ModuleInstallerInterface);
|
||||
$module_installer->install(['toolbar']);
|
||||
|
||||
$role = Role::load(RoleInterface::ANONYMOUS_ID);
|
||||
foreach ([
|
||||
'access toolbar',
|
||||
'access administration pages',
|
||||
'administer modules',
|
||||
'administer site configuration',
|
||||
'administer account settings',
|
||||
] as $permission) {
|
||||
$role->grantPermission($permission);
|
||||
}
|
||||
$role->save();
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\toolbar\Unit\PageCache;
|
||||
|
||||
use Drupal\toolbar\PageCache\AllowToolbarPath;
|
||||
use Drupal\Core\PageCache\RequestPolicyInterface;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\toolbar\PageCache\AllowToolbarPath
|
||||
* @group toolbar
|
||||
*/
|
||||
class AllowToolbarPathTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* The toolbar path policy under test.
|
||||
*
|
||||
* @var \Drupal\toolbar\PageCache\AllowToolbarPath
|
||||
*/
|
||||
protected $policy;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->policy = new AllowToolbarPath();
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that caching is allowed if the request goes to toolbar subtree.
|
||||
*
|
||||
* @dataProvider providerTestAllowToolbarPath
|
||||
* @covers ::check
|
||||
*/
|
||||
public function testAllowToolbarPath($expected_result, $path): void {
|
||||
$request = Request::create($path);
|
||||
$result = $this->policy->check($request);
|
||||
$this->assertSame($expected_result, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides data and expected results for the test method.
|
||||
*
|
||||
* @return array
|
||||
* Data and expected results.
|
||||
*/
|
||||
public static function providerTestAllowToolbarPath() {
|
||||
return [
|
||||
[NULL, '/'],
|
||||
[NULL, '/other-path?q=/toolbar/subtrees/'],
|
||||
[NULL, '/toolbar/subtrees/'],
|
||||
[NULL, '/toolbar/subtrees/some-hash/langcode/additional-stuff'],
|
||||
[RequestPolicyInterface::ALLOW, '/de/toolbar/subtrees/abcd'],
|
||||
[RequestPolicyInterface::ALLOW, '/en-us/toolbar/subtrees/xyz'],
|
||||
[RequestPolicyInterface::ALLOW, '/en-us/toolbar/subtrees/xyz/de'],
|
||||
[RequestPolicyInterface::ALLOW, '/a/b/c/toolbar/subtrees/xyz/de'],
|
||||
[RequestPolicyInterface::ALLOW, '/toolbar/subtrees/some-hash'],
|
||||
[RequestPolicyInterface::ALLOW, '/toolbar/subtrees/some-hash/en'],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user