Adds a kernel test that exercises SplideAdmin::getOptionsetsByGroupOptions()
with a NULL-label optionset injected via the raw config storage. The test
asserts the method does not throw a TypeError (PHP 8.1+) and that the bad
optionset surfaces in the returned options array with an empty label
string, so admins can still locate and fix it through the optionset edit
form rather than hitting a WSOD.

Pairs with splide-admin-null-label-escape.patch.

--- a/tests/src/Kernel/Form/SplideAdminNullLabelTest.php
+++ b/tests/src/Kernel/Form/SplideAdminNullLabelTest.php
@@ -0,0 +1,82 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Tests\splide\Kernel\Form;
+
+use Drupal\KernelTests\KernelTestBase;
+use Drupal\splide\Entity\Splide;
+
+/**
+ * Tests SplideAdmin handles optionsets with a NULL label without crashing.
+ *
+ * @coversDefaultClass \Drupal\splide\Form\SplideAdmin
+ *
+ * @group splide
+ */
+class SplideAdminNullLabelTest extends KernelTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = [
+    'system',
+    'image',
+    'file',
+    'blazy',
+    'splide',
+    'splide_ui',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+
+    $this->installConfig(['splide']);
+    $this->installEntitySchema('splide');
+  }
+
+  /**
+   * Optionsets with NULL labels must not crash the admin enumeration.
+   *
+   * Reproduces a real-world failure where a malformed config import (or a
+   * partial slick→splide migration) left a `splide.splide.*` config entity
+   * with `label: ~`. Under PHP 8.1+, Html::escape() rejects null and the
+   * block admin page (and any other form listing optionsets) WSODs with
+   * "Argument #1 ($text) must be of type string, null given".
+   *
+   * @covers ::getOptionsetsByGroupOptions
+   */
+  public function testGetOptionsetsByGroupOptionsToleratesNullLabel(): void {
+    // Create a well-formed optionset through the normal API.
+    Splide::create([
+      'name'  => 'good',
+      'label' => 'Good optionset',
+    ])->save();
+
+    // Inject a malformed optionset directly through config storage so we
+    // bypass any entity-level coercion of the label field. This is the
+    // shape that drupal.org/issue-against-PHP-8.1 users have reported in
+    // the wild (label key present but null).
+    $this->container->get('config.factory')
+      ->getEditable('splide.splide.broken')
+      ->setData([
+        'id'    => 'broken',
+        'name'  => 'broken',
+        'label' => NULL,
+        'group' => NULL,
+      ])
+      ->save();
+
+    /** @var \Drupal\splide\Form\SplideAdminInterface $admin */
+    $admin = $this->container->get('splide.admin');
+    $options = $admin->getOptionsetsByGroupOptions();
+
+    $this->assertArrayHasKey('good', $options);
+    $this->assertArrayHasKey('broken', $options);
+    $this->assertSame('', (string) $options['broken']);
+  }
+
+}
