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="["news","breaking"]"></teaser-listing>', + 'vue3' => '<teaser-listing title="Latest news" :tags="["news","breaking"]"></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