diff --git a/src/Plugin/UiPatterns/PropType/BooleanPropType.php b/src/Plugin/UiPatterns/PropType/BooleanPropType.php
index 25e74d25a614852101d73cdc5c81c4bddc960ecd..2b9d2bbafc8e0a8c60a8273655a217fb4fc167a6 100644
--- a/src/Plugin/UiPatterns/PropType/BooleanPropType.php
+++ b/src/Plugin/UiPatterns/PropType/BooleanPropType.php
@@ -6,6 +6,7 @@ namespace Drupal\ui_patterns\Plugin\UiPatterns\PropType;
 
 use Drupal\Core\StringTranslation\TranslatableMarkup;
 use Drupal\ui_patterns\Attribute\PropType;
+use Drupal\ui_patterns\PropTypeConversionTrait;
 use Drupal\ui_patterns\PropTypePluginBase;
 
 /**
@@ -22,10 +23,20 @@ use Drupal\ui_patterns\PropTypePluginBase;
 )]
 class BooleanPropType extends PropTypePluginBase {
 
+  use PropTypeConversionTrait;
+
   /**
    * {@inheritdoc}
    */
   public static function normalize(mixed $value, ?array $definition = NULL): bool {
+    if (is_bool($value)) {
+      return $value;
+    }
+    static::convertToScalar($value);
+    if (is_numeric($value) && is_string($value)) {
+      $value = (int) $value;
+      return (bool) $value;
+    }
     return (bool) $value;
   }
 
diff --git a/src/Plugin/UiPatterns/PropType/IdentifierPropType.php b/src/Plugin/UiPatterns/PropType/IdentifierPropType.php
index 17c88e0fb5d4038968cbb2db050a0d389ccd7d6e..c2f152da2def79efe9c7db12d6627c1d51539ba5 100644
--- a/src/Plugin/UiPatterns/PropType/IdentifierPropType.php
+++ b/src/Plugin/UiPatterns/PropType/IdentifierPropType.php
@@ -30,7 +30,7 @@ class IdentifierPropType extends PropTypePluginBase {
    * {@inheritdoc}
    */
   public static function normalize(mixed $value, ?array $definition = NULL): mixed {
-    return static::convertToString($value);
+    return strip_tags(static::convertToString($value));
   }
 
 }
