From 5b397d02c27427fdc3471303404c5d5804da96b6 Mon Sep 17 00:00:00 2001
From: Wim Leers <wim.leers@acquia.com>
Date: Wed, 7 May 2025 16:43:13 +0200
Subject: [PATCH 01/22] Add missing test coverage.

---
 .../Kernel/Config/ComponentValidationTest.php | 29 ++++++++++
 tests/src/Kernel/PropSourceTest.php           | 58 +++++++++++++++++++
 2 files changed, 87 insertions(+)

diff --git a/tests/src/Kernel/Config/ComponentValidationTest.php b/tests/src/Kernel/Config/ComponentValidationTest.php
index e131cf494b..2476b40601 100644
--- a/tests/src/Kernel/Config/ComponentValidationTest.php
+++ b/tests/src/Kernel/Config/ComponentValidationTest.php
@@ -12,6 +12,7 @@ use Drupal\experience_builder\Plugin\ComponentPluginManager;
 use Drupal\experience_builder\Plugin\ExperienceBuilder\ComponentSource\BlockComponent;
 use Drupal\experience_builder\Plugin\ExperienceBuilder\ComponentSource\JsComponent;
 use Drupal\experience_builder\Plugin\ExperienceBuilder\ComponentSource\SingleDirectoryComponent;
+use Drupal\Tests\experience_builder\Traits\BetterConfigDependencyManagerTrait;
 use Drupal\Tests\experience_builder\Traits\ContribStrictConfigSchemaTestTrait;
 use Drupal\Tests\experience_builder\Traits\GenerateComponentConfigTrait;
 use Symfony\Component\Yaml\Yaml;
@@ -24,6 +25,7 @@ use Symfony\Component\Yaml\Yaml;
  */
 class ComponentValidationTest extends BetterConfigEntityValidationTestBase {
 
+  use BetterConfigDependencyManagerTrait;
   use ContribStrictConfigSchemaTestTrait;
   use GenerateComponentConfigTrait;
 
@@ -116,6 +118,33 @@ class ComponentValidationTest extends BetterConfigEntityValidationTestBase {
     $this->componentPluginManager = $this->container->get(ComponentPluginManager::class);
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function testEntityIsValid(): void {
+    parent::testEntityIsValid();
+
+    // @todo Change the tested component here to one that includes an `image` prop shape, to allow testing with entity reference field type + media module dependency + media library dependency ONLY because of the widget.
+
+    // Beyond validity, validate config dependencies are computed correctly.
+    $this->assertSame(
+      [
+        'module' => [
+          'options',
+          'sdc_test',
+        ],
+      ],
+      $this->entity->getDependencies()
+    );
+    $this->assertSame([
+      'module' => [
+        'options',
+        'sdc_test',
+        'experience_builder',
+      ],
+    ], $this->getAllDependencies($this->entity));
+  }
+
   /**
    * @covers `type: experience_builder.component_source_settings.*`
    * @covers `type: experience_builder.generated_field_explicit_input_ux`
diff --git a/tests/src/Kernel/PropSourceTest.php b/tests/src/Kernel/PropSourceTest.php
index b883d370f2..161e4df9fc 100644
--- a/tests/src/Kernel/PropSourceTest.php
+++ b/tests/src/Kernel/PropSourceTest.php
@@ -25,6 +25,7 @@ use Drupal\experience_builder\PropSource\DynamicPropSource;
 use Drupal\experience_builder\PropSource\PropSource;
 use Drupal\experience_builder\PropSource\StaticPropSource;
 use Drupal\KernelTests\KernelTestBase;
+use Drupal\media\Entity\MediaType;
 use Drupal\node\Entity\NodeType;
 use Drupal\Tests\experience_builder\Traits\ContribStrictConfigSchemaTestTrait;
 use Drupal\Tests\node\Traits\NodeCreationTrait;
@@ -49,10 +50,25 @@ class PropSourceTest extends KernelTestBase {
     'user',
     'datetime',
     'datetime_range',
+    'media',
+    'media_library',
     'system',
     'media',
+    'views',
   ];
 
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+    MediaType::create([
+      'id' => 'image',
+      'label' => 'Image',
+      'source' => 'image',
+    ])->save();
+  }
+
   /**
    * @coversClass \Drupal\experience_builder\PropSource\StaticPropSource
    */
@@ -160,6 +176,48 @@ class PropSourceTest extends KernelTestBase {
       ],
     ], $complex_example->calculateDependencies());
 
+    // A complex *empty* example.
+    $complex_empty_example = StaticPropSource::parse([
+      'sourceType' => 'static:field_item:entity_reference',
+      'value' => NULL,
+      'expression' => 'ℹ︎entity_reference␟{src↝entity␜␜entity:media:image␝field_media_image␞␟entity␜␜entity:file␝uri␞␟url,alt↝entity␜␜entity:media:image␝field_media_image␞␟alt,width↝entity␜␜entity:media:image␝field_media_image␞␟width,height↝entity␜␜entity:media:image␝field_media_image␞␟height}',
+      'sourceTypeSettings' => [
+        'storage' => ['target_type' => 'media'],
+        'instance' => [
+          'handler' => 'default:media',
+          'handler_settings' => [
+            'target_bundles' => ['image' => 'image'],
+          ],
+        ],
+      ],
+    ]);
+    // First, get the string representation and parse it back, to prove
+    // serialization and deserialization works.
+    $json_representation = (string) $complex_empty_example;
+    $this->assertSame('{"sourceType":"static:field_item:entity_reference","value":null,"expression":"ℹ︎entity_reference␟{src↝entity␜␜entity:media:image␝field_media_image␞␟entity␜␜entity:file␝uri␞␟url,alt↝entity␜␜entity:media:image␝field_media_image␞␟alt,width↝entity␜␜entity:media:image␝field_media_image␞␟width,height↝entity␜␜entity:media:image␝field_media_image␞␟height}","sourceTypeSettings":{"storage":{"target_type":"media"},"instance":{"handler":"default:media","handler_settings":{"target_bundles":{"image":"image"}}}}}', $json_representation);
+    $decoded_representation = json_decode($json_representation, TRUE);
+    $complex_empty_example = PropSource::parse($decoded_representation);
+    $this->assertInstanceOf(StaticPropSource::class, $complex_empty_example);
+    // The contained information read back out.
+    $this->assertSame('static:field_item:entity_reference', $complex_empty_example->getSourceType());
+    $this->assertInstanceOf(FieldTypeObjectPropsExpression::class, StructuredDataPropExpression::fromString($complex_empty_example->asChoice()));
+    $this->assertNull($complex_empty_example->getValue());
+    self::assertSame([
+      'config' => [
+        'media.type.image',
+      ],
+      'content' => [],
+      'module' => [
+        'media',
+      ],
+      'plugin' => [
+        'field_type:entity_reference',
+        'field_type:entity_reference',
+        'field_type:entity_reference',
+        'field_type:entity_reference',
+      ],
+    ], $complex_empty_example->calculateDependencies());
+
     // A simple (expression targeting a simple prop) array example (with
     // cardinality specified, rather than the default of `cardinality=1`).
     $simple_array_example = StaticPropSource::parse([
-- 
GitLab


From 9bef31a7e566cf4fdb71231ed20a0bdc3382ea6a Mon Sep 17 00:00:00 2001
From: Wim Leers <wim.leers@acquia.com>
Date: Wed, 7 May 2025 16:43:35 +0200
Subject: [PATCH 02/22] Fix `StaticPropSource::calculateDependencies()`.

---
 src/PropSource/StaticPropSource.php | 17 ++++++++++++++++-
 1 file changed, 16 insertions(+), 1 deletion(-)

diff --git a/src/PropSource/StaticPropSource.php b/src/PropSource/StaticPropSource.php
index bd6460d5d3..7b2b7a9526 100644
--- a/src/PropSource/StaticPropSource.php
+++ b/src/PropSource/StaticPropSource.php
@@ -4,6 +4,7 @@ declare(strict_types=1);
 
 namespace Drupal\experience_builder\PropSource;
 
+use Drupal\Component\Utility\NestedArray;
 use Drupal\Core\Datetime\DrupalDateTime;
 use Drupal\Core\Entity\FieldableEntityInterface;
 use Drupal\Core\Entity\Plugin\DataType\EntityAdapter;
@@ -481,7 +482,21 @@ final class StaticPropSource extends PropSourceBase {
     // calculated dependencies will be limited to the entity types, bundle (if
     // any) and fields (if any) that this expression depends on.
     // @see \Drupal\Tests\experience_builder\Kernel\PropExpressionDependenciesTest
-    return $this->expression->calculateDependencies($this->fieldItemList);
+    $expression_deps = $this->expression->calculateDependencies($this->fieldItemList);
+
+    // Let the field type plugin specify its own dependencies, based on storage
+    // settings and instance settings.
+    $field_item_class = $this->fieldItemList->getItemDefinition()->getClass();
+    $instance_deps = $field_item_class::calculateDependencies($this->fieldItemList->getFieldDefinition());
+    $storage_deps = $field_item_class::calculateStorageDependencies($this->fieldItemList->getFieldDefinition()->getFieldStorageDefinition());
+
+    $dependencies = NestedArray::mergeDeep(
+      $expression_deps,
+      $instance_deps,
+      $storage_deps,
+    );
+    ksort($dependencies);
+    return $dependencies;
   }
 
 }
-- 
GitLab


From f0a32f345caa0d96d31e7d8acba64f93b4eb9dfb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ignacio=20S=C3=A1nchez?= <nacho@isholgueras.com>
Date: Thu, 15 May 2025 09:02:45 +0200
Subject: [PATCH 03/22] Issue #3460230: Return unique and sorted dependencies.

---
 src/PropSource/StaticPropSource.php | 5 +++++
 tests/src/Kernel/PropSourceTest.php | 5 -----
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/src/PropSource/StaticPropSource.php b/src/PropSource/StaticPropSource.php
index 7b2b7a9526..54175e8dc6 100644
--- a/src/PropSource/StaticPropSource.php
+++ b/src/PropSource/StaticPropSource.php
@@ -496,6 +496,11 @@ final class StaticPropSource extends PropSourceBase {
       $storage_deps,
     );
     ksort($dependencies);
+    $dependencies = array_map(function ($values) {
+      $values = array_unique($values);
+      ksort($values);
+      return $values;
+    }, $dependencies);
     return $dependencies;
   }
 
