From 0b8e605a1ad1e8ba985862bb2a646043f70d4cf0 Mon Sep 17 00:00:00 2001
From: Pamela Barone <31375-pameeela@users.noreply.drupalcode.org>
Date: Tue, 7 Jan 2025 20:35:37 +0000
Subject: [PATCH] Issue #3492262 by phenaproxima, pameeela, poker10,
 chrisfromredfin, catch, fjgarlin, greggles: Allow isMaintained and security
 coverage and number of installs to differentiate between NULL and a value

---
 .../ProjectBrowserSource/RandomDataPlugin.php |   2 +-
 .../ProjectBrowserSourceExample.php           |   2 -
 src/DevelopmentStatus.php                     |  45 --------
 src/Element/ProjectBrowser.php                |  52 ++-------
 src/MaintenanceStatus.php                     |  45 --------
 .../ProjectBrowserSource/DrupalCore.php       |   4 +-
 .../DrupalDotOrgJsonApi.php                   |  17 +--
 src/Plugin/ProjectBrowserSource/Recipes.php   |   6 +-
 src/ProjectBrowser/Project.php                |  25 +++--
 src/SecurityStatus.php                        |  45 --------
 sveltejs/public/build/bundle.js               | Bin 297515 -> 290374 bytes
 sveltejs/public/build/bundle.js.map           | Bin 275109 -> 269718 bytes
 sveltejs/src/DetailModal.svelte               |   2 +-
 sveltejs/src/MultipleChoiceFilter.svelte      |   9 +-
 sveltejs/src/Project/Project.svelte           |  26 +++--
 sveltejs/src/ProjectBrowser.svelte            |  49 ++++-----
 sveltejs/src/Search/BooleanFilter.svelte      |   4 +-
 sveltejs/src/Search/FilterApplied.svelte      |  35 +++---
 sveltejs/src/Search/Search.svelte             | 101 ++++++++----------
 sveltejs/src/constants.js                     |  13 +--
 sveltejs/src/stores.js                        |  30 ++----
 .../ProjectBrowserTestMock.php                |  76 +++----------
 .../ProjectBrowserInstallerUiTest.php         |   7 +-
 .../ProjectBrowserPluginTest.php              |   8 +-
 .../ProjectBrowserUiTest.php                  |   9 +-
 .../ProjectBrowserUiTestJsonApi.php           |  10 +-
 tests/src/Kernel/RecipeActivatorTest.php      |   2 -
 27 files changed, 163 insertions(+), 461 deletions(-)
 delete mode 100644 src/DevelopmentStatus.php
 delete mode 100644 src/MaintenanceStatus.php
 delete mode 100644 src/SecurityStatus.php

diff --git a/modules/project_browser_devel/src/Plugin/ProjectBrowserSource/RandomDataPlugin.php b/modules/project_browser_devel/src/Plugin/ProjectBrowserSource/RandomDataPlugin.php
index 7b16a795f..3fb484262 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 a8c47418a..c7c429eab 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 577db57d0..000000000
--- 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 184179ef8..2a30bd885 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 42cae92aa..000000000
--- 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 6a4d64e5f..12aa0cb49 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 70e1496b7..45fccfb8a 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 967ab4b2d..c10a934a3 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 01e9dda26..e03bdea3e 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 955975d82..000000000
--- 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
GIT binary patch
delta 4402
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
E10uMlzW@LL

delta 7361
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
O(16HM-X0KxVgC#5Vt={-

diff --git a/sveltejs/public/build/bundle.js.map b/sveltejs/public/build/bundle.js.map
index 7ff6140054aa9bcb6584a688d5dd76ff2429c12b..549b45caa54dda0bdfd51ecebcc69fc89310bd5c 100644
GIT binary patch
delta 5741
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
b*=oCd{~kpJUe|>=waR@gC?Bm=;@JNH*mvm6

delta 8888
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
Wwj?te=!1lO>Dy$Nwm)B5ME(a|JLMt(

diff --git a/sveltejs/src/DetailModal.svelte b/sveltejs/src/DetailModal.svelte
index aa1795d0b..8a80fe2cf 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 90a5bbe02..647c69288 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 6f45f8eae..f5550fd85 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 c7636e424..224135af9 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 5dd984b52..906cf43cc 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 7524e1e8f..80295a470 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 e588b8c8a..d24d3fb2b 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 dd3ab86e9..bd2219ade 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 8ec141381..771967409 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 081570f20..bfd5fea19 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 7b32c45e5..16225bf5f 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 013f583bf..252acbd10 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 1cb47bf99..1d6d87faf 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 5d65055a7..85a692f6b 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 74adbddce..c832f0d1c 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: '',
-- 
GitLab