From a3f46bbdd8493d76a0aa9547b6ceaa782922c298 Mon Sep 17 00:00:00 2001
From: benellefimostfa <benellefimostfa@gmail.com>
Date: Sun, 12 Jan 2025 14:19:31 +0100
Subject: [PATCH 1/6] Add test coverage for CustomElement Class

---
 tests/src/Kernel/CustomElementTest.php | 300 +++++++++++++++++++++++++
 1 file changed, 300 insertions(+)
 create mode 100644 tests/src/Kernel/CustomElementTest.php

diff --git a/tests/src/Kernel/CustomElementTest.php b/tests/src/Kernel/CustomElementTest.php
new file mode 100644
index 0000000..abb65be
--- /dev/null
+++ b/tests/src/Kernel/CustomElementTest.php
@@ -0,0 +1,300 @@
+<?php
+
+namespace Drupal\Tests\custom_elements\Kernel;
+
+use Drupal\Core\Render\RendererInterface;
+use Drupal\KernelTests\KernelTestBase;
+use Drupal\custom_elements\CustomElement;
+
+/**
+ * This class tests the functionality of the CustomElement class.
+ *
+ * @coversDefaultClass \Drupal\custom_elements\CustomElement
+ * @group custom_elements
+ */
+class CustomElementTest extends KernelTestBase {
+
+  /**
+   * Modules to install for these tests.
+   *
+   * @var array
+   */
+  protected static $modules = ['custom_elements'];
+
+  /**
+   * The CustomElement instance for testing.
+   *
+   * @var \Drupal\custom_elements\CustomElement
+   */
+  protected $customElement;
+
+
+  /**
+   * The CustomElementNormalizer instance for testing.
+   *
+   * @var \Symfony\Component\Serializer\Normalizer\NormalizerInterface
+   */
+  protected $normalizer;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+    $this->customElement = CustomElement::create('teaser-listing');
+    $this->normalizer = $this->container->get('custom_elements.normalizer');
+  }
+
+  /**
+   * Helper method to render the custom element to markup.
+   *
+   * @param \Drupal\custom_elements\CustomElement $customElement
+   *   The CustomElement to render.
+   *
+   * @return string
+   *   The rendered markup.
+   */
+  protected function renderCustomElement(CustomElement $customElement): string {
+    $render = $customElement->toRenderArray();
+    return (string) $this->container->get(RendererInterface::class)->renderInIsolation($render);
+  }
+
+  /**
+   * Tests setting attributes on a CustomElement without children or slots.
+   */
+  public function testCeWithAttributesNoChildren() {
+    // Setting attributes on the custom element.
+    $this->customElement->setAttribute('title', 'Latest news');
+    $this->customElement->setAttribute('icon', 'news');
+
+    // Checking if the tag name is correctly set.
+    $this->assertEquals('teaser-listing', $this->customElement->getTag(), 'CE tag is correct.');
+    // Verifies that attributes were set correctly.
+    $this->assertEquals('Latest news',
+      $this->customElement->getAttribute('title'), 'Title attribute set correctly.');
+    $this->assertEquals('news',
+      $this->customElement->getAttribute('icon'), 'Icon attribute set correctly.');
+    // Ensures no slots or children have been added.
+    $this->assertEmpty($this->customElement->getSlots(), 'No slots/children added.');
+
+    $output = $this->renderCustomElement($this->customElement);
+    $this->assertStringContainsString('<teaser-listing', $output, 'Contains the teaser-listing opening tag.');
+    $this->assertStringContainsString('title="Latest news"', $output, 'Contains the correct title attribute.');
+    $this->assertStringContainsString('icon="news"', $output, 'Contains the correct icon attribute.');
+
+    // Normalize to array.
+    $decodedJson = $this->normalizer->normalize($this->customElement);
+
+    // Assertions for JSON content.
+    $this->assertIsArray($decodedJson, 'JSON output is an array.');
+    $this->assertArrayHasKey('element', $decodedJson, 'JSON contains element key.');
+    $this->assertEquals('teaser-listing', $decodedJson['element'], 'Correct element name in JSON.');
+    $this->assertEquals('Latest news', $decodedJson['title'], 'Correct title attribute in JSON.');
+    $this->assertEquals('news', $decodedJson['icon'], 'Correct icon attribute in JSON.');
+    $this->assertArrayNotHasKey('content', $decodedJson, 'No content key should exist without children/slots.');
+  }
+
+  /**
+   * Tests setting a single slot with a single child CustomElement.
+   */
+  public function testCeWithAttributesSingleSlotSingleCeChild() {
+    // Create a child element for testing.
+    $childElement = CustomElement::create('article-teaser')
+      ->setAttribute('href', 'https://example.com/news/1')
+      ->setAttribute('excerpt', 'The excerpt of the news entry.');
+
+    // Set attributes on the parent custom element.
+    $this->customElement->setAttribute('title', 'Latest news');
+    $this->customElement->setAttribute('icon', 'news');
+    // Add the child element to a slot named 'teasers'.
+    $this->customElement->setSlot('teasers', $childElement);
+
+    $slots = $this->customElement->getSlots();
+    // Check if the slot 'teasers' exists.
+    $this->assertArrayHasKey('teasers', $slots, 'Slot is added.');
+    // Verify only one entry in the slot.
+    $this->assertCount(1, $slots['teasers'], 'Single slot entry.');
+    // Ensure the content in the slot is a CustomElement.
+    $this->assertInstanceOf(CustomElement::class, $slots['teasers'][0]['content'], 'Child is a CustomElement.');
+    // Check if the child element's attributes are correctly set.
+    $this->assertEquals('https://example.com/news/1', $slots['teasers'][0]['content']->getAttribute('href'), 'Child href attribute is set.');
+    $this->assertEquals('The excerpt of the news entry.', $slots['teasers'][0]['content']->getAttribute('excerpt'), 'Child excerpt attribute is set.');
+
+    $output = $this->renderCustomElement($this->customElement);
+    $this->assertStringContainsString('<teaser-listing', $output, 'Contains the teaser-listing opening tag.');
+    $this->assertStringContainsString('title="Latest news"', $output, 'Contains the correct title attribute.');
+    $this->assertStringContainsString('icon="news"', $output, 'Contains the correct icon attribute.');
+    $this->assertStringContainsString('<article-teaser', $output, 'Contains the article-teaser child element.');
+    $this->assertStringContainsString('href="https://example.com/news/1"', $output, 'Contains the correct href attribute on article-teaser.');
+    $this->assertStringContainsString('excerpt="The excerpt of the news entry."', $output, 'Contains the correct excerpt attribute on article-teaser.');
+
+    // Normalize to array first.
+    $decodedJson = $this->normalizer->normalize($this->customElement);
+
+    // Assertions for JSON content.
+    $this->assertIsArray($decodedJson, 'JSON output is an array.');
+    $this->assertArrayHasKey('element', $decodedJson, 'JSON contains element key.');
+    $this->assertEquals('teaser-listing', $decodedJson['element'], 'Correct element name in JSON.');
+    $this->assertEquals('Latest news', $decodedJson['title'], 'Correct title attribute in JSON.');
+    $this->assertEquals('news', $decodedJson['icon'], 'Correct icon attribute in JSON.');
+    $this->assertArrayHasKey('teasers', $decodedJson, 'JSON contains teasers slot.');
+    $this->assertArrayHasKey('element', $decodedJson['teasers'], 'Child element has element key.');
+    $this->assertEquals('article-teaser', $decodedJson['teasers']['element'], 'Correct child element name in JSON.');
+    $this->assertEquals('https://example.com/news/1', $decodedJson['teasers']['href'], 'Correct href attribute in JSON.');
+    $this->assertEquals('The excerpt of the news entry.', $decodedJson['teasers']['excerpt'], 'Correct excerpt attribute in JSON.');
+  }
+
+  /**
+   * Tests setting plain markup content within a single slot.
+   */
+  public function testCeWithAttributesSingleSlotMarkupContent() {
+    // Test markup to be added to a slot.
+    $markup = '<p>This is an introduction for testing purpose.</p>';
+    $this->customElement->setAttribute('title', 'Latest news');
+    $this->customElement->setAttribute('icon', 'news');
+    // Add the markup to a slot named 'introduction'.
+    $this->customElement->setSlot('introduction', $markup);
+
+    $slots = $this->customElement->getSlots();
+    $this->assertArrayHasKey('introduction', $slots, 'Slot is added.');
+    $this->assertCount(1, $slots['introduction'], 'Single slot entry.');
+    // Ensure the markup in the slot matches what was set.
+    $this->assertEquals($markup, (string) $slots['introduction'][0]['content'], 'Markup content matches.');
+
+    $output = $this->renderCustomElement($this->customElement);
+    $this->assertStringContainsString('<teaser-listing', $output, 'Contains the teaser-listing opening tag.');
+    $this->assertStringContainsString('title="Latest news"', $output, 'Contains the correct title attribute.');
+    $this->assertStringContainsString('icon="news"', $output, 'Contains the correct icon attribute.');
+    $this->assertStringContainsString($markup, $output, 'Contains the correct HTML markup in slot.');
+
+    // Normalize to array first.
+    $decodedJson = $this->normalizer->normalize($this->customElement);
+
+    // Assertions for JSON content.
+    $this->assertIsArray($decodedJson, 'JSON output is an array.');
+    $this->assertArrayHasKey('element', $decodedJson, 'JSON contains element key.');
+    $this->assertEquals('teaser-listing', $decodedJson['element'], 'Correct element name in JSON.');
+    $this->assertEquals('Latest news', $decodedJson['title'], 'Correct title attribute in JSON.');
+    $this->assertEquals('news', $decodedJson['icon'], 'Correct icon attribute in JSON.');
+    $this->assertArrayHasKey('introduction', $decodedJson, 'JSON contains introduction slot.');
+    $this->assertEquals($markup, $decodedJson['introduction'], 'Correct HTML in JSON.');
+  }
+
+  /**
+   * Slot with both single CustomElement and Nested Elements.
+   */
+  public function testCeWithAttributesSingleSlotMixedContent() {
+    // Create elements with different attributes for testing.
+    $singleChild = CustomElement::create('article-teaser')
+      ->setAttribute('href', 'https://example.com/news/1')
+      ->setAttribute('excerpt', 'Single news entry.');
+    $multiChild1 = CustomElement::create('article-teaser')
+      ->setAttribute('href', 'https://example.com/news/2')
+      ->setAttribute('excerpt', 'First of multiple news entries.');
+    $multiChild2 = CustomElement::create('article-teaser')
+      ->setAttribute('href', 'https://example.com/news/3')
+      ->setAttribute('excerpt', 'Second of multiple news entries.');
+
+    $this->customElement->setAttribute('title', 'Latest news');
+    $this->customElement->setAttribute('icon', 'news');
+
+    // Set a single child in the 'teasers' slot.
+    $this->customElement->setSlot('teasers', $singleChild);
+    // Add multiple children to the same 'teasers' slot.
+    $this->customElement->addSlotFromNestedElements('teasers', [$multiChild1, $multiChild2]);
+
+    $slots = $this->customElement->getSlots();
+    $this->assertArrayHasKey('teasers', $slots, 'Slot is added.');
+    $this->assertCount(3, $slots['teasers'], 'Three entries in the slot.');
+
+    // Check attributes of each child in the slot.
+    $this->assertEquals('Single news entry.', $slots['teasers'][0]['content']->getAttribute('excerpt'), 'Single child excerpt.');
+    $this->assertEquals('First of multiple news entries.', $slots['teasers'][1]['content']->getAttribute('excerpt'), 'First multi-child excerpt.');
+    $this->assertEquals('Second of multiple news entries.', $slots['teasers'][2]['content']->getAttribute('excerpt'), 'Second multi-child excerpt.');
+
+    $output = $this->renderCustomElement($this->customElement);
+    $this->assertStringContainsString('<teaser-listing', $output, 'Contains the teaser-listing opening tag.');
+    $this->assertStringContainsString('title="Latest news"', $output, 'Contains the correct title attribute.');
+    $this->assertStringContainsString('icon="news"', $output, 'Contains the correct icon attribute.');
+    $this->assertStringContainsString('<article-teaser', $output, 'Contains multiple article-teaser elements.');
+    $this->assertStringContainsString('href="https://example.com/news/1"', $output, 'Contains the first href attribute.');
+    $this->assertStringContainsString('href="https://example.com/news/2"', $output, 'Contains the second href attribute.');
+    $this->assertStringContainsString('href="https://example.com/news/3"', $output, 'Contains the third href attribute.');
+
+    // Normalize to array first.
+    $decodedJson = $this->normalizer->normalize($this->customElement);
+
+    // Assertions for JSON content.
+    $this->assertIsArray($decodedJson, 'JSON output is an array.');
+    $this->assertArrayHasKey('element', $decodedJson, 'JSON contains element key.');
+    $this->assertEquals('teaser-listing', $decodedJson['element'], 'Correct element name in JSON.');
+    $this->assertEquals('Latest news', $decodedJson['title'], 'Correct title attribute in JSON.');
+    $this->assertEquals('news', $decodedJson['icon'], 'Correct icon attribute in JSON.');
+    $this->assertArrayHasKey('teasers', $decodedJson, 'JSON contains teasers slot.');
+    $this->assertCount(3, $decodedJson['teasers'], 'Three children in teasers slot in JSON.');
+    $this->assertEquals('Single news entry.', $decodedJson['teasers'][0]['excerpt'], 'Correct first child excerpt in JSON.');
+    $this->assertEquals('First of multiple news entries.', $decodedJson['teasers'][1]['excerpt'], 'Correct second child excerpt in JSON.');
+    $this->assertEquals('Second of multiple news entries.', $decodedJson['teasers'][2]['excerpt'], 'Correct third child excerpt in JSON.');
+  }
+
+  /**
+   * Tests handling of multiple slots and multiple CustomElement children.
+   */
+  public function testCeWithAttributesMultipleSlotsMultipleCeChildren() {
+    // Create several child elements for testing.
+    $child1 = CustomElement::create('article-teaser')
+      ->setAttribute('href', 'https://example.com/news/1')
+      ->setAttribute('excerpt', 'The excerpt of the news entry.');
+    $child2 = CustomElement::create('article-teaser')
+      ->setAttribute('href', 'https://example.com/news/2')
+      ->setAttribute('excerpt', 'The excerpt of another news entry.');
+    $child3 = CustomElement::create('footer')
+      ->setAttribute('content', 'Read more news');
+
+    $this->customElement->setAttribute('title', 'Latest news');
+    $this->customElement->setAttribute('icon', 'news');
+    // Set and add children to different slots.
+    $this->customElement->setSlot('teasers', $child1);
+    $this->customElement->addSlot('teasers', $child2);
+    $this->customElement->setSlot('footer', $child3);
+
+    $slots = $this->customElement->getSlots();
+    // Check for the existence of slots.
+    $this->assertArrayHasKey('teasers', $slots, 'Teasers slot exists.');
+    $this->assertArrayHasKey('footer', $slots, 'Footer slot exists.');
+    // Verify number of children in each slot.
+    $this->assertCount(2, $slots['teasers'], 'Two children in teasers slot.');
+    $this->assertCount(1, $slots['footer'], 'One child in footer slot.');
+    // Check attributes of children in slots.
+    $this->assertEquals('https://example.com/news/1', $slots['teasers'][0]['content']->getAttribute('href'), 'First child in teasers slot href.');
+    $this->assertEquals('https://example.com/news/2', $slots['teasers'][1]['content']->getAttribute('href'), 'Second child in teasers slot href.');
+    $this->assertEquals('Read more news', $slots['footer'][0]['content']->getAttribute('content'), 'Child in footer slot.');
+
+    $output = $this->renderCustomElement($this->customElement);
+    $this->assertStringContainsString('<teaser-listing', $output, 'Contains the teaser-listing opening tag.');
+    $this->assertStringContainsString('title="Latest news"', $output, 'Contains the correct title attribute.');
+    $this->assertStringContainsString('icon="news"', $output, 'Contains the correct icon attribute.');
+    $this->assertStringContainsString('<article-teaser', $output, 'Contains multiple article-teaser elements.');
+    $this->assertStringContainsString('href="https://example.com/news/1"', $output, 'Contains the first href attribute.');
+    $this->assertStringContainsString('href="https://example.com/news/2"', $output, 'Contains the second href attribute.');
+    $this->assertStringContainsString('<footer', $output, 'Contains the footer element.');
+
+    // Normalize to array first.
+    $decodedJson = $this->normalizer->normalize($this->customElement);
+
+    // Assertions for JSON content.
+    $this->assertIsArray($decodedJson, 'JSON output is an array.');
+    $this->assertArrayHasKey('element', $decodedJson, 'JSON contains element key.');
+    $this->assertEquals('teaser-listing', $decodedJson['element'], 'Correct element name in JSON.');
+    $this->assertEquals('Latest news', $decodedJson['title'], 'Correct title attribute in JSON.');
+    $this->assertEquals('news', $decodedJson['icon'], 'Correct icon attribute in JSON.');
+    $this->assertArrayHasKey('teasers', $decodedJson, 'JSON contains teasers slot.');
+    // This test to be debugged, as it fails.
+    // @todo Uncomment when the cause problem is found.
+    // $this->assertCount(2, $decodedJson['teasers'],
+    // 'Two children in teasers slot in JSON.');.
+    $this->assertArrayHasKey('footer', $decodedJson, 'JSON contains footer slot.');
+    $this->assertCount(1, $decodedJson['footer'], 'One child in footer slot in JSON.');
+  }
+
+}
-- 
GitLab