diff --git a/tests/src/Kernel/PropSourceTest.php b/tests/src/Kernel/PropSourceTest.php
index 161e4df9fc..463d979a4c 100644
--- a/tests/src/Kernel/PropSourceTest.php
+++ b/tests/src/Kernel/PropSourceTest.php
@@ -168,11 +168,9 @@ class PropSourceTest extends KernelTestBase {
     self::assertSame([
       'module' => [
         'datetime_range',
-        'datetime_range',
       ],
       'plugin' => [
         'field_type:daterange',
-        'field_type:daterange',
       ],
     ], $complex_example->calculateDependencies());
 
@@ -212,9 +210,6 @@ class PropSourceTest extends KernelTestBase {
       ],
       'plugin' => [
         'field_type:entity_reference',
-        'field_type:entity_reference',
-        'field_type:entity_reference',
-        'field_type:entity_reference',
       ],
     ], $complex_empty_example->calculateDependencies());
 
-- 
GitLab


From 7ef84f4951ebb5ba7b69a60fbbe798acd91de67f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ignacio=20S=C3=A1nchez?= <nacho@isholgueras.com>
Date: Thu, 15 May 2025 09:16:33 +0200
Subject: [PATCH 04/22] Issue #3460230: Inline variable and reindex the
 dependencies array.

---
 src/PropSource/StaticPropSource.php | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/src/PropSource/StaticPropSource.php b/src/PropSource/StaticPropSource.php
index 54175e8dc6..c7f9a3133e 100644
--- a/src/PropSource/StaticPropSource.php
+++ b/src/PropSource/StaticPropSource.php
@@ -496,12 +496,11 @@ final class StaticPropSource extends PropSourceBase {
       $storage_deps,
     );
     ksort($dependencies);
-    $dependencies = array_map(function ($values) {
+    return array_map(static function ($values) {
       $values = array_unique($values);
-      ksort($values);
+      sort($values);
       return $values;
     }, $dependencies);
-    return $dependencies;
   }
 
 }
-- 
GitLab


From 5debfa446c13018dff4232fff5c9218516c7831c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ignacio=20S=C3=A1nchez?= <nacho@isholgueras.com>
Date: Thu, 15 May 2025 09:24:18 +0200
Subject: [PATCH 05/22] Issue #3460230: return unique dependencies for
 component sources.

---
 .../GeneratedFieldExplicitInputUxComponentSourceBase.php   | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/src/Plugin/ExperienceBuilder/ComponentSource/GeneratedFieldExplicitInputUxComponentSourceBase.php b/src/Plugin/ExperienceBuilder/ComponentSource/GeneratedFieldExplicitInputUxComponentSourceBase.php
index 8393dc4ea5..f94837d7ef 100644
--- a/src/Plugin/ExperienceBuilder/ComponentSource/GeneratedFieldExplicitInputUxComponentSourceBase.php
+++ b/src/Plugin/ExperienceBuilder/ComponentSource/GeneratedFieldExplicitInputUxComponentSourceBase.php
@@ -123,7 +123,12 @@ abstract class GeneratedFieldExplicitInputUxComponentSourceBase extends Componen
       $dependencies['module'][] = $field_widget_definition['provider'];
     }
 
