From 095be78acf49c4fdc1a22a8a0365dbd69c40b741 Mon Sep 17 00:00:00 2001 From: Si Hobbs <38379-sime@users.noreply.drupalcode.org> Date: Wed, 26 Jun 2024 14:05:08 +0000 Subject: [PATCH] Issue #3365180 by sime, phenaproxima, earthday47, chrisfromredfin, pfrilling, andy-blum, fjgarlin: Handle Package Manager errors and warnings more elegantly --- phpstan.neon | 16 ++++-- project_browser.libraries.yml | 1 + src/Controller/BrowserController.php | 24 +++++--- src/InstallReadiness.php | 27 ++++++--- src/ProjectBrowserServiceProvider.php | 2 + sveltejs/public/build/bundle.js | Bin 329173 -> 329350 bytes sveltejs/public/build/bundle.js.map | Bin 302084 -> 302330 bytes sveltejs/src/Project/ActionButton.svelte | 10 +--- sveltejs/src/Project/AddInstallButton.svelte | 8 +-- sveltejs/src/ProjectBrowser.svelte | 33 ++++++----- sveltejs/src/constants.js | 4 +- .../src/ProjectBrowserTestServiceProvider.php | 9 +++ .../src/TestInstallReadiness.php | 53 ++++++++++++++++++ .../ProjectBrowserInstallerUiTest.php | 44 ++++++++++++++- .../PackageManagerFixtureUtilityTrait.php | 16 ++++++ 15 files changed, 195 insertions(+), 52 deletions(-) create mode 100644 tests/modules/project_browser_test/src/TestInstallReadiness.php diff --git a/phpstan.neon b/phpstan.neon index 77763d0c8..47b437cf9 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -10,7 +10,7 @@ parameters: # @see https://www.drupal.org/docs/develop/development-tools/phpstan/handling-unsafe-usage-of-new-static#s-ignoring-the-issue identifier: new.static - # @todo: Remove the following ignores when support is dropped for Drupal 10.2, which does not have recipes. + # @todo: Remove the following rules when support is dropped for Drupal 10.2, which does not have recipes. - message: "#^Access to constant COMPOSER_PROJECT_TYPE on an unknown class Drupal\\\\Core\\\\Recipe\\\\Recipe\\.$#" paths: @@ -20,9 +20,7 @@ parameters: reportUnmatched: false - message: "#^Call to static method [a-zA-Z]+\\(\\) on an unknown class Drupal\\\\Core\\\\Recipe\\\\Recipe[a-zA-Z]*\\.$#" - paths: - - src/RecipeActivator.php - - tests/src/Kernel/RecipeActivatorTest.php + path: src/RecipeActivator.php reportUnmatched: false - message: "#^Class Drupal\\\\Core\\\\Recipe\\\\RecipeAppliedEvent not found\\.$#" @@ -32,3 +30,13 @@ parameters: message: "#^Parameter \\$event of method Drupal\\\\project_browser\\\\RecipeActivator\\:\\:onApply\\(\\) has invalid type Drupal\\\\Core\\\\Recipe\\\\RecipeAppliedEvent\\.$#" path: src/RecipeActivator.php reportUnmatched: false + + - + message: "#^Call to static method createFromDirectory\\(\\) on an unknown class Drupal\\\\Core\\\\Recipe\\\\Recipe\\.$#" + path: tests/src/Kernel/RecipeActivatorTest.php + reportUnmatched: false + + - + message: "#^Call to static method processRecipe\\(\\) on an unknown class Drupal\\\\Core\\\\Recipe\\\\RecipeRunner\\.$#" + path: tests/src/Kernel/RecipeActivatorTest.php + reportUnmatched: false diff --git a/project_browser.libraries.yml b/project_browser.libraries.yml index 23f93402b..eb86d09ec 100644 --- a/project_browser.libraries.yml +++ b/project_browser.libraries.yml @@ -11,6 +11,7 @@ svelte: - core/drupal.debounce - core/drupal.dialog - core/drupal.announce + - core/drupal.message - core/once - project_browser/project_browser diff --git a/src/Controller/BrowserController.php b/src/Controller/BrowserController.php index 5874890af..7ddf97e6c 100644 --- a/src/Controller/BrowserController.php +++ b/src/Controller/BrowserController.php @@ -32,13 +32,13 @@ class BrowserController extends ControllerBase { * @param \Drupal\project_browser\EnabledSourceHandler $enabledSource * The enabled project browser source. * @param \Drupal\project_browser\InstallReadiness|null $installReadiness - * The installer service. + * The installer readiness service, if available. */ public function __construct( private readonly ModuleExtensionList $moduleList, private readonly RequestStack $requestStack, private readonly EnabledSourceHandler $enabledSource, - private readonly InstallReadiness|NULL $installReadiness, + private readonly ?InstallReadiness $installReadiness, ) {} /** @@ -90,6 +90,19 @@ class BrowserController extends ControllerBase { $active_plugins[$source->getPluginId()] = $source->getPluginDefinition()['label']; } + $package_manager = $this->installReadiness?->validatePackageManager() ?? [ + 'errors' => [], + 'warnings' => [], + ]; + if ($ui_install_enabled) { + $package_manager['available'] = array_key_exists('package_manager', $this->moduleList->getAllInstalledInfo()); + } + else { + // If installing through the UI is disabled, then as far as Svelte need be + // concerned, Package Manager isn't open for business. + $package_manager['available'] = FALSE; + } + return [ '#theme' => 'project_browser_main_app', '#attached' => [ @@ -99,8 +112,6 @@ class BrowserController extends ControllerBase { 'drupalSettings' => [ 'project_browser' => [ 'active_plugins' => $active_plugins, - 'drupal_version' => \Drupal::VERSION, - 'drupal_core_compatibility' => \Drupal::CORE_COMPATIBILITY, 'module_path' => $this->moduleHandler()->getModule('project_browser')->getPath(), 'origin_url' => $request->getSchemeAndHttpHost() . $request->getBaseUrl(), 'special_ids' => $this->getSpecialIds(), @@ -110,10 +121,7 @@ class BrowserController extends ControllerBase { 'development_options' => DevelopmentStatus::asOptions(), 'default_plugin_id' => $current_source->getPluginId(), 'current_sources_keys' => $current_sources_keys, - 'ui_install' => $ui_install_enabled, - 'stage_available' => $ui_install_enabled ? $this->installReadiness->installerAvailable() : FALSE, - 'pm_validation' => $ui_install_enabled ? $this->installReadiness->validatePackageManager() : TRUE, - 'package_manager_available' => array_key_exists('package_manager', $this->moduleList->getAllInstalledInfo()), + 'package_manager' => $package_manager, ], ], ], diff --git a/src/InstallReadiness.php b/src/InstallReadiness.php index 9d76dbde1..c3e6ae040 100644 --- a/src/InstallReadiness.php +++ b/src/InstallReadiness.php @@ -4,6 +4,7 @@ namespace Drupal\project_browser; use Drupal\package_manager\StatusCheckTrait; use Drupal\project_browser\ComposerInstaller\Installer; +use Drupal\system\SystemManager; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; /** @@ -21,22 +22,32 @@ class InstallReadiness { /** * Checks if the environment meets Package Manager install requirements. * - * @return false|string - * FALSE if no validation errors, otherwise an error message. + * @return array[] + * errors - an array of messages with severity 2 + * messages - all other messages below severity 2 (warnings) */ public function validatePackageManager() { - $text = ''; - $results = $this->runStatusCheck($this->installer, $this->eventDispatcher); - foreach ($results as $result) { + $errors = []; + $warnings = []; + foreach ($this->runStatusCheck($this->installer, $this->eventDispatcher) as $result) { $messages = $result->messages; $summary = $result->summary; - if ($summary) { array_unshift($messages, $summary); } - $text .= implode("\n", $messages) . "\n"; + $text = implode("\n", $messages); + + if ($result->severity === SystemManager::REQUIREMENT_ERROR) { + $errors[] = $text; + } + else { + $warnings[] = $text; + } } - return $text ?: FALSE; + return [ + 'errors' => $errors, + 'warnings' => $warnings, + ]; } /** diff --git a/src/ProjectBrowserServiceProvider.php b/src/ProjectBrowserServiceProvider.php index 0f6078cb9..e9b8ddd67 100644 --- a/src/ProjectBrowserServiceProvider.php +++ b/src/ProjectBrowserServiceProvider.php @@ -30,6 +30,8 @@ class ProjectBrowserServiceProvider extends ServiceProviderBase { */ public function alter(ContainerBuilder $container) { if (array_key_exists('package_manager', $container->getParameter('container.modules'))) { + parent::register($container); + $container->register(Installer::class, Installer::class) ->setAutowired(TRUE); diff --git a/sveltejs/public/build/bundle.js b/sveltejs/public/build/bundle.js index 6a1d627c3d6286b09a63eb7e5ac5d1bfdf05ca04..62ff98b2c5c813145a0aa6005f6d239811e13236 100644 GIT binary patch delta 938 zcmccGEz;I2vLR&2<T*?FCs!_woP1!h(&T?j`Pr?xxD<e3y8mV-nP#hH+pU%{vi-1i zboO?1ca8UT^aD~sdZ|T4`9;NgIjMQ+B^e5K3I+;lY6<~piW18bGjkG?a#E)+bYu~k zzV{20-1dd@n3n4~;8cmt7*xA#ZEY0{wildb+QiOcVqs-8z2G;a@^;UwOg~tdOw6`B z-(o6fVKuX`GB%!G_?uB-`~7=NHXJr4R%V(C)ykP^xE+bp>ggM2GV5;7dCs(uefqx7 zOl;eQ-!rXZVKTGazW+1RS5`)o?OT5`b+RxTZnyu(l*`CuYO#IYf2QL+jHcVmc$hb{ zGMQLz*A`+vC?|>2+uQ#bFrPllWNEbh<Ui)uEKEj5+qW^Zgt0SPZ0F@>sbpp}-`>v0 z@|2m$#B_U}5Q`2ki;=OF3CM%u+qcTIxbm}Dn8Em>`YivLSu9Oqd_7~9e?Surx9eH3 zT$T;cgU1TU!y4#LLJjPi8jSENPb|vI%u9#Gz2@}9FcuRfaFXE!0$>PZH*<Qy4`xw& z9M+}f7r7=TXK17<*xD&n!wuu)%uOvW2BxvpBE7_v6pe)Gd%{@MSyif2YZIpPg|o;q zb8^;B5A<ddlg4H*)Tv;b>(On7`-51!Lo!l}QWbI&D;1Jb6*7y9OH+##$}=*PGZa$O z(o&O46jJib^K$YNQ-GnPkei=Unv+^=r2uxTjzYD(LP=#os+EE|)LwOkT1{(&M{B1) ebYl{o?k~c`F@45%CjRLfznS^A2Zpn>tOEdiA1&no delta 778 zcmZqc6}j3ivLR%NfTNF(e|UVTXS}Cha0rky`Qs8LuF}l-%)H`~#GIVT9!q8m2l&Q^ zIr@0IIEHxo`^CEk1^EYol<^ki#+N1LWTqsRWaj5hzP==tE8a28(bLD#$;TC>U~=42 zey;e$vc$}s#H5_m=G>**bC)t|{xHBaPeDyhA;8hu+tJ-M-q+C&NChFx(wT1eg~@O` z>pZ6AdK%~s2I*A9;=<{FMVRHMzdOUiHT}+gCXVg0XPFw=nM{qgFSx=a%gSVGuzmkc zrg9cmb0aHb<LQBJjN;P`c$hi2N8e+z;V`nWGSgJ3R?bXQAlAO^51%m|U}rYBvX~z8 znNef=nRiUBEYlafV`ASP@tNrZE2G7B&!0@4ER3exFaKrAWn?ikf+@}U&oq~Z(Q-Q% z5A$ZA%Z#`07i2ythY>Z~XBse{KFefbx;^b5^J^9+L(}cS%q(H-jE37+aj{e~Ga77H z;bVEq%w%k_om+@ShnL0B%*q7pwe3OjEUx@4=9Un?#P-d4EPt6<EG%I1r;S+t0ZlZS zeCM9>_IKti8)Op@;SY`+>~U67S&*8arhp~<6>M#771WDMiZb)k)j=kp2juj3p)4lq z3P8XK1i7ij#i@DesYQB;DJdG|nRzMs<$5kfr3HyOdL<g_(`SUSsM{%~78T_esp}|I vE2u-+R#@$>)dZ;lInGK!9imC0cDi5&v+(rg%bED6bIUXFZfA^OX<Y{Zbb9)D diff --git a/sveltejs/public/build/bundle.js.map b/sveltejs/public/build/bundle.js.map index 4e7d62e19b17da4af044d9b40e3763cb0ff69e5b..f3937ede71587d42c8dff0624832aee1c020be67 100644 GIT binary patch delta 1420 zcmZ{iNo*Tc9L6>8J)5LiWY-IAX~|1dVh4@fC5JRM4m^*ij%{pe($Y35N~ZRk*o_xr zXHQes3kM{MCH`<C>8VmA6cA|IvQ-=aA*2FAoN0xG1ga1xL{L>`>_p?3W)3r>`7gik z``)~EZRh&+oiBYP{1gHz3va-ES$H09%0d^seq3nRR*nn9Yw-9oYO#y-1e0PcERV)n zLVr99lQy(poA_H;bp^Ioe7saDmdd^yFN{xR=m0%LySp35eaeiI%_-+|T$9_-Z)h8e zrc>^@cQ>%>hA(;07s6B9)*nRPDlZTS&_f#UK_4H6*a+$(;<*Ba_ufQ%w6{mlugzyn zn7&M}ZCu_)=GL_rFaFQ*tWqju3*(!?!29EZyE4#i1)GYx4w##>D&1yz<$luLu?<%( zmSiQB@xtM+1*e_TJTI5^1K=f}l1_WaU{pn(?gNW_X-uSxCamUWYW@X&jC!gTJYr?} z)qRzBZnX6*ohz7~50bTM$q?4rC~ULn+aeA6RkSy4V8>2oc!|@zGEdKQnk|>7d6~{; zvZ)N^s>)MSG+mr6<cdmKPmSh_>FFFVAEPx7rmmWUMY`c(gX*D6@cU!v(aO6wS}UJ@ z+YD<hs7bSoqrxw6HH`BR9mKtaGsYn?i1)M_Z+?G7nhN&G+Ug*F>xwX;4g7%<$TbmU zgZ)9#=ny5wBr(od9F!-B3zlCdmUgo+#MlsgFpRy{)2uJr7t(Iu#nD#05S3u^N4y)p zKZ~2S6YDsRy{Dw<=y2UmV^oYV7ODGI<*ckC7ksE<rzOHty&<@Cl(@A&@8Lz8HNs*c zQHH-di36VAi<_*>{|u9Q;l*}xne>M*$Z&987wLbPw7@@h;)S(Wa2qVx$>*BKM!pN| z$Uk>V3^NuMC0O|v?}MAe*rIhL$dNXbIjs$ikULGV-br>OtLkD*nrn=~45U$flCfl6 zRS%AdvVQZYn5^4aNRu9$|16U;lIa-ipCSj`8YPz2gF!K7j2Pj<-EYt?w_Mw%XT<yw zCg}}&J`5($U<W+;6@JJ)y~#Kt)mkMc&4l1`O3z>yCyzLFTue1-q9GA}7$Z)bWWuVW zAoO5IBk@A@<m-v$kRefK(ovH*S0gs-US+ZRLnJ@JPU~!t#TSBbtAn_-1Scg2nmGwy P&XEa$X})>#^Zx$;VY{%d delta 1260 zcmaKp+e;Kt9LJe+j!P}+s;OP<G8wpv16oL?i_vdpPwTj=+pey~77UxNvbH<BsBNuM z_FxoL4+ZsmsOa_&6cwikx<HQxJ@j1GOAtgv&_fV4v%6MaY97v<%lG`gpWpX<e^fd1 zu=3=6>9w~U0)gN@BcdC6Z(qN)zzj%r&LasUVUAD4l1Y5wm9DMc+hYtsK=(qw9_%%E zI2;V;g{7I4k&Pu2L$QfO+Qbl-QceQ~Ko5W#;CTtoamnR0V%b<C89S7Wi_ct&-ofj4 zC5ILa>ps2Lh=c>mK~uKAWqjsOK=Xr-8$Hl#{ldRZu;QMyNu2m5-LGz53Mtpu%X|NE zlZ*5!@`2<@JWEIx=Q5A6)B~q)N*mE4Scz}06PLK{BC|D1ausLl$y2FeaakL1%Q9Vy z(+%Xow#G$|^B;)TC~5jDdSOHfaZdk^iCad6i2o7ug7b-7CY~N%?lt7j&Q94qJ~5Us zN8AhUEbh#9!zV+e1C2PTK`Cbq**I|@+7cfdOOLq~dFl_44dpH#bS&pv?A+GL^6|z6 z%PHCB?U3Dt!t!yv`dHc^YDY+V4!?P+jB9<gl$ac6e01EAQUOvbnw+k8#Q<W82B3L# z&?Y&4@zHii&JyGbg0jNg<I%Na>ZnijchQTt=(v9v-&L_H@#7=iN!&VgwX2E_07}Rn zxIGkH>|I?c77U%B4UT=#*4?a$8=q+RD$>@C!Lw`~&Yh!9;rdQ{Xq~QY?~WEq<SUL? zFe9qs2k;kbUcSQC&_Pqf-L0(V(py#{UjCrj^4bXay-HDTBY1+KB~>K?5Wx$Bv>G$- z=?Xkw#!Qy%8s%8AhRN7c!M=*hayHvZS|ehvhaD^>*#WWZ0DHfJ7r3n+v`O`^hCs0@ zN9Q;4WLDoHTbI=q5DWqzz^NhDxXC)pXoZaW0Q&NoD?R|9HPiOvwsUmz)+XyAnrxBQ z=CzXy*)i~W9}c`t#T{|h;y9{;ZmRhF6syLcuV{(ThgoVhY45{_Id+)riQBzZi;hY5 GtnL@BX?E5C diff --git a/sveltejs/src/Project/ActionButton.svelte b/sveltejs/src/Project/ActionButton.svelte index 07a05da91..9c5099115 100644 --- a/sveltejs/src/Project/ActionButton.svelte +++ b/sveltejs/src/Project/ActionButton.svelte @@ -1,10 +1,6 @@ <script> import { onMount } from 'svelte'; - import { - ORIGIN_URL, - ALLOW_UI_INSTALL, - PM_VALIDATION_ERROR, - } from '../constants'; + import { ORIGIN_URL, PACKAGE_MANAGER } from '../constants'; import Loading from '../Loading.svelte'; import { openPopup, getCommandsPopupMessage } from '../popup'; import AddInstallButton from './AddInstallButton.svelte'; @@ -140,7 +136,7 @@ // should reflect that by adding a progress spinner and disabling actions. // The app will check periodically to see if the status has changed and // update the UI. - if (ALLOW_UI_INSTALL) { + if (PACKAGE_MANAGER.available) { showStatus(); } }); @@ -155,7 +151,7 @@ </ProjectStatusIndicator> {:else} <span> - {#if ALLOW_UI_INSTALL && !PM_VALIDATION_ERROR} + {#if PACKAGE_MANAGER.available && PACKAGE_MANAGER.errors.length === 0} {#if loading} <Loading positionAbsolute={true} inline={true} /> <LoadingEllipsis message={loadingPhase} /> diff --git a/sveltejs/src/Project/AddInstallButton.svelte b/sveltejs/src/Project/AddInstallButton.svelte index add2b0af9..c3b445d4c 100644 --- a/sveltejs/src/Project/AddInstallButton.svelte +++ b/sveltejs/src/Project/AddInstallButton.svelte @@ -1,10 +1,6 @@ <script> import { openPopup } from '../popup'; - import { - ORIGIN_URL, - PM_VALIDATION_ERROR, - PACKAGE_MANAGER_AVAILABLE, - } from '../constants'; + import { ORIGIN_URL, PACKAGE_MANAGER } from '../constants'; import ProjectButtonBase from './ProjectButtonBase.svelte'; export let project; @@ -164,7 +160,7 @@ downloadProject(true); } }} - disabled={PM_VALIDATION_ERROR && PACKAGE_MANAGER_AVAILABLE} + disabled={PACKAGE_MANAGER.errors.length > 0 && PACKAGE_MANAGER.available} > {alreadyAdded ? Drupal.t('Install') : Drupal.t('Add and Install')}<span class="visually-hidden">{project.title}</span diff --git a/sveltejs/src/ProjectBrowser.svelte b/sveltejs/src/ProjectBrowser.svelte index 32567ccd9..fd7862117 100644 --- a/sveltejs/src/ProjectBrowser.svelte +++ b/sveltejs/src/ProjectBrowser.svelte @@ -31,10 +31,8 @@ ORIGIN_URL, FULL_MODULE_PATH, SORT_OPTIONS, - ALLOW_UI_INSTALL, - PM_VALIDATION_ERROR, ACTIVE_PLUGINS, - PACKAGE_MANAGER_AVAILABLE, + PACKAGE_MANAGER, } from './constants'; // cspell:ignore tabwise @@ -117,18 +115,27 @@ $rowsCount = data[$activeTab].totalResults; if ( - PACKAGE_MANAGER_AVAILABLE && - PM_VALIDATION_ERROR && - typeof PM_VALIDATION_ERROR === 'string' && - ALLOW_UI_INSTALL + PACKAGE_MANAGER.available && + (PACKAGE_MANAGER.errors.length || PACKAGE_MANAGER.warnings.length) ) { const messenger = new Drupal.Message(); - messenger.add( - Drupal.t('Unable to download modules via the UI: !error', { - '!error': PM_VALIDATION_ERROR, - }), - { type: 'error' }, - ); + + if (PACKAGE_MANAGER.errors.length) { + PACKAGE_MANAGER.errors.forEach((e) => { + messenger.add(`Unable to download modules via the UI: ${e}`, { + type: 'error', + }); + }); + } + + if (PACKAGE_MANAGER.warnings.length) { + PACKAGE_MANAGER.warnings.forEach((e) => { + messenger.add( + `There may be issues which effect downloading modules: ${e}`, + { type: 'warning' }, + ); + }); + } } } else { rows = []; diff --git a/sveltejs/src/constants.js b/sveltejs/src/constants.js index efc6460c8..67326af8d 100644 --- a/sveltejs/src/constants.js +++ b/sveltejs/src/constants.js @@ -16,10 +16,8 @@ export const CURRENT_SOURCES_KEYS = drupalSettings.project_browser.current_sources_keys; export const ORIGIN_URL = drupalSettings.project_browser.origin_url; export const FULL_MODULE_PATH = `${ORIGIN_URL}/${drupalSettings.project_browser.module_path}`; -export const ALLOW_UI_INSTALL = drupalSettings.project_browser.ui_install; export const DARK_COLOR_SCHEME = matchMedia('(forced-colors: active)').matches && matchMedia('(prefers-color-scheme: dark)').matches; -export const PM_VALIDATION_ERROR = drupalSettings.project_browser.pm_validation; export const ACTIVE_PLUGINS = drupalSettings.project_browser.active_plugins; -export const PACKAGE_MANAGER_AVAILABLE = drupalSettings.project_browser.package_manager_available; +export const PACKAGE_MANAGER = drupalSettings.project_browser.package_manager; diff --git a/tests/modules/project_browser_test/src/ProjectBrowserTestServiceProvider.php b/tests/modules/project_browser_test/src/ProjectBrowserTestServiceProvider.php index 20163bade..bb05089c6 100644 --- a/tests/modules/project_browser_test/src/ProjectBrowserTestServiceProvider.php +++ b/tests/modules/project_browser_test/src/ProjectBrowserTestServiceProvider.php @@ -4,6 +4,7 @@ namespace Drupal\project_browser_test; use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Core\DependencyInjection\ServiceProviderBase; +use Drupal\project_browser\InstallReadiness; /** * Overrides the module installer service. @@ -17,6 +18,14 @@ class ProjectBrowserTestServiceProvider extends ServiceProviderBase { $definition = $container->getDefinition('module_installer'); $definition->setClass('Drupal\project_browser_test\Extension\TestModuleInstaller') ->setLazy(FALSE); + + // The InstallReadiness service is defined by ProjectBrowserServiceProvider + // if Package Manager is installed. + if ($container->hasDefinition(InstallReadiness::class)) { + $container->register(TestInstallReadiness::class, TestInstallReadiness::class) + ->setAutowired(TRUE) + ->addTag('event_subscriber'); + } } } diff --git a/tests/modules/project_browser_test/src/TestInstallReadiness.php b/tests/modules/project_browser_test/src/TestInstallReadiness.php new file mode 100644 index 000000000..8dcacb2da --- /dev/null +++ b/tests/modules/project_browser_test/src/TestInstallReadiness.php @@ -0,0 +1,53 @@ +<?php + +namespace Drupal\project_browser_test; + +use Drupal\Core\State\StateInterface; +use Drupal\package_manager\Event\StatusCheckEvent; +use Drupal\system\SystemManager; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * Simulates status check results for Project Browser's installer. + */ +class TestInstallReadiness implements EventSubscriberInterface { + + public function __construct(private readonly StateInterface $state) { + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents(): array { + return [ + StatusCheckEvent::class => 'onStatusCheck', + ]; + } + + /** + * Sets simulated errors or warnings during a Project Browser status check. + * + * @param \Drupal\package_manager\Event\StatusCheckEvent $event + * The event object. + */ + public function onStatusCheck(StatusCheckEvent $event): void { + // We don't care about anything except Project Browser's installer. + if ($event->stage->getType() !== 'project_browser.installer') { + return; + } + + $severity = $this->state->get('project_browser_test.simulated_result_severity'); + + if ($severity === SystemManager::REQUIREMENT_ERROR) { + $event->addError([ + t('Simulate an error message for the project browser.'), + ]); + } + elseif ($severity === SystemManager::REQUIREMENT_WARNING) { + $event->addWarning([ + t('Simulate a warning message for the project browser.'), + ]); + } + } + +} diff --git a/tests/src/FunctionalJavascript/ProjectBrowserInstallerUiTest.php b/tests/src/FunctionalJavascript/ProjectBrowserInstallerUiTest.php index e0bc77d70..6f7d49f23 100644 --- a/tests/src/FunctionalJavascript/ProjectBrowserInstallerUiTest.php +++ b/tests/src/FunctionalJavascript/ProjectBrowserInstallerUiTest.php @@ -9,6 +9,7 @@ use Drupal\Core\Recipe\Recipe; use Drupal\Core\State\StateInterface; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; use Drupal\project_browser\EnabledSourceHandler; +use Drupal\system\SystemManager; use Drupal\Tests\project_browser\Traits\PackageManagerFixtureUtilityTrait; /** @@ -33,9 +34,6 @@ class ProjectBrowserInstallerUiTest extends WebDriverTestBase { * {@inheritdoc} */ protected static $modules = [ - 'package_manager_bypass', - 'package_manager', - 'package_manager_test_validation', 'project_browser', 'project_browser_test', ]; @@ -251,6 +249,46 @@ class ProjectBrowserInstallerUiTest extends WebDriverTestBase { $this->assertSame('✓ Cream cheese on a bagel is Installed', $installed_action->getText()); } + /** + * Confirm that a status check error prevents download and install. + */ + public function testPackageManagerErrorPreventsDownload(): void { + // @see \Drupal\project_browser_test\TestInstallReadiness + $this->container->get(StateInterface::class) + ->set('project_browser_test.simulated_result_severity', SystemManager::REQUIREMENT_ERROR); + + $assert_session = $this->assertSession(); + $this->drupalGet('admin/modules/browse'); + $this->svelteInitHelper('text', 'Cream cheese on a bagel'); + $assert_session->statusMessageContains("Simulate an error message for the project browser.", 'error'); + $cream_cheese_module_selector = '#project-browser .pb-layout__main ul > li:nth-child(1)'; + $download_button_text = $assert_session->waitForElementVisible('css', "$cream_cheese_module_selector button") + ?->getText(); + $this->assertSame('View Commands for Cream cheese on a bagel', $download_button_text); + } + + /** + * Confirm that a status check warning allows download and install. + */ + public function testPackageManagerWarningAllowsDownloadInstall(): void { + // @see \Drupal\project_browser_test\TestInstallReadiness + $this->container->get(StateInterface::class) + ->set('project_browser_test.simulated_result_severity', SystemManager::REQUIREMENT_WARNING); + + $assert_session = $this->assertSession(); + $this->drupalGet('admin/modules/browse'); + $this->svelteInitHelper('text', 'Cream cheese on a bagel'); + $assert_session->statusMessageContains("Simulate a warning message for the project browser.", 'warning'); + $cream_cheese_module_selector = '#project-browser .pb-layout__main ul > li:nth-child(1)'; + $download_button = $assert_session->waitForElementVisible('css', "$cream_cheese_module_selector button"); + $this->assertNotEmpty($download_button); + $this->assertSame('Add and Install Cream cheese on a bagel', $download_button->getText()); + $download_button->click(); + $installed = $assert_session->waitForElementVisible('css', "$cream_cheese_module_selector .project_status-indicator") + ?->waitFor(10, fn (NodeElement $button) => $button->getText() === '✓ Cream cheese on a bagel is Installed'); + $this->assertTrue($installed); + } + /** * Finds a project, from among the enabled sources, that can be installed. * diff --git a/tests/src/Traits/PackageManagerFixtureUtilityTrait.php b/tests/src/Traits/PackageManagerFixtureUtilityTrait.php index 7c10dfeef..ed77fa623 100644 --- a/tests/src/Traits/PackageManagerFixtureUtilityTrait.php +++ b/tests/src/Traits/PackageManagerFixtureUtilityTrait.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Drupal\Tests\project_browser\Traits; +use Drupal\Core\Extension\MissingDependencyException; use Drupal\package_manager\PathLocator; use Symfony\Component\Filesystem\Filesystem; @@ -22,6 +23,21 @@ trait PackageManagerFixtureUtilityTrait { * Initializes Package Manager. */ protected function initPackageManager(): void { + // @todo Move back to static::$modules in https://www.drupal.org/i/3349193. + $modules = [ + 'package_manager_bypass', + 'package_manager', + 'package_manager_test_validation', + ]; + try { + $this->container->get('module_installer')->install($modules); + // The container was rebuilt by the ModuleInstaller. + $this->container = \Drupal::getContainer(); + } + catch (MissingDependencyException $e) { + $this->markTestSkipped($e->getMessage()); + } + $pm_path = $this->container->get('extension.list.module')->getPath('package_manager'); $this->useFixtureDirectoryAsActive($pm_path . '/tests/fixtures/fake_site'); } -- GitLab