Repository: symfony/ux
Pull request #1991 (2024-07-15 14:44:43)

[Doc] Mention that boolean properties require the dynamic syntax
| Q             | A
| ------------- | ---
| Bug fix?      | no
| New feature?  | no
| Issues        | -
| License       | MIT

This confused me. On Symfony Slack folks mentioned that changing this behavior would be bad for DX (e.g. we can do it in components backend by a PHP class with typed properties but we cannot for anonymous components). So, I think it might be enough to mention this in the docs. Thanks.

Kocal: That's remind this ESLint rule for Vue.js: https://eslint.vuejs.org/rules/no-boolean-default

Ideally when you conceive your component, you don't want to use boolean props with a default value to `true`,  to enforce HTML standard. 

Depending on whether or not you want your close button to be displayed by default, you will need to have a property `withCloseButton` or `withoutCloseButton`: 
```twig
<twig:Alert message="..." withCloseButton/>

<twig:Alert message="..." withoutCloseButton />
```

Patch:
diff --git a/src/Icons/doc/index.rst b/src/Icons/doc/index.rst
index f1ee241e..8b07ee60 100644
--- a/src/Icons/doc/index.rst
+++ b/src/Icons/doc/index.rst
@@ -180,7 +180,7 @@ tag:
     <!-- you can also add any HTML attributes -->
     <twig:ux:icon name="user-profile" height="16" width="16" aria-hidden="true" />
 
-.. tip::
+.. note::
 
     To use the HTML syntax, the ``symfony/ux-twig-component`` package must be
     installed in your project.
diff --git a/src/LiveComponent/src/LiveComponentHydrator.php b/src/LiveComponent/src/LiveComponentHydrator.php
index 547106fa..4a713882 100644
--- a/src/LiveComponent/src/LiveComponentHydrator.php
+++ b/src/LiveComponent/src/LiveComponentHydrator.php
@@ -370,7 +370,6 @@ final class LiveComponentHydrator
         foreach ($parts as $part) {
             if (\is_array($currentValue)) {
                 $finalPropertyPath .= \sprintf('[%s]', $part);
-                $currentValue = $this->propertyAccessor->getValue($rawPropertyValue, $finalPropertyPath);
 
                 continue;
             }
@@ -380,10 +379,6 @@ final class LiveComponentHydrator
             }
 
             $finalPropertyPath .= $part;
-
-            if (null !== $currentValue) {
-                $currentValue = $this->propertyAccessor->getValue($rawPropertyValue, $finalPropertyPath);
-            }
         }
 
         return $finalPropertyPath;
diff --git a/src/Translator/CHANGELOG.md b/src/Translator/CHANGELOG.md
index b8d13254..62f7ae26 100644
--- a/src/Translator/CHANGELOG.md
+++ b/src/Translator/CHANGELOG.md
@@ -1,9 +1,5 @@
 # CHANGELOG
 
-## 2.19.0
-
--   Add configuration to filter dumped translations by domain.
-
 ## 2.16.0
 
 -   Increase version range of `intl-messageformat` to `^10.5.11`, in order to see
diff --git a/src/Translator/doc/index.rst b/src/Translator/doc/index.rst
index 0b36da69..011ce6ae 100644
--- a/src/Translator/doc/index.rst
+++ b/src/Translator/doc/index.rst
@@ -65,7 +65,7 @@ After installing the bundle, the following file should be created, thanks to the
 Usage
 -----
 
-When warming up the Symfony cache, your translations will be dumped as JavaScript into the ``var/translations/`` directory.
+When warming up the Symfony cache, all of your translations will be dumped as JavaScript into the ``var/translations/`` directory.
 For a better developer experience, TypeScript types definitions are also generated aside those JavaScript files.
 
 Then, you will be able to import those JavaScript translations in your assets.
@@ -75,24 +75,6 @@ Don't worry about your final bundle size, only the translations you use will be
 
     This package requires the `translator` to be enabled in your Symfony application. If you don't use the `translator` service, the warmup command will not generate any translations.
 
-Configuring the dumped translations
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-By default, all your translations will be exported. You can restrict the dumped messages by either
-including or excluding translation domains in your ``config/packages/ux_translator.yaml`` file:
-
-.. code-block:: yaml
-
-    ux_translator:
-            domains: ~    # Include all the domains
-
-            domains: foo  # Include only domain 'foo'
-            domains: '!foo' # Include all domains, except 'foo'
-
-            domains: [foo, bar]   # Include only domains 'foo' and 'bar'
-            domains: ['!foo', '!bar'] # Include all domains, except 'foo' and 'bar'
-
-
 Configuring the default locale
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
diff --git a/src/Translator/src/DependencyInjection/Configuration.php b/src/Translator/src/DependencyInjection/Configuration.php
index 5f2e31a3..485c1bdc 100644
--- a/src/Translator/src/DependencyInjection/Configuration.php
+++ b/src/Translator/src/DependencyInjection/Configuration.php
@@ -13,7 +13,6 @@ namespace Symfony\UX\Translator\DependencyInjection;
 
 use Symfony\Component\Config\Definition\Builder\TreeBuilder;
 use Symfony\Component\Config\Definition\ConfigurationInterface;
