Initial Drupal 11 with DDEV setup
This commit is contained in:
@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\help\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Verifies help for experimental modules.
|
||||
*
|
||||
* @group help
|
||||
*/
|
||||
class ExperimentalHelpTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* Modules to install.
|
||||
*
|
||||
* The experimental_module_test module implements hook_help() and is in the
|
||||
* Core (Experimental) package.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $modules = [
|
||||
'help',
|
||||
'experimental_module_test',
|
||||
'help_page_test',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* The admin user.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $adminUser;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->adminUser = $this->drupalCreateUser(['access help pages']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that a warning message is displayed for experimental modules.
|
||||
*/
|
||||
public function testExperimentalHelp(): void {
|
||||
$this->drupalLogin($this->adminUser);
|
||||
$this->drupalGet('admin/help/experimental_module_test');
|
||||
$this->assertSession()->statusMessageContains('This module is experimental.', 'warning');
|
||||
|
||||
// Regular modules should not display the message.
|
||||
$this->drupalGet('admin/help/help_page_test');
|
||||
$this->assertSession()->statusMessageNotContains('This module is experimental.');
|
||||
|
||||
// Ensure the actual help page is displayed to avoid a false positive.
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextContains('online documentation for the Help Page Test module');
|
||||
}
|
||||
|
||||
}
|
||||
14
web/core/modules/help/tests/src/Functional/GenericTest.php
Normal file
14
web/core/modules/help/tests/src/Functional/GenericTest.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\help\Functional;
|
||||
|
||||
use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
|
||||
|
||||
/**
|
||||
* Generic module test for help.
|
||||
*
|
||||
* @group help
|
||||
*/
|
||||
class GenericTest extends GenericModuleTestBase {}
|
||||
65
web/core/modules/help/tests/src/Functional/HelpBlockTest.php
Normal file
65
web/core/modules/help/tests/src/Functional/HelpBlockTest.php
Normal file
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\help\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests display of help block.
|
||||
*
|
||||
* @group help
|
||||
*/
|
||||
class HelpBlockTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'help',
|
||||
'help_page_test',
|
||||
'block',
|
||||
'more_help_page_test',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* The help block instance.
|
||||
*
|
||||
* @var \Drupal\block\Entity\Block
|
||||
*/
|
||||
protected $helpBlock;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->helpBlock = $this->placeBlock('help_block');
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs in users, tests help pages.
|
||||
*/
|
||||
public function testHelp(): void {
|
||||
$this->drupalGet('help_page_test/has_help');
|
||||
$this->assertSession()->pageTextContains('I have help!');
|
||||
$this->assertSession()->pageTextContains($this->helpBlock->label());
|
||||
|
||||
$this->drupalGet('help_page_test/no_help');
|
||||
// The help block should not appear when there is no help.
|
||||
$this->assertSession()->pageTextNotContains($this->helpBlock->label());
|
||||
|
||||
// Ensure that if two hook_help() implementations both return a render array
|
||||
// the output is as expected.
|
||||
$this->drupalGet('help_page_test/test_array');
|
||||
$this->assertSession()->pageTextContains('Help text from more_help_page_test_help module.');
|
||||
$this->assertSession()->pageTextContains('Help text from help_page_test_help module.');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\help\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Verify the order of the help page.
|
||||
*
|
||||
* @group help
|
||||
*/
|
||||
class HelpPageOrderTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['help', 'help_page_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Strings to search for on admin/help, in order.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $stringOrder = [
|
||||
'Module overviews are provided',
|
||||
'This description should appear',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Create and log in user.
|
||||
$account = $this->drupalCreateUser([
|
||||
'access help pages',
|
||||
'view the administration theme',
|
||||
'administer permissions',
|
||||
]);
|
||||
$this->drupalLogin($account);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the order of the help page.
|
||||
*/
|
||||
public function testHelp(): void {
|
||||
$pos = 0;
|
||||
$this->drupalGet('admin/help');
|
||||
$page_text = $this->getTextContent();
|
||||
foreach ($this->stringOrder as $item) {
|
||||
$new_pos = strpos($page_text, $item, $pos);
|
||||
$this->assertGreaterThan($pos, $new_pos, "Order of $item is not correct on help page");
|
||||
$pos = $new_pos;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\help\Functional;
|
||||
|
||||
/**
|
||||
* Verify the order of the help page with an alter hook.
|
||||
*
|
||||
* @group help
|
||||
*/
|
||||
class HelpPageReverseOrderTest extends HelpPageOrderTest {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['more_help_page_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Strings to search for on admin/help, in order.
|
||||
*
|
||||
* These are reversed, due to the alter hook.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $stringOrder = [
|
||||
'This description should appear',
|
||||
'Module overviews are provided',
|
||||
];
|
||||
|
||||
}
|
||||
199
web/core/modules/help/tests/src/Functional/HelpTest.php
Normal file
199
web/core/modules/help/tests/src/Functional/HelpTest.php
Normal file
@ -0,0 +1,199 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\help\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Verify help display and user access to help based on permissions.
|
||||
*
|
||||
* @group help
|
||||
*/
|
||||
class HelpTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* Modules to install.
|
||||
*
|
||||
* The help_test module implements hook_help() but does not provide a module
|
||||
* overview page. The help_page_test module has a page section plugin that
|
||||
* returns no links.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $modules = [
|
||||
'block_content',
|
||||
'breakpoint',
|
||||
'editor',
|
||||
'help',
|
||||
'help_page_test',
|
||||
'help_test',
|
||||
'history',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'claro';
|
||||
|
||||
/**
|
||||
* The admin user that will be created.
|
||||
*
|
||||
* @var \Drupal\user\Entity\User|false
|
||||
*/
|
||||
protected $adminUser;
|
||||
|
||||
/**
|
||||
* The anonymous user that will be created.
|
||||
*
|
||||
* @var \Drupal\user\Entity\User|false
|
||||
*/
|
||||
protected $anyUser;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Create users.
|
||||
$this->adminUser = $this->drupalCreateUser([
|
||||
'access help pages',
|
||||
'view the administration theme',
|
||||
'administer permissions',
|
||||
]);
|
||||
$this->anyUser = $this->drupalCreateUser([]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs in users, tests help pages.
|
||||
*/
|
||||
public function testHelp(): void {
|
||||
// Log in the root user to ensure as many admin links appear as possible on
|
||||
// the module overview pages.
|
||||
$this->drupalLogin($this->drupalCreateUser([
|
||||
'access help pages',
|
||||
'access administration pages',
|
||||
]));
|
||||
$this->verifyHelp();
|
||||
|
||||
// Log in the regular user.
|
||||
$this->drupalLogin($this->anyUser);
|
||||
$this->verifyHelp(403);
|
||||
|
||||
// Verify that introductory help text exists, goes for 100% module coverage.
|
||||
$this->drupalLogin($this->adminUser);
|
||||
$this->drupalGet('admin/help');
|
||||
$this->assertSession()->responseContains('For more information, refer to the help listed on this page or to the <a href="https://www.drupal.org/documentation">online documentation</a> and <a href="https://www.drupal.org/support">support</a> pages at <a href="https://www.drupal.org">drupal.org</a>.');
|
||||
|
||||
// Verify that hook_help() section title and description appear.
|
||||
$this->assertSession()->responseContains('<h2>Module overviews</h2>');
|
||||
$this->assertSession()->responseContains('<p>Module overviews are provided by modules. Overviews available for your installed modules:</p>');
|
||||
|
||||
// Verify that an empty section is handled correctly.
|
||||
$this->assertSession()->responseContains('<h2>Empty section</h2>');
|
||||
$this->assertSession()->responseContains('<p>This description should appear.</p>');
|
||||
$this->assertSession()->pageTextContains('There is currently nothing in this section.');
|
||||
|
||||
// Make sure links are properly added for modules implementing hook_help().
|
||||
foreach ($this->getModuleList() as $module => $name) {
|
||||
$this->assertSession()->linkExists($name, 0, "Link properly added to $name (admin/help/$module)");
|
||||
}
|
||||
|
||||
// Ensure a module which does not provide a module overview page is handled
|
||||
// correctly.
|
||||
$module_name = \Drupal::service('extension.list.module')->getName('help_test');
|
||||
$this->clickLink($module_name);
|
||||
$this->assertSession()->pageTextContains('No help is available for module ' . $module_name);
|
||||
|
||||
// Verify that the order of topics is alphabetical by displayed module
|
||||
// name, by checking the order of some modules, including some that would
|
||||
// have a different order if it was done by machine name instead.
|
||||
$this->drupalGet('admin/help');
|
||||
$page_text = $this->getTextContent();
|
||||
$start = strpos($page_text, 'Module overviews');
|
||||
$pos = $start;
|
||||
$list = ['Block', 'Block Content', 'Breakpoint', 'History', 'Text Editor'];
|
||||
foreach ($list as $name) {
|
||||
$this->assertSession()->linkExists($name);
|
||||
$new_pos = strpos($page_text, $name, $start);
|
||||
$this->assertGreaterThan($pos, $new_pos, "Order of $name is not correct on page");
|
||||
$pos = $new_pos;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies the logged in user has access to the various help pages.
|
||||
*
|
||||
* @param int $response
|
||||
* (optional) An HTTP response code. Defaults to 200.
|
||||
*/
|
||||
protected function verifyHelp($response = 200): void {
|
||||
$this->drupalGet('admin/index');
|
||||
$this->assertSession()->statusCodeEquals($response);
|
||||
if ($response == 200) {
|
||||
$this->assertSession()->pageTextContains('This page shows you all available administration tasks for each module.');
|
||||
}
|
||||
else {
|
||||
$this->assertSession()->pageTextNotContains('This page shows you all available administration tasks for each module.');
|
||||
}
|
||||
|
||||
$module_list = \Drupal::service('extension.list.module');
|
||||
foreach ($this->getModuleList() as $module => $name) {
|
||||
// View module help page.
|
||||
$this->drupalGet('admin/help/' . $module);
|
||||
$this->assertSession()->statusCodeEquals($response);
|
||||
if ($response == 200) {
|
||||
$this->assertSession()->titleEquals("$name | Drupal");
|
||||
$this->assertEquals($name, $this->cssSelect('h1.page-title')[0]->getText(), "$module heading was displayed");
|
||||
$info = $module_list->getExtensionInfo($module);
|
||||
$admin_tasks = \Drupal::service('system.module_admin_links_helper')->getModuleAdminLinks($module);
|
||||
if ($module_permissions_link = \Drupal::service('user.module_permissions_link_helper')->getModulePermissionsLink($module, $info['name'])) {
|
||||
$admin_tasks["user.admin_permissions.{$module}"] = $module_permissions_link;
|
||||
}
|
||||
if (!empty($admin_tasks)) {
|
||||
$this->assertSession()->pageTextContains($name . ' administration pages');
|
||||
}
|
||||
foreach ($admin_tasks as $task) {
|
||||
$this->assertSession()->linkExists($task['title']);
|
||||
// Ensure there are no double escaped '&' or '<' characters.
|
||||
$this->assertSession()->assertNoEscaped('&');
|
||||
$this->assertSession()->assertNoEscaped('<');
|
||||
// Ensure there are no escaped '<' characters.
|
||||
$this->assertSession()->assertNoEscaped('<');
|
||||
}
|
||||
// Ensure there are no double escaped '&' or '<' characters.
|
||||
$this->assertSession()->assertNoEscaped('&');
|
||||
$this->assertSession()->assertNoEscaped('<');
|
||||
|
||||
// The help for CKEditor 5 intentionally has escaped '<' so leave this
|
||||
// iteration before the assertion below.
|
||||
if ($module === 'ckeditor5') {
|
||||
continue;
|
||||
}
|
||||
// Ensure there are no escaped '<' characters.
|
||||
$this->assertSession()->assertNoEscaped('<');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of enabled modules that implement hook_help().
|
||||
*
|
||||
* @return array
|
||||
* A list of enabled modules.
|
||||
*/
|
||||
protected function getModuleList() {
|
||||
$modules = [];
|
||||
$module_data = $this->container->get('extension.list.module')->getList();
|
||||
\Drupal::moduleHandler()->invokeAllWith(
|
||||
'help',
|
||||
function (callable $hook, string $module) use (&$modules, $module_data) {
|
||||
$modules[$module] = $module_data[$module]->info['name'];
|
||||
}
|
||||
);
|
||||
return $modules;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,311 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\help\Functional;
|
||||
|
||||
use Drupal\Tests\Traits\Core\CronRunTrait;
|
||||
use Drupal\help\Plugin\Search\HelpSearch;
|
||||
|
||||
// cspell:ignore asdrsad barmm foomm hilfetestmodul sdeeeee sqruct testen
|
||||
// cspell:ignore wcsrefsdf übersetzung
|
||||
|
||||
/**
|
||||
* Verifies help topic search.
|
||||
*
|
||||
* @group help
|
||||
*/
|
||||
class HelpTopicSearchTest extends HelpTopicTranslatedTestBase {
|
||||
|
||||
use CronRunTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'search',
|
||||
'locale',
|
||||
'language',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Log in.
|
||||
$this->drupalLogin($this->createUser([
|
||||
'access help pages',
|
||||
'administer site configuration',
|
||||
'view the administration theme',
|
||||
'administer permissions',
|
||||
'administer languages',
|
||||
'administer search',
|
||||
'access test help',
|
||||
'search content',
|
||||
]));
|
||||
|
||||
// Add English language and set to default.
|
||||
$this->drupalGet('admin/config/regional/language/add');
|
||||
$this->submitForm([
|
||||
'predefined_langcode' => 'en',
|
||||
], 'Add language');
|
||||
$this->drupalGet('admin/config/regional/language');
|
||||
$this->submitForm([
|
||||
'site_default_language' => 'en',
|
||||
], 'Save configuration');
|
||||
// When default language is changed, the container is rebuilt in the child
|
||||
// site, so a rebuild in the main site is required to use the new container
|
||||
// here.
|
||||
$this->rebuildContainer();
|
||||
|
||||
// Before running cron, verify that a search returns no results and shows
|
||||
// warning.
|
||||
$this->drupalGet('search/help');
|
||||
$this->submitForm(['keys' => 'not-a-word-english'], 'Search');
|
||||
$this->assertSearchResultsCount(0);
|
||||
$this->assertSession()->statusMessageContains('Help search is not fully indexed', 'warning');
|
||||
|
||||
// Run cron until the topics are fully indexed, with a limit of 100 runs
|
||||
// to avoid infinite loops.
|
||||
$num_runs = 100;
|
||||
$plugin = HelpSearch::create($this->container, [], 'help_search', []);
|
||||
do {
|
||||
$this->cronRun();
|
||||
$remaining = $plugin->indexStatus()['remaining'];
|
||||
} while (--$num_runs && $remaining);
|
||||
$this->assertNotEmpty($num_runs);
|
||||
$this->assertEmpty($remaining);
|
||||
|
||||
// Visit the Search settings page and verify it says 100% indexed.
|
||||
$this->drupalGet('admin/config/search/pages');
|
||||
$this->assertSession()->pageTextContains('100% of the site has been indexed');
|
||||
// Search and verify there is no warning.
|
||||
$this->drupalGet('search/help');
|
||||
$this->submitForm(['keys' => 'not-a-word-english'], 'Search');
|
||||
$this->assertSearchResultsCount(1);
|
||||
$this->assertSession()->statusMessageNotContains('Help search is not fully indexed');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests help topic search.
|
||||
*/
|
||||
public function testHelpSearch(): void {
|
||||
$german = \Drupal::languageManager()->getLanguage('de');
|
||||
$session = $this->assertSession();
|
||||
|
||||
// Verify that when we search in English for a word that is only in
|
||||
// English text, we find the topic. Note that these "words" are provided
|
||||
// by the topics that come from
|
||||
// \Drupal\help_topics_test\Plugin\HelpSection\TestHelpSection.
|
||||
$this->drupalGet('search/help');
|
||||
$this->submitForm(['keys' => 'not-a-word-english'], 'Search');
|
||||
$this->assertSearchResultsCount(1);
|
||||
$session->linkExists('Foo in English title wcsrefsdf');
|
||||
|
||||
// Same for German.
|
||||
$this->drupalGet('search/help', ['language' => $german]);
|
||||
$this->submitForm(['keys' => 'not-a-word-german'], 'Search');
|
||||
$this->assertSearchResultsCount(1);
|
||||
$session->linkExists('Foomm Foreign heading');
|
||||
|
||||
// Verify when we search in English for a word that only exists in German,
|
||||
// we get no results.
|
||||
$this->drupalGet('search/help');
|
||||
$this->submitForm(['keys' => 'not-a-word-german'], 'Search');
|
||||
$this->assertSearchResultsCount(0);
|
||||
$session->pageTextContains('no results');
|
||||
|
||||
// Same for German.
|
||||
$this->drupalGet('search/help', ['language' => $german]);
|
||||
$this->submitForm(['keys' => 'not-a-word-english'], 'Search');
|
||||
$this->assertSearchResultsCount(0);
|
||||
$session->pageTextContains('no results');
|
||||
|
||||
// Verify when we search in English for a word that exists in one topic
|
||||
// in English and a different topic in German, we only get the one English
|
||||
// topic.
|
||||
$this->drupalGet('search/help');
|
||||
$this->submitForm(['keys' => 'sqruct'], 'Search');
|
||||
$this->assertSearchResultsCount(1);
|
||||
$session->linkExists('Foo in English title wcsrefsdf');
|
||||
|
||||
// Same for German.
|
||||
$this->drupalGet('search/help', ['language' => $german]);
|
||||
$this->submitForm(['keys' => 'asdrsad'], 'Search');
|
||||
$this->assertSearchResultsCount(1);
|
||||
$session->linkExists('Foomm Foreign heading');
|
||||
|
||||
// All of the above tests used the TestHelpSection plugin. Also verify
|
||||
// that we can search for translated regular help topics, in both English
|
||||
// and German.
|
||||
$this->drupalGet('search/help');
|
||||
$this->submitForm(['keys' => 'non-word-item'], 'Search');
|
||||
$this->assertSearchResultsCount(1);
|
||||
$session->linkExists('ABC Help Test module');
|
||||
// Click the link and verify we ended up on the topic page.
|
||||
$this->clickLink('ABC Help Test module');
|
||||
$session->pageTextContains('This is a test');
|
||||
|
||||
$this->drupalGet('search/help', ['language' => $german]);
|
||||
$this->submitForm(['keys' => 'non-word-german'], 'Search');
|
||||
$this->assertSearchResultsCount(1);
|
||||
$session->linkExists('ABC-Hilfetestmodul');
|
||||
$this->clickLink('ABC-Hilfetestmodul');
|
||||
$session->pageTextContains('Übersetzung testen.');
|
||||
|
||||
// Verify that we can search from the admin/help page.
|
||||
$this->drupalGet('admin/help');
|
||||
$session->pageTextContains('Search help');
|
||||
$this->submitForm(['keys' => 'non-word-item'], 'Search');
|
||||
$this->assertSearchResultsCount(1);
|
||||
$session->linkExists('ABC Help Test module');
|
||||
|
||||
// Same for German.
|
||||
$this->drupalGet('admin/help', ['language' => $german]);
|
||||
$this->submitForm(['keys' => 'non-word-german'], 'Search');
|
||||
$this->assertSearchResultsCount(1);
|
||||
$session->linkExists('ABC-Hilfetestmodul');
|
||||
|
||||
// Verify we can search for title text (other searches used text
|
||||
// that was part of the body).
|
||||
$this->drupalGet('search/help');
|
||||
$this->submitForm(['keys' => 'wcsrefsdf'], 'Search');
|
||||
$this->assertSearchResultsCount(1);
|
||||
$session->linkExists('Foo in English title wcsrefsdf');
|
||||
|
||||
$this->drupalGet('admin/help', ['language' => $german]);
|
||||
$this->submitForm(['keys' => 'sdeeeee'], 'Search');
|
||||
$this->assertSearchResultsCount(1);
|
||||
$session->linkExists('Barmm Foreign sdeeeee');
|
||||
|
||||
// Just changing the title and running cron is not enough to reindex so
|
||||
// 'sdeeeee' still hits a match. The content is updated because the help
|
||||
// topic is rendered each time.
|
||||
\Drupal::state()->set('help_topics_test:translated_title', 'Updated translated title');
|
||||
$this->cronRun();
|
||||
$this->drupalGet('admin/help', ['language' => $german]);
|
||||
$this->submitForm(['keys' => 'sdeeeee'], 'Search');
|
||||
$this->assertSearchResultsCount(1);
|
||||
$session->linkExists('Updated translated title');
|
||||
// Searching for the updated test shouldn't produce a match.
|
||||
$this->drupalGet('admin/help', ['language' => $german]);
|
||||
$this->submitForm(['keys' => 'translated title'], 'Search');
|
||||
$this->assertSearchResultsCount(0);
|
||||
|
||||
// Clear the caches and re-run cron - this should re-index the help.
|
||||
$this->rebuildAll();
|
||||
$this->cronRun();
|
||||
$this->drupalGet('admin/help', ['language' => $german]);
|
||||
$this->submitForm(['keys' => 'sdeeeee'], 'Search');
|
||||
$this->assertSearchResultsCount(0);
|
||||
$this->drupalGet('admin/help', ['language' => $german]);
|
||||
$this->submitForm(['keys' => 'translated title'], 'Search');
|
||||
$this->assertSearchResultsCount(1);
|
||||
$session->linkExists('Updated translated title');
|
||||
|
||||
// Verify the cache tags and contexts.
|
||||
$session->responseHeaderContains('X-Drupal-Cache-Tags', 'config:search.page.help_search');
|
||||
$session->responseHeaderContains('X-Drupal-Cache-Tags', 'search_index:help_search');
|
||||
$session->responseHeaderContains('X-Drupal-Cache-Contexts', 'user.permissions');
|
||||
$session->responseHeaderContains('X-Drupal-Cache-Contexts', 'languages:language_interface');
|
||||
|
||||
// Log in as a user that does not have permission to see TestHelpSection
|
||||
// items, and verify they can still search for help topics but not see these
|
||||
// items.
|
||||
$this->drupalLogin($this->createUser([
|
||||
'access help pages',
|
||||
'administer site configuration',
|
||||
'view the administration theme',
|
||||
'administer permissions',
|
||||
'administer languages',
|
||||
'administer search',
|
||||
'search content',
|
||||
]));
|
||||
|
||||
$this->drupalGet('admin/help');
|
||||
$session->pageTextContains('Search help');
|
||||
|
||||
$this->drupalGet('search/help');
|
||||
$this->submitForm(['keys' => 'non-word-item'], 'Search');
|
||||
$this->assertSearchResultsCount(1);
|
||||
$session->linkExists('ABC Help Test module');
|
||||
|
||||
$this->drupalGet('search/help');
|
||||
$this->submitForm(['keys' => 'not-a-word-english'], 'Search');
|
||||
$this->assertSearchResultsCount(0);
|
||||
$session->pageTextContains('no results');
|
||||
|
||||
// Uninstall the test module and verify its topics are immediately not
|
||||
// searchable.
|
||||
\Drupal::service('module_installer')->uninstall(['help_topics_test']);
|
||||
$this->drupalGet('search/help');
|
||||
$this->submitForm(['keys' => 'non-word-item'], 'Search');
|
||||
$this->assertSearchResultsCount(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests uninstalling the help_topics module.
|
||||
*/
|
||||
public function testUninstall(): void {
|
||||
\Drupal::service('module_installer')->uninstall(['help_topics_test']);
|
||||
// Ensure we can uninstall help_topics and use the help system without
|
||||
// breaking.
|
||||
$this->drupalLogin($this->createUser([
|
||||
'administer modules',
|
||||
'access help pages',
|
||||
]));
|
||||
$edit = [];
|
||||
$edit['uninstall[help]'] = TRUE;
|
||||
$this->drupalGet('admin/modules/uninstall');
|
||||
$this->submitForm($edit, 'Uninstall');
|
||||
$this->submitForm([], 'Uninstall');
|
||||
$this->assertSession()->statusMessageContains('The selected modules have been uninstalled.', 'status');
|
||||
$this->drupalGet('admin/help');
|
||||
$this->assertSession()->statusCodeEquals(404);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests uninstalling the search module.
|
||||
*/
|
||||
public function testUninstallSearch(): void {
|
||||
// Ensure we can uninstall search and use the help system without
|
||||
// breaking.
|
||||
$this->drupalLogin($this->createUser([
|
||||
'administer modules',
|
||||
'access help pages',
|
||||
]));
|
||||
$edit = [];
|
||||
$edit['uninstall[search]'] = TRUE;
|
||||
$this->drupalGet('admin/modules/uninstall');
|
||||
$this->submitForm($edit, 'Uninstall');
|
||||
$this->submitForm([], 'Uninstall');
|
||||
$this->assertSession()->statusMessageContains('The selected modules have been uninstalled.', 'status');
|
||||
$this->drupalGet('admin/help');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
|
||||
// Rebuild the container to reflect the latest changes.
|
||||
$this->rebuildContainer();
|
||||
$this->assertTrue(\Drupal::moduleHandler()->moduleExists('help'), 'The help module is still installed.');
|
||||
$this->assertFalse(\Drupal::moduleHandler()->moduleExists('search'), 'The search module is uninstalled.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that help search returned the expected number of results.
|
||||
*
|
||||
* @param int $count
|
||||
* The expected number of search results.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function assertSearchResultsCount(int $count): void {
|
||||
$this->assertSession()->elementsCount('css', '.help_search-results > li', $count);
|
||||
}
|
||||
|
||||
}
|
||||
296
web/core/modules/help/tests/src/Functional/HelpTopicTest.php
Normal file
296
web/core/modules/help/tests/src/Functional/HelpTopicTest.php
Normal file
@ -0,0 +1,296 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\help\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\Tests\system\Functional\Menu\AssertBreadcrumbTrait;
|
||||
|
||||
/**
|
||||
* Verifies help topic display and user access to help based on permissions.
|
||||
*
|
||||
* @group help
|
||||
*/
|
||||
class HelpTopicTest extends BrowserTestBase {
|
||||
use AssertBreadcrumbTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'help_topics_test',
|
||||
'help',
|
||||
'block',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* The admin user that will be created.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $adminUser;
|
||||
|
||||
/**
|
||||
* The user who can see help but not the special route.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $noTestUser;
|
||||
|
||||
/**
|
||||
* The anonymous user that will be created.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $anyUser;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// These tests rely on some markup from the 'stark' theme and we test theme
|
||||
// provided help topics.
|
||||
\Drupal::service('theme_installer')->install(['help_topics_test_theme']);
|
||||
|
||||
// Place various blocks.
|
||||
$settings = [
|
||||
'theme' => 'stark',
|
||||
'region' => 'help',
|
||||
];
|
||||
$this->placeBlock('help_block', $settings);
|
||||
$this->placeBlock('local_tasks_block', $settings);
|
||||
$this->placeBlock('local_actions_block', $settings);
|
||||
$this->placeBlock('page_title_block', $settings);
|
||||
$this->placeBlock('system_breadcrumb_block', $settings);
|
||||
|
||||
// Create users.
|
||||
$this->adminUser = $this->createUser([
|
||||
'access administration pages',
|
||||
'access help pages',
|
||||
'view the administration theme',
|
||||
'administer permissions',
|
||||
'administer site configuration',
|
||||
'access test help',
|
||||
]);
|
||||
|
||||
$this->noTestUser = $this->createUser([
|
||||
'access help pages',
|
||||
'view the administration theme',
|
||||
'administer permissions',
|
||||
'administer site configuration',
|
||||
]);
|
||||
|
||||
$this->anyUser = $this->createUser([]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the main help page and individual pages for topics.
|
||||
*/
|
||||
public function testHelp(): void {
|
||||
$session = $this->assertSession();
|
||||
|
||||
// Log in the regular user.
|
||||
$this->drupalLogin($this->anyUser);
|
||||
$this->verifyHelp(403);
|
||||
|
||||
// Log in the admin user.
|
||||
$this->drupalLogin($this->adminUser);
|
||||
$this->verifyHelp();
|
||||
$this->verifyBreadCrumb();
|
||||
|
||||
// Verify that help topics text appears on admin/help, and cache tags.
|
||||
$this->drupalGet('admin/help');
|
||||
$session->responseContains('<h2>Topics</h2>');
|
||||
$session->pageTextContains('Topics can be provided by modules or themes');
|
||||
$session->responseHeaderContains('X-Drupal-Cache-Tags', 'core.extension');
|
||||
|
||||
// Verify links for help topics and order.
|
||||
$page_text = $this->getTextContent();
|
||||
$start = strpos($page_text, 'Topics can be provided');
|
||||
$pos = $start;
|
||||
foreach ($this->getTopicList() as $info) {
|
||||
$name = $info['name'];
|
||||
$session->linkExists($name);
|
||||
$new_pos = strpos($page_text, $name, $start);
|
||||
$this->assertGreaterThan($pos, $new_pos, "Order of $name is not correct on page");
|
||||
$pos = $new_pos;
|
||||
}
|
||||
|
||||
// Ensure the plugin manager alter hook works as expected.
|
||||
$session->linkExists('ABC Help Test module');
|
||||
\Drupal::state()->set('help_topics_test.test:top_level', FALSE);
|
||||
\Drupal::service('plugin.manager.help_topic')->clearCachedDefinitions();
|
||||
$this->drupalGet('admin/help');
|
||||
$session->linkNotExists('ABC Help Test module');
|
||||
\Drupal::state()->set('help_topics_test.test:top_level', TRUE);
|
||||
\Drupal::service('plugin.manager.help_topic')->clearCachedDefinitions();
|
||||
$this->drupalGet('admin/help');
|
||||
|
||||
// Ensure all the expected links are present before uninstalling.
|
||||
$session->linkExists('ABC Help Test module');
|
||||
$session->linkExists('ABC Help Test');
|
||||
$session->linkExists('XYZ Help Test theme');
|
||||
|
||||
// Uninstall the test module and verify the topics are gone, after
|
||||
// reloading page.
|
||||
$this->container->get('module_installer')->uninstall(['help_topics_test']);
|
||||
$this->drupalGet('admin/help');
|
||||
$session->linkNotExists('ABC Help Test module');
|
||||
$session->linkNotExists('ABC Help Test');
|
||||
$session->linkExists('XYZ Help Test theme');
|
||||
|
||||
// Uninstall the test theme and verify the topic is gone.
|
||||
$this->container->get('theme_installer')->uninstall(['help_topics_test_theme']);
|
||||
$this->drupalGet('admin/help');
|
||||
$session->linkNotExists('XYZ Help Test theme');
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies the logged in user has access to various help links and pages.
|
||||
*
|
||||
* @param int $response
|
||||
* (optional) The HTTP response code to test for. If it's 200 (default),
|
||||
* the test verifies the user sees the help; if it's not, it verifies they
|
||||
* are denied access.
|
||||
*/
|
||||
protected function verifyHelp($response = 200): void {
|
||||
// Verify access to help topic pages.
|
||||
foreach ($this->getTopicList() as $topic => $info) {
|
||||
// View help topic page.
|
||||
$this->drupalGet('admin/help/topic/' . $topic);
|
||||
$session = $this->assertSession();
|
||||
$session->statusCodeEquals($response);
|
||||
if ($response == 200) {
|
||||
// Verify page information.
|
||||
$name = $info['name'];
|
||||
$session->titleEquals($name . ' | Drupal');
|
||||
$session->responseContains('<h1>' . $name . '</h1>');
|
||||
foreach ($info['tags'] as $tag) {
|
||||
$session->responseHeaderContains('X-Drupal-Cache-Tags', $tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies links on various topic pages.
|
||||
*/
|
||||
public function testHelpLinks(): void {
|
||||
$session = $this->assertSession();
|
||||
$this->drupalLogin($this->adminUser);
|
||||
|
||||
// Verify links on the test top-level page.
|
||||
$page = 'admin/help/topic/help_topics_test.test';
|
||||
// Array element is the page text if you click through.
|
||||
$links = [
|
||||
'Linked topic' => 'This topic is not supposed to be top-level',
|
||||
'Additional topic' => 'This topic should get listed automatically',
|
||||
'URL test topic' => 'It is used to test URLs',
|
||||
];
|
||||
foreach ($links as $link_text => $page_text) {
|
||||
$this->drupalGet($page);
|
||||
$this->clickLink($link_text);
|
||||
$session->pageTextContains($page_text);
|
||||
}
|
||||
|
||||
// Verify theme provided help topics work and can be related.
|
||||
$this->drupalGet('admin/help/topic/help_topics_test_theme.test');
|
||||
$session->pageTextContains('This is a theme provided topic.');
|
||||
$this->assertStringContainsString('This is a theme provided topic.', $session->elementExists('css', 'article')->getText());
|
||||
$this->clickLink('Additional topic');
|
||||
$session->linkExists('XYZ Help Test theme');
|
||||
|
||||
// Verify that the non-top-level topics do not appear on the Help page.
|
||||
$this->drupalGet('admin/help');
|
||||
$session->linkNotExists('Linked topic');
|
||||
$session->linkNotExists('Additional topic');
|
||||
|
||||
// Verify links and non-links on the URL test page.
|
||||
$this->drupalGet('admin/help/topic/help_topics_test.test_urls');
|
||||
$links = [
|
||||
'not a route' => FALSE,
|
||||
'missing params' => FALSE,
|
||||
'invalid params' => FALSE,
|
||||
'valid link' => TRUE,
|
||||
'Additional topic' => TRUE,
|
||||
'Missing help topic not_a_topic' => FALSE,
|
||||
];
|
||||
foreach ($links as $text => $should_be_link) {
|
||||
if ($should_be_link) {
|
||||
$session->linkExists($text);
|
||||
}
|
||||
else {
|
||||
// Should be text that is not a link.
|
||||
$session->pageTextContains($text);
|
||||
$session->linkNotExists($text);
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that the "no test" user, who should not be able to access
|
||||
// the 'valid link' URL, sees it as not a link.
|
||||
$this->drupalLogin($this->noTestUser);
|
||||
$this->drupalGet('admin/help/topic/help_topics_test.test_urls');
|
||||
$session->pageTextContains('valid link');
|
||||
$session->linkNotExists('valid link');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of topic IDs to test.
|
||||
*
|
||||
* @return array
|
||||
* A list of topics to test, in the order in which they should appear. The
|
||||
* keys are the machine names of the topics. The values are arrays with the
|
||||
* following elements:
|
||||
* - name: Displayed name.
|
||||
* - tags: Cache tags to test for.
|
||||
*/
|
||||
protected function getTopicList() {
|
||||
return [
|
||||
'help_topics_test.test' => [
|
||||
'name' => 'ABC Help Test module',
|
||||
'tags' => ['core.extension'],
|
||||
],
|
||||
'help_topics_derivatives:test_derived_topic' => [
|
||||
'name' => 'Label for test_derived_topic',
|
||||
'tags' => ['foobar'],
|
||||
],
|
||||
'help_topics_test_direct_yml' => [
|
||||
'name' => 'Test direct yaml topic label',
|
||||
'tags' => ['foobar'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests breadcrumb on a help topic page.
|
||||
*/
|
||||
public function verifyBreadCrumb(): void {
|
||||
// Verify Help Topics administration breadcrumbs.
|
||||
$trail = [
|
||||
'' => 'Home',
|
||||
'admin' => 'Administration',
|
||||
'admin/help' => 'Help',
|
||||
];
|
||||
$this->assertBreadcrumb('admin/help/topic/help_topics_test.test', $trail);
|
||||
// Ensure we are on the expected help topic page.
|
||||
$this->assertSession()->pageTextContains('Also there should be a related topic link below to the Help module topic page and the linked topic.');
|
||||
|
||||
// Verify that another page does not have the help breadcrumb.
|
||||
$trail = [
|
||||
'' => 'Home',
|
||||
'admin' => 'Administration',
|
||||
'admin/config' => 'Configuration',
|
||||
'admin/config/system' => 'System',
|
||||
];
|
||||
$this->assertBreadcrumb('admin/config/system/site-information', $trail);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\help\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
// cspell:ignore hilfetestmodul übersetzung
|
||||
|
||||
/**
|
||||
* Provides a base class for functional help topic tests that use translation.
|
||||
*
|
||||
* Installs in German, with a small PO file, and sets up the task, help, and
|
||||
* page title blocks.
|
||||
*/
|
||||
abstract class HelpTopicTranslatedTestBase extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'help_topics_test',
|
||||
'help',
|
||||
'block',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// These tests rely on some markup from the 'Claro' theme, as well as an
|
||||
// optional block added when Claro is enabled.
|
||||
\Drupal::service('theme_installer')->install(['claro']);
|
||||
\Drupal::configFactory()->getEditable('system.theme')
|
||||
->set('admin', 'claro')
|
||||
->save(TRUE);
|
||||
|
||||
// Place various blocks.
|
||||
$settings = [
|
||||
'theme' => 'claro',
|
||||
'region' => 'help',
|
||||
];
|
||||
$this->placeBlock('help_block', $settings);
|
||||
$this->placeBlock('local_tasks_block', $settings);
|
||||
$this->placeBlock('local_actions_block', $settings);
|
||||
$this->placeBlock('page_title_block', $settings);
|
||||
|
||||
// Create user.
|
||||
$this->drupalLogin($this->createUser([
|
||||
'access help pages',
|
||||
'view the administration theme',
|
||||
'administer permissions',
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function installParameters() {
|
||||
$parameters = parent::installParameters();
|
||||
// Install in German. This will ensure the language and locale modules are
|
||||
// installed.
|
||||
$parameters['parameters']['langcode'] = 'de';
|
||||
// Create a po file so we don't attempt to download one from
|
||||
// localize.drupal.org and to have a test translation that will not change.
|
||||
\Drupal::service('file_system')->mkdir($this->publicFilesDirectory . '/translations', NULL, TRUE);
|
||||
$contents = <<<PO
|
||||
msgid ""
|
||||
msgstr ""
|
||||
|
||||
msgid "ABC Help Test module"
|
||||
msgstr "ABC-Hilfetestmodul"
|
||||
|
||||
msgid "Test translation."
|
||||
msgstr "Übersetzung testen."
|
||||
|
||||
msgid "Non-word-item to translate."
|
||||
msgstr "Non-word-german sdfwedrsdf."
|
||||
|
||||
PO;
|
||||
$version = explode('.', \Drupal::VERSION)[0] . '.0.0';
|
||||
file_put_contents($this->publicFilesDirectory . "/translations/drupal-{$version}.de.po", $contents);
|
||||
return $parameters;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\help\Functional;
|
||||
|
||||
// cspell:ignore hilfetestmodul testen übersetzung
|
||||
|
||||
/**
|
||||
* Verifies help topic translations.
|
||||
*
|
||||
* @group help
|
||||
*/
|
||||
class HelpTopicTranslationTest extends HelpTopicTranslatedTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Create user and log in.
|
||||
$this->drupalLogin($this->createUser([
|
||||
'access help pages',
|
||||
'view the administration theme',
|
||||
'administer permissions',
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests help topic translations.
|
||||
*/
|
||||
public function testHelpTopicTranslations(): void {
|
||||
$session = $this->assertSession();
|
||||
|
||||
// Verify that help topic link is translated on admin/help.
|
||||
$this->drupalGet('admin/help');
|
||||
$session->linkExists('ABC-Hilfetestmodul');
|
||||
// Verify that the language cache tag appears on admin/help.
|
||||
$session->responseHeaderContains('X-Drupal-Cache-Contexts', 'languages:language_interface');
|
||||
// Verify that help topic is translated.
|
||||
$this->drupalGet('admin/help/topic/help_topics_test.test');
|
||||
$session->pageTextContains('ABC-Hilfetestmodul');
|
||||
$session->pageTextContains('Übersetzung testen.');
|
||||
// Verify that the language cache tag appears on a topic page.
|
||||
$session->responseHeaderContains('X-Drupal-Cache-Contexts', 'languages:language_interface');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,353 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\help\Functional;
|
||||
|
||||
use Drupal\Core\Extension\ExtensionLifecycle;
|
||||
use Drupal\Component\FrontMatter\FrontMatter;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\help\HelpTopicDiscovery;
|
||||
use Drupal\help_topics_twig_tester\HelpTestTwigNodeVisitor;
|
||||
use PHPUnit\Framework\ExpectationFailedException;
|
||||
use PHPUnit\Framework\AssertionFailedError;
|
||||
|
||||
/**
|
||||
* Verifies that all core Help topics can be rendered and comply with standards.
|
||||
*
|
||||
* @group help
|
||||
* @group #slow
|
||||
*/
|
||||
class HelpTopicsSyntaxTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'help',
|
||||
'help_topics_twig_tester',
|
||||
'locale',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Tests that all Core help topics can be rendered and have good syntax.
|
||||
*/
|
||||
public function testHelpTopics(): void {
|
||||
$this->drupalLogin($this->createUser([
|
||||
'administer modules',
|
||||
'access help pages',
|
||||
]));
|
||||
|
||||
// Enable all modules and themes, so that all routes mentioned in topics
|
||||
// will be defined.
|
||||
$module_directories = $this->listDirectories('module');
|
||||
$modules_to_install = array_keys($module_directories);
|
||||
\Drupal::service('module_installer')->install($modules_to_install);
|
||||
$theme_directories = $this->listDirectories('theme');
|
||||
\Drupal::service('theme_installer')->install(array_keys($theme_directories));
|
||||
|
||||
$directories = $module_directories + $theme_directories +
|
||||
$this->listDirectories('profile');
|
||||
$directories['core'] = \Drupal::root() . '/core/help_topics';
|
||||
$directories['bad_help_topics'] = \Drupal::service('extension.list.module')->getPath('help_topics_test') . '/bad_help_topics/syntax/';
|
||||
|
||||
// Filter out directories outside of core. If you want to run this test
|
||||
// on a contrib/custom module, remove the next line.
|
||||
$directories = array_filter($directories, function ($directory) {
|
||||
return str_starts_with($directory, 'core');
|
||||
});
|
||||
|
||||
// Verify that a few key modules, themes, and profiles are listed, so that
|
||||
// we can be certain our directory list is complete and we will be testing
|
||||
// all existing help topics. If these lines in the test fail in the future,
|
||||
// it is probably because something we chose to list here is being removed.
|
||||
// Substitute another item of the same type that still exists, so that this
|
||||
// test can continue.
|
||||
$this->assertArrayHasKey('system', $directories, 'System module is being scanned');
|
||||
$this->assertArrayHasKey('help', $directories, 'Help module is being scanned');
|
||||
$this->assertArrayHasKey('claro', $directories, 'Claro theme is being scanned');
|
||||
$this->assertArrayHasKey('standard', $directories, 'Standard profile is being scanned');
|
||||
|
||||
$definitions = (new HelpTopicDiscovery($directories))->getDefinitions();
|
||||
$this->assertGreaterThan(0, count($definitions), 'At least 1 topic was found');
|
||||
|
||||
// Test each topic for compliance with standards, or for failing in the
|
||||
// right way.
|
||||
foreach (array_keys($definitions) as $id) {
|
||||
if (str_starts_with($id, 'bad_help_topics.')) {
|
||||
$this->verifyBadTopic($id, $definitions);
|
||||
}
|
||||
else {
|
||||
$this->verifyTopic($id, $definitions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies rendering and standards compliance of one help topic.
|
||||
*
|
||||
* @param string $id
|
||||
* ID of the topic to verify.
|
||||
* @param array $definitions
|
||||
* Array of all topic definitions, keyed by ID.
|
||||
* @param int $response
|
||||
* Expected response from visiting the page for the topic.
|
||||
*/
|
||||
protected function verifyTopic($id, $definitions, $response = 200): void {
|
||||
$definition = $definitions[$id];
|
||||
HelpTestTwigNodeVisitor::setStateValue('manner', 0);
|
||||
|
||||
// Visit the URL for the topic.
|
||||
$this->drupalGet('admin/help/topic/' . $id);
|
||||
|
||||
// Verify the title and response.
|
||||
$session = $this->assertSession();
|
||||
$session->statusCodeEquals($response);
|
||||
if ($response == 200) {
|
||||
$session->titleEquals($definition['label'] . ' | Drupal');
|
||||
}
|
||||
|
||||
// Verify that all the related topics exist. Also check to see if any of
|
||||
// them are top-level (we will need that in the next section).
|
||||
$has_top_level_related = FALSE;
|
||||
if (isset($definition['related'])) {
|
||||
foreach ($definition['related'] as $related_id) {
|
||||
$this->assertArrayHasKey($related_id, $definitions, 'Topic ' . $id . ' is only related to topics that exist: ' . $related_id);
|
||||
$has_top_level_related = $has_top_level_related || !empty($definitions[$related_id]['top_level']);
|
||||
}
|
||||
}
|
||||
|
||||
// Verify this is either top-level or related to a top-level topic.
|
||||
$this->assertTrue(!empty($definition['top_level']) || $has_top_level_related, 'Topic ' . $id . ' is either top-level or related to at least one other top-level topic');
|
||||
|
||||
// Verify that the label is not empty.
|
||||
$this->assertNotEmpty($definition['label'], 'Topic ' . $id . ' has a non-empty label');
|
||||
|
||||
// Test the syntax and contents of the Twig file (without the front
|
||||
// matter, which is tested in other ways above). We need to render the
|
||||
// template several times with variations, so read it in once.
|
||||
$template = file_get_contents($definition[HelpTopicDiscovery::FILE_KEY]);
|
||||
$template_text = FrontMatter::create($template)->getContent();
|
||||
|
||||
// Verify that the body is not empty and is valid HTML.
|
||||
$text = $this->renderHelpTopic($template_text, 'bare_body');
|
||||
$this->assertNotEmpty($text, 'Topic ' . $id . ' contains some text outside of front matter');
|
||||
$this->validateHtml($text, $id);
|
||||
$max_chunk_num = HelpTestTwigNodeVisitor::getState()['max_chunk'];
|
||||
$this->assertTrue($max_chunk_num >= 0, 'Topic ' . $id . ' has at least one translated chunk');
|
||||
|
||||
// Verify that each chunk of the translated text is locale-safe and
|
||||
// valid HTML.
|
||||
$chunk_num = 0;
|
||||
$number_checked = 0;
|
||||
while ($chunk_num <= $max_chunk_num) {
|
||||
$chunk_str = $id . ' section ' . $chunk_num;
|
||||
|
||||
// Render the topic, asking for just one chunk, and extract the chunk.
|
||||
// Note that some chunks may not actually get rendered, if they are inside
|
||||
// set statements, because we skip rendering variable output.
|
||||
HelpTestTwigNodeVisitor::setStateValue('return_chunk', $chunk_num);
|
||||
$text = $this->renderHelpTopic($template_text, 'translated_chunk');
|
||||
$matches = [];
|
||||
$matched = preg_match('|' . HelpTestTwigNodeVisitor::DELIMITER . '(.*)' . HelpTestTwigNodeVisitor::DELIMITER . '|', $text, $matches);
|
||||
if ($matched) {
|
||||
$number_checked++;
|
||||
$text = $matches[1];
|
||||
$this->assertNotEmpty($text, 'Topic ' . $chunk_str . ' contains text');
|
||||
|
||||
// Verify the chunk is OK.
|
||||
$this->assertTrue(locale_string_is_safe($text), 'Topic ' . $chunk_str . ' translatable string is locale-safe');
|
||||
$this->validateHtml($text, $chunk_str);
|
||||
}
|
||||
$chunk_num++;
|
||||
}
|
||||
$this->assertTrue($number_checked > 0, 'Tested at least one translated chunk in ' . $id);
|
||||
|
||||
// Validate the HTML in the body with the translated text replaced by a
|
||||
// dummy string, to verify that HTML syntax is not partly in and partly out
|
||||
// of the translated text.
|
||||
$text = $this->renderHelpTopic($template_text, 'replace_translated');
|
||||
$this->validateHtml($text, $id);
|
||||
|
||||
// Verify that if we remove all the translated text, whitespace, and
|
||||
// HTML tags, there is nothing left (that is, all text is translated).
|
||||
$text = preg_replace('|\s+|', '', $this->renderHelpTopic($template_text, 'remove_translated'));
|
||||
$this->assertEmpty($text, 'Topic ' . $id . ' Twig file has all of its text translated');
|
||||
|
||||
// Verify that the Twig url() function was not used.
|
||||
$this->assertStringNotContainsString('url(', $template, 'Topic ' . $id . ' appears to use the url() function. Replace with help_topic_link() or help_topic_route(). See https://drupal.org/node/3074421');
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the HTML and header hierarchy for topic text.
|
||||
*
|
||||
* @param string $body
|
||||
* Body text to validate.
|
||||
* @param string $id
|
||||
* ID of help topic (for error messages).
|
||||
*/
|
||||
protected function validateHtml(string $body, string $id): void {
|
||||
$doc = new \DOMDocument();
|
||||
$doc->strictErrorChecking = TRUE;
|
||||
$doc->validateOnParse = FALSE;
|
||||
libxml_use_internal_errors(TRUE);
|
||||
if (!$doc->loadXML('<html><body>' . $body . '</body></html>')) {
|
||||
foreach (libxml_get_errors() as $error) {
|
||||
$this->fail('Topic ' . $id . ' fails HTML validation: ' . $error->message);
|
||||
}
|
||||
|
||||
libxml_clear_errors();
|
||||
}
|
||||
|
||||
// Check for headings hierarchy.
|
||||
$levels = [1, 2, 3, 4, 5, 6];
|
||||
foreach ($levels as $level) {
|
||||
$num_headings[$level] = $doc->getElementsByTagName('h' . $level)->length;
|
||||
if ($level == 1) {
|
||||
$this->assertSame(0, $num_headings[1], 'Topic ' . $id . ' has no H1 tag');
|
||||
// Set num_headings to 1 for this level, so the rest of the hierarchy
|
||||
// can be tested using simpler code.
|
||||
$num_headings[1] = 1;
|
||||
}
|
||||
else {
|
||||
// We should either not have this heading, or if we do have one at this
|
||||
// level, we should also have the next-smaller level. That is, if we
|
||||
// have an h3, we should have also had an h2.
|
||||
$this->assertTrue($num_headings[$level - 1] > 0 || $num_headings[$level] == 0,
|
||||
'Topic ' . $id . ' has the correct H2-H6 heading hierarchy');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that a bad topic fails in the expected way.
|
||||
*
|
||||
* @param string $id
|
||||
* ID of the topic to verify. It should start with "bad_help_topics.".
|
||||
* @param array $definitions
|
||||
* Array of all topic definitions, keyed by ID.
|
||||
*/
|
||||
protected function verifyBadTopic($id, $definitions): void {
|
||||
$bad_topic_type = substr($id, 16);
|
||||
// Topics should fail verifyTopic() in specific ways.
|
||||
$found_error = FALSE;
|
||||
try {
|
||||
$this->verifyTopic($id, $definitions, 404);
|
||||
}
|
||||
catch (ExpectationFailedException | AssertionFailedError $e) {
|
||||
$found_error = TRUE;
|
||||
$message = $e->getMessage();
|
||||
switch ($bad_topic_type) {
|
||||
case 'related':
|
||||
$this->assertStringContainsString('only related to topics that exist', $message);
|
||||
break;
|
||||
|
||||
case 'bad_html':
|
||||
case 'bad_html2':
|
||||
case 'bad_html3':
|
||||
$this->assertStringContainsString('Opening and ending tag mismatch', $message);
|
||||
break;
|
||||
|
||||
case 'top_level':
|
||||
$this->assertStringContainsString('is either top-level or related to at least one other top-level topic', $message);
|
||||
break;
|
||||
|
||||
case 'empty':
|
||||
$this->assertStringContainsString('contains some text outside of front matter', $message);
|
||||
break;
|
||||
|
||||
case 'translated':
|
||||
$this->assertStringContainsString('Twig file has all of its text translated', $message);
|
||||
break;
|
||||
|
||||
case 'locale':
|
||||
$this->assertStringContainsString('translatable string is locale-safe', $message);
|
||||
break;
|
||||
|
||||
case 'h1':
|
||||
$this->assertStringContainsString('has no H1 tag', $message);
|
||||
break;
|
||||
|
||||
case 'hierarchy':
|
||||
$this->assertStringContainsString('has the correct H2-H6 heading hierarchy', $message);
|
||||
break;
|
||||
|
||||
case 'url_func_used':
|
||||
$this->assertStringContainsString('appears to use the url() function', $message);
|
||||
break;
|
||||
|
||||
default:
|
||||
// This was an unexpected error.
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$found_error) {
|
||||
$this->fail('Bad help topic ' . $bad_topic_type . ' did not fail as expected');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists the extension help topic directories of a certain type.
|
||||
*
|
||||
* @param string $type
|
||||
* The type of extension to list: module, theme, or profile.
|
||||
*
|
||||
* @return string[]
|
||||
* An array of all of the help topic directories for this type of
|
||||
* extension, keyed by extension short name.
|
||||
*/
|
||||
protected function listDirectories($type) {
|
||||
$directories = [];
|
||||
|
||||
// Find the extensions of this type, even if they are not installed, but
|
||||
// excluding test ones.
|
||||
$lister = \Drupal::service('extension.list.' . $type);
|
||||
foreach ($lister->getAllAvailableInfo() as $name => $info) {
|
||||
// Skip obsolete and deprecated modules.
|
||||
if ($info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] === ExtensionLifecycle::OBSOLETE || $info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] === ExtensionLifecycle::DEPRECATED) {
|
||||
continue;
|
||||
}
|
||||
$path = $lister->getPath($name);
|
||||
// You can tell test modules because they are in package 'Testing', but
|
||||
// test themes are only known by being found in test directories. So...
|
||||
// exclude things in test directories.
|
||||
if (!str_contains($path, '/tests') && !str_contains($path, '/testing')) {
|
||||
$directories[$name] = $path . '/help_topics';
|
||||
}
|
||||
}
|
||||
return $directories;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a help topic in a special manner.
|
||||
*
|
||||
* @param string $content
|
||||
* Template text, without the front matter.
|
||||
* @param string $manner
|
||||
* The special processing choice for topic rendering.
|
||||
*
|
||||
* @return string
|
||||
* The rendered topic.
|
||||
*/
|
||||
protected function renderHelpTopic(string $content, string $manner): string {
|
||||
// Set up the special state variables for rendering.
|
||||
HelpTestTwigNodeVisitor::setStateValue('manner', $manner);
|
||||
HelpTestTwigNodeVisitor::setStateValue('max_chunk', -1);
|
||||
HelpTestTwigNodeVisitor::setStateValue('chunk_count', -1);
|
||||
|
||||
// Add a random comment to the end, to thwart caching, and render. We need
|
||||
// the HelpTestTwigNodeVisitor class to hit it each time we render.
|
||||
$build = [
|
||||
'#type' => 'inline_template',
|
||||
'#template' => $content . "\n{# " . rand() . " #}",
|
||||
];
|
||||
return (string) \Drupal::service('renderer')->renderInIsolation($build);
|
||||
}
|
||||
|
||||
}
|
||||
65
web/core/modules/help/tests/src/Functional/NoHelpTest.php
Normal file
65
web/core/modules/help/tests/src/Functional/NoHelpTest.php
Normal file
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\help\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Verify no help is displayed for modules not providing any help.
|
||||
*
|
||||
* @group help
|
||||
*/
|
||||
class NoHelpTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* Modules to install.
|
||||
*
|
||||
* Use one of the test modules that do not implement hook_help().
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $modules = ['help', 'menu_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* The user who will be created.
|
||||
*
|
||||
* @var \Drupal\user\Entity\User|false
|
||||
*/
|
||||
protected $adminUser;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->adminUser = $this->drupalCreateUser(['access help pages']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures modules not implementing help do not appear on admin/help.
|
||||
*/
|
||||
public function testMainPageNoHelp(): void {
|
||||
$this->drupalLogin($this->adminUser);
|
||||
|
||||
$this->drupalGet('admin/help');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextContains('Module overviews are provided by modules');
|
||||
$this->assertFalse(\Drupal::moduleHandler()->hasImplementations('help', 'menu_test'), 'The menu_test module does not implement hook_help');
|
||||
// Make sure the test module menu_test does not display a help link on
|
||||
// admin/help.
|
||||
$this->assertSession()->pageTextNotContains(\Drupal::service('extension.list.module')->getName('menu_test'));
|
||||
|
||||
// Ensure that the module overview help page for a module that does not
|
||||
// implement hook_help() results in a 404.
|
||||
$this->drupalGet('admin/help/menu_test');
|
||||
$this->assertSession()->statusCodeEquals(404);
|
||||
}
|
||||
|
||||
}
|
||||
54
web/core/modules/help/tests/src/Kernel/HelpEmptyPageTest.php
Normal file
54
web/core/modules/help/tests/src/Kernel/HelpEmptyPageTest.php
Normal file
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\help\Kernel;
|
||||
|
||||
use Drupal\Core\DependencyInjection\ContainerBuilder;
|
||||
use Drupal\Core\Routing\RouteMatch;
|
||||
use Drupal\help_test\SupernovaGenerator;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Tests the empty HTML page.
|
||||
*
|
||||
* @group help
|
||||
*/
|
||||
class HelpEmptyPageTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['system', 'help_test', 'user', 'path_alias'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function register(ContainerBuilder $container): void {
|
||||
parent::register($container);
|
||||
|
||||
$container->set('url_generator', new SupernovaGenerator());
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that no URL generator is called on a page without hook_help().
|
||||
*/
|
||||
public function testEmptyHookHelp(): void {
|
||||
$all_modules = \Drupal::service('extension.list.module')->getList();
|
||||
$all_modules = array_filter($all_modules, function ($module) {
|
||||
// Filter contrib, hidden, already enabled modules and modules in the
|
||||
// Testing package.
|
||||
if ($module->origin !== 'core' || !empty($module->info['hidden']) || $module->status == TRUE || $module->info['package'] == 'Testing') {
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
});
|
||||
|
||||
$this->enableModules(array_keys($all_modules));
|
||||
$this->installEntitySchema('menu_link_content');
|
||||
|
||||
$route = \Drupal::service('router.route_provider')->getRouteByName('<front>');
|
||||
\Drupal::service('module_handler')->invokeAll('help', ['<front>', new RouteMatch('<front>', $route)]);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\help\Kernel;
|
||||
|
||||
use Drupal\Core\Access\AccessibleInterface;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\search\Plugin\SearchIndexingInterface;
|
||||
|
||||
/**
|
||||
* Tests search plugin behaviors.
|
||||
*
|
||||
* @group help
|
||||
*
|
||||
* @see \Drupal\help\Plugin\Search\HelpSearch
|
||||
*/
|
||||
class HelpSearchPluginTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['help', 'search'];
|
||||
|
||||
/**
|
||||
* Tests search plugin annotation and interfaces.
|
||||
*/
|
||||
public function testAnnotation(): void {
|
||||
/** @var \Drupal\search\SearchPluginManager $manager */
|
||||
$manager = \Drupal::service('plugin.manager.search');
|
||||
/** @var \Drupal\help\Plugin\Search\HelpSearch $plugin */
|
||||
$plugin = $manager->createInstance('help_search');
|
||||
$this->assertInstanceOf(AccessibleInterface::class, $plugin);
|
||||
$this->assertInstanceOf(SearchIndexingInterface::class, $plugin);
|
||||
$this->assertSame('Help', (string) $plugin->getPluginDefinition()['title']);
|
||||
$this->assertTrue($plugin->usesAdminTheme());
|
||||
}
|
||||
|
||||
}
|
||||
246
web/core/modules/help/tests/src/Unit/HelpTopicDiscoveryTest.php
Normal file
246
web/core/modules/help/tests/src/Unit/HelpTopicDiscoveryTest.php
Normal file
@ -0,0 +1,246 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\help\Unit;
|
||||
|
||||
use Drupal\Component\Discovery\DiscoveryException;
|
||||
use Drupal\Core\DependencyInjection\ContainerBuilder;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\help\HelpTopicDiscovery;
|
||||
use Drupal\help\HelpTopicTwig;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
use org\bovigo\vfs\vfsStream;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\help\HelpTopicDiscovery
|
||||
* @group help
|
||||
*/
|
||||
class HelpTopicDiscoveryTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* @covers ::findAll
|
||||
*/
|
||||
public function testDiscoveryExceptionMissingLabel(): void {
|
||||
vfsStream::setup('root');
|
||||
|
||||
vfsStream::create([
|
||||
'modules' => [
|
||||
'test' => [
|
||||
'help_topics' => [
|
||||
// The content of the help topic does not matter.
|
||||
'test.topic.html.twig' => '',
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
$discovery = new HelpTopicDiscovery(['test' => vfsStream::url('root/modules/test/help_topics')]);
|
||||
|
||||
$this->expectException(DiscoveryException::class);
|
||||
$this->expectExceptionMessage("vfs://root/modules/test/help_topics/test.topic.html.twig does not contain the required key with name='label'");
|
||||
$discovery->getDefinitions();
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::findAll
|
||||
*/
|
||||
public function testDiscoveryExceptionInvalidYamlKey(): void {
|
||||
vfsStream::setup('root');
|
||||
$topic_content = <<<EOF
|
||||
---
|
||||
label: 'A label'
|
||||
foo: bar
|
||||
---
|
||||
EOF;
|
||||
|
||||
vfsStream::create([
|
||||
'modules' => [
|
||||
'test' => [
|
||||
'help_topics' => [
|
||||
'test.topic.html.twig' => $topic_content,
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
$discovery = new HelpTopicDiscovery(['test' => vfsStream::url('root/modules/test/help_topics')]);
|
||||
|
||||
$this->expectException(DiscoveryException::class);
|
||||
$this->expectExceptionMessage("vfs://root/modules/test/help_topics/test.topic.html.twig contains invalid key='foo'");
|
||||
$discovery->getDefinitions();
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::findAll
|
||||
*/
|
||||
public function testDiscoveryExceptionInvalidTopLevel(): void {
|
||||
vfsStream::setup('root');
|
||||
$topic_content = <<<EOF
|
||||
---
|
||||
label: 'A label'
|
||||
top_level: bar
|
||||
---
|
||||
EOF;
|
||||
|
||||
vfsStream::create([
|
||||
'modules' => [
|
||||
'test' => [
|
||||
'help_topics' => [
|
||||
'test.topic.html.twig' => $topic_content,
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
$discovery = new HelpTopicDiscovery(['test' => vfsStream::url('root/modules/test/help_topics')]);
|
||||
|
||||
$this->expectException(DiscoveryException::class);
|
||||
$this->expectExceptionMessage("vfs://root/modules/test/help_topics/test.topic.html.twig contains invalid value for 'top_level' key, the value must be a Boolean");
|
||||
$discovery->getDefinitions();
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::findAll
|
||||
*/
|
||||
public function testDiscoveryExceptionInvalidRelated(): void {
|
||||
vfsStream::setup('root');
|
||||
$topic_content = <<<EOF
|
||||
---
|
||||
label: 'A label'
|
||||
related: "one, two"
|
||||
---
|
||||
EOF;
|
||||
|
||||
vfsStream::create([
|
||||
'modules' => [
|
||||
'test' => [
|
||||
'help_topics' => [
|
||||
'test.topic.html.twig' => $topic_content,
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
$discovery = new HelpTopicDiscovery(['test' => vfsStream::url('root/modules/test/help_topics')]);
|
||||
|
||||
$this->expectException(DiscoveryException::class);
|
||||
$this->expectExceptionMessage("vfs://root/modules/test/help_topics/test.topic.html.twig contains invalid value for 'related' key, the value must be an array of strings");
|
||||
$discovery->getDefinitions();
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::findAll
|
||||
*/
|
||||
public function testHelpTopicsExtensionProviderSpecialCase(): void {
|
||||
vfsStream::setup('root');
|
||||
$topic_content = <<<EOF
|
||||
---
|
||||
label: Test
|
||||
---
|
||||
<h2>Test</h2>
|
||||
EOF;
|
||||
|
||||
vfsStream::create([
|
||||
'modules' => [
|
||||
'help' => [
|
||||
'help_topics' => [
|
||||
'core.topic.html.twig' => $topic_content,
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
$discovery = new HelpTopicDiscovery(['help' => vfsStream::url('root/modules/help/help_topics')]);
|
||||
$this->assertArrayHasKey('core.topic', $discovery->getDefinitions());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::findAll
|
||||
*/
|
||||
public function testHelpTopicsInCore(): void {
|
||||
vfsStream::setup('root');
|
||||
$topic_content = <<<EOF
|
||||
---
|
||||
label: Test
|
||||
---
|
||||
<h2>Test</h2>
|
||||
EOF;
|
||||
|
||||
vfsStream::create([
|
||||
'core' => [
|
||||
'help_topics' => [
|
||||
'core.topic.html.twig' => $topic_content,
|
||||
],
|
||||
],
|
||||
]);
|
||||
$discovery = new HelpTopicDiscovery(['core' => vfsStream::url('root/core/help_topics')]);
|
||||
$this->assertArrayHasKey('core.topic', $discovery->getDefinitions());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::findAll
|
||||
*/
|
||||
public function testHelpTopicsBrokenYaml(): void {
|
||||
vfsStream::setup('root');
|
||||
$topic_content = <<<EOF
|
||||
---
|
||||
foo : [bar}
|
||||
---
|
||||
<h2>Test</h2>
|
||||
EOF;
|
||||
|
||||
vfsStream::create([
|
||||
'modules' => [
|
||||
'help' => [
|
||||
'help_topics' => [
|
||||
'core.topic.html.twig' => $topic_content,
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
$discovery = new HelpTopicDiscovery(['help' => vfsStream::url('root/modules/help/help_topics')]);
|
||||
$this->expectException(DiscoveryException::class);
|
||||
$this->expectExceptionMessage("Malformed YAML in help topic \"vfs://root/modules/help/help_topics/core.topic.html.twig\":");
|
||||
$discovery->getDefinitions();
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::findAll
|
||||
*/
|
||||
public function testHelpTopicsDefinition(): void {
|
||||
$container = new ContainerBuilder();
|
||||
$container->set('string_translation', $this->getStringTranslationStub());
|
||||
\Drupal::setContainer($container);
|
||||
|
||||
vfsStream::setup('root');
|
||||
$topic_content = <<<EOF
|
||||
---
|
||||
label: 'Test'
|
||||
top_level: true
|
||||
related:
|
||||
- one
|
||||
- two
|
||||
- three
|
||||
---
|
||||
<h2>Test</h2>
|
||||
EOF;
|
||||
|
||||
vfsStream::create([
|
||||
'modules' => [
|
||||
'foo' => [
|
||||
'help_topics' => [
|
||||
'foo.topic.html.twig' => $topic_content,
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
$discovery = new HelpTopicDiscovery(['foo' => vfsStream::url('root/modules/foo/help_topics')]);
|
||||
$definition = $discovery->getDefinitions()['foo.topic'];
|
||||
$this->assertEquals('Test', $definition['label']);
|
||||
$this->assertInstanceOf(TranslatableMarkup::class, $definition['label']);
|
||||
$this->assertTrue($definition['top_level']);
|
||||
// Each related plugin ID should be trimmed.
|
||||
$this->assertSame(['one', 'two', 'three'], $definition['related']);
|
||||
$this->assertSame('foo', $definition['provider']);
|
||||
$this->assertSame(HelpTopicTwig::class, $definition['class']);
|
||||
$this->assertSame(vfsStream::url('root/modules/foo/help_topics/foo.topic.html.twig'), $definition['_discovered_file_path']);
|
||||
$this->assertSame('foo.topic', $definition['id']);
|
||||
}
|
||||
|
||||
}
|
||||
134
web/core/modules/help/tests/src/Unit/HelpTopicTwigLoaderTest.php
Normal file
134
web/core/modules/help/tests/src/Unit/HelpTopicTwigLoaderTest.php
Normal file
@ -0,0 +1,134 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\help\Unit;
|
||||
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Extension\ThemeHandlerInterface;
|
||||
use Drupal\help\HelpTopicTwigLoader;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
use org\bovigo\vfs\vfsStream;
|
||||
use Twig\Error\LoaderError;
|
||||
|
||||
/**
|
||||
* Unit test for the HelpTopicTwigLoader class.
|
||||
*
|
||||
* @coversDefaultClass \Drupal\help\HelpTopicTwigLoader
|
||||
* @group help
|
||||
*/
|
||||
class HelpTopicTwigLoaderTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* The help topic loader instance to test.
|
||||
*
|
||||
* @var \Drupal\help\HelpTopicTwigLoader
|
||||
*/
|
||||
protected $helpLoader;
|
||||
|
||||
/**
|
||||
* The virtual directories to use in testing.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $directories;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->setUpVfs();
|
||||
|
||||
$module_handler = $this->createMock(ModuleHandlerInterface::class);
|
||||
$module_handler
|
||||
->method('getModuleDirectories')
|
||||
->willReturn($this->directories['module']);
|
||||
|
||||
/** @var \Drupal\Core\Extension\ThemeHandlerInterface|\Prophecy\Prophecy\ObjectProphecy $module_handler */
|
||||
$theme_handler = $this->createMock(ThemeHandlerInterface::class);
|
||||
$theme_handler
|
||||
->method('getThemeDirectories')
|
||||
->willReturn($this->directories['theme']);
|
||||
|
||||
$this->helpLoader = new HelpTopicTwigLoader('\fake\root\path', $module_handler, $theme_handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::__construct
|
||||
*/
|
||||
public function testConstructor(): void {
|
||||
// Verify that the module/theme directories were added in the constructor,
|
||||
// and non-existent directories were omitted.
|
||||
$paths = $this->helpLoader->getPaths(HelpTopicTwigLoader::MAIN_NAMESPACE);
|
||||
$this->assertCount(2, $paths);
|
||||
$this->assertContains($this->directories['module']['test'] . '/help_topics', $paths);
|
||||
$this->assertContains($this->directories['theme']['test'] . '/help_topics', $paths);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getSourceContext
|
||||
*/
|
||||
public function testGetSourceContext(): void {
|
||||
$source = $this->helpLoader->getSourceContext('@' . HelpTopicTwigLoader::MAIN_NAMESPACE . '/test.topic.html.twig');
|
||||
$this->assertEquals('{% line 4 %}<h2>Test</h2>', $source->getCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getSourceContext
|
||||
*/
|
||||
public function testGetSourceContextException(): void {
|
||||
$this->expectException(LoaderError::class);
|
||||
$this->expectExceptionMessage("Malformed YAML in help topic \"vfs://root/modules/test/help_topics/test.invalid_yaml.html.twig\":");
|
||||
|
||||
$this->helpLoader->getSourceContext('@' . HelpTopicTwigLoader::MAIN_NAMESPACE . '/test.invalid_yaml.html.twig');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the virtual file system.
|
||||
*/
|
||||
protected function setUpVfs(): void {
|
||||
$content = <<<EOF
|
||||
---
|
||||
label: Test
|
||||
---
|
||||
<h2>Test</h2>
|
||||
EOF;
|
||||
$invalid_content = <<<EOF
|
||||
---
|
||||
foo : [bar}
|
||||
---
|
||||
<h2>Test</h2>
|
||||
EOF;
|
||||
$help_topics_dir = [
|
||||
'help_topics' => [
|
||||
'test.topic.html.twig' => $content,
|
||||
'test.invalid_yaml.html.twig' => $invalid_content,
|
||||
],
|
||||
];
|
||||
|
||||
vfsStream::setup('root');
|
||||
vfsStream::create([
|
||||
'modules' => [
|
||||
'test' => $help_topics_dir,
|
||||
],
|
||||
'themes' => [
|
||||
'test' => $help_topics_dir,
|
||||
],
|
||||
]);
|
||||
|
||||
$this->directories = [
|
||||
'root' => vfsStream::url('root'),
|
||||
'module' => [
|
||||
'test' => vfsStream::url('root/modules/test'),
|
||||
'not_a_dir' => vfsStream::url('root/modules/not_a_dir'),
|
||||
],
|
||||
'theme' => [
|
||||
'test' => vfsStream::url('root/themes/test'),
|
||||
'not_a_dir' => vfsStream::url('root/themes/not_a_dir'),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
120
web/core/modules/help/tests/src/Unit/HelpTopicTwigTest.php
Normal file
120
web/core/modules/help/tests/src/Unit/HelpTopicTwigTest.php
Normal file
@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\help\Unit;
|
||||
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\help\HelpTopicTwig;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
use Twig\Template;
|
||||
use Twig\TemplateWrapper;
|
||||
|
||||
/**
|
||||
* Unit test for the HelpTopicTwig class.
|
||||
*
|
||||
* Note that the toUrl() and toLink() methods are not covered, because they
|
||||
* have calls to new Url() and new Link() in them, so they cannot be unit
|
||||
* tested.
|
||||
*
|
||||
* @coversDefaultClass \Drupal\help\HelpTopicTwig
|
||||
* @group help
|
||||
*/
|
||||
class HelpTopicTwigTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* The help topic instance to test.
|
||||
*
|
||||
* @var \Drupal\help\HelpTopicTwig
|
||||
*/
|
||||
protected $helpTopic;
|
||||
|
||||
/**
|
||||
* The plugin information to use for setting up a test topic.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
const PLUGIN_INFORMATION = [
|
||||
'id' => 'test.topic',
|
||||
'provider' => 'test',
|
||||
'label' => 'This is the topic label',
|
||||
'top_level' => TRUE,
|
||||
'related' => ['something'],
|
||||
'body' => '<p>This is the topic body</p>',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->helpTopic = new HelpTopicTwig([],
|
||||
self::PLUGIN_INFORMATION['id'],
|
||||
self::PLUGIN_INFORMATION,
|
||||
$this->getTwigMock());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getBody
|
||||
* @covers ::getLabel
|
||||
*/
|
||||
public function testText(): void {
|
||||
$this->assertEquals($this->helpTopic->getBody(),
|
||||
['#markup' => self::PLUGIN_INFORMATION['body']]);
|
||||
$this->assertEquals($this->helpTopic->getLabel(),
|
||||
self::PLUGIN_INFORMATION['label']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getProvider
|
||||
* @covers ::isTopLevel
|
||||
* @covers ::getRelated
|
||||
*/
|
||||
public function testDefinition(): void {
|
||||
$this->assertEquals($this->helpTopic->getProvider(),
|
||||
self::PLUGIN_INFORMATION['provider']);
|
||||
$this->assertEquals($this->helpTopic->isTopLevel(),
|
||||
self::PLUGIN_INFORMATION['top_level']);
|
||||
$this->assertEquals($this->helpTopic->getRelated(),
|
||||
self::PLUGIN_INFORMATION['related']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getCacheContexts
|
||||
* @covers ::getCacheTags
|
||||
* @covers ::getCacheMaxAge
|
||||
*/
|
||||
public function testCacheInfo(): void {
|
||||
$this->assertEquals([], $this->helpTopic->getCacheContexts());
|
||||
$this->assertEquals(['core.extension'], $this->helpTopic->getCacheTags());
|
||||
$this->assertEquals(Cache::PERMANENT, $this->helpTopic->getCacheMaxAge());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a mock Twig loader class for the test.
|
||||
*/
|
||||
protected function getTwigMock() {
|
||||
$twig = $this
|
||||
->getMockBuilder('Drupal\Core\Template\TwigEnvironment')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
||||
$template = $this
|
||||
->getMockBuilder(Template::class)
|
||||
->onlyMethods(['render', 'getTemplateName', 'getDebugInfo', 'getSourceContext', 'doDisplay'])
|
||||
->setConstructorArgs([$twig])
|
||||
->getMock();
|
||||
|
||||
$template
|
||||
->method('render')
|
||||
->willReturn(self::PLUGIN_INFORMATION['body']);
|
||||
|
||||
$twig
|
||||
->method('load')
|
||||
->willReturn(new TemplateWrapper($twig, $template));
|
||||
|
||||
return $twig;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user