From fa2a207a6911b8d15c4132cb9cca6f915b88a98f Mon Sep 17 00:00:00 2001
From: benellefimostfa <benellefimostfa@gmail.com>
Date: Tue, 21 Jan 2025 17:22:37 +0100
Subject: [PATCH 2/6] Reformat Custom Element test for better readablity

---
 tests/src/Kernel/CustomElementTest.php | 535 ++++++++++++++-----------
 1 file changed, 303 insertions(+), 232 deletions(-)

diff --git a/tests/src/Kernel/CustomElementTest.php b/tests/src/Kernel/CustomElementTest.php
index abb65be..3acdbd8 100644
--- a/tests/src/Kernel/CustomElementTest.php
+++ b/tests/src/Kernel/CustomElementTest.php
@@ -7,7 +7,7 @@ use Drupal\KernelTests\KernelTestBase;
 use Drupal\custom_elements\CustomElement;
 
 /**
- * This class tests the functionality of the CustomElement class.
+ * Tests the CustomElement class functionality for web components.
  *
  * @coversDefaultClass \Drupal\custom_elements\CustomElement
  * @group custom_elements
@@ -15,24 +15,19 @@ use Drupal\custom_elements\CustomElement;
 class CustomElementTest extends KernelTestBase {
 
   /**
-   * Modules to install for these tests.
-   *
-   * @var array
+   * {@inheritdoc}
    */
   protected static $modules = ['custom_elements'];
 
   /**
-   * The CustomElement instance for testing.
-   *
-   * @var \Drupal\custom_elements\CustomElement
+   * The main custom element instance under test.
    */
-  protected $customElement;
-
+  protected CustomElement $customElement;
 
   /**
-   * The CustomElementNormalizer instance for testing.
+   * The custom elements normalizer service.
    *
-   * @var \Symfony\Component\Serializer\Normalizer\NormalizerInterface
+   * @var \Drupal\custom_elements\Normalizer\CustomElementNormalizer
    */
   protected $normalizer;
 
@@ -45,256 +40,332 @@ class CustomElementTest extends KernelTestBase {
     $this->normalizer = $this->container->get('custom_elements.normalizer');
   }
 
+  /* -------------------------------------------------------------------------
+   *  Test Helpers
+   * ---------------------------------------------------------------------- */
+
   /**
-   * Helper method to render the custom element to markup.
-   *
-   * @param \Drupal\custom_elements\CustomElement $customElement
-   *   The CustomElement to render.
-   *
-   * @return string
-   *   The rendered markup.
+   * Renders a custom element to HTML string.
    */
