View source
<?php
namespace Drupal\Tests\help_topics\Functional;
use Drupal\Core\Extension\ExtensionLifecycle;
use Drupal\Tests\BrowserTestBase;
use Drupal\help_topics\HelpTopicDiscovery;
use PHPUnit\Framework\ExpectationFailedException;
use PHPUnit\Framework\AssertionFailedError;
class HelpTopicsSyntaxTest extends BrowserTestBase {
protected static $modules = [
'help',
'help_topics',
'locale',
];
protected $defaultTheme = 'classy';
public function testHelpTopics() {
$this
->drupalLogin($this->rootUser);
$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/';
$directories = array_filter($directories, function ($directory) {
return strpos($directory, 'core') === 0;
});
$this
->assertArrayHasKey('system', $directories, 'System module is being scanned');
$this
->assertArrayHasKey('help', $directories, 'Help module is being scanned');
$this
->assertArrayHasKey('seven', $directories, 'Seven 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');
foreach (array_keys($definitions) as $id) {
if (strpos($id, 'bad_help_topics.') === 0) {
$this
->verifyBadTopic($id, $definitions);
}
else {
$this
->verifyTopic($id, $definitions);
}
}
}
protected function verifyTopic($id, $definitions, $response = 200) {
$definition = $definitions[$id];
$this
->drupalGet('admin/help/topic/' . $id);
$session = $this
->assertSession();
$session
->statusCodeEquals($response);
if ($response == 200) {
$session
->titleEquals($definition['label'] . ' | Drupal');
}
$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']);
}
}
$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');
$this
->assertNotEmpty($definition['label'], 'Topic ' . $id . ' has a non-empty label');
$body = file_get_contents($definition[HelpTopicDiscovery::FILE_KEY]);
$this
->assertNotEmpty($body, 'Topic ' . $id . ' has a non-empty Twig file');
$body = preg_replace('|---.*---|sU', '', $body);
$body = preg_replace('|\\{\\{.*\\}\\}|sU', '', $body);
$body = preg_replace('|\\{\\% set.*\\%\\}|sU', '', $body);
$body = preg_replace('|\\{\\% endset \\%\\}|sU', '', $body);
$body = trim($body);
$this
->assertNotEmpty($body, 'Topic ' . $id . ' Twig file contains some text outside of front matter');
$text = preg_replace('|\\{\\% trans \\%\\}.*\\{\\% endtrans \\%\\}|sU', '', $body);
$text = strip_tags($text);
$text = preg_replace('|\\s+|', '', $text);
$this
->assertEmpty($text, 'Topic ' . $id . ' Twig file has all of its text translated');
$matches = [];
preg_match_all('|\\{\\% trans \\%\\}(.*)\\{\\% endtrans \\%\\}|sU', $body, $matches, PREG_PATTERN_ORDER);
foreach ($matches[1] as $string) {
$this
->assertTrue(locale_string_is_safe($string), 'Topic ' . $id . ' Twig file translatable strings are all exportable');
$this
->validateHtml($string, $id);
}
$this
->validateHtml($body, $id);
$text = preg_replace('|\\{\\% trans \\%\\}.*\\{\\% endtrans \\%\\}|sU', 'dummy', $body);
$this
->validateHtml($text, $id);
}
protected function validateHtml(string $body, string $id) {
$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();
}
$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');
$num_headings[1] = 1;
}
else {
$this
->assertTrue($num_headings[$level - 1] > 0 || $num_headings[$level] == 0, 'Topic ' . $id . ' has the correct H2-H6 heading hierarchy');
}
}
}
protected function verifyBadTopic($id, $definitions) {
$bad_topic_type = substr($id, 16);
$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 'h1':
$this
->assertStringContainsString('has no H1 tag', $message);
break;
case 'hierarchy':
$this
->assertStringContainsString('has the correct H2-H6 heading hierarchy', $message);
break;
default:
throw $e;
}
}
if (!$found_error) {
$this
->fail('Bad help topic ' . $bad_topic_type . ' did not fail as expected');
}
}
protected function listDirectories($type) {
$directories = [];
$lister = \Drupal::service('extension.list.' . $type);
foreach ($lister
->getAllAvailableInfo() as $name => $info) {
if (isset($info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER]) && $info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] === ExtensionLifecycle::OBSOLETE) {
continue;
}
$path = $lister
->getPath($name);
if (strpos($path, '/tests') === FALSE && strpos($path, '/testing') === FALSE) {
$directories[$name] = $path . '/help_topics';
}
}
return $directories;
}
}