-use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
 
 /**
  * @author Hugo Alliaume <hugo@alliau.me>
@@ -29,59 +28,6 @@ class Configuration implements ConfigurationInterface
         $rootNode
             ->children()
                 ->scalarNode('dump_directory')->defaultValue('%kernel.project_dir%/var/translations')->end()
-                ->arrayNode('domains')
-                    ->info('List of domains to include/exclude from the generated translations. Prefix with a `!` to exclude a domain.')
-                    ->children()
-                        ->scalarNode('type')
-                            ->validate()
-                                ->ifNotInArray(['inclusive', 'exclusive'])
-                                ->thenInvalid('The type of domains has to be inclusive or exclusive')
-                            ->end()
-                        ->end()
-                        ->arrayNode('elements')
-                            ->scalarPrototype()->end()
-                        ->end()
-                    ->end()
-                    ->canBeUnset()
-                    ->beforeNormalization()
-                        ->ifString()
-                        ->then(fn ($v) => ['elements' => [$v]])
-                    ->end()
-                    ->beforeNormalization()
-                        ->ifTrue(function ($v) { return \is_array($v) && is_numeric(key($v)); })
-                        ->then(function ($v) { return ['elements' => $v]; })
-                    ->end()
-                    ->validate()
-                        ->always(function ($v) {
-                            $isExclusive = null;
-                            $elements = [];
-                            if (isset($v['type'])) {
-                                $isExclusive = 'exclusive' === $v['type'];
-                            }
-                            foreach ($v['elements'] as $domain) {
-                                if (str_starts_with($domain, '!')) {
-                                    if (false === $isExclusive) {
-                                        throw new InvalidConfigurationException('You cannot mix included and excluded domains.');
-                                    }
-                                    $isExclusive = true;
-                                    $elements[] = substr($domain, 1);
-                                } else {
-                                    if (true === $isExclusive) {
-                                        throw new InvalidConfigurationException('You cannot mix included and excluded domains.');
-                                    }
-                                    $isExclusive = false;
-                                    $elements[] = $domain;
-                                }
-                            }
-
-                            if (!\count($elements)) {
-                                return null;
-                            }
-
-                            return ['type' => $isExclusive ? 'exclusive' : 'inclusive', 'elements' => array_unique($elements)];
-                        })
-                    ->end()
-                ->end()
             ->end()
         ;
 
diff --git a/src/Translator/src/DependencyInjection/UxTranslatorExtension.php b/src/Translator/src/DependencyInjection/UxTranslatorExtension.php
index 9ba3a029..65a15484 100644
--- a/src/Translator/src/DependencyInjection/UxTranslatorExtension.php
+++ b/src/Translator/src/DependencyInjection/UxTranslatorExtension.php
@@ -35,15 +35,7 @@ class UxTranslatorExtension extends Extension implements PrependExtensionInterfa
         $loader = (new PhpFileLoader($container, new FileLocator(\dirname(__DIR__).'/../config')));
         $loader->load('services.php');
 
-        $dumperDefinition = $container->getDefinition('ux.translator.translations_dumper');
-        $dumperDefinition->setArgument(0, $config['dump_directory']);
-
-        if (isset($config['domains'])) {
-            $method = 'inclusive' === $config['domains']['type'] ? 'addIncludedDomain' : 'addExcludedDomain';
-            foreach ($config['domains']['elements'] as $domainName) {
-                $dumperDefinition->addMethodCall($method, [$domainName]);
-            }
-        }
+        $container->getDefinition('ux.translator.translations_dumper')->setArgument(0, $config['dump_directory']);
     }
 
     public function prepend(ContainerBuilder $container)
diff --git a/src/Translator/src/TranslationsDumper.php b/src/Translator/src/TranslationsDumper.php
index cba61117..751f4203 100644
--- a/src/Translator/src/TranslationsDumper.php
+++ b/src/Translator/src/TranslationsDumper.php
@@ -32,9 +32,6 @@ use function Symfony\Component\String\s;
  */
 class TranslationsDumper
 {
-    private array $excludedDomains = [];
-    private array $includedDomains = [];
-
     public function __construct(
         private string $dumpDir,
         private MessageParametersExtractor $messageParametersExtractor,
@@ -87,22 +84,6 @@ TS
         );
     }
 
-    public function addExcludedDomain(string $domain): void
-    {
-        if ($this->includedDomains) {
-            throw new \LogicException('You cannot set both "excluded_domains" and "included_domains" at the same time.');
-        }
-        $this->excludedDomains[] = $domain;
-    }
-
-    public function addIncludedDomain(string $domain): void
-    {
-        if ($this->excludedDomains) {
-            throw new \LogicException('You cannot set both "excluded_domains" and "included_domains" at the same time.');
-        }
-        $this->includedDomains[] = $domain;
-    }
-
     /**
      * @return array<MessageId, array<Domain, array<Locale, string>>>
      */