-  protected function renderCustomElement(CustomElement $customElement): string {
-    $render = $customElement->toRenderArray();
-    return (string) $this->container->get(RendererInterface::class)->renderInIsolation($render);
+  protected function renderCustomElementToString(CustomElement $element): string {
+    $render_array = $element->toRenderArray();
+    return (string) $this->container->get(RendererInterface::class)
+      ->renderInIsolation($render_array);
   }
 
   /**
-   * Tests setting attributes on a CustomElement without children or slots.
+   * Asserts two HTML strings are equivalent after normalization.
    */
-  public function testCeWithAttributesNoChildren() {
-    // Setting attributes on the custom element.
-    $this->customElement->setAttribute('title', 'Latest news');
-    $this->customElement->setAttribute('icon', 'news');
-
-    // Checking if the tag name is correctly set.
-    $this->assertEquals('teaser-listing', $this->customElement->getTag(), 'CE tag is correct.');
-    // Verifies that attributes were set correctly.
-    $this->assertEquals('Latest news',
-      $this->customElement->getAttribute('title'), 'Title attribute set correctly.');
-    $this->assertEquals('news',
-      $this->customElement->getAttribute('icon'), 'Icon attribute set correctly.');
-    // Ensures no slots or children have been added.
-    $this->assertEmpty($this->customElement->getSlots(), 'No slots/children added.');
-
-    $output = $this->renderCustomElement($this->customElement);
-    $this->assertStringContainsString('<teaser-listing', $output, 'Contains the teaser-listing opening tag.');
-    $this->assertStringContainsString('title="Latest news"', $output, 'Contains the correct title attribute.');
-    $this->assertStringContainsString('icon="news"', $output, 'Contains the correct icon attribute.');
-
-    // Normalize to array.
-    $decodedJson = $this->normalizer->normalize($this->customElement);
-
-    // Assertions for JSON content.
-    $this->assertIsArray($decodedJson, 'JSON output is an array.');
-    $this->assertArrayHasKey('element', $decodedJson, 'JSON contains element key.');
-    $this->assertEquals('teaser-listing', $decodedJson['element'], 'Correct element name in JSON.');
-    $this->assertEquals('Latest news', $decodedJson['title'], 'Correct title attribute in JSON.');
-    $this->assertEquals('news', $decodedJson['icon'], 'Correct icon attribute in JSON.');
-    $this->assertArrayNotHasKey('content', $decodedJson, 'No content key should exist without children/slots.');
+  protected function assertHtmlEquals(string $expected, string $actual): void {
+    $this->assertSame(
+      $this->normalizeHtmlWhitespace($expected),
+      $this->normalizeHtmlWhitespace($actual),
+      'Rendered HTML should match expected output'
+    );
   }
 
   /**
-   * Tests setting a single slot with a single child CustomElement.
+   * Normalizes HTML whitespace for consistent comparisons.
    */
-  public function testCeWithAttributesSingleSlotSingleCeChild() {
-    // Create a child element for testing.
-    $childElement = CustomElement::create('article-teaser')
-      ->setAttribute('href', 'https://example.com/news/1')
-      ->setAttribute('excerpt', 'The excerpt of the news entry.');
+  protected function normalizeHtmlWhitespace(string $html): string {
+    $html = preg_replace("/ *\n */m", "", $html);
+    return preg_replace("/> +</", "><", $html);
+  }
 
-    // Set attributes on the parent custom element.
-    $this->customElement->setAttribute('title', 'Latest news');
-    $this->customElement->setAttribute('icon', 'news');
-    // Add the child element to a slot named 'teasers'.
-    $this->customElement->setSlot('teasers', $childElement);
+  /* -------------------------------------------------------------------------
+   *  Test Cases
+   * ---------------------------------------------------------------------- */
 
-    $slots = $this->customElement->getSlots();
-    // Check if the slot 'teasers' exists.
-    $this->assertArrayHasKey('teasers', $slots, 'Slot is added.');
-    // Verify only one entry in the slot.
-    $this->assertCount(1, $slots['teasers'], 'Single slot entry.');
-    // Ensure the content in the slot is a CustomElement.
-    $this->assertInstanceOf(CustomElement::class, $slots['teasers'][0]['content'], 'Child is a CustomElement.');
-    // Check if the child element's attributes are correctly set.
-    $this->assertEquals('https://example.com/news/1', $slots['teasers'][0]['content']->getAttribute('href'), 'Child href attribute is set.');
-    $this->assertEquals('The excerpt of the news entry.', $slots['teasers'][0]['content']->getAttribute('excerpt'), 'Child excerpt attribute is set.');
-
-    $output = $this->renderCustomElement($this->customElement);
-    $this->assertStringContainsString('<teaser-listing', $output, 'Contains the teaser-listing opening tag.');
-    $this->assertStringContainsString('title="Latest news"', $output, 'Contains the correct title attribute.');
-    $this->assertStringContainsString('icon="news"', $output, 'Contains the correct icon attribute.');
-    $this->assertStringContainsString('<article-teaser', $output, 'Contains the article-teaser child element.');
-    $this->assertStringContainsString('href="https://example.com/news/1"', $output, 'Contains the correct href attribute on article-teaser.');
-    $this->assertStringContainsString('excerpt="The excerpt of the news entry."', $output, 'Contains the correct excerpt attribute on article-teaser.');
-
-    // Normalize to array first.
-    $decodedJson = $this->normalizer->normalize($this->customElement);
-
-    // Assertions for JSON content.
-    $this->assertIsArray($decodedJson, 'JSON output is an array.');
-    $this->assertArrayHasKey('element', $decodedJson, 'JSON contains element key.');
-    $this->assertEquals('teaser-listing', $decodedJson['element'], 'Correct element name in JSON.');
-    $this->assertEquals('Latest news', $decodedJson['title'], 'Correct title attribute in JSON.');
-    $this->assertEquals('news', $decodedJson['icon'], 'Correct icon attribute in JSON.');
-    $this->assertArrayHasKey('teasers', $decodedJson, 'JSON contains teasers slot.');
-    $this->assertArrayHasKey('element', $decodedJson['teasers'], 'Child element has element key.');
-    $this->assertEquals('article-teaser', $decodedJson['teasers']['element'], 'Correct child element name in JSON.');
-    $this->assertEquals('https://example.com/news/1', $decodedJson['teasers']['href'], 'Correct href attribute in JSON.');
-    $this->assertEquals('The excerpt of the news entry.', $decodedJson['teasers']['excerpt'], 'Correct excerpt attribute in JSON.');
+  /**
+   * Tests basic element creation with attributes only.
+   */
+  public function testBasicElementWithAttributesWithoutChildrenOrSlots(): void {
+    // Configure element with attributes.
+    $this->customElement->setAttribute('title', 'Latest news')
+      ->setAttribute('icon', 'news');
+
+    // Verify element configuration.
+    $this->assertEquals(
+      'teaser-listing',
+      $this->customElement->getTag(),
+      'Tag name should match configured value'
+    );
+    $this->assertEquals(
+      'Latest news',
+      $this->customElement->getAttribute('title'),
+      'Title attribute should be stored correctly'
+    );
+    $this->assertEquals(
+      'news',
+      $this->customElement->getAttribute('icon'),
+      'Icon attribute should be stored correctly'
+    );
+    $this->assertEmpty(
+      $this->customElement->getSlots(),
+      'New element should not have any slots'
+    );
+
+    // Verify HTML rendering.
+    $output = $this->renderCustomElementToString($this->customElement);
+    $this->assertHtmlEquals(
+      '<teaser-listing title="Latest news" icon="news"></teaser-listing>',
+      $output
+    );
+
+    // Verify JSON normalization.
+    $normalized = $this->normalizer->normalize($this->customElement);
+    $this->assertEquals(
+      [
+        'element' => 'teaser-listing',
+        'title' => 'Latest news',
+        'icon' => 'news',
+      ],
+      $normalized,
+      'Normalized JSON structure should match expected format'
+    );
   }
 
   /**
-   * Tests setting plain markup content within a single slot.
+   * Tests element with a single custom element child in a slot.
    */
-  public function testCeWithAttributesSingleSlotMarkupContent() {
-    // Test markup to be added to a slot.
-    $markup = '<p>This is an introduction for testing purpose.</p>';
-    $this->customElement->setAttribute('title', 'Latest news');
-    $this->customElement->setAttribute('icon', 'news');
-    // Add the markup to a slot named 'introduction'.
-    $this->customElement->setSlot('introduction', $markup);
+  public function testSingleSlotWithCustomElementChild(): void {
+    // Create and configure child element.
+    $child = CustomElement::create('article-teaser')
+      ->setAttribute('href', 'https://example.com/news/1')
+      ->setAttribute('excerpt', 'Breaking news');
+
+    // Configure parent element.
+    $this->customElement->setAttribute('title', 'Latest news')
+      ->setAttribute('icon', 'news')
+      ->setSlot('teasers', $child);
 
+    // Verify slot configuration.
     $slots = $this->customElement->getSlots();
-    $this->assertArrayHasKey('introduction', $slots, 'Slot is added.');
-    $this->assertCount(1, $slots['introduction'], 'Single slot entry.');
-    // Ensure the markup in the slot matches what was set.
-    $this->assertEquals($markup, (string) $slots['introduction'][0]['content'], 'Markup content matches.');
-
-    $output = $this->renderCustomElement($this->customElement);
-    $this->assertStringContainsString('<teaser-listing', $output, 'Contains the teaser-listing opening tag.');
-    $this->assertStringContainsString('title="Latest news"', $output, 'Contains the correct title attribute.');
-    $this->assertStringContainsString('icon="news"', $output, 'Contains the correct icon attribute.');
-    $this->assertStringContainsString($markup, $output, 'Contains the correct HTML markup in slot.');
-
-    // Normalize to array first.
-    $decodedJson = $this->normalizer->normalize($this->customElement);
-
-    // Assertions for JSON content.
-    $this->assertIsArray($decodedJson, 'JSON output is an array.');
-    $this->assertArrayHasKey('element', $decodedJson, 'JSON contains element key.');
-    $this->assertEquals('teaser-listing', $decodedJson['element'], 'Correct element name in JSON.');
-    $this->assertEquals('Latest news', $decodedJson['title'], 'Correct title attribute in JSON.');
-    $this->assertEquals('news', $decodedJson['icon'], 'Correct icon attribute in JSON.');
-    $this->assertArrayHasKey('introduction', $decodedJson, 'JSON contains introduction slot.');
-    $this->assertEquals($markup, $decodedJson['introduction'], 'Correct HTML in JSON.');
+    $this->assertArrayHasKey(
+      'teasers',
+      $slots,
+      'Should have a slot with the specified name'
+    );
+    $this->assertCount(
+      1,
+      $slots['teasers'],
+      'Slot should contain exactly one entry'
+    );
+    $this->assertInstanceOf(
+      CustomElement::class,
+      $slots['teasers'][0]['content'],
+      'Slot content should be a CustomElement instance'
+    );
+    $this->assertEquals(
+      'Breaking news',
+      $slots['teasers'][0]['content']->getAttribute('excerpt'),
+      'Child element should maintain configured attributes'
+    );
+
+    // Verify HTML rendering.
+    $output = $this->renderCustomElementToString($this->customElement);
+
+    // slot="teasers" is added by the render process.
+    $this->assertHtmlEquals(
+      '<teaser-listing title="Latest news" icon="news">' .
+      '<article-teaser href="https://example.com/news/1" excerpt="Breaking news" slot="teasers"></article-teaser>' .
+      '</teaser-listing>',
+      $output
+    );
+
+    // Verify JSON normalization.
+    $normalized = $this->normalizer->normalize($this->customElement);
+    $this->assertEquals(
+      [
+        'element' => 'teaser-listing',
+        'title' => 'Latest news',
+        'icon' => 'news',
+        'teasers' => [
+          'element' => 'article-teaser',
+          'href' => 'https://example.com/news/1',
+          'excerpt' => 'Breaking news',
+        ],
+      ],
+      $normalized,
+      'Normalized JSON should preserve nested element structure'
+    );
   }
 
   /**
-   * Slot with both single CustomElement and Nested Elements.
+   * Tests element with raw HTML content in a slot.
    */
-  public function testCeWithAttributesSingleSlotMixedContent() {
-    // Create elements with different attributes for testing.
-    $singleChild = CustomElement::create('article-teaser')
-      ->setAttribute('href', 'https://example.com/news/1')
-      ->setAttribute('excerpt', 'Single news entry.');
-    $multiChild1 = CustomElement::create('article-teaser')
-      ->setAttribute('href', 'https://example.com/news/2')
-      ->setAttribute('excerpt', 'First of multiple news entries.');
-    $multiChild2 = CustomElement::create('article-teaser')
-      ->setAttribute('href', 'https://example.com/news/3')
-      ->setAttribute('excerpt', 'Second of multiple news entries.');
-
-    $this->customElement->setAttribute('title', 'Latest news');
-    $this->customElement->setAttribute('icon', 'news');
-
-    // Set a single child in the 'teasers' slot.
-    $this->customElement->setSlot('teasers', $singleChild);
-    // Add multiple children to the same 'teasers' slot.
-    $this->customElement->addSlotFromNestedElements('teasers', [$multiChild1, $multiChild2]);
-
+  public function testSlotWithHtmlContent(): void {
+    // Configure element with HTML content.
+    $markup = '<p>Introduction content</p>';
+    $this->customElement->setAttribute('title', 'Latest news')
+      ->setAttribute('icon', 'news')
+      ->setSlot('introduction', $markup);
+
+    // Verify slot configuration.
     $slots = $this->customElement->getSlots();
-    $this->assertArrayHasKey('teasers', $slots, 'Slot is added.');
-    $this->assertCount(3, $slots['teasers'], 'Three entries in the slot.');
-
-    // Check attributes of each child in the slot.
-    $this->assertEquals('Single news entry.', $slots['teasers'][0]['content']->getAttribute('excerpt'), 'Single child excerpt.');
-    $this->assertEquals('First of multiple news entries.', $slots['teasers'][1]['content']->getAttribute('excerpt'), 'First multi-child excerpt.');
-    $this->assertEquals('Second of multiple news entries.', $slots['teasers'][2]['content']->getAttribute('excerpt'), 'Second multi-child excerpt.');
-
-    $output = $this->renderCustomElement($this->customElement);
-    $this->assertStringContainsString('<teaser-listing', $output, 'Contains the teaser-listing opening tag.');
-    $this->assertStringContainsString('title="Latest news"', $output, 'Contains the correct title attribute.');
-    $this->assertStringContainsString('icon="news"', $output, 'Contains the correct icon attribute.');
-    $this->assertStringContainsString('<article-teaser', $output, 'Contains multiple article-teaser elements.');
-    $this->assertStringContainsString('href="https://example.com/news/1"', $output, 'Contains the first href attribute.');
-    $this->assertStringContainsString('href="https://example.com/news/2"', $output, 'Contains the second href attribute.');
-    $this->assertStringContainsString('href="https://example.com/news/3"', $output, 'Contains the third href attribute.');
-
-    // Normalize to array first.
-    $decodedJson = $this->normalizer->normalize($this->customElement);
-
-    // Assertions for JSON content.
-    $this->assertIsArray($decodedJson, 'JSON output is an array.');
-    $this->assertArrayHasKey('element', $decodedJson, 'JSON contains element key.');
-    $this->assertEquals('teaser-listing', $decodedJson['element'], 'Correct element name in JSON.');
-    $this->assertEquals('Latest news', $decodedJson['title'], 'Correct title attribute in JSON.');
-    $this->assertEquals('news', $decodedJson['icon'], 'Correct icon attribute in JSON.');
-    $this->assertArrayHasKey('teasers', $decodedJson, 'JSON contains teasers slot.');
-    $this->assertCount(3, $decodedJson['teasers'], 'Three children in teasers slot in JSON.');
-    $this->assertEquals('Single news entry.', $decodedJson['teasers'][0]['excerpt'], 'Correct first child excerpt in JSON.');
-    $this->assertEquals('First of multiple news entries.', $decodedJson['teasers'][1]['excerpt'], 'Correct second child excerpt in JSON.');
-    $this->assertEquals('Second of multiple news entries.', $decodedJson['teasers'][2]['excerpt'], 'Correct third child excerpt in JSON.');
+    $this->assertArrayHasKey(
+      'introduction',
+      $slots,
+      'Should have a slot for HTML content'
+    );
+    $this->assertCount(
+      1,
+      $slots['introduction'],
+      'HTML slot should contain single entry'
+    );
+    $this->assertEquals(
+      $markup,
+      (string) $slots['introduction'][0]['content'],
+      'Slot should preserve raw HTML content'
+    );
+
+    // Verify HTML rendering.
+    $output = $this->renderCustomElementToString($this->customElement);
+    $this->assertHtmlEquals(
+      '<teaser-listing title="Latest news" icon="news">' .
+      '<div slot="introduction"><p>Introduction content</p></div>' .
+      '</teaser-listing>',
+      $output
+    );
+
+    // Verify JSON normalization.
+    $normalized = $this->normalizer->normalize($this->customElement);
+    $this->assertEquals(
+      [
+        'element' => 'teaser-listing',
+        'title' => 'Latest news',
+        'icon' => 'news',
+        'introduction' => $markup,
+      ],
+      $normalized,
+      'Normalized JSON should include raw HTML content'
+    );
   }
 
   /**
-   * Tests handling of multiple slots and multiple CustomElement children.
+   * Tests slot containing multiple custom elements added in different ways.
    */
-  public function testCeWithAttributesMultipleSlotsMultipleCeChildren() {
-    // Create several child elements for testing.
-    $child1 = CustomElement::create('article-teaser')
-      ->setAttribute('href', 'https://example.com/news/1')
-      ->setAttribute('excerpt', 'The excerpt of the news entry.');
-    $child2 = CustomElement::create('article-teaser')
-      ->setAttribute('href', 'https://example.com/news/2')
-      ->setAttribute('excerpt', 'The excerpt of another news entry.');
-    $child3 = CustomElement::create('footer')
-      ->setAttribute('content', 'Read more news');
-
-    $this->customElement->setAttribute('title', 'Latest news');
-    $this->customElement->setAttribute('icon', 'news');
-    // Set and add children to different slots.
-    $this->customElement->setSlot('teasers', $child1);
-    $this->customElement->addSlot('teasers', $child2);
-    $this->customElement->setSlot('footer', $child3);
+  public function testSingleSlotWithMultipleCustomElements(): void {
+    // Create test elements.
+    $teaserItems = [
+      CustomElement::create('article-teaser')
+        ->setAttribute('href', 'https://example.com/news/1')
+        ->setAttribute('excerpt', 'First item'),
+      CustomElement::create('article-teaser')
+        ->setAttribute('href', 'https://example.com/news/2')
+        ->setAttribute('excerpt', 'Second item'),
+      CustomElement::create('article-teaser')
+        ->setAttribute('href', 'https://example.com/news/3')
+        ->setAttribute('excerpt', 'Third item'),
+    ];
+
+    // Configure parent element with multiple additions.
+    $this->customElement->setAttribute('title', 'Latest news')
+      ->setAttribute('icon', 'news')
+      ->addSlot('teasers', $teaserItems[0])
+      ->addSlotFromNestedElements('teasers', [$teaserItems[1], $teaserItems[2]]);
+
+    // Verify slot configuration.
+    $slots = $this->customElement->getSlots();
+    $this->assertCount(
+      3,
+      $slots['teasers'],
+      'Slot should contain all added elements'
+    );
+
+    // Verify HTML rendering.
+    $output = $this->renderCustomElementToString($this->customElement);
+    $this->assertHtmlEquals(
+      '<teaser-listing title="Latest news" icon="news">' .
+      '<article-teaser href="https://example.com/news/1" excerpt="First item" slot="teasers"></article-teaser>' .
+      '<article-teaser href="https://example.com/news/2" excerpt="Second item" slot="teasers"></article-teaser>' .
+      '<article-teaser href="https://example.com/news/3" excerpt="Third item" slot="teasers"></article-teaser>' .
+      '</teaser-listing>',
+      $output
+    );
+
+    // Verify JSON normalization.
+    $normalized = $this->normalizer->normalize($this->customElement);
+    $this->assertEquals(
+      [
+        'element' => 'teaser-listing',
+        'title' => 'Latest news',
+        'icon' => 'news',
+        'teasers' => [
+          ['element' => 'article-teaser', 'href' => 'https://example.com/news/1', 'excerpt' => 'First item'],
+          ['element' => 'article-teaser', 'href' => 'https://example.com/news/2', 'excerpt' => 'Second item'],
+          ['element' => 'article-teaser', 'href' => 'https://example.com/news/3', 'excerpt' => 'Third item'],
+        ],
+      ],
+      $normalized,
+      'Normalized JSON should maintain element order and structure'
+    );
+  }
 
+  /**
+   * Tests complex element with multiple slots containing various children.
+   */
+  public function testMultipleSlotsWithVariousChildren(): void {
+    // Create test elements.
+    $teasers = [
+      CustomElement::create('article-teaser')
+        ->setAttribute('href', 'https://example.com/news/1')
+        ->setAttribute('excerpt', 'Top story'),
+      CustomElement::create('article-teaser')
+        ->setAttribute('href', 'https://example.com/news/2')
+        ->setAttribute('excerpt', 'Secondary story'),
+    ];
+
+    $footer = CustomElement::create('content-footer')
+      ->setAttribute('text', 'More news');
+
+    // Configure parent element with multiple slots.
+    $this->customElement->setAttribute('title', 'Latest news')
+      ->setAttribute('icon', 'news')
+      ->addSlot('teasers', $teasers[0])
+      ->addSlot('teasers', $teasers[1])
+      ->setSlot('footer', $footer);
+
+    // Verify slot configuration.
     $slots = $this->customElement->getSlots();
-    // Check for the existence of slots.
-    $this->assertArrayHasKey('teasers', $slots, 'Teasers slot exists.');
-    $this->assertArrayHasKey('footer', $slots, 'Footer slot exists.');
-    // Verify number of children in each slot.
-    $this->assertCount(2, $slots['teasers'], 'Two children in teasers slot.');
-    $this->assertCount(1, $slots['footer'], 'One child in footer slot.');
-    // Check attributes of children in slots.
-    $this->assertEquals('https://example.com/news/1', $slots['teasers'][0]['content']->getAttribute('href'), 'First child in teasers slot href.');
-    $this->assertEquals('https://example.com/news/2', $slots['teasers'][1]['content']->getAttribute('href'), 'Second child in teasers slot href.');
-    $this->assertEquals('Read more news', $slots['footer'][0]['content']->getAttribute('content'), 'Child in footer slot.');
-
-    $output = $this->renderCustomElement($this->customElement);
-    $this->assertStringContainsString('<teaser-listing', $output, 'Contains the teaser-listing opening tag.');
-    $this->assertStringContainsString('title="Latest news"', $output, 'Contains the correct title attribute.');
-    $this->assertStringContainsString('icon="news"', $output, 'Contains the correct icon attribute.');
-    $this->assertStringContainsString('<article-teaser', $output, 'Contains multiple article-teaser elements.');
-    $this->assertStringContainsString('href="https://example.com/news/1"', $output, 'Contains the first href attribute.');
-    $this->assertStringContainsString('href="https://example.com/news/2"', $output, 'Contains the second href attribute.');
-    $this->assertStringContainsString('<footer', $output, 'Contains the footer element.');
-
-    // Normalize to array first.
-    $decodedJson = $this->normalizer->normalize($this->customElement);
-
-    // Assertions for JSON content.
-    $this->assertIsArray($decodedJson, 'JSON output is an array.');
-    $this->assertArrayHasKey('element', $decodedJson, 'JSON contains element key.');
-    $this->assertEquals('teaser-listing', $decodedJson['element'], 'Correct element name in JSON.');
-    $this->assertEquals('Latest news', $decodedJson['title'], 'Correct title attribute in JSON.');
-    $this->assertEquals('news', $decodedJson['icon'], 'Correct icon attribute in JSON.');
-    $this->assertArrayHasKey('teasers', $decodedJson, 'JSON contains teasers slot.');
-    // This test to be debugged, as it fails.
-    // @todo Uncomment when the cause problem is found.
-    // $this->assertCount(2, $decodedJson['teasers'],
-    // 'Two children in teasers slot in JSON.');.
-    $this->assertArrayHasKey('footer', $decodedJson, 'JSON contains footer slot.');
-    $this->assertCount(1, $decodedJson['footer'], 'One child in footer slot in JSON.');
+    $this->assertCount(
+      2,
+      $slots['teasers'],
+      'Main content slot should contain all teasers'
+    );
+    $this->assertCount(
+      1,
+      $slots['footer'],
+      'Footer slot should contain single element'
+    );
+
+    // Verify HTML rendering.
+    $output = $this->renderCustomElementToString($this->customElement);
+    $this->assertHtmlEquals(
+      '<teaser-listing title="Latest news" icon="news">' .
+      '<article-teaser href="https://example.com/news/1" excerpt="Top story" slot="teasers"></article-teaser>' .
+      '<article-teaser href="https://example.com/news/2" excerpt="Secondary story" slot="teasers"></article-teaser>' .
+      '<content-footer text="More news" slot="footer"></content-footer>' .
+      '</teaser-listing>',
+      $output
+    );
+
+    // Verify JSON normalization.
+    $normalized = $this->normalizer->normalize($this->customElement);
+    $this->assertEquals(
+      [
+        'element' => 'teaser-listing',
+        'title' => 'Latest news',
+        'icon' => 'news',
+        'teasers' => [
+          ['element' => 'article-teaser', 'href' => 'https://example.com/news/1', 'excerpt' => 'Top story'],
+          ['element' => 'article-teaser', 'href' => 'https://example.com/news/2', 'excerpt' => 'Secondary story'],
+        ],
+        'footer' => ['element' => 'content-footer', 'text' => 'More news'],
+      ],
+      $normalized,
+      'Normalized JSON should maintain separate slot structures'
+    );
   }
 
 }
-- 
GitLab


From f5ee59b8c561818ce4732017dcd5c5c56ccdc027 Mon Sep 17 00:00:00 2001
From: benellefimostfa <benellefimostfa@gmail.com>
Date: Sun, 26 Jan 2025 21:10:10 +0100
Subject: [PATCH 3/6] Reformat Custom Element test and add setSlot addSlot
 comparison test

---
 tests/src/Kernel/CustomElementTest.php | 239 ++++++++++++++-----------
 1 file changed, 138 insertions(+), 101 deletions(-)

diff --git a/tests/src/Kernel/CustomElementTest.php b/tests/src/Kernel/CustomElementTest.php
index 3acdbd8..c09f3eb 100644
--- a/tests/src/Kernel/CustomElementTest.php
+++ b/tests/src/Kernel/CustomElementTest.php
@@ -21,6 +21,8 @@ class CustomElementTest extends KernelTestBase {
 
   /**
    * The main custom element instance under test.
+   *
+   * @var \Drupal\custom_elements\CustomElement
    */
   protected CustomElement $customElement;
 
@@ -56,11 +58,11 @@ class CustomElementTest extends KernelTestBase {
   /**
    * Asserts two HTML strings are equivalent after normalization.
    */
-  protected function assertHtmlEquals(string $expected, string $actual): void {
+  protected function assertHtmlEquals(string $expected, string $actual, string $message = ''): void {
     $this->assertSame(
       $this->normalizeHtmlWhitespace($expected),
       $this->normalizeHtmlWhitespace($actual),
-      'Rendered HTML should match expected output'
+      $message ?: 'Rendered HTML should match expected output'
     );
   }
 
@@ -72,6 +74,24 @@ class CustomElementTest extends KernelTestBase {
     return preg_replace("/> +</", "><", $html);
   }
 
+  /**
+   * Tests rendering for both Vue versions.
+   */
+  protected function runVueVersionTests(CustomElement $element, array $expected): void {
+    foreach (['vue-2' => FALSE, 'vue-3' => TRUE] as $version => $isVue3) {
+      $this->config('custom_elements.settings')->set('markup_style', $version)->save();
+
+      $output = $this->renderCustomElementToString($element);
+      $expectedHtml = $isVue3 ? $expected['vue3'] : $expected['vue2'];
+
+      $this->assertHtmlEquals(
+        $expectedHtml,
+        $output,
+        "HTML output mismatch for $version rendering"
+      );
+    }
+  }
+
   /* -------------------------------------------------------------------------
    *  Test Cases
    * ---------------------------------------------------------------------- */
@@ -81,14 +101,15 @@ class CustomElementTest extends KernelTestBase {
    */
   public function testBasicElementWithAttributesWithoutChildrenOrSlots(): void {
     // Configure element with attributes.
-    $this->customElement->setAttribute('title', 'Latest news')
-      ->setAttribute('icon', 'news');
+    $this->customElement
+      ->setAttribute('title', 'Latest news')
+      ->setAttribute('tags', ['news', 'breaking']);
 
     // Verify element configuration.
     $this->assertEquals(
       'teaser-listing',
       $this->customElement->getTag(),
-      'Tag name should match configured value'
+      'Element tag name should match constructor value'
     );
     $this->assertEquals(
       'Latest news',
@@ -96,21 +117,20 @@ class CustomElementTest extends KernelTestBase {
       'Title attribute should be stored correctly'
     );
     $this->assertEquals(
-      'news',
-      $this->customElement->getAttribute('icon'),
-      'Icon attribute should be stored correctly'
+      ['news', 'breaking'],
+      $this->customElement->getAttribute('tags'),
+      'Array attributes should be stored correctly'
     );
     $this->assertEmpty(
       $this->customElement->getSlots(),
       'New element should not have any slots'
     );
 
-    // Verify HTML rendering.
-    $output = $this->renderCustomElementToString($this->customElement);
-    $this->assertHtmlEquals(
-      '<teaser-listing title="Latest news" icon="news"></teaser-listing>',
-      $output
-    );
+    // Test Vue version rendering differences.
+    $this->runVueVersionTests($this->customElement, [
+      'vue2' => '<teaser-listing title="Latest news" tags="[&quot;news&quot;,&quot;breaking&quot;]"></teaser-listing>',
+      'vue3' => '<teaser-listing title="Latest news" :tags="[&quot;news&quot;,&quot;breaking&quot;]"></teaser-listing>',
+    ]);
 
     // Verify JSON normalization.
     $normalized = $this->normalizer->normalize($this->customElement);
@@ -118,10 +138,10 @@ class CustomElementTest extends KernelTestBase {
       [
         'element' => 'teaser-listing',
         'title' => 'Latest news',
-        'icon' => 'news',
+        'tags' => ['news', 'breaking'],
       ],
       $normalized,
-      'Normalized JSON structure should match expected format'
+      'Normalized JSON should match expected structure'
     );
   }
 
@@ -129,13 +149,12 @@ class CustomElementTest extends KernelTestBase {
    * Tests element with a single custom element child in a slot.
    */
   public function testSingleSlotWithCustomElementChild(): void {
-    // Create and configure child element.
     $child = CustomElement::create('article-teaser')
       ->setAttribute('href', 'https://example.com/news/1')
       ->setAttribute('excerpt', 'Breaking news');
 
-    // Configure parent element.
-    $this->customElement->setAttribute('title', 'Latest news')
+    $this->customElement
+      ->setAttribute('title', 'Latest news')
       ->setAttribute('icon', 'news')
       ->setSlot('teasers', $child);
 
@@ -144,7 +163,7 @@ class CustomElementTest extends KernelTestBase {
     $this->assertArrayHasKey(
       'teasers',
       $slots,
-      'Should have a slot with the specified name'
+      'Element should have slot with specified name'
     );
     $this->assertCount(
       1,
@@ -162,16 +181,17 @@ class CustomElementTest extends KernelTestBase {
       'Child element should maintain configured attributes'
     );
 
-    // Verify HTML rendering.
-    $output = $this->renderCustomElementToString($this->customElement);
-
-    // slot="teasers" is added by the render process.
-    $this->assertHtmlEquals(
-      '<teaser-listing title="Latest news" icon="news">' .
-      '<article-teaser href="https://example.com/news/1" excerpt="Breaking news" slot="teasers"></article-teaser>' .
-      '</teaser-listing>',
-      $output
-    );
+    // Test Vue version rendering differences.
+    $this->runVueVersionTests($this->customElement, [
+      'vue2' => '<teaser-listing title="Latest news" icon="news">
+    <article-teaser href="https://example.com/news/1" excerpt="Breaking news" slot="teasers"></article-teaser>
+  </teaser-listing>',
+      'vue3' => '<teaser-listing title="Latest news" icon="news">
+    <template #teasers>
+      <article-teaser href="https://example.com/news/1" excerpt="Breaking news" slot="teasers"></article-teaser>
+    </template>
+  </teaser-listing>',
+    ]);
 
     // Verify JSON normalization.
     $normalized = $this->normalizer->normalize($this->customElement);
@@ -195,9 +215,9 @@ class CustomElementTest extends KernelTestBase {
    * Tests element with raw HTML content in a slot.
    */
   public function testSlotWithHtmlContent(): void {
-    // Configure element with HTML content.
     $markup = '<p>Introduction content</p>';
-    $this->customElement->setAttribute('title', 'Latest news')
+    $this->customElement
+      ->setAttribute('title', 'Latest news')
       ->setAttribute('icon', 'news')
       ->setSlot('introduction', $markup);
 
@@ -206,7 +226,7 @@ class CustomElementTest extends KernelTestBase {
     $this->assertArrayHasKey(
       'introduction',
       $slots,
-      'Should have a slot for HTML content'
+      'Element should have slot for HTML content'
     );
     $this->assertCount(
       1,
@@ -216,17 +236,23 @@ class CustomElementTest extends KernelTestBase {
     $this->assertEquals(
       $markup,
       (string) $slots['introduction'][0]['content'],
-      'Slot should preserve raw HTML content'
+      'Slot content should preserve raw HTML'
     );
 
-    // Verify HTML rendering.
-    $output = $this->renderCustomElementToString($this->customElement);
-    $this->assertHtmlEquals(
-      '<teaser-listing title="Latest news" icon="news">' .
-      '<div slot="introduction"><p>Introduction content</p></div>' .
-      '</teaser-listing>',
-      $output
-    );
+    // Test Vue2 version rendering.
+    $expectedVue2Html = '<teaser-listing title="Latest news" icon="news">'
+      . '<div slot="introduction"><p>Introduction content</p></div>'
+      . '</teaser-listing>';
+
+    // Test Vue3 version rendering.
+    $expectedVue3Html = '<teaser-listing title="Latest news" icon="news">'
+      . '<template #introduction><p>Introduction content</p></template>'
+      . '</teaser-listing>';
+
+    $this->runVueVersionTests($this->customElement, [
+      'vue2' => $expectedVue2Html,
+      'vue3' => $expectedVue3Html,
+    ]);
 
     // Verify JSON normalization.
     $normalized = $this->normalizer->normalize($this->customElement);
@@ -243,63 +269,67 @@ class CustomElementTest extends KernelTestBase {
   }
 
   /**
-   * Tests slot containing multiple custom elements added in different ways.
+   * Tests slot operations with different method call orders.
    */
-  public function testSingleSlotWithMultipleCustomElements(): void {
+  public function testSlotOperationsWithMultipleChildren(): void {
     // Create test elements.
-    $teaserItems = [
-      CustomElement::create('article-teaser')
-        ->setAttribute('href', 'https://example.com/news/1')
-        ->setAttribute('excerpt', 'First item'),
-      CustomElement::create('article-teaser')
-        ->setAttribute('href', 'https://example.com/news/2')
-        ->setAttribute('excerpt', 'Second item'),
-      CustomElement::create('article-teaser')
-        ->setAttribute('href', 'https://example.com/news/3')
-        ->setAttribute('excerpt', 'Third item'),
-    ];
+    $child1 = CustomElement::create('test-element')->setAttribute('id', '1');
+    $child2 = CustomElement::create('test-element')->setAttribute('id', '2');
+    $child3 = CustomElement::create('test-element')->setAttribute('id', '3');
+
+    // Scenario 1: setSlot -> addSlot -> addSlot.
+    $element1 = CustomElement::create('test-container');
+    $element1->setSlot('main', $child1)
+      ->addSlot('main', $child2)
+      ->addSlot('main', $child3);
+
+    // Scenario 2: addSlot -> addSlot -> addSlot.
+    $element2 = CustomElement::create('test-container');
+    $element2->addSlot('main', $child1)
+      ->addSlot('main', $child2)
+      ->addSlot('main', $child3);
+
+    // Verify slot content counts.
+    $this->assertCount(3, $element1->getSlots()['main'], 'setSlot sequence should have 3 elements');
+    $this->assertCount(3, $element2->getSlots()['main'], 'addSlot sequence should have 3 elements');
+
+    // Expected HTML output.
+    $expectedHtml = '<test-container>
+  <test-element id="1" slot="main"></test-element>
+  <test-element id="2" slot="main"></test-element>
+  <test-element id="3" slot="main"></test-element>
+</test-container>';
 
-    // Configure parent element with multiple additions.
-    $this->customElement->setAttribute('title', 'Latest news')
-      ->setAttribute('icon', 'news')
-      ->addSlot('teasers', $teaserItems[0])
-      ->addSlotFromNestedElements('teasers', [$teaserItems[1], $teaserItems[2]]);
+    // Verify HTML rendering.
+    $output1 = $this->renderCustomElementToString($element1);
+    $this->assertHtmlEquals($expectedHtml, $output1, 'HTML mismatch for setSlot sequence');
 
-    // Verify slot configuration.
-    $slots = $this->customElement->getSlots();
-    $this->assertCount(
-      3,
-      $slots['teasers'],
-      'Slot should contain all added elements'
-    );
+    $output2 = $this->renderCustomElementToString($element2);
+    $this->assertHtmlEquals($expectedHtml, $output2, 'HTML mismatch for addSlot sequence');
 
-    // Verify HTML rendering.
-    $output = $this->renderCustomElementToString($this->customElement);
-    $this->assertHtmlEquals(
-      '<teaser-listing title="Latest news" icon="news">' .
-      '<article-teaser href="https://example.com/news/1" excerpt="First item" slot="teasers"></article-teaser>' .
-      '<article-teaser href="https://example.com/news/2" excerpt="Second item" slot="teasers"></article-teaser>' .
-      '<article-teaser href="https://example.com/news/3" excerpt="Third item" slot="teasers"></article-teaser>' .
-      '</teaser-listing>',
-      $output
-    );
+    // Verify JSON normalization.
+    // AddSlot after SetSlot resets the slot array.
+    // Check https://www.drupal.org/project/custom_elements/issues/3501417.
+    $expectedSetSlotJson = [
+      'element' => 'test-container',
+      'main' => [
+        'element' => 'test-element',
+        'id' => '1',
+      ],
+    ];
 
     // Verify JSON normalization.
-    $normalized = $this->normalizer->normalize($this->customElement);
-    $this->assertEquals(
-      [
-        'element' => 'teaser-listing',
-        'title' => 'Latest news',
-        'icon' => 'news',
-        'teasers' => [
-          ['element' => 'article-teaser', 'href' => 'https://example.com/news/1', 'excerpt' => 'First item'],
-          ['element' => 'article-teaser', 'href' => 'https://example.com/news/2', 'excerpt' => 'Second item'],
-          ['element' => 'article-teaser', 'href' => 'https://example.com/news/3', 'excerpt' => 'Third item'],
-        ],
+    $expectedAddSlotJson = [
+      'element' => 'test-container',
+      'main' => [
+        ['element' => 'test-element', 'id' => '1'],
+        ['element' => 'test-element', 'id' => '2'],
+        ['element' => 'test-element', 'id' => '3'],
       ],
-      $normalized,
-      'Normalized JSON should maintain element order and structure'
-    );
+    ];
+
+    $this->assertEquals($expectedSetSlotJson, $this->normalizer->normalize($element1), 'JSON mismatch for setSlot sequence');
+    $this->assertEquals($expectedAddSlotJson, $this->normalizer->normalize($element2), 'JSON mismatch for addSlot sequence');
   }
 
   /**
@@ -339,16 +369,23 @@ class CustomElementTest extends KernelTestBase {
       'Footer slot should contain single element'
     );
 
-    // Verify HTML rendering.
-    $output = $this->renderCustomElementToString($this->customElement);
-    $this->assertHtmlEquals(
-      '<teaser-listing title="Latest news" icon="news">' .
-      '<article-teaser href="https://example.com/news/1" excerpt="Top story" slot="teasers"></article-teaser>' .
-      '<article-teaser href="https://example.com/news/2" excerpt="Secondary story" slot="teasers"></article-teaser>' .
-      '<content-footer text="More news" slot="footer"></content-footer>' .
-      '</teaser-listing>',
-      $output
-    );
+    // Test Vue version rendering differences.
+    $this->runVueVersionTests($this->customElement, [
+      'vue2' => '<teaser-listing title="Latest news" icon="news">
+    <article-teaser href="https://example.com/news/1" excerpt="Top story" slot="teasers"></article-teaser>
+    <article-teaser href="https://example.com/news/2" excerpt="Secondary story" slot="teasers"></article-teaser>
+    <content-footer text="More news" slot="footer"></content-footer>
+  </teaser-listing>',
+      'vue3' => '<teaser-listing title="Latest news" icon="news">
+    <template #teasers>
+      <article-teaser href="https://example.com/news/1" excerpt="Top story" slot="teasers"></article-teaser>
+      <article-teaser href="https://example.com/news/2" excerpt="Secondary story" slot="teasers"></article-teaser>
+    </template>
+    <template #footer>
+      <content-footer text="More news" slot="footer"></content-footer>
+    </template>
+  </teaser-listing>',
+    ]);
 
     // Verify JSON normalization.
     $normalized = $this->normalizer->normalize($this->customElement);
-- 
GitLab


From db0be232cb4345f19e93dab62e0a645fa7f1c416 Mon Sep 17 00:00:00 2001
From: Roderik Muit <roderik.muit@drunomics.com>
Date: Mon, 27 Jan 2025 18:20:55 +0100
Subject: [PATCH 4/6] Extend setSlot() vs addSlot() tests.

---
 tests/src/Kernel/CustomElementTest.php | 261 ++++++++++++++-----------
 1 file changed, 151 insertions(+), 110 deletions(-)

diff --git a/tests/src/Kernel/CustomElementTest.php b/tests/src/Kernel/CustomElementTest.php
index c09f3eb..b781f11 100644
--- a/tests/src/Kernel/CustomElementTest.php
+++ b/tests/src/Kernel/CustomElementTest.php
@@ -153,36 +153,52 @@ class CustomElementTest extends KernelTestBase {
       ->setAttribute('href', 'https://example.com/news/1')
       ->setAttribute('excerpt', 'Breaking news');
 
-    $this->customElement
+    // setSlot() without $index argument explicity marks the slot as having a
+    // single value. addSlot() or setSlot() with an index do not do this,
+    // meaning the slot stays an array value.
+    $random_index = 5;
+    $element1 = CustomElement::create('teaser-listing')
       ->setAttribute('title', 'Latest news')
       ->setAttribute('icon', 'news')
       ->setSlot('teasers', $child);
+    $element2 = CustomElement::create('teaser-listing')
+      ->setAttribute('title', 'Latest news')
+      ->setAttribute('icon', 'news')
+      ->setSlot('teasers', $child, 'div', [], $random_index);
+    $element3 = CustomElement::create('teaser-listing')
+      ->setAttribute('title', 'Latest news')
+      ->setAttribute('icon', 'news')
+      ->addSlot('teasers', $child);
 
     // Verify slot configuration.
-    $slots = $this->customElement->getSlots();
-    $this->assertArrayHasKey(
-      'teasers',
-      $slots,
-      'Element should have slot with specified name'
-    );
-    $this->assertCount(
-      1,
-      $slots['teasers'],
-      'Slot should contain exactly one entry'
-    );
-    $this->assertInstanceOf(
-      CustomElement::class,
-      $slots['teasers'][0]['content'],
-      'Slot content should be a CustomElement instance'
-    );
-    $this->assertEquals(
-      'Breaking news',
-      $slots['teasers'][0]['content']->getAttribute('excerpt'),
-      'Child element should maintain configured attributes'
-    );
+    foreach ([$element1, $element2, $element3] as $index => $element) {
+      // Quickly hardcoded: only $element2 has index 5 populated.
+      $slot_index = $index == 1 ? $random_index : 0;
+      $slots = $element->getSlots();
+      $this->assertArrayHasKey(
+        'teasers',
+        $slots,
+        'Element should have slot with specified name'
+      );
+      $this->assertCount(
+        1,
+        $slots['teasers'],
+        'Slot should contain exactly one entry'
+      );
+      $this->assertInstanceOf(
+        CustomElement::class,
+        $slots['teasers'][$slot_index]['content'],
+        'Slot content should be a CustomElement instance'
+      );
+      $this->assertEquals(
+        'Breaking news',
+        $slots['teasers'][$slot_index]['content']->getAttribute('excerpt'),
+        'Child element should maintain configured attributes'
+      );
+    }
 
-    // Test Vue version rendering differences.
-    $this->runVueVersionTests($this->customElement, [
+    // Markup output does not differ for 'single value' slots.
+    $expected = [
       'vue2' => '<teaser-listing title="Latest news" icon="news">
     <article-teaser href="https://example.com/news/1" excerpt="Breaking news" slot="teasers"></article-teaser>
   </teaser-listing>',
@@ -191,24 +207,37 @@ class CustomElementTest extends KernelTestBase {
       <article-teaser href="https://example.com/news/1" excerpt="Breaking news" slot="teasers"></article-teaser>
     </template>
   </teaser-listing>',
-    ]);
-
-    // Verify JSON normalization.
-    $normalized = $this->normalizer->normalize($this->customElement);
-    $this->assertEquals(
-      [
-        'element' => 'teaser-listing',
-        'title' => 'Latest news',
-        'icon' => 'news',
-        'teasers' => [
+    ];
+    $this->runVueVersionTests($element1, $expected);
+    $this->runVueVersionTests($element2, $expected);
+    $this->runVueVersionTests($element3, $expected);
+
+    // JSON output differs for 'single value' slots.
+    $singleValueTeaser = [
+      'element' => 'teaser-listing',
+      'title' => 'Latest news',
+      'icon' => 'news',
+      'teasers' => [
+        'element' => 'article-teaser',
+        'href' => 'https://example.com/news/1',
+        'excerpt' => 'Breaking news',
+      ],
+    ];
+    $arrayTeaser = [
+      'element' => 'teaser-listing',
+      'title' => 'Latest news',
+      'icon' => 'news',
+      'teasers' => [
+        [
           'element' => 'article-teaser',
           'href' => 'https://example.com/news/1',
           'excerpt' => 'Breaking news',
         ],
       ],
-      $normalized,
-      'Normalized JSON should preserve nested element structure'
-    );
+    ];
+    $this->assertEquals($singleValueTeaser, $this->normalizer->normalize($element1), 'Normalized JSON should preserve nested element structure');
+    $this->assertEquals($arrayTeaser, $this->normalizer->normalize($element2), 'Normalized JSON should preserve nested element structure');
+    $this->assertEquals($arrayTeaser, $this->normalizer->normalize($element3), 'Normalized JSON should preserve nested element structure');
   }
 
   /**
@@ -216,56 +245,73 @@ class CustomElementTest extends KernelTestBase {
    */
   public function testSlotWithHtmlContent(): void {
     $markup = '<p>Introduction content</p>';
-    $this->customElement
+
+    // Test addSlot() vs setSlot() with similar setup as above.
+    $random_index = 5;
+    $element1 = CustomElement::create('teaser-listing')
       ->setAttribute('title', 'Latest news')
       ->setAttribute('icon', 'news')
       ->setSlot('introduction', $markup);
+    $element2 = CustomElement::create('teaser-listing')
+      ->setAttribute('title', 'Latest news')
+      ->setAttribute('icon', 'news')
+      ->setSlot('introduction', $markup, 'div', [], $random_index);
+    $element3 = CustomElement::create('teaser-listing')
+      ->setAttribute('title', 'Latest news')
+      ->setAttribute('icon', 'news')
+      ->addSlot('introduction', $markup);
 
     // Verify slot configuration.
-    $slots = $this->customElement->getSlots();
-    $this->assertArrayHasKey(
-      'introduction',
-      $slots,
-      'Element should have slot for HTML content'
-    );
-    $this->assertCount(
-      1,
-      $slots['introduction'],
-      'HTML slot should contain single entry'
-    );
-    $this->assertEquals(
-      $markup,
-      (string) $slots['introduction'][0]['content'],
-      'Slot content should preserve raw HTML'
-    );
-
-    // Test Vue2 version rendering.
-    $expectedVue2Html = '<teaser-listing title="Latest news" icon="news">'
-      . '<div slot="introduction"><p>Introduction content</p></div>'
-      . '</teaser-listing>';
-
-    // Test Vue3 version rendering.
-    $expectedVue3Html = '<teaser-listing title="Latest news" icon="news">'
-      . '<template #introduction><p>Introduction content</p></template>'
-      . '</teaser-listing>';
-
-    $this->runVueVersionTests($this->customElement, [
-      'vue2' => $expectedVue2Html,
-      'vue3' => $expectedVue3Html,
-    ]);
+    foreach ([$element1, $element2, $element3] as $index => $element) {
+      // Quickly hardcoded: only $element2 has index 5 populated.
+      $slot_index = $index == 1 ? $random_index : 0;
+      $slots = $element->getSlots();
+      $this->assertArrayHasKey(
+        'introduction',
+        $slots,
+        'Element should have slot for HTML content'
+      );
+      $this->assertCount(
+        1,
+        $slots['introduction'],
+        'HTML slot should contain single entry'
+      );
+      $this->assertEquals(
+        $markup,
+        (string) $slots['introduction'][$slot_index]['content'],
+        'Slot content should preserve raw HTML'
+      );
+    }
 
-    // Verify JSON normalization.
-    $normalized = $this->normalizer->normalize($this->customElement);
-    $this->assertEquals(
-      [
-        'element' => 'teaser-listing',
-        'title' => 'Latest news',
-        'icon' => 'news',
-        'introduction' => $markup,
-      ],
-      $normalized,
-      'Normalized JSON should include raw HTML content'
-    );
+    // Markup output does not differ for 'single value' slots.
+    $expected = [
+      'vue2' => '<teaser-listing title="Latest news" icon="news">'
+        . '<div slot="introduction"><p>Introduction content</p></div>'
+        . '</teaser-listing>',
+      'vue3' => '<teaser-listing title="Latest news" icon="news">'
+        . '<template #introduction><p>Introduction content</p></template>'
+        . '</teaser-listing>',
+    ];
+    $this->runVueVersionTests($element1, $expected);
+    $this->runVueVersionTests($element2, $expected);
+    $this->runVueVersionTests($element3, $expected);
+
+    // JSON output differs for 'single value' slots.
+    $singleValueIntro = [
+      'element' => 'teaser-listing',
+      'title' => 'Latest news',
+      'icon' => 'news',
+      'introduction' => $markup,
+    ];
+    $arrayIntro = [
+      'element' => 'teaser-listing',
+      'title' => 'Latest news',
+      'icon' => 'news',
+      'introduction' => [$markup],
+    ];
+    $this->assertEquals($singleValueIntro, $this->normalizer->normalize($element1), 'Normalized JSON should include raw HTML content');
+    $this->assertEquals($arrayIntro, $this->normalizer->normalize($element2), 'Normalized JSON should include raw HTML content');
+    $this->assertEquals($arrayIntro, $this->normalizer->normalize($element3), 'Normalized JSON should include raw HTML content');
   }
 
   /**
@@ -277,15 +323,14 @@ class CustomElementTest extends KernelTestBase {
     $child2 = CustomElement::create('test-element')->setAttribute('id', '2');
     $child3 = CustomElement::create('test-element')->setAttribute('id', '3');
 
-    // Scenario 1: setSlot -> addSlot -> addSlot.
     $element1 = CustomElement::create('test-container');
-    $element1->setSlot('main', $child1)
+    $element1->addSlot('main', $child1)
       ->addSlot('main', $child2)
       ->addSlot('main', $child3);
-
-    // Scenario 2: addSlot -> addSlot -> addSlot.
+    // Test setSlot() without $index, combined with other addSlot() / setSlot()
+    // calls; see behavior below.
     $element2 = CustomElement::create('test-container');
-    $element2->addSlot('main', $child1)
+    $element2->setSlot('main', $child1)
       ->addSlot('main', $child2)
       ->addSlot('main', $child3);
 
@@ -293,33 +338,30 @@ class CustomElementTest extends KernelTestBase {
     $this->assertCount(3, $element1->getSlots()['main'], 'setSlot sequence should have 3 elements');
     $this->assertCount(3, $element2->getSlots()['main'], 'addSlot sequence should have 3 elements');
 
-    // Expected HTML output.
-    $expectedHtml = '<test-container>
+    // Markup output does not differ for 'single value' slots.
+    $expected = [
+      'vue2' => '<test-container>
   <test-element id="1" slot="main"></test-element>
   <test-element id="2" slot="main"></test-element>
   <test-element id="3" slot="main"></test-element>
-</test-container>';
-
-    // Verify HTML rendering.
-    $output1 = $this->renderCustomElementToString($element1);
-    $this->assertHtmlEquals($expectedHtml, $output1, 'HTML mismatch for setSlot sequence');
-
-    $output2 = $this->renderCustomElementToString($element2);
-    $this->assertHtmlEquals($expectedHtml, $output2, 'HTML mismatch for addSlot sequence');
+</test-container>',
+      'vue3' => '<test-container>
+  <template #main>
+    <test-element id="1" slot="main"></test-element>
+    <test-element id="2" slot="main"></test-element>
+    <test-element id="3" slot="main"></test-element>
+  </template>
+</test-container>',
+    ];
+    $this->runVueVersionTests($element1, $expected);
 
-    // Verify JSON normalization.
-    // AddSlot after SetSlot resets the slot array.
-    // Check https://www.drupal.org/project/custom_elements/issues/3501417.
-    $expectedSetSlotJson = [
+    // JSON output contains only the 'single value' slot; all slots added (with
+    // the same slot name) after the setSlot() are ignored.
+    $singleSlotValue = [
       'element' => 'test-container',
-      'main' => [
-        'element' => 'test-element',
-        'id' => '1',
-      ],
+      'main' => ['element' => 'test-element', 'id' => '1'],
     ];
-
-    // Verify JSON normalization.
-    $expectedAddSlotJson = [
+    $allSlotValues = [
       'element' => 'test-container',
       'main' => [
         ['element' => 'test-element', 'id' => '1'],
@@ -327,9 +369,8 @@ class CustomElementTest extends KernelTestBase {
         ['element' => 'test-element', 'id' => '3'],
       ],
     ];
-
-    $this->assertEquals($expectedSetSlotJson, $this->normalizer->normalize($element1), 'JSON mismatch for setSlot sequence');
-    $this->assertEquals($expectedAddSlotJson, $this->normalizer->normalize($element2), 'JSON mismatch for addSlot sequence');
+    $this->assertEquals($allSlotValues, $this->normalizer->normalize($element1), 'JSON mismatch');
+    $this->assertEquals($singleSlotValue, $this->normalizer->normalize($element2), 'JSON mismatch with expected single-value slot output');
   }
 
   /**
-- 
GitLab


From 33b6bb584056778a22532d0b1a4abc15515b1b29 Mon Sep 17 00:00:00 2001
From: Roderik Muit <roderik.muit@drunomics.com>
Date: Mon, 27 Jan 2025 18:32:29 +0100
Subject: [PATCH 5/6] Rename to CustomElementBaseRenderTest

---
 ...est.php => CustomElementBaseRenderTest.php} | 18 +++++++++++++++---
 1 file changed, 15 insertions(+), 3 deletions(-)
 rename tests/src/Kernel/{CustomElementTest.php => CustomElementBaseRenderTest.php} (95%)

diff --git a/tests/src/Kernel/CustomElementTest.php b/tests/src/Kernel/CustomElementBaseRenderTest.php
similarity index 95%
rename from tests/src/Kernel/CustomElementTest.php
rename to tests/src/Kernel/CustomElementBaseRenderTest.php
index b781f11..112ea74 100644
--- a/tests/src/Kernel/CustomElementTest.php
+++ b/tests/src/Kernel/CustomElementBaseRenderTest.php
@@ -7,12 +7,24 @@ use Drupal\KernelTests\KernelTestBase;
 use Drupal\custom_elements\CustomElement;
 
 /**
- * Tests the CustomElement class functionality for web components.
+ * Tests custom elements output generation directly from CustomElement classes.
+ *
+ * This is the base test that other 'render tests' can rely on. It contains
+ * - some assertions on standalone CustomElement objects, though not as
+ *   separate test methods. Since CustomElement is just a value object, this is
+ *   not the most interesting part. (These could be extracted into a separate
+ *   unit test file, if needed.)
+ * - tests for rendering CustomElement structures into all supported output
+ *   formats.
+ *
+ * JSON output needs CustomElementNormalizer, so this tests the combination of
+ * CustomElement + CustomElementNormalizer classes.
+ *
+ * Not all details of CustomElementNormalizer have test coverage yet.
  *
- * @coversDefaultClass \Drupal\custom_elements\CustomElement
  * @group custom_elements
  */
-class CustomElementTest extends KernelTestBase {
+class CustomElementBaseRenderTest extends KernelTestBase {
 
   /**
    * {@inheritdoc}
-- 
GitLab


From 48f6435c27accfbc34581af9e92fdb26d7770eef Mon Sep 17 00:00:00 2001
From: Roderik Muit <roderik.muit@drunomics.com>
Date: Mon, 27 Jan 2025 18:46:24 +0100
Subject: [PATCH 6/6] phpcs

---
 tests/src/Kernel/CustomElementBaseRenderTest.php | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/tests/src/Kernel/CustomElementBaseRenderTest.php b/tests/src/Kernel/CustomElementBaseRenderTest.php
index 112ea74..fcf5b6a 100644
--- a/tests/src/Kernel/CustomElementBaseRenderTest.php
+++ b/tests/src/Kernel/CustomElementBaseRenderTest.php
@@ -41,7 +41,7 @@ class CustomElementBaseRenderTest extends KernelTestBase {
   /**
    * The custom elements normalizer service.
    *
-   * @var \Drupal\custom_elements\Normalizer\CustomElementNormalizer
+   * @var \Drupal\custom_elements\CustomElementNormalizer
    */
   protected $normalizer;
 
@@ -298,11 +298,11 @@ class CustomElementBaseRenderTest extends KernelTestBase {
     // Markup output does not differ for 'single value' slots.
     $expected = [
       'vue2' => '<teaser-listing title="Latest news" icon="news">'
-        . '<div slot="introduction"><p>Introduction content</p></div>'
-        . '</teaser-listing>',
+      . '<div slot="introduction"><p>Introduction content</p></div>'
+      . '</teaser-listing>',
       'vue3' => '<teaser-listing title="Latest news" icon="news">'
-        . '<template #introduction><p>Introduction content</p></template>'
-        . '</teaser-listing>',
+      . '<template #introduction><p>Introduction content</p></template>'
+      . '</teaser-listing>',
     ];
     $this->runVueVersionTests($element1, $expected);
     $this->runVueVersionTests($element2, $expected);
-- 
GitLab