diff --git a/modules/project_browser_devel/src/Plugin/ProjectBrowserSource/RandomDataPlugin.php b/modules/project_browser_devel/src/Plugin/ProjectBrowserSource/RandomDataPlugin.php index 7b16a795fb73c873f5aed5c56a05e4dc15b1a86f..3fb484262afee880487f9dec31de2bd5f4297d56 100644 --- a/modules/project_browser_devel/src/Plugin/ProjectBrowserSource/RandomDataPlugin.php +++ b/modules/project_browser_devel/src/Plugin/ProjectBrowserSource/RandomDataPlugin.php @@ -145,7 +145,7 @@ class RandomDataPlugin extends ProjectBrowserSourceBase { NULL, ); $filters['developmentStatus'] = new BooleanFilter( - TRUE, + FALSE, $this->t('Show projects under active development'), $this->t('Show all'), $this->t('Development status'), diff --git a/modules/project_browser_source_example/src/Plugin/ProjectBrowserSource/ProjectBrowserSourceExample.php b/modules/project_browser_source_example/src/Plugin/ProjectBrowserSource/ProjectBrowserSourceExample.php index a8c47418a82ef9f771ad39e7994882a2bae176a5..c7c429eab83e7126891f89e65665681d1f9a73f5 100644 --- a/modules/project_browser_source_example/src/Plugin/ProjectBrowserSource/ProjectBrowserSourceExample.php +++ b/modules/project_browser_source_example/src/Plugin/ProjectBrowserSource/ProjectBrowserSourceExample.php @@ -140,7 +140,6 @@ class ProjectBrowserSourceExample extends ProjectBrowserSourceBase { isCompatible: TRUE, isMaintained: TRUE, isCovered: TRUE, - projectUsageTotal: 0, machineName: $project_from_source['unique_name'], body: [ 'summary' => $project_from_source['short_description'], @@ -161,7 +160,6 @@ class ProjectBrowserSourceExample extends ProjectBrowserSourceBase { isCompatible: TRUE, isMaintained: TRUE, isCovered: TRUE, - projectUsageTotal: 0, machineName: $project_from_source['unique_name'] . '2', body: [ 'summary' => $project_from_source['short_description'] . ' (different commands)', diff --git a/src/DevelopmentStatus.php b/src/DevelopmentStatus.php deleted file mode 100644 index 577db57d078d24711a5bfc32a82b08b2bcb2bc86..0000000000000000000000000000000000000000 --- a/src/DevelopmentStatus.php +++ /dev/null @@ -1,45 +0,0 @@ -<?php - -declare(strict_types=1); - -namespace Drupal\project_browser; - -use Drupal\Core\StringTranslation\TranslatableMarkup; - -/** - * The development statuses available to the project browser. - */ -enum DevelopmentStatus: string { - - case Active = '1'; - case All = '0'; - - /** - * Represents this enum as a set of options. - * - * @return array<string, \Drupal\Core\StringTranslation\TranslatableMarkup> - * The cases in this enum. The keys are the backing values, and the values - * are the translatable labels. - */ - public static function asOptions(): array { - $options = []; - foreach (self::cases() as $case) { - $options[$case->value] = $case->label(); - } - return $options; - } - - /** - * Returns a translatable label for the current case. - * - * @return \Drupal\Core\StringTranslation\TranslatableMarkup - * A translatable label. - */ - public function label(): TranslatableMarkup { - return match ($this) { - self::Active => t('Show projects under active development'), - self::All => t('Show all'), - }; - } - -} diff --git a/src/Element/ProjectBrowser.php b/src/Element/ProjectBrowser.php index 184179ef884e4addde65066ac15130920d67a12f..2a30bd88592754e8b8ae1cf62304ceead0180ebd 100644 --- a/src/Element/ProjectBrowser.php +++ b/src/Element/ProjectBrowser.php @@ -11,12 +11,9 @@ use Drupal\Core\Render\Attribute\RenderElement; use Drupal\Core\Render\Element; use Drupal\Core\Render\Element\ElementInterface; use Drupal\Core\Render\Element\RenderElementBase; -use Drupal\project_browser\DevelopmentStatus; use Drupal\project_browser\EnabledSourceHandler; use Drupal\project_browser\InstallReadiness; -use Drupal\project_browser\MaintenanceStatus; use Drupal\project_browser\Plugin\ProjectBrowserSourceInterface; -use Drupal\project_browser\SecurityStatus; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -116,12 +113,12 @@ final class ProjectBrowser implements ElementInterface, ContainerFactoryPluginIn * An array of Drupal settings. */ private function getDrupalSettings(string $source, ?int $max_selections = NULL): array { + $source = $this->enabledSourceHandler->getCurrentSources()[$source]; + assert($source instanceof ProjectBrowserSourceInterface); + if (is_int($max_selections) && $max_selections <= 0) { throw new \InvalidArgumentException('$max_selections must be a positive integer or NULL.'); } - $current_sources = [ - $source => $this->enabledSourceHandler->getCurrentSources()[$source], - ]; $package_manager = [ 'available' => (bool) $this->configFactory->get('project_browser.admin_settings')->get('allow_ui_install'), @@ -137,51 +134,16 @@ final class ProjectBrowser implements ElementInterface, ContainerFactoryPluginIn } return [ - 'active_plugins' => array_map( - fn (ProjectBrowserSourceInterface $source) => $source->getPluginDefinition()['label'], - $current_sources, - ), + 'active_plugin' => $source->getPluginDefinition()['label'], 'module_path' => $this->moduleHandler->getModule('project_browser')->getPath(), - 'special_ids' => $this->getSpecialIds(), - 'sort_options' => array_map( - fn (ProjectBrowserSourceInterface $source) => array_values($source->getSortOptions()), - $current_sources, - ), - 'maintenance_options' => MaintenanceStatus::asOptions(), - 'security_options' => SecurityStatus::asOptions(), - 'development_options' => DevelopmentStatus::asOptions(), - 'default_plugin_id' => reset($current_sources)->getPluginId(), + 'sort_options' => array_values($source->getSortOptions()), + 'default_plugin_id' => $source->getPluginId(), 'package_manager' => $package_manager, - 'filters' => array_map( - fn (ProjectBrowserSourceInterface $source) => $source->getFilterDefinitions(), - $current_sources, - ), + 'filters' => (object) $source->getFilterDefinitions(), 'max_selections' => $max_selections, ]; } - /** - * Return special IDs for some vocabularies. - * - * @return array - * List of special IDs per vocabulary. - */ - private static function getSpecialIds(): array { - $maintained = MaintenanceStatus::Maintained; - $covered = SecurityStatus::Covered; - return [ - 'maintenance_status' => [ - 'id' => $maintained->value, - 'name' => $maintained->label(), - ], - 'security_coverage' => [ - 'id' => $covered->value, - 'name' => $covered->label(), - ], - 'all_values' => MaintenanceStatus::All->value, - ]; - } - /** * {@inheritdoc} */ diff --git a/src/MaintenanceStatus.php b/src/MaintenanceStatus.php deleted file mode 100644 index 42cae92aa93135e981cd501e446d2c9d129ef072..0000000000000000000000000000000000000000 --- a/src/MaintenanceStatus.php +++ /dev/null @@ -1,45 +0,0 @@ -<?php - -declare(strict_types=1); - -namespace Drupal\project_browser; - -use Drupal\Core\StringTranslation\TranslatableMarkup; - -/** - * The maintenance statuses available to the project browser. - */ -enum MaintenanceStatus: string { - - case Maintained = '1'; - case All = '0'; - - /** - * Represents this enum as a set of options. - * - * @return array<string, \Drupal\Core\StringTranslation\TranslatableMarkup> - * The cases in this enum. The keys are the backing values, and the values - * are the translatable labels. - */ - public static function asOptions(): array { - $options = []; - foreach (self::cases() as $case) { - $options[$case->value] = $case->label(); - } - return $options; - } - - /** - * Returns a translatable label for the current case. - * - * @return \Drupal\Core\StringTranslation\TranslatableMarkup - * A translatable label. - */ - public function label(): TranslatableMarkup { - return match ($this) { - self::Maintained => t('Show actively maintained projects'), - self::All => t('Show all'), - }; - } - -} diff --git a/src/Plugin/ProjectBrowserSource/DrupalCore.php b/src/Plugin/ProjectBrowserSource/DrupalCore.php index 6a4d64e5f33b57fa469809e9f9672f3623e097d4..12aa0cb490320db4ae4a600511478306c7201a81 100644 --- a/src/Plugin/ProjectBrowserSource/DrupalCore.php +++ b/src/Plugin/ProjectBrowserSource/DrupalCore.php @@ -9,7 +9,6 @@ use Drupal\Core\Site\Settings; use Drupal\project_browser\Plugin\ProjectBrowserSourceBase; use Drupal\project_browser\ProjectBrowser\Project; use Drupal\project_browser\ProjectBrowser\ProjectsResultsPage; -use Drupal\project_browser\SecurityStatus; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\RequestStack; @@ -128,7 +127,7 @@ class DrupalCore extends ProjectBrowserSourceBase { } // Filter by coverage. - if (!empty($query['security_advisory_coverage']) && $query['security_advisory_coverage'] === SecurityStatus::Covered->value) { + if (!empty($query['security_advisory_coverage'])) { $projects = array_filter($projects, fn(Project $project) => $project->isCovered); } @@ -191,7 +190,6 @@ class DrupalCore extends ProjectBrowserSourceBase { isCompatible: TRUE, isMaintained: TRUE, isCovered: $module->info['package'] !== 'Core (Experimental)', - projectUsageTotal: -1, machineName: $module_name, body: [ 'summary' => $module->info['description'], diff --git a/src/Plugin/ProjectBrowserSource/DrupalDotOrgJsonApi.php b/src/Plugin/ProjectBrowserSource/DrupalDotOrgJsonApi.php index 70e1496b7eacf94cd00ab3b2a91f9c0e9888b71d..45fccfb8ad01b8fd5817ee24b5033b567d258f42 100644 --- a/src/Plugin/ProjectBrowserSource/DrupalDotOrgJsonApi.php +++ b/src/Plugin/ProjectBrowserSource/DrupalDotOrgJsonApi.php @@ -9,14 +9,11 @@ use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Extension\ExtensionVersion; use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\Core\Url; -use Drupal\project_browser\DevelopmentStatus; -use Drupal\project_browser\MaintenanceStatus; use Drupal\project_browser\Plugin\ProjectBrowserSourceBase; use Drupal\project_browser\ProjectBrowser\Filter\BooleanFilter; use Drupal\project_browser\ProjectBrowser\Filter\MultipleChoiceFilter; use Drupal\project_browser\ProjectBrowser\Project; use Drupal\project_browser\ProjectBrowser\ProjectsResultsPage; -use Drupal\project_browser\SecurityStatus; use GuzzleHttp\ClientInterface; use GuzzleHttp\Exception\GuzzleException; use Psr\Log\LoggerInterface; @@ -265,7 +262,7 @@ class DrupalDotOrgJsonApi extends ProjectBrowserSourceBase { NULL, ); $filters['developmentStatus'] = new BooleanFilter( - TRUE, + FALSE, $this->t('Show projects under active development'), $this->t('Show all'), $this->t('Development status'), @@ -672,27 +669,21 @@ class DrupalDotOrgJsonApi extends ProjectBrowserSourceBase { // Maintenance options. $maintenance = NULL; if (!empty($query['maintenance_status'])) { - if ($query['maintenance_status'] == MaintenanceStatus::Maintained->value) { - $maintenance = implode(',', $maintained_values); - } + $maintenance = implode(',', $maintained_values); } $query['maintenance_status'] = $maintenance; // Development options. $development = NULL; if (!empty($query['development_status'])) { - if ($query['development_status'] == DevelopmentStatus::Active->value) { - $development = implode(',', $active_values); - } + $development = implode(',', $active_values); } $query['development_status'] = $development; // Security options. $security = NULL; if (!empty($query['security_advisory_coverage'])) { - if ($query['security_advisory_coverage'] == SecurityStatus::Covered->value) { - $security = implode(',', self::COVERED_VALUES); - } + $security = implode(',', self::COVERED_VALUES); } $query['security_advisory_coverage'] = $security; diff --git a/src/Plugin/ProjectBrowserSource/Recipes.php b/src/Plugin/ProjectBrowserSource/Recipes.php index 967ab4b2d8aac69017a7e2ae7eb3720a85810da8..c10a934a346904bb98f92538b12b850be372e441 100644 --- a/src/Plugin/ProjectBrowserSource/Recipes.php +++ b/src/Plugin/ProjectBrowserSource/Recipes.php @@ -19,7 +19,6 @@ use Drupal\project_browser\Plugin\ProjectBrowserSourceBase; use Drupal\project_browser\ProjectBrowser\Project; use Drupal\project_browser\ProjectBrowser\ProjectsResultsPage; use Drupal\project_browser\ProjectType; -use Drupal\project_browser\SecurityStatus; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\Finder\Finder; @@ -112,9 +111,6 @@ class Recipes extends ProjectBrowserSourceBase { ]), ], isCompatible: TRUE, - isMaintained: TRUE, - isCovered: TRUE, - projectUsageTotal: 0, machineName: basename($path), body: $description ? ['value' => $description] : [], title: $recipe['name'], @@ -133,7 +129,7 @@ class Recipes extends ProjectBrowserSourceBase { } // Filter by coverage. - if (!empty($query['security_advisory_coverage']) && $query['security_advisory_coverage'] === SecurityStatus::Covered->value) { + if (!empty($query['security_advisory_coverage'])) { $projects = array_filter($projects, fn(Project $project) => $project->isCovered); } diff --git a/src/ProjectBrowser/Project.php b/src/ProjectBrowser/Project.php index 01e9dda2649da6f49d74c6b291213c17d2f50400..e03bdea3eddb2b2360355dd7341b5bb0824755a4 100644 --- a/src/ProjectBrowser/Project.php +++ b/src/ProjectBrowser/Project.php @@ -70,10 +70,6 @@ class Project implements \JsonSerializable { * Logo of the project. * @param bool $isCompatible * Whether the project is compatible with the current version of Drupal. - * @param bool $isCovered - * Whether the project is considered to be covered or not. - * @param int $projectUsageTotal - * Total usage of the project. * @param string $machineName * Value of project_machine_name of the project. * @param array $body @@ -84,8 +80,15 @@ class Project implements \JsonSerializable { * Author of the project in array format. * @param string $packageName * The Composer package name of this project, e.g. `drupal/project_browser`. - * @param bool $isMaintained - * Whether the project is considered to be maintained or not. + * @param int|null $projectUsageTotal + * (optional) Total number of sites known to be using this project, or NULL + * if this information is not known. Defaults to NULL. + * @param bool|null $isCovered + * (optional) Whether or not the project is covered by security advisories, + * or NULL if this information is not known. Defaults to NULL. + * @param bool|null $isMaintained + * (optional) Whether or not the project is considered maintained, or NULL + * if this information is not known. Defaults to NULL. * @param \Drupal\Core\Url|null $url * URL of the project, if any. Defaults to NULL. * @param array $categories @@ -107,14 +110,14 @@ class Project implements \JsonSerializable { public function __construct( public array $logo, public bool $isCompatible, - public bool $isCovered, - public int $projectUsageTotal, public string $machineName, private array $body, public string $title, public array $author, public string $packageName, - public bool $isMaintained = FALSE, + public ?int $projectUsageTotal = NULL, + public ?bool $isCovered = NULL, + public ?bool $isMaintained = NULL, public ?Url $url = NULL, public array $categories = [], public array $images = [], @@ -124,6 +127,10 @@ class Project implements \JsonSerializable { ) { $this->setSummary($body); + if (is_int($projectUsageTotal) && $projectUsageTotal < 0) { + throw new \InvalidArgumentException('The $projectUsageTotal argument cannot be a negative number.'); + } + if (is_string($type)) { // If the $type can't be mapped to a ProjectType case, use it as-is. $type = ProjectType::tryFrom($type) ?? $type; diff --git a/src/SecurityStatus.php b/src/SecurityStatus.php deleted file mode 100644 index 955975d82a178665621e7d555439e1db9190bb43..0000000000000000000000000000000000000000 --- a/src/SecurityStatus.php +++ /dev/null @@ -1,45 +0,0 @@ -<?php - -declare(strict_types=1); - -namespace Drupal\project_browser; - -use Drupal\Core\StringTranslation\TranslatableMarkup; - -/** - * The security coverage statuses available to the project browser. - */ -enum SecurityStatus: string { - - case Covered = '1'; - case All = '0'; - - /** - * Represents this enum as a set of options. - * - * @return array<string, \Drupal\Core\StringTranslation\TranslatableMarkup> - * The cases in this enum. The keys are the backing values, and the values - * are the translatable labels. - */ - public static function asOptions(): array { - $options = []; - foreach (self::cases() as $case) { - $options[$case->value] = $case->label(); - } - return $options; - } - - /** - * Returns a translatable label for the current case. - * - * @return \Drupal\Core\StringTranslation\TranslatableMarkup - * A translatable label. - */ - public function label(): TranslatableMarkup { - return match ($this) { - self::Covered => t('Show projects covered by a security policy'), - self::All => t('Show all'), - }; - } - -} diff --git a/sveltejs/public/build/bundle.js b/sveltejs/public/build/bundle.js index 01d1f1bf8badee2edff3b1f1df82cd51a0e46bde..f88935469ce32d580ef7621bca8538d94951cb69 100644 Binary files a/sveltejs/public/build/bundle.js and b/sveltejs/public/build/bundle.js differ diff --git a/sveltejs/public/build/bundle.js.map b/sveltejs/public/build/bundle.js.map index 7ff6140054aa9bcb6584a688d5dd76ff2429c12b..549b45caa54dda0bdfd51ecebcc69fc89310bd5c 100644 Binary files a/sveltejs/public/build/bundle.js.map and b/sveltejs/public/build/bundle.js.map differ diff --git a/sveltejs/src/DetailModal.svelte b/sveltejs/src/DetailModal.svelte index aa1795d0bb248d23096a6861c06826206bc25ec9..8a80fe2cf41f1cf00ce9e4162860086021afc599 100644 --- a/sveltejs/src/DetailModal.svelte +++ b/sveltejs/src/DetailModal.svelte @@ -79,7 +79,7 @@ {Drupal.t('Not compatible with your Drupal installation')} </div> {/if} - {#if project.project_usage_total !== -1} + {#if typeof project.project_usage_total === 'number'} <div class="pb-detail-modal__sidebar_element"> <ProjectIcon type="usage" diff --git a/sveltejs/src/MultipleChoiceFilter.svelte b/sveltejs/src/MultipleChoiceFilter.svelte index 90a5bbe02895fd791cd09d8743a5e3d38b0e75a8..647c692889acc7aa4cbf60286b05196cf1bbac4d 100644 --- a/sveltejs/src/MultipleChoiceFilter.svelte +++ b/sveltejs/src/MultipleChoiceFilter.svelte @@ -4,10 +4,9 @@ moduleCategoryFilter, moduleCategoryVocabularies, activeTab, - sourceFilters, } from './stores'; import { normalizeOptions, shallowCompare } from './util'; - import { BASE_URL } from './constants'; + import { BASE_URL, FILTERS } from './constants'; const { Drupal } = window; const dispatch = createEventDispatcher(); @@ -86,13 +85,13 @@ return; } // Tab without shift moves to next filter. - const keys = Object.keys($sourceFilters); - const filterMap = Object.fromEntries(Object.entries($sourceFilters)); + const keys = Object.keys(FILTERS); + const filterMap = Object.fromEntries(Object.entries(FILTERS)); const indexOfCategories = keys.indexOf('categories'); if (indexOfCategories !== -1 && indexOfCategories + 1 < keys.length) { const nextKey = keys[indexOfCategories + 1]; - const nextElement = $sourceFilters[nextKey]; + const nextElement = FILTERS[nextKey]; const nextElementKey = Object.keys(filterMap).find( (key) => filterMap[key] === nextElement, ); diff --git a/sveltejs/src/Project/Project.svelte b/sveltejs/src/Project/Project.svelte index 6f45f8eae2dc7f8c669a5f87bf340b9581ec7d74..f5550fd855bc59b8aeeeaa64d76912dbfb831893 100644 --- a/sveltejs/src/Project/Project.svelte +++ b/sveltejs/src/Project/Project.svelte @@ -75,18 +75,16 @@ <ProjectIcon type="maintained" /> </span> {/if} - {#if toggleView === 'Grid' && project.project_usage_total !== -1} - {#if project.project_usage_total !== 0} - <div class="pb-project__install-count-container"> - <span class="pb-project__install-count"> - {Drupal.formatPlural( - project.project_usage_total, - `${numberFormatter.format(1)} install`, - `${numberFormatter.format(project.project_usage_total)} installs`, - )} - </span> - </div> - {/if} + {#if toggleView === 'Grid' && typeof project.project_usage_total === 'number' && project.project_usage_total > 0} + <div class="pb-project__install-count-container"> + <span class="pb-project__install-count"> + {Drupal.formatPlural( + project.project_usage_total, + `${numberFormatter.format(1)} install`, + `${numberFormatter.format(project.project_usage_total)} installs`, + )} + </span> + </div> {/if} {#if project.warnings && project.warnings.length > 0} {#each project.warnings as warning} @@ -96,12 +94,12 @@ </span> {/each} {/if} - {#if toggleView === 'List' && project.project_usage_total !== -1} + {#if toggleView === 'List' && typeof project.project_usage_total === 'number'} <div class="pb-project__project-usage-container"> <div class="pb-project__image pb-project__image--{displayMode}"> <ProjectIcon type="usage" variant="project-listing" /> </div> - {#if project.project_usage_total !== 0} + {#if project.project_usage_total > 0} <div class="pb-project__active-installs-text"> {Drupal.formatPlural( project.project_usage_total, diff --git a/sveltejs/src/ProjectBrowser.svelte b/sveltejs/src/ProjectBrowser.svelte index c7636e424162e06625196904e73c4e400de1b337..224135af90995b598b92782fb3858f5e6a493210 100644 --- a/sveltejs/src/ProjectBrowser.svelte +++ b/sveltejs/src/ProjectBrowser.svelte @@ -24,13 +24,10 @@ } from './stores'; import MediaQuery from './MediaQuery.svelte'; import { - ACTIVELY_MAINTAINED_ID, - COVERED_ID, - ALL_VALUES_ID, DEFAULT_SOURCE_ID, BASE_URL, FULL_MODULE_PATH, - ACTIVE_PLUGINS, + ACTIVE_PLUGIN, PACKAGE_MANAGER, MAX_SELECTIONS, } from './constants'; @@ -100,14 +97,23 @@ // If no category filter is set, reset the tracking for the active tab. $categoryCheckedTrack[$activeTab] = []; } - if ($filters.developmentStatus && $filters.developmentStatus.length) { - searchParams.set('development_status', $filters.developmentStatus); + if ('developmentStatus' in $filters) { + searchParams.set( + 'development_status', + Number($filters.developmentStatus).toString(), + ); } - if ($filters.maintenanceStatus && $filters.maintenanceStatus.length) { - searchParams.set('maintenance_status', $filters.maintenanceStatus); + if ('maintenanceStatus' in $filters) { + searchParams.set( + 'maintenance_status', + Number($filters.maintenanceStatus).toString(), + ); } - if ($filters.securityCoverage && $filters.securityCoverage.length) { - searchParams.set('security_advisory_coverage', $filters.securityCoverage); + if ('securityCoverage' in $filters) { + searchParams.set( + 'security_advisory_coverage', + Number($filters.securityCoverage).toString(), + ); } if (Object.keys($categoryCheckedTrack).length !== 0) { searchParams.set( @@ -157,19 +163,6 @@ loading = false; } - async function filterRecommended() { - // Show recommended projects on initial page load only when no filters are applied. - if ( - $filters.developmentStatus.length === 0 && - $filters.maintenanceStatus.length === 0 && - $filters.securityCoverage.length === 0 - ) { - $filters.maintenanceStatus = ACTIVELY_MAINTAINED_ID; - $filters.securityCoverage = COVERED_ID; - $filters.developmentStatus = ALL_VALUES_ID; - } - } - /** * Load remote data when the Svelte component is mounted. */ @@ -191,8 +184,6 @@ projectId = null; } - await filterRecommended(); - await load($page); const focus = element ? document.getElementById(element) : false; if (focus) { @@ -236,11 +227,7 @@ await load(0); page.set(0); } - async function onAdvancedFilter(event) { - $filters.developmentStatus = event.detail.developmentStatus; - $filters.maintenanceStatus = event.detail.maintenanceStatus; - $filters.securityCoverage = event.detail.securityCoverage; - + async function onAdvancedFilter() { await load(0); page.set(0); } @@ -269,7 +256,7 @@ Drupal.t('@count Results for @active_tab, Sorted by @sortText', { '@count': numberFormatter.format($rowsCount), '@sortText': sortText, - '@active_tab': ACTIVE_PLUGINS[$activeTab], + '@active_tab': ACTIVE_PLUGIN, }), ); }, 210); diff --git a/sveltejs/src/Search/BooleanFilter.svelte b/sveltejs/src/Search/BooleanFilter.svelte index 5dd984b529fb9e5e6d9278948f674df2545cc040..906cf43cc1358acdbc05c8565762374959e929dc 100644 --- a/sveltejs/src/Search/BooleanFilter.svelte +++ b/sveltejs/src/Search/BooleanFilter.svelte @@ -16,7 +16,7 @@ bind:value={$filters[type]} on:change={changeHandler} > - <option value="1">{onLabel}</option> - <option value="0">{offLabel}</option> + <option value={true}>{onLabel}</option> + <option value={false}>{offLabel}</option> </select> </div> diff --git a/sveltejs/src/Search/FilterApplied.svelte b/sveltejs/src/Search/FilterApplied.svelte index 7524e1e8f50663be09574abf238eec22e43a16ed..80295a4707823bd1b90abaac25ca984ac67a2c6e 100644 --- a/sveltejs/src/Search/FilterApplied.svelte +++ b/sveltejs/src/Search/FilterApplied.svelte @@ -1,27 +1,24 @@ <script> - import { ALL_VALUES_ID, FULL_MODULE_PATH } from '../constants'; + import { FULL_MODULE_PATH } from '../constants'; - export let id; export let label; export let clickHandler; const { Drupal } = window; </script> -{#if id !== ALL_VALUES_ID} - <p class="filter-applied"> - <span class="filter-applied__label">{label}</span> - <button - type="button" - on:click={clickHandler} - class="filter-applied__button-close" - > - {#if label} - <span class="visually-hidden" - >{Drupal.t('Remove @filter', { '@filter': label })}</span - > - {/if} - <img src="{FULL_MODULE_PATH}/images/chip-close-icon.svg" alt="" /> - </button> - </p> -{/if} +<p class="filter-applied"> + <span class="filter-applied__label">{label}</span> + <button + type="button" + on:click={clickHandler} + class="filter-applied__button-close" + > + {#if label} + <span class="visually-hidden" + >{Drupal.t('Remove @filter', { '@filter': label })}</span + > + {/if} + <img src="{FULL_MODULE_PATH}/images/chip-close-icon.svg" alt="" /> + </button> +</p> diff --git a/sveltejs/src/Search/Search.svelte b/sveltejs/src/Search/Search.svelte index e588b8c8aaa062bfa75989801fe6397bf32c3324..d24d3fb2b60354838a6eeea1860c7e5901066b35 100644 --- a/sveltejs/src/Search/Search.svelte +++ b/sveltejs/src/Search/Search.svelte @@ -1,14 +1,11 @@ <script> - import { createEventDispatcher, getContext, onMount } from 'svelte'; + import { createEventDispatcher, getContext } from 'svelte'; import FilterApplied from './FilterApplied.svelte'; import BooleanFilter from './BooleanFilter.svelte'; - import { normalizeOptions, shallowCompare } from '../util'; import MultipleChoiceFilter from '../MultipleChoiceFilter.svelte'; import SearchSort from './SearchSort.svelte'; import { - sourceFilters, filters, - filtersVocabularies, moduleCategoryFilter, categoryCheckedTrack, moduleCategoryVocabularies, @@ -16,16 +13,7 @@ searchString, sortCriteria, } from '../stores'; - import { - COVERED_ID, - ACTIVELY_MAINTAINED_ID, - MAINTENANCE_OPTIONS, - DEVELOPMENT_OPTIONS, - SECURITY_OPTIONS, - ALL_VALUES_ID, - FULL_MODULE_PATH, - DARK_COLOR_SCHEME, - } from '../constants'; + import { FULL_MODULE_PATH, DARK_COLOR_SCHEME, FILTERS } from '../constants'; const { Drupal } = window; const dispatch = createEventDispatcher(); @@ -51,21 +39,6 @@ let sortText = sortMatch.text; let filterComponent; - const updateVocabularies = (vocabulary, value) => { - const normalizedValue = normalizeOptions(value); - const storedValue = JSON.parse(localStorage.getItem(`pb.${vocabulary}`)); - if (storedValue === null || !shallowCompare(normalizedValue, storedValue)) { - $filtersVocabularies[vocabulary] = normalizedValue; - localStorage.setItem(`pb.${vocabulary}`, JSON.stringify(normalizedValue)); - } - }; - - onMount(() => { - updateVocabularies('developmentStatus', DEVELOPMENT_OPTIONS); - updateVocabularies('maintenanceStatus', MAINTENANCE_OPTIONS); - updateVocabularies('securityCoverage', SECURITY_OPTIONS); - }); - export async function onSearch(event) { const state = stateContext.getState(); const detail = { @@ -79,7 +52,9 @@ rows: state.filteredRows, }; dispatch('search', detail); - filterComponent.setModuleCategoryVocabulary(); + if (filterComponent) { + filterComponent.setModuleCategoryVocabulary(); + } if (detail.preventDefault !== true) { if (detail.searchText.length === 0) { stateContext.setRows(state.rows); @@ -96,12 +71,19 @@ } const onAdvancedFilter = async (event) => { + if (event) { + const filterName = event.target.name; + + if (FILTERS[filterName]._type === 'boolean') { + $filters[filterName] = event.target.value === 'true'; + } else { + $filters[filterName] = event.target.value; + } + } + const state = stateContext.getState(); const detail = { originalEvent: event, - developmentStatus: $filters.developmentStatus, - maintenanceStatus: $filters.maintenanceStatus, - securityCoverage: $filters.securityCoverage, page: state.page, pageIndex: state.pageIndex, pageSize: state.pageSize, @@ -134,20 +116,30 @@ document.getElementById('pb-text').focus(); } + const filterDefinitions = Object.entries(FILTERS); + /** - * Actions performed when clicking filter resets such as "recommended" - * @param {string} maintenanceId - * ID of the selected maintenance status. - * @param {string} developmentId - * ID of the selected development status. - * @param {string} securityId - * ID of the selected security status. + * Resets the filters to the initial values provided by the source. + * + * @param {boolean} clear + * Whether to clear all filter values (i.e., not reset them to their defaults, + * but actually negate them all). */ - const filterResets = (maintenanceId, developmentId, securityId) => { - $filters.maintenanceStatus = maintenanceId; - $filters.developmentStatus = developmentId; - $filters.securityCoverage = securityId; - $filters = $filters; + const resetFilters = (clear) => { + $filters = {}; + filterDefinitions.forEach(([name, definition]) => { + let value; + if (clear) { + if (definition._type === 'boolean') { + value = false; + } else if (definition._type === 'multiple_choice') { + value = []; + } + } else { + value = definition.value; + } + $filters[name] = value; + }); $moduleCategoryFilter = []; $categoryCheckedTrack = {}; onAdvancedFilter(); @@ -209,10 +201,10 @@ </button> </div> </div> - {#if $sourceFilters.length !== 0} + {#if filterDefinitions.length !== 0} <div class="search__form-filters-container"> <div class="search__form-filters"> - {#each Object.entries($sourceFilters) as [filterType, filter]} + {#each filterDefinitions as [filterType, filter]} {#if filter._type === 'boolean'} <BooleanFilter name={filter.name} @@ -239,7 +231,6 @@ <div class="search__results-count"> {#each $moduleCategoryFilter as category} <FilterApplied - id={category} label={$moduleCategoryVocabularies[category]} clickHandler={() => { $moduleCategoryFilter.splice( @@ -252,26 +243,20 @@ /> {/each} - {#if $filters.securityCoverage !== ALL_VALUES_ID || $filters.maintenanceStatus !== ALL_VALUES_ID || $filters.developmentStatus !== ALL_VALUES_ID || $moduleCategoryFilter.length} + {#if $filters.securityCoverage || $filters.maintenanceStatus || $filters.developmentStatus || $moduleCategoryFilter.length} <button class="search__filter-button" type="button" - on:click|preventDefault={() => - filterResets(ALL_VALUES_ID, ALL_VALUES_ID, ALL_VALUES_ID)} + on:click|preventDefault={() => resetFilters(true)} > {Drupal.t('Clear filters')} </button> {/if} - {#if !($filters.maintenanceStatus === ACTIVELY_MAINTAINED_ID && $filters.securityCoverage === COVERED_ID && $filters.developmentStatus === ALL_VALUES_ID && $moduleCategoryFilter.length === 0)} + {#if !($filters.maintenanceStatus && $filters.securityCoverage && !$filters.developmentStatus && $moduleCategoryFilter.length === 0)} <button class="search__filter-button" type="button" - on:click|preventDefault={() => - filterResets( - ACTIVELY_MAINTAINED_ID, - ALL_VALUES_ID, - COVERED_ID, - )} + on:click|preventDefault={() => resetFilters()} > {Drupal.t('Recommended filters')} </button> diff --git a/sveltejs/src/constants.js b/sveltejs/src/constants.js index dd3ab86e9d88a93dd7d274595f59a0108d2d3b1f..bd2219ade5ba245dbb88de915a467e3ce5f91132 100644 --- a/sveltejs/src/constants.js +++ b/sveltejs/src/constants.js @@ -1,15 +1,4 @@ -export const MAINTENANCE_OPTIONS = - drupalSettings.project_browser.maintenance_options; -export const SECURITY_OPTIONS = drupalSettings.project_browser.security_options; -export const DEVELOPMENT_OPTIONS = - drupalSettings.project_browser.development_options; export const SORT_OPTIONS = drupalSettings.project_browser.sort_options; -export const ACTIVELY_MAINTAINED_ID = - drupalSettings.project_browser.special_ids.maintenance_status.id; -export const COVERED_ID = - drupalSettings.project_browser.special_ids.security_coverage.id; -export const ALL_VALUES_ID = - drupalSettings.project_browser.special_ids.all_values; export const DEFAULT_SOURCE_ID = drupalSettings.project_browser.default_plugin_id; export const BASE_URL = `${window.location.protocol}//${window.location.host}${drupalSettings.path.baseUrl + drupalSettings.path.pathPrefix}`; @@ -17,7 +6,7 @@ export const FULL_MODULE_PATH = `${BASE_URL}${drupalSettings.project_browser.mod export const DARK_COLOR_SCHEME = matchMedia('(forced-colors: active)').matches && matchMedia('(prefers-color-scheme: dark)').matches; -export const ACTIVE_PLUGINS = drupalSettings.project_browser.active_plugins; +export const ACTIVE_PLUGIN = drupalSettings.project_browser.active_plugin; export const PACKAGE_MANAGER = drupalSettings.project_browser.package_manager; export const FILTERS = drupalSettings.project_browser.filters || {}; export const MAX_SELECTIONS = drupalSettings.project_browser.max_selections; diff --git a/sveltejs/src/stores.js b/sveltejs/src/stores.js index 8ec141381f71a7f0d9806e96febe47ea4e786381..77196740905e6ba0be0e8bfc730a17988080912e 100644 --- a/sveltejs/src/stores.js +++ b/sveltejs/src/stores.js @@ -7,28 +7,16 @@ import { // Store the selected tab. const storedActiveTab = DEFAULT_SOURCE_ID; -let activeFilters = {}; -if (storedActiveTab in FILTERS) { - activeFilters = FILTERS[storedActiveTab]; -} -export const sourceFilters = writable(activeFilters); // Store for applied advanced filters. -const storedFilters = { - developmentStatus: '', - maintenanceStatus: '', - securityCoverage: '' -}; -export const filters = writable(storedFilters); +const defaultFilters = {}; +Object.entries(FILTERS).forEach(([name, definition]) => { + defaultFilters[name] = definition.value; +}); +export const filters = writable(defaultFilters); export const rowsCount = writable(0); -export const filtersVocabularies = writable({ - developmentStatus: JSON.parse(localStorage.getItem('pb.developmentStatus')) || [], - maintenanceStatus: JSON.parse(localStorage.getItem('pb.maintenanceStatus')) || [], - securityCoverage: JSON.parse(localStorage.getItem('pb.securityCoverage')) || [] -}); - // Store for applied category filters. const storedModuleCategoryFilter = []; export const moduleCategoryFilter = writable(storedModuleCategoryFilter); @@ -38,10 +26,6 @@ const storedModuleCategoryVocabularies = JSON.parse(localStorage.getItem('module export const moduleCategoryVocabularies = writable(storedModuleCategoryVocabularies); moduleCategoryVocabularies.subscribe((val) => localStorage.setItem('moduleCategoryVocabularies', JSON.stringify(val))); -// Store used to check if the page has loaded once already. -const storedIsFirstLoad = true; -export const isFirstLoad = writable(storedIsFirstLoad); - // Store the page the user is on. const storedPage = 0; export const page = writable(storedPage); @@ -49,7 +33,7 @@ export const page = writable(storedPage); export const activeTab = writable(storedActiveTab); // Store the current sort selected. -const storedSort = SORT_OPTIONS[storedActiveTab][0].id; +const storedSort = SORT_OPTIONS[0].id; export const sort = writable(storedSort); // Store tab-wise checked categories. @@ -65,7 +49,7 @@ const storedSearchString = ''; export const searchString = writable(storedSearchString); // Store for sort criteria. -const storedSortCriteria = SORT_OPTIONS[storedActiveTab]; +const storedSortCriteria = SORT_OPTIONS; export const sortCriteria = writable(storedSortCriteria); // Store the selected toggle view. diff --git a/tests/modules/project_browser_test/src/Plugin/ProjectBrowserSource/ProjectBrowserTestMock.php b/tests/modules/project_browser_test/src/Plugin/ProjectBrowserSource/ProjectBrowserTestMock.php index 081570f20346b7f22d30b5b8a1de65cad6b722ef..bfd5fea1900a1b96e830e117b18cea942dace920 100644 --- a/tests/modules/project_browser_test/src/Plugin/ProjectBrowserSource/ProjectBrowserTestMock.php +++ b/tests/modules/project_browser_test/src/Plugin/ProjectBrowserSource/ProjectBrowserTestMock.php @@ -9,14 +9,11 @@ use Drupal\Core\Database\Connection; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\State\StateInterface; use Drupal\Core\Url; -use Drupal\project_browser\DevelopmentStatus; -use Drupal\project_browser\MaintenanceStatus; use Drupal\project_browser\Plugin\ProjectBrowserSourceBase; use Drupal\project_browser\ProjectBrowser\Filter\BooleanFilter; use Drupal\project_browser\ProjectBrowser\Filter\MultipleChoiceFilter; use Drupal\project_browser\ProjectBrowser\Project; use Drupal\project_browser\ProjectBrowser\ProjectsResultsPage; -use Drupal\project_browser\SecurityStatus; use Psr\Log\LoggerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -218,23 +215,10 @@ class ProjectBrowserTestMock extends ProjectBrowserSourceBase { */ protected function convertMaintenance(array &$query) { if (!empty($query['maintenance_status'])) { - $options_available = MaintenanceStatus::asOptions(); - if (!in_array($query['maintenance_status'], array_keys($options_available))) { - unset($query['maintenance_status']); - } - else { - // Valid value. - switch ($query['maintenance_status']) { - case MaintenanceStatus::Maintained->value: - $query['maintenance_status'] = self::MAINTAINED_VALUES; - break; - - case MaintenanceStatus::All->value: - unset($query['maintenance_status']); - break; - - } - } + $query['maintenance_status'] = self::MAINTAINED_VALUES; + } + else { + unset($query['maintenance_status']); } } @@ -246,23 +230,10 @@ class ProjectBrowserTestMock extends ProjectBrowserSourceBase { */ protected function convertDevelopment(array &$query) { if (!empty($query['development_status'])) { - $options_available = DevelopmentStatus::asOptions(); - if (!in_array($query['development_status'], array_keys($options_available))) { - unset($query['development_status']); - } - else { - // Valid value. - switch ($query['development_status']) { - case DevelopmentStatus::Active->value: - $query['development_status'] = self::ACTIVE_VALUES; - break; - - case DevelopmentStatus::All->value: - unset($query['development_status']); - break; - - } - } + $query['development_status'] = self::ACTIVE_VALUES; + } + else { + unset($query['development_status']); } } @@ -274,28 +245,13 @@ class ProjectBrowserTestMock extends ProjectBrowserSourceBase { */ protected function convertSecurity(array &$query) { if (!empty($query['security_advisory_coverage'])) { - $options_available = SecurityStatus::asOptions(); - if (!in_array($query['security_advisory_coverage'], array_keys($options_available))) { - unset($query['security_advisory_coverage']); - } - else { - // Valid value. - switch ($query['security_advisory_coverage']) { - case SecurityStatus::Covered->value: - $query['security_advisory_coverage'] = self::COVERED_VALUES; - break; - - case SecurityStatus::All->value: - $keys = []; - $options = $this->getSecurityCoverages(); - foreach ($options as $option) { - $keys[] = $option['id']; - } - $query['security_advisory_coverage'] = $keys; - break; - - } - } + $query['security_advisory_coverage'] = self::COVERED_VALUES; + } + else { + $query['security_advisory_coverage'] = array_column( + $this->getSecurityCoverages(), + 'id' + ); } } @@ -367,7 +323,7 @@ class ProjectBrowserTestMock extends ProjectBrowserSourceBase { NULL, ); $filters['developmentStatus'] = new BooleanFilter( - TRUE, + FALSE, $this->t('Show projects under active development'), $this->t('Show all'), $this->t('Development status'), diff --git a/tests/src/FunctionalJavascript/ProjectBrowserInstallerUiTest.php b/tests/src/FunctionalJavascript/ProjectBrowserInstallerUiTest.php index 7b32c45e59d7aa01919f5351afe47943f1c1995d..16225bf5fd91c8b7dd09f0a59a9123d09a74d466 100644 --- a/tests/src/FunctionalJavascript/ProjectBrowserInstallerUiTest.php +++ b/tests/src/FunctionalJavascript/ProjectBrowserInstallerUiTest.php @@ -57,8 +57,11 @@ class ProjectBrowserInstallerUiTest extends WebDriverTestBase { $this->installState = $this->container->get(InstallState::class); - $this->config('project_browser.admin_settings')->set('enabled_sources', ['project_browser_test_mock'])->save(TRUE); - $this->config('project_browser.admin_settings')->set('allow_ui_install', TRUE)->save(); + $this->config('project_browser.admin_settings') + ->set('enabled_sources', ['project_browser_test_mock']) + ->set('allow_ui_install', TRUE) + ->set('max_selections', 1) + ->save(); $this->drupalLogin($this->drupalCreateUser([ 'administer modules', 'administer site configuration', diff --git a/tests/src/FunctionalJavascript/ProjectBrowserPluginTest.php b/tests/src/FunctionalJavascript/ProjectBrowserPluginTest.php index 013f583bf2075d048801f9591d2cfab114731a4a..252acbd106e2f8f460f7f6ea7a3999a7432f0f40 100644 --- a/tests/src/FunctionalJavascript/ProjectBrowserPluginTest.php +++ b/tests/src/FunctionalJavascript/ProjectBrowserPluginTest.php @@ -107,13 +107,11 @@ class ProjectBrowserPluginTest extends WebDriverTestBase { $this->assertEquals('Show actively maintained projects', $this->getElementText(self::MAINTENANCE_OPTION_SELECTOR . self::OPTION_CHECKED)); $this->assertEquals('Show all', $this->getElementText(self::DEVELOPMENT_OPTION_SELECTOR . self::OPTION_CHECKED)); + $page = $this->getSession()->getPage(); // Clear the security covered filter. - $this->clickWithWait(self::SECURITY_OPTION_SELECTOR . self::OPTION_LAST_CHILD); - $this->assertEquals('Show all', $this->getElementText(self::SECURITY_OPTION_SELECTOR . self::OPTION_CHECKED)); - + $page->selectFieldOption('securityCoverage', 'Show all'); // Set the development status filter. - $this->clickWithWait(self::DEVELOPMENT_OPTION_SELECTOR . self::OPTION_FIRST_CHILD); - $this->assertEquals('Show projects under active development', $this->getElementText(self::DEVELOPMENT_OPTION_SELECTOR . self::OPTION_CHECKED)); + $page->selectFieldOption('developmentStatus', 'Show projects under active development'); // Clear all filters. $this->pressWithWait('Clear filters'); diff --git a/tests/src/FunctionalJavascript/ProjectBrowserUiTest.php b/tests/src/FunctionalJavascript/ProjectBrowserUiTest.php index 1cb47bf99673a57ac39866b25047bd5af987f026..1d6d87faffa3921e16f718d238bfb26466522238 100644 --- a/tests/src/FunctionalJavascript/ProjectBrowserUiTest.php +++ b/tests/src/FunctionalJavascript/ProjectBrowserUiTest.php @@ -70,9 +70,7 @@ class ProjectBrowserUiTest extends WebDriverTestBase { $this->getSession()->resizeWindow(1250, 1000); $this->drupalGet('admin/modules/browse/project_browser_test_mock'); $this->svelteInitHelper('css', '.pb-project.pb-project--grid'); - $assert_session->waitForElementVisible('css', '#pb-project-browser .pb-display__button[value="Grid"]'); - $grid_text = $this->getElementText('#project-browser .pb-display__button[value="Grid"]'); - $this->assertEquals('Grid', $grid_text); + $this->assertNotEmpty($assert_session->waitForButton('Grid')); $this->svelteInitHelper('text', '10 Results'); $assert_session->elementsCount('css', '#project-browser .pb-project.pb-project--grid', 10); $this->assertTrue($assert_session->waitForText('Results')); @@ -380,10 +378,7 @@ class ProjectBrowserUiTest extends WebDriverTestBase { } // Click the Active filter. - $this->clickWithWait(self::DEVELOPMENT_OPTION_SELECTOR . self::OPTION_FIRST_CHILD); - - // Make sure the correct filter was applied. - $this->assertEquals('Show projects under active development', $this->getElementText(self::DEVELOPMENT_OPTION_SELECTOR . self::OPTION_CHECKED)); + $page->selectFieldOption('developmentStatus', 'Show projects under active development'); $this->assertProjectsVisible([ 'Jazz', diff --git a/tests/src/FunctionalJavascript/ProjectBrowserUiTestJsonApi.php b/tests/src/FunctionalJavascript/ProjectBrowserUiTestJsonApi.php index 5d65055a79e5dbd5ae95717ca7041d1198f5a892..85a692f6bd83c94cd5fbe253ec1f801458cf1bca 100644 --- a/tests/src/FunctionalJavascript/ProjectBrowserUiTestJsonApi.php +++ b/tests/src/FunctionalJavascript/ProjectBrowserUiTestJsonApi.php @@ -228,9 +228,7 @@ class ProjectBrowserUiTestJsonApi extends WebDriverTestBase { $assert_session->pageTextNotContains(' 0 Results'); // Make sure the second filter applied is the security covered filter. - $option = $assert_session->optionExists('securityCoverage', '1'); - $this->assertSame('Show projects covered by a security policy', $option->getText()); - $this->assertTrue($option->isSelected()); + $this->assertTrue($assert_session->optionExists('securityCoverage', 'Show projects covered by a security policy')->isSelected()); // Clear the security covered filter. $this->clickWithWait(self::SECURITY_OPTION_SELECTOR . self::OPTION_LAST_CHILD); @@ -309,11 +307,9 @@ class ProjectBrowserUiTestJsonApi extends WebDriverTestBase { $this->pressWithWait('Recommended filters'); // Check that the actively maintained tag is present. - $maintenance_checked_option = $this->assertSession()->optionExists('maintenanceStatus', '1'); - $this->assertTrue($maintenance_checked_option->isSelected()); - $this->assertEquals('Show actively maintained projects', $maintenance_checked_option->getText()); + $this->assertTrue($assert_session->optionExists('maintenanceStatus', 'Show actively maintained projects')->isSelected()); // Make sure the second filter applied is the security covered filter. - $assert_session->fieldValueEquals('securityCoverage', '1'); + $this->assertTrue($assert_session->optionExists('securityCoverage', 'Show projects covered by a security policy')->isSelected()); $this->assertTrue($assert_session->waitForText(' Results')); $assert_session->pageTextNotContains(' 0 Results'); } diff --git a/tests/src/Kernel/RecipeActivatorTest.php b/tests/src/Kernel/RecipeActivatorTest.php index 74adbddce67baffe1689aa09d9ae560124d3b0a6..c832f0d1cc778c1f196a449f577adc21eb45ba10 100644 --- a/tests/src/Kernel/RecipeActivatorTest.php +++ b/tests/src/Kernel/RecipeActivatorTest.php @@ -49,8 +49,6 @@ class RecipeActivatorTest extends KernelTestBase { $project = new Project( logo: [], isCompatible: TRUE, - isCovered: TRUE, - projectUsageTotal: 0, machineName: 'My Project', body: [], title: '',