diff --git a/core/core.services.yml b/core/core.services.yml index 20f3ac145e..e6f968202a 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -551,7 +551,7 @@ services: class: Drupal\Core\Extension\ModuleRequiredByThemesUninstallValidator tags: - { name: module_install.uninstall_validator } - arguments: ['@string_translation', '@extension.list.module', '@extension.list.theme', '@config.factory'] + arguments: ['@string_translation', '@extension.list.module', '@extension.list.theme'] lazy: true theme_handler: class: Drupal\Core\Extension\ThemeHandler diff --git a/core/modules/system/src/ModuleDependencyMessageTrait.php b/core/modules/system/src/ModuleDependencyMessageTrait.php index 66165089e6..31d3f12c69 100644 --- a/core/modules/system/src/ModuleDependencyMessageTrait.php +++ b/core/modules/system/src/ModuleDependencyMessageTrait.php @@ -12,7 +12,7 @@ trait ModuleDependencyMessageTrait { /** - * Provides messages for missing or incompatible dependencies on modules. + * Provides messages for missing modules or incompatible dependencies. * * @param array $modules * The list of existing modules. diff --git a/core/modules/system/tests/src/Functional/Theme/ThemeUiTest.php b/core/modules/system/tests/src/Functional/Theme/ThemeUiTest.php index cdaaea5227..383acd46be 100644 --- a/core/modules/system/tests/src/Functional/Theme/ThemeUiTest.php +++ b/core/modules/system/tests/src/Functional/Theme/ThemeUiTest.php @@ -59,64 +59,74 @@ public function testModulePermissions() { * * @dataProvider providerTestThemeInstallWithModuleDependencies */ - public function testThemeInstallWithModuleDependencies($theme_name) { + public function testThemeInstallWithModuleDependencies( + $theme_name, + $first_expected_required_list_items, + $first_module_enable, + $first_confirm_checked, + $second_expected_required_list_items, + $second_module_enable, + $second_confirm_checked, + $disabled_attributes, + $required_by_messages, + $module_uninstall_message + ) { $assert_session = $this->assertSession(); $page = $this->getSession()->getPage(); $this->drupalGet('admin/appearance'); - $expected_requires_list_items = [ - 'Test Module Required by Theme (disabled)', - 'Test Another Module Required by Theme (disabled)', - ]; - $this->assertUninstallableTheme($expected_requires_list_items, $theme_name); + + $this->assertUninstallableTheme($first_expected_required_list_items, $theme_name); // Enable one of the two required modules. - $this->drupalPostForm('admin/modules', [ - 'modules[test_module_required_by_theme][enable]' => 1, - ], 'Install'); - $this->assertSession()->elementExists('css', '#edit-modules-test-module-required-by-theme-enable[checked]'); + $this->drupalPostForm('admin/modules', $first_module_enable, 'Install'); + foreach ($first_confirm_checked as $selector) { + $this->assertSession()->elementExists('css', $selector); + } $this->drupalGet('admin/appearance'); // Confirm the theme is still uninstallable due to a remaining module // dependency. - $expected_requires_list_items = [ - 'Test Another Module Required by Theme (disabled)', - ]; - $this->assertUninstallableTheme($expected_requires_list_items, $theme_name); - - $this->drupalPostForm('admin/modules', [ - 'modules[test_another_module_required_by_theme][enable]' => 1, - ], 'Install'); - $this->assertSession()->elementExists('css', '#edit-modules-test-another-module-required-by-theme-enable[checked]'); - $this->assertSession()->elementExists('css', '#edit-modules-test-module-required-by-theme-enable[checked]'); + $this->assertUninstallableTheme($second_expected_required_list_items, $theme_name); + $this->drupalPostForm('admin/modules', $second_module_enable, 'Install'); + foreach ($second_confirm_checked as $selector) { + $this->assertSession()->elementExists('css', $selector); + } // The theme should now be installable, so install it. $this->drupalGet('admin/appearance'); - $page->clickLink('Install Test Theme Depending on Modules theme'); + $page->clickLink("Install $theme_name theme"); $assert_session->addressEquals('admin/appearance'); - $assert_session->pageTextContains('The Test Theme Depending on Modules theme has been installed'); + $assert_session->pageTextContains("The $theme_name theme has been installed"); // Confirm that the dependee modules can't be uninstalled because an enabled // theme depends on them. $this->drupalGet('admin/modules/uninstall'); - $assert_session->elementExists('css', '[name="uninstall[test_module_required_by_theme]"][disabled]'); - $assert_session->elementExists('css', '[name="uninstall[test_another_module_required_by_theme]"][disabled]'); - $assert_session->elementTextContains('css', '[data-drupal-selector="edit-test-another-module-required-by-theme"] .item-list', 'Required by the theme: test theme depending on modules'); - $assert_session->elementTextContains('css', '[data-drupal-selector="edit-test-module-required-by-theme"] .item-list', 'Required by the theme: test theme depending on modules'); + foreach ($disabled_attributes as $attribute) { + $assert_session->elementExists('css', "[name=\"uninstall[$attribute]\"][disabled]"); + } + foreach ($required_by_messages as $selector => $message) { + $assert_session->elementTextContains('css', $selector, $message); + } // Uninstall the theme that depends on the modules, and confirm the modules // can now be uninstalled. $this->drupalGet('admin/appearance'); - $this->clickLink('Uninstall Test Theme Depending on Modules theme'); - $assert_session->pageTextContains('The Test Theme Depending on Modules theme has been uninstalled.'); + $theme_to_uninstall = $theme_name === 'Test Theme with a Base Theme Depending on Modules' ? 'Test Theme Depending on Modules' : $theme_name; + $this->clickLink("Uninstall $theme_to_uninstall theme"); + $assert_session->pageTextContains("The $theme_to_uninstall theme has been uninstalled."); $this->drupalGet('admin/modules/uninstall'); - $assert_session->elementExists('css', '[name="uninstall[test_module_required_by_theme]"]:not([disabled])'); - $assert_session->elementExists('css', '[name="uninstall[test_another_module_required_by_theme]"]:not([disabled])'); - $this->drupalPostForm('admin/modules/uninstall', [ - 'uninstall[test_module_required_by_theme]' => 1, - 'uninstall[test_another_module_required_by_theme]' => 1, - ], 'Uninstall'); - $confirmation_message = 'The following modules will be completely uninstalled from your site, and all data from these modules will be lost!test another module required by themetest module required by themeWould you like to continue with uninstalling the above?'; + + foreach ($disabled_attributes as $attribute) { + $assert_session->elementExists('css', "[name=\"uninstall[$attribute]\"]:not([disabled])"); + } + $to_uninstall = []; + foreach ($disabled_attributes as $attribute) { + $to_uninstall["uninstall[$attribute]"] = 1; + } + + $this->drupalPostForm('admin/modules/uninstall', $to_uninstall, 'Uninstall'); + $confirmation_message = 'The following modules will be completely uninstalled from your site, and all data from these modules will be lost!' . $module_uninstall_message . 'Would you like to continue with uninstalling the above?'; $assert_session->pageTextContains($confirmation_message); $page->pressButton('Uninstall'); $assert_session->pageTextContains('The selected modules have been uninstalled.'); @@ -127,8 +137,141 @@ public function testThemeInstallWithModuleDependencies($theme_name) { */ public function providerTestThemeInstallWithModuleDependencies() { return [ - 'test theme depending on modules' => ['Test Theme Depending on Modules'], - 'test theme with a base theme depending on modules' => ['Test Theme with a Base Theme Depending on Modules'], + 'test theme with a module dependency and base theme with a different module dependency' => [ + // $theme_name. + 'Test Theme with a Module Dependency and Base Theme with a Different Module Dependency', + // $first_expected_required_list_items. + [ + 'Help (disabled)', + 'Test Module Required by Theme (disabled)', + 'Test Another Module Required by Theme (disabled)', + ], + // $first_module_enable. + [ + 'modules[test_module_required_by_theme][enable]' => 1, + 'modules[test_another_module_required_by_theme][enable]' => 1, + ], + // $first_confirm_checked. + [ + '#edit-modules-test-module-required-by-theme-enable[checked]', + '#edit-modules-test-another-module-required-by-theme-enable[checked]', + ], + // $second_expected_required_list_items. + [ + 'Help (disabled)', + 'Test Module Required by Theme', + 'Test Another Module Required by Theme', + ], + // $second_module_enable. + [ + 'modules[help][enable]' => 1, + ], + // $second_confirm_checked. + [ + '#edit-modules-test-module-required-by-theme-enable[checked]', + '#edit-modules-test-another-module-required-by-theme-enable[checked]', + '#edit-modules-help-enable[checked]', + ], + // $disabled_attributes. + [ + 'help', + ], + // $required_by_messages. + [ + '[data-drupal-selector="edit-test-another-module-required-by-theme"] .item-list' => 'Required by the theme: test theme depending on modules', + '[data-drupal-selector="edit-test-module-required-by-theme"] .item-list' => 'Required by the theme: test theme depending on modules', + '[data-drupal-selector="edit-help"] .item-list' => 'Required by the theme: Test Theme with a Module Dependency and Base Theme with a Different Module Dependency', + + ], + // $module_uninstall_message. + 'help', + ], + 'test theme depending on modules' => [ + // $theme_name. + 'Test Theme Depending on Modules', + // $first_expected_required_list_items. + [ + 'Test Module Required by Theme (disabled)', + 'Test Another Module Required by Theme (disabled)', + ], + // $first_module_enable. + [ + 'modules[test_module_required_by_theme][enable]' => 1, + ], + // $first_confirm_checked. + [ + '#edit-modules-test-module-required-by-theme-enable[checked]', + ], + // $second_expected_required_list_items. + [ + 'Test Module Required by Theme', + 'Test Another Module Required by Theme (disabled)', + ], + // $second_module_enable. + [ + 'modules[test_another_module_required_by_theme][enable]' => 1, + ], + // $second_confirm_checked. + [ + '#edit-modules-test-module-required-by-theme-enable[checked]', + '#edit-modules-test-another-module-required-by-theme-enable[checked]', + ], + // $disabled_attributes. + [ + 'test_module_required_by_theme', + 'test_another_module_required_by_theme', + ], + // $required_by_messages. + [ + '[data-drupal-selector="edit-test-another-module-required-by-theme"] .item-list' => 'Required by the theme: test theme depending on modules', + '[data-drupal-selector="edit-test-module-required-by-theme"] .item-list' => 'Required by the theme: test theme depending on modules', + ], + // $module_uninstall_message. + 'test another module required by themetest module required by theme', + ], + 'test theme with a base theme depending on modules' => [ + // $theme_name. + 'Test Theme with a Base Theme Depending on Modules', + // $first_expected_required_list_items. + [ + 'Test Module Required by Theme (disabled)', + 'Test Another Module Required by Theme (disabled)', + ], + // $first_module_enable. + [ + 'modules[test_module_required_by_theme][enable]' => 1, + ], + // $first_confirm_checked. + [ + '#edit-modules-test-module-required-by-theme-enable[checked]', + ], + // $second_expected_required_list_items. + [ + 'Test Module Required by Theme', + 'Test Another Module Required by Theme (disabled)', + ], + // $second_module_enable. + [ + 'modules[test_another_module_required_by_theme][enable]' => 1, + ], + // $second_confirm_checked. + [ + '#edit-modules-test-module-required-by-theme-enable[checked]', + '#edit-modules-test-another-module-required-by-theme-enable[checked]', + ], + // $disabled_attributes. + [ + 'test_module_required_by_theme', + 'test_another_module_required_by_theme', + ], + // $required_by_messages. + [ + '[data-drupal-selector="edit-test-another-module-required-by-theme"] .item-list' => 'Required by the theme: test theme depending on modules', + '[data-drupal-selector="edit-test-module-required-by-theme"] .item-list' => 'Required by the theme: test theme depending on modules', + ], + // $module_uninstall_message. + 'test another module required by themetest module required by theme', + ], ]; } @@ -142,7 +285,8 @@ public function providerTestThemeInstallWithModuleDependencies() { */ protected function assertUninstallableTheme(array $expected_requires_list_items, $theme_name) { $theme_container = $this->getSession()->getPage()->find('css', "h3:contains(\"$theme_name\")")->getParent(); - $requires_list_items = $theme_container->find('css', '.theme-info__requires li'); + $requires_list_items = $theme_container->findAll('css', '.theme-info__requires li'); + $this->assertCount(count($expected_requires_list_items), $requires_list_items); foreach ($requires_list_items as $key => $item) { $this->assertSame($expected_requires_list_items[$key], $item->getText()); diff --git a/core/modules/system/tests/themes/test_theme_mixed_module_dependencies/test_theme_mixed_module_dependencies.info.yml b/core/modules/system/tests/themes/test_theme_mixed_module_dependencies/test_theme_mixed_module_dependencies.info.yml new file mode 100644 index 0000000000..19aa00e49c --- /dev/null +++ b/core/modules/system/tests/themes/test_theme_mixed_module_dependencies/test_theme_mixed_module_dependencies.info.yml @@ -0,0 +1,6 @@ +name: Test Theme with a Module Dependency and Base Theme with a Different Module Dependency +type: theme +core: 8.x +base theme: test_theme_depending_on_modules +dependencies: + - help diff --git a/core/tests/Drupal/KernelTests/Core/Theme/ThemeInstallerTest.php b/core/tests/Drupal/KernelTests/Core/Theme/ThemeInstallerTest.php index e0027056be..30a7ba7841 100644 --- a/core/tests/Drupal/KernelTests/Core/Theme/ThemeInstallerTest.php +++ b/core/tests/Drupal/KernelTests/Core/Theme/ThemeInstallerTest.php @@ -106,11 +106,21 @@ public function testInstallSubTheme() { */ public function testInstallNonExisting() { $name = 'non_existing_theme'; + + $themes = $this->themeHandler()->listInfo(); + $this->assertEmpty(array_keys($themes)); + + try { + $message = 'ThemeInstaller::install() throws UnknownExtensionException upon installing a non-existing theme.'; + $this->themeInstaller()->install([$name]); + $this->fail($message); + } + catch (UnknownExtensionException $e) { + $this->pass(get_class($e) . ': ' . $e->getMessage()); + } + $themes = $this->themeHandler()->listInfo(); $this->assertEmpty(array_keys($themes)); - $this->expectException(UnknownExtensionException::class); - $this->expectExceptionMessage('Unknown themes: non_existing_theme.'); - $this->themeInstaller()->install([$name]); } /** @@ -131,14 +141,49 @@ public function testInstallNameTooLong() { /** * Tests installing a theme with unmet module dependencies. + * + * @dataProvider providerTestInstallThemeWithUnmetModuleDependencies */ - public function testInstallThemeWithUnmetModuleDependencies() { - $name = 'test_theme_depending_on_modules'; + public function testInstallThemeWithUnmetModuleDependencies($theme_name, $missing_dependencies, $installed_modules) { + $this->container->get('module_installer')->install($installed_modules); $themes = $this->themeHandler()->listInfo(); $this->assertEmpty(array_keys($themes)); $this->expectException(MissingDependencyException::class); - $this->expectExceptionMessage('Unable to install theme: \'test_theme_depending_on_modules\' due to missing module dependencies: \'test_module_required_by_theme, test_another_module_required_by_theme.'); - $this->themeInstaller()->install([$name]); + $this->expectExceptionMessage("Unable to install theme: '$theme_name' due to missing module dependencies: '$missing_dependencies'"); + $this->themeInstaller()->install([$theme_name]); + } + + /** + * Data provider for testInstallThemeWithUnmetModuleDependencies(). + */ + public function providerTestInstallThemeWithUnmetModuleDependencies() { + return [ + 'test_theme_depending_on_modules' => [ + 'test_theme_depending_on_modules', + 'test_module_required_by_theme, test_another_module_required_by_theme.', + [], + ], + 'test_theme_with_a_base_theme_depending_on_modules' => [ + 'test_theme_with_a_base_theme_depending_on_modules', + 'test_module_required_by_theme, test_another_module_required_by_theme.', + [], + ], + 'test_theme_mixed_module_dependencies' => [ + 'test_theme_mixed_module_dependencies', + 'help, test_module_required_by_theme, test_another_module_required_by_theme.', + [], + ], + 'test_theme_mixed_module_dependencies - node enabled' => [ + 'test_theme_mixed_module_dependencies', + 'test_module_required_by_theme, test_another_module_required_by_theme.', + ['help'], + ], + 'test_theme_mixed_module_dependencies - test modules enabled' => [ + 'test_theme_mixed_module_dependencies', + 'help.', + ['test_module_required_by_theme', 'test_another_module_required_by_theme'], + ], + ]; } /**