Issue #3294914 by Spokje, quietone, bbrala, longwave, Gábor Hojtsy, benjifisher, dww, xjm, rkoller: Create dedicated error section for missing removed core modules/themes on update

merge-requests/2663/merge
catch 2022-12-02 10:53:59 +00:00
parent 7643161492
commit 22a56817e0
2 changed files with 382 additions and 87 deletions

View File

@ -28,6 +28,30 @@ use Drupal\Core\Url;
use GuzzleHttp\Exception\TransferException; use GuzzleHttp\Exception\TransferException;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
// cspell:ignore quickedit
/**
* An array of machine names of modules that were removed from Drupal core.
*/
const DRUPAL_CORE_REMOVED_MODULE_LIST = [
'aggregator' => 'Aggregator',
'ckeditor' => 'CKEditor',
'color' => 'Color',
'hal' => 'HAL',
'quickedit' => 'Quick Edit',
'rdf' => 'RDF',
];
/**
* An array of machine names of themes that were removed from Drupal core.
*/
const DRUPAL_CORE_REMOVED_THEME_LIST = [
'bartik' => 'Bartik',
'classy' => 'Classy',
'seven' => 'Seven',
'stable' => 'Stable',
];
/** /**
* Implements hook_requirements(). * Implements hook_requirements().
*/ */
@ -989,13 +1013,16 @@ function system_requirements($phase) {
// Display an error if a newly introduced dependency in a module is not resolved. // Display an error if a newly introduced dependency in a module is not resolved.
if ($phase === 'update' || $phase === 'runtime') { if ($phase === 'update' || $phase === 'runtime') {
$create_extension_incompatibility_list = function ($extension_names, $description, $title) { $create_extension_incompatibility_list = function (array $extension_names, PluralTranslatableMarkup $description, PluralTranslatableMarkup $title, TranslatableMarkup|string $message = '', TranslatableMarkup|string $additional_description = '') {
if ($message === '') {
$message = new TranslatableMarkup('Review the <a href=":url"> suggestions for resolving this incompatibility</a> to repair your installation, and then re-run update.php.', [':url' => 'https://www.drupal.org/docs/updating-drupal/troubleshooting-database-updates']);
}
// Use an inline twig template to: // Use an inline twig template to:
// - Concatenate two MarkupInterface objects and preserve safeness. // - Concatenate MarkupInterface objects and preserve safeness.
// - Use the item_list theme for the extension list. // - Use the item_list theme for the extension list.
$template = [ $template = [
'#type' => 'inline_template', '#type' => 'inline_template',
'#template' => '{{ description }}{{ extensions }}', '#template' => '{{ description }}{{ extensions }}{{ additional_description }}<br>',
'#context' => [ '#context' => [
'extensions' => [ 'extensions' => [
'#theme' => 'item_list', '#theme' => 'item_list',
@ -1004,15 +1031,13 @@ function system_requirements($phase) {
]; ];
$template['#context']['extensions']['#items'] = $extension_names; $template['#context']['extensions']['#items'] = $extension_names;
$template['#context']['description'] = $description; $template['#context']['description'] = $description;
$template['#context']['additional_description'] = $additional_description;
return [ return [
'title' => $title, 'title' => $title,
'value' => [ 'value' => [
'list' => $template, 'list' => $template,
'handbook_link' => [ 'handbook_link' => [
'#markup' => t( '#markup' => $message,
'Review the <a href=":url"> suggestions for resolving this incompatibility</a> to repair your installation, and then re-run update.php.',
[':url' => 'https://www.drupal.org/docs/8/update/troubleshooting-database-updates']
),
], ],
], ],
'severity' => REQUIREMENT_ERROR, 'severity' => REQUIREMENT_ERROR,
@ -1134,13 +1159,89 @@ function system_requirements($phase) {
); );
} }
// Look for invalid modules.
$extension_config = \Drupal::configFactory()->get('core.extension'); $extension_config = \Drupal::configFactory()->get('core.extension');
$is_missing_extension = function ($extension_name) use (&$module_extension_list) {
return !$module_extension_list->exists($extension_name);
};
$invalid_modules = array_filter(array_keys($extension_config->get('module')), $is_missing_extension); // Look for removed core modules.
$is_removed_module = function ($extension_name) use ($module_extension_list) {
return !$module_extension_list->exists($extension_name)
&& array_key_exists($extension_name, DRUPAL_CORE_REMOVED_MODULE_LIST);
};
$removed_modules = array_filter(array_keys($extension_config->get('module')), $is_removed_module);
if (!empty($removed_modules)) {
$list = [];
foreach ($removed_modules as $removed_module) {
$list[] = t('<a href=":url">@module</a>', [
':url' => "https://www.drupal.org/project/$removed_module",
'@module' => DRUPAL_CORE_REMOVED_MODULE_LIST[$removed_module],
]);
}
$requirements['removed_module'] = $create_extension_incompatibility_list(
$list,
new PluralTranslatableMarkup(
count($removed_modules),
'You must add the following contributed module and reload this page.',
'You must add the following contributed modules and reload this page.'
),
new PluralTranslatableMarkup(
count($removed_modules),
'Removed core module',
'Removed core modules'
),
new TranslatableMarkup(
'For more information read the <a href=":url">documentation on deprecated modules.</a>',
[':url' => 'https://www.drupal.org/node/3223395#s-recommendations-for-deprecated-modules']
),
new PluralTranslatableMarkup(
count($removed_modules),
'This module is installed on your site but is no longer provided by Core.',
'These modules are installed on your site but are no longer provided by Core.'
),
);
}
// Look for removed core themes.
$is_removed_theme = function ($extension_name) use ($theme_extension_list) {
return !$theme_extension_list->exists($extension_name)
&& array_key_exists($extension_name, DRUPAL_CORE_REMOVED_THEME_LIST);
};
$removed_themes = array_filter(array_keys($extension_config->get('theme')), $is_removed_theme);
if (!empty($removed_themes)) {
$list = [];
foreach ($removed_themes as $removed_theme) {
$list[] = t('<a href=":url">@theme</a>', [
':url' => "https://www.drupal.org/project/$removed_theme",
'@theme' => DRUPAL_CORE_REMOVED_THEME_LIST[$removed_theme],
]);
}
$requirements['removed_theme'] = $create_extension_incompatibility_list(
$list,
new PluralTranslatableMarkup(
count($removed_themes),
'You must add the following contributed theme and reload this page.',
'You must add the following contributed themes and reload this page.'
),
new PluralTranslatableMarkup(
count($removed_themes),
'Removed core theme',
'Removed core themes'
),
new TranslatableMarkup(
'For more information read the <a href=":url">documentation on deprecated themes.</a>',
[':url' => 'https://www.drupal.org/node/3223395#s-recommendations-for-deprecated-themes']
),
new PluralTranslatableMarkup(
count($removed_themes),
'This theme is installed on your site but is no longer provided by Core.',
'These themes are installed on your site but are no longer provided by Core.'
),
);
}
// Look for missing modules.
$is_missing_module = function ($extension_name) use ($module_extension_list) {
return !$module_extension_list->exists($extension_name) && !in_array($extension_name, array_keys(DRUPAL_CORE_REMOVED_MODULE_LIST), TRUE);
};
$invalid_modules = array_filter(array_keys($extension_config->get('module')), $is_missing_module);
if (!empty($invalid_modules)) { if (!empty($invalid_modules)) {
$requirements['invalid_module'] = $create_extension_incompatibility_list( $requirements['invalid_module'] = $create_extension_incompatibility_list(
@ -1160,7 +1261,7 @@ function system_requirements($phase) {
// Look for invalid themes. // Look for invalid themes.
$is_missing_theme = function ($extension_name) use (&$theme_extension_list) { $is_missing_theme = function ($extension_name) use (&$theme_extension_list) {
return !$theme_extension_list->exists($extension_name); return !$theme_extension_list->exists($extension_name) && !in_array($extension_name, array_keys(DRUPAL_CORE_REMOVED_THEME_LIST), TRUE);
}; };
$invalid_themes = array_filter(array_keys($extension_config->get('theme')), $is_missing_theme); $invalid_themes = array_filter(array_keys($extension_config->get('theme')), $is_missing_theme);
if (!empty($invalid_themes)) { if (!empty($invalid_themes)) {

View File

@ -224,7 +224,7 @@ class UpdateScriptTest extends BrowserTestBase {
* *
* @dataProvider providerExtensionCompatibilityChange * @dataProvider providerExtensionCompatibilityChange
*/ */
public function testExtensionCompatibilityChange(array $correct_info, array $breaking_info, $expected_error) { public function testExtensionCompatibilityChange(array $correct_info, array $breaking_info, string $expected_error): void {
$extension_type = $correct_info['type']; $extension_type = $correct_info['type'];
$this->drupalLogin( $this->drupalLogin(
$this->drupalCreateUser( $this->drupalCreateUser(
@ -236,8 +236,9 @@ class UpdateScriptTest extends BrowserTestBase {
) )
); );
$extension_machine_name = "changing_extension"; $extension_machine_names = ['changing_extension'];
$extension_name = "$extension_machine_name name"; $extension_name = "$extension_machine_names[0] name";
$test_error_urls = ['https://www.drupal.org/docs/updating-drupal/troubleshooting-database-updates'];
$test_error_text = "Incompatible $extension_type " $test_error_text = "Incompatible $extension_type "
. $expected_error . $expected_error
@ -247,25 +248,28 @@ class UpdateScriptTest extends BrowserTestBase {
if ($extension_type === 'theme') { if ($extension_type === 'theme') {
$base_info['base theme'] = FALSE; $base_info['base theme'] = FALSE;
} }
$folder_path = \Drupal::getContainer()->getParameter('site.path') . "/{$extension_type}s/$extension_machine_name"; $folder_path = \Drupal::getContainer()->getParameter('site.path') . "/{$extension_type}s/$extension_machine_names[0]";
$file_path = "$folder_path/$extension_machine_name.info.yml"; $file_path = "$folder_path/$extension_machine_names[0].info.yml";
mkdir($folder_path, 0777, TRUE); mkdir($folder_path, 0777, TRUE);
file_put_contents($file_path, Yaml::encode($base_info + $correct_info)); file_put_contents($file_path, Yaml::encode($base_info + $correct_info));
$this->enableExtension($extension_type, $extension_machine_name, $extension_name); $this->enableExtensions($extension_type, $extension_machine_names, [$extension_name]);
$this->assertInstalledExtensionConfig($extension_type, $extension_machine_name); $this->assertInstalledExtensionsConfig($extension_type, $extension_machine_names);
// If there are no requirements warnings or errors, we expect to be able to // If there are no requirements warnings or errors, we expect to be able to
// go through the update process uninterrupted. // go through the update process uninterrupted.
$this->assertUpdateWithNoError($test_error_text, $extension_type, $extension_machine_name); $this->drupalGet($this->statusReportUrl);
$this->assertUpdateWithNoErrors([$test_error_text], $extension_type, $extension_machine_names);
// Change the values in the info.yml and confirm updating is not possible. // Change the values in the info.yml and confirm updating is not possible.
file_put_contents($file_path, Yaml::encode($base_info + $breaking_info)); file_put_contents($file_path, Yaml::encode($base_info + $breaking_info));
$this->assertErrorOnUpdate($test_error_text, $extension_type, $extension_machine_name); $this->drupalGet($this->statusReportUrl);
$this->assertErrorOnUpdates([$test_error_text], $extension_type, $extension_machine_names, $test_error_urls);
// Fix the values in the info.yml file and confirm updating is possible // Fix the values in the info.yml file and confirm updating is possible
// again. // again.
file_put_contents($file_path, Yaml::encode($base_info + $correct_info)); file_put_contents($file_path, Yaml::encode($base_info + $correct_info));
$this->assertUpdateWithNoError($test_error_text, $extension_type, $extension_machine_name); $this->drupalGet($this->statusReportUrl);
$this->assertUpdateWithNoErrors([$test_error_text], $extension_type, $extension_machine_names);
} }
/** /**
@ -329,53 +333,165 @@ class UpdateScriptTest extends BrowserTestBase {
/** /**
* Tests that a missing extension prevents updates. * Tests that a missing extension prevents updates.
* *
* @param string $extension_type * @param array $core
* The extension type, either 'module' or 'theme'. * An array keyed by 'module' and 'theme' where each sub array contains
* a list of extension machine names.
* @param array $contrib
* An array keyed by 'module' and 'theme' where each sub array contains
* a list of extension machine names.
* *
* @dataProvider providerMissingExtension * @dataProvider providerMissingExtension
*/ */
public function testMissingExtension($extension_type) { public function testMissingExtension(array $core, array $contrib): void {
$this->drupalLogin( $this->drupalLogin(
$this->drupalCreateUser( $this->drupalCreateUser(
[ [
'administer software updates', 'administer software updates',
'administer site configuration', 'administer site configuration',
$extension_type === 'module' ? 'administer modules' : 'administer themes', 'administer modules',
'administer themes',
] ]
) )
); );
$extension_machine_name = "disappearing_$extension_type";
$extension_name = 'The magically disappearing extension'; $all_extensions_info = [];
$test_error_text = "Missing or invalid $extension_type " $file_paths = [];
. "The following $extension_type is marked as installed in the core.extension configuration, but it is missing:" $test_error_texts = [];
. $extension_machine_name $test_error_urls = [];
. static::HANDBOOK_MESSAGE; $extension_base_info = [
$extension_info = [ 'version' => 'VERSION',
'name' => $extension_name,
'type' => $extension_type,
'core_version_requirement' => '^8 || ^9 || ^10', 'core_version_requirement' => '^8 || ^9 || ^10',
]; ];
if ($extension_type === 'theme') {
// For each core extension create and error of info.yml information and
// the expected error message.
foreach ($core as $type => $extensions) {
$removed_list = [];
$error_url = 'https://www.drupal.org/node/3223395#s-recommendations-for-deprecated-modules';
$extension_base_info += ['package' => 'Core'];
if ($type === 'module') {
$removed_core_list = \DRUPAL_CORE_REMOVED_MODULE_LIST;
}
else {
$removed_core_list = \DRUPAL_CORE_REMOVED_THEME_LIST;
}
foreach ($extensions as $extension) {
$extension_info = $extension_base_info +
[
'name' => "The magically disappearing core $type $extension",
'type' => $type,
];
if ($type === 'theme') {
$extension_info['base theme'] = FALSE; $extension_info['base theme'] = FALSE;
} }
$folder_path = \Drupal::getContainer()->getParameter('site.path') . "/{$extension_type}s/$extension_machine_name"; $all_extensions_info[$extension] = $extension_info;
$file_path = "$folder_path/$extension_machine_name.info.yml"; $removed_list[] = $removed_core_list[$extension];
}
// Create the requirements test message.
if (!empty($extensions)) {
$handbook_message = "For more information read the documentation on deprecated {$type}s.";
if (count($removed_list) === 1) {
$test_error_texts[$type][] = "Removed core {$type} "
. "You must add the following contributed $type and reload this page."
. implode($removed_list)
. "This $type is installed on your site but is no longer provided by Core."
. $handbook_message;
}
else {
$test_error_texts[$type][] = "Removed core {$type}s "
. "You must add the following contributed {$type}s and reload this page."
. implode($removed_list)
. "These {$type}s are installed on your site but are no longer provided by Core."
. $handbook_message;
}
$test_error_urls[$type][] = $error_url;
}
}
// For each contrib extension create and error of info.yml information and
// the expected error message.
foreach ($contrib as $type => $extensions) {
unset($extension_base_info['package']);
$handbook_message = 'Review the suggestions for resolving this incompatibility to repair your installation, and then re-run update.php.';
$error_url = 'https://www.drupal.org/docs/updating-drupal/troubleshooting-database-updates';
foreach ($extensions as $extension) {
$extension_info = $extension_base_info +
[
'name' => "The magically disappearing contrib $type $extension",
'type' => $type,
];
if ($type === 'theme') {
$extension_info['base theme'] = FALSE;
}
$all_extensions_info[$extension] = $extension_info;
}
// Create the requirements test message.
if (!empty($extensions)) {
if (count($extensions) === 1) {
$test_error_texts[$type][] = "Missing or invalid {$type} "
. "The following {$type} is marked as installed in the core.extension configuration, but it is missing:"
. implode($extensions)
. $handbook_message;
}
else {
$test_error_texts[$type][] = "Missing or invalid {$type}s "
. "The following {$type}s are marked as installed in the core.extension configuration, but they are missing:"
. implode($extensions)
. $handbook_message;
}
$test_error_urls[$type][] = $error_url;
}
}
// Create the info.yml files for each extension.
foreach ($all_extensions_info as $machine_name => $extension_info) {
$type = $extension_info['type'];
$folder_path = \Drupal::getContainer()->getParameter('site.path') . "/{$type}s/contrib/$machine_name";
$file_path = "$folder_path/$machine_name.info.yml";
mkdir($folder_path, 0777, TRUE); mkdir($folder_path, 0777, TRUE);
file_put_contents($file_path, Yaml::encode($extension_info)); file_put_contents($file_path, Yaml::encode($extension_info));
$this->enableExtension($extension_type, $extension_machine_name, $extension_name); $file_paths[$machine_name] = $file_path;
}
// Enable all the extensions.
foreach ($all_extensions_info as $machine_name => $extension_info) {
$extension_machine_names = [$machine_name];
$extension_names = [$extension_info['name']];
$this->enableExtensions($extension_info['type'], $extension_machine_names, $extension_names);
}
// If there are no requirements warnings or errors, we expect to be able to // If there are no requirements warnings or errors, we expect to be able to
// go through the update process uninterrupted. // go through the update process uninterrupted.
$this->assertUpdateWithNoError($test_error_text, $extension_type, $extension_machine_name); $this->drupalGet($this->statusReportUrl);
$types = ['module', 'theme'];
foreach ($types as $type) {
$all = array_merge($core[$type], $contrib[$type]);
$this->assertUpdateWithNoErrors($test_error_texts[$type], $type, $all);
}
// Delete the info.yml and confirm updates are prevented. // Delete the info.yml(s) and confirm updates are prevented.
foreach ($file_paths as $file_path) {
unlink($file_path); unlink($file_path);
$this->assertErrorOnUpdate($test_error_text, $extension_type, $extension_machine_name); }
$this->drupalGet($this->statusReportUrl);
foreach ($types as $type) {
$all = array_merge($core[$type], $contrib[$type]);
$this->assertErrorOnUpdates($test_error_texts[$type], $type, $all, $test_error_urls[$type]);
}
// Add the info.yml file back and confirm we are able to go through the // Add the info.yml file(s) back and confirm we are able to go through the
// update process uninterrupted. // update process uninterrupted.
file_put_contents($file_path, Yaml::encode($extension_info)); foreach ($all_extensions_info as $machine_name => $extension_info) {
$this->assertUpdateWithNoError($test_error_text, $extension_type, $extension_machine_name); file_put_contents($file_paths[$machine_name], Yaml::encode($extension_info));
}
$this->drupalGet($this->statusReportUrl);
foreach ($types as $type) {
$all = array_merge($core[$type], $contrib[$type]);
$this->assertUpdateWithNoErrors($test_error_texts[$type], $type, $all);
}
} }
/** /**
@ -427,12 +543,44 @@ class UpdateScriptTest extends BrowserTestBase {
} }
/** /**
* Data provider for testMissingExtension(). * Data provider for ::testMissingExtension().
*
* @return array[]
* Set of testcases to pass to the test method.
*/ */
public function providerMissingExtension() { public function providerMissingExtension(): array {
return [ return [
'core only' => [
'core' => [
'module' => ['aggregator'],
'theme' => ['seven'],
],
'contrib' => [
'module' => [],
'theme' => [],
],
],
'contrib only' => [
'core' => [
'module' => [],
'theme' => [],
],
'contrib' => [
'module' => ['module'], 'module' => ['module'],
'theme' => ['theme'], 'theme' => ['theme'],
],
],
'core and contrib' =>
[
'core' => [
'module' => ['aggregator', 'rdf'],
'theme' => ['seven'],
],
'contrib' => [
'module' => ['module_a', 'module_b'],
'theme' => ['theme_a', 'theme_b'],
],
],
]; ];
} }
@ -441,24 +589,57 @@ class UpdateScriptTest extends BrowserTestBase {
* *
* @param string $extension_type * @param string $extension_type
* The extension type. * The extension type.
* @param string $extension_machine_name * @param array $extension_machine_names
* The extension machine name. * An array of the extension machine names.
* @param string $extension_name * @param array $extension_names
* The extension name. * An array of extension names.
*/ */
protected function enableExtension($extension_type, $extension_machine_name, $extension_name) { protected function enableExtensions(string $extension_type, array $extension_machine_names, array $extension_names): void {
if ($extension_type === 'module') { if ($extension_type === 'module') {
$edit = [ $edit = [];
"modules[$extension_machine_name][enable]" => $extension_machine_name, foreach ($extension_machine_names as $extension_machine_name) {
]; $edit["modules[$extension_machine_name][enable]"] = $extension_machine_name;
}
$this->drupalGet('admin/modules'); $this->drupalGet('admin/modules');
$this->submitForm($edit, 'Install'); $this->submitForm($edit, 'Install');
} }
elseif ($extension_type === 'theme') { elseif ($extension_type === 'theme') {
$this->drupalGet('admin/appearance'); $this->drupalGet('admin/appearance');
foreach ($extension_names as $extension_name) {
$this->click("a[title~=\"$extension_name\"]"); $this->click("a[title~=\"$extension_name\"]");
} }
} }
}
/**
* Enables extensions the UI.
*
* @param array $extension_info
* An array of extension information arrays. The array is keyed by 'module'
* and 'theme'.
*/
protected function enableMissingExtensions(array $extension_info): void {
$edit = [];
foreach ($extension_info as $info) {
if ($info['type'] === 'module') {
$machine_name = $info['machine_name'];
$edit["modules[$machine_name][enable]"] = $machine_name;
}
if (!empty($edit)) {
$this->drupalGet('admin/modules');
$this->submitForm($edit, 'Install');
}
}
if (isset($extension_info['theme'])) {
$this->drupalGet('admin/appearance');
foreach ($extension_info as $info) {
if ($info['type' === 'theme']) {
$this->click('a[title~="' . $info['name'] . '"]');
}
}
}
}
/** /**
* Tests the effect of using the update script on the theme system. * Tests the effect of using the update script on the theme system.
@ -779,70 +960,83 @@ class UpdateScriptTest extends BrowserTestBase {
* *
* @param string $extension_type * @param string $extension_type
* The extension type, either 'module' or 'theme'. * The extension type, either 'module' or 'theme'.
* @param string $extension_machine_name * @param array $extension_machine_names
* The extension machine name. * An array of the extension machine names.
* *
* @internal * @internal
*/ */
protected function assertInstalledExtensionConfig(string $extension_type, string $extension_machine_name): void { protected function assertInstalledExtensionsConfig(string $extension_type, array $extension_machine_names): void {
$extension_config = $this->container->get('config.factory')->getEditable('core.extension'); $extension_config = $this->container->get('config.factory')->getEditable('core.extension');
foreach ($extension_machine_names as $extension_machine_name) {
$this->assertSame(0, $extension_config->get("$extension_type.$extension_machine_name")); $this->assertSame(0, $extension_config->get("$extension_type.$extension_machine_name"));
} }
}
/** /**
* Asserts a particular error is not shown on update and status report pages. * Asserts particular errors are not shown on update and status report pages.
* *
* @param string $unexpected_error_text * @param array $unexpected_error_texts
* The error text that should not be shown. * An array of the error texts that should not be shown.
* @param string $extension_type * @param string $extension_type
* The extension type, either 'module' or 'theme'. * The extension type, either 'module' or 'theme'.
* @param string $extension_machine_name * @param array $extension_machine_names
* The extension machine name. * An array of the extension machine names.
* *
* @throws \Behat\Mink\Exception\ResponseTextException * @throws \Behat\Mink\Exception\ResponseTextException
* *
* @internal * @internal
*/ */
protected function assertUpdateWithNoError(string $unexpected_error_text, string $extension_type, string $extension_machine_name): void { protected function assertUpdateWithNoErrors(array $unexpected_error_texts, string $extension_type, array $extension_machine_names): void {
$assert_session = $this->assertSession(); $assert_session = $this->assertSession();
$this->drupalGet($this->statusReportUrl); foreach ($unexpected_error_texts as $unexpected_error_text) {
$this->assertSession()->pageTextNotContains($unexpected_error_text); $this->assertSession()->pageTextNotContains($unexpected_error_text);
}
$this->drupalGet($this->updateUrl, ['external' => TRUE]); $this->drupalGet($this->updateUrl, ['external' => TRUE]);
foreach ($unexpected_error_texts as $unexpected_error_text) {
$this->assertSession()->pageTextNotContains($unexpected_error_text); $this->assertSession()->pageTextNotContains($unexpected_error_text);
}
$this->updateRequirementsProblem(); $this->updateRequirementsProblem();
$this->clickLink('Continue'); $this->clickLink('Continue');
$assert_session->pageTextContains('No pending updates.'); $assert_session->pageTextContains('No pending updates.');
$this->assertInstalledExtensionConfig($extension_type, $extension_machine_name); $this->assertInstalledExtensionsConfig($extension_type, $extension_machine_names);
} }
/** /**
* Asserts an error is shown on the update and status report pages. * Asserts errors are shown on the update and status report pages.
* *
* @param string $expected_error_text * @param array $expected_error_texts
* The expected error text. * The expected error texts.
* @param string $extension_type * @param string $extension_type
* The extension type, either 'module' or 'theme'. * The extension type, either 'module' or 'theme'.
* @param string $extension_machine_name * @param array $extension_machine_names
* The extension machine name. * The extension machine names.
* @param array $test_error_urls
* The URLs in the error texts.
* *
* @throws \Behat\Mink\Exception\ExpectationException * @throws \Behat\Mink\Exception\ExpectationException
* @throws \Behat\Mink\Exception\ResponseTextException * @throws \Behat\Mink\Exception\ResponseTextException
* *
* @internal * @internal
*/ */
protected function assertErrorOnUpdate(string $expected_error_text, string $extension_type, string $extension_machine_name): void { protected function assertErrorOnUpdates(array $expected_error_texts, string $extension_type, array $extension_machine_names, array $test_error_urls): void {
$assert_session = $this->assertSession(); $assert_session = $this->assertSession();
$this->drupalGet($this->statusReportUrl); foreach ($expected_error_texts as $expected_error_text) {
$this->assertSession()->pageTextContains($expected_error_text); $this->assertSession()->pageTextContains($expected_error_text);
}
foreach ($test_error_urls as $test_error_url) {
$this->assertSession()->linkByHrefExists($test_error_url);
}
// Reload the update page to ensure the extension with the breaking values // Reload the update page to ensure the extension with the breaking values
// has not been uninstalled or otherwise affected. // has not been uninstalled or otherwise affected.
for ($reload = 0; $reload <= 1; $reload++) { for ($reload = 0; $reload <= 1; $reload++) {
$this->drupalGet($this->updateUrl, ['external' => TRUE]); $this->drupalGet($this->updateUrl, ['external' => TRUE]);
foreach ($expected_error_texts as $expected_error_text) {
$this->assertSession()->pageTextContains($expected_error_text); $this->assertSession()->pageTextContains($expected_error_text);
}
$assert_session->linkNotExists('Continue'); $assert_session->linkNotExists('Continue');
} }
$this->assertInstalledExtensionConfig($extension_type, $extension_machine_name); $this->assertInstalledExtensionsConfig($extension_type, $extension_machine_names);
} }
} }