-    return $dependencies;
+    ksort($dependencies);
+    return array_map(static function ($values) {
+      $values = array_unique($values);
+      sort($values);
+      return $values;
+    }, $dependencies);
   }
 
   /**
-- 
GitLab


From 9362d1af00af583d3b42fbdecf63a17551fb4094 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ignacio=20S=C3=A1nchez?= <nacho@isholgueras.com>
Date: Thu, 15 May 2025 09:39:39 +0200
Subject: [PATCH 06/22] Issue #3460320: Fix dependencies tests.

---
 .../SingleDirectoryComponentTest.php              | 15 +--------------
 1 file changed, 1 insertion(+), 14 deletions(-)

diff --git a/tests/src/Kernel/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponentTest.php b/tests/src/Kernel/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponentTest.php
index 5ef2e3744a..c7fa13dc6a 100644
--- a/tests/src/Kernel/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponentTest.php
+++ b/tests/src/Kernel/Plugin/ExperienceBuilder/ComponentSource/SingleDirectoryComponentTest.php
@@ -707,44 +707,38 @@ HTML,
     self::assertSame([
       'sdc.xb_test_sdc.crash' => [
         'module' => [
-          'core',
           'core',
           'xb_test_sdc',
         ],
       ],
       'sdc.xb_test_sdc.deprecated' => [
         'module' => [
-          'core',
           'core',
           'xb_test_sdc',
         ],
       ],
       'sdc.xb_test_sdc.experimental' => [
         'module' => [
-          'core',
           'core',
           'xb_test_sdc',
         ],
       ],
       'sdc.xb_test_sdc.grid-container' => [
         'module' => [
-          'options',
           'core',
+          'options',
           'xb_test_sdc',
         ],
       ],
       'sdc.xb_test_sdc.image-gallery' => [
         'module' => [
-          'core',
           'core',
           'image',
-          'image',
           'xb_test_sdc',
         ],
       ],
       'sdc.xb_test_sdc.image-optional-with-example' => [
         'module' => [
-          'image',
           'image',
           'xb_test_sdc',
         ],
@@ -752,43 +746,36 @@ HTML,
       'sdc.xb_test_sdc.image-optional-with-example-and-additional-prop' => [
         'module' => [
           'core',
-          'core',
-          'image',
           'image',
           'xb_test_sdc',
         ],
       ],
       'sdc.xb_test_sdc.image-optional-without-example' => [
         'module' => [
-          'image',
           'image',
           'xb_test_sdc',
         ],
       ],
       'sdc.xb_test_sdc.image-required-with-example' => [
         'module' => [
-          'image',
           'image',
           'xb_test_sdc',
         ],
       ],
       'sdc.xb_test_sdc.props-no-slots' => [
         'module' => [
-          'core',
           'core',
           'xb_test_sdc',
         ],
       ],
       'sdc.xb_test_sdc.props-slots' => [
         'module' => [
-          'core',
           'core',
           'xb_test_sdc',
         ],
       ],
       'sdc.xb_test_sdc.sparkline' => [
         'module' => [
-          'core',
           'core',
           'xb_test_sdc',
         ],
-- 
GitLab


From 910da164d72ff24339e6646e2d52af50e629c0cf Mon Sep 17 00:00:00 2001
From: Ted Bowman <ted+git@tedbow.com>
Date: Mon, 19 May 2025 12:41:47 -0400
Subject: [PATCH 07/22] assert field item class

---
 src/PropSource/StaticPropSource.php | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/PropSource/StaticPropSource.php b/src/PropSource/StaticPropSource.php
index c7f9a3133e..ea92626580 100644
--- a/src/PropSource/StaticPropSource.php
+++ b/src/PropSource/StaticPropSource.php
@@ -487,6 +487,7 @@ final class StaticPropSource extends PropSourceBase {
     // Let the field type plugin specify its own dependencies, based on storage
     // settings and instance settings.
     $field_item_class = $this->fieldItemList->getItemDefinition()->getClass();
+    assert(is_subclass_of($field_item_class, FieldItemInterface::class));
     $instance_deps = $field_item_class::calculateDependencies($this->fieldItemList->getFieldDefinition());
     $storage_deps = $field_item_class::calculateStorageDependencies($this->fieldItemList->getFieldDefinition()->getFieldStorageDefinition());
 
-- 
GitLab


From 6a0be485414dec5b471b6616454decfa67670d02 Mon Sep 17 00:00:00 2001
From: Ted Bowman <ted+git@tedbow.com>
Date: Mon, 19 May 2025 15:33:48 -0400
Subject: [PATCH 08/22] set up source field for media in PropSourceTest

---
 src/Hook/ShapeMatchingHooks.php     |  1 -
 tests/src/Kernel/PropSourceTest.php | 23 +++++++++++++++++++----
 2 files changed, 19 insertions(+), 5 deletions(-)

diff --git a/src/Hook/ShapeMatchingHooks.php b/src/Hook/ShapeMatchingHooks.php
index d0d0f0a81d..c7df951677 100644
--- a/src/Hook/ShapeMatchingHooks.php
+++ b/src/Hook/ShapeMatchingHooks.php
@@ -244,7 +244,6 @@ class ShapeMatchingHooks {
       }
       $source_field_definition = $image_media_type->getSource()
         ->getSourceFieldDefinition($image_media_type);
-      \assert($source_field_definition !== \NULL);
       $source_field_name = $source_field_definition->getName();
       $storable_prop_shape->fieldTypeProp = new FieldTypeObjectPropsExpression('entity_reference', [
         'src' => new ReferenceFieldTypePropExpression(new FieldTypePropExpression('entity_reference', 'entity'), new ReferenceFieldPropExpression(new FieldPropExpression(EntityDataDefinition::create('media', 'image'), $source_field_name, \NULL, 'entity'), new FieldPropExpression(EntityDataDefinition::create('file'), 'uri', \NULL, 'url'))),
diff --git a/tests/src/Kernel/PropSourceTest.php b/tests/src/Kernel/PropSourceTest.php
index 463d979a4c..bd37303f0e 100644
--- a/tests/src/Kernel/PropSourceTest.php
+++ b/tests/src/Kernel/PropSourceTest.php
@@ -46,6 +46,8 @@ class PropSourceTest extends KernelTestBase {
    */
   protected static $modules = [
     'experience_builder',
+    'field',
+    'file',
     'node',
     'user',
     'datetime',
@@ -62,11 +64,24 @@ class PropSourceTest extends KernelTestBase {
    */
   protected function setUp(): void {
     parent::setUp();
-    MediaType::create([
+    $this->installEntitySchema('field_storage_config');
+    $this->installEntitySchema('field_config');
+    $media_type = MediaType::create([
       'id' => 'image',
       'label' => 'Image',
-      'source' => 'image',
-    ])->save();
+      'source' => 'file',
+    ]);
+    $media_type->save();
+    // Create the source field.
+    $source_field = $media_type->getSource()->createSourceField($media_type);
+    $source_field->getFieldStorageDefinition()->save();
+
+    $source_field->save();
+    $media_type
+      ->set('source_configuration', [
+        'source_field' => $source_field->getName(),
+      ])
+      ->save();
   }
 
   /**
@@ -651,7 +666,7 @@ class PropSourceTest extends KernelTestBase {
    * @coversClass \Drupal\experience_builder\PropSource\DefaultRelativeUrlPropSource
    */
   public function testDefaultRelativeUrlPropSource(): void {
-    $this->enableModules(['xb_test_sdc', 'link', 'image', 'file', 'options']);
+    $this->enableModules(['xb_test_sdc', 'link', 'image', 'options']);
     // Force rebuilding of the definitions which will create the required
     // component.
     $plugin_manager = $this->container->get(ComponentPluginManager::class);
-- 
GitLab


From 373166d315c3ae055cf616f942630a42bdc46007 Mon Sep 17 00:00:00 2001
From: Ted Bowman <ted+git@tedbow.com>
Date: Mon, 19 May 2025 15:40:03 -0400
Subject: [PATCH 09/22] dependencies are now ensured not to have the same value
 more than once

---
 .../ExperienceBuilder/ComponentSource/JsComponentTest.php     | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/tests/src/Kernel/Plugin/ExperienceBuilder/ComponentSource/JsComponentTest.php b/tests/src/Kernel/Plugin/ExperienceBuilder/ComponentSource/JsComponentTest.php
index c49e9b253d..50bc468ed1 100644
--- a/tests/src/Kernel/Plugin/ExperienceBuilder/ComponentSource/JsComponentTest.php
+++ b/tests/src/Kernel/Plugin/ExperienceBuilder/ComponentSource/JsComponentTest.php
@@ -483,7 +483,6 @@ final class JsComponentTest extends ComponentSourceTestBase {
       'js.xb_test_code_components_vanilla_image' => [
         'module' => [
           'image',
-          'image',
         ],
         'config' => [
           'experience_builder.js_component.xb_test_code_components_vanilla_image',
@@ -497,9 +496,6 @@ final class JsComponentTest extends ComponentSourceTestBase {
       'js.xb_test_code_components_with_props' => [
         'module' => [
           'core',
-          'core',
-          'core',
-          'core',
         ],
         'config' => [
           'experience_builder.js_component.xb_test_code_components_with_props',
-- 
GitLab


From 98bd34ce675e3595d240f4ea613f8b3929af91c9 Mon Sep 17 00:00:00 2001
From: Ted Bowman <ted+git@tedbow.com>
Date: Mon, 19 May 2025 15:48:56 -0400
Subject: [PATCH 10/22] accidental removal

---
 src/Hook/ShapeMatchingHooks.php | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/Hook/ShapeMatchingHooks.php b/src/Hook/ShapeMatchingHooks.php
index c7df951677..d0d0f0a81d 100644
--- a/src/Hook/ShapeMatchingHooks.php
+++ b/src/Hook/ShapeMatchingHooks.php
@@ -244,6 +244,7 @@ class ShapeMatchingHooks {
       }
       $source_field_definition = $image_media_type->getSource()
         ->getSourceFieldDefinition($image_media_type);
+      \assert($source_field_definition !== \NULL);
       $source_field_name = $source_field_definition->getName();
       $storable_prop_shape->fieldTypeProp = new FieldTypeObjectPropsExpression('entity_reference', [
         'src' => new ReferenceFieldTypePropExpression(new FieldTypePropExpression('entity_reference', 'entity'), new ReferenceFieldPropExpression(new FieldPropExpression(EntityDataDefinition::create('media', 'image'), $source_field_name, \NULL, 'entity'), new FieldPropExpression(EntityDataDefinition::create('file'), 'uri', \NULL, 'url'))),
-- 
GitLab


From ff5b64128873f5d469c7e05009f51098a200e21a Mon Sep 17 00:00:00 2001
From: Ted Bowman <ted+git@tedbow.com>
Date: Mon, 19 May 2025 15:49:33 -0400
Subject: [PATCH 11/22] phpstan

---
 tests/src/Kernel/PropSourceTest.php | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/tests/src/Kernel/PropSourceTest.php b/tests/src/Kernel/PropSourceTest.php
index bd37303f0e..ffe305c851 100644
--- a/tests/src/Kernel/PropSourceTest.php
+++ b/tests/src/Kernel/PropSourceTest.php
@@ -4,6 +4,7 @@ declare(strict_types=1);
 
 namespace Drupal\Tests\experience_builder\Kernel;
 
+use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Field\FieldStorageDefinitionInterface;
 use Drupal\Core\Field\Plugin\Field\FieldWidget\NumberWidget;
 use Drupal\Core\Extension\ExtensionPathResolver;
@@ -74,7 +75,9 @@ class PropSourceTest extends KernelTestBase {
     $media_type->save();
     // Create the source field.
     $source_field = $media_type->getSource()->createSourceField($media_type);
-    $source_field->getFieldStorageDefinition()->save();
+    $field_storage_definition = $source_field->getFieldStorageDefinition();
+    assert($field_storage_definition instanceof EntityInterface);
+    $field_storage_definition->save();
 
     $source_field->save();
     $media_type
-- 
GitLab


From d88237a833ba7efd2c2157b06a5676033fa72737 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ignacio=20S=C3=A1nchez?= <nacho@isholgueras.com>
Date: Tue, 20 May 2025 14:48:23 +0200
Subject: [PATCH 12/22] Issue #3460230: Add static:field_item:boolean and
 simplify simple static tests with a provider.

---
 tests/src/Kernel/PropSourceTest.php | 194 +++++++++++++++++-----------
 1 file changed, 120 insertions(+), 74 deletions(-)

diff --git a/tests/src/Kernel/PropSourceTest.php b/tests/src/Kernel/PropSourceTest.php
index ffe305c851..de4899a27d 100644
--- a/tests/src/Kernel/PropSourceTest.php
+++ b/tests/src/Kernel/PropSourceTest.php
@@ -5,10 +5,12 @@ declare(strict_types=1);
 namespace Drupal\Tests\experience_builder\Kernel;
 
 use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Extension\ExtensionPathResolver;
 use Drupal\Core\Field\FieldStorageDefinitionInterface;
+use Drupal\Core\Field\Plugin\Field\FieldWidget\BooleanCheckboxWidget;
 use Drupal\Core\Field\Plugin\Field\FieldWidget\NumberWidget;
-use Drupal\Core\Extension\ExtensionPathResolver;
 use Drupal\Core\Field\Plugin\Field\FieldWidget\StringTextfieldWidget;
+use Drupal\Core\Field\Plugin\Field\FieldWidget\UriWidget;
 use Drupal\Core\Url;
 use Drupal\datetime\Plugin\Field\FieldType\DateTimeItem;
 use Drupal\datetime_range\Plugin\Field\FieldWidget\DateRangeDatelistWidget;
@@ -89,18 +91,32 @@ class PropSourceTest extends KernelTestBase {
 
   /**
    * @coversClass \Drupal\experience_builder\PropSource\StaticPropSource
+   * @dataProvider providerSimpleStaticPropSource
    */
-  public function testStaticPropSource(): void {
+  public function testSimpleStaticPropSource(
+    string $source_type,
+    array|null $source_type_settings,
+    mixed $value,
+    string $expression,
+    string $expected_json_representation,
+    array $field_widgets,
+    mixed $expected_user_value,
+    string $expected_prop_expection_class,
+    array $expected_dependencies,
+  ): void {
+
     // A simple example.
+    // @phpstan-ignore-next-line
     $simple_example = StaticPropSource::parse([
-      'sourceType' => 'static:field_item:string',
-      'value' => 'Hello, world!',
-      'expression' => 'ℹ︎string␟value',
+      'sourceType' => $source_type,
+      'value' => $value,
+      'expression' => $expression,
+      'sourceTypeSettings' => $source_type_settings,
     ]);
     // First, get the string representation and parse it back, to prove
     // serialization and deserialization works.
     $json_representation = (string) $simple_example;
-    $this->assertSame('{"sourceType":"static:field_item:string","value":"Hello, world!","expression":"ℹ︎string␟value"}', $json_representation);
+    $this->assertSame($expected_json_representation, $json_representation);
     $decoded_representation = json_decode($json_representation, TRUE);
     try {
       StaticPropSource::isMinimalRepresentation($decoded_representation);
@@ -111,86 +127,116 @@ class PropSourceTest extends KernelTestBase {
     $simple_example = PropSource::parse($decoded_representation);
     $this->assertInstanceOf(StaticPropSource::class, $simple_example);
     // The contained information read back out.
-    $this->assertSame('static:field_item:string', $simple_example->getSourceType());
-    $this->assertInstanceOf(FieldTypePropExpression::class, StructuredDataPropExpression::fromString($simple_example->asChoice()));
-    $this->assertSame('Hello, world!', $simple_example->getValue());
+    $this->assertSame($source_type, $simple_example->getSourceType());
+    /** @var class-string $expected_prop_expection_class */
+    $this->assertInstanceOf($expected_prop_expection_class, StructuredDataPropExpression::fromString($simple_example->asChoice()));
+    $this->assertSame($value, $simple_example->getValue());
     // Test the functionality of a StaticPropSource:
     // - evaluate it to populate an SDC prop
-    $this->assertSame('Hello, world!', $simple_example->evaluate(User::create([])));
+    $this->assertSame($expected_user_value, $simple_example->evaluate(User::create([])));
     // - the field type's item's raw value is minimized if it is single-property
-    $this->assertSame('Hello, world!', $simple_example->getValue());
+    $this->assertSame($value, $simple_example->getValue());
     // - generate a widget to edit the stored value — using the default widget
     //   or a specified widget.
     // @see \Drupal\experience_builder\Entity\Component::$defaults
-    $this->assertInstanceOf(StringTextfieldWidget::class, $simple_example->getWidget('irrelevant-for-test', $this->randomString(), NULL));
-    $this->assertInstanceOf(StringTextfieldWidget::class, $simple_example->getWidget('irrelevant-for-test', $this->randomString(), 'string_textfield'));
-    // The widget plugin manager ignores any request for another widget type and
-    // falls back to the default widget if
-    // @see \Drupal\Core\Field\WidgetPluginManager::getInstance()
-    $this->assertInstanceOf(StringTextfieldWidget::class, $simple_example->getWidget('irrelevant-for-test', $this->randomString(), 'string_textarea'));
-    self::assertSame([
-      'plugin' => [
-        'field_type:string',
-      ],
-    ], $simple_example->calculateDependencies());
+    foreach ($field_widgets as $widget_type => $expected_widget_class) {
+      $this->assertInstanceOf($expected_widget_class, $simple_example->getWidget('irrelevant-for-test', $this->randomString(), $widget_type));
+    }
+    self::assertSame($expected_dependencies, $simple_example->calculateDependencies());
+  }
 
-    // A complex example.
-    $complex_example = StaticPropSource::parse([
-      'sourceType' => 'static:field_item:daterange',
-      'value' => [
-        'value' => '2020-04-16T00:00',
-        'end_value' => '2024-07-10T10:24',
+  public static function providerSimpleStaticPropSource(): array {
+    return [
+      [
+        'sourceType' => 'static:field_item:string',
+        'sourceTypeSettings' => NULL,
+        'value' => 'Hello, world!',
+        'expression' => 'ℹ︎string␟value',
+        'expected_json_representation' => '{"sourceType":"static:field_item:string","value":"Hello, world!","expression":"ℹ︎string␟value"}',
+        'field_widgets' => [
+          NULL => StringTextfieldWidget::class,
+          'string_textfield' => StringTextfieldWidget::class,
+          'string_textarea' => StringTextfieldWidget::class,
+        ],
+        'expected_user_value' => 'Hello, world!',
+        'expected_prop_expression' => FieldTypePropExpression::class,
+        'expected_dependencies' => [
+          'plugin' => [
+            'field_type:string',
+          ],
+        ],
       ],
-      'expression' => 'ℹ︎daterange␟{start↠value,stop↠end_value}',
-    ]);
-    // First, get the string representation and parse it back, to prove
-    // serialization and deserialization works.
-    $json_representation = (string) $complex_example;
-    $this->assertSame('{"sourceType":"static:field_item:daterange","value":{"value":"2020-04-16T00:00","end_value":"2024-07-10T10:24"},"expression":"ℹ︎daterange␟{start↠value,stop↠end_value}"}', $json_representation);
-    $decoded_representation = json_decode($json_representation, TRUE);
-    try {
-      StaticPropSource::isMinimalRepresentation($decoded_representation);
-    }
-    catch (\LogicException) {
-      $this->fail("Not a minimal representation: $json_representation.");
-    }
-    $complex_example = PropSource::parse($decoded_representation);
-    $this->assertInstanceOf(StaticPropSource::class, $complex_example);
-    // The contained information read back out.
-    $this->assertSame('static:field_item:daterange', $complex_example->getSourceType());
-    $this->assertInstanceOf(FieldTypeObjectPropsExpression::class, StructuredDataPropExpression::fromString($complex_example->asChoice()));
-    $this->assertSame([
-      'value' => '2020-04-16T00:00',
-      'end_value' => '2024-07-10T10:24',
-    ], $complex_example->getValue());
-    // Test the functionality of a StaticPropSource:
-    // - evaluate it to populate an SDC prop
-    $this->assertSame([
-      'start' => '2020-04-16T00:00',
-      'stop' => '2024-07-10T10:24',
-    ], $complex_example->evaluate(User::create([])));
-    // - the field type's item's raw value is minimized if it is single-property
-    $this->assertSame(
       [
-        'value' => '2020-04-16T00:00',
-        'end_value' => '2024-07-10T10:24',
+        'sourceType' => 'static:field_item:uri',
+        'sourceTypeSettings' => NULL,
+        'value' => 'https://drupal.org',
+        'expression' => 'ℹ︎uri␟value',
+        'expected_json_representation' => '{"sourceType":"static:field_item:uri","value":"https:\/\/drupal.org","expression":"ℹ︎uri␟value"}',
+        'field_widgets' => [
+          NULL => UriWidget::class,
+          'uri' => UriWidget::class,
+        ],
+        'expected_user_value' => 'https://drupal.org',
+        'expected_prop_expression' => FieldTypePropExpression::class,
+        'expected_dependencies' => [
+          'plugin' => [
+            'field_type:uri',
+          ],
+        ],
       ],
-      $complex_example->getValue()
-    );
-    // - generate a widget to edit the stored value — using the default widget
-    //   or a specified widget.
-    // @see \Drupal\experience_builder\Entity\Component::$defaults
-    $this->assertInstanceOf(DateRangeDefaultWidget::class, $complex_example->getWidget('irrelevant-for-test', $this->randomString(), NULL));
-    $this->assertInstanceOf(DateRangeDefaultWidget::class, $complex_example->getWidget('irrelevant-for-test', $this->randomString(), 'daterange_default'));
-    $this->assertInstanceOf(DateRangeDatelistWidget::class, $complex_example->getWidget('irrelevant-for-test', $this->randomString(), 'daterange_datelist'));
-    self::assertSame([
-      'module' => [
-        'datetime_range',
+      [
+        'sourceType' => 'static:field_item:boolean',
+        'sourceTypeSettings' => NULL,
+        'value' => TRUE,
+        'expression' => 'ℹ︎boolean␟value',
+        'expected_json_representation' => '{"sourceType":"static:field_item:boolean","value":true,"expression":"ℹ︎boolean␟value"}',
+        'field_widgets' => [
+          NULL => BooleanCheckboxWidget::class,
+          'boolean_checkbox' => BooleanCheckboxWidget::class,
+        ],
+        'expected_user_value' => TRUE,
+        'expected_prop_expression' => FieldTypePropExpression::class,
+        'expected_dependencies' => [
+          'plugin' => [
+            'field_type:boolean',
+          ],
+        ],
       ],
-      'plugin' => [
-        'field_type:daterange',
+      [
+        'sourceType' => 'static:field_item:daterange',
+        'sourceTypeSettings' => NULL,
+        'value' => [
+          'value' => '2020-04-16T00:00',
+          'end_value' => '2024-07-10T10:24',
+        ],
+        'expression' => 'ℹ︎daterange␟{start↠value,stop↠end_value}',
+        'expected_json_representation' => '{"sourceType":"static:field_item:daterange","value":{"value":"2020-04-16T00:00","end_value":"2024-07-10T10:24"},"expression":"ℹ︎daterange␟{start↠value,stop↠end_value}"}',
+        'field_widgets' => [
+          NULL => DateRangeDefaultWidget::class,
+          'daterange_default' => DateRangeDefaultWidget::class,
+          'daterange_datelist' => DateRangeDatelistWidget::class,
+        ],
+        'expected_user_value' => [
+          'start' => '2020-04-16T00:00',
+          'stop' => '2024-07-10T10:24',
+        ],
+        'expected_prop_expression' => FieldTypeObjectPropsExpression::class,
+        'expected_dependencies' => [
+          'module' => [
+            'datetime_range',
+          ],
+          'plugin' => [
+            'field_type:daterange',
+          ],
+        ],
       ],
-    ], $complex_example->calculateDependencies());
+    ];
+  }
+
+  /**
+   * @coversClass \Drupal\experience_builder\PropSource\StaticPropSource
+   */
+  public function testStaticPropSource(): void {
 
     // A complex *empty* example.
     $complex_empty_example = StaticPropSource::parse([
-- 
GitLab


From a9adf71988a9f3a75723ed40339d44f65f259630 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ignacio=20S=C3=A1nchez?= <nacho@isholgueras.com>
Date: Tue, 20 May 2025 15:51:26 +0200
Subject: [PATCH 13/22] Issue #3460230: Convert provider to Generator for Dx

---
 tests/src/Kernel/PropSourceTest.php | 150 ++++++++++++++--------------
 1 file changed, 74 insertions(+), 76 deletions(-)

diff --git a/tests/src/Kernel/PropSourceTest.php b/tests/src/Kernel/PropSourceTest.php
index de4899a27d..0dd7857c89 100644
--- a/tests/src/Kernel/PropSourceTest.php
+++ b/tests/src/Kernel/PropSourceTest.php
@@ -145,89 +145,87 @@ class PropSourceTest extends KernelTestBase {
     self::assertSame($expected_dependencies, $simple_example->calculateDependencies());
   }
 
-  public static function providerSimpleStaticPropSource(): array {
-    return [
-      [
-        'sourceType' => 'static:field_item:string',
-        'sourceTypeSettings' => NULL,
-        'value' => 'Hello, world!',
-        'expression' => 'ℹ︎string␟value',
-        'expected_json_representation' => '{"sourceType":"static:field_item:string","value":"Hello, world!","expression":"ℹ︎string␟value"}',
-        'field_widgets' => [
-          NULL => StringTextfieldWidget::class,
-          'string_textfield' => StringTextfieldWidget::class,
-          'string_textarea' => StringTextfieldWidget::class,
-        ],
-        'expected_user_value' => 'Hello, world!',
-        'expected_prop_expression' => FieldTypePropExpression::class,
-        'expected_dependencies' => [
-          'plugin' => [
-            'field_type:string',
-          ],
+  public static function providerSimpleStaticPropSource(): \Generator {
+    yield "simple string" => [
+      'sourceType' => 'static:field_item:string',
+      'sourceTypeSettings' => NULL,
+      'value' => 'Hello, world!',
+      'expression' => 'ℹ︎string␟value',
+      'expected_json_representation' => '{"sourceType":"static:field_item:string","value":"Hello, world!","expression":"ℹ︎string␟value"}',
+      'field_widgets' => [
+        NULL => StringTextfieldWidget::class,
+        'string_textfield' => StringTextfieldWidget::class,
+        'string_textarea' => StringTextfieldWidget::class,
+      ],
+      'expected_user_value' => 'Hello, world!',
+      'expected_prop_expression' => FieldTypePropExpression::class,
+      'expected_dependencies' => [
+        'plugin' => [
+          'field_type:string',
         ],
       ],
-      [
-        'sourceType' => 'static:field_item:uri',
-        'sourceTypeSettings' => NULL,
-        'value' => 'https://drupal.org',
-        'expression' => 'ℹ︎uri␟value',
-        'expected_json_representation' => '{"sourceType":"static:field_item:uri","value":"https:\/\/drupal.org","expression":"ℹ︎uri␟value"}',
-        'field_widgets' => [
-          NULL => UriWidget::class,
-          'uri' => UriWidget::class,
-        ],
-        'expected_user_value' => 'https://drupal.org',
-        'expected_prop_expression' => FieldTypePropExpression::class,
-        'expected_dependencies' => [
-          'plugin' => [
-            'field_type:uri',
-          ],
+    ];
+    yield "simple uri" => [
+      'sourceType' => 'static:field_item:uri',
+      'sourceTypeSettings' => NULL,
+      'value' => 'https://drupal.org',
+      'expression' => 'ℹ︎uri␟value',
+      'expected_json_representation' => '{"sourceType":"static:field_item:uri","value":"https:\/\/drupal.org","expression":"ℹ︎uri␟value"}',
+      'field_widgets' => [
+        NULL => UriWidget::class,
+        'uri' => UriWidget::class,
+      ],
+      'expected_user_value' => 'https://drupal.org',
+      'expected_prop_expression' => FieldTypePropExpression::class,
+      'expected_dependencies' => [
+        'plugin' => [
+          'field_type:uri',
         ],
       ],
-      [
-        'sourceType' => 'static:field_item:boolean',
-        'sourceTypeSettings' => NULL,
-        'value' => TRUE,
-        'expression' => 'ℹ︎boolean␟value',
-        'expected_json_representation' => '{"sourceType":"static:field_item:boolean","value":true,"expression":"ℹ︎boolean␟value"}',
-        'field_widgets' => [
-          NULL => BooleanCheckboxWidget::class,
-          'boolean_checkbox' => BooleanCheckboxWidget::class,
-        ],
-        'expected_user_value' => TRUE,
-        'expected_prop_expression' => FieldTypePropExpression::class,
-        'expected_dependencies' => [
-          'plugin' => [
-            'field_type:boolean',
-          ],
+    ];
+    yield "simple boolean" => [
+      'sourceType' => 'static:field_item:boolean',
+      'sourceTypeSettings' => NULL,
+      'value' => TRUE,
+      'expression' => 'ℹ︎boolean␟value',
+      'expected_json_representation' => '{"sourceType":"static:field_item:boolean","value":true,"expression":"ℹ︎boolean␟value"}',
+      'field_widgets' => [
+        NULL => BooleanCheckboxWidget::class,
+        'boolean_checkbox' => BooleanCheckboxWidget::class,
+      ],
+      'expected_user_value' => TRUE,
+      'expected_prop_expression' => FieldTypePropExpression::class,
+      'expected_dependencies' => [
+        'plugin' => [
+          'field_type:boolean',
         ],
       ],
-      [
-        'sourceType' => 'static:field_item:daterange',
-        'sourceTypeSettings' => NULL,
-        'value' => [
-          'value' => '2020-04-16T00:00',
-          'end_value' => '2024-07-10T10:24',
-        ],
-        'expression' => 'ℹ︎daterange␟{start↠value,stop↠end_value}',
-        'expected_json_representation' => '{"sourceType":"static:field_item:daterange","value":{"value":"2020-04-16T00:00","end_value":"2024-07-10T10:24"},"expression":"ℹ︎daterange␟{start↠value,stop↠end_value}"}',
-        'field_widgets' => [
-          NULL => DateRangeDefaultWidget::class,
-          'daterange_default' => DateRangeDefaultWidget::class,
-          'daterange_datelist' => DateRangeDatelistWidget::class,
-        ],
-        'expected_user_value' => [
-          'start' => '2020-04-16T00:00',
-          'stop' => '2024-07-10T10:24',
+    ];
+    yield "simple daterange" => [
+      'sourceType' => 'static:field_item:daterange',
+      'sourceTypeSettings' => NULL,
+      'value' => [
+        'value' => '2020-04-16T00:00',
+        'end_value' => '2024-07-10T10:24',
+      ],
+      'expression' => 'ℹ︎daterange␟{start↠value,stop↠end_value}',
+      'expected_json_representation' => '{"sourceType":"static:field_item:daterange","value":{"value":"2020-04-16T00:00","end_value":"2024-07-10T10:24"},"expression":"ℹ︎daterange␟{start↠value,stop↠end_value}"}',
+      'field_widgets' => [
+        NULL => DateRangeDefaultWidget::class,
+        'daterange_default' => DateRangeDefaultWidget::class,
+        'daterange_datelist' => DateRangeDatelistWidget::class,
+      ],
+      'expected_user_value' => [
+        'start' => '2020-04-16T00:00',
+        'stop' => '2024-07-10T10:24',
+      ],
+      'expected_prop_expression' => FieldTypeObjectPropsExpression::class,
+      'expected_dependencies' => [
+        'module' => [
+          'datetime_range',
         ],
-        'expected_prop_expression' => FieldTypeObjectPropsExpression::class,
-        'expected_dependencies' => [
-          'module' => [
-            'datetime_range',
-          ],
-          'plugin' => [
-            'field_type:daterange',
-          ],
+        'plugin' => [
+          'field_type:daterange',
         ],
       ],
     ];
-- 
GitLab


From 6a67fa3034b87e7d28a5f0f78b65e5c0f3d8edcd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ignacio=20S=C3=A1nchez?= <nacho@isholgueras.com>
Date: Tue, 20 May 2025 16:15:15 +0200
Subject: [PATCH 14/22] Issue #3460230: Refactor testStaticPropSource to a
 provider with \Generator.

---
 tests/src/Kernel/PropSourceTest.php | 271 +++++++++++-----------------
 1 file changed, 105 insertions(+), 166 deletions(-)

diff --git a/tests/src/Kernel/PropSourceTest.php b/tests/src/Kernel/PropSourceTest.php
index 0dd7857c89..65f8cd6c20 100644
--- a/tests/src/Kernel/PropSourceTest.php
+++ b/tests/src/Kernel/PropSourceTest.php
@@ -91,15 +91,15 @@ class PropSourceTest extends KernelTestBase {
 
   /**
    * @coversClass \Drupal\experience_builder\PropSource\StaticPropSource
-   * @dataProvider providerSimpleStaticPropSource
+   * @dataProvider providerStaticPropSource
    */
-  public function testSimpleStaticPropSource(
+  public function testStaticPropSource(
     string $source_type,
     array|null $source_type_settings,
     mixed $value,
     string $expression,
     string $expected_json_representation,
-    array $field_widgets,
+    array|null $field_widgets,
     mixed $expected_user_value,
     string $expected_prop_expection_class,
     array $expected_dependencies,
@@ -107,7 +107,7 @@ class PropSourceTest extends KernelTestBase {
 
     // A simple example.
     // @phpstan-ignore-next-line
-    $simple_example = StaticPropSource::parse([
+    $prop_source_example = StaticPropSource::parse([
       'sourceType' => $source_type,
       'value' => $value,
       'expression' => $expression,
@@ -115,37 +115,43 @@ class PropSourceTest extends KernelTestBase {
     ]);
     // First, get the string representation and parse it back, to prove
     // serialization and deserialization works.
-    $json_representation = (string) $simple_example;
+    $json_representation = (string) $prop_source_example;
     $this->assertSame($expected_json_representation, $json_representation);
     $decoded_representation = json_decode($json_representation, TRUE);
+    $prop_source_example = PropSource::parse($decoded_representation);
+    $this->assertInstanceOf(StaticPropSource::class, $prop_source_example);
+    // The contained information read back out.
+    $this->assertSame($source_type, $prop_source_example->getSourceType());
+    /** @var class-string $expected_prop_expection_class */
+    $this->assertInstanceOf($expected_prop_expection_class, StructuredDataPropExpression::fromString($prop_source_example->asChoice()));
+    if (NULL === $value) {
+      // Do not continue testing if there is no values.
+      return;
+    }
+
     try {
       StaticPropSource::isMinimalRepresentation($decoded_representation);
     }
     catch (\LogicException) {
       $this->fail("Not a minimal representation: $json_representation.");
     }
-    $simple_example = PropSource::parse($decoded_representation);
-    $this->assertInstanceOf(StaticPropSource::class, $simple_example);
-    // The contained information read back out.
-    $this->assertSame($source_type, $simple_example->getSourceType());
-    /** @var class-string $expected_prop_expection_class */
-    $this->assertInstanceOf($expected_prop_expection_class, StructuredDataPropExpression::fromString($simple_example->asChoice()));
-    $this->assertSame($value, $simple_example->getValue());
+    $this->assertSame($value, $prop_source_example->getValue());
     // Test the functionality of a StaticPropSource:
     // - evaluate it to populate an SDC prop
-    $this->assertSame($expected_user_value, $simple_example->evaluate(User::create([])));
+    $this->assertSame($expected_user_value, $prop_source_example->evaluate(User::create([])));
     // - the field type's item's raw value is minimized if it is single-property
-    $this->assertSame($value, $simple_example->getValue());
+    $this->assertSame($value, $prop_source_example->getValue());
     // - generate a widget to edit the stored value — using the default widget
     //   or a specified widget.
     // @see \Drupal\experience_builder\Entity\Component::$defaults
+    \assert(is_array($field_widgets));
     foreach ($field_widgets as $widget_type => $expected_widget_class) {
-      $this->assertInstanceOf($expected_widget_class, $simple_example->getWidget('irrelevant-for-test', $this->randomString(), $widget_type));
+      $this->assertInstanceOf($expected_widget_class, $prop_source_example->getWidget('irrelevant-for-test', $this->randomString(), $widget_type));
     }
-    self::assertSame($expected_dependencies, $simple_example->calculateDependencies());
+    self::assertSame($expected_dependencies, $prop_source_example->calculateDependencies());
   }
 
-  public static function providerSimpleStaticPropSource(): \Generator {
+  public static function providerStaticPropSource(): \Generator {
     yield "simple string" => [
       'sourceType' => 'static:field_item:string',
       'sourceTypeSettings' => NULL,
@@ -201,6 +207,38 @@ class PropSourceTest extends KernelTestBase {
         ],
       ],
     ];
+    yield "simple integer" => [
+      'sourceType' => 'static:field_item:integer',
+      'sourceTypeSettings' => [
+        'cardinality' => 5,
+      ],
+      'value' => [
+        20,
+        06,
+        1,
+        88,
+        92,
+      ],
+      'expression' => 'ℹ︎integer␟value',
+      'expected_json_representation' => '{"sourceType":"static:field_item:integer","value":[20,6,1,88,92],"expression":"ℹ︎integer␟value","sourceTypeSettings":{"cardinality":5}}',
+      'field_widgets' => [
+        NULL => NumberWidget::class,
+        'number' => NumberWidget::class,
+      ],
+      'expected_user_value' => [
+        20,
+        06,
+        1,
+        88,
+        92,
+      ],
+      'expected_prop_expression' => FieldTypePropExpression::class,
+      'expected_dependencies' => [
+        'plugin' => [
+          'field_type:integer',
+        ],
+      ],
+    ];
     yield "simple daterange" => [
       'sourceType' => 'static:field_item:daterange',
       'sourceTypeSettings' => NULL,
@@ -229,103 +267,7 @@ class PropSourceTest extends KernelTestBase {
         ],
       ],
     ];
-  }
-
-  /**
-   * @coversClass \Drupal\experience_builder\PropSource\StaticPropSource
-   */
-  public function testStaticPropSource(): void {
-
-    // A complex *empty* example.
-    $complex_empty_example = StaticPropSource::parse([
-      'sourceType' => 'static:field_item:entity_reference',
-      'value' => NULL,
-      'expression' => 'ℹ︎entity_reference␟{src↝entity␜␜entity:media:image␝field_media_image␞␟entity␜␜entity:file␝uri␞␟url,alt↝entity␜␜entity:media:image␝field_media_image␞␟alt,width↝entity␜␜entity:media:image␝field_media_image␞␟width,height↝entity␜␜entity:media:image␝field_media_image␞␟height}',
-      'sourceTypeSettings' => [
-        'storage' => ['target_type' => 'media'],
-        'instance' => [
-          'handler' => 'default:media',
-          'handler_settings' => [
-            'target_bundles' => ['image' => 'image'],
-          ],
-        ],
-      ],
-    ]);
-    // First, get the string representation and parse it back, to prove
-    // serialization and deserialization works.
-    $json_representation = (string) $complex_empty_example;
-    $this->assertSame('{"sourceType":"static:field_item:entity_reference","value":null,"expression":"ℹ︎entity_reference␟{src↝entity␜␜entity:media:image␝field_media_image␞␟entity␜␜entity:file␝uri␞␟url,alt↝entity␜␜entity:media:image␝field_media_image␞␟alt,width↝entity␜␜entity:media:image␝field_media_image␞␟width,height↝entity␜␜entity:media:image␝field_media_image␞␟height}","sourceTypeSettings":{"storage":{"target_type":"media"},"instance":{"handler":"default:media","handler_settings":{"target_bundles":{"image":"image"}}}}}', $json_representation);
-    $decoded_representation = json_decode($json_representation, TRUE);
-    $complex_empty_example = PropSource::parse($decoded_representation);
-    $this->assertInstanceOf(StaticPropSource::class, $complex_empty_example);
-    // The contained information read back out.
-    $this->assertSame('static:field_item:entity_reference', $complex_empty_example->getSourceType());
-    $this->assertInstanceOf(FieldTypeObjectPropsExpression::class, StructuredDataPropExpression::fromString($complex_empty_example->asChoice()));
-    $this->assertNull($complex_empty_example->getValue());
-    self::assertSame([
-      'config' => [
-        'media.type.image',
-      ],
-      'content' => [],
-      'module' => [
-        'media',
-      ],
-      'plugin' => [
-        'field_type:entity_reference',
-      ],
-    ], $complex_empty_example->calculateDependencies());
-
-    // A simple (expression targeting a simple prop) array example (with
-    // cardinality specified, rather than the default of `cardinality=1`).
-    $simple_array_example = StaticPropSource::parse([
-      'sourceType' => 'static:field_item:integer',
-      'sourceTypeSettings' => [
-        'cardinality' => 5,
-      ],
-      'value' => [
-        20,
-        06,
-        1,
-        88,
-        92,
-      ],
-      'expression' => 'ℹ︎integer␟value',
-    ]);
-    // First, get the string representation and parse it back, to prove
-    // serialization and deserialization works.
-    $json_representation = (string) $simple_array_example;
-    $this->assertSame('{"sourceType":"static:field_item:integer","value":[20,6,1,88,92],"expression":"ℹ︎integer␟value","sourceTypeSettings":{"cardinality":5}}', $json_representation);
-    $decoded_representation = json_decode($json_representation, TRUE);
-    try {
-      StaticPropSource::isMinimalRepresentation($decoded_representation);
-    }
-    catch (\LogicException) {
-      $this->fail("Not a minimal representation: $json_representation.");
-    }
-    $simple_array_example = PropSource::parse($decoded_representation);
-    $this->assertInstanceOf(StaticPropSource::class, $simple_array_example);
-    // The contained information read back out.
-    $this->assertSame('static:field_item:integer', $simple_array_example->getSourceType());
-    $this->assertInstanceOf(FieldTypePropExpression::class, StructuredDataPropExpression::fromString($simple_array_example->asChoice()));
-    $this->assertSame([20, 06, 1, 88, 92], $simple_array_example->getValue());
-    // Test the functionality of a StaticPropSource:
-    // - evaluate it to populate an SDC prop
-    $this->assertSame([20, 06, 1, 88, 92], $simple_array_example->evaluate(User::create([])));
-    // - the field type's item's raw value is minimized if it is single-property
-    $this->assertSame([20, 06, 1, 88, 92], $simple_array_example->getValue());
-    // - generate a widget to edit the stored value — using the default widget
-    //   or a specified widget.
-    // @see \Drupal\experience_builder\Entity\Component::$defaults
-    $this->assertInstanceOf(NumberWidget::class, $simple_array_example->getWidget('irrelevant-for-test', $this->randomString(), NULL));
-    $this->assertInstanceOf(NumberWidget::class, $simple_array_example->getWidget('irrelevant-for-test', $this->randomString(), 'number'));
-    // The widget plugin manager ignores any request for another widget type and
-    // falls back to the default widget if
-    // @see \Drupal\Core\Field\WidgetPluginManager::getInstance()
-    $this->assertInstanceOf(NumberWidget::class, $simple_array_example->getWidget('irrelevant-for-test', $this->randomString(), 'number'));
-
-    // A complex (expression targeting multiple props) array example (with
-    // cardinality specified, rather than the default of `cardinality=1`).
-    $complex_array_example = StaticPropSource::parse([
+    yield "complex daterange with cardinality" => [
       'sourceType' => 'static:field_item:daterange',
       'sourceTypeSettings' => [
         'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
@@ -341,65 +283,62 @@ class PropSourceTest extends KernelTestBase {
         ],
       ],
       'expression' => 'ℹ︎daterange␟{start↠value,stop↠end_value}',
-    ]);
-    // First, get the string representation and parse it back, to prove
-    // serialization and deserialization works.
-    $json_representation = (string) $complex_array_example;
-    $this->assertSame('{"sourceType":"static:field_item:daterange","value":[{"value":"2020-04-16T00:00","end_value":"2024-07-10T10:24"},{"value":"2020-04-16T00:00","end_value":"2024-09-26T11:31"}],"expression":"ℹ︎daterange␟{start↠value,stop↠end_value}","sourceTypeSettings":{"cardinality":-1}}', $json_representation);
-    $decoded_representation = json_decode($json_representation, TRUE);
-    try {
-      StaticPropSource::isMinimalRepresentation($decoded_representation);
-    }
-    catch (\LogicException) {
-      $this->fail("Not a minimal representation: $json_representation.");
-    }
-    $complex_array_example = PropSource::parse($decoded_representation);
-    $this->assertInstanceOf(StaticPropSource::class, $complex_array_example);
-    // The contained information read back out.
-    $this->assertSame('static:field_item:daterange', $complex_array_example->getSourceType());
-    $this->assertInstanceOf(FieldTypeObjectPropsExpression::class, StructuredDataPropExpression::fromString($complex_array_example->asChoice()));
-    $this->assertSame([
-      [
-        'value' => '2020-04-16T00:00',
-        'end_value' => '2024-07-10T10:24',
+      'expected_json_representation' => '{"sourceType":"static:field_item:daterange","value":[{"value":"2020-04-16T00:00","end_value":"2024-07-10T10:24"},{"value":"2020-04-16T00:00","end_value":"2024-09-26T11:31"}],"expression":"ℹ︎daterange␟{start↠value,stop↠end_value}","sourceTypeSettings":{"cardinality":-1}}',
+      'field_widgets' => [
+        NULL => DateRangeDefaultWidget::class,
+        'daterange_default' => DateRangeDefaultWidget::class,
+        'daterange_datelist' => DateRangeDatelistWidget::class,
       ],
-      [
-        'value' => '2020-04-16T00:00',
-        'end_value' => '2024-09-26T11:31',
+      'expected_user_value' => [
+        [
+          'start' => '2020-04-16T00:00',
+          'stop' => '2024-07-10T10:24',
+        ],
+        [
+          'start' => '2020-04-16T00:00',
+          'stop' => '2024-09-26T11:31',
+        ],
       ],
-    ], $complex_array_example->getValue());
-    // Test the functionality of a StaticPropSource:
-    // - evaluate it to populate an SDC prop
-    $this->assertSame([
-      [
-        'start' => '2020-04-16T00:00',
-        'stop' => '2024-07-10T10:24',
+      'expected_prop_expression' => FieldTypeObjectPropsExpression::class,
+      'expected_dependencies' => [
+        'module' => [
+          'datetime_range',
+        ],
+        'plugin' => [
+          'field_type:daterange',
+        ],
       ],
-      [
-        'start' => '2020-04-16T00:00',
-        'stop' => '2024-09-26T11:31',
+    ];
+    yield "complex empty example with entity_reference" => [
+      'sourceType' => 'static:field_item:entity_reference',
+      'sourceTypeSettings' => [
+        'storage' => ['target_type' => 'media'],
+        'instance' => [
+          'handler' => 'default:media',
+          'handler_settings' => [
+            'target_bundles' => ['image' => 'image'],
+          ],
+        ],
       ],
-    ], $complex_array_example->evaluate(User::create([])));
-    // - the field type's item's raw value is minimized if it is single-property
-    $this->assertSame(
-      [
-        [
-          'value' => '2020-04-16T00:00',
-          'end_value' => '2024-07-10T10:24',
+      'value' => NULL,
+      'expression' => 'ℹ︎entity_reference␟{src↝entity␜␜entity:media:image␝field_media_image␞␟entity␜␜entity:file␝uri␞␟url,alt↝entity␜␜entity:media:image␝field_media_image␞␟alt,width↝entity␜␜entity:media:image␝field_media_image␞␟width,height↝entity␜␜entity:media:image␝field_media_image␞␟height}',
+      'expected_json_representation' => '{"sourceType":"static:field_item:entity_reference","value":null,"expression":"ℹ︎entity_reference␟{src↝entity␜␜entity:media:image␝field_media_image␞␟entity␜␜entity:file␝uri␞␟url,alt↝entity␜␜entity:media:image␝field_media_image␞␟alt,width↝entity␜␜entity:media:image␝field_media_image␞␟width,height↝entity␜␜entity:media:image␝field_media_image␞␟height}","sourceTypeSettings":{"storage":{"target_type":"media"},"instance":{"handler":"default:media","handler_settings":{"target_bundles":{"image":"image"}}}}}',
+      'field_widgets' => NULL,
+      'expected_user_value' => NULL,
+      'expected_prop_expression' => FieldTypeObjectPropsExpression::class,
+      'expected_dependencies' => [
+        'config' => [
+          'media.type.image',
         ],
-        [
-          'value' => '2020-04-16T00:00',
-          'end_value' => '2024-09-26T11:31',
+        'content' => [],
+        'module' => [
+          'media',
+        ],
+        'plugin' => [
+          'field_type:entity_reference',
         ],
       ],
-      $complex_array_example->getValue()
-    );
-    // - generate a widget to edit the stored value — using the default widget
-    //   or a specified widget.
-    // @see \Drupal\experience_builder\Entity\Component::$defaults
-    $this->assertInstanceOf(DateRangeDefaultWidget::class, $complex_array_example->getWidget('irrelevant-for-test', $this->randomString(), NULL));
-    $this->assertInstanceOf(DateRangeDefaultWidget::class, $complex_array_example->getWidget('irrelevant-for-test', $this->randomString(), 'daterange_default'));
-    $this->assertInstanceOf(DateRangeDatelistWidget::class, $complex_array_example->getWidget('irrelevant-for-test', $this->randomString(), 'daterange_datelist'));
+    ];
   }
 
   /**
-- 
GitLab


From 6b63910aec63e6b953fd968c79433e0d51dd336e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ignacio=20S=C3=A1nchez?= <nacho@isholgueras.com>
Date: Tue, 20 May 2025 16:28:29 +0200
Subject: [PATCH 15/22] Issue #3460230: Fix mispelling.

---
 tests/src/Kernel/PropSourceTest.php | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/tests/src/Kernel/PropSourceTest.php b/tests/src/Kernel/PropSourceTest.php
index 65f8cd6c20..e9522bbf73 100644
--- a/tests/src/Kernel/PropSourceTest.php
+++ b/tests/src/Kernel/PropSourceTest.php
@@ -101,7 +101,7 @@ class PropSourceTest extends KernelTestBase {
     string $expected_json_representation,
     array|null $field_widgets,
     mixed $expected_user_value,
-    string $expected_prop_expection_class,
+    string $expected_prop_expression_class,
     array $expected_dependencies,
   ): void {
 
@@ -122,8 +122,8 @@ class PropSourceTest extends KernelTestBase {
     $this->assertInstanceOf(StaticPropSource::class, $prop_source_example);
     // The contained information read back out.
     $this->assertSame($source_type, $prop_source_example->getSourceType());
-    /** @var class-string $expected_prop_expection_class */
-    $this->assertInstanceOf($expected_prop_expection_class, StructuredDataPropExpression::fromString($prop_source_example->asChoice()));
+    /** @var class-string $expected_prop_expression_class */
+    $this->assertInstanceOf($expected_prop_expression_class, StructuredDataPropExpression::fromString($prop_source_example->asChoice()));
     if (NULL === $value) {
       // Do not continue testing if there is no values.
       return;
-- 
GitLab


From 8deb320422377d02fbfb86f1aaaa4ba229f60c69 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ignacio=20S=C3=A1nchez?= <nacho@isholgueras.com>
Date: Tue, 20 May 2025 18:41:50 +0200
Subject: [PATCH 16/22] Issue #3460230: Add a component with media_library to
 cover the dependencies.

---
 .../Kernel/Config/ComponentValidationTest.php | 36 +++++++++++++++++--
 1 file changed, 34 insertions(+), 2 deletions(-)

diff --git a/tests/src/Kernel/Config/ComponentValidationTest.php b/tests/src/Kernel/Config/ComponentValidationTest.php
index 2476b40601..e371b922f9 100644
--- a/tests/src/Kernel/Config/ComponentValidationTest.php
+++ b/tests/src/Kernel/Config/ComponentValidationTest.php
@@ -46,6 +46,12 @@ class ComponentValidationTest extends BetterConfigEntityValidationTestBase {
     'options',
     'path',
     'link',
+    'field',
+    'media',
+    'media_library',
+    'views',
+    'user',
+    'filter',
   ];
 
   /**
@@ -72,6 +78,12 @@ class ComponentValidationTest extends BetterConfigEntityValidationTestBase {
    */
   protected function setUp(): void {
     parent::setUp();
+    $this->installEntitySchema('media');
+    $this->installEntitySchema('user');
+    $this->setInstallProfile('standard');
+    $this->installConfig(['media']);
+    $this->installSchema('file', ['file_usage']);
+    $this->installEntitySchema('filter_format');
 
     $this->entity = Component::create([
       'id' => 'sdc.sdc_test.my-cta',
@@ -110,6 +122,23 @@ class ComponentValidationTest extends BetterConfigEntityValidationTestBase {
             'default_value' => NULL,
             'expression' => 'ℹ︎list_string␟value',
           ],
+          'image' => [
+            'field_type' => 'image',
+            'field_storage_settings' => [
+              'target_type' => 'media',
+            ],
+            'field_instance_settings' => [
+              'handler' => 'default:media',
+              'handler_settings' => [
+                'target_bundles' => [
+                  'image' => 'image',
+                ],
+              ],
+            ],
+            'field_widget' => 'media_library_widget',
+            'default_value' => [],
+            'expression' => 'ℹ︎image␟{src↝entity␜␜entity:file␝uri␞␟url,alt↠alt,width↠width,height↠height}',
+          ],
         ],
       ],
       'label' => 'Test',
@@ -124,12 +153,12 @@ class ComponentValidationTest extends BetterConfigEntityValidationTestBase {
   public function testEntityIsValid(): void {
     parent::testEntityIsValid();
 
-    // @todo Change the tested component here to one that includes an `image` prop shape, to allow testing with entity reference field type + media module dependency + media library dependency ONLY because of the widget.
-
     // Beyond validity, validate config dependencies are computed correctly.
     $this->assertSame(
       [
         'module' => [
+          'image',
+          'media_library',
           'options',
           'sdc_test',
         ],
@@ -138,6 +167,8 @@ class ComponentValidationTest extends BetterConfigEntityValidationTestBase {
     );
     $this->assertSame([
       'module' => [
+        'image',
+        'media_library',
         'options',
         'sdc_test',
         'experience_builder',
@@ -207,6 +238,7 @@ class ComponentValidationTest extends BetterConfigEntityValidationTestBase {
     ]);
     $this->assertValidationErrors([
       'settings.prop_field_definitions' => "'target' is a required key.",
+      'settings.prop_field_definitions.image' => "'image' is not a supported key.",
     ]);
 
     // @see \Drupal\experience_builder\Plugin\ExperienceBuilder\ComponentSource\BlockComponent
-- 
GitLab


From 4af04039dc2b2fb3b45cbe26de14097bae85db72 Mon Sep 17 00:00:00 2001
From: Ted Bowman <ted+git@tedbow.com>
Date: Tue, 20 May 2025 16:12:29 -0400
Subject: [PATCH 17/22] image property is not in the test component

---
 tests/src/Kernel/Config/ComponentValidationTest.php | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/tests/src/Kernel/Config/ComponentValidationTest.php b/tests/src/Kernel/Config/ComponentValidationTest.php
index e371b922f9..5b86b59552 100644
--- a/tests/src/Kernel/Config/ComponentValidationTest.php
+++ b/tests/src/Kernel/Config/ComponentValidationTest.php
@@ -231,14 +231,16 @@ class ComponentValidationTest extends BetterConfigEntityValidationTestBase {
         'local_source_id' => 'my-cta',
         'prop_field_definitions' => array_diff_key(
           $this->entity->getSettings()['prop_field_definitions'],
-          array_flip(['target']),
+          // Remove the 'target' key to trigger a validation error.
+          // Remove the 'image' because the property is not in the JS component
+          // created above.
+          array_flip(['target', 'image']),
         ),
       ],
       'label' => 'Test',
     ]);
     $this->assertValidationErrors([
       'settings.prop_field_definitions' => "'target' is a required key.",
-      'settings.prop_field_definitions.image' => "'image' is not a supported key.",
     ]);
 
     // @see \Drupal\experience_builder\Plugin\ExperienceBuilder\ComponentSource\BlockComponent
-- 
GitLab


From c62d5eb8fc90ca8d28479d6d6bac213c77c1e623 Mon Sep 17 00:00:00 2001
From: Ted Bowman <ted+git@tedbow.com>
Date: Tue, 20 May 2025 17:05:26 -0400
Subject: [PATCH 18/22] test EntityReferenceAutocompleteWidget widget for
 static prop source

---
 tests/src/Kernel/PropSourceTest.php | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/tests/src/Kernel/PropSourceTest.php b/tests/src/Kernel/PropSourceTest.php
index e9522bbf73..00dc7eace8 100644
--- a/tests/src/Kernel/PropSourceTest.php
+++ b/tests/src/Kernel/PropSourceTest.php
@@ -8,6 +8,7 @@ use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Extension\ExtensionPathResolver;
 use Drupal\Core\Field\FieldStorageDefinitionInterface;
 use Drupal\Core\Field\Plugin\Field\FieldWidget\BooleanCheckboxWidget;
+use Drupal\Core\Field\Plugin\Field\FieldWidget\EntityReferenceAutocompleteWidget;
 use Drupal\Core\Field\Plugin\Field\FieldWidget\NumberWidget;
 use Drupal\Core\Field\Plugin\Field\FieldWidget\StringTextfieldWidget;
 use Drupal\Core\Field\Plugin\Field\FieldWidget\UriWidget;
@@ -323,7 +324,11 @@ class PropSourceTest extends KernelTestBase {
       'value' => NULL,
       'expression' => 'ℹ︎entity_reference␟{src↝entity␜␜entity:media:image␝field_media_image␞␟entity␜␜entity:file␝uri␞␟url,alt↝entity␜␜entity:media:image␝field_media_image␞␟alt,width↝entity␜␜entity:media:image␝field_media_image␞␟width,height↝entity␜␜entity:media:image␝field_media_image␞␟height}',
       'expected_json_representation' => '{"sourceType":"static:field_item:entity_reference","value":null,"expression":"ℹ︎entity_reference␟{src↝entity␜␜entity:media:image␝field_media_image␞␟entity␜␜entity:file␝uri␞␟url,alt↝entity␜␜entity:media:image␝field_media_image␞␟alt,width↝entity␜␜entity:media:image␝field_media_image␞␟width,height↝entity␜␜entity:media:image␝field_media_image␞␟height}","sourceTypeSettings":{"storage":{"target_type":"media"},"instance":{"handler":"default:media","handler_settings":{"target_bundles":{"image":"image"}}}}}',
-      'field_widgets' => NULL,
+      'field_widgets' => [
+        NULL => EntityReferenceAutocompleteWidget::class,
+        'entity_reference_autocomplete' => EntityReferenceAutocompleteWidget::class,
+        'daterange_datelist' => EntityReferenceAutocompleteWidget::class,
+      ],
       'expected_user_value' => NULL,
       'expected_prop_expression' => FieldTypeObjectPropsExpression::class,
       'expected_dependencies' => [
-- 
GitLab


From 03cb8ac0a6e0dfa1e05e4a61f5a18333592178dd Mon Sep 17 00:00:00 2001
From: Ted Bowman <ted+git@tedbow.com>
Date: Tue, 20 May 2025 17:06:16 -0400
Subject: [PATCH 19/22] remove 'simple example' not comment no longer needed

---
 tests/src/Kernel/PropSourceTest.php | 2 --
 1 file changed, 2 deletions(-)

diff --git a/tests/src/Kernel/PropSourceTest.php b/tests/src/Kernel/PropSourceTest.php
index 00dc7eace8..8856f321fc 100644
--- a/tests/src/Kernel/PropSourceTest.php
+++ b/tests/src/Kernel/PropSourceTest.php
@@ -105,8 +105,6 @@ class PropSourceTest extends KernelTestBase {
     string $expected_prop_expression_class,
     array $expected_dependencies,
   ): void {
-
-    // A simple example.
     // @phpstan-ignore-next-line
     $prop_source_example = StaticPropSource::parse([
       'sourceType' => $source_type,
-- 
GitLab


From 8de7c8df460224617f16ff2e58912a6d175d245d Mon Sep 17 00:00:00 2001
From: Ted Bowman <ted+git@tedbow.com>
Date: Tue, 20 May 2025 17:07:29 -0400
Subject: [PATCH 20/22] we can test depedencies and widget even with no value

---
 tests/src/Kernel/PropSourceTest.php | 21 +++++++++++++--------
 1 file changed, 13 insertions(+), 8 deletions(-)

diff --git a/tests/src/Kernel/PropSourceTest.php b/tests/src/Kernel/PropSourceTest.php
index 8856f321fc..234eb1c294 100644
--- a/tests/src/Kernel/PropSourceTest.php
+++ b/tests/src/Kernel/PropSourceTest.php
@@ -123,7 +123,20 @@ class PropSourceTest extends KernelTestBase {
     $this->assertSame($source_type, $prop_source_example->getSourceType());
     /** @var class-string $expected_prop_expression_class */
     $this->assertInstanceOf($expected_prop_expression_class, StructuredDataPropExpression::fromString($prop_source_example->asChoice()));
+    self::assertSame($expected_dependencies, $prop_source_example->calculateDependencies());
+    // - generate a widget to edit the stored value — using the default widget
+    //   or a specified widget.
+    // @see \Drupal\experience_builder\Entity\Component::$defaults
+    \assert(is_array($field_widgets));
+    // Ensure we always test the default widget.
+    \assert(isset($field_widgets[NULL]));
+    // Ensure an unknown widget type is handled gracefully.
+    $field_widgets['not_real'] = $field_widgets[NULL];
+    foreach ($field_widgets as $widget_type => $expected_widget_class) {
+      $this->assertInstanceOf($expected_widget_class, $prop_source_example->getWidget('irrelevant-for-test', $this->randomString(), $widget_type));
+    }
     if (NULL === $value) {
+      $this->assertNull($expected_user_value);
       // Do not continue testing if there is no values.
       return;
     }
@@ -140,14 +153,6 @@ class PropSourceTest extends KernelTestBase {
     $this->assertSame($expected_user_value, $prop_source_example->evaluate(User::create([])));
     // - the field type's item's raw value is minimized if it is single-property
     $this->assertSame($value, $prop_source_example->getValue());
-    // - generate a widget to edit the stored value — using the default widget
-    //   or a specified widget.
-    // @see \Drupal\experience_builder\Entity\Component::$defaults
-    \assert(is_array($field_widgets));
-    foreach ($field_widgets as $widget_type => $expected_widget_class) {
-      $this->assertInstanceOf($expected_widget_class, $prop_source_example->getWidget('irrelevant-for-test', $this->randomString(), $widget_type));
-    }
-    self::assertSame($expected_dependencies, $prop_source_example->calculateDependencies());
   }
 
   public static function providerStaticPropSource(): \Generator {
-- 
GitLab


From 2f69d01ae5783e67c732b860d5e7463fe78113c9 Mon Sep 17 00:00:00 2001
From: Wim Leers <44946-wimleers@users.noreply.drupalcode.org>
Date: Wed, 21 May 2025 10:37:34 +0000
Subject: [PATCH 21/22] Nits: restore lost comments + clarify case labels.

---
 tests/src/Kernel/PropSourceTest.php | 16 ++++++++++------
 1 file changed, 10 insertions(+), 6 deletions(-)

diff --git a/tests/src/Kernel/PropSourceTest.php b/tests/src/Kernel/PropSourceTest.php
index 234eb1c294..a179c90763 100644
--- a/tests/src/Kernel/PropSourceTest.php
+++ b/tests/src/Kernel/PropSourceTest.php
@@ -156,7 +156,7 @@ class PropSourceTest extends KernelTestBase {
   }
 
   public static function providerStaticPropSource(): \Generator {
-    yield "simple string" => [
+    yield "scalar shape, field type=string, cardinality=1" => [
       'sourceType' => 'static:field_item:string',
       'sourceTypeSettings' => NULL,
       'value' => 'Hello, world!',
@@ -175,7 +175,7 @@ class PropSourceTest extends KernelTestBase {
         ],
       ],
     ];
-    yield "simple uri" => [
+    yield "scalar shape, field type=uri, cardinality=1" => [
       'sourceType' => 'static:field_item:uri',
       'sourceTypeSettings' => NULL,
       'value' => 'https://drupal.org',
@@ -193,7 +193,7 @@ class PropSourceTest extends KernelTestBase {
         ],
       ],
     ];
-    yield "simple boolean" => [
+    yield "scalar shape, field type=boolean, cardinality=1" => [
       'sourceType' => 'static:field_item:boolean',
       'sourceTypeSettings' => NULL,
       'value' => TRUE,
@@ -211,7 +211,9 @@ class PropSourceTest extends KernelTestBase {
         ],
       ],
     ];
-    yield "simple integer" => [
+    // A simple (expression targeting a simple prop) array example (with
+    // cardinality specified, rather than the default of `cardinality=1`).
+    yield "scalar shape, field type=integer, cardinality=5" => [
       'sourceType' => 'static:field_item:integer',
       'sourceTypeSettings' => [
         'cardinality' => 5,
@@ -243,7 +245,7 @@ class PropSourceTest extends KernelTestBase {
         ],
       ],
     ];
-    yield "simple daterange" => [
+    yield "object shape, daterange field, cardinality=1" => [
       'sourceType' => 'static:field_item:daterange',
       'sourceTypeSettings' => NULL,
       'value' => [
@@ -271,7 +273,9 @@ class PropSourceTest extends KernelTestBase {
         ],
       ],
     ];
-    yield "complex daterange with cardinality" => [
+    // A complex (expression targeting multiple props) array example (with
+    // cardinality specified, rather than the default of `cardinality=1`).
+    yield "object shape, daterange field, cardinality=UNLIMITED" => [
       'sourceType' => 'static:field_item:daterange',
       'sourceTypeSettings' => [
         'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
-- 
GitLab


From 159e46e91b77cdbd595637ad10c4f9842c2463a7 Mon Sep 17 00:00:00 2001
From: Wim Leers <44946-wimleers@users.noreply.drupalcode.org>
Date: Wed, 21 May 2025 10:44:35 +0000
Subject: [PATCH 22/22] Two `@todo`s pointing to
 https://www.drupal.org/project/experience_builder/issues/3525759

---
 tests/src/Kernel/Config/ComponentValidationTest.php | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/tests/src/Kernel/Config/ComponentValidationTest.php b/tests/src/Kernel/Config/ComponentValidationTest.php
index 5b86b59552..cf4868b288 100644
--- a/tests/src/Kernel/Config/ComponentValidationTest.php
+++ b/tests/src/Kernel/Config/ComponentValidationTest.php
@@ -122,6 +122,7 @@ class ComponentValidationTest extends BetterConfigEntityValidationTestBase {
             'default_value' => NULL,
             'expression' => 'ℹ︎list_string␟value',
           ],
+          // @todo This will start failing validation in https://www.drupal.org/i/3525759.
           'image' => [
             'field_type' => 'image',
             'field_storage_settings' => [
@@ -234,6 +235,7 @@ class ComponentValidationTest extends BetterConfigEntityValidationTestBase {
           // Remove the 'target' key to trigger a validation error.
           // Remove the 'image' because the property is not in the JS component
           // created above.
+          // @todo Remove "image" from this in https://www.drupal.org/i/3525759.
           array_flip(['target', 'image']),
         ),
       ],
-- 
GitLab