diff --git a/core/.phpstan-baseline.php b/core/.phpstan-baseline.php index 0abeb6f41cba47ac08bad74185d5982d1320c413..dff06b76d3c41df57e770d3e0fc78e9208603d7a 100644 --- a/core/.phpstan-baseline.php +++ b/core/.phpstan-baseline.php @@ -52983,18 +52983,7 @@ 'path' => __DIR__ . '/tests/Drupal/Tests/BrowserTestBase.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\BrowserTestBase\\:\\:htmlOutput\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/tests/Drupal/Tests/BrowserTestBase.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\BrowserTestBase\\:\\:initBrowserOutputFile\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/tests/Drupal/Tests/BrowserTestBase.php', -]; -$ignoreErrors[] = [ + // identifier: missingType.return 'message' => '#^Method Drupal\\\\Tests\\\\BrowserTestBase\\:\\:initConfig\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, diff --git a/core/modules/navigation/tests/src/Functional/NavigationContentTopTest.php b/core/modules/navigation/tests/src/Kernel/NavigationContentTopTest.php similarity index 71% rename from core/modules/navigation/tests/src/Functional/NavigationContentTopTest.php rename to core/modules/navigation/tests/src/Kernel/NavigationContentTopTest.php index 981b628259c7d15f8031d8a1536e4242867cb845..7782612877029eb6b3e42a7346d76645851d33a9 100644 --- a/core/modules/navigation/tests/src/Functional/NavigationContentTopTest.php +++ b/core/modules/navigation/tests/src/Kernel/NavigationContentTopTest.php @@ -2,11 +2,13 @@ declare(strict_types=1); -namespace Drupal\Tests\navigation\Functional; +namespace Drupal\Tests\navigation\Kernel; use Drupal\Core\Cache\Cache; use Drupal\Core\Url; -use Drupal\Tests\BrowserTestBase; +use Drupal\KernelTests\KernelTestBase; +use Drupal\Tests\HttpKernelUiHelperTrait; +use Drupal\Tests\user\Traits\UserCreationTrait; // cspell:ignore foobarbaz baznew @@ -15,17 +17,15 @@ * * @group navigation */ -class NavigationContentTopTest extends BrowserTestBase { +class NavigationContentTopTest extends KernelTestBase { - /** - * {@inheritdoc} - */ - protected static $modules = ['navigation', 'navigation_test', 'test_page_test']; + use HttpKernelUiHelperTrait; + use UserCreationTrait; /** * {@inheritdoc} */ - protected $defaultTheme = 'stark'; + protected static $modules = ['navigation', 'navigation_test', 'test_page_test', 'user', 'system', 'layout_builder', 'layout_discovery']; /** * {@inheritdoc} @@ -33,7 +33,9 @@ class NavigationContentTopTest extends BrowserTestBase { protected function setUp(): void { parent::setUp(); - $this->drupalLogin($this->createUser([ + $this->installConfig('navigation'); + $this->installEntitySchema('user'); + $this->setCurrentUser($this->createUser([ 'access navigation', ])); } diff --git a/core/modules/navigation/tests/src/Functional/NavigationDefaultBlockDefinitionTest.php b/core/modules/navigation/tests/src/Kernel/NavigationDefaultBlockDefinitionTest.php similarity index 74% rename from core/modules/navigation/tests/src/Functional/NavigationDefaultBlockDefinitionTest.php rename to core/modules/navigation/tests/src/Kernel/NavigationDefaultBlockDefinitionTest.php index 45cd4a2b20db7b39abb4e8eb14823a3e2fed3ef5..e39cb532fa5955f2b544cfd26fd2837bd796e50b 100644 --- a/core/modules/navigation/tests/src/Functional/NavigationDefaultBlockDefinitionTest.php +++ b/core/modules/navigation/tests/src/Kernel/NavigationDefaultBlockDefinitionTest.php @@ -2,27 +2,35 @@ declare(strict_types=1); -namespace Drupal\Tests\navigation\Functional; +namespace Drupal\Tests\navigation\Kernel; use Drupal\Core\Url; -use Drupal\Tests\BrowserTestBase; +use Drupal\KernelTests\KernelTestBase; +use Drupal\Tests\HttpKernelUiHelperTrait; +use Drupal\Tests\user\Traits\UserCreationTrait; /** * Tests the default block provider logic. * * @group navigation */ -class NavigationDefaultBlockDefinitionTest extends BrowserTestBase { +class NavigationDefaultBlockDefinitionTest extends KernelTestBase { + + use HttpKernelUiHelperTrait; + use UserCreationTrait; /** * {@inheritdoc} */ - protected static $modules = ['test_page_test', 'block']; + protected static $modules = ['test_page_test', 'block', 'user', 'system', 'layout_discovery', 'layout_builder']; /** * {@inheritdoc} */ - protected $defaultTheme = 'stark'; + protected function setUp(): void { + parent::setUp(); + $this->installEntitySchema('user'); + } /** * Tests the default block flow enabling Navigation module first. @@ -33,7 +41,8 @@ public function testNavigationDefaultAfterNavigation(): void { // After installing Navigation, the bar is present, but not the block. $module_installer->install(['navigation']); - $this->drupalLogin($this->drupalCreateUser(['access navigation'])); + $this->installConfig(['navigation']); + $this->setCurrentUser($this->createUser(['access navigation'])); $this->drupalGet($test_page_url); $this->assertSession()->elementExists('css', '.admin-toolbar'); $this->assertSession()->elementNotExists('css', '.toolbar-button--icon--test-block'); @@ -60,7 +69,8 @@ public function testNavigationDefaultBeforeNavigation(): void { // After installing Navigation, both elements are present. $module_installer->install(['navigation']); - $this->drupalLogin($this->drupalCreateUser(['access navigation'])); + $this->installConfig(['navigation']); + $this->setCurrentUser($this->createUser(['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'); diff --git a/core/modules/node/tests/src/Functional/NodeLinksTest.php b/core/modules/node/tests/src/Functional/NodeLinksTest.php deleted file mode 100644 index cb70b80a9cf855db7465c9f2301f2a86ed25aeac..0000000000000000000000000000000000000000 --- a/core/modules/node/tests/src/Functional/NodeLinksTest.php +++ /dev/null @@ -1,51 +0,0 @@ -<?php - -declare(strict_types=1); - -namespace Drupal\Tests\node\Functional; - -use Drupal\node\NodeInterface; - -/** - * Tests the output of node links (read more, add new comment, etc). - * - * @group node - */ -class NodeLinksTest extends NodeTestBase { - - /** - * {@inheritdoc} - */ - protected static $modules = ['views']; - - /** - * {@inheritdoc} - */ - protected $defaultTheme = 'stark'; - - /** - * Tests that the links can be hidden in the view display settings. - */ - public function testHideLinks(): void { - $node = $this->drupalCreateNode([ - 'type' => 'article', - 'promote' => NodeInterface::PROMOTED, - ]); - - // Links are displayed by default. - $this->drupalGet('node'); - $this->assertSession()->pageTextContains($node->getTitle()); - $this->assertSession()->linkExists('Read more'); - - // Hide links. - \Drupal::service('entity_display.repository') - ->getViewDisplay('node', 'article', 'teaser') - ->removeComponent('links') - ->save(); - - $this->drupalGet('node'); - $this->assertSession()->pageTextContains($node->getTitle()); - $this->assertSession()->linkNotExists('Read more'); - } - -} diff --git a/core/modules/node/tests/src/Kernel/NodeLinksTest.php b/core/modules/node/tests/src/Kernel/NodeLinksTest.php new file mode 100644 index 0000000000000000000000000000000000000000..eaf1de622c1d00d47a3deae233311c05245b3b9f --- /dev/null +++ b/core/modules/node/tests/src/Kernel/NodeLinksTest.php @@ -0,0 +1,93 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\Tests\node\Kernel; + +use Drupal\Core\Datetime\Entity\DateFormat; +use Drupal\KernelTests\KernelTestBase; +use Drupal\node\Entity\NodeType; +use Drupal\node\NodeInterface; +use Drupal\Tests\HttpKernelUiHelperTrait; +use Drupal\Tests\node\Traits\NodeCreationTrait; +use Drupal\Tests\user\Traits\UserCreationTrait; + +/** + * Tests the output of node links (read more, add new comment, etc). + * + * @group node + */ +class NodeLinksTest extends KernelTestBase { + + use HttpKernelUiHelperTrait; + use UserCreationTrait; + use NodeCreationTrait; + + /** + * {@inheritdoc} + */ + protected static $modules = [ + 'system', + 'user', + 'field', + 'datetime', + 'filter', + 'text', + 'node', + 'views', + ]; + + /** + * {@inheritdoc} + */ + protected function setUp(): void { + parent::setUp(); + + $this->installEntitySchema('user'); + $this->installEntitySchema('node'); + $this->installConfig(['filter']); + $this->installConfig(['node']); + + $this->setUpCurrentUser(permissions: [ + 'access content', + ]); + + DateFormat::create([ + 'id' => 'fallback', + 'label' => 'Fallback', + 'pattern' => 'Y-m-d', + ])->save(); + + $node_type = NodeType::create([ + 'type' => 'article', + 'name' => 'Article', + ]); + $node_type->save(); + } + + /** + * Tests that the links can be hidden in the view display settings. + */ + public function testHideLinks(): void { + $node = $this->createNode([ + 'type' => 'article', + 'promote' => NodeInterface::PROMOTED, + ]); + + // Links are displayed by default. + $this->drupalGet('node'); + $this->assertSession()->pageTextContains($node->getTitle()); + $this->assertSession()->linkExists('Read more'); + + // Hide links. + \Drupal::service('entity_display.repository') + ->getViewDisplay('node', 'article', 'teaser') + ->removeComponent('links') + ->save(); + + $this->drupalGet('node'); + $this->assertSession()->pageTextContains($node->getTitle()); + $this->assertSession()->linkNotExists('Read more'); + } + +} diff --git a/core/modules/taxonomy/tests/src/Functional/TermIndexTest.php b/core/modules/taxonomy/tests/src/Functional/TermIndexTest.php index e782646e1cd70d202ef85fbb230f9919afad37d3..a3b20727863442cbf07ed667bac7ccc949cb940b 100644 --- a/core/modules/taxonomy/tests/src/Functional/TermIndexTest.php +++ b/core/modules/taxonomy/tests/src/Functional/TermIndexTest.php @@ -4,7 +4,6 @@ namespace Drupal\Tests\taxonomy\Functional; -use Drupal\Core\Link; use Drupal\Core\Database\Database; use Drupal\Core\Field\FieldStorageDefinitionInterface; @@ -235,22 +234,4 @@ public function testTaxonomyIndex(): void { $this->assertEquals(0, $index_count, 'Term 2 is not indexed.'); } - /** - * Tests that there is a link to the parent term on the child term page. - */ - public function testTaxonomyTermHierarchyBreadcrumbs(): void { - // Create two taxonomy terms and set term2 as the parent of term1. - $term1 = $this->createTerm($this->vocabulary); - $term2 = $this->createTerm($this->vocabulary); - $term1->parent = [$term2->id()]; - $term1->save(); - - // Verify that the page breadcrumbs include a link to the parent term. - $this->drupalGet('taxonomy/term/' . $term1->id()); - // Breadcrumbs are not rendered with a language, prevent the term - // language from being added to the options. - // Check that parent term link is displayed when viewing the node. - $this->assertSession()->responseContains(Link::fromTextAndUrl($term2->getName(), $term2->toUrl('canonical', ['language' => NULL]))->toString()); - } - } diff --git a/core/modules/taxonomy/tests/src/Kernel/TermHierarchyTest.php b/core/modules/taxonomy/tests/src/Kernel/TermHierarchyTest.php new file mode 100644 index 0000000000000000000000000000000000000000..829989e64aa648dbbcc264b4fa3811eabcf56236 --- /dev/null +++ b/core/modules/taxonomy/tests/src/Kernel/TermHierarchyTest.php @@ -0,0 +1,84 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\Tests\taxonomy\Kernel; + +use Drupal\Core\Link; +use Drupal\KernelTests\KernelTestBase; +use Drupal\Tests\block\Traits\BlockCreationTrait; +use Drupal\Tests\HttpKernelUiHelperTrait; +use Drupal\Tests\taxonomy\Traits\TaxonomyTestTrait; +use Drupal\Tests\user\Traits\UserCreationTrait; + +/** + * Tests term hierarchy. + * + * @group taxonomy + */ +class TermHierarchyTest extends KernelTestBase { + + use HttpKernelUiHelperTrait; + use UserCreationTrait; + use BlockCreationTrait; + use TaxonomyTestTrait; + + /** + * {@inheritdoc} + */ + protected static $modules = [ + 'system', + 'user', + 'block', + 'node', + 'filter', + 'text', + 'taxonomy', + ]; + + /** + * {@inheritdoc} + */ + protected function setUp(): void { + parent::setUp(); + $this->installConfig(['filter']); + $this->installEntitySchema('taxonomy_term'); + $this->installEntitySchema('user'); + + $this->container->get('theme_installer')->install(['stark']); + $this->container->get('theme.manager')->setActiveTheme( + $this->container->get('theme.initialization')->initTheme('stark') + ); + + $this->placeBlock('system_breadcrumb_block', [ + 'region' => 'content', + 'theme' => 'stark', + ]); + + $this->setUpCurrentUser(permissions: [ + 'administer taxonomy', + 'bypass node access', + ]); + } + + /** + * Tests that there is a link to the parent term on the child term page. + */ + public function testTaxonomyTermHierarchyBreadcrumbs(): void { + $vocabulary = $this->createVocabulary(); + + // Create two taxonomy terms and set term2 as the parent of term1. + $term1 = $this->createTerm($vocabulary); + $term2 = $this->createTerm($vocabulary); + $term1->parent = [$term2->id()]; + $term1->save(); + + // Verify that the page breadcrumbs include a link to the parent term. + $this->drupalGet('taxonomy/term/' . $term1->id()); + // Breadcrumbs are not rendered with a language, prevent the term + // language from being added to the options. + // Check that parent term link is displayed when viewing the node. + $this->assertSession()->responseContains(Link::fromTextAndUrl($term2->getName(), $term2->toUrl('canonical', ['language' => NULL]))->toString()); + } + +} diff --git a/core/tests/Drupal/Tests/BrowserHtmlDebugTrait.php b/core/tests/Drupal/Tests/BrowserHtmlDebugTrait.php index 95c568afd683fb7ed1489d95162de133ca260386..d71c38caa828cd32b0526c1c527dfea711a54503 100644 --- a/core/tests/Drupal/Tests/BrowserHtmlDebugTrait.php +++ b/core/tests/Drupal/Tests/BrowserHtmlDebugTrait.php @@ -105,6 +105,8 @@ protected function getHtmlOutputHeaders() { * current page content is used. * * @see \Drupal\Tests\Listeners\VerbosePrinter::printResult() + * + * @phpstan-ignore missingType.return */ protected function htmlOutput($message = NULL) { if (!$this->htmlOutputEnabled) { @@ -123,6 +125,8 @@ protected function htmlOutput($message = NULL) { /** * Creates the directory to store browser output. + * + * @phpstan-ignore missingType.return */ protected function initBrowserOutputFile() { $browserOutputFile = getenv('BROWSERTEST_OUTPUT_FILE'); @@ -143,7 +147,7 @@ protected function initBrowserOutputFile() { file_put_contents($this->htmlOutputDirectory . '/.htaccess', "<IfModule mod_expires.c>\nExpiresActive Off\n</IfModule>\n"); } $this->htmlOutputCounterStorage = $this->htmlOutputDirectory . '/' . $this->htmlOutputClassName . '.counter'; - $this->htmlOutputTestId = str_replace('sites/simpletest/', '', $this->siteDirectory); + $this->htmlOutputTestId = str_replace(['sites/simpletest/', 'vfs://root/'], '', $this->siteDirectory); if (is_file($this->htmlOutputCounterStorage)) { $this->htmlOutputCounter = max(1, (int) file_get_contents($this->htmlOutputCounterStorage)) + 1; } diff --git a/core/tests/Drupal/Tests/HttpKernelUiHelperTrait.php b/core/tests/Drupal/Tests/HttpKernelUiHelperTrait.php new file mode 100644 index 0000000000000000000000000000000000000000..1b71ce94be3f358b3c5cbc484bf6fdd353a3d69e --- /dev/null +++ b/core/tests/Drupal/Tests/HttpKernelUiHelperTrait.php @@ -0,0 +1,167 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\Tests; + +use Behat\Mink\Driver\BrowserKitDriver; +use Behat\Mink\Driver\DriverInterface; +use Behat\Mink\Mink; +use Behat\Mink\Selector\SelectorsHandler; +use Behat\Mink\Session; +use Drupal\Core\Test\RefreshVariablesTrait; +use Drupal\Core\Url; +use Symfony\Component\HttpKernel\HttpKernelBrowser; + +/** + * Provides UI helper methods using the HTTP kernel to make requests. + * + * This can be used by Kernel tests. + */ +trait HttpKernelUiHelperTrait { + + use BrowserHtmlDebugTrait; + use RefreshVariablesTrait; + + /** + * Mink session manager. + * + * This is lazily initialized by the first call to self::drupalGet(). + * + * @var \Behat\Mink\Mink|null + */ + protected ?Mink $mink; + + /** + * Retrieves a Drupal path. + * + * Requests are sent to the HTTP kernel. + * + * There is no logged in user. Use \Drupal\Tests\user\Traits\UserCreationTrait + * to set a current user. + * + * There is no active theme. To place blocks, a test must first install a + * theme and then set it as active. + * + * Page caching modules will not function, because the + * CommandLineOrUnsafeMethod caching policy will deny caching. + * + * Some requests may not function correctly because DrupalKernel is not + * explicitly designed to handle multiple requests: see + * https://www.drupal.org/project/drupal/issues/2708827. + * + * @param string|\Drupal\Core\Url $path + * Drupal path or URL to load into Mink controlled browser. + * @param array $options + * (optional) Options to be forwarded to the URL generator. + * @param string[] $headers + * An array containing additional HTTP request headers, the array keys are + * the header names and the array values the header values. This is useful + * to set for example the "Accept-Language" header for requesting the page + * in a different language. Note that not all headers are supported, for + * example the "Accept" header is always overridden by the browser. For + * testing REST APIs it is recommended to obtain a separate HTTP client + * using getHttpClient() and performing requests that way. + * + * @return string + * The retrieved HTML string. + * + * @see \Drupal\Tests\BrowserTestBase::getHttpClient() + */ + protected function drupalGet($path, array $options = [], array $headers = []): string { + // Convert a Url object to a string. + if ($path instanceof Url) { + $url_options = $path->getOptions(); + $options = $url_options + $options; + $path->setOptions($options); + $path = $path->setAbsolute(FALSE)->toString(); + } + + $session = $this->getSession(); + + $session->visit($path); + + $out = $session->getPage()->getContent(); + $this->content = $out; + + // Ensure that any changes to variables in the other thread are picked up. + $this->refreshVariables(); + + if ($this->htmlOutputEnabled) { + $html_output = 'GET request to: ' . $path; + $html_output .= '<hr />' . $out; + $html_output .= $this->getHtmlOutputHeaders(); + $this->htmlOutput($html_output); + } + + return $out; + } + + /** + * Returns Mink session. + * + * @param string $name + * (optional) Name of the session. Defaults to the active session. + * + * @return \Behat\Mink\Session + * The active Mink session object. + */ + public function getSession($name = NULL): Session { + // Lazily initialize the Mink session. We do this because unlike Browser + // tests where there should definitely be requests made, this is not + // necessarily the case with Kernel tests. + if (!isset($this->mink)) { + $this->initMink(); + // Set up the browser test output file. + $this->initBrowserOutputFile(); + } + + return $this->mink->getSession($name); + } + + /** + * Initializes Mink sessions. + */ + protected function initMink(): void { + $driver = $this->getDefaultDriverInstance(); + + $selectors_handler = new SelectorsHandler([ + 'hidden_field_selector' => new HiddenFieldSelector(), + ]); + $session = new Session($driver, $selectors_handler); + $this->mink = new Mink(); + $this->mink->registerSession('default', $session); + $this->mink->setDefaultSessionName('default'); + } + + /** + * Gets an instance of the default Mink driver. + * + * @return \Behat\Mink\Driver\DriverInterface + * Instance of default Mink driver. + * + * @throws \InvalidArgumentException + * When provided default Mink driver class can't be instantiated. + */ + protected function getDefaultDriverInstance(): DriverInterface { + $http_kernel = $this->container->get('http_kernel'); + $browserkit_client = new HttpKernelBrowser($http_kernel); + $driver = new BrowserKitDriver($browserkit_client); + return $driver; + } + + /** + * Returns WebAssert object. + * + * @param string $name + * (optional) Name of the session. Defaults to the active session. + * + * @return \Drupal\Tests\WebAssert + * A new web-assert option for asserting the presence of elements with. + */ + public function assertSession($name = NULL): WebAssert { + $this->addToAssertionCount(1); + return new WebAssert($this->getSession($name)); + } + +}