@@ -113,12 +94,6 @@ TS
         foreach ($catalogues as $catalogue) {
             $locale = $catalogue->getLocale();
             foreach ($catalogue->getDomains() as $domain) {
-                if (\in_array($domain, $this->excludedDomains, true)) {
-                    continue;
-                }
-                if ($this->includedDomains && !\in_array($domain, $this->includedDomains, true)) {
-                    continue;
-                }
                 foreach ($catalogue->all($domain) as $id => $message) {
                     $realDomain = $catalogue->has($id, $domain.MessageCatalogueInterface::INTL_DOMAIN_SUFFIX)
                         ? $domain.MessageCatalogueInterface::INTL_DOMAIN_SUFFIX
diff --git a/src/TwigComponent/doc/index.rst b/src/TwigComponent/doc/index.rst
index 61220b90..3ef0dd6a 100644
--- a/src/TwigComponent/doc/index.rst
+++ b/src/TwigComponent/doc/index.rst
@@ -293,7 +293,19 @@ prefix the attribute with ``:`` or use the normal ``{{ }}`` syntax:
     <twig:Alert message="hello!" user="{{ user.id }}" />
 
     // pass object, array, or anything you imagine
-    <twig:Alert :foo="{col: ['foo', 'oof']}" />
+    <twig:Alert :foo="['col' => ['foo', 'oof']]" />
+
+Boolean props require using the dynamic syntax:
+
+.. code-block:: html+twig
+
+    {# in this example, the 'false' value is passed as a string
+       (so it's converted automatically to the true boolean value) #}
+    <twig:Alert message="..." withCloseButton="false" />
+
+    {# in the following examples, the 'false' value is passed as a boolean property #}
+    <twig:Alert message="..." :withCloseButton="false" />
+    <twig:Alert message="..." withCloseButton="{{ false }}" />
 
 Don't forget that you can mix and match props with attributes that you
 want to render on the root element:
diff --git a/src/TwigComponent/src/Twig/PropsNode.php b/src/TwigComponent/src/Twig/PropsNode.php
index 97f98d51..70b35d64 100644
--- a/src/TwigComponent/src/Twig/PropsNode.php
+++ b/src/TwigComponent/src/Twig/PropsNode.php
@@ -35,10 +35,6 @@ class PropsNode extends Node
             ->write('$propsNames = [];')
         ;
 
-        if (!$this->getAttribute('names')) {
-            return;
-        }
-
         foreach ($this->getAttribute('names') as $name) {
             $compiler
                 ->write('if (isset($context[\'__props\'][\''.$name.'\'])) {')
@@ -77,23 +73,7 @@ class PropsNode extends Node
                 ->raw(";\n")
                 ->outdent()
                 ->write('}')
-                ->write("\n")
-            ;
-
-            // overwrite the context value if a props with a similar name and a default value exist
-            if ($this->hasNode($name)) {
-                $compiler
-                    ->write('if (isset($context[\'__context\'][\''.$name.'\'])) {')
-                    ->raw("\n")
-                    ->indent()
-                    ->write('$context[\''.$name.'\'] = ')
-                    ->subcompile($this->getNode($name))
-                    ->raw(";\n")
-                    ->outdent()
-                    ->write('}')
-                    ->raw("\n")
-                ;
-            }
+                ->write("\n");
         }
 
         $compiler
@@ -114,5 +94,20 @@ class PropsNode extends Node
             ->write('}')
             ->raw("\n")
         ;
+
+        // overwrite the context value if a props with a similar name and a default value exist
+        if ($this->hasNode($name)) {
+            $compiler
+                ->write('if (isset($context[\'__context\'][\''.$name.'\'])) {')
+                ->raw("\n")
+                ->indent()
+                ->write('$context[\''.$name.'\'] = ')
+                ->subcompile($this->getNode($name))
+                ->raw(";\n")
+                ->outdent()
+                ->write('}')
+                ->raw("\n")
+            ;
+        }
     }
 }
diff --git a/ux.symfony.com/.php-cs-fixer.dist.php b/ux.symfony.com/.php-cs-fixer.dist.php
index 840416ce..c5a5e790 100644
--- a/ux.symfony.com/.php-cs-fixer.dist.php
+++ b/ux.symfony.com/.php-cs-fixer.dist.php
@@ -23,8 +23,6 @@ file that was distributed with this source code.
 EOF;
 
 return (new PhpCsFixer\Config())
-    // @see https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/pull/7777
-    ->setParallelConfig(PhpCsFixer\Runner\Parallel\ParallelConfigFactory::detect())
     ->setRules([
         '@PHPUnit75Migration:risky' => true,
         '@Symfony' => true,


Test patch:
diff --git a/src/LiveComponent/tests/Fixtures/Dto/HoldsArrayOfDtos.php b/src/LiveComponent/tests/Fixtures/Dto/HoldsArrayOfDtos.php
deleted file mode 100644
index 13eaec51..00000000
--- a/src/LiveComponent/tests/Fixtures/Dto/HoldsArrayOfDtos.php
+++ /dev/null
@@ -1,13 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace Symfony\UX\LiveComponent\Tests\Fixtures\Dto;
-
-final class HoldsArrayOfDtos
-{
-    /**
-     * @var Address[] $addresses
-     */
-    public array $addresses;
-}
diff --git a/src/LiveComponent/tests/Integration/LiveComponentHydratorTest.php b/src/LiveComponent/tests/Integration/LiveComponentHydratorTest.php
index 2441146f..fd01de4c 100644
--- a/src/LiveComponent/tests/Integration/LiveComponentHydratorTest.php
+++ b/src/LiveComponent/tests/Integration/LiveComponentHydratorTest.php
@@ -23,7 +23,6 @@ use Symfony\UX\LiveComponent\Tests\Fixtures\Dto\Address;
 use Symfony\UX\LiveComponent\Tests\Fixtures\Dto\BlogPostWithSerializationContext;
 use Symfony\UX\LiveComponent\Tests\Fixtures\Dto\CustomerDetails;
 use Symfony\UX\LiveComponent\Tests\Fixtures\Dto\Embeddable2;
-use Symfony\UX\LiveComponent\Tests\Fixtures\Dto\HoldsArrayOfDtos;
 use Symfony\UX\LiveComponent\Tests\Fixtures\Dto\Money;
 use Symfony\UX\LiveComponent\Tests\Fixtures\Dto\ParentDTO;
 use Symfony\UX\LiveComponent\Tests\Fixtures\Dto\SimpleDto;
@@ -969,127 +968,6 @@ final class LiveComponentHydratorTest extends KernelTestCase
                 });
         }];
 
-        yield 'Array with DTOs: fully writable allows anything to change' => [function () {
-            $address1 = create(Address::class, ['address' => '17 Arcadia Road', 'city' => 'London'])->object();
-            $address2 = create(Address::class, ['address' => '4 Privet Drive', 'city' => 'Little Whinging'])->object();
-            $address3 = create(Address::class, ['address' => '124 Conch St.', 'city' => 'Bikini Bottom'])->object();
-            $address4 = create(Address::class, ['address' => '32 Windsor Gardens', 'city' => 'London'])->object();
-
-            return HydrationTest::create(new class() {
-                /**
-                 * @var Symfony\UX\LiveComponent\Tests\Fixtures\Dto\Address[]
-                 */
-                #[LiveProp(writable: true, useSerializerForHydration: true)]
-                public array $addresses = [];
-            })
-                ->mountWith(['addresses' => [$address1, $address2]])
-                ->assertDehydratesTo(['addresses' => [
-                    [
-                        'address' => '17 Arcadia Road',
-                        'city' => 'London',
-                    ],
-                    [
-                        'address' => '4 Privet Drive',
-                        'city' => 'Little Whinging',
-                    ],
-                ]])
-                ->userUpdatesProps(['addresses' => [$address3, $address4]])
-                ->assertObjectAfterHydration(function (object $object) {
-                    self::assertEquals([
-                        create(Address::class, ['address' => '124 Conch St.', 'city' => 'Bikini Bottom'])->object(),
-                        create(Address::class, ['address' => '32 Windsor Gardens', 'city' => 'London'])->object(),
-                    ], $object->addresses);
-                });
-        }];
-
-        yield 'Array with DTOs: fully writable allows partial changes' => [function () {
-            $address1 = create(Address::class, ['address' => '1600 Pennsylvania Avenue', 'city' => 'Washington DC'])->object();
-            $address2 = create(Address::class, ['address' => '221 B Baker St', 'city' => 'Birmingham'])->object();
-
-            return HydrationTest::create(new class() {
-                /**
-                 * @var Symfony\UX\LiveComponent\Tests\Fixtures\Dto\Address[]
-                 */
-                #[LiveProp(writable: true, useSerializerForHydration: true)]
-                public array $addresses = [];
-            })
-                ->mountWith(['addresses' => [$address1, $address2]])
-                ->assertDehydratesTo(['addresses' => [
-                    [
-                        'address' => '1600 Pennsylvania Avenue',
-                        'city' => 'Washington DC',
-                    ],
-                    [
-                        'address' => '221 B Baker St',
-                        'city' => 'Birmingham',
-                    ],
-                ]])
-                ->userUpdatesProps(['addresses.1.city' => 'London'])
-                ->assertObjectAfterHydration(function (object $object) {
-                    self::assertEquals([
-                        create(Address::class, ['address' => '1600 Pennsylvania Avenue', 'city' => 'Washington DC'])->object(),
-                        create(Address::class, ['address' => '221 B Baker St', 'city' => 'London'])->object(),
-                    ], $object->addresses);
-                });
-        }];
-
-        yield 'Array with DTOs: fully writable allows deep partial changes' => [function () {
-            return HydrationTest::create(new class() {
-                /**
-                 * @var Symfony\UX\LiveComponent\Tests\Fixtures\Dto\HoldsArrayOfDtos[]
-                 */
-                #[LiveProp(writable: true, useSerializerForHydration: true)]
-                public array $dtos = [];
-            })
-                ->mountWith(['dtos' => [
-                    create(HoldsArrayOfDtos::class, ['addresses' => [
-                        create(Address::class, ['address' => '742 Evergreen Terrace', 'city' => 'Boston'])->object(),
-                        create(Address::class, ['address' => 'Apartment 5A, 129 West 81st Street', 'city' => 'New York'])->object(),
-                        create(Address::class, ['address' => '52 Festive Road', 'city' => 'London'])->object(),
-                    ]])->object(),
-                    create(HoldsArrayOfDtos::class, ['addresses' => [
-                        create(Address::class, ['address' => '698 Sycamore Road', 'city' => 'San Pueblo'])->object(),
-                        create(Address::class, ['address' => 'Madison Square Garden', 'city' => 'Chicago'])->object(),
-                    ]])->object(),
-                ]])
-                ->assertDehydratesTo(['dtos' => [
-                    [
-                        'addresses' => [
-                            ['address' => '742 Evergreen Terrace', 'city' => 'Boston'],
-                            ['address' => 'Apartment 5A, 129 West 81st Street', 'city' => 'New York'],
-                            ['address' => '52 Festive Road', 'city' => 'London'],
-                        ],
-                    ],
-                    [
-                        'addresses' => [
-                            ['address' => '698 Sycamore Road', 'city' => 'San Pueblo'],
-                            ['address' => 'Madison Square Garden', 'city' => 'Chicago'],
-                        ],
-                    ],
-                ]])
-                ->userUpdatesProps([
-                    'dtos.0.addresses.0.city' => 'Springfield',
-                    'dtos.1.addresses.1.address' => '1060 West Addison Street',
-                    'dtos.1.addresses.1' => create(Address::class, ['address' => '10 Downing Street', 'city' => 'London'])->object(),
-                ])
-                ->assertObjectAfterHydration(function (object $object) {
-                    self::assertEquals(
-                        [
-                            create(HoldsArrayOfDtos::class, ['addresses' => [
-                                create(Address::class, ['address' => '742 Evergreen Terrace', 'city' => 'Springfield'])->object(),
-                                create(Address::class, ['address' => 'Apartment 5A, 129 West 81st Street', 'city' => 'New York'])->object(),
-                                create(Address::class, ['address' => '52 Festive Road', 'city' => 'London'])->object(),
-                            ]])->object(),
-                            create(HoldsArrayOfDtos::class, ['addresses' => [
-                                create(Address::class, ['address' => '698 Sycamore Road', 'city' => 'San Pueblo'])->object(),
-                                create(Address::class, ['address' => '10 Downing Street', 'city' => 'London'])->object(),
-                            ]])->object(),
-                        ],
-                        $object->dtos
-                    );
-                });
-        }];
-
         yield 'Object: (de)hydrates nested objects with phpdoc typehints' => [function () {
             return HydrationTest::create(new class() {
                 #[LiveProp(writable: true)]
diff --git a/src/Translator/tests/TranslationsDumperTest.php b/src/Translator/tests/TranslationsDumperTest.php
index c68a9fef..c21c3a8c 100644
--- a/src/Translator/tests/TranslationsDumperTest.php
+++ b/src/Translator/tests/TranslationsDumperTest.php
@@ -22,7 +22,6 @@ use Symfony\UX\Translator\TranslationsDumper;
 class TranslationsDumperTest extends TestCase
 {
     protected static $translationsDumpDir;
-    private TranslationsDumper $translationsDumper;
 
     public static function setUpBeforeClass(): void
     {
@@ -34,115 +33,17 @@ class TranslationsDumperTest extends TestCase
         @rmdir(self::$translationsDumpDir);
     }
 
-    protected function setUp(): void
+    public function testDump(): void
     {
-        $this->translationsDumper = new TranslationsDumper(
+        $translationsDumper = new TranslationsDumper(
             self::$translationsDumpDir,
             new MessageParametersExtractor(),
             new IntlMessageParametersExtractor(),
             new TypeScriptMessageParametersPrinter(),
             new Filesystem(),
         );
-    }
-
-    public function testDump(): void
-    {
-        $this->translationsDumper->dump(...self::getMessageCatalogues());
-
-        $this->assertFileExists(self::$translationsDumpDir.'/index.js');
-        $this->assertFileExists(self::$translationsDumpDir.'/index.d.ts');
-
-        $this->assertStringEqualsFile(self::$translationsDumpDir.'/index.js', <<<'JAVASCRIPT'
-export const NOTIFICATION_COMMENT_CREATED = {"id":"notification.comment_created","translations":{"messages+intl-icu":{"en":"Your post received a comment!","fr":"Votre article a re\u00e7u un commentaire !"}}};
-export const NOTIFICATION_COMMENT_CREATED_DESCRIPTION = {"id":"notification.comment_created.description","translations":{"messages+intl-icu":{"en":"Your post \"{title}\" has received a new comment. You can read the comment by following <a href=\"{link}\">this link<\/a>","fr":"Votre article \"{title}\" a re\u00e7u un nouveau commentaire. Vous pouvez lire le commentaire en suivant <a href=\"{link}\">ce lien<\/a>"}}};
-export const POST_NUM_COMMENTS = {"id":"post.num_comments","translations":{"messages+intl-icu":{"en":"{count, plural, one {# comment} other {# comments}}","fr":"{count, plural, one {# commentaire} other {# commentaires}}"},"foobar":{"en":"There is 1 comment|There are %count% comments","fr":"Il y a 1 comment|Il y a %count% comments"}}};
-export const POST_NUM_COMMENTS_1 = {"id":"post.num_comments.","translations":{"messages+intl-icu":{"en":"{count, plural, one {# comment} other {# comments}} (should not conflict with the previous one.)","fr":"{count, plural, one {# commentaire} other {# commentaires}} (ne doit pas rentrer en conflit avec la traduction pr\u00e9c\u00e9dente)"}}};
-export const SYMFONY_GREAT = {"id":"symfony.great","translations":{"messages":{"en":"Symfony is awesome!","fr":"Symfony est g\u00e9nial !"}}};
-export const SYMFONY_WHAT = {"id":"symfony.what","translations":{"messages":{"en":"Symfony is %what%!","fr":"Symfony est %what%!"}}};
-export const SYMFONY_WHAT_1 = {"id":"symfony.what!","translations":{"messages":{"en":"Symfony is %what%! (should not conflict with the previous one.)","fr":"Symfony est %what%! (ne doit pas rentrer en conflit avec la traduction pr\u00e9c\u00e9dente)"}}};
-export const SYMFONY_WHAT_2 = {"id":"symfony.what.","translations":{"messages":{"en":"Symfony is %what%. (should also not conflict with the previous one.)","fr":"Symfony est %what%. (ne doit pas non plus rentrer en conflit avec la traduction pr\u00e9c\u00e9dente)"}}};
-export const APPLES_COUNT0 = {"id":"apples.count.0","translations":{"messages":{"en":"There is 1 apple|There are %count% apples","fr":"Il y a 1 pomme|Il y a %count% pommes"}}};
-export const APPLES_COUNT1 = {"id":"apples.count.1","translations":{"messages":{"en":"{1} There is one apple|]1,Inf] There are %count% apples","fr":"{1} Il y a une pomme|]1,Inf] Il y a %count% pommes"}}};
-export const APPLES_COUNT2 = {"id":"apples.count.2","translations":{"messages":{"en":"{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples","fr":"{0} Il n'y a pas de pommes|{1} Il y a une pomme|]1,Inf] Il y a %count% pommes"}}};
-export const APPLES_COUNT3 = {"id":"apples.count.3","translations":{"messages":{"en":"one: There is one apple|more: There are %count% apples","fr":"one: Il y a une pomme|more: Il y a %count% pommes"}}};
-export const APPLES_COUNT4 = {"id":"apples.count.4","translations":{"messages":{"en":"one: There is one apple|more: There are more than one apple","fr":"one: Il y a une pomme|more: Il y a plus d'une pomme"}}};
-export const WHAT_COUNT1 = {"id":"what.count.1","translations":{"messages":{"en":"{1} There is one %what%|]1,Inf] There are %count% %what%","fr":"{1} Il y a une %what%|]1,Inf] Il y a %count% %what%"}}};
-export const WHAT_COUNT2 = {"id":"what.count.2","translations":{"messages":{"en":"{0} There are no %what%|{1} There is one %what%|]1,Inf] There are %count% %what%","fr":"{0} Il n'y a pas de %what%|{1} Il y a une %what%|]1,Inf] Il y a %count% %what%"}}};
-export const WHAT_COUNT3 = {"id":"what.count.3","translations":{"messages":{"en":"one: There is one %what%|more: There are %count% %what%","fr":"one: Il y a une %what%|more: Il y a %count% %what%"}}};
-export const WHAT_COUNT4 = {"id":"what.count.4","translations":{"messages":{"en":"one: There is one %what%|more: There are more than one %what%","fr":"one: Il y a une %what%|more: Il y a more than one %what%"}}};
-export const ANIMAL_DOG_CAT = {"id":"animal.dog-cat","translations":{"messages":{"en":"Dog and cat","fr":"Chien et chat"}}};
-export const ANIMAL_DOG_CAT_1 = {"id":"animal.dog_cat","translations":{"messages":{"en":"Dog and cat (should not conflict with the previous one)","fr":"Chien et chat (ne doit pas rentrer en conflit avec la traduction pr\u00e9c\u00e9dente)"}}};
-export const _0STARTS_WITH_NUMERIC = {"id":"0starts.with.numeric","translations":{"messages":{"en":"Key starts with numeric char","fr":"La touche commence par un caract\u00e8re num\u00e9rique"}}};
-
-JAVASCRIPT);
-
-        $this->assertStringEqualsFile(self::$translationsDumpDir.'/index.d.ts', <<<'TYPESCRIPT'
-import { Message, NoParametersType } from '@symfony/ux-translator';
-
-export declare const NOTIFICATION_COMMENT_CREATED: Message<{ 'messages+intl-icu': { parameters: NoParametersType } }, 'en'|'fr'>;
-export declare const NOTIFICATION_COMMENT_CREATED_DESCRIPTION: Message<{ 'messages+intl-icu': { parameters: { 'title': string, 'link': string } } }, 'en'|'fr'>;
-export declare const POST_NUM_COMMENTS: Message<{ 'messages+intl-icu': { parameters: { 'count': number } }, 'foobar': { parameters: { '%count%': number } } }, 'en'|'fr'>;
-export declare const POST_NUM_COMMENTS_1: Message<{ 'messages+intl-icu': { parameters: { 'count': number } } }, 'en'|'fr'>;
-export declare const SYMFONY_GREAT: Message<{ 'messages': { parameters: NoParametersType } }, 'en'|'fr'>;
-export declare const SYMFONY_WHAT: Message<{ 'messages': { parameters: { '%what%': string } } }, 'en'|'fr'>;
-export declare const SYMFONY_WHAT_1: Message<{ 'messages': { parameters: { '%what%': string } } }, 'en'|'fr'>;
-export declare const SYMFONY_WHAT_2: Message<{ 'messages': { parameters: { '%what%': string } } }, 'en'|'fr'>;
-export declare const APPLES_COUNT0: Message<{ 'messages': { parameters: { '%count%': number } } }, 'en'|'fr'>;
-export declare const APPLES_COUNT1: Message<{ 'messages': { parameters: { '%count%': number } } }, 'en'|'fr'>;
-export declare const APPLES_COUNT2: Message<{ 'messages': { parameters: { '%count%': number } } }, 'en'|'fr'>;
-export declare const APPLES_COUNT3: Message<{ 'messages': { parameters: { '%count%': number } } }, 'en'|'fr'>;
-export declare const APPLES_COUNT4: Message<{ 'messages': { parameters: NoParametersType } }, 'en'|'fr'>;
-export declare const WHAT_COUNT1: Message<{ 'messages': { parameters: { '%what%': string, '%count%': number } } }, 'en'|'fr'>;
-export declare const WHAT_COUNT2: Message<{ 'messages': { parameters: { '%what%': string, '%count%': number } } }, 'en'|'fr'>;
-export declare const WHAT_COUNT3: Message<{ 'messages': { parameters: { '%what%': string, '%count%': number } } }, 'en'|'fr'>;
-export declare const WHAT_COUNT4: Message<{ 'messages': { parameters: { '%what%': string } } }, 'en'|'fr'>;
-export declare const ANIMAL_DOG_CAT: Message<{ 'messages': { parameters: NoParametersType } }, 'en'|'fr'>;
-export declare const ANIMAL_DOG_CAT_1: Message<{ 'messages': { parameters: NoParametersType } }, 'en'|'fr'>;
-export declare const _0STARTS_WITH_NUMERIC: Message<{ 'messages': { parameters: NoParametersType } }, 'en'|'fr'>;
-
-TYPESCRIPT);
-    }
-
-    public function testDumpWithExcludedDomains(): void
-    {
-        $this->translationsDumper->addExcludedDomain('foobar');
-        $this->translationsDumper->dump(...$this->getMessageCatalogues());
-
-        $this->assertFileExists(self::$translationsDumpDir.'/index.js');
-        $this->assertStringNotContainsString('foobar', file_get_contents(self::$translationsDumpDir.'/index.js'));
-    }
-
-    public function testDumpIncludedDomains(): void
-    {
-        $this->translationsDumper->addIncludedDomain('messages');
-        $this->translationsDumper->dump(...$this->getMessageCatalogues());
 
-        $this->assertFileExists(self::$translationsDumpDir.'/index.js');
-        $this->assertStringNotContainsString('foobar', file_get_contents(self::$translationsDumpDir.'/index.js'));
-    }
-
-    public function testSetBothIncludedAndExcludedDomains(): void
-    {
-        $this->expectException(\LogicException::class);
-        $this->expectExceptionMessage('You cannot set both "excluded_domains" and "included_domains" at the same time.');
-        $this->translationsDumper->addIncludedDomain('foobar');
-        $this->translationsDumper->addExcludedDomain('messages');
-    }
-
-    public function testSetBothExcludedAndIncludedDomains(): void
-    {
-        $this->expectException(\LogicException::class);
-        $this->expectExceptionMessage('You cannot set both "excluded_domains" and "included_domains" at the same time.');
-        $this->translationsDumper->addExcludedDomain('foobar');
-        $this->translationsDumper->addIncludedDomain('messages');
-    }
-
-    /**
-     * @return list<MessageCatalogue>
-     */
-    private static function getMessageCatalogues(): array
-    {
-        return [
+        $translationsDumper->dump(
             new MessageCatalogue('en', [
                 'messages+intl-icu' => [
                     'notification.comment_created' => 'Your post received a comment!',
@@ -200,7 +101,60 @@ TYPESCRIPT);
                 'foobar' => [
                     'post.num_comments' => 'Il y a 1 comment|Il y a %count% comments',
                 ],
-            ]),
-        ];
+            ])
+        );
+
+        $this->assertFileExists(self::$translationsDumpDir.'/index.js');
+        $this->assertFileExists(self::$translationsDumpDir.'/index.d.ts');
+
+        $this->assertStringEqualsFile(self::$translationsDumpDir.'/index.js', <<<'JAVASCRIPT'
+export const NOTIFICATION_COMMENT_CREATED = {"id":"notification.comment_created","translations":{"messages+intl-icu":{"en":"Your post received a comment!","fr":"Votre article a re\u00e7u un commentaire !"}}};
+export const NOTIFICATION_COMMENT_CREATED_DESCRIPTION = {"id":"notification.comment_created.description","translations":{"messages+intl-icu":{"en":"Your post \"{title}\" has received a new comment. You can read the comment by following <a href=\"{link}\">this link<\/a>","fr":"Votre article \"{title}\" a re\u00e7u un nouveau commentaire. Vous pouvez lire le commentaire en suivant <a href=\"{link}\">ce lien<\/a>"}}};
+export const POST_NUM_COMMENTS = {"id":"post.num_comments","translations":{"messages+intl-icu":{"en":"{count, plural, one {# comment} other {# comments}}","fr":"{count, plural, one {# commentaire} other {# commentaires}}"},"foobar":{"en":"There is 1 comment|There are %count% comments","fr":"Il y a 1 comment|Il y a %count% comments"}}};
+export const POST_NUM_COMMENTS_1 = {"id":"post.num_comments.","translations":{"messages+intl-icu":{"en":"{count, plural, one {# comment} other {# comments}} (should not conflict with the previous one.)","fr":"{count, plural, one {# commentaire} other {# commentaires}} (ne doit pas rentrer en conflit avec la traduction pr\u00e9c\u00e9dente)"}}};
+export const SYMFONY_GREAT = {"id":"symfony.great","translations":{"messages":{"en":"Symfony is awesome!","fr":"Symfony est g\u00e9nial !"}}};
+export const SYMFONY_WHAT = {"id":"symfony.what","translations":{"messages":{"en":"Symfony is %what%!","fr":"Symfony est %what%!"}}};
+export const SYMFONY_WHAT_1 = {"id":"symfony.what!","translations":{"messages":{"en":"Symfony is %what%! (should not conflict with the previous one.)","fr":"Symfony est %what%! (ne doit pas rentrer en conflit avec la traduction pr\u00e9c\u00e9dente)"}}};
+export const SYMFONY_WHAT_2 = {"id":"symfony.what.","translations":{"messages":{"en":"Symfony is %what%. (should also not conflict with the previous one.)","fr":"Symfony est %what%. (ne doit pas non plus rentrer en conflit avec la traduction pr\u00e9c\u00e9dente)"}}};
+export const APPLES_COUNT0 = {"id":"apples.count.0","translations":{"messages":{"en":"There is 1 apple|There are %count% apples","fr":"Il y a 1 pomme|Il y a %count% pommes"}}};
+export const APPLES_COUNT1 = {"id":"apples.count.1","translations":{"messages":{"en":"{1} There is one apple|]1,Inf] There are %count% apples","fr":"{1} Il y a une pomme|]1,Inf] Il y a %count% pommes"}}};
+export const APPLES_COUNT2 = {"id":"apples.count.2","translations":{"messages":{"en":"{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples","fr":"{0} Il n'y a pas de pommes|{1} Il y a une pomme|]1,Inf] Il y a %count% pommes"}}};
+export const APPLES_COUNT3 = {"id":"apples.count.3","translations":{"messages":{"en":"one: There is one apple|more: There are %count% apples","fr":"one: Il y a une pomme|more: Il y a %count% pommes"}}};
+export const APPLES_COUNT4 = {"id":"apples.count.4","translations":{"messages":{"en":"one: There is one apple|more: There are more than one apple","fr":"one: Il y a une pomme|more: Il y a plus d'une pomme"}}};
+export const WHAT_COUNT1 = {"id":"what.count.1","translations":{"messages":{"en":"{1} There is one %what%|]1,Inf] There are %count% %what%","fr":"{1} Il y a une %what%|]1,Inf] Il y a %count% %what%"}}};
+export const WHAT_COUNT2 = {"id":"what.count.2","translations":{"messages":{"en":"{0} There are no %what%|{1} There is one %what%|]1,Inf] There are %count% %what%","fr":"{0} Il n'y a pas de %what%|{1} Il y a une %what%|]1,Inf] Il y a %count% %what%"}}};
+export const WHAT_COUNT3 = {"id":"what.count.3","translations":{"messages":{"en":"one: There is one %what%|more: There are %count% %what%","fr":"one: Il y a une %what%|more: Il y a %count% %what%"}}};
+export const WHAT_COUNT4 = {"id":"what.count.4","translations":{"messages":{"en":"one: There is one %what%|more: There are more than one %what%","fr":"one: Il y a une %what%|more: Il y a more than one %what%"}}};
+export const ANIMAL_DOG_CAT = {"id":"animal.dog-cat","translations":{"messages":{"en":"Dog and cat","fr":"Chien et chat"}}};
+export const ANIMAL_DOG_CAT_1 = {"id":"animal.dog_cat","translations":{"messages":{"en":"Dog and cat (should not conflict with the previous one)","fr":"Chien et chat (ne doit pas rentrer en conflit avec la traduction pr\u00e9c\u00e9dente)"}}};
+export const _0STARTS_WITH_NUMERIC = {"id":"0starts.with.numeric","translations":{"messages":{"en":"Key starts with numeric char","fr":"La touche commence par un caract\u00e8re num\u00e9rique"}}};
+
+JAVASCRIPT);
+
+        $this->assertStringEqualsFile(self::$translationsDumpDir.'/index.d.ts', <<<'TYPESCRIPT'
+import { Message, NoParametersType } from '@symfony/ux-translator';
+
+export declare const NOTIFICATION_COMMENT_CREATED: Message<{ 'messages+intl-icu': { parameters: NoParametersType } }, 'en'|'fr'>;
+export declare const NOTIFICATION_COMMENT_CREATED_DESCRIPTION: Message<{ 'messages+intl-icu': { parameters: { 'title': string, 'link': string } } }, 'en'|'fr'>;
+export declare const POST_NUM_COMMENTS: Message<{ 'messages+intl-icu': { parameters: { 'count': number } }, 'foobar': { parameters: { '%count%': number } } }, 'en'|'fr'>;
+export declare const POST_NUM_COMMENTS_1: Message<{ 'messages+intl-icu': { parameters: { 'count': number } } }, 'en'|'fr'>;
+export declare const SYMFONY_GREAT: Message<{ 'messages': { parameters: NoParametersType } }, 'en'|'fr'>;
+export declare const SYMFONY_WHAT: Message<{ 'messages': { parameters: { '%what%': string } } }, 'en'|'fr'>;
+export declare const SYMFONY_WHAT_1: Message<{ 'messages': { parameters: { '%what%': string } } }, 'en'|'fr'>;
+export declare const SYMFONY_WHAT_2: Message<{ 'messages': { parameters: { '%what%': string } } }, 'en'|'fr'>;
+export declare const APPLES_COUNT0: Message<{ 'messages': { parameters: { '%count%': number } } }, 'en'|'fr'>;
+export declare const APPLES_COUNT1: Message<{ 'messages': { parameters: { '%count%': number } } }, 'en'|'fr'>;
+export declare const APPLES_COUNT2: Message<{ 'messages': { parameters: { '%count%': number } } }, 'en'|'fr'>;
+export declare const APPLES_COUNT3: Message<{ 'messages': { parameters: { '%count%': number } } }, 'en'|'fr'>;
+export declare const APPLES_COUNT4: Message<{ 'messages': { parameters: NoParametersType } }, 'en'|'fr'>;
+export declare const WHAT_COUNT1: Message<{ 'messages': { parameters: { '%what%': string, '%count%': number } } }, 'en'|'fr'>;
+export declare const WHAT_COUNT2: Message<{ 'messages': { parameters: { '%what%': string, '%count%': number } } }, 'en'|'fr'>;
+export declare const WHAT_COUNT3: Message<{ 'messages': { parameters: { '%what%': string, '%count%': number } } }, 'en'|'fr'>;
+export declare const WHAT_COUNT4: Message<{ 'messages': { parameters: { '%what%': string } } }, 'en'|'fr'>;
+export declare const ANIMAL_DOG_CAT: Message<{ 'messages': { parameters: NoParametersType } }, 'en'|'fr'>;
+export declare const ANIMAL_DOG_CAT_1: Message<{ 'messages': { parameters: NoParametersType } }, 'en'|'fr'>;
+export declare const _0STARTS_WITH_NUMERIC: Message<{ 'messages': { parameters: NoParametersType } }, 'en'|'fr'>;
+
+TYPESCRIPT);
     }
 }
diff --git a/src/TwigComponent/tests/Fixtures/templates/anonymous_component_with_empty_props.html.twig b/src/TwigComponent/tests/Fixtures/templates/anonymous_component_with_empty_props.html.twig
deleted file mode 100644
index 2b05a2dc..00000000
--- a/src/TwigComponent/tests/Fixtures/templates/anonymous_component_with_empty_props.html.twig
+++ /dev/null
@@ -1,1 +0,0 @@
-<twig:EmptyProps/>
\ No newline at end of file
diff --git a/src/TwigComponent/tests/Fixtures/templates/anonymous_component_with_props_overwrite_parents_props.html.twig b/src/TwigComponent/tests/Fixtures/templates/anonymous_component_with_props_overwrite_parents_props.html.twig
deleted file mode 100644
index 91a293f6..00000000
--- a/src/TwigComponent/tests/Fixtures/templates/anonymous_component_with_props_overwrite_parents_props.html.twig
+++ /dev/null
@@ -1,3 +0,0 @@
-<twig:Alert size='md'>
-    <twig:Icon/>
-</twig:Alert>
\ No newline at end of file
diff --git a/src/TwigComponent/tests/Fixtures/templates/components/Alert.html.twig b/src/TwigComponent/tests/Fixtures/templates/components/Alert.html.twig
index 46b09843..e88a5795 100644
--- a/src/TwigComponent/tests/Fixtures/templates/components/Alert.html.twig
+++ b/src/TwigComponent/tests/Fixtures/templates/components/Alert.html.twig
@@ -31,6 +31,5 @@
 }) %}
 
 <div class="{{ alert.apply({color, size}, attributes.render('class'), 'flex p-4') }}">
-    {% block content %}
-    {% endblock %}
+    ...
 </div>
\ No newline at end of file
diff --git a/src/TwigComponent/tests/Fixtures/templates/components/EmptyProps.html.twig b/src/TwigComponent/tests/Fixtures/templates/components/EmptyProps.html.twig
deleted file mode 100644
index a8d7ae11..00000000
--- a/src/TwigComponent/tests/Fixtures/templates/components/EmptyProps.html.twig
+++ /dev/null
@@ -1,3 +0,0 @@
-{% props %}
-
-<p>I have an empty props tag</p>
\ No newline at end of file
diff --git a/src/TwigComponent/tests/Fixtures/templates/components/Icon.html.twig b/src/TwigComponent/tests/Fixtures/templates/components/Icon.html.twig
deleted file mode 100644
index e0037a02..00000000
--- a/src/TwigComponent/tests/Fixtures/templates/components/Icon.html.twig
+++ /dev/null
@@ -1,6 +0,0 @@
-{% props size = null %}
-
-<p>I am an icon</p>
-{% if size is not null %}
-    <p>I am {{ size }}</p>
-{% endif %}
\ No newline at end of file
diff --git a/src/TwigComponent/tests/Integration/ComponentExtensionTest.php b/src/TwigComponent/tests/Integration/ComponentExtensionTest.php
index 04390622..ef55dbc3 100644
--- a/src/TwigComponent/tests/Integration/ComponentExtensionTest.php
+++ b/src/TwigComponent/tests/Integration/ComponentExtensionTest.php
@@ -366,21 +366,6 @@ final class ComponentExtensionTest extends KernelTestCase
         self::getContainer()->get(Environment::class)->render('component_with_conflict_between_props_from_template_and_class.html.twig');
     }
 
-    public function testComponentWithEmptyProps(): void
-    {
-        $output = self::getContainer()->get(Environment::class)->render('anonymous_component_with_empty_props.html.twig');
-
-        $this->assertStringContainsString('I have an empty props tag', $output);
-    }
-
-    public function testAnonymousComponentWithPropsOverwriteParentsProps(): void
-    {
-        $output = self::getContainer()->get(Environment::class)->render('anonymous_component_with_props_overwrite_parents_props.html.twig');
-
-        $this->assertStringContainsString('I am an icon', $output);
-        $this->assertStringNotContainsString('I am md', $output);
-    }
-
     private function renderComponent(string $name, array $data = []): string
     {
         return self::getContainer()->get(Environment::class)->render('render_component.html.twig', [