Initial Drupal 11 with DDEV setup
This commit is contained in:
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\navigation\Functional;
|
||||
|
||||
use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
|
||||
|
||||
/**
|
||||
* Generic module test for navigation.
|
||||
*
|
||||
* @group navigation
|
||||
*/
|
||||
class GenericTest extends GenericModuleTestBase {}
|
||||
@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\navigation\Functional;
|
||||
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
// cspell:ignore foobarbaz baznew
|
||||
|
||||
/**
|
||||
* Tests for navigation content_top section.
|
||||
*
|
||||
* @group navigation
|
||||
*/
|
||||
class NavigationContentTopTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['navigation', 'navigation_test', 'test_page_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->drupalLogin($this->createUser([
|
||||
'access navigation',
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests behavior of content_top section hooks.
|
||||
*/
|
||||
public function testNavigationContentTop(): void {
|
||||
$test_page_url = Url::fromRoute('test_page_test.test_page');
|
||||
$this->drupalGet($test_page_url);
|
||||
$this->assertSession()->elementNotExists('css', '.admin-toolbar__content-top');
|
||||
\Drupal::keyValue('navigation_test')->set('content_top', 1);
|
||||
Cache::invalidateTags(['navigation_test']);
|
||||
$this->drupalGet($test_page_url);
|
||||
$this->assertSession()->elementTextContains('css', '.admin-toolbar__content-top', 'foobarbaz');
|
||||
\Drupal::keyValue('navigation_test')->set('content_top_alter', 1);
|
||||
Cache::invalidateTags(['navigation_test']);
|
||||
$this->drupalGet($test_page_url);
|
||||
$this->assertSession()->elementTextContains('css', '.admin-toolbar__content-top', 'baznew bar');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\navigation\Functional;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests the default block provider logic.
|
||||
*
|
||||
* @group navigation
|
||||
*/
|
||||
class NavigationDefaultBlockDefinitionTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['test_page_test', 'block'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Tests the default block flow enabling Navigation module first.
|
||||
*/
|
||||
public function testNavigationDefaultAfterNavigation(): void {
|
||||
$test_page_url = Url::fromRoute('test_page_test.test_page');
|
||||
$module_installer = \Drupal::service('module_installer');
|
||||
|
||||
// After installing Navigation, the bar is present, but not the block.
|
||||
$module_installer->install(['navigation']);
|
||||
$this->drupalLogin($this->drupalCreateUser(['access navigation']));
|
||||
$this->drupalGet($test_page_url);
|
||||
$this->assertSession()->elementExists('css', '.admin-toolbar');
|
||||
$this->assertSession()->elementNotExists('css', '.toolbar-button--icon--test-block');
|
||||
|
||||
// After installing Navigation Test Block, both elements are present.
|
||||
$module_installer->install(['navigation_test_block']);
|
||||
$this->drupalGet($test_page_url);
|
||||
$this->assertSession()->elementExists('css', '.admin-toolbar');
|
||||
$this->assertSession()->elementContains('css', '.toolbar-button--icon--test-block', 'Test Navigation Block');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the default block flow enabling the block provider module first.
|
||||
*/
|
||||
public function testNavigationDefaultBeforeNavigation(): void {
|
||||
$test_page_url = Url::fromRoute('test_page_test.test_page');
|
||||
$module_installer = \Drupal::service('module_installer');
|
||||
|
||||
// After installing Navigation Test Block, none of the elements are present.
|
||||
$module_installer->install(['navigation_test_block']);
|
||||
$this->drupalGet($test_page_url);
|
||||
$this->assertSession()->elementNotExists('css', '.admin-toolbar');
|
||||
$this->assertSession()->elementNotExists('css', '.toolbar-button--icon--test-block');
|
||||
|
||||
// After installing Navigation, both elements are present.
|
||||
$module_installer->install(['navigation']);
|
||||
$this->drupalLogin($this->drupalCreateUser(['access navigation']));
|
||||
$this->drupalGet($test_page_url);
|
||||
$this->assertSession()->elementExists('css', '.admin-toolbar');
|
||||
$this->assertSession()->elementContains('css', '.toolbar-button--icon--test-block', 'Test Navigation Block');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\navigation\Functional;
|
||||
|
||||
use Drupal\Core\Menu\MenuLinkManagerInterface;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests Navigation Icon behavior.
|
||||
*
|
||||
* @group navigation
|
||||
*/
|
||||
class NavigationIconTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['navigation', 'navigation_test', 'test_page_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->drupalLogin($this->createUser([
|
||||
'access navigation',
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the behavior of custom icons.
|
||||
*/
|
||||
public function testNavigationIcon(): void {
|
||||
$this->drupalGet('/test-page');
|
||||
$this->assertSession()->elementAttributeContains('css', 'a.toolbar-button--icon--star svg', 'width', '25');
|
||||
$this->assertSession()->elementAttributeContains('css', 'a.toolbar-button--icon--star svg', 'class', 'toolbar-button__icon foo');
|
||||
$this->assertSession()->elementAttributeContains('css', 'a.toolbar-button--icon--pencil svg', 'width', '20');
|
||||
$this->assertSession()->elementAttributeContains('css', 'a.toolbar-button--icon--pencil svg', 'class', 'toolbar-button__icon');
|
||||
$this->assertSession()->elementAttributeContains('css', 'a.toolbar-button--icon--navigation-media svg', 'width', '20');
|
||||
$this->assertSession()->elementAttributeContains('css', 'a.toolbar-button--icon--navigation-media svg', 'class', 'toolbar-button__icon');
|
||||
$this->assertSession()->elementExists('css', 'a.toolbar-button--icon--navigation-test-navigation__no-icon');
|
||||
$this->assertSession()->elementNotExists('css', 'a.toolbar-button--icon--navigation-test-navigation__no-icon svg');
|
||||
|
||||
// Rebuild menu with alterations and reload the page to check them.
|
||||
\Drupal::keyValue('navigation_test')->set('menu_links_discovered_alter', 1);
|
||||
\Drupal::service(MenuLinkManagerInterface::class)->rebuild();
|
||||
|
||||
$this->drupalGet('/test-page');
|
||||
$this->assertSession()->elementAttributeContains('css', 'a.toolbar-button--icon--star svg', 'width', '25');
|
||||
$this->assertSession()->elementAttributeContains('css', 'a.toolbar-button--icon--star svg', 'class', 'toolbar-button__icon foo');
|
||||
$this->assertSession()->elementNotExists('css', 'a.toolbar-button--icon--pencil svg');
|
||||
$this->assertSession()->elementAttributeContains('css', 'a.toolbar-button--icon--navigation-media svg', 'width', '20');
|
||||
$this->assertSession()->elementAttributeContains('css', 'a.toolbar-button--icon--navigation-media svg', 'class', 'toolbar-button__icon');
|
||||
$this->assertSession()->elementAttributeContains('css', 'a.toolbar-button--icon--radioactive svg', 'width', '20');
|
||||
$this->assertSession()->elementAttributeContains('css', 'a.toolbar-button--icon--radioactive svg', 'class', 'toolbar-button__icon');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,231 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\navigation\Functional;
|
||||
|
||||
use Drupal\Core\Cache\CacheableMetadata;
|
||||
use Drupal\Core\Plugin\Context\Context;
|
||||
use Drupal\Core\Plugin\Context\ContextDefinition;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\layout_builder\SectionComponent;
|
||||
use Drupal\Tests\system\Functional\Cache\PageCacheTagsTestBase;
|
||||
|
||||
/**
|
||||
* Tests for \Drupal\navigation\Plugin\Block\NavigationLinkBlockTest.
|
||||
*
|
||||
* @group navigation
|
||||
*/
|
||||
class NavigationLinkBlockTest extends PageCacheTagsTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['navigation', 'test_page_test', 'entity_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* User with permission to administer navigation blocks and access navigation.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $adminUser;
|
||||
|
||||
/**
|
||||
* An authenticated user to test navigation block caching.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $normalUser;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Create an admin user, log in and enable test navigation blocks.
|
||||
$this->adminUser = $this->drupalCreateUser([
|
||||
'access administration pages',
|
||||
'administer site configuration',
|
||||
'access navigation',
|
||||
'view test entity',
|
||||
]);
|
||||
|
||||
// Create additional users to test caching modes.
|
||||
$this->normalUser = $this->drupalCreateUser([
|
||||
'access navigation',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test output of the link navigation with regards to caching and contents.
|
||||
*/
|
||||
public function testNavigationLinkBlockCache(): void {
|
||||
$label = 'Admin Main Page';
|
||||
$link_title = 'Navigation Settings';
|
||||
$link_uri = '/admin/config/user-interface/navigation/settings';
|
||||
$link_icon = 'admin-link';
|
||||
$this->appendNavigationLinkBlock($label, $link_title, 'internal:' . $link_uri, $link_icon);
|
||||
// Verify some basic cacheability metadata. Ensures that we're not doing
|
||||
// anything so egregious as to upset expected caching behavior. In this
|
||||
// case, as an anonymous user, we should have zero effect on the page.
|
||||
$test_page_url = Url::fromRoute('test_page_test.test_page');
|
||||
$this->verifyPageCache($test_page_url, 'MISS');
|
||||
$this->verifyPageCache($test_page_url, 'HIT');
|
||||
|
||||
// Login as a limited access user, and verify that the dynamic page cache
|
||||
// is working as expected.
|
||||
$this->drupalLogin($this->normalUser);
|
||||
$this->verifyDynamicPageCache($test_page_url, 'MISS');
|
||||
$this->verifyDynamicPageCache($test_page_url, 'HIT');
|
||||
// We should not see the admin page link in the page.
|
||||
$link_selector = '.admin-toolbar__item .toolbar-button--icon--' . $link_icon;
|
||||
$this->assertSession()->elementNotExists('css', $link_selector);
|
||||
$this->assertSession()->pageTextNotContains($link_title);
|
||||
$this->assertSession()->pageTextNotContains($label);
|
||||
|
||||
// Login as a different user, UI should update.
|
||||
$this->drupalLogin($this->adminUser);
|
||||
$this->verifyDynamicPageCache($test_page_url, 'MISS');
|
||||
$this->verifyDynamicPageCache($test_page_url, 'HIT');
|
||||
$this->drupalGet(Url::fromRoute('navigation.settings'));
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->elementExists('css', $link_selector);
|
||||
$this->assertSession()->pageTextContains($link_title);
|
||||
$this->assertSession()->pageTextContains($label);
|
||||
$this->assertSession()
|
||||
->elementTextContains('css', $link_selector, $link_title);
|
||||
// The link should link to the admin page.
|
||||
$link = $this->getSession()->getPage()->find('named', [
|
||||
'link',
|
||||
$link_title,
|
||||
]);
|
||||
$this->assertStringContainsString('/admin/config/user-interface/navigation/settings', $link->getAttribute('href'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test block visibility based on the link access logic.
|
||||
*/
|
||||
public function testNavigationLinkBlockVisibility(): void {
|
||||
// Add a link to an external URL.
|
||||
$external_label = 'External Link Block';
|
||||
$external_link_title = 'Link to example';
|
||||
$this->appendNavigationLinkBlock($external_label, $external_link_title, 'http://example.com', 'external');
|
||||
// Create an entity and create a link to it.
|
||||
$entity_type_manager = \Drupal::entityTypeManager();
|
||||
$entity_test_storage = $entity_type_manager->getStorage('entity_test');
|
||||
$entity_test_link = $entity_test_storage->create(['name' => 'test']);
|
||||
$entity_test_link->save();
|
||||
$entity_label = 'Entity Link BLock';
|
||||
$entity_link_title = 'Link to entity';
|
||||
$this->appendNavigationLinkBlock($entity_label, $entity_link_title, 'entity:entity_test/' . $entity_test_link->id(), 'entity');
|
||||
// Link to admin page.
|
||||
$admin_label = 'Admin Main Page';
|
||||
$admin_link_title = 'Navigation Settings';
|
||||
$this->appendNavigationLinkBlock($admin_label, $admin_link_title, 'internal:/admin/config/user-interface/navigation/settings', 'admin');
|
||||
// Link to generic internal page (Help Link).
|
||||
$help_label = 'Help Block';
|
||||
$help_link_title = 'Link to help';
|
||||
$this->appendNavigationLinkBlock($help_label, $help_link_title, 'internal:/admin/help', 'internal');
|
||||
|
||||
// Admin user should be capable to access to all the links but the internal
|
||||
// one, since Help module is not enabled.
|
||||
$test_page_url = Url::fromRoute('test_page_test.test_page');
|
||||
$this->drupalLogin($this->adminUser);
|
||||
$this->drupalGet($test_page_url);
|
||||
|
||||
$this->assertSession()->pageTextContains($external_label);
|
||||
$this->assertSession()->pageTextContains($external_link_title);
|
||||
$this->assertSession()->pageTextContains($entity_label);
|
||||
$this->assertSession()->pageTextContains($entity_link_title);
|
||||
$this->assertSession()->pageTextContains($admin_label);
|
||||
$this->assertSession()->pageTextContains($admin_link_title);
|
||||
$this->assertSession()->pageTextNotContains($help_label);
|
||||
$this->assertSession()->pageTextNotContains($help_link_title);
|
||||
|
||||
// Normal user should not have access only to the external link.
|
||||
$this->drupalLogin($this->normalUser);
|
||||
$this->drupalGet($test_page_url);
|
||||
|
||||
$this->assertSession()->pageTextContains($external_label);
|
||||
$this->assertSession()->pageTextContains($external_link_title);
|
||||
$this->assertSession()->pageTextNotContains($entity_label);
|
||||
$this->assertSession()->pageTextNotContains($entity_link_title);
|
||||
$this->assertSession()->pageTextNotContains($admin_label);
|
||||
$this->assertSession()->pageTextNotContains($admin_link_title);
|
||||
$this->assertSession()->pageTextNotContains($help_label);
|
||||
$this->assertSession()->pageTextNotContains($help_link_title);
|
||||
|
||||
// Enable Help module and grant permissions to admin user.
|
||||
// Admin user should be capable to access to all the links
|
||||
\Drupal::service('module_installer')->install(['help']);
|
||||
$this->adminUser->addRole($this->drupalCreateRole(['access help pages']))->save();
|
||||
|
||||
$this->drupalLogin($this->adminUser);
|
||||
$this->drupalGet($test_page_url);
|
||||
|
||||
$this->assertSession()->pageTextContains($external_label);
|
||||
$this->assertSession()->pageTextContains($external_link_title);
|
||||
$this->assertSession()->pageTextContains($entity_label);
|
||||
$this->assertSession()->pageTextContains($entity_link_title);
|
||||
$this->assertSession()->pageTextContains($admin_label);
|
||||
$this->assertSession()->pageTextContains($admin_link_title);
|
||||
$this->assertSession()->pageTextContains($help_label);
|
||||
$this->assertSession()->pageTextContains($help_link_title);
|
||||
|
||||
// Normal user should not have access only to the external link.
|
||||
$this->drupalLogin($this->normalUser);
|
||||
$this->drupalGet($test_page_url);
|
||||
|
||||
$this->assertSession()->pageTextContains($external_label);
|
||||
$this->assertSession()->pageTextContains($external_link_title);
|
||||
$this->assertSession()->pageTextNotContains($entity_label);
|
||||
$this->assertSession()->pageTextNotContains($entity_link_title);
|
||||
$this->assertSession()->pageTextNotContains($admin_label);
|
||||
$this->assertSession()->pageTextNotContains($admin_link_title);
|
||||
$this->assertSession()->pageTextNotContains($help_label);
|
||||
$this->assertSession()->pageTextNotContains($help_link_title);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a Navigation Link Block to the sidebar.
|
||||
*
|
||||
* @param string $label
|
||||
* The block label.
|
||||
* @param string $link_title
|
||||
* The link title.
|
||||
* @param string $link_uri
|
||||
* The link uri.
|
||||
* @param string $link_icon
|
||||
* The link icon CSS class.
|
||||
*/
|
||||
protected function appendNavigationLinkBlock(string $label, string $link_title, string $link_uri, string $link_icon): void {
|
||||
$section_storage_manager = \Drupal::service('plugin.manager.layout_builder.section_storage');
|
||||
$cacheability = new CacheableMetadata();
|
||||
$contexts = [
|
||||
'navigation' => new Context(ContextDefinition::create('string'), 'navigation'),
|
||||
];
|
||||
/** @var \Drupal\layout_builder\SectionListInterface $section_list */
|
||||
$section_list = $section_storage_manager->findByContext($contexts, $cacheability);
|
||||
$section = $section_list->getSection(0);
|
||||
|
||||
$section->appendComponent(new SectionComponent(\Drupal::service('uuid')->generate(), 'content', [
|
||||
'id' => 'navigation_link',
|
||||
'label' => $label,
|
||||
'label_display' => '1',
|
||||
'provider' => 'navigation',
|
||||
'context_mapping' => [],
|
||||
'title' => $link_title,
|
||||
'uri' => $link_uri,
|
||||
'icon_class' => $link_icon,
|
||||
]));
|
||||
|
||||
$section_list->save();
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,127 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\navigation\Functional;
|
||||
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Drupal\file\Entity\File;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\Tests\TestFileCreationTrait;
|
||||
|
||||
/**
|
||||
* Tests for \Drupal\navigation\Form\SettingsForm.
|
||||
*
|
||||
* @group navigation
|
||||
*/
|
||||
class NavigationLogoTest extends BrowserTestBase {
|
||||
|
||||
use StringTranslationTrait;
|
||||
use TestFileCreationTrait;
|
||||
|
||||
/**
|
||||
* The file system service.
|
||||
*
|
||||
* @var \Drupal\Core\File\FileSystemInterface
|
||||
*/
|
||||
protected $fileSystem;
|
||||
|
||||
/**
|
||||
* A user with administrative permissions.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $adminUser;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'navigation',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Inject the file_system service.
|
||||
$this->fileSystem = $this->container->get('file_system');
|
||||
|
||||
// Create and log in an administrative user.
|
||||
$this->adminUser = $this->drupalCreateUser([
|
||||
'administer site configuration',
|
||||
'access navigation',
|
||||
]);
|
||||
$this->drupalLogin($this->adminUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests Navigation logo configuration base options.
|
||||
*/
|
||||
public function testSettingsLogoOptionsForm(): void {
|
||||
$test_files = $this->getTestFiles('image');
|
||||
// Navigate to the settings form.
|
||||
$this->drupalGet('/admin/config/user-interface/navigation/settings');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
|
||||
// Option 1. Check the default logo provider.
|
||||
$this->assertSession()->fieldValueEquals('logo_provider', 'default');
|
||||
$this->assertSession()->elementExists('css', 'a.admin-toolbar__logo > svg');
|
||||
|
||||
// Option 2: Set the logo provider to hide and check.
|
||||
$edit = [
|
||||
'logo_provider' => 'hide',
|
||||
];
|
||||
$this->submitForm($edit, 'Save configuration');
|
||||
$this->assertSession()->pageTextContains('The configuration options have been saved.');
|
||||
$this->assertSession()->elementNotExists('css', 'a.admin-toolbar__logo');
|
||||
|
||||
// Option 3: Set the logo provider to custom and upload a logo.
|
||||
$file = reset($test_files);
|
||||
$logo_file = File::create((array) $file + ['status' => 1]);
|
||||
$logo_file->save();
|
||||
$this->assertNotEmpty($logo_file, 'File entity is not empty.');
|
||||
|
||||
$edit = [
|
||||
'logo_provider' => 'custom',
|
||||
'logo_path' => $logo_file->getFileUri(),
|
||||
];
|
||||
$this->submitForm($edit, 'Save configuration');
|
||||
// Refresh the page to verify custom logo is placed.
|
||||
$this->drupalGet('/admin/config/user-interface/navigation/settings');
|
||||
$this->assertSession()->elementExists('css', 'a.admin-toolbar__logo > img');
|
||||
$this->assertSession()->elementAttributeContains('css', 'a.admin-toolbar__logo > img', 'src', $logo_file->getFilename());
|
||||
|
||||
// Option 4: Set the custom logo to an image in the source code.
|
||||
$edit = [
|
||||
'logo_provider' => 'custom',
|
||||
'logo_path' => 'core/misc/logo/drupal-logo.svg',
|
||||
];
|
||||
$this->submitForm($edit, 'Save configuration');
|
||||
// Refresh the page to verify custom logo is placed.
|
||||
$this->drupalGet('/admin/config/user-interface/navigation/settings');
|
||||
$this->assertSession()->elementExists('css', 'a.admin-toolbar__logo > img');
|
||||
$this->assertSession()->elementAttributeContains('css', 'a.admin-toolbar__logo > img', 'src', 'drupal-logo.svg');
|
||||
|
||||
// Option 5: Upload custom logo.
|
||||
$file = end($test_files);
|
||||
$edit = [
|
||||
'logo_provider' => 'custom',
|
||||
'files[logo_upload]' => $this->fileSystem->realpath($file->uri),
|
||||
];
|
||||
$this->submitForm($edit, 'Save configuration');
|
||||
$this->assertSession()->statusMessageContains('The image was resized to fit within the navigation logo expected dimensions of 40x40 pixels. The new dimensions of the resized image are 40x27 pixels.');
|
||||
// Refresh the page to verify custom logo is placed.
|
||||
$this->drupalGet('/admin/config/user-interface/navigation/settings');
|
||||
$this->assertSession()->elementExists('css', 'a.admin-toolbar__logo > img');
|
||||
$this->assertSession()->elementAttributeContains('css', 'a.admin-toolbar__logo > img', 'src', $file->name);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\navigation\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests the definition of navigation safe blocks.
|
||||
*
|
||||
* @group navigation
|
||||
*/
|
||||
class NavigationSafeBlockDefinitionTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['navigation', 'navigation_test', 'block'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* User with permission to administer navigation blocks and access navigation.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $adminUser;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Create an admin user, log in and enable test navigation blocks.
|
||||
$this->adminUser = $this->drupalCreateUser([
|
||||
'configure navigation layout',
|
||||
'access navigation',
|
||||
'administer blocks',
|
||||
]);
|
||||
|
||||
$this->drupalLogin($this->adminUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests logic to include blocks in Navigation Layout UI.
|
||||
*/
|
||||
public function testNavigationSafeBlockDefinition(): void {
|
||||
// Confirm that default blocks are available.
|
||||
$layout_url = '/layout_builder/choose/block/navigation/navigation.block_layout/0/content';
|
||||
$this->drupalGet($layout_url);
|
||||
|
||||
$this->assertSession()->linkExists('Administration');
|
||||
$this->assertSession()->linkExists('Content');
|
||||
$this->assertSession()->linkExists('Footer');
|
||||
$this->assertSession()->linkExists('Navigation Shortcuts');
|
||||
$this->assertSession()->linkExists('User');
|
||||
$this->assertSession()->linkNotExists('Link');
|
||||
|
||||
// Apply changes, clear cache and confirm that changes are applied.
|
||||
\Drupal::state()->set('navigation_safe_alter', TRUE);
|
||||
\Drupal::cache('discovery')->delete('block_plugins');
|
||||
|
||||
$this->drupalGet($this->getUrl());
|
||||
$this->assertSession()->linkExists('Administration');
|
||||
$this->assertSession()->linkExists('Content');
|
||||
$this->assertSession()->linkExists('Footer');
|
||||
$this->assertSession()->linkExists('Link');
|
||||
$this->assertSession()->linkNotExists('Navigation Shortcuts');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests logic to exclude blocks in Block Layout UI.
|
||||
*/
|
||||
public function testNavigationBlocksHiddenInBlockLayout(): void {
|
||||
$block_url = '/admin/structure/block';
|
||||
$this->drupalGet($block_url);
|
||||
$this->clickLink('Place block');
|
||||
$this->assertSession()->linkByHrefNotExists('/admin/structure/block/add/navigation_menu%3Aadmin/stark');
|
||||
$this->assertSession()->linkByHrefNotExists('/admin/structure/block/add/navigation_menu%3Acontent/stark');
|
||||
$this->assertSession()->linkByHrefNotExists('/admin/structure/block/add/navigation_shortcuts/stark');
|
||||
$this->assertSession()->linkByHrefNotExists('/admin/structure/block/add/navigation_user/stark');
|
||||
$this->assertSession()->linkByHrefNotExists('/admin/structure/block/add/navigation_link/stark');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,224 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\navigation\Functional;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\shortcut\Entity\Shortcut;
|
||||
use Drupal\shortcut\Entity\ShortcutSet;
|
||||
use Drupal\Tests\system\Functional\Cache\AssertPageCacheContextsAndTagsTrait;
|
||||
use Drupal\Tests\system\Functional\Cache\PageCacheTagsTestBase;
|
||||
|
||||
/**
|
||||
* Tests for \Drupal\navigation\Plugin\Block\NavigationShortcutsBlock.
|
||||
*
|
||||
* @group navigation
|
||||
*/
|
||||
class NavigationShortcutsBlockTest extends PageCacheTagsTestBase {
|
||||
|
||||
use AssertPageCacheContextsAndTagsTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['navigation', 'shortcut', 'test_page_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Tests visibility and cacheability of shortcuts in the navigation bar.
|
||||
*/
|
||||
public function testNavigationBlock(): void {
|
||||
$this->drupalPlaceBlock('page_title_block', ['id' => 'title']);
|
||||
|
||||
$test_page_url = Url::fromRoute('test_page_test.test_page');
|
||||
$this->verifyPageCache($test_page_url, 'MISS');
|
||||
$this->verifyPageCache($test_page_url, 'HIT');
|
||||
|
||||
// Ensure that without enabling the shortcuts-in-page-title-link feature
|
||||
// in the theme, the shortcut_list cache tag is not added to the page.
|
||||
$admin_user = $this->drupalCreateUser([
|
||||
'administer site configuration',
|
||||
'access navigation',
|
||||
'administer shortcuts',
|
||||
'access shortcuts',
|
||||
]);
|
||||
$this->drupalLogin($admin_user);
|
||||
$this->drupalGet('admin/config/system/cron');
|
||||
$expected_cache_tags = array_merge([
|
||||
'CACHE_MISS_IF_UNCACHEABLE_HTTP_METHOD:form',
|
||||
'block_view',
|
||||
'config:block.block.title',
|
||||
'config:block_list',
|
||||
'config:navigation.settings',
|
||||
'config:navigation.block_layout',
|
||||
'config:shortcut.set.default',
|
||||
'config:system.menu.admin',
|
||||
'config:system.menu.content',
|
||||
'config:system.menu.navigation-user-links',
|
||||
'http_response',
|
||||
'rendered',
|
||||
], $admin_user->getCacheTags());
|
||||
$this->assertCacheTags($expected_cache_tags);
|
||||
|
||||
\Drupal::configFactory()
|
||||
->getEditable('stark.settings')
|
||||
->set('third_party_settings.shortcut.module_link', TRUE)
|
||||
->save(TRUE);
|
||||
|
||||
// Add cron to the default shortcut set, now the shortcut list cache tag
|
||||
// is expected.
|
||||
$this->drupalGet('admin/config/system/cron');
|
||||
$this->clickLink('Add to Default shortcuts');
|
||||
$expected_cache_tags[] = 'config:shortcut_set_list';
|
||||
$this->assertCacheTags($expected_cache_tags);
|
||||
|
||||
// Verify that users without the 'access shortcuts' permission can't see the
|
||||
// shortcuts.
|
||||
$this->drupalLogin($this->drupalCreateUser(['access navigation']));
|
||||
$this->assertSession()->pageTextNotContains('Shortcuts');
|
||||
$this->verifyDynamicPageCache($test_page_url, 'MISS');
|
||||
$this->verifyDynamicPageCache($test_page_url, 'HIT');
|
||||
|
||||
// Verify that users without the 'administer site configuration' permission
|
||||
// can't see the cron shortcut nor the shortcuts navigation item.
|
||||
$this->drupalLogin($this->drupalCreateUser([
|
||||
'access navigation',
|
||||
'access shortcuts',
|
||||
]));
|
||||
$this->verifyDynamicPageCache($test_page_url, 'MISS');
|
||||
$this->verifyDynamicPageCache($test_page_url, 'HIT');
|
||||
$this->assertSession()->pageTextNotContains('Shortcuts');
|
||||
$this->assertSession()->linkNotExists('Cron');
|
||||
|
||||
// Create a role with access to shortcuts as well as the necessary
|
||||
// permissions to see specific shortcuts.
|
||||
$site_configuration_role = $this->drupalCreateRole([
|
||||
'access navigation',
|
||||
'access shortcuts',
|
||||
'administer site configuration',
|
||||
'access administration pages',
|
||||
'configure navigation layout',
|
||||
]);
|
||||
|
||||
// Create two different users with the same role to assert that the second
|
||||
// user has a cache hit despite the user cache context, as
|
||||
// the returned cache contexts include those from lazy-builder content.
|
||||
$site_configuration_user1 = $this->drupalCreateUser();
|
||||
$site_configuration_user1->addRole($site_configuration_role)->save();
|
||||
$site_configuration_user2 = $this->drupalCreateUser();
|
||||
$site_configuration_user2->addRole($site_configuration_role)->save();
|
||||
|
||||
$this->drupalLogin($site_configuration_user1);
|
||||
$this->verifyDynamicPageCache($test_page_url, 'MISS');
|
||||
$this->verifyDynamicPageCache($test_page_url, 'HIT');
|
||||
$this->assertCacheContexts(['user', 'url.query_args:_wrapper_format', 'session', 'route']);
|
||||
$this->assertSession()->pageTextContains('Shortcuts');
|
||||
$this->assertSession()->linkExists('Cron');
|
||||
|
||||
$this->drupalLogin($site_configuration_user2);
|
||||
$this->verifyDynamicPageCache($test_page_url, 'HIT');
|
||||
$this->assertCacheContexts(['user', 'url.query_args:_wrapper_format', 'session', 'route']);
|
||||
$this->assertSession()->pageTextContains('Shortcuts');
|
||||
$this->assertSession()->linkExists('Cron');
|
||||
|
||||
// Add another shortcut.
|
||||
$shortcut = Shortcut::create([
|
||||
'shortcut_set' => 'default',
|
||||
'title' => 'Llama',
|
||||
'weight' => 0,
|
||||
'link' => [['uri' => 'internal:/admin/config']],
|
||||
]);
|
||||
$shortcut->save();
|
||||
|
||||
// The shortcuts are displayed in a lazy builder, so the page is still a
|
||||
// cache HIT but shows the new shortcut immediately.
|
||||
$this->verifyDynamicPageCache($test_page_url, 'HIT');
|
||||
$this->assertSession()->linkExists('Cron');
|
||||
$this->assertSession()->linkExists('Llama');
|
||||
|
||||
// Update the shortcut title and assert that it is updated.
|
||||
$shortcut->set('title', 'Alpaca');
|
||||
$shortcut->save();
|
||||
$this->verifyDynamicPageCache($test_page_url, 'HIT');
|
||||
$this->assertSession()->linkExists('Cron');
|
||||
$this->assertSession()->linkExists('Alpaca');
|
||||
|
||||
// Delete the shortcut and assert that the link is gone.
|
||||
$shortcut->delete();
|
||||
$this->verifyDynamicPageCache($test_page_url, 'HIT');
|
||||
$this->assertSession()->linkExists('Cron');
|
||||
$this->assertSession()->linkNotExists('Alpaca');
|
||||
|
||||
// Add a new Shortcut Set with a single link.
|
||||
$new_set = ShortcutSet::create([
|
||||
'id' => 'llama-set',
|
||||
'label' => 'Llama Set',
|
||||
]);
|
||||
$new_set->save();
|
||||
$new_shortcut = Shortcut::create([
|
||||
'shortcut_set' => 'llama-set',
|
||||
'title' => 'New Llama',
|
||||
'weight' => 0,
|
||||
'link' => [['uri' => 'internal:/admin/config']],
|
||||
]);
|
||||
$new_shortcut->save();
|
||||
|
||||
// Assign the new shortcut set to user 2 and confirm that links are changed
|
||||
// automatically.
|
||||
\Drupal::entityTypeManager()
|
||||
->getStorage('shortcut_set')
|
||||
->assignUser($new_set, $site_configuration_user2);
|
||||
$this->verifyDynamicPageCache($test_page_url, 'HIT');
|
||||
$this->assertSession()->linkExists('Cron');
|
||||
$this->assertSession()->linkExists('New Llama');
|
||||
// Confirm that links for user 1 have not been affected.
|
||||
$this->drupalLogin($site_configuration_user1);
|
||||
$this->verifyDynamicPageCache($test_page_url, 'HIT');
|
||||
$this->assertSession()->linkExists('Cron');
|
||||
$this->assertSession()->linkNotExists('New Llama');
|
||||
// Confirm that removing assignment automatically changes the links too.
|
||||
$this->drupalLogin($site_configuration_user2);
|
||||
$this->verifyDynamicPageCache($test_page_url, 'HIT');
|
||||
$this->assertSession()->linkExists('Cron');
|
||||
$this->assertSession()->linkExists('New Llama');
|
||||
\Drupal::entityTypeManager()
|
||||
->getStorage('shortcut_set')
|
||||
->unassignUser($site_configuration_user2);
|
||||
$this->verifyDynamicPageCache($test_page_url, 'HIT');
|
||||
$this->assertSession()->linkExists('Cron');
|
||||
$this->assertSession()->linkNotExists('New Llama');
|
||||
// Confirm that deleting a shortcut set automatically changes the links too.
|
||||
\Drupal::entityTypeManager()
|
||||
->getStorage('shortcut_set')
|
||||
->assignUser($new_set, $site_configuration_user2);
|
||||
$this->verifyDynamicPageCache($test_page_url, 'HIT');
|
||||
$this->assertSession()->linkExists('Cron');
|
||||
$this->assertSession()->linkExists('New Llama');
|
||||
\Drupal::entityTypeManager()
|
||||
->getStorage('shortcut_set')
|
||||
->delete([$new_set]);
|
||||
$this->verifyDynamicPageCache($test_page_url, 'HIT');
|
||||
$this->assertSession()->linkExists('Cron');
|
||||
$this->assertSession()->linkNotExists('New Llama');
|
||||
|
||||
// Verify that block disappears gracefully when shortcut module is disabled.
|
||||
// Shortcut entities has to be removed first.
|
||||
$link_storage = \Drupal::entityTypeManager()->getStorage('shortcut');
|
||||
$link_storage->delete($link_storage->loadMultiple());
|
||||
\Drupal::service('module_installer')->uninstall(['shortcut']);
|
||||
$this->verifyDynamicPageCache($test_page_url, 'MISS');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextNotContains('Shortcuts');
|
||||
|
||||
// Confirm that Navigation Blocks page is working.
|
||||
// @see https://www.drupal.org/project/drupal/issues/3445184
|
||||
$this->drupalGet('/admin/config/user-interface/navigation-block');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\navigation\Functional;
|
||||
|
||||
use Drupal\node\NodeInterface;
|
||||
use Drupal\Tests\content_moderation\Functional\ModerationStateTestBase;
|
||||
|
||||
/**
|
||||
* Tests the top bar behavior along with content moderation.
|
||||
*
|
||||
* @group navigation
|
||||
*/
|
||||
class NavigationTopBarContentModerationTest extends ModerationStateTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'content_moderation',
|
||||
'node',
|
||||
'navigation',
|
||||
];
|
||||
|
||||
/**
|
||||
* Node used to check top bar options.
|
||||
*
|
||||
* @var \Drupal\node\NodeInterface
|
||||
*/
|
||||
protected NodeInterface $node;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->adminUser
|
||||
->addRole($this->drupalCreateRole(['access navigation']))
|
||||
->save();
|
||||
$this->drupalLogin($this->adminUser);
|
||||
$this->createContentTypeFromUi('Moderated content', 'moderated_content', TRUE);
|
||||
$this->grantUserPermissionToCreateContentOfType($this->adminUser, 'moderated_content');
|
||||
|
||||
$this->node = $this->drupalCreateNode([
|
||||
'type' => 'moderated_content',
|
||||
'moderation_state' => 'published',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the interaction of page actions and content moderation.
|
||||
*/
|
||||
public function testContentModerationPageActions(): void {
|
||||
$this->drupalGet($this->node->toUrl());
|
||||
$this->assertSession()->elementNotContains('xpath', '//*[@id="top-bar-page-actions"]/ul', 'Latest version');
|
||||
|
||||
// Publish a new draft.
|
||||
$this->node->setNewRevision(TRUE);
|
||||
$this->node->setTitle($this->node->getTitle() . ' - draft');
|
||||
$this->node->moderation_state->value = 'draft';
|
||||
$this->node->save();
|
||||
|
||||
$this->drupalGet($this->node->toUrl());
|
||||
$this->assertSession()->elementContains('xpath', '//*[@id="top-bar-page-actions"]/ul', 'Latest version');
|
||||
$this->assertSession()->elementContains('css', '.toolbar-badge--success', 'Published (Draft available)');
|
||||
|
||||
// Confirm that Edit option is featured in Latest version page.
|
||||
$this->clickLink('Latest version');
|
||||
$this->assertSession()->elementNotContains('xpath', '//*[@id="top-bar-page-actions"]/ul', 'Edit');
|
||||
$this->assertSession()->elementContains('css', '.toolbar-badge--info', 'Draft');
|
||||
$this->assertSession()->elementTextEquals('xpath', "//div[contains(@class, 'top-bar__content')]/div[contains(@class, 'top-bar__actions')]/a[contains(@class, 'toolbar-button--icon--pencil')]", "Edit");
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\navigation\Functional;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\Tests\node\Traits\ContentTypeCreationTrait;
|
||||
use Drupal\Tests\node\Traits\NodeCreationTrait;
|
||||
use Drupal\user\UserInterface;
|
||||
|
||||
/**
|
||||
* Tests the PageContext top bar item functionality.
|
||||
*
|
||||
* @group navigation
|
||||
*/
|
||||
class NavigationTopBarPageContextTest extends BrowserTestBase {
|
||||
|
||||
use ContentTypeCreationTrait;
|
||||
use NodeCreationTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'node',
|
||||
'navigation',
|
||||
'test_page_test',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* An admin user to configure the test environment.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected UserInterface $adminUser;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Create and log in an administrative user.
|
||||
$this->adminUser = $this->drupalCreateUser([
|
||||
'access navigation',
|
||||
'bypass node access',
|
||||
]);
|
||||
$this->drupalLogin($this->adminUser);
|
||||
|
||||
// Ensure the 'article' content type exists.
|
||||
$this->createContentType(['type' => 'article', 'name' => 'Article']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the PageContext top bar item output for a published node.
|
||||
*/
|
||||
public function testPageContextTopBarItemNode(): void {
|
||||
// Create a published node entity.
|
||||
$node = $this->createNode([
|
||||
'type' => 'article',
|
||||
'title' => 'No easy twist on the bow',
|
||||
'status' => 1,
|
||||
'uid' => $this->adminUser->id(),
|
||||
]);
|
||||
|
||||
$test_page_url = Url::fromRoute('test_page_test.test_page');
|
||||
$this->drupalGet($test_page_url);
|
||||
// Ensure the top bar item is not present.
|
||||
$this->assertSession()->elementNotExists('css', '.top-bar .top-bar__context .toolbar-title');
|
||||
|
||||
// Test the PageContext output for the published node.
|
||||
$this->drupalGet($node->toUrl());
|
||||
// Ensure the top bar exists and is valid.
|
||||
$this->assertSession()->elementTextEquals('css', '.top-bar .top-bar__context .toolbar-title', 'No easy twist on the bow');
|
||||
$this->assertSession()->elementTextEquals('css', '.top-bar .top-bar__context .toolbar-badge', 'Published');
|
||||
$this->drupalGet($node->toUrl('edit-form'));
|
||||
// Ensure the top bar exists and is valid.
|
||||
$this->assertSession()->elementTextEquals('css', '.top-bar .top-bar__context .toolbar-title', 'No easy twist on the bow');
|
||||
$this->assertSession()->elementTextEquals('css', '.top-bar .top-bar__context .toolbar-badge', 'Published');
|
||||
|
||||
// Unpublish the node.
|
||||
$node->setUnpublished();
|
||||
$node->save();
|
||||
|
||||
// Test the PageContext output for the unpublished node.
|
||||
$this->drupalGet($node->toUrl());
|
||||
// Ensure the top bar exists and is valid.
|
||||
$this->assertSession()->elementTextEquals('css', '.top-bar .top-bar__context .toolbar-title', 'No easy twist on the bow');
|
||||
$this->assertSession()->elementTextEquals('css', '.top-bar .top-bar__context .toolbar-badge', 'Unpublished');
|
||||
$this->drupalGet($node->toUrl('edit-form'));
|
||||
// Ensure the top bar exists and is valid.
|
||||
$this->assertSession()->elementTextEquals('css', '.top-bar .top-bar__context .toolbar-title', 'No easy twist on the bow');
|
||||
$this->assertSession()->elementTextEquals('css', '.top-bar .top-bar__context .toolbar-badge', 'Unpublished');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,162 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\navigation\Functional;
|
||||
|
||||
use Behat\Mink\Element\NodeElement;
|
||||
use Drupal\Component\Utility\SortArray;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay;
|
||||
use Drupal\Tests\system\Functional\Cache\PageCacheTagsTestBase;
|
||||
|
||||
/**
|
||||
* Tests the top bar functionality.
|
||||
*
|
||||
* @group navigation
|
||||
*/
|
||||
class NavigationTopBarTest extends PageCacheTagsTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'navigation',
|
||||
'navigation_test_top_bar',
|
||||
'node',
|
||||
'layout_builder',
|
||||
'test_page_test',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* An admin user to configure the test environment.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $adminUser;
|
||||
|
||||
/**
|
||||
* Node used to check top bar options.
|
||||
*
|
||||
* @var \Drupal\node\NodeInterface
|
||||
*/
|
||||
protected $node;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
// Create and log in an administrative user.
|
||||
$this->adminUser = $this->drupalCreateUser([
|
||||
'administer site configuration',
|
||||
'access administration pages',
|
||||
'access navigation',
|
||||
'bypass node access',
|
||||
'configure any layout',
|
||||
]);
|
||||
$this->drupalLogin($this->adminUser);
|
||||
|
||||
// Create a new content type and enable Layout Builder for it.
|
||||
$node_type = $this->createContentType(['type' => 'node_type']);
|
||||
LayoutBuilderEntityViewDisplay::load('node.node_type.default')
|
||||
->enableLayoutBuilder()
|
||||
->setOverridable()
|
||||
->save();
|
||||
|
||||
// Place the tabs block to check its presence.
|
||||
$this->drupalPlaceBlock('local_tasks_block', ['id' => 'tabs']);
|
||||
|
||||
// Enable some test blocks.
|
||||
$this->node = $this->drupalCreateNode(['type' => $node_type->id()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the top bar visibility.
|
||||
*/
|
||||
public function testTopBarVisibility(): void {
|
||||
// Test page does not include the Top Bar.
|
||||
$test_page_url = Url::fromRoute('test_page_test.test_page');
|
||||
$this->verifyDynamicPageCache($test_page_url, 'MISS');
|
||||
$this->verifyDynamicPageCache($test_page_url, 'HIT');
|
||||
$this->assertSession()->elementNotExists('xpath', "//div[contains(@class, 'top-bar__content')]/div[contains(@class, 'top-bar__actions')]/button");
|
||||
|
||||
// Top Bar is visible on node pages.
|
||||
$this->verifyDynamicPageCache($this->node->toUrl(), 'MISS');
|
||||
$this->verifyDynamicPageCache($this->node->toUrl(), 'HIT');
|
||||
$this->assertSession()->elementExists('xpath', "(//div[contains(@class, 'top-bar__content')]/div[contains(@class, 'top-bar__actions')]/button)[1]");
|
||||
$this->assertSession()->elementTextEquals('xpath', "//div[contains(@class, 'top-bar__content')]/div[contains(@class, 'top-bar__actions')]/a[contains(@class, 'toolbar-button--icon--pencil')]", "Edit");
|
||||
$this->assertSession()->elementAttributeContains('xpath', "(//div[contains(@class, 'top-bar__content')]/div[contains(@class, 'top-bar__actions')]/button)[1]", 'class', 'toolbar-button--icon--dots');
|
||||
|
||||
// Verify that the action link contains an extra attribute.
|
||||
$this->assertSession()->elementTextEquals('xpath', "//div[contains(@class, 'top-bar__content')]/div[contains(@class, 'top-bar__actions')]/a[contains(@class, 'toolbar-button--icon--database')]", "Test link");
|
||||
$this->assertSession()->elementAttributeContains('xpath', "//div[contains(@class, 'top-bar__content')]/div[contains(@class, 'top-bar__actions')]/a[contains(@class, 'toolbar-button--icon--database')]", 'data-dialog-type', 'modal');
|
||||
|
||||
// Find all the dropdown links and check if the top bar is there as well.
|
||||
$toolbar_links = $this->mink->getSession()->getPage()->find('xpath', '//*[@id="top-bar-page-actions"]/ul');
|
||||
|
||||
foreach ($toolbar_links->findAll('css', 'li') as $toolbar_link) {
|
||||
$this->clickLink($toolbar_link->getText());
|
||||
$this->assertSession()->elementExists('xpath', "(//div[contains(@class, 'top-bar__content')]/div[contains(@class, 'top-bar__actions')]/button)[1]");
|
||||
$this->assertSession()->elementAttributeContains('xpath', "(//div[contains(@class, 'top-bar__content')]/div[contains(@class, 'top-bar__actions')]/button)[1]", 'class', 'toolbar-button--icon--dots');
|
||||
// Ensure that link to current page is not included in the dropdown.
|
||||
$url = $this->getSession()->getCurrentUrl();
|
||||
$this->assertSession()->linkByHrefNotExistsExact(parse_url($url, PHP_URL_PATH));
|
||||
// Ensure that the actions are displayed in the correct order.
|
||||
$this->assertActionsWeight($toolbar_links);
|
||||
}
|
||||
|
||||
// Regular tabs are visible for user that cannot access to navigation.
|
||||
$this->drupalLogin($this->drupalCreateUser([
|
||||
'bypass node access',
|
||||
]));
|
||||
|
||||
$this->drupalGet($this->node->toUrl());
|
||||
$this->assertSession()->elementNotExists('xpath', "//div[contains(@class, 'top-bar__content')]/div[contains(@class, 'top-bar__actions')]/button");
|
||||
$this->assertSession()->elementExists('xpath', '//div[@id="block-tabs"]');
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that top bar actions respect local tasks weights.
|
||||
*
|
||||
* @param \Behat\Mink\Element\NodeElement $toolbar_links
|
||||
* Action links to assert.
|
||||
*/
|
||||
protected function assertActionsWeight(NodeElement $toolbar_links): void {
|
||||
// Displayed action links in the top bar.
|
||||
$displayed_links = array_map(
|
||||
fn($link) => $link->getText(),
|
||||
$toolbar_links->findAll('css', 'li')
|
||||
);
|
||||
|
||||
// Extract the route name from the URL.
|
||||
$current_url = $this->getSession()->getCurrentUrl();
|
||||
// Convert alias to system path.
|
||||
$path = parse_url($current_url, PHP_URL_PATH);
|
||||
|
||||
if ($GLOBALS['base_path'] !== '/') {
|
||||
$path = str_replace($GLOBALS['base_path'], '/', $path);
|
||||
}
|
||||
|
||||
// Get local tasks for the current route.
|
||||
$entity_local_tasks = \Drupal::service('plugin.manager.menu.local_task')->getLocalTasks(Url::fromUserInput($path)->getRouteName());
|
||||
|
||||
// Sort order of tabs based on their weights.
|
||||
uasort($entity_local_tasks['tabs'], [SortArray::class, 'sortByWeightProperty']);
|
||||
|
||||
// Extract the expected order based on sorted weights.
|
||||
$expected_order = array_values(array_map(fn($task) => $task['#link']['title'], $entity_local_tasks['tabs']));
|
||||
|
||||
// Filter out elements not in displayed_links.
|
||||
$expected_order = array_values(array_filter($expected_order, fn($title) => in_array($title, $displayed_links, TRUE)));
|
||||
|
||||
// Ensure that the displayed links match the expected order.
|
||||
$this->assertSame($expected_order, $displayed_links, 'Local tasks are displayed in the correct order based on their weights.');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,129 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\navigation\Functional;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\Tests\system\Functional\Cache\PageCacheTagsTestBase;
|
||||
|
||||
// cspell:ignore navigationuser linksuserwrapper
|
||||
|
||||
/**
|
||||
* Tests for \Drupal\navigation\Plugin\NavigationBlock\NavigationUserBlock.
|
||||
*
|
||||
* @group navigation
|
||||
*/
|
||||
class NavigationUserBlockTest extends PageCacheTagsTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['navigation', 'test_page_test', 'block'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* User with permission to administer navigation blocks and access navigation.
|
||||
*
|
||||
* @var object
|
||||
*/
|
||||
protected $adminUser;
|
||||
|
||||
/**
|
||||
* An authenticated user to test navigation block caching.
|
||||
*
|
||||
* @var object
|
||||
*/
|
||||
protected $normalUser;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Create an admin user, log in and enable test navigation blocks.
|
||||
$this->adminUser = $this->drupalCreateUser([
|
||||
'access administration pages',
|
||||
'access navigation',
|
||||
]);
|
||||
|
||||
// Create additional users to test caching modes.
|
||||
$this->normalUser = $this->drupalCreateUser([
|
||||
'access navigation',
|
||||
]);
|
||||
|
||||
// Note that we don't need to setup a user navigation block b/c it's
|
||||
// installed by default.
|
||||
}
|
||||
|
||||
/**
|
||||
* Test output of user navigation block with regards to caching and contents.
|
||||
*/
|
||||
public function testNavigationUserBlock(): void {
|
||||
// Verify some basic cacheability metadata. Ensures that we're not doing
|
||||
// anything so egregious as to upset expected caching behavior. In this
|
||||
// case, as an anonymous user, we should have zero effect on the page.
|
||||
$test_page_url = Url::fromRoute('test_page_test.test_page');
|
||||
$this->verifyPageCache($test_page_url, 'MISS');
|
||||
$this->verifyPageCache($test_page_url, 'HIT');
|
||||
|
||||
// Login as a limited access user, and verify that the dynamic page cache
|
||||
// is working as expected.
|
||||
$this->drupalLogin($this->normalUser);
|
||||
$this->verifyDynamicPageCache($test_page_url, 'MISS');
|
||||
$this->verifyDynamicPageCache($test_page_url, 'HIT');
|
||||
// We should see the users name in the navigation menu.
|
||||
$rendered_user_name = $this->cssSelect('[aria-controls="navigation-link-navigationuser-linksuserwrapper"] > .toolbar-button__label')[0]->getText();
|
||||
$this->assertEquals($this->normalUser->getDisplayName(), $rendered_user_name);
|
||||
|
||||
// We should see all three user links in the page.
|
||||
$link_labels = ['View profile', 'Edit profile', 'Log out'];
|
||||
$block = $this->assertSession()->elementExists('css', sprintf('.toolbar-block:contains("%s")', $rendered_user_name));
|
||||
foreach ($link_labels as $link_label) {
|
||||
$links = $block->findAll('named', ['link', $link_label]);
|
||||
$this->assertCount(1, $links, sprintf('Found %s links with label %s.', count($links), $link_label));
|
||||
}
|
||||
// The Edit profile link should link to the users edit profile page.
|
||||
$links = $this->getSession()->getPage()->findAll('named', ['link', 'Edit profile']);
|
||||
$this->assertStringContainsString('/user/edit', $links[0]->getAttribute('href'));
|
||||
|
||||
// Login as a different user, UI should update.
|
||||
$this->drupalLogin($this->adminUser);
|
||||
$this->verifyDynamicPageCache($test_page_url, 'MISS');
|
||||
$this->verifyDynamicPageCache($test_page_url, 'HIT');
|
||||
$rendered_user_name = $this->cssSelect('[aria-controls="navigation-link-navigationuser-linksuserwrapper"] > .toolbar-button__label')[0]->getText();
|
||||
$this->assertEquals($this->adminUser->getDisplayName(), $rendered_user_name);
|
||||
// The Edit profile link should link to the users edit profile page.
|
||||
$links = $this->getSession()->getPage()->findAll('named', ['link', 'Edit profile']);
|
||||
$this->assertStringContainsString('/user/edit', $links[0]->getAttribute('href'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test output of user navigation block when there are no menu items.
|
||||
*/
|
||||
public function testNavigationUserBlockFallback(): void {
|
||||
$menu_link_manager = \Drupal::service('plugin.manager.menu.link');
|
||||
$items = [
|
||||
'navigation.user_links.user.page',
|
||||
'navigation.user_links.user.edit',
|
||||
'navigation.user_links.user.logout',
|
||||
];
|
||||
|
||||
foreach ($items as $item) {
|
||||
$front_page_link = $menu_link_manager->getDefinition($item);
|
||||
$front_page_link['enabled'] = FALSE;
|
||||
$menu_link_manager->updateDefinition($item, $front_page_link);
|
||||
}
|
||||
|
||||
$this->drupalLogin($this->normalUser);
|
||||
// We should see the users name in the navigation menu in a link.
|
||||
$rendered_user_name = $this->cssSelect('a.toolbar-button--icon--navigation-user-links-user-wrapper')[0]->getText();
|
||||
$this->assertEquals($this->normalUser->getDisplayName(), $rendered_user_name);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\navigation\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests for \Drupal\navigation\WorkspacesLazyBuilder.
|
||||
*
|
||||
* @group navigation
|
||||
*/
|
||||
class NavigationWorkspacesUiTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['navigation', 'workspaces_ui'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$admin_user = $this->drupalCreateUser([
|
||||
'access navigation',
|
||||
'administer workspaces',
|
||||
]);
|
||||
$this->drupalLogin($admin_user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the Workspaces button in the navigation bar.
|
||||
*/
|
||||
public function testWorkspacesNavigationButton(): void {
|
||||
$this->drupalGet('<front>');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->elementAttributeContains('css', 'a.toolbar-button--icon--workspaces svg', 'width', '20');
|
||||
$this->assertSession()->elementAttributeContains('css', 'a.toolbar-button--icon--workspaces svg', 'class', 'toolbar-button__icon');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,245 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\navigation\FunctionalJavascript;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
|
||||
use Drupal\Tests\block\Traits\BlockCreationTrait;
|
||||
use Drupal\Tests\contextual\FunctionalJavascript\ContextualLinkClickTrait;
|
||||
use Drupal\Tests\layout_builder\FunctionalJavascript\LayoutBuilderSortTrait;
|
||||
use Drupal\Tests\system\Traits\OffCanvasTestTrait;
|
||||
use Drupal\user\UserInterface;
|
||||
|
||||
/**
|
||||
* Tests that the navigation block UI exists and stores data correctly.
|
||||
*
|
||||
* @group navigation
|
||||
*/
|
||||
class NavigationBlockUiTest extends WebDriverTestBase {
|
||||
|
||||
use BlockCreationTrait;
|
||||
use ContextualLinkClickTrait;
|
||||
use LayoutBuilderSortTrait;
|
||||
use OffCanvasTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'navigation',
|
||||
'block_content',
|
||||
'layout_builder',
|
||||
'layout_test',
|
||||
'layout_builder_form_block_test',
|
||||
'node',
|
||||
'field_ui',
|
||||
'shortcut',
|
||||
'off_canvas_test',
|
||||
'navigation_test',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'starterkit_theme';
|
||||
|
||||
/**
|
||||
* An administrative user to configure the test environment.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected UserInterface $adminUser;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->drupalPlaceBlock('page_title_block', ['id' => 'title']);
|
||||
// Create an administrative user.
|
||||
$this->adminUser = $this->drupalCreateUser([
|
||||
'configure navigation layout',
|
||||
'access administration pages',
|
||||
'access navigation',
|
||||
'access shortcuts',
|
||||
'access contextual links',
|
||||
'administer shortcuts',
|
||||
'administer site configuration',
|
||||
'access administration pages',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests navigation block admin page exists and functions correctly.
|
||||
*/
|
||||
public function testNavigationBlockAdminUiPageNestedForm(): void {
|
||||
$layout_url = '/admin/config/user-interface/navigation-block';
|
||||
$this->drupalLogin($this->adminUser);
|
||||
|
||||
// Edit the layout and add a block that contains a form.
|
||||
$this->drupalGet($layout_url);
|
||||
$this->getSession()->getPage()->pressButton('Enable edit mode');
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
$this->openAddBlockForm('Layout Builder form block test form api form block');
|
||||
$this->getSession()->getPage()->checkField('settings[label_display]');
|
||||
|
||||
// Save the new block, and ensure it is displayed on the page.
|
||||
$this->getSession()->getPage()->pressButton('Add block');
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
$this->assertSession()->assertNoElementAfterWait('css', '#drupal-off-canvas');
|
||||
$this->assertSession()->addressEquals($layout_url);
|
||||
$this->assertSession()->pageTextContains('Layout Builder form block test form api form block');
|
||||
$this->getSession()->getPage()->pressButton('Save');
|
||||
$unexpected_save_message = 'You have unsaved changes';
|
||||
$expected_save_message = 'Saved navigation blocks';
|
||||
$this->assertSession()->statusMessageNotContains($unexpected_save_message);
|
||||
$this->assertSession()->statusMessageContains($expected_save_message);
|
||||
|
||||
// Try to save the layout again and confirm it can save because there are no
|
||||
// nested form tags.
|
||||
$this->drupalGet($layout_url);
|
||||
$this->getSession()->getPage()->pressButton('Enable edit mode');
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
$this->getSession()->getPage()->checkField('toggle_content_preview');
|
||||
$this->getSession()->getPage()->pressButton('Save');
|
||||
$this->assertSession()->statusMessageNotContains($unexpected_save_message);
|
||||
$this->assertSession()->statusMessageContains($expected_save_message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests navigation block admin page exists and functions correctly.
|
||||
*/
|
||||
public function testNavigationBlockAdminUiPage(): void {
|
||||
$layout_url = '/admin/config/user-interface/navigation-block';
|
||||
$this->drupalGet($layout_url);
|
||||
$this->assertSession()->pageTextContains('Access denied');
|
||||
// Add at least one shortcut.
|
||||
$shortcut_set = \Drupal::entityTypeManager()
|
||||
->getStorage('shortcut_set')
|
||||
->getDisplayedToUser($this->adminUser);
|
||||
$shortcut = \Drupal::entityTypeManager()->getStorage('shortcut')->create([
|
||||
'title' => 'Run cron',
|
||||
'shortcut_set' => $shortcut_set->id(),
|
||||
'link' => [
|
||||
'uri' => 'internal:/admin/config/system/cron',
|
||||
],
|
||||
]);
|
||||
$shortcut->save();
|
||||
$this->drupalLogin($this->adminUser);
|
||||
$this->drupalGet($layout_url);
|
||||
$page = $this->getSession()->getPage();
|
||||
$this->getSession()->getPage()->pressButton('Enable edit mode');
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
|
||||
// Add section should not be present
|
||||
$this->assertSession()->linkNotExists('Add section');
|
||||
// Configure section should not be present.
|
||||
$this->assertSession()->linkNotExists('Configure Section 1');
|
||||
// Remove section should not be present.
|
||||
$this->assertSession()->linkNotExists('Remove Section 1');
|
||||
|
||||
// Remove the shortcut block.
|
||||
$this->assertSession()->pageTextContains('Shortcuts');
|
||||
$this->clickContextualLink('.layout-builder .block-navigation-shortcuts', 'Remove block');
|
||||
$this->assertOffCanvasFormAfterWait('layout_builder_remove_block');
|
||||
$this->assertSession()->pageTextContains('Are you sure you want to remove the Shortcuts block?');
|
||||
$this->assertSession()->pageTextContains('This action cannot be undone.');
|
||||
$page->pressButton('Remove');
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
$this->assertSession()->assertNoElementAfterWait('css', '#drupal-off-canvas');
|
||||
|
||||
$this->assertSession()->elementNotExists('css', '.layout-builder .block-navigation-shortcuts');
|
||||
|
||||
// Add a new block.
|
||||
$this->getSession()->getPage()->uncheckField('toggle_content_preview');
|
||||
$this->openAddBlockForm('Navigation Shortcuts');
|
||||
|
||||
$page->fillField('settings[label]', 'New Shortcuts');
|
||||
$page->checkField('settings[label_display]');
|
||||
|
||||
// Save the new block, and ensure it is displayed on the page.
|
||||
$page->pressButton('Add block');
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
$this->assertSession()->assertNoElementAfterWait('css', '#drupal-off-canvas');
|
||||
$this->assertSession()->addressEquals($layout_url);
|
||||
$this->assertSession()->pageTextContains('Shortcuts');
|
||||
$this->assertSession()->pageTextContains('New Shortcuts');
|
||||
|
||||
// Until the layout is saved, the new block is not visible on the node page.
|
||||
$front = Url::fromRoute('<front>');
|
||||
$this->drupalGet($front);
|
||||
$this->assertSession()->pageTextNotContains('New Shortcuts');
|
||||
|
||||
// When returning to the layout page, the new block is not visible.
|
||||
$this->drupalGet($layout_url);
|
||||
$this->assertSession()->pageTextNotContains('New Shortcuts');
|
||||
|
||||
// When returning to the layout edit mode, the new block is visible.
|
||||
$this->getSession()->getPage()->pressButton('Enable edit mode');
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
$this->assertSession()->pageTextContains('New Shortcuts');
|
||||
|
||||
// Save the layout, and the new block is visible in the front page.
|
||||
$page->pressButton('Save');
|
||||
$this->drupalGet($front);
|
||||
$this->assertSession()->pageTextContains('New Shortcuts');
|
||||
|
||||
// Reconfigure a block and ensure that the layout content is updated.
|
||||
$this->drupalGet($layout_url);
|
||||
$this->getSession()->getPage()->pressButton('Enable edit mode');
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
$this->clickContextualLink('.layout-builder .block-navigation-shortcuts', 'Configure');
|
||||
$this->assertOffCanvasFormAfterWait('layout_builder_update_block');
|
||||
|
||||
$page->fillField('settings[label]', 'Newer Shortcuts');
|
||||
$page->pressButton('Update');
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
$this->assertSession()->assertNoElementAfterWait('css', '#drupal-off-canvas');
|
||||
|
||||
$this->assertSession()->addressEquals($layout_url);
|
||||
$this->assertSession()->pageTextContains('Newer Shortcuts');
|
||||
$this->assertSession()->elementTextNotContains('css', 'form', 'New Shortcuts');
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the add block form in the off-canvas dialog.
|
||||
*
|
||||
* @param string $block_title
|
||||
* The block title which will be the link text.
|
||||
*
|
||||
* @todo move this from into a trait from
|
||||
* \Drupal\Tests\layout_builder\FunctionalJavascript\LayoutBuilderTest
|
||||
*/
|
||||
private function openAddBlockForm($block_title): void {
|
||||
$this->assertSession()->linkExists('Add block');
|
||||
$this->clickLink('Add block');
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
$this->assertNotEmpty($this->assertSession()->waitForElementVisible('named', ['link', $block_title]));
|
||||
$this->clickLink($block_title);
|
||||
$this->assertOffCanvasFormAfterWait('layout_builder_add_block');
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the specified form and returns it when available and visible.
|
||||
*
|
||||
* @param string $expected_form_id
|
||||
* The expected form ID.
|
||||
*
|
||||
* @todo move this from into a trait from
|
||||
* \Drupal\Tests\layout_builder\FunctionalJavascript\LayoutBuilderTest
|
||||
*/
|
||||
private function assertOffCanvasFormAfterWait(string $expected_form_id): void {
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
$this->waitForOffCanvasArea();
|
||||
$off_canvas = $this->assertSession()->elementExists('css', '#drupal-off-canvas');
|
||||
$this->assertNotNull($off_canvas);
|
||||
$form_id_element = $off_canvas->find('hidden_field_selector', ['hidden_field', 'form_id']);
|
||||
// Ensure the form ID has the correct value and that the form is visible.
|
||||
$this->assertNotEmpty($form_id_element);
|
||||
$this->assertSame($expected_form_id, $form_id_element->getValue());
|
||||
$this->assertTrue($form_id_element->getParent()->isVisible());
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\navigation\FunctionalJavascript;
|
||||
|
||||
use Behat\Mink\Element\Element;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
|
||||
|
||||
// cspell:ignore navigationuser linksuserwrapper
|
||||
|
||||
/**
|
||||
* Tests for \Drupal\navigation\Plugin\NavigationBlock\NavigationUserBlock.
|
||||
*
|
||||
* @group navigation
|
||||
*/
|
||||
class NavigationUserBlockTest extends WebDriverTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'navigation', 'test_page_test', 'block',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* User with permission to administer navigation blocks and access navigation.
|
||||
*
|
||||
* @var object
|
||||
*/
|
||||
protected $adminUser;
|
||||
|
||||
/**
|
||||
* An authenticated user to test navigation block caching.
|
||||
*
|
||||
* @var object
|
||||
*/
|
||||
protected $normalUser;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Create an admin user, log in and enable test navigation blocks.
|
||||
$this->adminUser = $this->drupalCreateUser([
|
||||
'access administration pages',
|
||||
'access navigation',
|
||||
'change own username',
|
||||
]);
|
||||
|
||||
// Create additional users to test caching modes.
|
||||
$this->normalUser = $this->drupalCreateUser([
|
||||
'access navigation',
|
||||
]);
|
||||
|
||||
// Note that we don't need to setup a user navigation block b/c it's
|
||||
// installed by default.
|
||||
}
|
||||
|
||||
/**
|
||||
* Test output of user navigation block with regards to contents.
|
||||
*/
|
||||
public function testNavigationUserBlock(): void {
|
||||
$test_page_url = Url::fromRoute('test_page_test.test_page');
|
||||
// Login as a limited access user, and verify that the username is displayed
|
||||
// correctly.
|
||||
$this->drupalLogin($this->normalUser);
|
||||
$this->drupalGet($test_page_url);
|
||||
|
||||
// Wait for the default 'My Account' text to be replaced.
|
||||
$this->getSession()->getPage()->waitFor(10, function (Element $page) {
|
||||
return $page->find('css', '[aria-controls="navigation-link-navigationuser-linksuserwrapper"] > .toolbar-button__label')->getText() !== 'My Account';
|
||||
});
|
||||
// We should see the users name in the navigation menu.
|
||||
$rendered_user_name = $this->cssSelect('[aria-controls="navigation-link-navigationuser-linksuserwrapper"] > .toolbar-button__label')[0]->getText();
|
||||
$this->assertEquals($this->normalUser->getDisplayName(), $rendered_user_name);
|
||||
|
||||
// Login as an admin access user, and verify that the username is displayed
|
||||
// correctly.
|
||||
$this->drupalLogin($this->adminUser);
|
||||
$this->drupalGet($test_page_url);
|
||||
// Wait for the default 'My Account' text to be replaced.
|
||||
$this->getSession()->getPage()->waitFor(10, function (Element $page) {
|
||||
return $page->find('css', '[aria-controls="navigation-link-navigationuser-linksuserwrapper"] > .toolbar-button__label')->getText() !== 'My Account';
|
||||
});
|
||||
// We should see the users name in the navigation menu.
|
||||
$rendered_user_name = $this->cssSelect('[aria-controls="navigation-link-navigationuser-linksuserwrapper"] > .toolbar-button__label')[0]->getText();
|
||||
$this->assertEquals($this->adminUser->getDisplayName(), $rendered_user_name);
|
||||
|
||||
// Change the users name, assert that the changes reflect in the navigation.
|
||||
$new_username = $this->randomMachineName();
|
||||
$this->drupalGet('user/' . $this->adminUser->id() . '/edit');
|
||||
$this->submitForm(['name' => $new_username], 'Save');
|
||||
// Wait for the default 'My Account' text to be replaced.
|
||||
$this->getSession()->getPage()->waitFor(10, function (Element $page) {
|
||||
return $page->find('css', '[aria-controls="navigation-link-navigationuser-linksuserwrapper"] > .toolbar-button__label')->getText() !== 'My Account';
|
||||
});
|
||||
// We should see the users name in the navigation menu.
|
||||
$rendered_user_name = $this->cssSelect('[aria-controls="navigation-link-navigationuser-linksuserwrapper"] > .toolbar-button__label')[0]->getText();
|
||||
$this->assertEquals($new_username, $rendered_user_name);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\navigation\FunctionalJavascript;
|
||||
|
||||
use Drupal\FunctionalJavascriptTests\PerformanceTestBase;
|
||||
|
||||
/**
|
||||
* Tests performance with the navigation toolbar enabled.
|
||||
*
|
||||
* Stark is used as the default theme so that this test is not Olivero specific.
|
||||
*
|
||||
* @todo move this coverage to StandardPerformanceTest when Navigation is
|
||||
* enabled by default.
|
||||
*
|
||||
* @group Common
|
||||
* @group #slow
|
||||
* @requires extension apcu
|
||||
*/
|
||||
class PerformanceTest extends PerformanceTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $profile = 'standard';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
// Uninstall the toolbar.
|
||||
\Drupal::service('module_installer')->uninstall(['toolbar']);
|
||||
\Drupal::service('module_installer')->install(['navigation']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests performance of the navigation toolbar.
|
||||
*/
|
||||
public function testLogin(): void {
|
||||
$user = $this->drupalCreateUser();
|
||||
$user->addRole('administrator');
|
||||
$user->save();
|
||||
$this->drupalLogin($user);
|
||||
// Request the front page twice to ensure all cache collectors are fully
|
||||
// warmed. The exact contents of cache collectors depends on the order in
|
||||
// which requests complete so this ensures that the second request completes
|
||||
// after asset aggregates are served.
|
||||
$this->drupalGet('');
|
||||
sleep(1);
|
||||
$this->drupalGet('');
|
||||
// Flush the dynamic page cache to simulate visiting a page that is not
|
||||
// already fully cached.
|
||||
\Drupal::cache('dynamic_page_cache')->deleteAll();
|
||||
$performance_data = $this->collectPerformanceData(function () {
|
||||
$this->drupalGet('');
|
||||
}, 'navigation');
|
||||
|
||||
$expected_queries = [
|
||||
'SELECT "session" FROM "sessions" WHERE "sid" = "SESSION_ID" LIMIT 0, 1',
|
||||
'SELECT * FROM "users_field_data" "u" WHERE "u"."uid" = "2" AND "u"."default_langcode" = 1',
|
||||
'SELECT "roles_target_id" FROM "user__roles" WHERE "entity_id" = "2"',
|
||||
'SELECT "name", "value" FROM "key_value" WHERE "name" IN ( "theme:stark" ) AND "collection" = "config.entity.key_store.block"',
|
||||
];
|
||||
$recorded_queries = $performance_data->getQueries();
|
||||
$this->assertSame($expected_queries, $recorded_queries);
|
||||
|
||||
$expected = [
|
||||
'QueryCount' => 4,
|
||||
'CacheGetCount' => 47,
|
||||
'CacheGetCountByBin' => [
|
||||
'config' => 11,
|
||||
'data' => 4,
|
||||
'discovery' => 10,
|
||||
'bootstrap' => 6,
|
||||
'dynamic_page_cache' => 1,
|
||||
'render' => 14,
|
||||
'menu' => 1,
|
||||
],
|
||||
'CacheSetCount' => 2,
|
||||
'CacheSetCountByBin' => [
|
||||
'dynamic_page_cache' => 2,
|
||||
],
|
||||
'CacheDeleteCount' => 0,
|
||||
'CacheTagInvalidationCount' => 0,
|
||||
'CacheTagLookupQueryCount' => 13,
|
||||
'ScriptCount' => 3,
|
||||
'ScriptBytes' => 167569,
|
||||
'StylesheetCount' => 2,
|
||||
'StylesheetBytes' => 46000,
|
||||
];
|
||||
$this->assertMetrics($expected, $performance_data);
|
||||
|
||||
// Check that the navigation toolbar is cached without any high-cardinality
|
||||
// cache contexts (user, route, query parameters etc.).
|
||||
$this->assertIsObject(\Drupal::cache('render')->get('navigation:navigation:[languages:language_interface]=en:[theme]=stark:[user.permissions]=is-admin'));
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\navigation\Kernel\ConfigAction;
|
||||
|
||||
use Drupal\Core\Config\Action\ConfigActionException;
|
||||
use Drupal\Core\Plugin\Context\Context;
|
||||
use Drupal\Core\Plugin\Context\ContextDefinition;
|
||||
use Drupal\FunctionalTests\Core\Recipe\RecipeTestTrait;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
|
||||
/**
|
||||
* @covers \Drupal\navigation\Plugin\ConfigAction\AddNavigationBlock
|
||||
* @group navigation
|
||||
* @group Recipe
|
||||
*/
|
||||
class AddNavigationBlockConfigActionTest extends KernelTestBase {
|
||||
|
||||
use RecipeTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'navigation',
|
||||
'layout_builder',
|
||||
'layout_discovery',
|
||||
'system',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->installConfig('navigation');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests add item logic.
|
||||
*
|
||||
* @testWith [0, 0]
|
||||
* [1, 1]
|
||||
* [3, 3]
|
||||
* [7, 3]
|
||||
*/
|
||||
public function testAddBlockToNavigation($delta, $computed_delta): void {
|
||||
// Load the navigation section storage.
|
||||
$navigation_storage = \Drupal::service('plugin.manager.layout_builder.section_storage')->load('navigation', [
|
||||
'navigation' => new Context(new ContextDefinition('string'), 'navigation'),
|
||||
]);
|
||||
$section = $navigation_storage->getSection(0);
|
||||
$components = $section->getComponentsByRegion('content');
|
||||
$this->assertCount(3, $components);
|
||||
$data = [
|
||||
'delta' => $delta,
|
||||
'configuration' => [
|
||||
'id' => 'navigation_menu:content',
|
||||
'label' => 'Content From Recipe',
|
||||
'label_display' => 1,
|
||||
'provider' => 'navigation',
|
||||
'level' => 1,
|
||||
'depth' => 2,
|
||||
],
|
||||
];
|
||||
|
||||
// Use the action to add a new block to Navigation.
|
||||
/** @var \Drupal\Core\Config\Action\ConfigActionManager $manager */
|
||||
$manager = $this->container->get('plugin.manager.config_action');
|
||||
$manager->applyAction('addNavigationBlock', 'navigation.block_layout', $data);
|
||||
|
||||
// Load the config after the execution.
|
||||
$navigation_storage = \Drupal::service('plugin.manager.layout_builder.section_storage')->load('navigation', [
|
||||
'navigation' => new Context(new ContextDefinition('string'), 'navigation'),
|
||||
]);
|
||||
$section = $navigation_storage->getSection(0);
|
||||
$components = $section->getComponentsByRegion('content');
|
||||
$this->assertCount(4, $components);
|
||||
$component = array_values($components)[$computed_delta];
|
||||
$this->assertSame('content', $component->getRegion());
|
||||
$this->assertEquals($data['configuration'], $component->get('configuration'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks invalid config exception.
|
||||
*/
|
||||
public function testActionOnlySupportsNavigationConfig(): void {
|
||||
$this->expectException(ConfigActionException::class);
|
||||
$this->expectExceptionMessage('addNavigationBlock can only be executed for the navigation.block_layout config.');
|
||||
// Try to apply the Config Action against an unexpected config entity.
|
||||
/** @var \Drupal\Core\Config\Action\ConfigActionManager $manager */
|
||||
$manager = $this->container->get('plugin.manager.config_action');
|
||||
$manager->applyAction('addNavigationBlock', 'navigation.settings', []);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,136 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\navigation\Kernel;
|
||||
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\Tests\media\Traits\MediaTypeCreationTrait;
|
||||
use Drupal\Tests\node\Traits\ContentTypeCreationTrait;
|
||||
use Drupal\Tests\user\Traits\UserCreationTrait;
|
||||
|
||||
/**
|
||||
* Tests \Drupal\navigation\NavigationContentLinks.
|
||||
*
|
||||
* @group navigation
|
||||
* @see \Drupal\navigation\NavigationContentLinks
|
||||
*/
|
||||
class NavigationContentLinksTest extends KernelTestBase {
|
||||
|
||||
use ContentTypeCreationTrait;
|
||||
use MediaTypeCreationTrait;
|
||||
use UserCreationTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'navigation',
|
||||
'node',
|
||||
'file',
|
||||
'media',
|
||||
'layout_builder',
|
||||
'system',
|
||||
'views',
|
||||
'user',
|
||||
'field',
|
||||
'media_test_source',
|
||||
'image',
|
||||
'text',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->installEntitySchema('node');
|
||||
$this->installConfig(['node', 'file']);
|
||||
|
||||
$this->createContentType(['type' => 'article']);
|
||||
$this->createContentType(['type' => 'blog']);
|
||||
$this->createContentType(['type' => 'landing_page']);
|
||||
|
||||
$this->installEntitySchema('media');
|
||||
$this->createMediaType('test', ['id' => 'document', 'label' => 'Document']);
|
||||
$this->createMediaType('test', ['id' => 'image', 'label' => 'Image']);
|
||||
$this->createMediaType('test', ['id' => 'special', 'label' => 'Special']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if the expected navigation content links are added/removed correctly.
|
||||
*/
|
||||
public function testNavigationContentLinks(): void {
|
||||
$module_installer = \Drupal::service('module_installer');
|
||||
$menu_link_manager = \Drupal::service('plugin.manager.menu.link');
|
||||
$links = $menu_link_manager->getDefinitions();
|
||||
|
||||
// Assert that the "Create" link is added to the menu.
|
||||
$this->assertArrayHasKey('navigation.create', $links);
|
||||
$this->assertEquals('node.add_page', $links['navigation.create']['route_name']);
|
||||
$this->assertEquals('Create', $links['navigation.create']['title']);
|
||||
|
||||
// Assert that the "Content" link is added to the menu.
|
||||
$this->assertArrayHasKey('navigation.content', $links);
|
||||
$this->assertEquals('system.admin_content', $links['navigation.content']['route_name']);
|
||||
$this->assertEquals('Content', $links['navigation.content']['title']);
|
||||
|
||||
// Assert that the "Article" submenu link is added to the menu.
|
||||
$this->assertArrayHasKey('navigation.content.node_type.article', $links);
|
||||
$this->assertEquals('node.add', $links['navigation.content.node_type.article']['route_name']);
|
||||
$this->assertEquals('article', $links['navigation.content.node_type.article']['title']);
|
||||
|
||||
// Assert that the "Blog" submenu link is added to the menu.
|
||||
$this->assertArrayHasKey('navigation.content.node_type.blog', $links);
|
||||
$this->assertEquals('node.add', $links['navigation.content.node_type.blog']['route_name']);
|
||||
$this->assertEquals('blog', $links['navigation.content.node_type.blog']['title']);
|
||||
|
||||
// Assert that the "Landing Page" submenu link is added to the menu.
|
||||
$this->assertArrayHasKey('navigation.content.node_type.landing_page', $links);
|
||||
$this->assertEquals('node.add', $links['navigation.content.node_type.landing_page']['route_name']);
|
||||
$this->assertEquals('landing_page', $links['navigation.content.node_type.landing_page']['title']);
|
||||
|
||||
// Assert that the "Create User" submenu link is added to the menu.
|
||||
$this->assertArrayHasKey('navigation.create.user', $links);
|
||||
$this->assertEquals('user.admin_create', $links['navigation.create.user']['route_name']);
|
||||
$this->assertEquals('User', $links['navigation.create.user']['title']);
|
||||
|
||||
// Assert that the "Document" media type link is added to the menu.
|
||||
$this->assertArrayHasKey('navigation.content.media_type.document', $links);
|
||||
$this->assertEquals('entity.media.add_form', $links['navigation.content.media_type.document']['route_name']);
|
||||
$this->assertEquals('Document', $links['navigation.content.media_type.document']['title']);
|
||||
|
||||
// Assert that the "Image" media type link is added to the menu.
|
||||
$this->assertArrayHasKey('navigation.content.media_type.image', $links);
|
||||
$this->assertEquals('entity.media.add_form', $links['navigation.content.media_type.image']['route_name']);
|
||||
$this->assertEquals('Image', $links['navigation.content.media_type.image']['title']);
|
||||
|
||||
// Assert that the "Special" media type link is not added to the menu.
|
||||
$this->assertArrayNotHasKey('navigation.content.media_type.special', $links);
|
||||
|
||||
// Assert that the "Media" link is added.
|
||||
$this->assertArrayHasKey('navigation.media', $links);
|
||||
$this->assertEquals('entity.media.collection', $links['navigation.media']['route_name']);
|
||||
$this->assertEquals('Media', $links['navigation.media']['title']);
|
||||
|
||||
// Assert that the "Files" link is added.
|
||||
$this->assertArrayHasKey('navigation.files', $links);
|
||||
$this->assertEquals('view.files.page_1', $links['navigation.files']['route_name']);
|
||||
$this->assertEquals('Files', $links['navigation.files']['title']);
|
||||
|
||||
// Assert that "Blocks" link is not added.
|
||||
$this->assertArrayNotHasKey('navigation.blocks', $links);
|
||||
|
||||
// Install the block_content module and rebuild the menu links.
|
||||
$module_installer->install(['block_content']);
|
||||
// Rebuild the links after module installation.
|
||||
$menu_link_manager->rebuild();
|
||||
$links = $menu_link_manager->getDefinitions();
|
||||
|
||||
// Assert that "Blocks" link is added.
|
||||
$this->assertArrayHasKey('navigation.blocks', $links);
|
||||
$this->assertEquals('entity.block_content.collection', $links['navigation.blocks']['route_name']);
|
||||
$this->assertEquals('Blocks', $links['navigation.blocks']['title']);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,434 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\navigation\Kernel;
|
||||
|
||||
use Drupal\block\Entity\Block;
|
||||
use Drupal\Core\Render\Element;
|
||||
use Drupal\Core\Render\MetadataBubblingUrlGenerator;
|
||||
use Drupal\Core\Routing\RouteObjectInterface;
|
||||
use Drupal\Core\Routing\UrlGenerator;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\navigation\Plugin\Block\NavigationMenuBlock;
|
||||
use Drupal\system\Controller\SystemController;
|
||||
use Drupal\system\Entity\Menu;
|
||||
use Drupal\system\Tests\Routing\MockRouteProvider;
|
||||
use Drupal\Tests\Core\Menu\MenuLinkMock;
|
||||
use Drupal\user\Entity\User;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Session\Session;
|
||||
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
|
||||
use Symfony\Component\Routing\Route;
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
|
||||
/**
|
||||
* Tests \Drupal\navigation\Plugin\Block\NavigationMenuBlock.
|
||||
*
|
||||
* @group navigation
|
||||
* @see \Drupal\navigation\Plugin\Derivative\SystemMenuNavigationBlock
|
||||
* @see \Drupal\navigation\Plugin\Block\NavigationMenuBlock
|
||||
* @todo Expand test coverage to all SystemMenuNavigationBlock functionality,
|
||||
* including block_menu_delete().
|
||||
*/
|
||||
class NavigationMenuBlockTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'system',
|
||||
'navigation',
|
||||
'menu_test',
|
||||
'menu_link_content',
|
||||
'field',
|
||||
'block',
|
||||
'user',
|
||||
'link',
|
||||
'layout_builder',
|
||||
];
|
||||
|
||||
/**
|
||||
* The navigation block under test.
|
||||
*
|
||||
* @var \Drupal\navigation\Plugin\Block\NavigationMenuBlock
|
||||
*/
|
||||
protected $navigationBlock;
|
||||
|
||||
/**
|
||||
* The menu for testing.
|
||||
*
|
||||
* @var \Drupal\system\MenuInterface
|
||||
*/
|
||||
protected $menu;
|
||||
|
||||
/**
|
||||
* The menu link tree service.
|
||||
*
|
||||
* @var \Drupal\Core\Menu\MenuLinkTree
|
||||
*/
|
||||
protected $linkTree;
|
||||
|
||||
/**
|
||||
* The menu link plugin manager service.
|
||||
*
|
||||
* @var \Drupal\Core\Menu\MenuLinkManagerInterface
|
||||
*/
|
||||
protected $menuLinkManager;
|
||||
|
||||
/**
|
||||
* The block manager service.
|
||||
*
|
||||
* @var \Drupal\Core\Block\BlockManager
|
||||
*/
|
||||
protected $blockManager;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->installEntitySchema('user');
|
||||
$this->installEntitySchema('menu_link_content');
|
||||
|
||||
$account = User::create([
|
||||
'name' => $this->randomMachineName(),
|
||||
'status' => 1,
|
||||
]);
|
||||
$account->save();
|
||||
$this->container->get('current_user')->setAccount($account);
|
||||
|
||||
$this->menuLinkManager = $this->container->get('plugin.manager.menu.link');
|
||||
$this->linkTree = $this->container->get('menu.link_tree');
|
||||
$this->blockManager = $this->container->get('plugin.manager.block');
|
||||
|
||||
$routes = new RouteCollection();
|
||||
$requirements = ['_access' => 'TRUE'];
|
||||
$options = ['_access_checks' => ['access_check.default']];
|
||||
$special_options = $options + ['_no_path' => TRUE];
|
||||
$routes->add('example2', new Route('/example2', [], $requirements, $options));
|
||||
$routes->add('example4', new Route('/example4', ['_controller' => SystemController::class . '::systemAdminMenuBlockPage'], $requirements, $options));
|
||||
$routes->add('example9', new Route('/example9', [], $requirements, $options));
|
||||
$routes->add('example11', new Route('/example11', ['_controller' => SystemController::class . '::systemAdminMenuBlockPage'], $requirements, $options));
|
||||
$routes->add('example13', new Route('/example13', [], $requirements, $options));
|
||||
$routes->add('example14', new Route('/example14', [], $requirements, $options));
|
||||
$routes->add('example15', new Route('/example15', [], $requirements, $options));
|
||||
$routes->add('example16', new Route('/example16', [], $requirements, $options));
|
||||
$routes->add('example17', new Route('/example17', [], $requirements, $options));
|
||||
$routes->add('example18', new Route('/example18', [], $requirements, $options));
|
||||
$routes->add('example19', new Route('/example19', [], ['_access' => 'FALSE'], $options));
|
||||
|
||||
// Mock special routes defined in system.routing.yml.
|
||||
$routes->add('<nolink>', new Route('', [], $requirements, $special_options));
|
||||
$routes->add('<button>', new Route('', [], $requirements, $special_options));
|
||||
|
||||
// Define our RouteProvider mock.
|
||||
$mock_route_provider = new MockRouteProvider($routes);
|
||||
$this->container->set('router.route_provider', $mock_route_provider);
|
||||
|
||||
// Define our UrlGenerator service that use the new RouteProvider.
|
||||
$url_generator_non_bubbling = new UrlGenerator(
|
||||
$mock_route_provider,
|
||||
$this->container->get('path_processor_manager'),
|
||||
$this->container->get('route_processor_manager'),
|
||||
$this->container->get('request_stack'),
|
||||
$this->container->getParameter('filter_protocols')
|
||||
);
|
||||
$url_generator = new MetadataBubblingUrlGenerator($url_generator_non_bubbling, $this->container->get('renderer'));
|
||||
$this->container->set('url_generator', $url_generator);
|
||||
|
||||
// Add a new custom menu.
|
||||
$menu_name = 'mock';
|
||||
$label = $this->randomMachineName(16);
|
||||
|
||||
$this->menu = Menu::create([
|
||||
'id' => $menu_name,
|
||||
'label' => $label,
|
||||
'description' => 'Description text',
|
||||
]);
|
||||
$this->menu->save();
|
||||
|
||||
// This creates a tree with the following structure:
|
||||
// - 1 (nolink)
|
||||
// - 2
|
||||
// - 3 (nolink)
|
||||
// - 4 (list of child links)
|
||||
// - 9
|
||||
// - 5 (button)
|
||||
// - 7 (button)
|
||||
// - 10 (nolink)
|
||||
// - 6
|
||||
// - 8 (nolink)
|
||||
// - 11 (list of child links)
|
||||
// - 12 (button)
|
||||
// - 13
|
||||
// - 14 (not a list of child links)
|
||||
// - 15
|
||||
// - 16
|
||||
// - 17
|
||||
// - 18 (disabled)
|
||||
// - 19 (access denied)
|
||||
// - 20 (links to same routed URL as 17)
|
||||
// With link 6 being the only external link.
|
||||
// phpcs:disable
|
||||
$links = [
|
||||
1 => MenuLinkMock::create(['id' => 'test.example1', 'route_name' => '<nolink>', 'title' => 'title 1', 'parent' => '', 'weight' => 0]),
|
||||
2 => MenuLinkMock::create(['id' => 'test.example2', 'route_name' => 'example2', 'title' => 'title 2', 'parent' => '', 'route_parameters' => ['foo' => 'bar'], 'weight' => 1]),
|
||||
3 => MenuLinkMock::create(['id' => 'test.example3', 'route_name' => '<nolink>', 'title' => 'title 3', 'parent' => 'test.example2', 'weight' => 2]),
|
||||
4 => MenuLinkMock::create(['id' => 'test.example4', 'route_name' => 'example4', 'title' => 'title 4', 'parent' => 'test.example3', 'weight' => 3]),
|
||||
5 => MenuLinkMock::create(['id' => 'test.example5', 'route_name' => '<button>', 'title' => 'title 5', 'parent' => '', 'expanded' => TRUE, 'weight' => 4]),
|
||||
6 => MenuLinkMock::create(['id' => 'test.example6', 'route_name' => '', 'url' => 'https://www.drupal.org/', 'title' => 'title 6', 'parent' => '', 'weight' => 5, 'options' => ['attributes' => ['target' => '_blank', 'class' => ['external-link']]]]),
|
||||
7 => MenuLinkMock::create(['id' => 'test.example7', 'route_name' => '<button>', 'title' => 'title 7', 'parent' => 'test.example5', 'weight' => 6]),
|
||||
8 => MenuLinkMock::create(['id' => 'test.example8', 'route_name' => '<nolink>', 'title' => 'title 8', 'parent' => '', 'weight' => 7]),
|
||||
9 => MenuLinkMock::create(['id' => 'test.example9', 'route_name' => 'example9', 'title' => 'title 9', 'parent' => 'test.example4', 'weight' => 7]),
|
||||
10 => MenuLinkMock::create(['id' => 'test.example10', 'route_name' => '<nolink>', 'title' => 'title 10', 'parent' => 'test.example7', 'weight' => 7]),
|
||||
11 => MenuLinkMock::create(['id' => 'test.example11', 'route_name' => 'example11', 'title' => 'title 11', 'parent' => 'test.example8', 'weight' => 7]),
|
||||
12 => MenuLinkMock::create(['id' => 'test.example12', 'route_name' => '<button>', 'title' => 'title 12', 'parent' => 'test.example11', 'weight' => 7]),
|
||||
13 => MenuLinkMock::create(['id' => 'test.example13', 'route_name' => 'example13', 'title' => 'title 13', 'parent' => '', 'weight' => 8]),
|
||||
14 => MenuLinkMock::create(['id' => 'test.example14', 'route_name' => 'example14', 'title' => 'title 14', 'parent' => 'test.example13', 'weight' => 8]),
|
||||
15 => MenuLinkMock::create(['id' => 'test.example15', 'route_name' => 'example15', 'title' => 'title 15', 'parent' => 'test.example14', 'weight' => 8]),
|
||||
16 => MenuLinkMock::create(['id' => 'test.example16', 'route_name' => 'example16', 'title' => 'title 16', 'parent' => '', 'weight' => 9]),
|
||||
17 => MenuLinkMock::create(['id' => 'test.example17', 'route_name' => 'example17', 'title' => 'title 17', 'parent' => 'test.example16', 'weight' => 9]),
|
||||
18 => MenuLinkMock::create(['id' => 'test.example18', 'route_name' => 'example18', 'title' => 'title 18', 'parent' => 'test.example17', 'weight' => 9, 'enabled' => FALSE]),
|
||||
19 => MenuLinkMock::create(['id' => 'test.example19', 'route_name' => 'example19', 'title' => 'title 19', 'parent' => 'test.example17', 'weight' => 9]),
|
||||
20 => MenuLinkMock::create(['id' => 'test.example20', 'route_name' => 'example17', 'title' => 'title 20', 'parent' => 'test.example17', 'weight' => 9]),
|
||||
];
|
||||
// phpcs:enable
|
||||
foreach ($links as $instance) {
|
||||
$this->menuLinkManager->addDefinition($instance->getPluginId(), $instance->getPluginDefinition());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests calculation of a system navigation menu block's config dependencies.
|
||||
*/
|
||||
public function testSystemMenuBlockConfigDependencies(): void {
|
||||
$block = Block::create([
|
||||
'plugin' => 'navigation_menu:' . $this->menu->id(),
|
||||
'region' => 'content',
|
||||
'id' => 'machine_name',
|
||||
'theme' => 'stark',
|
||||
]);
|
||||
|
||||
$dependencies = $block->calculateDependencies()->getDependencies();
|
||||
$expected = [
|
||||
'config' => [
|
||||
'system.menu.' . $this->menu->id(),
|
||||
],
|
||||
'module' => [
|
||||
'navigation',
|
||||
'system',
|
||||
],
|
||||
'theme' => [
|
||||
'stark',
|
||||
],
|
||||
];
|
||||
$this->assertSame($expected, $dependencies);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the config start level and depth.
|
||||
*/
|
||||
public function testConfigLevelDepth(): void {
|
||||
// Helper function to generate a configured navigation block instance.
|
||||
$place_block = function ($level, $depth) {
|
||||
return $this->blockManager->createInstance('navigation_menu:' . $this->menu->id(), [
|
||||
'region' => 'content',
|
||||
'id' => 'machine_name',
|
||||
'level' => $level,
|
||||
'depth' => $depth,
|
||||
]);
|
||||
};
|
||||
|
||||
// All the different navigation block instances we're going to test.
|
||||
$blocks = [
|
||||
'level_1_only' => $place_block(1, 0),
|
||||
'level_2_only' => $place_block(2, 0),
|
||||
'level_3_only' => $place_block(3, 0),
|
||||
'level_1_and_beyond' => $place_block(1, NavigationMenuBlock::NAVIGATION_MAX_DEPTH - 1),
|
||||
'level_2_and_beyond' => $place_block(2, NavigationMenuBlock::NAVIGATION_MAX_DEPTH - 1),
|
||||
'level_3_and_beyond' => $place_block(3, NavigationMenuBlock::NAVIGATION_MAX_DEPTH - 1),
|
||||
];
|
||||
|
||||
// Expectations are independent of the active trail.
|
||||
$expectations = [];
|
||||
$expectations['level_1_only'] = [
|
||||
'test.example1' => [],
|
||||
'test.example2' => [],
|
||||
'test.example5' => [],
|
||||
'test.example6' => [],
|
||||
'test.example8' => [],
|
||||
'test.example13' => [],
|
||||
'test.example16' => [],
|
||||
];
|
||||
$expectations['level_2_only'] = [
|
||||
'test.example3' => [],
|
||||
'test.example7' => [],
|
||||
'test.example11' => [],
|
||||
'test.example14' => [],
|
||||
'test.example17' => [],
|
||||
];
|
||||
$expectations['level_3_only'] = [
|
||||
'test.example4' => [],
|
||||
'test.example10' => [],
|
||||
'test.example12' => [],
|
||||
'test.example15' => [],
|
||||
'test.example20' => [],
|
||||
];
|
||||
$expectations['level_1_and_beyond'] = [
|
||||
'test.example1' => [],
|
||||
'test.example2' => [
|
||||
'test.example3' => [
|
||||
'test.example4' => [],
|
||||
],
|
||||
],
|
||||
'test.example5' => [
|
||||
'test.example7' => [
|
||||
'test.example10' => [],
|
||||
],
|
||||
],
|
||||
'test.example6' => [],
|
||||
'test.example8' => [
|
||||
'test.example11' => [
|
||||
'test.example12' => [],
|
||||
],
|
||||
],
|
||||
'test.example13' => [
|
||||
'test.example14' => [
|
||||
'test.example14.navigation_overview' => [],
|
||||
'test.example15' => [],
|
||||
],
|
||||
],
|
||||
'test.example16' => [
|
||||
// 17 only has inaccessible and disabled child links, and a child item
|
||||
// that links to the same url as 17, so there should be no overview link
|
||||
// child added.
|
||||
'test.example17' => [
|
||||
'test.example20' => [],
|
||||
],
|
||||
],
|
||||
];
|
||||
$expectations['level_2_and_beyond'] = [
|
||||
'test.example3' => [
|
||||
'test.example4' => [
|
||||
'test.example9' => [],
|
||||
],
|
||||
],
|
||||
'test.example7' => [
|
||||
'test.example10' => [],
|
||||
],
|
||||
'test.example11' => [
|
||||
'test.example12' => [],
|
||||
],
|
||||
'test.example14' => [
|
||||
'test.example15' => [],
|
||||
],
|
||||
'test.example17' => [
|
||||
'test.example20' => [],
|
||||
],
|
||||
];
|
||||
$expectations['level_3_and_beyond'] = [
|
||||
'test.example4' => [
|
||||
'test.example9' => [],
|
||||
],
|
||||
'test.example10' => [],
|
||||
'test.example12' => [],
|
||||
'test.example15' => [],
|
||||
'test.example20' => [],
|
||||
];
|
||||
// Scenario 1: test all navigation block instances when there's no active
|
||||
// trail.
|
||||
foreach ($blocks as $id => $block) {
|
||||
$block_build = $block->build();
|
||||
$items = $block_build['#items'] ?? [];
|
||||
$this->assertSame($expectations[$id], $this->convertBuiltMenuToIdTree($items), "Menu block $id with no active trail renders the expected tree.");
|
||||
}
|
||||
|
||||
// Scenario 2: test all navigation block instances when there's an active
|
||||
// trail.
|
||||
$route = $this->container->get('router.route_provider')->getRouteByName('example3');
|
||||
$request = new Request();
|
||||
$request->attributes->set(RouteObjectInterface::ROUTE_NAME, 'example3');
|
||||
$request->attributes->set(RouteObjectInterface::ROUTE_OBJECT, $route);
|
||||
$request->setSession(new Session(new MockArraySessionStorage()));
|
||||
$this->container->get('request_stack')->push($request);
|
||||
// \Drupal\Core\Menu\MenuActiveTrail uses the cache collector pattern, which
|
||||
// includes static caching. Since this second scenario simulates a second
|
||||
// request, we must also simulate it for the MenuActiveTrail service, by
|
||||
// clearing the cache collector's static cache.
|
||||
\Drupal::service('menu.active_trail')->clear();
|
||||
|
||||
foreach ($blocks as $id => $block) {
|
||||
$block_build = $block->build();
|
||||
$items = $block_build['#items'] ?? [];
|
||||
$this->assertSame($expectations[$id], $this->convertBuiltMenuToIdTree($items), "Menu navigation block $id with an active trail renders the expected tree.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the generated HTML markup.
|
||||
*/
|
||||
public function testHtmlMarkup(): void {
|
||||
$block = $this->blockManager->createInstance('navigation_menu:' . $this->menu->id(), [
|
||||
'region' => 'content',
|
||||
'id' => 'machine_name',
|
||||
'level' => 1,
|
||||
'depth' => NavigationMenuBlock::NAVIGATION_MAX_DEPTH - 1,
|
||||
]);
|
||||
|
||||
$block_build = $block->build();
|
||||
$render = \Drupal::service('renderer')->renderRoot($block_build);
|
||||
|
||||
$dom = new \DOMDocument();
|
||||
$dom->loadHTML((string) $render);
|
||||
$xpath = new \DOMXPath($dom);
|
||||
|
||||
$items_query = [
|
||||
"//li[contains(@class,'toolbar-block__list-item')]/span/span[text()='title 1']",
|
||||
"//li[contains(@class,'toolbar-block__list-item')]/button/span[text()='title 2']",
|
||||
"//li[contains(@class,'toolbar-menu__item--level-1')]/button/span[text()='title 3']",
|
||||
"//li[contains(@class,'toolbar-menu__item--level-2')]/a[text()='title 4']",
|
||||
"//li[contains(@class,'toolbar-block__list-item')]/button/span[text()='title 5']",
|
||||
"//li[contains(@class,'toolbar-block__list-item')]/a/span[text()='title 6']",
|
||||
"//li[contains(@class,'toolbar-block__list-item')]/a[contains(@class, 'external-link')]",
|
||||
"//li[contains(@class,'toolbar-block__list-item')]/a[contains(@class, 'external-link')]",
|
||||
"//li[contains(@class,'toolbar-block__list-item')]/a[@target='_blank']",
|
||||
"//li[contains(@class,'toolbar-menu__item--level-1')]/button/span[text()='title 7']",
|
||||
"//li[contains(@class,'toolbar-block__list-item')]/button/span[text()='title 8']",
|
||||
"//li[contains(@class,'toolbar-menu__item--level-2')]/span[text()='title 10']",
|
||||
"//li[contains(@class,'toolbar-menu__item--level-1')]/button/span[text()='title 11']",
|
||||
"//li[contains(@class,'toolbar-menu__item--level-2')]/button[text()='title 12']",
|
||||
"//li[contains(@class,'toolbar-block__list-item')]/button/span[text()='title 13']",
|
||||
"//li[contains(@class,'toolbar-menu__item--level-1')]/button/span[text()='title 14']",
|
||||
"//li[contains(@class,'toolbar-menu__item--level-2')]/a[text()='Overview']",
|
||||
"//li[contains(@class,'toolbar-menu__item--level-1')]/button/span[text()='title 17']",
|
||||
];
|
||||
foreach ($items_query as $query) {
|
||||
$span = $xpath->query($query);
|
||||
$this->assertEquals(1, $span->length, $query);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to allow for easy menu link tree structure assertions.
|
||||
*
|
||||
* Converts the result of MenuLinkTree::build() in a "menu link ID tree".
|
||||
*
|
||||
* @param array $build
|
||||
* The return value of MenuLinkTree::build()
|
||||
*
|
||||
* @return array
|
||||
* The "menu link ID tree" representation of the given render array.
|
||||
*/
|
||||
protected function convertBuiltMenuToIdTree(array $build): array {
|
||||
$level = [];
|
||||
foreach (Element::children($build) as $id) {
|
||||
$level[$id] = [];
|
||||
if (isset($build[$id]['below'])) {
|
||||
$level[$id] = $this->convertBuiltMenuToIdTree($build[$id]['below']);
|
||||
}
|
||||
}
|
||||
return $level;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\navigation\Kernel;
|
||||
|
||||
use Drupal\Core\Menu\MenuTreeParameters;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\navigation\Menu\NavigationMenuLinkTree;
|
||||
|
||||
/**
|
||||
* Tests \Drupal\navigation\Menu\NavigationMenuLinkTree.
|
||||
*
|
||||
* @group navigation
|
||||
*
|
||||
* @see \Drupal\navigation\Menu\NavigationMenuLinkTree
|
||||
*/
|
||||
class NavigationMenuLinkTreeTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* The tested navigation menu link tree.
|
||||
*
|
||||
* @var \Drupal\navigation\Menu\NavigationMenuLinkTree
|
||||
*/
|
||||
protected NavigationMenuLinkTree $linkTree;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'field',
|
||||
'layout_builder',
|
||||
'layout_discovery',
|
||||
'link',
|
||||
'menu_link_content',
|
||||
'menu_test',
|
||||
'navigation',
|
||||
'navigation_test',
|
||||
'system',
|
||||
'user',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->installEntitySchema('user');
|
||||
$this->installEntitySchema('menu_link_content');
|
||||
|
||||
$this->linkTree = $this->container->get('navigation.menu_tree');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the hook_navigation_menu_link_tree_alter logic.
|
||||
*/
|
||||
public function testNavigationMenuLinkTreeAlter(): void {
|
||||
/** @var \Drupal\system\MenuStorage $storage */
|
||||
$storage = \Drupal::entityTypeManager()->getStorage('menu');
|
||||
$storage->create(['id' => 'menu1', 'label' => 'Menu 1'])->save();
|
||||
$storage->create(['id' => 'menu2', 'label' => 'Menu 2'])->save();
|
||||
|
||||
\Drupal::entityTypeManager()
|
||||
->getStorage('menu_link_content')
|
||||
->create([
|
||||
'link' => ['uri' => 'internal:/menu_name_test'],
|
||||
'menu_name' => 'menu1',
|
||||
'bundle' => 'menu_link_content',
|
||||
'title' => 'Link test',
|
||||
])->save();
|
||||
\Drupal::entityTypeManager()
|
||||
->getStorage('menu_link_content')
|
||||
->create([
|
||||
'link' => ['uri' => 'internal:/menu_name_test'],
|
||||
'menu_name' => 'menu1',
|
||||
'bundle' => 'menu_link_content',
|
||||
'title' => 'Link test',
|
||||
])->save();
|
||||
\Drupal::entityTypeManager()
|
||||
->getStorage('menu_link_content')
|
||||
->create([
|
||||
'link' => ['uri' => 'internal:/menu_name_test'],
|
||||
'menu_name' => 'menu2',
|
||||
'bundle' => 'menu_link_content',
|
||||
'title' => 'Link test',
|
||||
])->save();
|
||||
|
||||
$output = $this->linkTree->load('menu1', new MenuTreeParameters());
|
||||
$this->assertCount(2, $output);
|
||||
$output = $this->linkTree->transform($output, []);
|
||||
$this->assertCount(0, $output);
|
||||
$output = $this->linkTree->load('menu2', new MenuTreeParameters());
|
||||
$this->assertCount(1, $output);
|
||||
$output = $this->linkTree->transform($output, []);
|
||||
$this->assertCount(1, $output);
|
||||
$item = reset($output);
|
||||
$this->assertSame('New Link Title', $item->link->getTitle());
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,161 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\navigation\Kernel;
|
||||
|
||||
use Drupal\Core\Render\MetadataBubblingUrlGenerator;
|
||||
use Drupal\Core\Routing\UrlGenerator;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\navigation\Plugin\Block\NavigationMenuBlock;
|
||||
use Drupal\system\Entity\Menu;
|
||||
use Drupal\system\Tests\Routing\MockRouteProvider;
|
||||
use Drupal\Tests\Core\Menu\MenuLinkMock;
|
||||
use Symfony\Component\Routing\Route;
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
|
||||
/**
|
||||
* Tests \Drupal\navigation\Plugin\Block\NavigationMenuBlock.
|
||||
*
|
||||
* @group navigation
|
||||
* @see \Drupal\navigation\Plugin\Derivative\SystemMenuNavigationBlock
|
||||
* @see \Drupal\navigation\Plugin\Block\NavigationMenuBlock
|
||||
*/
|
||||
class NavigationMenuMarkupTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'system',
|
||||
'navigation',
|
||||
'menu_test',
|
||||
'menu_link_content',
|
||||
'field',
|
||||
'block',
|
||||
'user',
|
||||
'link',
|
||||
'layout_builder',
|
||||
];
|
||||
|
||||
/**
|
||||
* The menu for testing.
|
||||
*
|
||||
* @var \Drupal\system\MenuInterface
|
||||
*/
|
||||
protected $menu;
|
||||
|
||||
/**
|
||||
* The menu link tree service.
|
||||
*
|
||||
* @var \Drupal\Core\Menu\MenuLinkTree
|
||||
*/
|
||||
protected $linkTree;
|
||||
|
||||
/**
|
||||
* The menu link plugin manager service.
|
||||
*
|
||||
* @var \Drupal\Core\Menu\MenuLinkManagerInterface
|
||||
*/
|
||||
protected $menuLinkManager;
|
||||
|
||||
/**
|
||||
* The block manager service.
|
||||
*
|
||||
* @var \Drupal\Core\Block\BlockManager
|
||||
*/
|
||||
protected $blockManager;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->installEntitySchema('menu_link_content');
|
||||
|
||||
$this->menuLinkManager = $this->container->get('plugin.manager.menu.link');
|
||||
$this->linkTree = $this->container->get('menu.link_tree');
|
||||
$this->blockManager = $this->container->get('plugin.manager.block');
|
||||
|
||||
$routes = new RouteCollection();
|
||||
$requirements = ['_access' => 'TRUE'];
|
||||
$options = ['_access_checks' => ['access_check.default']];
|
||||
$routes->add('example1', new Route('/example1', [], $requirements, $options));
|
||||
$routes->add('example2', new Route('/example2', [], $requirements, $options));
|
||||
$routes->add('example3', new Route('/example3', [], $requirements, $options));
|
||||
|
||||
// Define our RouteProvider mock.
|
||||
$mock_route_provider = new MockRouteProvider($routes);
|
||||
$this->container->set('router.route_provider', $mock_route_provider);
|
||||
|
||||
// Define our UrlGenerator service that use the new RouteProvider.
|
||||
$url_generator_non_bubbling = new UrlGenerator(
|
||||
$mock_route_provider,
|
||||
$this->container->get('path_processor_manager'),
|
||||
$this->container->get('route_processor_manager'),
|
||||
$this->container->get('request_stack'),
|
||||
$this->container->getParameter('filter_protocols')
|
||||
);
|
||||
$url_generator = new MetadataBubblingUrlGenerator($url_generator_non_bubbling, $this->container->get('renderer'));
|
||||
$this->container->set('url_generator', $url_generator);
|
||||
|
||||
// Add a new custom menu.
|
||||
$menu_name = 'mock';
|
||||
$label = $this->randomMachineName(16);
|
||||
|
||||
$this->menu = Menu::create([
|
||||
'id' => $menu_name,
|
||||
'label' => $label,
|
||||
'description' => 'Description text',
|
||||
]);
|
||||
$this->menu->save();
|
||||
|
||||
// This creates a tree with the following structure:
|
||||
// - 1
|
||||
// - 2
|
||||
// - 3
|
||||
// phpcs:disable
|
||||
$links = [
|
||||
1 => MenuLinkMock::create(['id' => 'test.example1', 'route_name' => 'example1', 'title' => 'title 1', 'parent' => '', 'weight' => 0]),
|
||||
2 => MenuLinkMock::create(['id' => 'test.example2', 'route_name' => 'example2', 'title' => 'Another title', 'parent' => '', 'route_parameters' => ['foo' => 'bar'], 'weight' => 1]),
|
||||
3 => MenuLinkMock::create(['id' => 'test.example3', 'route_name' => 'example3', 'title' => 'Nested menu link', 'parent' => 'test.example2', 'weight' => 2]),
|
||||
];
|
||||
// phpcs:enable
|
||||
foreach ($links as $instance) {
|
||||
$this->menuLinkManager->addDefinition($instance->getPluginId(), $instance->getPluginDefinition());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the generated HTML markup.
|
||||
*/
|
||||
public function testToolbarButtonAttributes(): void {
|
||||
$block = $this->blockManager->createInstance('navigation_menu:' . $this->menu->id(), [
|
||||
'region' => 'content',
|
||||
'id' => 'machine_name',
|
||||
'level' => 1,
|
||||
'depth' => NavigationMenuBlock::NAVIGATION_MAX_DEPTH - 1,
|
||||
]);
|
||||
|
||||
$block_build = $block->build();
|
||||
$render = \Drupal::service('renderer')->renderRoot($block_build);
|
||||
|
||||
$dom = new \DOMDocument();
|
||||
$dom->loadHTML((string) $render);
|
||||
$xpath = new \DOMXPath($dom);
|
||||
|
||||
$items_query = [
|
||||
"//li[contains(@class,'toolbar-block__list-item')]/a[@data-index-text='t']",
|
||||
"//li[contains(@class,'toolbar-block__list-item')]/a[@data-icon-text='ti']",
|
||||
"//li[contains(@class,'toolbar-block__list-item')]/button[@data-index-text='a']",
|
||||
"//li[contains(@class,'toolbar-block__list-item')]/button[@data-icon-text='An']",
|
||||
"//li[contains(@class,'toolbar-menu__item--level-1')]/a[@data-index-text='n']",
|
||||
"//li[contains(@class,'toolbar-menu__item--level-1')]/a[@data-icon-text='Ne']",
|
||||
];
|
||||
foreach ($items_query as $query) {
|
||||
$element = $xpath->query($query);
|
||||
$this->assertEquals(1, $element->length, $query);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
const navigationTest = {
|
||||
'@tags': ['core', 'a11y', 'a11y:admin', 'navigation'],
|
||||
|
||||
before(browser) {
|
||||
browser
|
||||
.drupalInstall({
|
||||
installProfile: 'nightwatch_a11y_testing',
|
||||
})
|
||||
.drupalInstallModule('navigation', true)
|
||||
.setWindowSize(1220, 800);
|
||||
},
|
||||
after(browser) {
|
||||
browser.drupalUninstall();
|
||||
},
|
||||
};
|
||||
const testCases = [{ name: 'Claro page', path: '/user/1/edit' }];
|
||||
|
||||
testCases.forEach((testCase) => {
|
||||
navigationTest[`Accessibility - Navigation Module - ${testCase.name}`] = (
|
||||
browser,
|
||||
) => {
|
||||
browser.drupalLoginAsAdmin(() => {
|
||||
browser
|
||||
.drupalRelativeURL(testCase.path)
|
||||
.axeInject()
|
||||
.axeRun('body', testCase.options || {});
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
module.exports = navigationTest;
|
||||
@ -0,0 +1,47 @@
|
||||
const selectors = {
|
||||
expandButton: {
|
||||
expanded: '.admin-toolbar__expand-button[aria-expanded=true]',
|
||||
collapsed: '.admin-toolbar__expand-button[aria-expanded=false]',
|
||||
},
|
||||
htmlAttribute: {
|
||||
expanded: '[data-admin-toolbar="expanded"]',
|
||||
collapsed: '[data-admin-toolbar="collapsed"]',
|
||||
},
|
||||
clearCacheButton: 'input[data-drupal-selector="edit-clear"]',
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
'@tags': ['core', 'navigation'],
|
||||
browser(browser) {
|
||||
browser
|
||||
.drupalInstall()
|
||||
.drupalInstallModule('navigation', true)
|
||||
.drupalInstallModule('big_pipe')
|
||||
.setWindowSize(1220, 800);
|
||||
},
|
||||
after(browser) {
|
||||
browser.drupalUninstall();
|
||||
},
|
||||
|
||||
'Expand/Collapse': (browser) => {
|
||||
browser.drupalLoginAsAdmin(() => {
|
||||
browser
|
||||
.drupalRelativeURL('/admin/config/development/performance')
|
||||
.click(selectors.clearCacheButton)
|
||||
.waitForElementPresent(
|
||||
'[data-once="admin-toolbar-document-triggers-listener"]',
|
||||
)
|
||||
// This pause required to wait for first init event.
|
||||
.waitForElementNotPresent(selectors.expandButton.expanded)
|
||||
.waitForElementPresent(selectors.expandButton.collapsed)
|
||||
.waitForElementPresent(selectors.htmlAttribute.collapsed)
|
||||
.click(selectors.expandButton.collapsed)
|
||||
.waitForElementPresent(selectors.expandButton.expanded)
|
||||
.waitForElementPresent(selectors.htmlAttribute.expanded)
|
||||
.click(selectors.expandButton.expanded)
|
||||
.waitForElementNotPresent(selectors.expandButton.expanded)
|
||||
.waitForElementPresent(selectors.expandButton.collapsed)
|
||||
.waitForElementPresent(selectors.htmlAttribute.collapsed);
|
||||
});
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Verify that Drupal.displace() attribute is properly added by JavaScript.
|
||||
*/
|
||||
module.exports = {
|
||||
'@tags': ['core', 'navigation'],
|
||||
browser(browser) {
|
||||
browser
|
||||
.drupalInstall()
|
||||
.drupalInstallModule('navigation', true)
|
||||
.drupalInstallModule('big_pipe')
|
||||
.setWindowSize(1220, 800);
|
||||
},
|
||||
after(browser) {
|
||||
browser.drupalUninstall();
|
||||
},
|
||||
|
||||
'Verify displace attribute': (browser) => {
|
||||
browser.drupalLoginAsAdmin(() => {
|
||||
browser
|
||||
.drupalRelativeURL('/admin/')
|
||||
.waitForElementPresent(
|
||||
'.admin-toolbar__displace-placeholder[data-offset-left]',
|
||||
);
|
||||
});
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,308 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\navigation\Unit;
|
||||
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Menu\MenuLinkTreeElement;
|
||||
use Drupal\Core\Menu\StaticMenuLinkOverridesInterface;
|
||||
use Drupal\Core\Routing\RouteProviderInterface;
|
||||
use Drupal\Core\StringTranslation\TranslationInterface;
|
||||
use Drupal\navigation\Menu\NavigationMenuLinkTreeManipulators;
|
||||
use Drupal\system\Controller\SystemController;
|
||||
use Drupal\Tests\Core\Menu\MenuLinkMock;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
use Symfony\Component\Routing\Route;
|
||||
|
||||
/**
|
||||
* Tests the navigation menu link tree manipulator.
|
||||
*
|
||||
* @group navigation
|
||||
*
|
||||
* @coversDefaultClass \Drupal\navigation\Menu\NavigationMenuLinkTreeManipulators
|
||||
*/
|
||||
class NavigationMenuLinkTreeManipulatorsTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* Tests the addSecondLevelOverviewLinks() tree manipulator.
|
||||
*
|
||||
* @covers ::addSecondLevelOverviewLinks
|
||||
*/
|
||||
public function testAddSecondLevelOverviewLinks(): void {
|
||||
$routeProvider = $this->createMock(RouteProviderInterface::class);
|
||||
// For only the route named 'child_list', return a route object with the
|
||||
// SystemController::systemAdminMenuBlockPage as the controller.
|
||||
$childListRoute = new Route('/test-child-list', ['_controller' => SystemController::class . '::systemAdminMenuBlockPage']);
|
||||
$routeProvider->expects($this->any())
|
||||
->method('getRouteByName')
|
||||
->willReturnCallback(static fn ($name) => $name === 'child_list' ? $childListRoute : new Route("/$name"));
|
||||
$overrides = $this->createMock(StaticMenuLinkOverridesInterface::class);
|
||||
$translation = $this->createMock(TranslationInterface::class);
|
||||
$translation
|
||||
->method('translateString')
|
||||
->willReturnCallback(static fn ($string) => $string);
|
||||
$manipulator = new NavigationMenuLinkTreeManipulators($routeProvider, $overrides, $translation);
|
||||
|
||||
$originalTree = $this->mockTree();
|
||||
// Make sure overview links do not already exist.
|
||||
$this->assertArrayNotHasKey('test.example3.navigation_overview', $originalTree[2]->subtree[3]->subtree);
|
||||
$this->assertArrayNotHasKey('test.example6.navigation_overview', $originalTree[5]->subtree[6]->subtree);
|
||||
$tree = $manipulator->addSecondLevelOverviewLinks($originalTree);
|
||||
|
||||
// First level menu items should not have any children added.
|
||||
$this->assertEmpty($tree[1]->subtree);
|
||||
$this->assertEquals($originalTree[2]->subtree, $tree[2]->subtree);
|
||||
$this->assertEquals($originalTree[5]->subtree, $tree[5]->subtree);
|
||||
$this->assertEquals($originalTree[8]->subtree, $tree[8]->subtree);
|
||||
$this->assertEquals($originalTree[11]->subtree, $tree[11]->subtree);
|
||||
$this->assertEquals($originalTree[13]->subtree, $tree[13]->subtree);
|
||||
$this->assertEquals($originalTree[16]->subtree, $tree[16]->subtree);
|
||||
$this->assertEquals($originalTree[19]->subtree, $tree[19]->subtree);
|
||||
|
||||
// Leaves should not have any children added.
|
||||
$this->assertEmpty($tree[2]->subtree[3]->subtree[4]->subtree);
|
||||
$this->assertEmpty($tree[5]->subtree[6]->subtree[7]->subtree);
|
||||
$this->assertEmpty($tree[8]->subtree[9]->subtree[10]->subtree);
|
||||
$this->assertEmpty($tree[11]->subtree[12]->subtree);
|
||||
$this->assertEmpty($tree[13]->subtree[14]->subtree[15]->subtree);
|
||||
$this->assertEmpty($tree[16]->subtree[17]->subtree[18]->subtree);
|
||||
$this->assertEmpty($tree[19]->subtree[20]->subtree[21]->subtree);
|
||||
$this->assertEmpty($tree[19]->subtree[20]->subtree[22]->subtree);
|
||||
|
||||
// Links 3 and 6 should have overview children, even though 6 is unrouted.
|
||||
$this->assertArrayHasKey('test.example3.navigation_overview', $tree[2]->subtree[3]->subtree);
|
||||
$this->assertArrayHasKey('test.example6.navigation_overview', $tree[5]->subtree[6]->subtree);
|
||||
|
||||
// Link 9 is a child list page, so it should not have an overview child.
|
||||
$this->assertArrayNotHasKey('test.example9.navigation_overview', $tree[8]->subtree[9]->subtree);
|
||||
|
||||
// Link 14 and Link 17 are <nolink> and <button> routes, so they should not
|
||||
// have overview children.
|
||||
$this->assertArrayNotHasKey('test.example14.navigation_overview', $tree[13]->subtree[14]->subtree);
|
||||
$this->assertArrayNotHasKey('test.example17.navigation_overview', $tree[16]->subtree[17]->subtree);
|
||||
|
||||
// Link 20's child links are either inaccessible, disabled, or link to the
|
||||
// same route as 20, so it should not have an overview child.
|
||||
$this->assertArrayNotHasKey('test.example20.navigation_overview', $tree[19]->subtree[20]->subtree);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a mock tree.
|
||||
*
|
||||
* This mocks a tree with the following structure:
|
||||
* - 1
|
||||
* - 2
|
||||
* - 3
|
||||
* - 4
|
||||
* - 5
|
||||
* - 6 (external)
|
||||
* - 7
|
||||
* - 8
|
||||
* - 9
|
||||
* - 10
|
||||
* - 11
|
||||
* - 12
|
||||
* - 13
|
||||
* - 14 (nolink)
|
||||
* - 15
|
||||
* - 16
|
||||
* - 17 (button)
|
||||
* - 18
|
||||
* - 19
|
||||
* - 20
|
||||
* - 21 (disabled)
|
||||
* - 22 (access denied)
|
||||
* - 23 (links to same routed URL as 20)
|
||||
*
|
||||
* With link 9 linking to a page that contains a list of child menu links.
|
||||
*
|
||||
* @return \Drupal\Core\Menu\MenuLinkTreeElement[]
|
||||
* The mock menu tree.
|
||||
*/
|
||||
protected function mockTree(): array {
|
||||
$links = [
|
||||
1 => MenuLinkMock::create([
|
||||
'id' => 'test.example1',
|
||||
'route_name' => 'example1',
|
||||
'title' => 'foo',
|
||||
'parent' => '',
|
||||
]),
|
||||
2 => MenuLinkMock::create([
|
||||
'id' => 'test.example2',
|
||||
'route_name' => 'example2',
|
||||
'title' => 'foo',
|
||||
'parent' => '',
|
||||
]),
|
||||
3 => MenuLinkMock::create([
|
||||
'id' => 'test.example3',
|
||||
'route_name' => 'example3',
|
||||
'title' => 'baz',
|
||||
'parent' => 'test.example2',
|
||||
]),
|
||||
4 => MenuLinkMock::create([
|
||||
'id' => 'test.example4',
|
||||
'route_name' => 'example4',
|
||||
'title' => 'qux',
|
||||
'parent' => 'test.example3',
|
||||
]),
|
||||
5 => MenuLinkMock::create([
|
||||
'id' => 'test.example5',
|
||||
'route_name' => 'example5',
|
||||
'title' => 'title5',
|
||||
'parent' => '',
|
||||
]),
|
||||
6 => MenuLinkMock::create([
|
||||
'id' => 'test.example6',
|
||||
'route_name' => '',
|
||||
'url' => 'https://www.drupal.org/',
|
||||
'title' => 'bar_bar',
|
||||
'parent' => 'test.example5',
|
||||
]),
|
||||
7 => MenuLinkMock::create([
|
||||
'id' => 'test.example7',
|
||||
'route_name' => 'example7',
|
||||
'title' => 'title7',
|
||||
'parent' => 'test.example6',
|
||||
]),
|
||||
8 => MenuLinkMock::create([
|
||||
'id' => 'test.example8',
|
||||
'route_name' => 'example8',
|
||||
'title' => 'title8',
|
||||
'parent' => '',
|
||||
]),
|
||||
9 => MenuLinkMock::create([
|
||||
'id' => 'test.example9',
|
||||
'route_name' => 'child_list',
|
||||
'title' => 'title9',
|
||||
'parent' => 'test.example8',
|
||||
]),
|
||||
10 => MenuLinkMock::create([
|
||||
'id' => 'test.example10',
|
||||
'route_name' => 'example9',
|
||||
'title' => 'title10',
|
||||
'parent' => 'test.example9',
|
||||
]),
|
||||
11 => MenuLinkMock::create([
|
||||
'id' => 'test.example11',
|
||||
'route_name' => 'example11',
|
||||
'title' => 'title11',
|
||||
'parent' => '',
|
||||
]),
|
||||
12 => MenuLinkMock::create([
|
||||
'id' => 'test.example12',
|
||||
'route_name' => 'example12',
|
||||
'title' => 'title12',
|
||||
'parent' => 'text.example11',
|
||||
]),
|
||||
13 => MenuLinkMock::create([
|
||||
'id' => 'test.example13',
|
||||
'route_name' => 'example13',
|
||||
'title' => 'title13',
|
||||
'parent' => '',
|
||||
]),
|
||||
14 => MenuLinkMock::create([
|
||||
'id' => 'test.example14',
|
||||
'route_name' => '<nolink>',
|
||||
'title' => 'title14',
|
||||
'parent' => 'text.example13',
|
||||
]),
|
||||
15 => MenuLinkMock::create([
|
||||
'id' => 'test.example15',
|
||||
'route_name' => 'example15',
|
||||
'title' => 'title15',
|
||||
'parent' => 'text.example14',
|
||||
]),
|
||||
16 => MenuLinkMock::create([
|
||||
'id' => 'test.example16',
|
||||
'route_name' => 'example16',
|
||||
'title' => 'title16',
|
||||
'parent' => '',
|
||||
]),
|
||||
17 => MenuLinkMock::create([
|
||||
'id' => 'test.example17',
|
||||
'route_name' => '<button>',
|
||||
'title' => 'title17',
|
||||
'parent' => 'text.example16',
|
||||
]),
|
||||
18 => MenuLinkMock::create([
|
||||
'id' => 'test.example18',
|
||||
'route_name' => 'example18',
|
||||
'title' => 'title18',
|
||||
'parent' => 'text.example17',
|
||||
]),
|
||||
19 => MenuLinkMock::create([
|
||||
'id' => 'test.example19',
|
||||
'route_name' => 'example19',
|
||||
'title' => 'title19',
|
||||
'parent' => '',
|
||||
]),
|
||||
20 => MenuLinkMock::create([
|
||||
'id' => 'test.example20',
|
||||
'route_name' => 'example20',
|
||||
'title' => 'title20',
|
||||
'parent' => 'test.example19',
|
||||
]),
|
||||
21 => MenuLinkMock::create([
|
||||
'id' => 'test.example21',
|
||||
'route_name' => 'example21',
|
||||
'title' => 'title21',
|
||||
'parent' => 'test.example20',
|
||||
'enabled' => FALSE,
|
||||
]),
|
||||
22 => MenuLinkMock::create([
|
||||
'id' => 'test.example22',
|
||||
'route_name' => 'no_access',
|
||||
'title' => 'title22',
|
||||
'parent' => 'test.example20',
|
||||
]),
|
||||
23 => MenuLinkMock::create([
|
||||
'id' => 'test.example23',
|
||||
'route_name' => 'example20',
|
||||
'title' => 'title23',
|
||||
'parent' => 'test.example20',
|
||||
]),
|
||||
];
|
||||
$tree = [];
|
||||
$tree[1] = new MenuLinkTreeElement($links[1], FALSE, 1, FALSE, []);
|
||||
$tree[2] = new MenuLinkTreeElement($links[2], TRUE, 1, FALSE, [
|
||||
3 => new MenuLinkTreeElement($links[3], TRUE, 2, FALSE, [
|
||||
4 => new MenuLinkTreeElement($links[4], FALSE, 3, FALSE, []),
|
||||
]),
|
||||
]);
|
||||
$tree[5] = new MenuLinkTreeElement($links[5], TRUE, 1, FALSE, [
|
||||
6 => new MenuLinkTreeElement($links[6], TRUE, 2, FALSE, [
|
||||
7 => new MenuLinkTreeElement($links[7], FALSE, 3, FALSE, []),
|
||||
]),
|
||||
]);
|
||||
$tree[8] = new MenuLinkTreeElement($links[8], TRUE, 1, FALSE, [
|
||||
9 => new MenuLinkTreeElement($links[9], TRUE, 2, FALSE, [
|
||||
10 => new MenuLinkTreeElement($links[10], FALSE, 3, FALSE, []),
|
||||
]),
|
||||
]);
|
||||
$tree[11] = new MenuLinkTreeElement($links[11], TRUE, 1, FALSE, [
|
||||
12 => new MenuLinkTreeElement($links[12], FALSE, 2, FALSE, []),
|
||||
]);
|
||||
$tree[13] = new MenuLinkTreeElement($links[13], TRUE, 1, FALSE, [
|
||||
14 => new MenuLinkTreeElement($links[14], TRUE, 2, FALSE, [
|
||||
15 => new MenuLinkTreeElement($links[15], FALSE, 3, FALSE, []),
|
||||
]),
|
||||
]);
|
||||
$tree[16] = new MenuLinkTreeElement($links[16], TRUE, 1, FALSE, [
|
||||
17 => new MenuLinkTreeElement($links[17], TRUE, 2, FALSE, [
|
||||
18 => new MenuLinkTreeElement($links[18], FALSE, 3, FALSE, []),
|
||||
]),
|
||||
]);
|
||||
$tree[19] = new MenuLinkTreeElement($links[19], TRUE, 1, FALSE, [
|
||||
20 => new MenuLinkTreeElement($links[20], TRUE, 2, FALSE, [
|
||||
21 => new MenuLinkTreeElement($links[21], FALSE, 3, FALSE, []),
|
||||
22 => new MenuLinkTreeElement($links[22], FALSE, 3, FALSE, []),
|
||||
23 => new MenuLinkTreeElement($links[23], FALSE, 3, FALSE, []),
|
||||
]),
|
||||
]);
|
||||
$tree[19]->subtree[20]->subtree[22]->access = AccessResult::forbidden();
|
||||
|
||||
return $tree;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\navigation\Unit;
|
||||
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\navigation\TopBarRegion;
|
||||
use Drupal\navigation_test\Plugin\TopBarItem\TopBarItemInstantiation;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\navigation\TopBarItemBase
|
||||
*
|
||||
* @group navigation
|
||||
*/
|
||||
class TopBarItemBaseTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* @covers ::label
|
||||
* @covers ::region
|
||||
*/
|
||||
public function testTopBarItemBase(): void {
|
||||
$definition = [
|
||||
'label' => new TranslatableMarkup('label'),
|
||||
'region' => TopBarRegion::Tools,
|
||||
];
|
||||
|
||||
$top_bar_item_base = new TopBarItemInstantiation([], 'test_top_bar_item_base', $definition);
|
||||
|
||||
$this->assertEquals($definition['label'], $top_bar_item_base->label());
|
||||
$this->assertEquals($definition['region'], $top_bar_item_base->region());
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\navigation\Unit;
|
||||
|
||||
use Drupal\Component\Plugin\Discovery\DiscoveryInterface;
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\DependencyInjection\ContainerBuilder;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Drupal\navigation\TopBarItemManager;
|
||||
use Drupal\navigation\TopBarItemManagerInterface;
|
||||
use Drupal\navigation\TopBarRegion;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\navigation\TopBarItemManager
|
||||
*
|
||||
* @group navigation
|
||||
*/
|
||||
class TopBarItemManagerTest extends UnitTestCase {
|
||||
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* The top bar item manager under test.
|
||||
*
|
||||
* @var \Drupal\navigation\TopBarItemManagerInterface
|
||||
*/
|
||||
protected TopBarItemManagerInterface $manager;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$container = new ContainerBuilder();
|
||||
$container->set('string_translation', $this->getStringTranslationStub());
|
||||
\Drupal::setContainer($container);
|
||||
|
||||
$cache_backend = $this->prophesize(CacheBackendInterface::class);
|
||||
$module_handler = $this->prophesize(ModuleHandlerInterface::class);
|
||||
$this->manager = new TopBarItemManager(new \ArrayObject(), $cache_backend->reveal(), $module_handler->reveal());
|
||||
|
||||
$discovery = $this->prophesize(DiscoveryInterface::class);
|
||||
// Specify the 'broken' block, as well as 3 other blocks with admin labels
|
||||
// that are purposefully not in alphabetical order.
|
||||
$discovery->getDefinitions()->willReturn([
|
||||
'tools' => [
|
||||
'label' => 'Tools',
|
||||
'region' => TopBarRegion::Tools,
|
||||
],
|
||||
'context' => [
|
||||
'admin_label' => 'Context',
|
||||
'region' => TopBarRegion::Context,
|
||||
],
|
||||
'actions' => [
|
||||
'label' => 'Actions',
|
||||
'region' => TopBarRegion::Actions,
|
||||
],
|
||||
'more_actions' => [
|
||||
'label' => 'More Actions',
|
||||
'region' => TopBarRegion::Actions,
|
||||
],
|
||||
]);
|
||||
// Force the discovery object onto the block manager.
|
||||
$property = new \ReflectionProperty(TopBarItemManager::class, 'discovery');
|
||||
$property->setValue($this->manager, $discovery->reveal());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getDefinitions
|
||||
*/
|
||||
public function testDefinitions(): void {
|
||||
$definitions = $this->manager->getDefinitions();
|
||||
$this->assertSame(['tools', 'context', 'actions', 'more_actions'], array_keys($definitions));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getDefinitionsByRegion
|
||||
*/
|
||||
public function testGetDefinitionsByRegion(): void {
|
||||
$tools = $this->manager->getDefinitionsByRegion(TopBarRegion::Tools);
|
||||
$this->assertSame(['tools'], array_keys($tools));
|
||||
$context = $this->manager->getDefinitionsByRegion(TopBarRegion::Context);
|
||||
$this->assertSame(['context'], array_keys($context));
|
||||
$actions = $this->manager->getDefinitionsByRegion(TopBarRegion::Actions);
|
||||
$this->assertSame(['actions', 'more_actions'], array_keys($actions));
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user