diff --git a/src/PropTypeConversionTrait.php b/src/PropTypeConversionTrait.php
index c062430bdf56ea75737f5bac9173abe119a29499..137bf628c806d4d44d5e489af4c1674119378110 100644
--- a/src/PropTypeConversionTrait.php
+++ b/src/PropTypeConversionTrait.php
@@ -52,7 +52,9 @@ trait PropTypeConversionTrait {
       return NULL;
     }
     if (!empty(Element::properties($array))) {
-      $value = (string) \Drupal::service('renderer')->render($array);
+      /** @var \Drupal\Core\Render\Renderer $renderer */
+      $renderer = \Drupal::service('renderer');
+      $value = (string) $renderer->renderInIsolation($array);
       if ($strip_tags_from_render_arrays) {
         $value = strip_tags($value);
       }
diff --git a/tests/modules/ui_patterns_test/components/prop_types_tests/prop_types_tests.component.yml b/tests/modules/ui_patterns_test/components/prop_types_tests/prop_types_tests.component.yml
index 5883dcde9b541706f5319e6a58146516f3b343cb..cebe4ed827b8cc0039d10e3d77f23784f0adf6ca 100644
--- a/tests/modules/ui_patterns_test/components/prop_types_tests/prop_types_tests.component.yml
+++ b/tests/modules/ui_patterns_test/components/prop_types_tests/prop_types_tests.component.yml
@@ -5,8 +5,6 @@ props:
   required:
     - string
   properties:
-    nothing:
-      title: "Unknown (nothing)"
     object:
       title: "Unknown (empty object)"
       type: "object"
diff --git a/tests/modules/ui_patterns_test/components/test-component/test-component.twig b/tests/modules/ui_patterns_test/components/test-component/test-component.twig
index 1288e69f2f2fd86eee8cf7149af3dc675fcba144..7abf561dbefebc421a3a7fdd8a0f315141ec296e 100644
--- a/tests/modules/ui_patterns_test/components/test-component/test-component.twig
+++ b/tests/modules/ui_patterns_test/components/test-component/test-component.twig
@@ -1,5 +1,9 @@
 {% set variant = variant|default('') %}
-<div{{ attributes.addClass(['ui-patterns-test-component', 'ui-patterns-test-component-variant-' ~ variant]) }}>
+{% set my_attributes = create_attribute(attributes.storage()) %}
+<div{{ my_attributes.addClass(['ui-patterns-test-component', 'ui-patterns-test-component-variant-' ~ variant]) }}>
+  <div class="ui-patterns-props-attributes">
+    <div{{ attributes }}></div>
+  </div>
   <div class="ui-patterns-props-string">
     {{ string }}
   </div>
diff --git a/tests/src/Unit/PropTypeNormalization/AttributesPropTypeNormalizationTest.php b/tests/src/Kernel/PropTypeNormalization/AttributesPropTypeTest.php
similarity index 52%
rename from tests/src/Unit/PropTypeNormalization/AttributesPropTypeNormalizationTest.php
rename to tests/src/Kernel/PropTypeNormalization/AttributesPropTypeTest.php
index 496d2d996d929a908c114d4c2ff363a8ad72dc2c..2b218e719975b9a900f77337dd25ab784e6b9e64 100644
--- a/tests/src/Unit/PropTypeNormalization/AttributesPropTypeNormalizationTest.php
+++ b/tests/src/Kernel/PropTypeNormalization/AttributesPropTypeTest.php
@@ -2,48 +2,81 @@
 
 declare(strict_types=1);
 
-namespace Drupal\Tests\ui_patterns\Unit;
+namespace Drupal\Tests\ui_patterns\Kernel\PropTypeNormalization;
 
-use Drupal\Tests\UnitTestCase;
+use Drupal\Core\Template\Attribute;
+use Drupal\Tests\ui_patterns\Kernel\PropTypeNormalizationTestBase;
 use Drupal\ui_patterns\Plugin\UiPatterns\PropType\AttributesPropType;
 
 /**
- * @coversDefaultClass Drupal\ui_patterns\Plugin\UiPatterns\PropType\LinksPropType
+ * Test AttributesPropType normalization.
  *
+ * @coversDefaultClass \Drupal\ui_patterns\Plugin\UiPatterns\PropType\AttributesPropType
  * @group ui_patterns
  */
-final class AttributesPropTypeNormalizationTest extends UnitTestCase {
+class AttributesPropTypeTest extends PropTypeNormalizationTestBase {
 
   /**
-   * Test the method ::normalize().
+   * Test normalize static method.
    *
-   * @dataProvider provideNormalizationData
+   * @dataProvider normalizationTests
    */
-  public function testNormalize(array $value, array $expected): void {
-    $normalized = AttributesPropType::normalize($value);
-    self::assertEquals($normalized, $expected);
+  public function testNormalization(mixed $value, mixed $expected) : void {
+    $normalized = AttributesPropType::normalize($value, $this->testComponentProps['attributes']);
+    $this->assertEquals($normalized, $expected);
   }
 
   /**
-   * Provide data for testNormalize.
+   * Test rendered component with prop.
+   *
+   * @dataProvider renderingTests
    */
-  public static function provideNormalizationData(): \Generator {
-    $data = [
-      "Empty value" => [
-        "value" => [],
-        "expected" => [],
-      ],
+  public function testRendering(mixed $value, mixed $rendered_value) : void {
+    $this->runRenderPropTest('attributes', ["value" => $value, "rendered_value" => $rendered_value]);
+  }
+
+  /**
+   * Provides data for testNormalization.
+   */
+  public static function normalizationTests() : array {
+    return [
+      "Empty value" => [[], []],
       "Standardized primitives, so already OK" => self::standardizedPrimitives(),
       "Type transformations" => self::typeTransformation(),
       "List array" => self::listArray(),
+    ];
+  }
 
+  /**
+   * Provides data for testNormalization.
+   */
+  public static function renderingTests() : array {
+    return [
+      "Empty Value" => [
+        [],
+        '<div data-component-id="ui_patterns_test:test-component"></div>',
+      ],
+      "attribute_object" => [
+        new Attribute(["data-foo" => "bar"]),
+        ' data-foo="bar"',
+      ],
+      "integer" => [
+        ["data-foo" => 1],
+        ' data-foo="1"',
+      ],
+      "array" => [
+        ["key" => ["1", "2"]],
+        ' key="1 2"',
+      ],
+      "escaping" => [
+        ["key" => '"'],
+        ' key="&quot;"',
+      ],
+      "rendered value" => [
+        ["key" => ["#markup" => "value"]],
+        ' key="value"',
+      ],
     ];
-    foreach ($data as $label => $test) {
-      yield $label => [
-        $test['value'],
-        $test['expected'],
-      ];
-    };
   }
 
   /**
@@ -61,11 +94,7 @@ final class AttributesPropTypeNormalizationTest extends UnitTestCase {
       "integer" => 4,
       "float" => 1.4,
     ];
-    $expected = $value;
-    return [
-      "value" => $value,
-      "expected" => $expected,
-    ];
+    return [$value, $value];
   }
 
   /**
@@ -112,10 +141,7 @@ final class AttributesPropTypeNormalizationTest extends UnitTestCase {
         '{"deep":{"very deep":["foo","bar"]}}',
       ],
     ];
-    return [
-      "value" => $value,
-      "expected" => $expected,
-    ];
+    return [$value, $expected];
   }
 
   /**
@@ -134,10 +160,7 @@ final class AttributesPropTypeNormalizationTest extends UnitTestCase {
       "1" => "Two",
       "2" => 3,
     ];
-    return [
-      "value" => $value,
-      "expected" => $expected,
-    ];
+    return [$value, $expected];
   }
 
 }
diff --git a/tests/src/Kernel/PropTypeNormalization/BooleanPropTypeTest.php b/tests/src/Kernel/PropTypeNormalization/BooleanPropTypeTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..63417c9a44124f03bddfa90891a5f44612bf58c7
--- /dev/null
+++ b/tests/src/Kernel/PropTypeNormalization/BooleanPropTypeTest.php
@@ -0,0 +1,84 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Tests\ui_patterns\Kernel\PropTypeNormalization;
+
+use Drupal\Tests\ui_patterns\Kernel\PropTypeNormalizationTestBase;
+use Drupal\ui_patterns\Plugin\UiPatterns\PropType\BooleanPropType;
+
+/**
+ * Test BooleanPropType normalization.
+ *
+ * @coversDefaultClass \Drupal\ui_patterns\Plugin\UiPatterns\PropType\BooleanPropType
+ * @group ui_patterns
+ */
+class BooleanPropTypeTest extends PropTypeNormalizationTestBase {
+
+  /**
+   * Test normalize static method.
+   *
+   * @dataProvider normalizationTests
+   */
+  public function testNormalization(mixed $value, mixed $expected) : void {
+    $normalized = BooleanPropType::normalize($value, $this->testComponentProps['boolean']);
+    $this->assertEquals($normalized, $expected);
+  }
+
+  /**
+   * Test rendered component with prop.
+   *
+   * @dataProvider renderingTests
+   */
+  public function testRendering(mixed $value, mixed $rendered_value) : void {
+    $this->runRenderPropTest('boolean', ["value" => $value, "rendered_value" => $rendered_value]);
+  }
+
+  /**
+   * Provides data for testNormalization.
+   */
+  public static function normalizationTests() : array {
+    return [
+      "null value" => [NULL, NULL],
+      "false value" => [FALSE, FALSE],
+      "true value" => [TRUE, TRUE],
+      "integer 0" => [0, FALSE],
+      "integer pos" => [22, TRUE],
+      "string empty" => ["", FALSE],
+      "string zero" => ["0", FALSE],
+      "string not zero" => ["22", TRUE],
+      "html" => ["<p>0</p>", TRUE],
+      "markup 0" => [["#markup" => "0"], FALSE],
+      "markup 1" => [["#markup" => "1"], TRUE],
+    ];
+  }
+
+  /**
+   * Provides data for testNormalization.
+   */
+  public static function renderingTests() : array {
+    return [
+      "null value" => [
+        NULL,
+        '<div class="ui-patterns-props-boolean"></div>',
+      ],
+      "false value" => [
+        FALSE,
+        '<div class="ui-patterns-props-boolean"></div>',
+      ],
+      "true value" => [
+        TRUE,
+        '<div class="ui-patterns-props-boolean">1</div>',
+      ],
+      "zero string value" => [
+        "0",
+        '<div class="ui-patterns-props-boolean"></div>',
+      ],
+      "not zero string value" => [
+        "22",
+        '<div class="ui-patterns-props-boolean">1</div>',
+      ],
+    ];
+  }
+
+}
diff --git a/tests/src/Kernel/PropTypeNormalization/ComplexTest.php b/tests/src/Kernel/PropTypeNormalization/ComplexTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..c5490f09269de320128e91a95acee03bf96710cf
--- /dev/null
+++ b/tests/src/Kernel/PropTypeNormalization/ComplexTest.php
@@ -0,0 +1,58 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Tests\ui_patterns\Kernel\PropTypeNormalization;
+
+use Drupal\Tests\ui_patterns\Kernel\PropTypeNormalizationTestBase;
+
+/**
+ * Test some complex cases.
+ *
+ * @group ui_patterns
+ */
+class ComplexTest extends PropTypeNormalizationTestBase {
+
+  /**
+   * Test slot normalization.
+   */
+  public function testNestedComponentWithForm() : void {
+    // Test nested component with form.
+    $render_array_tests = [
+      [
+        "#type" => "inline_template",
+        "#template" => "
+        {% set comp_form = include('ui_patterns_test:test-form-component', {}) %}
+        {{ include('ui_patterns_test:test-component', {slot: comp_form}) }}",
+        "#context" => [],
+      ],
+      [
+        "#type" => "component",
+        '#component' => 'ui_patterns_test:test-component',
+        "#slots" => [
+          "slot" => [
+            "#type" => "component",
+            '#component' => 'ui_patterns_test:test-form-component',
+          ],
+        ],
+      ],
+    ];
+    foreach ($render_array_tests as $render_array_test) {
+      $this->assertExpectedOutput(
+        [
+          "rendered_value" => "<input ",
+          "assert" => "assertStringContainsString",
+        ],
+        $render_array_test
+      );
+      $this->assertExpectedOutput(
+        [
+          "rendered_value" => "<form ",
+          "assert" => "assertStringContainsString",
+        ],
+        $render_array_test
+      );
+    }
+  }
+
+}
diff --git a/tests/src/Kernel/PropTypeNormalization/EnumListPropTypeTest.php b/tests/src/Kernel/PropTypeNormalization/EnumListPropTypeTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..e986a262793dd0e5294e0f0793fc148770c63054
--- /dev/null
+++ b/tests/src/Kernel/PropTypeNormalization/EnumListPropTypeTest.php
@@ -0,0 +1,67 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Tests\ui_patterns\Kernel\PropTypeNormalization;
+
+use Drupal\Core\Template\Attribute;
+use Drupal\Tests\ui_patterns\Kernel\PropTypeNormalizationTestBase;
+use Drupal\ui_patterns\Plugin\UiPatterns\PropType\EnumListPropType;
+
+/**
+ * Test EnumListPropType normalization.
+ *
+ * @coversDefaultClass \Drupal\ui_patterns\Plugin\UiPatterns\PropType\EnumListPropType
+ * @group ui_patterns
+ */
+class EnumListPropTypeTest extends PropTypeNormalizationTestBase {
+
+  /**
+   * Test normalize static method.
+   *
+   * @dataProvider normalizationTests
+   */
+  public function testNormalization(mixed $value, mixed $expected) : void {
+    $normalized = EnumListPropType::normalize($value, $this->testComponentProps['enum_list_multiple']);
+    $this->assertEquals($normalized, $expected);
+  }
+
+  /**
+   * Test rendered component with prop.
+   *
+   * @dataProvider renderingTests
+   */
+  public function testRendering(mixed $value, mixed $rendered_value) : void {
+    $this->runRenderPropTest('enum_list_multiple', ["value" => $value, "rendered_value" => $rendered_value]);
+  }
+
+  /**
+   * Provides data for testNormalization.
+   */
+  public static function normalizationTests() : array {
+    return [
+      "null value" => [NULL, []],
+      "single item" => [[2], [2]],
+      "single item string" => [["2"], [2]],
+      "single string" => ["2", [2]],
+      "multiple items" => [[2, 2, 2], [2, 2, 2]],
+    ];
+  }
+
+  /**
+   * Provides data for testNormalization.
+   */
+  public static function renderingTests() : array {
+    return [
+      "null value" => [
+        NULL,
+        '<div class="ui-patterns-props-enum_list_multiple"></div>',
+      ],
+      "multiple items with bad values" => [
+        [2, "BAD", "2", 2, 444, "BAD", new Attribute(), [2]],
+        '<div class="ui-patterns-props-enum_list_multiple"><span>2</span><span>2</span><span>2</span></div>',
+      ],
+    ];
+  }
+
+}
diff --git a/tests/src/Kernel/PropTypeNormalization/EnumPropTypeTest.php b/tests/src/Kernel/PropTypeNormalization/EnumPropTypeTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..2fb87a7bd064f63a1ca3b75572ef7e765b5dd5d3
--- /dev/null
+++ b/tests/src/Kernel/PropTypeNormalization/EnumPropTypeTest.php
@@ -0,0 +1,66 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Tests\ui_patterns\Kernel\PropTypeNormalization;
+
+use Drupal\Tests\ui_patterns\Kernel\PropTypeNormalizationTestBase;
+use Drupal\ui_patterns\Plugin\UiPatterns\PropType\EnumPropType;
+
+/**
+ * Test EnumPropType normalization.
+ *
+ * @coversDefaultClass \Drupal\ui_patterns\Plugin\UiPatterns\PropType\EnumPropType
+ * @group ui_patterns
+ */
+class EnumPropTypeTest extends PropTypeNormalizationTestBase {
+
+  /**
+   * Test normalize static method.
+   *
+   * @dataProvider normalizationTests
+   */
+  public function testNormalization(mixed $value, mixed $expected) : void {
+    $normalized = EnumPropType::normalize($value, $this->testComponentProps['enum_integer']);
+    $this->assertEquals($normalized, $expected);
+  }
+
+  /**
+   * Test rendered component with prop.
+   *
+   * @dataProvider renderingTests
+   */
+  public function testRendering(mixed $value, mixed $rendered_value) : void {
+    $this->runRenderPropTest('enum_integer', ["value" => $value, "rendered_value" => $rendered_value]);
+  }
+
+  /**
+   * Provides data for testNormalization.
+   */
+  public static function normalizationTests() : array {
+    return [
+      "null value" => [NULL, NULL],
+      "integer" => [2, 2],
+      "string" => ["2", 2],
+      "string bad" => ["BAD VALUE", NULL],
+      "object" => [new \stdClass(), NULL],
+      "array" => [[2], 2],
+      "array assoc" => [["aa" => 2], 2],
+      "array assoc bad" => [["1" => NULL, "aa" => 2], 2],
+      "array markup" => [['#markup' => "2"], 2],
+    ];
+  }
+
+  /**
+   * Provides data for testNormalization.
+   */
+  public static function renderingTests() : array {
+    return [
+      "null value" => [
+        NULL,
+        '<div class="ui-patterns-props-enum_integer"></div>',
+      ],
+    ];
+  }
+
+}
diff --git a/tests/src/Kernel/PropTypeNormalization/EnumSetPropTypeTest.php b/tests/src/Kernel/PropTypeNormalization/EnumSetPropTypeTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..7a914b4e4b9bbf61297070e82a559d1526c466fc
--- /dev/null
+++ b/tests/src/Kernel/PropTypeNormalization/EnumSetPropTypeTest.php
@@ -0,0 +1,71 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Tests\ui_patterns\Kernel\PropTypeNormalization;
+
+use Drupal\Core\Template\Attribute;
+use Drupal\Tests\ui_patterns\Kernel\PropTypeNormalizationTestBase;
+use Drupal\ui_patterns\Plugin\UiPatterns\PropType\EnumSetPropType;
+
+/**
+ * Test EnumSetPropType normalization.
+ *
+ * @coversDefaultClass \Drupal\ui_patterns\Plugin\UiPatterns\PropType\EnumSetPropType
+ * @group ui_patterns
+ */
+class EnumSetPropTypeTest extends PropTypeNormalizationTestBase {
+
+  /**
+   * Test normalize static method.
+   *
+   * @dataProvider normalizationTests
+   */
+  public function testNormalization(mixed $value, mixed $expected) : void {
+    $normalized = EnumSetPropType::normalize($value, $this->testComponentProps['enum_set']);
+    $this->assertEquals($normalized, $expected);
+  }
+
+  /**
+   * Test rendered component with prop.
+   *
+   * @dataProvider renderingTests
+   */
+  public function testRendering(mixed $value, mixed $rendered_value) : void {
+    $this->runRenderPropTest('enum_set', ["value" => $value, "rendered_value" => $rendered_value]);
+  }
+
+  /**
+   * Provides data for testNormalization.
+   */
+  public static function normalizationTests() : array {
+    return [
+      "null value" => [NULL, []],
+      "single item" => [[2], [2]],
+      "single item string" => [["2"], [2]],
+      "single string" => ["2", [2]],
+      "multiple items" => [[2, 2, 2], [2]],
+      "multiple items with bad values" => [
+        [2, "BAD", 2, 2, 444, "BAD", new Attribute(), [2]],
+        [2],
+      ],
+    ];
+  }
+
+  /**
+   * Provides data for testNormalization.
+   */
+  public static function renderingTests() : array {
+    return [
+      "null value" => [
+        NULL,
+        '<div class="ui-patterns-props-enum_set"></div>',
+      ],
+      "multiple items with bad values" => [
+        [2, "BAD", "2", 2, 444, "BAD", new Attribute(), [2]],
+        '<div class="ui-patterns-props-enum_set"><span>2</span></div>',
+      ],
+    ];
+  }
+
+}
diff --git a/tests/src/Kernel/PropTypeNormalization/IdentifierPropTypeTest.php b/tests/src/Kernel/PropTypeNormalization/IdentifierPropTypeTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..28c99bd341975c30934e457d329d6bd2fa3b824a
--- /dev/null
+++ b/tests/src/Kernel/PropTypeNormalization/IdentifierPropTypeTest.php
@@ -0,0 +1,79 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Tests\ui_patterns\Kernel\PropTypeNormalization;
+
+use Drupal\Tests\ui_patterns\Kernel\PropTypeNormalizationTestBase;
+use Drupal\ui_patterns\Plugin\UiPatterns\PropType\IdentifierPropType;
+use Twig\Error\RuntimeError;
+
+/**
+ * Test IdentifierPropType normalization.
+ *
+ * @coversDefaultClass \Drupal\ui_patterns\Plugin\UiPatterns\PropType\IdentifierPropType
+ * @group ui_patterns
+ */
+class IdentifierPropTypeTest extends PropTypeNormalizationTestBase {
+
+  /**
+   * Test normalize static method.
+   *
+   * @dataProvider normalizationTests
+   */
+  public function testNormalization(mixed $value, mixed $expected) : void {
+    $normalized = IdentifierPropType::normalize($value, $this->testComponentProps['identifier']);
+    $this->assertEquals($normalized, $expected);
+  }
+
+  /**
+   * Test rendered component with prop.
+   *
+   * @dataProvider renderingTests
+   */
+  public function testRendering(mixed $value, mixed $rendered_value, ?string $exception_class = NULL) : void {
+    $this->runRenderPropTest('identifier',
+      ["value" => $value, "rendered_value" => $rendered_value, "exception_class" => $exception_class]);
+  }
+
+  /**
+   * Provides data for testNormalization.
+   */
+  public static function normalizationTests() : array {
+    return [
+      "null value" => [NULL, NULL],
+      "markup" => [['#markup' => "abc"], "abc"],
+      "string" => ["abc", "abc"],
+      "string with markup" => ["<b>abc</b>", "abc"],
+      "string with square brackets" => ["a[v][eee]", "a[v][eee]"],
+    ];
+  }
+
+  /**
+   * Provides data for testNormalization.
+   */
+  public static function renderingTests() : array {
+    return [
+      "null value" => [
+        NULL,
+        '<div class="ui-patterns-props-identifier"></div>',
+        RuntimeError::class,
+      ],
+      "empty value" => [
+        "",
+        '<div class="ui-patterns-props-identifier"></div>',
+        RuntimeError::class,
+      ],
+      "invalid value" => [
+        "2",
+        '<div class="ui-patterns-props-identifier"></div>',
+        RuntimeError::class,
+      ],
+      "correct value" => [
+        "correct-value🔥",
+        '<div class="ui-patterns-props-identifier">correct-value🔥</div>',
+      ],
+    ];
+  }
+
+}
diff --git a/tests/src/Unit/PropTypeNormalization/LinksPropTypeNormalizationTest.php b/tests/src/Kernel/PropTypeNormalization/LinksPropTypeTest.php
similarity index 63%
rename from tests/src/Unit/PropTypeNormalization/LinksPropTypeNormalizationTest.php
rename to tests/src/Kernel/PropTypeNormalization/LinksPropTypeTest.php
index b4028366a131fa6d311cba3c1e5002d23e7d8c1b..237e22497d83f4e2a4bbfe2a9e66b24e5aeb1dac 100644
--- a/tests/src/Unit/PropTypeNormalization/LinksPropTypeNormalizationTest.php
+++ b/tests/src/Kernel/PropTypeNormalization/LinksPropTypeTest.php
@@ -2,38 +2,110 @@
 
 declare(strict_types=1);
 
-namespace Drupal\Tests\ui_patterns\Unit;
+namespace Drupal\Tests\ui_patterns\Kernel\PropTypeNormalization;
 
+use Drupal\Core\Render\Markup;
 use Drupal\Core\Template\Attribute;
 use Drupal\Core\Url;
-use Drupal\Tests\UnitTestCase;
+use Drupal\Tests\ui_patterns\Kernel\PropTypeNormalizationTestBase;
 use Drupal\ui_patterns\Plugin\UiPatterns\PropType\LinksPropType;
 
 /**
- * @coversDefaultClass \Drupal\ui_patterns\Plugin\UiPatterns\PropType\LinksPropType
+ * Test LinksPropType normalization.
  *
+ * @coversDefaultClass \Drupal\ui_patterns\Plugin\UiPatterns\PropType\LinksPropType
  * @group ui_patterns
  */
-final class LinksPropTypeNormalizationTest extends UnitTestCase {
+class LinksPropTypeTest extends PropTypeNormalizationTestBase {
 
   /**
-   * Test the method ::normalize().
+   * Test normalize static method.
    *
-   * @dataProvider provideNormalizationData
+   * @dataProvider normalizationTests
    */
-  public function testNormalize(array $value, array $expected): void {
-    $normalized = LinksPropType::normalize($value);
-    self::assertEquals($normalized, $expected);
+  public function testNormalization(mixed $value, mixed $expected) : void {
+    $normalized = LinksPropType::normalize($value, $this->testComponentProps['links']);
+    $this->assertEquals($normalized, $expected);
   }
 
   /**
-   * Provide data for testNormalize.
+   * Test rendered component with prop.
+   *
+   * @dataProvider renderingTests
    */
-  public static function provideNormalizationData(): \Generator {
-    $data = [
-      "Empty value" => [
-        "value" => [],
-        "expected" => [],
+  public function testRendering(mixed $value, mixed $rendered_value) : void {
+    $this->runRenderPropTest('links', ["value" => $value, "rendered_value" => $rendered_value]);
+  }
+
+  /**
+   * Provides data for testNormalization.
+   */
+  public static function normalizationTests() : array {
+    return [
+      "null value" => [NULL, []],
+      "single item" => [
+        [[
+          "url" => "https://drupal.org",
+          "title" => "Drupal",
+        ],
+        ],
+        [[
+          "url" => "https://drupal.org",
+          "title" => "Drupal",
+        ],
+        ],
+      ],
+      "single item with integer title" => [
+        [[
+          "url" => "https://drupal.org",
+          "title" => 2,
+        ],
+        ],
+        [[
+          "url" => "https://drupal.org",
+          "title" => 2,
+        ],
+        ],
+      ],
+      "single item with markup title" => [
+        [[
+          "url" => Url::fromUri("https://drupal.org"),
+          "title" => Markup::create("Drupal"),
+        ],
+        ],
+        [[
+          "url" => "https://drupal.org",
+          "title" => "Drupal",
+        ],
+        ],
+      ],
+      "single item with nolink url" => [
+        [[
+          "url" => "<nolink>",
+          "title" => "Drupal",
+        ],
+        ],
+        [[
+          "url" => "<nolink>",
+          "title" => "Drupal",
+        ],
+        ],
+      ],
+      "single item with front url and svg" => [
+        [[
+          "url" => "<front>",
+          "title" => "<svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">
+            <path d=\"m8 3.293 4.712 4.712A4.5 4.5 0 0 0 8.758 15H3.5A1.5 1.5 0 0 1 2 13.5V9.293z\"></path>
+          </svg>",
+        ],
+        ],
+        [[
+          "url" => "<front>",
+          "title" => "<svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">
+            <path d=\"m8 3.293 4.712 4.712A4.5 4.5 0 0 0 8.758 15H3.5A1.5 1.5 0 0 1 2 13.5V9.293z\"></path>
+          </svg>",
+        ],
+        ],
       ],
       "Standardized structure, flat, only primitives" => self::standardizedFlatPrimitives(),
       // "Standardized structure, flat, with objects" =>
@@ -45,12 +117,60 @@ final class LinksPropTypeNormalizationTest extends UnitTestCase {
       // "Menu, as generated by the Menu module" => self::menu(),
       "Where link_attributes is already manually set" => self::linkAttributes(),
     ];
-    foreach ($data as $label => $test) {
-      yield $label => [
-        $test['value'],
-        $test['expected'],
-      ];
-    };
+  }
+
+  /**
+   * Provides data for testNormalization.
+   */
+  public static function renderingTests() : array {
+    return [
+      "null value" => [
+        NULL,
+        '<div class="ui-patterns-props-links"></div>',
+      ],
+      "single item" => [
+        [[
+          "url" => "https://drupal.org",
+          "title" => "Drupal",
+        ],
+        ],
+        '<a href="https://drupal.org">Drupal</a>',
+      ],
+      "single item with integer title" => [
+        [[
+          "url" => "https://drupal.org",
+          "title" => 2,
+        ],
+        ],
+        '<a href="https://drupal.org">2</a>',
+      ],
+      "single item with markup title" => [
+        [[
+          "url" => Url::fromUri("https://drupal.org"),
+          "title" => Markup::create("Drupal"),
+        ],
+        ],
+        '<a href="https://drupal.org">Drupal</a>',
+      ],
+      "single item with nolink url" => [
+        [[
+          "url" => "<nolink>",
+          "title" => "Drupal",
+        ],
+        ],
+        '<div class="ui-patterns-props-url"></div>',
+      ],
+      "single item with front url and svg" => [
+        [[
+          "url" => "<front>",
+          "title" => "<svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">
+            <path d=\"m8 3.293 4.712 4.712A4.5 4.5 0 0 0 8.758 15H3.5A1.5 1.5 0 0 1 2 13.5V9.293z\"></path>
+          </svg>",
+        ],
+        ],
+        "><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\"><path d=\"m8 3.293 4.712 4.712A4.5 4.5 0 0 0 8.758 15H3.5A1.5 1.5 0 0 1 2 13.5V9.293z\"></path></svg></a>",
+      ],
+    ];
   }
 
   /**
@@ -74,10 +194,7 @@ final class LinksPropTypeNormalizationTest extends UnitTestCase {
       ],
     ];
     $expected = $value;
-    return [
-      "value" => $value,
-      "expected" => $expected,
-    ];
+    return [$value, $expected];
   }
 
   /**
@@ -119,10 +236,7 @@ final class LinksPropTypeNormalizationTest extends UnitTestCase {
         "url" => "/foo/bar",
       ],
     ];
-    return [
-      "value" => $value,
-      "expected" => $expected,
-    ];
+    return [$value, $expected];
   }
 
   /**
@@ -152,10 +266,7 @@ final class LinksPropTypeNormalizationTest extends UnitTestCase {
         "url" => "/foo",
       ],
     ];
-    return [
-      "value" => $value,
-      "expected" => $expected,
-    ];
+    return [$value, $expected];
   }
 
   /**
@@ -189,10 +300,7 @@ final class LinksPropTypeNormalizationTest extends UnitTestCase {
         "url" => "/articles?page=2",
       ],
     ];
-    return [
-      "value" => $value,
-      "expected" => $expected,
-    ];
+    return [$value, $expected];
   }
 
   /**
@@ -240,10 +348,7 @@ final class LinksPropTypeNormalizationTest extends UnitTestCase {
         "url" => "?page=3",
       ],
     ];
-    return [
-      "value" => $value,
-      "expected" => $expected,
-    ];
+    return [$value, $expected];
   }
 
   /**
@@ -290,10 +395,7 @@ final class LinksPropTypeNormalizationTest extends UnitTestCase {
         "url" => "?page=3",
       ],
     ];
-    return [
-      "value" => $value,
-      "expected" => $expected,
-    ];
+    return [$value, $expected];
   }
 
   /**
@@ -335,10 +437,7 @@ final class LinksPropTypeNormalizationTest extends UnitTestCase {
         "url" => "/user/logout",
       ],
     ];
-    return [
-      "value" => $value,
-      "expected" => $expected,
-    ];
+    return [$value, $expected];
   }
 
   /**
@@ -372,10 +471,7 @@ final class LinksPropTypeNormalizationTest extends UnitTestCase {
         ],
       ],
     ];
-    return [
-      "value" => $value,
-      "expected" => $expected,
-    ];
+    return [$value, $expected];
   }
 
 }
diff --git a/tests/src/Kernel/PropTypeNormalization/ListPropTypeTest.php b/tests/src/Kernel/PropTypeNormalization/ListPropTypeTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..fbd443564f688e6a388d5dc04dc958dfc82c5ff6
--- /dev/null
+++ b/tests/src/Kernel/PropTypeNormalization/ListPropTypeTest.php
@@ -0,0 +1,58 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Tests\ui_patterns\Kernel\PropTypeNormalization;
+
+use Drupal\Tests\ui_patterns\Kernel\PropTypeNormalizationTestBase;
+use Drupal\ui_patterns\Plugin\UiPatterns\PropType\ListPropType;
+
+/**
+ * Test ListPropType normalization.
+ *
+ * @coversDefaultClass \Drupal\ui_patterns\Plugin\UiPatterns\PropType\ListPropType
+ * @group ui_patterns
+ */
+class ListPropTypeTest extends PropTypeNormalizationTestBase {
+
+  /**
+   * Test normalize static method.
+   *
+   * @dataProvider normalizationTests
+   */
+  public function testNormalization(mixed $value, mixed $expected) : void {
+    $normalized = ListPropType::normalize($value, $this->testComponentProps['list_mixed']);
+    $this->assertEquals($normalized, $expected);
+  }
+
+  /**
+   * Test rendered component with prop.
+   *
+   * @dataProvider renderingTests
+   */
+  public function testRendering(mixed $value, mixed $rendered_value) : void {
+    $this->runRenderPropTest('list_mixed', ["value" => $value, "rendered_value" => $rendered_value]);
+  }
+
+  /**
+   * Provides data for testNormalization.
+   */
+  public static function normalizationTests() : array {
+    return [
+      "null value" => [NULL, NULL],
+    ];
+  }
+
+  /**
+   * Provides data for testNormalization.
+   */
+  public static function renderingTests() : array {
+    return [
+      "null value" => [
+        NULL,
+        '<div class="ui-patterns-props-list_mixed"></div>',
+      ],
+    ];
+  }
+
+}
diff --git a/tests/src/Kernel/PropTypeNormalization/NumberPropTypeTest.php b/tests/src/Kernel/PropTypeNormalization/NumberPropTypeTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..d29d489e309b66413ea6de5b7b7dbb77baf7e7cb
--- /dev/null
+++ b/tests/src/Kernel/PropTypeNormalization/NumberPropTypeTest.php
@@ -0,0 +1,58 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Tests\ui_patterns\Kernel\PropTypeNormalization;
+
+use Drupal\Tests\ui_patterns\Kernel\PropTypeNormalizationTestBase;
+use Drupal\ui_patterns\Plugin\UiPatterns\PropType\NumberPropType;
+
+/**
+ * Test NumberPropType normalization.
+ *
+ * @coversDefaultClass \Drupal\ui_patterns\Plugin\UiPatterns\PropType\NumberPropType
+ * @group ui_patterns
+ */
+class NumberPropTypeTest extends PropTypeNormalizationTestBase {
+
+  /**
+   * Test normalize static method.
+   *
+   * @dataProvider normalizationTests
+   */
+  public function testNormalization(mixed $value, mixed $expected) : void {
+    $normalized = NumberPropType::normalize($value, $this->testComponentProps['number']);
+    $this->assertEquals($normalized, $expected);
+  }
+
+  /**
+   * Test rendered component with prop.
+   *
+   * @dataProvider renderingTests
+   */
+  public function testRendering(mixed $value, mixed $rendered_value) : void {
+    $this->runRenderPropTest('number', ["value" => $value, "rendered_value" => $rendered_value]);
+  }
+
+  /**
+   * Provides data for testNormalization.
+   */
+  public static function normalizationTests() : array {
+    return [
+      "null value" => [NULL, NULL],
+    ];
+  }
+
+  /**
+   * Provides data for testNormalization.
+   */
+  public static function renderingTests() : array {
+    return [
+      "null value" => [
+        NULL,
+        '<div class="ui-patterns-props-number"></div>',
+      ],
+    ];
+  }
+
+}
diff --git a/tests/src/Kernel/PropTypeNormalization/SlotPropTypeTest.php b/tests/src/Kernel/PropTypeNormalization/SlotPropTypeTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..e0afe86ee14beb5a999d444bdb3e66751f904b79
--- /dev/null
+++ b/tests/src/Kernel/PropTypeNormalization/SlotPropTypeTest.php
@@ -0,0 +1,74 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Tests\ui_patterns\Kernel\PropTypeNormalization;
+
+use Drupal\Core\Render\Markup;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\Tests\ui_patterns\Kernel\PropTypeNormalizationTestBase;
+use Drupal\ui_patterns\Plugin\UiPatterns\PropType\SlotPropType;
+
+/**
+ * Test SlotPropType normalization.
+ *
+ * @coversDefaultClass \Drupal\ui_patterns\Plugin\UiPatterns\PropType\SlotPropType
+ * @group ui_patterns
+ */
+class SlotPropTypeTest extends PropTypeNormalizationTestBase {
+
+  /**
+   * Test normalize static method.
+   *
+   * @dataProvider normalizationTests
+   */
+  public function testNormalization(mixed $value, mixed $expected) : void {
+    $normalized = SlotPropType::normalize($value);
+    $this->assertEquals($normalized, $expected);
+  }
+
+  /**
+   * Test rendered component with prop.
+   *
+   * @dataProvider renderingTests
+   */
+  public function testRendering(mixed $value, mixed $rendered_value) : void {
+    $this->runRenderPropTest('slot', ["value" => $value, "rendered_value" => $rendered_value]);
+  }
+
+  /**
+   * Provides data for testNormalization.
+   */
+  public static function normalizationTests() : array {
+    return [
+      "null value" => [NULL, NULL],
+    ];
+  }
+
+  /**
+   * Provides data for testNormalization.
+   */
+  public static function renderingTests() : array {
+    return [
+      "null value" => [
+        NULL,
+        '<div class="ui-patterns-slots-slot"></div>',
+      ],
+      "string value" => ["my slot", "my slot"],
+      "string in array" => [["my slot"], "my slot"],
+      "string as array value" => [["aa" => "my slot"], "my slot"],
+      "markup value" => [Markup::create("my slot"), "my slot"],
+      "markup in array" => [[Markup::create("my slot")], "my slot"],
+      "markup in array value" => [["uu" => Markup::create("my slot")], "my slot"],
+      "translatable" => [new TranslatableMarkup("my slot"), "my slot"],
+      "t function" => [t("my slot"), "my slot"],
+      "array value" => [["#markup" => "my slot"], "my slot"],
+      "inline template" => [["#type" => "inline_template", "#template" => "my slot"], "my slot"],
+      "array value with weight" => [
+        ["b" => ["#weight" => 2, "#markup" => "slot"], "a" => ["#weight" => 1, "#markup" => "my "]],
+        "my slot",
+      ],
+    ];
+  }
+
+}
diff --git a/tests/src/Kernel/PropTypeNormalization/StringPropTypeTest.php b/tests/src/Kernel/PropTypeNormalization/StringPropTypeTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..94418d1b9ac1f8ebff485d7933364c497481fbd8
--- /dev/null
+++ b/tests/src/Kernel/PropTypeNormalization/StringPropTypeTest.php
@@ -0,0 +1,79 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Tests\ui_patterns\Kernel\PropTypeNormalization;
+
+use Drupal\Core\Link;
+use Drupal\Core\Render\Markup;
+use Drupal\Core\Url;
+use Drupal\Tests\ui_patterns\Kernel\PropTypeNormalizationTestBase;
+use Drupal\ui_patterns\Plugin\UiPatterns\PropType\StringPropType;
+
+/**
+ * Test StringPropType normalization.
+ *
+ * @coversDefaultClass \Drupal\ui_patterns\Plugin\UiPatterns\PropType\StringPropType
+ * @group ui_patterns
+ */
+class StringPropTypeTest extends PropTypeNormalizationTestBase {
+
+  /**
+   * Test normalize static method.
+   *
+   * @dataProvider normalizationTests
+   */
+  public function testNormalization(mixed $value, mixed $expected) : void {
+    $normalized = StringPropType::normalize($value, $this->testComponentProps['string']);
+    $this->assertEquals($normalized, $expected);
+  }
+
+  /**
+   * Test rendered component with prop.
+   *
+   * @dataProvider renderingTests
+   */
+  public function testRendering(mixed $value, mixed $rendered_value) : void {
+    $this->runRenderPropTest('string', ["value" => $value, "rendered_value" => $rendered_value]);
+  }
+
+  /**
+   * Provides data for testNormalization.
+   */
+  public static function normalizationTests() : array {
+    return [
+      "null value" => [NULL, ""],
+      "string" => ["my string", "my string"],
+      "string empty" => ["", ""],
+      "int" => [2, "2"],
+      "render array" => [["#markup" => "my string"], "my string"],
+      "string with markup" => [Markup::create("my string"), "my string"],
+      "string with url" => [Url::fromUri("https://drupal.org"), "https://drupal.org"],
+    ];
+  }
+
+  /**
+   * Provides data for testNormalization.
+   */
+  public static function renderingTests() : array {
+    return [
+      "null value" => [
+        NULL,
+        '<div class="ui-patterns-props-string"></div>',
+      ],
+      "string with link" => [
+        Link::fromTextAndUrl(Markup::create("test"), Url::fromUri("https://drupal.org")),
+        '<div class="ui-patterns-props-string">&lt;a href=&quot;https://drupal.org&quot;&gt;test&lt;/a&gt;</div>',
+      ],
+      "html string" => [
+        "<b>test</b>",
+        '<div class="ui-patterns-props-string">&lt;b&gt;test&lt;/b&gt;</div>',
+      ],
+      "html markup object" => [
+        Markup::create("<b>test</b>"),
+        '<div class="ui-patterns-props-string">&lt;b&gt;test&lt;/b&gt;</div>',
+      ],
+    ];
+  }
+
+}
diff --git a/tests/src/Kernel/PropTypeNormalization/UrlPropTypeTest.php b/tests/src/Kernel/PropTypeNormalization/UrlPropTypeTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..666788dbbbd440fa4927e48b025b33dbfbc2beb3
--- /dev/null
+++ b/tests/src/Kernel/PropTypeNormalization/UrlPropTypeTest.php
@@ -0,0 +1,151 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Tests\ui_patterns\Kernel\PropTypeNormalization;
+
+use Drupal\Core\Url;
+use Drupal\Tests\ui_patterns\Kernel\PropTypeNormalizationTestBase;
+use Drupal\ui_patterns\Plugin\UiPatterns\PropType\UrlPropType;
+
+/**
+ * Test UrlPropType normalization.
+ *
+ * @coversDefaultClass \Drupal\ui_patterns\Plugin\UiPatterns\PropType\UrlPropType
+ * @group ui_patterns
+ */
+class UrlPropTypeTest extends PropTypeNormalizationTestBase {
+
+  /**
+   * Test normalize static method.
+   *
+   * @dataProvider normalizationTests
+   */
+  public function testNormalization(mixed $value, mixed $expected) : void {
+    $normalized = UrlPropType::normalize($value, $this->testComponentProps['url']);
+    $this->assertEquals($normalized, $expected);
+  }
+
+  /**
+   * Test normalize static method manually.
+   *
+   * We need the container to be initialized to use the Url::fromUri method.
+   * So it's not possible to use a dataProvider for this test.
+   */
+  public function testNormalizationManualData() : void {
+    $tests = [
+      "uri" => [Url::fromUri("https://drupal.org"), "https://drupal.org"],
+      "uri internal" => [Url::fromUri("internal:/user"), "/user"],
+      "uri internal front" => [Url::fromUri("internal:/"), "/"],
+    ];
+    foreach ($tests as $test) {
+      $value = $test[0];
+      $expected = $test[1];
+      $normalized = UrlPropType::normalize($value, $this->testComponentProps['url']);
+      $this->assertEquals($normalized, $expected);
+    }
+  }
+
+  /**
+   * Test rendered component with prop.
+   *
+   * @dataProvider renderingTests
+   */
+  public function testRendering(mixed $value, mixed $rendered_value) : void {
+    $this->runRenderPropTest('url', ["value" => $value, "rendered_value" => $rendered_value]);
+  }
+
+  /**
+   * Provides data for testNormalization.
+   */
+  public static function normalizationTests() : array {
+    return [
+      "null value" => [NULL, ""],
+      "string" => ["https://drupal.org", "https://drupal.org"],
+      "string empty" => ["", ""],
+      "uri internal classic" => ["/user", "/user"],
+      "uri from shortcut" => ["<front>", "/"],
+    ] + self::notAnUrl() + self::validUrl();
+  }
+
+  /**
+   * Provides data for testNormalization.
+   */
+  public static function renderingTests() : array {
+    return [
+      "null value" => [
+        NULL,
+        '<div class="ui-patterns-props-url"></div>',
+      ],
+    ];
+  }
+
+  /**
+   * Not an URL.
+   */
+  protected static function notAnUrl() {
+    return [
+      "Empty string" => [
+        "",
+        "",
+      ],
+      "Boolean" => [
+        TRUE,
+        "",
+      ],
+      "Integer" => [
+        3,
+        "",
+      ],
+      "Array" => [
+      [],
+        "",
+      ],
+    ];
+  }
+
+  /**
+   * Valid URL.
+   */
+  protected static function validUrl() {
+    return [
+      "HTTP URL (domain only)" => [
+        "http://www.foo.com",
+        "http://www.foo.com",
+      ],
+      "HTTP URL" => [
+        "http://www.foo.com/path/to",
+        "http://www.foo.com/path/to",
+      ],
+      "HTTPS URL" => [
+        "https://www.foo.com/path/to",
+        "https://www.foo.com/path/to",
+      ],
+      "HTTP(S) URL" => [
+        "//www.foo.com/path/to",
+        "//www.foo.com/path/to",
+      ],
+      "SFTP URL" => [
+        "sftp://www.foo.com/path/to",
+        "sftp://www.foo.com/path/to",
+      ],
+      "Full path" => [
+        "/path/to",
+        "/path/to",
+      ],
+      "Relative path" => [
+        "path/to",
+        "path/to",
+      ],
+      "HTTPS IRI" => [
+        "https://en.tranché.org/bien-sûr",
+        "https://en.tranché.org/bien-sûr",
+      ],
+      "HTTPS IRI percent encoded" => [
+        "https://en.wiktionary.org/wiki/%E1%BF%AC%CF%8C%CE%B4%CE%BF%CF%82",
+        "https://en.wiktionary.org/wiki/%E1%BF%AC%CF%8C%CE%B4%CE%BF%CF%82",
+      ],
+    ];
+  }
+
+}
diff --git a/tests/src/Kernel/PropTypeNormalization/VariantPropTypeTest.php b/tests/src/Kernel/PropTypeNormalization/VariantPropTypeTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..f0624ba471a429dca77ac4c676780ae00a80148c
--- /dev/null
+++ b/tests/src/Kernel/PropTypeNormalization/VariantPropTypeTest.php
@@ -0,0 +1,74 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Tests\ui_patterns\Kernel\PropTypeNormalization;
+
+use Drupal\Tests\ui_patterns\Kernel\PropTypeNormalizationTestBase;
+use Drupal\ui_patterns\Plugin\UiPatterns\PropType\VariantPropType;
+
+/**
+ * Test VariantPropType normalization.
+ *
+ * @coversDefaultClass \Drupal\ui_patterns\Plugin\UiPatterns\PropType\VariantPropType
+ * @group ui_patterns
+ */
+class VariantPropTypeTest extends PropTypeNormalizationTestBase {
+
+  /**
+   * Test normalize static method.
+   *
+   * @dataProvider normalizationTests
+   */
+  public function testNormalization(mixed $value, mixed $expected) : void {
+    $normalized = VariantPropType::normalize($value, $this->testComponentProps['variant']);
+    $this->assertEquals($normalized, $expected);
+  }
+
+  /**
+   * Test rendered component with prop.
+   *
+   * @dataProvider renderingTests
+   */
+  public function testRendering(mixed $value, mixed $rendered_value) : void {
+    $this->runRenderPropTest('variant', ["value" => $value, "rendered_value" => $rendered_value]);
+  }
+
+  /**
+   * Provides data for testNormalization.
+   */
+  public static function normalizationTests() : array {
+    return [
+      "null value" => [NULL, ""],
+      "empty string" => ["", ""],
+      "default value" => ["default", "default"],
+      "other value" => ["other", "other"],
+      "bad value" => ["BAD", ""],
+      "integer value" => [2, ""],
+      "array value" => [[], ""],
+      "object value" => [new \stdClass(), ""],
+      "render array" => [["#markup" => "other"], "other"],
+    ];
+  }
+
+  /**
+   * Provides data for testNormalization.
+   */
+  public static function renderingTests() : array {
+    return [
+      "null value" => [
+        NULL,
+        ' class="ui-patterns-test-component ui-patterns-test-component-variant-"',
+      ],
+      "empty value" => [
+        "",
+        ' class="ui-patterns-test-component ui-patterns-test-component-variant-"',
+      ],
+      "other" => [
+        "other",
+        ' class="ui-patterns-test-component ui-patterns-test-component-variant-other"',
+      ],
+    ];
+  }
+
+}
diff --git a/tests/src/Kernel/PropTypeNormalizationTestBase.php b/tests/src/Kernel/PropTypeNormalizationTestBase.php
new file mode 100644
index 0000000000000000000000000000000000000000..33b62d1b6fdadf06955a955b997dbade7933387a
--- /dev/null
+++ b/tests/src/Kernel/PropTypeNormalizationTestBase.php
@@ -0,0 +1,85 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Tests\ui_patterns\Kernel;
+
+use Drupal\KernelTests\KernelTestBase;
+use Drupal\Tests\ui_patterns\Traits\TestDataTrait;
+
+/**
+ * Base class to test prop type normalization.
+ *
+ * @group ui_patterns
+ */
+abstract class PropTypeNormalizationTestBase extends KernelTestBase {
+
+  use TestDataTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = [
+    'system',
+    'user',
+    'text',
+    'field',
+    'node',
+    'ui_patterns',
+    'ui_patterns_test',
+    'datetime',
+    'filter',
+  ];
+
+  /**
+   * @var array|null
+   *   The test component props.
+   */
+  protected ?array $testComponentProps;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+    $this->installSchema('node', 'node_access');
+    $this->installEntitySchema('node');
+    $this->installEntitySchema('user');
+    $this->installConfig(['system', 'filter']);
+    $component = \Drupal::service('plugin.manager.sdc')->find('ui_patterns_test:test-component');
+    $this->testComponentProps = $component->metadata->schema['properties'] ?? [];
+  }
+
+  /**
+   * Run tests with a given prop.
+   *
+   * @param string $prop
+   *   The prop to test.
+   * @param array $tested_value
+   *   The tested value.
+   */
+  protected function runRenderPropTest(string $prop, array $tested_value) : void {
+    $expectedOutput = [
+      "rendered_value" => $tested_value["rendered_value"],
+      "assert" => $tested_value["assert"] ?? "assertStringContainsString",
+    ];
+    $exception_class = $tested_value["exception_class"] ?? NULL;
+    if (!empty($exception_class)) {
+      $this->expectException($exception_class);
+    }
+    $this->assertExpectedOutput($expectedOutput, [
+      "#type" => "inline_template",
+      "#template" => sprintf("{{ include('ui_patterns_test:test-component', {%s: injected}) }}", $prop),
+      "#context" => ["injected" => $tested_value["value"]],
+    ]);
+    if (!empty($exception_class)) {
+      $this->expectException($exception_class);
+    }
+    $this->assertExpectedOutput($expectedOutput, [
+      "#type" => "component",
+      '#component' => 'ui_patterns_test:test-component',
+      "#props" => [$prop => $tested_value["value"]],
+    ]);
+  }
+
+}
diff --git a/tests/src/Kernel/PropTypesNormalizationTest.php b/tests/src/Kernel/PropTypesNormalizationTest.php
deleted file mode 100644
index 2b79bb237eb247b5f524ad71a80115989aa9f806..0000000000000000000000000000000000000000
--- a/tests/src/Kernel/PropTypesNormalizationTest.php
+++ /dev/null
@@ -1,366 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace Drupal\Tests\ui_patterns\Kernel;
-
-use Drupal\Core\Link;
-use Drupal\Core\Render\Markup;
-use Drupal\Core\StringTranslation\TranslatableMarkup;
-use Drupal\Core\Template\Attribute;
-use Drupal\Core\Url;
-use Drupal\KernelTests\KernelTestBase;
-use Drupal\Tests\ui_patterns\Traits\TestDataTrait;
-
-/**
- * Test prop types normalization.
- *
- * @group ui_patterns
- */
-class PropTypesNormalizationTest extends KernelTestBase {
-
-  use TestDataTrait;
-
-  /**
-   * {@inheritdoc}
-   */
-  protected static $modules = [
-    'system',
-    'user',
-    'text',
-    'field',
-    'node',
-    'ui_patterns',
-    'ui_patterns_test',
-    'datetime',
-    'filter',
-  ];
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function setUp(): void {
-    parent::setUp();
-    $this->installSchema('node', 'node_access');
-    $this->installEntitySchema('node');
-    $this->installEntitySchema('user');
-    $this->installConfig(['system', 'filter']);
-  }
-
-  /**
-   * Test prop normalization.
-   *
-   * @dataProvider propTypeDataProvider
-   */
-  public function testPropTypeNormalization(string $prop, array $tested_values) : void {
-    $render_array_test = [
-      "#type" => "inline_template",
-      "#template" => sprintf("{{ include('ui_patterns_test:test-component', {%s: injected}) }}", $prop),
-      "#context" => ["injected" => ""],
-    ];
-    foreach ($tested_values as $tested_value) {
-      $this->assertExpectedOutput(
-        [
-          "rendered_value" => $tested_value["rendered_value"],
-          "assert" => $tested_value["assert"] ?? "assertStringContainsString",
-        ],
-        array_merge($render_array_test, ["#context" => $tested_value["#context"]])
-      );
-    }
-    // With render array.
-    $render_array_test = [
-      "#type" => "component",
-      '#component' => 'ui_patterns_test:test-component',
-      "#props" => [$prop => []],
-    ];
-    foreach ($tested_values as $tested_value) {
-      $render_array_test["#props"][$prop] = $tested_value["#context"]["injected"];
-
-      $this->assertExpectedOutput(
-          [
-            "rendered_value" => $tested_value["rendered_value"],
-            "assert" => $tested_value["assert"] ?? "assertStringContainsString",
-          ],
-          $render_array_test
-        );
-    }
-  }
-
-  /**
-   * Provides prop type data.
-   */
-  public static function propTypeDataProvider() : array {
-    $returned = [];
-    // Url.
-    $returned["url"] = [
-      "url",
-      [
-        ["#context" => ["injected" => "https://drupal.org"], "rendered_value" => "https://drupal.org"],
-        ["#context" => ["injected" => Url::fromUri("https://drupal.org")], "rendered_value" => "https://drupal.org"],
-        ["#context" => ["injected" => "/user"], "rendered_value" => "/user"],
-        ["#context" => ["injected" => "internal:/user"], "rendered_value" => "/user"],
-      ],
-    ];
-    // Links.
-    $returned["links"] = [
-      "links",
-      [
-        [
-          "#context" => [
-            "injected" => [
-              [
-                "url" => "https://drupal.org",
-                "title" => "Drupal",
-              ],
-            ],
-          ],
-          "rendered_value" => '<a href="https://drupal.org">Drupal</a>',
-        ],
-        [
-          "#context" => [
-            "injected" => [
-              [
-                "url" => "https://drupal.org",
-                "title" => 2,
-              ],
-            ],
-          ],
-          "rendered_value" => '<a href="https://drupal.org">2</a>',
-        ],
-        [
-          "#context" => [
-            "injected" => [
-               [
-                 "url" => Url::fromUri("https://drupal.org"),
-                 "title" => Markup::create("Drupal"),
-               ],
-            ],
-          ],
-          "rendered_value" => '<a href="https://drupal.org">Drupal</a>',
-        ],
-        [
-          "#context" => [
-            "injected" => [
-               [
-                 "url" => "<nolink>",
-                 "title" => "Drupal",
-               ],
-            ],
-          ],
-          "rendered_value" => '<div class="ui-patterns-props-url"></div>',
-        ],
-        [
-          "#context" => [
-            "injected" => [
-               [
-                 "url" => "<front>",
-                 "title" => "<svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">
-                           <path d=\"m8 3.293 4.712 4.712A4.5 4.5 0 0 0 8.758 15H3.5A1.5 1.5 0 0 1 2 13.5V9.293z\"></path>
-                         </svg>",
-               ],
-            ],
-          ],
-          "rendered_value" => "><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\"><path d=\"m8 3.293 4.712 4.712A4.5 4.5 0 0 0 8.758 15H3.5A1.5 1.5 0 0 1 2 13.5V9.293z\"></path></svg></a>",
-        ],
-      ],
-    ];
-    // Enum integer.
-    $returned["enum_integer"] = [
-      "enum_integer",
-      [
-        ["#context" => ["injected" => 2], "rendered_value" => '<div class="ui-patterns-props-enum_integer">2</div>'],
-        ["#context" => ["injected" => "2"], "rendered_value" => '<div class="ui-patterns-props-enum_integer">2</div>'],
-        [
-          "#context" => ["injected" => "BAD VALUE"],
-          "rendered_value" => '<div class="ui-patterns-props-enum_integer"></div>',
-        ],
-        [
-          "#context" => ["injected" => new \stdClass()],
-          "rendered_value" => '<div class="ui-patterns-props-enum_integer"></div>',
-        ],
-        [
-          "#context" => ["injected" => [2]],
-          "rendered_value" => '<div class="ui-patterns-props-enum_integer">2</div>',
-        ],
-        [
-          "#context" => ["injected" => ["aa" => 2]],
-          "rendered_value" => '<div class="ui-patterns-props-enum_integer">2</div>',
-        ],
-        [
-          "#context" => ["injected" => ["1" => NULL, "aa" => 2]],
-          "rendered_value" => '<div class="ui-patterns-props-enum_integer">2</div>',
-        ],
-        [
-          "#context" => ["injected" => ['#markup' => "2"]],
-          "rendered_value" => '<div class="ui-patterns-props-enum_integer">2</div>',
-        ],
-      ],
-    ];
-    // enum_list_multiple.
-    $returned["enum_list_multiple"] = [
-      "enum_list_multiple",
-      [
-        [
-          "#context" => ["injected" => [2]],
-          "rendered_value" => '<div class="ui-patterns-props-enum_list_multiple"><span>2</span></div>',
-        ],
-        [
-          "#context" => ["injected" => ["2"]],
-          "rendered_value" => '<div class="ui-patterns-props-enum_list_multiple"><span>2</span></div>',
-        ],
-        [
-          "#context" => ["injected" => "2"],
-          "rendered_value" => '<div class="ui-patterns-props-enum_list_multiple"><span>2</span></div>',
-        ],
-        [
-          "#context" => ["injected" => [2, 2, 2]],
-          "rendered_value" =>
-          '<div class="ui-patterns-props-enum_list_multiple"><span>2</span><span>2</span><span>2</span></div>',
-        ],
-        [
-          "#context" => ["injected" => [2, "BAD", 2, 2, 444, "BAD", new Attribute(), [2]]],
-          "rendered_value" =>
-          '<div class="ui-patterns-props-enum_list_multiple"><span>2</span><span>2</span><span>2</span></div>',
-        ],
-      ],
-    ];
-    // enum_set.
-    $returned["enum_set"] = [
-      "enum_set",
-      [
-        [
-          "#context" => ["injected" => [2]],
-          "rendered_value" => '<div class="ui-patterns-props-enum_set"><span>2</span></div>',
-        ],
-        [
-          "#context" => ["injected" => ["2"]],
-          "rendered_value" => '<div class="ui-patterns-props-enum_set"><span>2</span></div>',
-        ],
-        [
-          "#context" => ["injected" => "2"],
-          "rendered_value" => '<div class="ui-patterns-props-enum_set"><span>2</span></div>',
-        ],
-        [
-          "#context" => ["injected" => [2, 2, 2]],
-          "rendered_value" =>
-          '<div class="ui-patterns-props-enum_set"><span>2</span></div>',
-        ],
-        [
-          "#context" => ["injected" => [2, "BAD", 2, 2, 444, "BAD", new Attribute(), [2]]],
-          "rendered_value" =>
-          '<div class="ui-patterns-props-enum_set"><span>2</span></div>',
-        ],
-      ],
-    ];
-    // Strings.
-    $returned["string"] = [
-      "string",
-      [
-        ["#context" => ["injected" => "my string"], "rendered_value" => "my string"],
-        [
-          "#context" => ["injected" => ['#markup' => "my string2"]],
-          "rendered_value" => "my string2",
-        ],
-        [
-          "#context" => ["injected" => Markup::create("my string3")],
-          "rendered_value" => "my string3",
-        ],
-        [
-          "#context" => ["injected" => Url::fromUri("https://drupal.org")],
-          "rendered_value" => "https://drupal.org",
-        ],
-        [
-          "#context" => [
-            "injected" =>
-            Link::fromTextAndUrl(Markup::create("test"), Url::fromUri("https://drupal.org")),
-          ],
-          "rendered_value" =>
-          '<div class="ui-patterns-props-string">&lt;a href=&quot;https://drupal.org&quot;&gt;test&lt;/a&gt;</div>',
-        ],
-        ["#context" => ["injected" => ""], "rendered_value" => ""],
-        ["#context" => ["injected" => NULL], "rendered_value" => ""],
-        ["#context" => ["injected" => 2], "rendered_value" => "2"],
-      ],
-    ];
-    // Variants.
-    $returned["variant"] = [
-      "variant",
-      [
-        ["#context" => ["injected" => "default"], "rendered_value" => "ui-patterns-test-component-variant-default"],
-        ["#context" => ["injected" => ""], "rendered_value" => "ui-patterns-test-component-variant-"],
-        ["#context" => ["injected" => "other"], "rendered_value" => "ui-patterns-test-component-variant-other"],
-        ["#context" => ["injected" => "BAD"], "rendered_value" => "ui-patterns-test-component-variant-"],
-        ["#context" => ["injected" => 2], "rendered_value" => "ui-patterns-test-component-variant-"],
-        ["#context" => ["injected" => []], "rendered_value" => "ui-patterns-test-component-variant-"],
-        ["#context" => ["injected" => new \stdClass()], "rendered_value" => "ui-patterns-test-component-variant-"],
-      ],
-    ];
-    // Test slot values.
-    $tested_slot_values = [
-      Markup::create("my slot"),
-      new TranslatableMarkup("my slot"),
-      ["my slot"],
-      "my slot",
-      [Markup::create("my slot")],
-      ["uu" => Markup::create("my slot")],
-      ["#markup" => "my slot"],
-      ["a" => ["#markup" => "my "], "b" => ["#markup" => "slot"]],
-      ["a" => ["#markup" => "my "], "b" => ["slot"]],
-      ["b" => ["#weight" => 2, "#markup" => "slot"], "a" => ["#weight" => 1, "#markup" => "my "]],
-      ["aa" => "my slot"],
-      t("my slot"),
-      ["#type" => "inline_template", "#template" => "my slot"],
-    ];
-    $returned["slot"] = [
-      "slot",
-      [],
-    ];
-    foreach ($tested_slot_values as $tested_value) {
-      $returned["slot"][1][] = ["#context" => ["injected" => $tested_value], "rendered_value" => "my slot"];
-    }
-    return $returned;
-  }
-
-  /**
-   * Test slot normalization.
-   */
-  public function testNestedComponentWithForm() : void {
-    // Test nested component with form.
-    $render_array_tests = [
-      [
-        "#type" => "inline_template",
-        "#template" => "
-        {% set comp_form = include('ui_patterns_test:test-form-component', {}) %}
-        {{ include('ui_patterns_test:test-component', {slot: comp_form}) }}",
-        "#context" => [],
-      ],
-      [
-        "#type" => "component",
-        '#component' => 'ui_patterns_test:test-component',
-        "#slots" => [
-          "slot" => [
-            "#type" => "component",
-            '#component' => 'ui_patterns_test:test-form-component',
-          ],
-        ],
-      ],
-    ];
-    foreach ($render_array_tests as $render_array_test) {
-      $this->assertExpectedOutput(
-        [
-          "rendered_value" => "<input ",
-          "assert" => "assertStringContainsString",
-        ],
-        $render_array_test
-      );
-      $this->assertExpectedOutput(
-        [
-          "rendered_value" => "<form ",
-          "assert" => "assertStringContainsString",
-        ],
-        $render_array_test
-      );
-    }
-  }
-
-}
diff --git a/tests/src/Traits/TestDataTrait.php b/tests/src/Traits/TestDataTrait.php
index 78b6f9c0d8c6f641beb953e7a0af7f749b336381..a2baf6aebf1a2226d8a9ff7f6102681c0b739308 100644
--- a/tests/src/Traits/TestDataTrait.php
+++ b/tests/src/Traits/TestDataTrait.php
@@ -104,14 +104,7 @@ trait TestDataTrait {
     }
     if (isset($expected_result['rendered_value']) || isset($expected_result['rendered_value_plain'])) {
       // $rendered = \Drupal::service('renderer')->renderRoot($result);
-      $rendered = NULL;
-      try {
-        $rendered = is_array($result) ? \Drupal::service('renderer')->renderRoot($result) : $result;
-      }
-      catch (\Exception $e) {
-        // @phpstan-ignore-next-line
-        $this->assertTrue(FALSE, sprintf("%s: ERROR, failed to render result: %s \n (%s)", $message, $e->getMessage(), print_r($result, TRUE)));
-      }
+      $rendered = is_array($result) ? \Drupal::service('renderer')->renderInIsolation($result) : $result;
       if ($rendered instanceof MarkupInterface) {
         $rendered = "" . $rendered;
       }
diff --git a/tests/src/Unit/PropTypeNormalization/UrlPropTypeNormalizationTest.php b/tests/src/Unit/PropTypeNormalization/UrlPropTypeNormalizationTest.php
deleted file mode 100644
index 0eee9fa1f2c64618a6acf12665f4aef16237ffc0..0000000000000000000000000000000000000000
--- a/tests/src/Unit/PropTypeNormalization/UrlPropTypeNormalizationTest.php
+++ /dev/null
@@ -1,108 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace Drupal\Tests\ui_patterns\Unit;
-
-use Drupal\Tests\UnitTestCase;
-use Drupal\ui_patterns\Plugin\UiPatterns\PropType\UrlPropType;
-
-/**
- * @coversDefaultClass \Drupal\ui_patterns\Plugin\UiPatterns\PropType\UrlPropType
- *
- * @group ui_patterns
- */
-final class UrlPropTypeNormalizationTest extends UnitTestCase {
-
-  /**
-   * Test the method ::normalize().
-   *
-   * @dataProvider provideNormalizationData
-   */
-  public function testNormalize(mixed $value, string $expected): void {
-    $normalized = UrlPropType::normalize($value);
-    self::assertEquals($normalized, $expected);
-  }
-
-  /**
-   * Provide data for testNormalize.
-   */
-  public static function provideNormalizationData(): \Generator {
-    $data = self::notAnUrl() + self::validUrl();
-    foreach ($data as $label => $test) {
-      yield $label => [
-        $test['value'],
-        $test['expected'],
-      ];
-    };
-  }
-
-  /**
-   * Not an URL.
-   */
-  protected static function notAnUrl() {
-    return [
-      "Empty string" => [
-        "value" => "",
-        "expected" => "",
-      ],
-      "Boolean" => [
-        "value" => TRUE,
-        "expected" => "",
-      ],
-      "Integer" => [
-        "value" => 3,
-        "expected" => "",
-      ],
-      "Array" => [
-        "value" => [],
-        "expected" => "",
-      ],
-    ];
-  }
-
-  /**
-   * Valid URL.
-   */
-  protected static function validUrl() {
-    return [
-      "HTTP URL (domain only)" => [
-        "value" => "http://www.foo.com",
-        "expected" => "http://www.foo.com",
-      ],
-      "HTTP URL" => [
-        "value" => "http://www.foo.com/path/to",
-        "expected" => "http://www.foo.com/path/to",
-      ],
-      "HTTPS URL" => [
-        "value" => "https://www.foo.com/path/to",
-        "expected" => "https://www.foo.com/path/to",
-      ],
-      "HTTP(S) URL" => [
-        "value" => "//www.foo.com/path/to",
-        "expected" => "//www.foo.com/path/to",
-      ],
-      "SFTP URL" => [
-        "value" => "sftp://www.foo.com/path/to",
-        "expected" => "sftp://www.foo.com/path/to",
-      ],
-      "Full path" => [
-        "value" => "/path/to",
-        "expected" => "/path/to",
-      ],
-      "Relative path" => [
-        "value" => "path/to",
-        "expected" => "path/to",
-      ],
-      "HTTPS IRI" => [
-        "value" => "https://en.tranché.org/bien-sûr",
-        "expected" => "https://en.tranché.org/bien-sûr",
-      ],
-      "HTTPS IRI percent encoded" => [
-        "value" => "https://en.wiktionary.org/wiki/%E1%BF%AC%CF%8C%CE%B4%CE%BF%CF%82",
-        "expected" => "https://en.wiktionary.org/wiki/%E1%BF%AC%CF%8C%CE%B4%CE%BF%CF%82",
-      ],
-    ];
-  }
-
-}