Repository: symfony/ux
Pull request #2763 (2025-05-22 10:29:40)

[Map] add definition to after create events in controller
| Q             | A
| ------------- | ---
| Bug fix?      | yes
| New feature?  |
| Docs?         | no
| Issues        | Fix 
| License       | MIT

Hi,
The [documentation](https://symfony.com/bundles/ux-map/current/index.html#advanced-passing-extra-data-from-php-to-the-stimulus-controller )states that the definition should be accessible in the <type>:after-create events in the map controllers.

carsonbot: It looks like you unchecked the "Allow edits from maintainer" box. That is fine, but please note that if you have multiple commits, you'll need to squash your commits into one before this can be merged. Or, you can check the "Allow edits from maintainers" box and the maintainer can squash for you.

Cheers!

Carsonbot
github-actions[bot]: <h1>📊 Packages dist files size difference</h1>

Thanks for the PR! Here is the difference in size of the packages dist files between the base branch and the PR.
Please review the changes and make sure they are expected.

<table>
    <thead><tr><th>File</th><th>Before (Size / Gzip)</th><th>After (Size / Gzip)</th></tr></thead>
    <tbody><tr><td colspan="3"><a href="https://github.com/insiders/ux/tree/map-after-create/src/Map/assets/dist"><b>Map</b></a></td></tr><tr>
                <td><a href="https://github.com/insiders/ux/blob/map-after-create/src/Map/assets/dist/abstract_map_controller.js"><code>abstract_map_controller.js</code></a></td>
            <td>
                    <code>3.97 kB</code>
                    / <code>1.07 kB</code> 
                </td><td>
                    <code>3.99 kB</code><sup>+1% 📈</sup>
                    / <code>1.07 kB</code><sup>0%</sup>
                </td></tr></tbody>
</table>

<!-- Sticky Pull Request Comment -->

Patch:
diff --git a/.github/workflows/.utils.sh b/.github/workflows/.utils.sh
deleted file mode 100644
index 78b0d277..00000000
--- a/.github/workflows/.utils.sh
+++ /dev/null
@@ -1,21 +0,0 @@
-_run_task() {
-    local ok=0
-    local title="$1"
-    local start=$(date -u +%s)
-    OUTPUT=$(bash -xc "$2" 2>&1) || ok=$?
-    local end=$(date -u +%s)
-
-    if [[ $ok -ne 0 ]]; then
-      printf "\n%-70s%10s\n" $title $(($end-$start))s
-      echo "$OUTPUT"
-      echo "Job exited with: $ok"
-      echo -e "\n::error::KO $title\\n"
-    else
-      printf "::group::%-68s%10s\n" $title $(($end-$start))s
-      echo "$OUTPUT"
-      echo -e "\n\\e[32mOK\\e[0m $title\\n\\n::endgroup::"
-    fi
-
-    exit $ok
-}
-export -f _run_task
diff --git a/.github/workflows/code-quality.yaml b/.github/workflows/code-quality.yaml
deleted file mode 100644
index 84896b53..00000000
--- a/.github/workflows/code-quality.yaml
+++ /dev/null
@@ -1,72 +0,0 @@
-name: Code Quality
-
-on:
-    push:
-        paths-ignore:
-            - 'src/*/doc/**'
-            - 'src/**/*.md'
-            - 'ux.symfony.com/**'
-    pull_request:
-        paths-ignore:
-            - 'src/*/doc/**'
-            - 'src/**/*.md'
-            - 'ux.symfony.com/**'
-
-concurrency:
-    group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
-    cancel-in-progress: true
-
-jobs:
-    coding-style-js:
-        name: JavaScript Coding Style
-        runs-on: ubuntu-latest
-        steps:
-            - uses: actions/checkout@v4
-            - run: npm i -g corepack && corepack enable
-            - uses: actions/setup-node@v4
-              with:
-                cache: 'yarn'
-            - run: yarn --immutable
-            - run: yarn ci
-
-    phpstan:
-        name: PHPStan
-        runs-on: ubuntu-latest
-        strategy:
-            fail-fast: false
-            matrix:
-                package: ${{ fromJson(needs.php-packages-matrix.outputs.packages) }}
-        steps:
-            -   name: Checkout
-                uses: actions/checkout@v4
-
-            - name: Configure environment
-              run: |
-                  echo COLUMNS=120 >> $GITHUB_ENV
-                  echo COMPOSER_MIN_STAB='composer config minimum-stability stable --ansi' >> $GITHUB_ENV
-                  echo COMPOSER_UP='composer update --no-progress --no-interaction --ansi' >> $GITHUB_ENV
-                  echo PHPSTAN='vendor/bin/phpstan' >> $GITHUB_ENV
-
-                  # TODO: Only Turbo has PHPStan configuration, let's improve this later :)
-                  PACKAGES=Turbo
-                  #PACKAGES=$(find src/ -mindepth 2 -type f -name composer.json -not -path "*/vendor/*" -printf '%h\n' | sed 's/^src\///' | sort |  tr '\n' ' ')
-                  echo "Packages: $PACKAGES"
-                  echo "PACKAGES=$PACKAGES" >> $GITHUB_ENV
-
-            -   name: Setup PHP
-                uses: shivammathur/setup-php@v2
-                with:
-                    php-version: '8.1'
-                    tools: flex
-
-            - name: Install root dependencies
-              run: composer install
-
-            - name: Build root packages
-              run: php .github/build-packages.php
-
-            - name: Run PHPStan on packages
-              run: |
-                  source .github/workflows/.utils.sh
-
-                  echo "$PACKAGES" | xargs -n1 | parallel -j +3 "_run_task {} '(cd src/{} && $COMPOSER_MIN_STAB && $COMPOSER_UP && $PHPSTAN)'"
diff --git a/.github/workflows/dist-files-unbuilt.yaml b/.github/workflows/dist-files-unbuilt.yaml
deleted file mode 100644
index cd76bdaf..00000000
--- a/.github/workflows/dist-files-unbuilt.yaml
+++ /dev/null
@@ -1,42 +0,0 @@
-name: Dist Files Unbuilt
-
-on:
-    push:
-        paths-ignore:
-            - 'src/*/doc/**'
-            - 'src/**/*.md'
-            - 'ux.symfony.com/**'
-    pull_request:
-        paths-ignore:
-            - 'src/*/doc/**'
-            - 'src/**/*.md'
-            - 'ux.symfony.com/**'
-
-concurrency:
-    group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
-    cancel-in-progress: true
-
-jobs:
-  check:
-    runs-on: ubuntu-latest
-    steps:
-      - uses: actions/checkout@v4
-      - run: npm i -g corepack && corepack enable
-      - uses: actions/setup-node@v4
-        with:
-          cache: 'yarn'
-          cache-dependency-path: |
-            yarn.lock
-            **/package.json
-      - run: yarn --immutable && yarn build
-
-      - name: Check if JS dist files are current
-        run: |
-          if ! git diff --quiet; then
-            echo "The Git workspace is unclean! Changes detected:"
-            git status --porcelain
-            git diff
-            exit 1
-          else
-            echo "The Git workspace is clean. No changes detected."
-          fi
diff --git a/.github/workflows/toolkit-kits-code-quality.yaml b/.github/workflows/toolkit-kits-cs.yaml
similarity index 95%
rename from .github/workflows/toolkit-kits-code-quality.yaml
rename to .github/workflows/toolkit-kits-cs.yaml
index 0ed5ccfc..fcd5e36f 100644
--- a/.github/workflows/toolkit-kits-code-quality.yaml
+++ b/.github/workflows/toolkit-kits-cs.yaml
@@ -1,4 +1,4 @@
-name: Toolkit Kits Code Quality
+name: Toolkit Kits
 
 on:
     push:
diff --git a/src/Autocomplete/doc/index.rst b/src/Autocomplete/doc/index.rst
index aa759554..6307e60f 100644
--- a/src/Autocomplete/doc/index.rst
+++ b/src/Autocomplete/doc/index.rst
@@ -418,6 +418,10 @@ likely need to create your own :ref:`custom autocomplete endpoint <custom-autoco
 Customizing the AJAX URL/Route
 ------------------------------
 
+.. versionadded:: 2.7
+
+    The ability to specify the route was added in Twig Components 2.7.
+
 The default route for the Ajax calls used by the Autocomplete component is ``/autocomplete/{alias}/``.
 Sometimes it may be useful to customize this URL - e.g. so that the URL lives
 under a specific firewall.
diff --git a/src/Icons/src/Twig/IconFinder.php b/src/Icons/src/Twig/IconFinder.php
index a2b68442..0d8fd48f 100644
--- a/src/Icons/src/Twig/IconFinder.php
+++ b/src/Icons/src/Twig/IconFinder.php
@@ -67,14 +67,12 @@ final class IconFinder
     private function templateFiles(LoaderInterface $loader): iterable
     {
         if ($loader instanceof FilesystemLoader) {
-            $paths = $loader->getPaths();
+            $paths = [];
             foreach ($loader->getNamespaces() as $namespace) {
                 $paths = [...$paths, ...$loader->getPaths($namespace)];
             }
-            if ($paths) {
-                foreach ((new Finder())->files()->in($paths)->name('*.twig') as $file) {
-                    yield (string) $file;
-                }
+            foreach ((new Finder())->files()->in($paths)->name('*.twig') as $file) {
+                yield (string) $file;
             }
         }
 
diff --git a/src/LiveComponent/doc/index.rst b/src/LiveComponent/doc/index.rst
index 3ab3fdad..f893d1c0 100644
--- a/src/LiveComponent/doc/index.rst
+++ b/src/LiveComponent/doc/index.rst
@@ -409,6 +409,10 @@ If you're building a form (:ref:`more on forms later <forms>`),
 instead of adding ``data-model`` to every field, you can instead
 rely on the ``name`` attribute.
 
+.. versionadded:: 2.3
+
+    The ``data-model`` attribute on the ``form`` is required since version 2.3.
+
 To activate this, you must add a ``data-model`` attribute to
 the ``<form>`` element:
 
@@ -547,6 +551,10 @@ changed, added or removed::
 Checkboxes, Select Elements Radios & Arrays
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
+.. versionadded:: 2.8
+
+    The ability to use checkboxes to set boolean values was added in LiveComponent 2.8.
+
 Checkboxes can be used to set a boolean or an array of strings::
 
     #[AsLiveComponent]
@@ -600,6 +608,10 @@ single value or an array of values::
 LiveProp Date Formats
 ~~~~~~~~~~~~~~~~~~~~~
 
+.. versionadded:: 2.8
+
+    The ``format`` option was introduced in Live Components 2.8.
+
 If you have a writable ``LiveProp`` that is some sort of ``DateTime`` instance,
 you can control the format of the model on the frontend with the ``format``
 option::
@@ -707,6 +719,10 @@ make this work:
 Hydrating with the Serializer
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
+.. versionadded:: 2.8
+
+    The ``useSerializerForHydration`` option was added in LiveComponent 2.8.
+
 To hydrate/dehydrate through Symfony's serializer, use the ``useSerializerForHydration``
 option::
 
@@ -995,6 +1011,10 @@ changes until loading has taken longer than a certain amount of time:
 Targeting Loading for a Specific Action
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
+.. versionadded:: 2.5
+
+    The ``action()`` modifier was introduced in Live Components 2.5.
+
 To only toggle the loading behavior when a specific action is triggered,
 use the ``action()`` modifier with the name of the action - e.g. ``saveForm()``:
 
@@ -1008,6 +1028,10 @@ use the ``action()`` modifier with the name of the action - e.g. ``saveForm()``:
 Targeting Loading When a Specific Model Changes
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
+.. versionadded:: 2.5
+
+    The ``model()`` modifier was introduced in Live Components 2.5.
+
 You can also toggle the loading behavior only if a specific model value
 was just changed using the ``model()`` modifier:
 
@@ -1875,6 +1899,10 @@ When the user clicks ``removeComment()``, a similar process happens.
 Using LiveCollectionType
 ~~~~~~~~~~~~~~~~~~~~~~~~
 
+.. versionadded:: 2.2
+
+    The ``LiveCollectionType`` and the ``LiveCollectionTrait`` was added in LiveComponent 2.2.
+
 The ``LiveCollectionType`` uses the same method described above, but in
 a generic way, so it needs even less code. This form type adds an 'Add'
 and a 'Delete' button for each row by default, which work out of the box
@@ -2671,6 +2699,10 @@ validated. To validate it, you have to set up a `PostMount hook`_::
 Communication Between Components: Emitting Events
 -------------------------------------------------
 
+.. versionadded:: 2.8
+
+    The ability to emit events was added in Live Components 2.8.
+
 Events allow you to communicate between any two components that live
 on your page.
 
@@ -2945,6 +2977,11 @@ Suppose the user updates the ``listName`` model and the parent component
 re-renders. In this case, the child component will *not* re-render by design:
 each component lives in its own universe.
 
+.. versionadded:: 2.8
+
+    The ``updateFromParent`` option was added in Live Components 2.8. Previously,
+    a child would re-render when *any* props passed into it changed.
+
 However, if the user adds a *new* todo item then we *do* want the ``TodoFooter``
 child component to re-render: using the new ``count`` value. To trigger this,
 in the ``TodoFooter`` component, add the ``updateFromParent`` option::
@@ -3202,14 +3239,14 @@ Rendering Quirks with List of Elements
 
 If you're rendering a list of elements in your component, to help LiveComponents
 understand which element is which between re-renders (i.e. if something re-orders
-or removes some of those elements), you can add a ``key`` attribute to
+or removes some of those elements), you can add a ``id`` attribute to
 each element
 
 .. code-block:: html+twig
 
     {# templates/components/Invoice.html.twig #}
     {% for lineItem in lineItems %}
-        <div key="{{ lineItem.id }}">
+        <div id="{{ lineItem.id }}">
             {{ lineItem.name }}
         </div>
     {% endfor %}
@@ -3224,6 +3261,10 @@ the list changes as the user types into a search box... or by clicking
 "delete" on an item. In this case, the wrong children may be removed
 or existing child components may not disappear when they should.
 
+.. versionadded:: 2.8
+
+    The ``key`` prop was added in Symfony UX Live Component 2.8.
+
 To fix this, add a ``key`` prop to each child component that's unique
 to that component:
 
@@ -3238,7 +3279,9 @@ to that component:
     {% endfor %}
 
 The ``key`` will be used to generate an ``id`` attribute,
-which will be used to identify each child component.
+which will be used to identify each child component. You can
+also pass in a ``id`` attribute directly, but ``key`` is
+a bit more convenient.
 
 .. _rendering-loop-new-element:
 
@@ -3455,6 +3498,10 @@ When a component re-renders, the new HTML is "morphed" onto the existing
 elements on the page. For example, if the re-render includes a new ``class``
 on an existing element, that class will be added to that element.
 
+.. versionadded:: 2.8
+
+    The smart re-render algorithm was introduced in LiveComponent 2.8.
+
 The rendering system is also smart enough to know when an element was changed
 by something *outside* of the LiveComponents system: e.g. some JavaScript
 that added a class to an element. In this case, the class will be preserved
@@ -3529,6 +3576,10 @@ In this case, any changes to the ``<select>`` element attributes will still be
 Define another route for your Component
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
+.. versionadded:: 2.7
+
+    The ``route`` option  was added in LiveComponents 2.7.
+
 The default route for live components is ``/components/{_live_component}/{_live_action}``.
 Sometimes it may be useful to customize this URL - e.g. so that the component lives
 under a specific firewall.
diff --git a/src/Map/assets/dist/abstract_map_controller.js b/src/Map/assets/dist/abstract_map_controller.js
index bd50b95f..228967fd 100644
--- a/src/Map/assets/dist/abstract_map_controller.js
+++ b/src/Map/assets/dist/abstract_map_controller.js
@@ -43,7 +43,7 @@ class default_1 extends Controller {
     createInfoWindow({ definition, element, }) {
         this.dispatchEvent('info-window:before-create', { definition, element });
         const infoWindow = this.doCreateInfoWindow({ definition, element });
-        this.dispatchEvent('info-window:after-create', { infoWindow, element });
+        this.dispatchEvent('info-window:after-create', { infoWindow, definition, element });
         this.infoWindows.push(infoWindow);
         return infoWindow;
     }
@@ -74,7 +74,7 @@ class default_1 extends Controller {
         return ({ definition }) => {
             this.dispatchEvent(eventBefore, { definition });
             const drawing = factory({ definition });
-            this.dispatchEvent(eventAfter, { [type]: drawing });
+            this.dispatchEvent(eventAfter, { [type]: drawing, definition });
             draws.set(definition['@id'], drawing);
             return drawing;
         };
diff --git a/src/Map/assets/src/abstract_map_controller.ts b/src/Map/assets/src/abstract_map_controller.ts
index db5dd7e5..b73f0bcc 100644
--- a/src/Map/assets/src/abstract_map_controller.ts
+++ b/src/Map/assets/src/abstract_map_controller.ts
@@ -206,7 +206,7 @@ export default abstract class<
     }): InfoWindow {
         this.dispatchEvent('info-window:before-create', { definition, element });
         const infoWindow = this.doCreateInfoWindow({ definition, element });
-        this.dispatchEvent('info-window:after-create', { infoWindow, element });
+        this.dispatchEvent('info-window:after-create', { infoWindow, definition, element });
 
         this.infoWindows.push(infoWindow);
 
@@ -334,7 +334,7 @@ export default abstract class<
         return ({ definition }: { definition: WithIdentifier<any> }) => {
             this.dispatchEvent(eventBefore, { definition });
             const drawing = factory({ definition }) as Draw;
-            this.dispatchEvent(eventAfter, { [type]: drawing });
+            this.dispatchEvent(eventAfter, { [type]: drawing, definition });
 
             draws.set(definition['@id'], drawing);
 
diff --git a/src/Map/src/Bridge/Google/assets/dist/map_controller.js b/src/Map/src/Bridge/Google/assets/dist/map_controller.js
index 7dad2397..1bc19b99 100644
--- a/src/Map/src/Bridge/Google/assets/dist/map_controller.js
+++ b/src/Map/src/Bridge/Google/assets/dist/map_controller.js
@@ -44,7 +44,7 @@ class default_1 extends Controller {
     createInfoWindow({ definition, element, }) {
         this.dispatchEvent('info-window:before-create', { definition, element });
         const infoWindow = this.doCreateInfoWindow({ definition, element });
-        this.dispatchEvent('info-window:after-create', { infoWindow, element });
+        this.dispatchEvent('info-window:after-create', { infoWindow, definition, element });
         this.infoWindows.push(infoWindow);
         return infoWindow;
     }
@@ -75,7 +75,7 @@ class default_1 extends Controller {
         return ({ definition }) => {
             this.dispatchEvent(eventBefore, { definition });
             const drawing = factory({ definition });
-            this.dispatchEvent(eventAfter, { [type]: drawing });
+            this.dispatchEvent(eventAfter, { [type]: drawing, definition });
             draws.set(definition['@id'], drawing);
             return drawing;
         };
diff --git a/src/Map/src/Bridge/Leaflet/assets/dist/map_controller.js b/src/Map/src/Bridge/Leaflet/assets/dist/map_controller.js
index 636c99f3..208cf321 100644
--- a/src/Map/src/Bridge/Leaflet/assets/dist/map_controller.js
+++ b/src/Map/src/Bridge/Leaflet/assets/dist/map_controller.js
@@ -45,7 +45,7 @@ class default_1 extends Controller {
     createInfoWindow({ definition, element, }) {
         this.dispatchEvent('info-window:before-create', { definition, element });
         const infoWindow = this.doCreateInfoWindow({ definition, element });
-        this.dispatchEvent('info-window:after-create', { infoWindow, element });
+        this.dispatchEvent('info-window:after-create', { infoWindow, definition, element });
         this.infoWindows.push(infoWindow);
         return infoWindow;
     }
@@ -76,7 +76,7 @@ class default_1 extends Controller {
         return ({ definition }) => {
             this.dispatchEvent(eventBefore, { definition });
             const drawing = factory({ definition });
-            this.dispatchEvent(eventAfter, { [type]: drawing });
+            this.dispatchEvent(eventAfter, { [type]: drawing, definition });
             draws.set(definition['@id'], drawing);
             return drawing;
         };
diff --git a/src/React/CHANGELOG.md b/src/React/CHANGELOG.md
index da9a6a6b..c9c5c066 100644
--- a/src/React/CHANGELOG.md
+++ b/src/React/CHANGELOG.md
@@ -1,13 +1,9 @@
 # CHANGELOG
 
-## 2.26.0
-
--   Improve error handling when resolving a React component
-
 ## 2.21.0
 
--   Add `permanent` option to the `react_component` Twig function, to prevent the
-    _unmounting_ when the component is deconnected and immediately re-connected
+-   Add `permanent` option to the `react_component` Twig function, to prevent the 
+    _unmounting_ when the component is deconnected and immediately re-connected. 
 
 ## 2.13.2
 
diff --git a/src/React/assets/dist/register_controller.js b/src/React/assets/dist/register_controller.js
index d0c38da6..2eef8cd8 100644
--- a/src/React/assets/dist/register_controller.js
+++ b/src/React/assets/dist/register_controller.js
@@ -10,10 +10,6 @@ function registerReactControllerComponents(context) {
         const component = reactControllers[`./${name}.jsx`] || reactControllers[`./${name}.tsx`];
         if (typeof component === 'undefined') {
             const possibleValues = Object.keys(reactControllers).map((key) => key.replace('./', '').replace('.jsx', '').replace('.tsx', ''));
-            if (possibleValues.includes(name)) {
-                throw new Error(`
-                    React controller "${name}" could not be resolved. Ensure the module exports the controller as a default export.`);
-            }
             throw new Error(`React controller "${name}" does not exist. Possible values: ${possibleValues.join(', ')}`);
         }
         return component;
diff --git a/src/React/assets/src/register_controller.ts b/src/React/assets/src/register_controller.ts
index 8be8923a..e9d59afe 100644
--- a/src/React/assets/src/register_controller.ts
+++ b/src/React/assets/src/register_controller.ts
@@ -37,12 +37,6 @@ export function registerReactControllerComponents(context: __WebpackModuleApi.Re
             const possibleValues = Object.keys(reactControllers).map((key) =>
                 key.replace('./', '').replace('.jsx', '').replace('.tsx', '')
             );
-
-            if (possibleValues.includes(name)) {
-                throw new Error(`
-                    React controller "${name}" could not be resolved. Ensure the module exports the controller as a default export.`);
-            }
-
             throw new Error(`React controller "${name}" does not exist. Possible values: ${possibleValues.join(', ')}`);
         }
 
diff --git a/src/React/doc/index.rst b/src/React/doc/index.rst
index 0faa32c4..92097ca9 100644
--- a/src/React/doc/index.rst
+++ b/src/React/doc/index.rst
@@ -81,10 +81,6 @@ For example:
         return <div>Hello {props.fullName}</div>;
     }
 
-.. note::
-
-    Ensure your module exports the controller as the ``export default``. The default export is used when resolving components.
-
 .. code-block:: html+twig
 
     {# templates/home.html.twig #}
diff --git a/src/StimulusBundle/doc/index.rst b/src/StimulusBundle/doc/index.rst
index 39e3aa48..88ede629 100644
--- a/src/StimulusBundle/doc/index.rst
+++ b/src/StimulusBundle/doc/index.rst
@@ -174,15 +174,15 @@ For example:
 
 .. code-block:: html+twig
 
-    <div {{ stimulus_controller('hello', { 'name': 'World', 'data': [1, 2, 3, 4] }) }}>
+    <div {{ stimulus_controller('chart', { 'name': 'Likes', 'data': [1, 2, 3, 4] }) }}>
         Hello
     </div>
 
     <!-- would render -->
     <div
-       data-controller="hello"
-       data-hello-name-value="World"
-       data-hello-data-value="&#x5B;1,2,3,4&#x5D;"
+       data-controller="chart"
+       data-chart-name-value="Likes"
+       data-chart-data-value="&#x5B;1,2,3,4&#x5D;"
     >
        Hello
     </div>
@@ -191,22 +191,22 @@ If you want to set CSS classes:
 
 .. code-block:: html+twig
 
-    <div {{ stimulus_controller('hello', { 'name': 'World', 'data': [1, 2, 3, 4] }, { 'loading': 'spinner' }) }}>
+    <div {{ stimulus_controller('chart', { 'name': 'Likes', 'data': [1, 2, 3, 4] }, { 'loading': 'spinner' }) }}>
         Hello
     </div>
 
     <!-- would render -->
     <div
-       data-controller="hello"
-       data-hello-name-value="World"
-       data-hello-data-value="&#x5B;1,2,3,4&#x5D;"
-       data-hello-loading-class="spinner"
+       data-controller="chart"
+       data-chart-name-value="Likes"
+       data-chart-data-value="&#x5B;1,2,3,4&#x5D;"
+       data-chart-loading-class="spinner"
     >
        Hello
     </div>
 
     <!-- or without values -->
-    <div {{ stimulus_controller('hello', controllerClasses: { 'loading': 'spinner' }) }}>
+    <div {{ stimulus_controller('chart', controllerClasses = { 'loading': 'spinner' }) }}>
         Hello
     </div>
 
@@ -214,8 +214,8 @@ And with outlets:
 
 .. code-block:: html+twig
 
-    <div {{ stimulus_controller('hello',
-            { 'name': 'World', 'data': [1, 2, 3, 4] },
+    <div {{ stimulus_controller('chart',
+            { 'name': 'Likes', 'data': [1, 2, 3, 4] },
             { 'loading': 'spinner' },
             { 'other': '.target' } ) }}>
         Hello
@@ -223,17 +223,17 @@ And with outlets:
 
     <!-- would render -->
     <div
-       data-controller="hello"
-       data-hello-name-value="World"
-       data-hello-data-value="&#x5B;1,2,3,4&#x5D;"
-       data-hello-loading-class="spinner"
-       data-hello-other-outlet=".target"
+       data-controller="chart"
+       data-chart-name-value="Likes"
+       data-chart-data-value="&#x5B;1,2,3,4&#x5D;"
+       data-chart-loading-class="spinner"
+       data-chart-other-outlet=".target"
     >
        Hello
     </div>
 
     <!-- or without values/classes -->
-    <div {{ stimulus_controller('hello', controllerOutlets: { 'other': '.target' }) }}>
+    <div {{ stimulus_controller('chart', controllerOutlets = { 'other': '.target' }) }}>
         Hello
     </div>
 
@@ -246,12 +246,12 @@ there's also a ``stimulus_controller`` filter:
 
 .. code-block:: html+twig
 
-    <div {{ stimulus_controller('hello', { 'name': 'World' })|stimulus_controller('other-controller') }}>
+    <div {{ stimulus_controller('chart', { 'name': 'Likes' })|stimulus_controller('other-controller') }}>
         Hello
     </div>
 
     <!-- would render -->
-    <div data-controller="hello other-controller" data-hello-name-value="World">
+    <div data-controller="chart other-controller" data-chart-name-value="Likes">
         Hello
     </div>
 
@@ -259,7 +259,7 @@ You can also retrieve the generated attributes as an array, which can be helpful
 
 .. code-block:: twig
 
-    {{ form_start(form, { attr: stimulus_controller('hello', { 'name': 'World' }).toArray() }) }}
+    {{ form_start(form, { attr: stimulus_controller('chart', { 'name': 'Likes' }).toArray() }) }}
 
 stimulus_action
 ~~~~~~~~~~~~~~~
diff --git a/src/TwigComponent/src/Twig/ComponentTokenParser.php b/src/TwigComponent/src/Twig/ComponentTokenParser.php
index ec543ee7..ff072124 100644
--- a/src/TwigComponent/src/Twig/ComponentTokenParser.php
+++ b/src/TwigComponent/src/Twig/ComponentTokenParser.php
@@ -12,7 +12,6 @@
 namespace Symfony\UX\TwigComponent\Twig;
 
 use Symfony\UX\TwigComponent\BlockStack;
-use Twig\Error\SyntaxError;
 use Twig\Node\Expression\AbstractExpression;
 use Twig\Node\Expression\ArrayExpression;
 use Twig\Node\Expression\ConstantExpression;
@@ -34,7 +33,6 @@ final class ComponentTokenParser extends AbstractTokenParser
     public function parse(Token $token): Node
     {
         $stream = $this->parser->getStream();
-
         if (method_exists($this->parser, 'parseExpression')) {
             // Since Twig 3.21
             $componentName = $this->componentName($this->parser->parseExpression());
@@ -42,10 +40,6 @@ final class ComponentTokenParser extends AbstractTokenParser
             $componentName = $this->componentName($this->parser->getExpressionParser()->parseExpression());
         }
 
-        if (null === $componentName) {
-            throw new SyntaxError('Could not parse component name.', $stream->getCurrent()->getLine(), $stream->getSourceContext());
-        }
-
         [$propsExpression, $only] = $this->parseArguments();
 
         // Write a fake: "extends __parent__" into the "embedded" template.
@@ -86,7 +80,7 @@ final class ComponentTokenParser extends AbstractTokenParser
         return 'component';
     }
 
-    private function componentName(AbstractExpression $expression): ?string
+    private function componentName(AbstractExpression $expression): string
     {
         if ($expression instanceof ConstantExpression) { // using {% component 'name' %}
             return $expression->getAttribute('value');
@@ -96,7 +90,7 @@ final class ComponentTokenParser extends AbstractTokenParser
             return $expression->getAttribute('name');
         }
 
-        return null;
+        throw new \LogicException('Could not parse component name.');
     }
 
     /**


Test patch:
diff --git a/.github/workflows/app-tests.yaml b/.github/workflows/app-tests.yaml
deleted file mode 100644
index c6b5da06..00000000
--- a/.github/workflows/app-tests.yaml
+++ /dev/null
@@ -1,107 +0,0 @@
-name: App Tests
-
-on:
-    push:
-        paths-ignore:
-            - 'src/*/doc/**'
-            - 'src/**/*.md'
-            - 'ux.symfony.com/**'
-    pull_request:
-        paths-ignore:
-            - 'src/*/doc/**'
-            - 'src/**/*.md'
-            - 'ux.symfony.com/**'
-
-concurrency:
-    group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
-    cancel-in-progress: true
-
-jobs:
-    encore-app:
-      name: "Encore (${{ matrix.name}})"
-      runs-on: ubuntu-latest
-      strategy:
-        fail-fast: false
-        matrix:
-          include:
-            - name: Internal, from "vendor/"
-              ux-packages-source: php-vendor
-            - name: External, from "npm add"
-              ux-packages-source: js-packages
-      steps:
-        - uses: actions/checkout@v4
-
-        - run: npm i -g corepack && corepack enable
-
-        - uses: actions/setup-node@v4
-          with:
-            cache: 'yarn'
-            cache-dependency-path: |
-              yarn.lock
-              package.json
-              src/**/package.json
-              test_apps/encore-app/package.json
-
-        - uses: shivammathur/setup-php@v2
-
-        - name: Install root dependencies
-          uses: ramsey/composer-install@v3
-          with:
-            working-directory: ${{ github.workspace }}
-
-        - name: Build root packages
-          run: php .github/build-packages.php
-          working-directory: ${{ github.workspace }}
-
-        # We always install PHP deps because we of the UX Translator, which requires `var/translations` to exists
-        - uses: ramsey/composer-install@v3
-          with:
-            dependency-versions: 'highest'
-            working-directory: test_apps/encore-app
-
-        - if: matrix.ux-packages-source == 'php-vendor'
-          name: Refresh dependencies from vendor/
-          working-directory: test_apps/encore-app
-          run: yarn
-          env:
-            YARN_ENABLE_HARDENED_MODE: 0
-            YARN_ENABLE_IMMUTABLE_INSTALLS: 0
-
-        - if: matrix.ux-packages-source == 'js-packages'
-          name: Install UX JS packages with a JS package manager
-          working-directory: test_apps/encore-app
-          run: |
-            PACKAGES_TO_INSTALL=''
-            for PACKAGE in $(cd ../..; yarn workspaces list --no-private --json); do
-              PACKAGE_DIR=../../$(echo $PACKAGE | jq -r '.location')
-              PACKAGES_TO_INSTALL="$PACKAGES_TO_INSTALL $PACKAGE_DIR"
-            done
-            echo "Installing packages: $PACKAGES_TO_INSTALL"
-            yarn add --dev $PACKAGES_TO_INSTALL
-
-        - name: Ensure UX packages are installed from "${{ matrix.ux-packages-source == 'php-vendor' && 'vendor/symfony/ux-...' || '../../../src/**/assets' }}"
-          working-directory: test_apps/encore-app
-          run: |
-            for PACKAGE in $(cat package.json | jq -c '(.dependencies // {}) + (.devDependencies // {}) | to_entries[] | select(.key | startswith("@symfony/ux-")) | {name: .key, version: .value}'); do
-              PACKAGE_NAME=$(echo $PACKAGE | jq -r '.name')
-              PACKAGE_VERSION=$(echo $PACKAGE | jq -r '.version')
-
-              echo -n "Checking $PACKAGE_NAME@$PACKAGE_VERSION..."
-              if [[ $PACKAGE_VERSION == $EXPECTED_PATTERN* ]]; then
-                echo " OK"
-              else
-                echo " KO"
-                echo "The package version of $PACKAGE_NAME must starts with the pattern (e.g.: $EXPECTED_PATTERN), got $PACKAGE_VERSION instead."
-                exit 1
-              fi
-            done;
-          env:
-            EXPECTED_PATTERN: ${{ matrix.ux-packages-source == 'php-vendor' && 'file:vendor/symfony/*' || '../../src/*' }}
-
-        - name: Run Encore (dev)
-          working-directory: test_apps/encore-app
-          run: yarn encore dev
-
-        - name: Run Encore (prod)
-          working-directory: test_apps/encore-app
-          run: yarn encore production
diff --git a/.github/workflows/functional-tests.yml b/.github/workflows/test-turbo.yml
similarity index 78%
rename from .github/workflows/functional-tests.yml
rename to .github/workflows/test-turbo.yml
index 27990123..cabbea8d 100644
--- a/.github/workflows/functional-tests.yml
+++ b/.github/workflows/test-turbo.yml
@@ -1,4 +1,4 @@
-name: Functional Tests
+name: Symfony UX Turbo
 
 on:
     push:
@@ -9,7 +9,31 @@ on:
             - 'src/Turbo/**'
 
 jobs:
-    turbo-tests:
+    phpstan:
+        runs-on: ubuntu-latest
+        steps:
+            -   name: Checkout
+                uses: actions/checkout@v4
+
+            - name: Setup PHP
+              uses: shivammathur/setup-php@v2
+              with:
+                  php-version: '8.1'
+                  extensions: zip
+
+            - uses: ramsey/composer-install@v3
+              with:
+                  working-directory: src/Turbo
+
+            - name: Install PHPUnit dependencies
+              working-directory: src/Turbo
+              run: vendor/bin/simple-phpunit --version
+
+            - name: PHPStan
+              working-directory: src/Turbo
+              run: vendor/bin/phpstan analyse --no-progress
+
+    tests:
         runs-on: ubuntu-latest
         strategy:
            fail-fast: false
diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
new file mode 100644
index 00000000..7ee1a445
--- /dev/null
+++ b/.github/workflows/test.yaml
@@ -0,0 +1,236 @@
+name: Symfony UX
+
+on:
+    push:
+        paths-ignore:
+            - 'src/*/doc/**'
+            - 'src/**/*.md'
+            - 'ux.symfony.com/**'
+    pull_request:
+        paths-ignore:
+            - 'src/*/doc/**'
+            - 'src/**/*.md'
+            - 'ux.symfony.com/**'
+
+concurrency:
+    group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
+    cancel-in-progress: true
+
+jobs:
+    coding-style-js:
+        name: JavaScript Coding Style
+        runs-on: ubuntu-latest
+        steps:
+            - uses: actions/checkout@v4
+            - run: npm i -g corepack && corepack enable
+            - uses: actions/setup-node@v4
+              with:
+                cache: 'yarn'
+            - run: yarn --immutable
+            - run: yarn ci
+
+    js-dist-current:
+        name: Check for UnBuilt JS Dist Files
+        runs-on: ubuntu-latest
+        steps:
+            - uses: actions/checkout@v4
+            - run: npm i -g corepack && corepack enable
+            - uses: actions/setup-node@v4
+              with:
+                cache: 'yarn'
+                cache-dependency-path: |
+                  yarn.lock
+                  **/package.json
+            - run: yarn --immutable && yarn build
+
+            - name: Check if JS dist files are current
+              run: |
+                if ! git diff --quiet; then
+                  echo "The Git workspace is unclean! Changes detected:"
+                  git status --porcelain
+                  git diff
+                  exit 1
+                else
+                  echo "The Git workspace is clean. No changes detected."
+                fi
+
+    tests-php:
+        runs-on: ubuntu-latest
+        strategy:
+            fail-fast: false
+            matrix:
+                php-version: ['8.1', '8.2', '8.3', '8.4']
+                dependency-version: ['']
+                symfony-version: ['']
+                minimum-stability: ['stable']
+                include:
+                    # dev packages (probably not needed to have multiple such jobs)
+                    -   minimum-stability: 'dev'
+                        php-version: '8.4'
+                    # lowest deps
+                    -   php-version: '8.1'
+                        dependency-version: 'lowest'
+                    # LTS version of Symfony
+                    -   php-version: '8.1'
+                        symfony-version: '6.4.*'
+
+        env:
+            SYMFONY_REQUIRE: ${{ matrix.symfony-version }}
+        steps:
+            - uses: actions/checkout@v4
+
+            - name: Configure environment
+              run: |
+                  echo COLUMNS=120 >> $GITHUB_ENV
+                  echo COMPOSER_MIN_STAB='composer config minimum-stability ${{ matrix.minimum-stability || 'stable' }} --ansi' >> $GITHUB_ENV
+                  echo COMPOSER_UP='composer update ${{ matrix.dependency-version == 'lowest' && '--prefer-lowest' || '' }} --no-progress --no-interaction --ansi' >> $GITHUB_ENV
+                  echo PHPUNIT='vendor/bin/simple-phpunit' >> $GITHUB_ENV
+                  [ 'lowest' = '${{ matrix.dependency-version }}' ] && export SYMFONY_DEPRECATIONS_HELPER=weak
+
+                  # Swup and Typed have no tests, Turbo has its own workflow file
+                  PACKAGES=$(find src/ -mindepth 2 -type f -name composer.json -not -path "*/vendor/*" -printf '%h\n' | sed 's/^src\///' | grep -Ev "Swup|Turbo|Typed" | sort |  tr '\n' ' ')
+                  echo "Packages: $PACKAGES"
+                  echo "PACKAGES=$PACKAGES" >> $GITHUB_ENV
+
+            - name: Setup PHP
+              uses: shivammathur/setup-php@v2
+              with:
+                php-version: ${{ matrix.php-version }}
+                tools: flex
+
+            - name: Install root dependencies
+              run: composer install
+
+            - name: Build root packages
+              run: php .github/build-packages.php
+
+            - name: Run packages tests
+              run: |
+                _run_task() {
+                    local ok=0
+                    local title="$1"
+                    local start=$(date -u +%s)
+                    OUTPUT=$(bash -xc "$2" 2>&1) || ok=$?
+                    local end=$(date -u +%s)
+
+                    if [[ $ok -ne 0 ]]; then
+                      printf "\n%-70s%10s\n" $title $(($end-$start))s
+                      echo "$OUTPUT"
+                      echo "Job exited with: $ok"
+                      echo -e "\n::error::KO $title\\n"
+                    else
+                      printf "::group::%-68s%10s\n" $title $(($end-$start))s
+                      echo "$OUTPUT"
+                      echo -e "\n\\e[32mOK\\e[0m $title\\n\\n::endgroup::"
+                    fi
+
+                    exit $ok
+                }
+                export -f _run_task
+
+                echo "$PACKAGES" | xargs -n1 | parallel -j +3 "_run_task {} '(cd src/{} && $COMPOSER_MIN_STAB && $COMPOSER_UP && $PHPUNIT)'"
+
+    tests-js:
+        runs-on: ubuntu-latest
+        steps:
+            - uses: actions/checkout@v4
+            - run: npm i -g corepack && corepack enable
+            - uses: actions/setup-node@v4
+              with:
+                cache: 'yarn'
+                cache-dependency-path: |
+                  yarn.lock
+                  package.json
+                  src/**/package.json
+            - run: yarn --immutable
+            - run: yarn playwright install
+            - run: yarn test
+
+    test-app-encore-app:
+      name: "Test Apps / Encore (${{ matrix.name}})"
+      runs-on: ubuntu-latest
+      strategy:
+        fail-fast: false
+        matrix:
+          include:
+            - name: Internal, from "vendor/"
+              ux-packages-source: php-vendor
+            - name: External, from "npm add"
+              ux-packages-source: js-packages
+      steps:
+        - uses: actions/checkout@v4
+
+        - run: npm i -g corepack && corepack enable
+
+        - uses: actions/setup-node@v4
+          with:
+            cache: 'yarn'
+            cache-dependency-path: |
+              yarn.lock
+              package.json
+              src/**/package.json
+              test_apps/encore-app/package.json
+
+        - uses: shivammathur/setup-php@v2
+
+        - name: Install root dependencies
+          uses: ramsey/composer-install@v3
+          with:
+            working-directory: ${{ github.workspace }}
+
+        - name: Build root packages
+          run: php .github/build-packages.php
+          working-directory: ${{ github.workspace }}
+
+        # We always install PHP deps because we of the UX Translator, which requires `var/translations` to exists
+        - uses: ramsey/composer-install@v3
+          with:
+            dependency-versions: 'highest'
+            working-directory: test_apps/encore-app
+
+        - if: matrix.ux-packages-source == 'php-vendor'
+          name: Refresh dependencies from vendor/
+          working-directory: test_apps/encore-app
+          run: yarn
+          env:
+            YARN_ENABLE_HARDENED_MODE: 0
+            YARN_ENABLE_IMMUTABLE_INSTALLS: 0
+
+        - if: matrix.ux-packages-source == 'js-packages'
+          name: Install UX JS packages with a JS package manager
+          working-directory: test_apps/encore-app
+          run: |
+            PACKAGES_TO_INSTALL=''
+            for PACKAGE in $(cd ../..; yarn workspaces list --no-private --json); do
+              PACKAGE_DIR=../../$(echo $PACKAGE | jq -r '.location')
+              PACKAGES_TO_INSTALL="$PACKAGES_TO_INSTALL $PACKAGE_DIR"
+            done
+            echo "Installing packages: $PACKAGES_TO_INSTALL"
+            yarn add --dev $PACKAGES_TO_INSTALL
+
+        - name: Ensure UX packages are installed from "${{ matrix.ux-packages-source == 'php-vendor' && 'vendor/symfony/ux-...' || '../../../src/**/assets' }}"
+          working-directory: test_apps/encore-app
+          run: |
+            for PACKAGE in $(cat package.json | jq -c '(.dependencies // {}) + (.devDependencies // {}) | to_entries[] | select(.key | startswith("@symfony/ux-")) | {name: .key, version: .value}'); do
+              PACKAGE_NAME=$(echo $PACKAGE | jq -r '.name')
+              PACKAGE_VERSION=$(echo $PACKAGE | jq -r '.version')
+
+              echo -n "Checking $PACKAGE_NAME@$PACKAGE_VERSION..."
+              if [[ $PACKAGE_VERSION == $EXPECTED_PATTERN* ]]; then
+                echo " OK"
+              else
+                echo " KO"
+                echo "The package version of $PACKAGE_NAME must starts with the pattern (e.g.: $EXPECTED_PATTERN), got $PACKAGE_VERSION instead."
+                exit 1
+              fi
+            done;
+          env:
+            EXPECTED_PATTERN: ${{ matrix.ux-packages-source == 'php-vendor' && 'file:vendor/symfony/*' || '../../src/*' }}
+
+        - name: Run Encore (dev)
+          working-directory: test_apps/encore-app
+          run: yarn encore dev
+
+        - name: Run Encore (prod)
+          working-directory: test_apps/encore-app
+          run: yarn encore production
diff --git a/.github/workflows/unit-tests.yaml b/.github/workflows/unit-tests.yaml
deleted file mode 100644
index f9ee2aab..00000000
--- a/.github/workflows/unit-tests.yaml
+++ /dev/null
@@ -1,90 +0,0 @@
-name: Unit Tests
-
-on:
-    push:
-        paths-ignore:
-            - 'src/*/doc/**'
-            - 'src/**/*.md'
-            - 'ux.symfony.com/**'
-    pull_request:
-        paths-ignore:
-            - 'src/*/doc/**'
-            - 'src/**/*.md'
-            - 'ux.symfony.com/**'
-
-concurrency:
-    group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
-    cancel-in-progress: true
-
-jobs:
-    php:
-        runs-on: ubuntu-latest
-        strategy:
-            fail-fast: false
-            matrix:
-                php-version: ['8.1', '8.2', '8.3', '8.4']
-                dependency-version: ['']
-                symfony-version: ['']
-                minimum-stability: ['stable']
-                include:
-                    # dev packages (probably not needed to have multiple such jobs)
-                    -   minimum-stability: 'dev'
-                        php-version: '8.4'
-                    # lowest deps
-                    -   php-version: '8.1'
-                        dependency-version: 'lowest'
-                    # LTS version of Symfony
-                    -   php-version: '8.1'
-                        symfony-version: '6.4.*'
-
-        env:
-            SYMFONY_REQUIRE: ${{ matrix.symfony-version }}
-        steps:
-            - uses: actions/checkout@v4
-
-            - name: Configure environment
-              run: |
-                  echo COLUMNS=120 >> $GITHUB_ENV
-                  echo COMPOSER_MIN_STAB='composer config minimum-stability ${{ matrix.minimum-stability || 'stable' }} --ansi' >> $GITHUB_ENV
-                  echo COMPOSER_UP='composer update ${{ matrix.dependency-version == 'lowest' && '--prefer-lowest' || '' }} --no-progress --no-interaction --ansi' >> $GITHUB_ENV
-                  echo PHPUNIT='vendor/bin/simple-phpunit' >> $GITHUB_ENV
-                  [ 'lowest' = '${{ matrix.dependency-version }}' ] && export SYMFONY_DEPRECATIONS_HELPER=weak
-
-                  # Swup and Typed have no tests, Turbo has its own workflow file
-                  PACKAGES=$(find src/ -mindepth 2 -type f -name composer.json -not -path "*/vendor/*" -printf '%h\n' | sed 's/^src\///' | grep -Ev "Swup|Turbo|Typed" | sort |  tr '\n' ' ')
-                  echo "Packages: $PACKAGES"
-                  echo "PACKAGES=$PACKAGES" >> $GITHUB_ENV
-
-            - name: Setup PHP
-              uses: shivammathur/setup-php@v2
-              with:
-                php-version: ${{ matrix.php-version }}
-                tools: flex
-
-            - name: Install root dependencies
-              run: composer install
-
-            - name: Build root packages
-              run: php .github/build-packages.php
-
-            - name: Run packages tests
-              run: |
-                source .github/workflows/.utils.sh
-
-                echo "$PACKAGES" | xargs -n1 | parallel -j +3 "_run_task {} '(cd src/{} && $COMPOSER_MIN_STAB && $COMPOSER_UP && $PHPUNIT)'"
-
-    js:
-        runs-on: ubuntu-latest
-        steps:
-            - uses: actions/checkout@v4
-            - run: npm i -g corepack && corepack enable
-            - uses: actions/setup-node@v4
-              with:
-                cache: 'yarn'
-                cache-dependency-path: |
-                  yarn.lock
-                  package.json
-                  src/**/package.json
-            - run: yarn --immutable
-            - run: yarn playwright install
-            - run: yarn test
diff --git a/src/React/assets/test/register_controller.test.tsx b/src/React/assets/test/register_controller.test.tsx
index dcb38082..4595a7d9 100644
--- a/src/React/assets/test/register_controller.test.tsx
+++ b/src/React/assets/test/register_controller.test.tsx
@@ -17,7 +17,6 @@ const createFakeFixturesContext = (): RequireContext => {
     const files: any = {
         './MyJsxComponent.jsx': { default: MyJsxComponent },
         './MyTsxComponent.tsx': { default: MyTsxComponent },
-        './NoDefaultExportComponent.jsx': { default: undefined },
     };
 
     const context = (id: string): any => files[id];
@@ -46,13 +45,4 @@ describe('registerReactControllerComponents', () => {
             'React controller "MyABCComponent" does not exist. Possible values: MyJsxComponent, MyTsxComponent'
         );
     });
-
-    it('throws when no default export found in imported module', () => {
-        registerReactControllerComponents(createFakeFixturesContext());
-        const resolveComponent = (window as any).resolveReactComponent;
-
-        expect(() => resolveComponent('NoDefaultExportComponent')).toThrow(
-            'React controller "NoDefaultExportComponent" could not be resolved. Ensure the module exports the controller as a default export.'
-        );
-    });
 });
diff --git a/src/TwigComponent/tests/Integration/Twig/ComponentParserTest.php b/src/TwigComponent/tests/Integration/Twig/ComponentParserTest.php
index c5799440..476ee4f2 100644
--- a/src/TwigComponent/tests/Integration/Twig/ComponentParserTest.php
+++ b/src/TwigComponent/tests/Integration/Twig/ComponentParserTest.php
@@ -13,7 +13,6 @@ namespace Symfony\UX\TwigComponent\Tests\Integration\Twig;
 
 use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
 use Twig\Environment;
-use Twig\Error\SyntaxError;
 use Twig\Loader\ArrayLoader;
 use Twig\TemplateWrapper;
 
@@ -29,12 +28,12 @@ final class ComponentParserTest extends KernelTestCase
      */
     public function testAcceptTwigComponentTagWithValidComponentName(string $name): void
     {
-        $environment = $this->createEnvironment();
+        $environment = self::createEnvironment();
         $source = str_replace('XXX', $name, "{% component 'XXX' %}{% endcomponent %}");
 
         $template = $environment->createTemplate($source);
 
-        $this->assertInstanceOf(TemplateWrapper::class, $template);
+        self::assertInstanceOf(TemplateWrapper::class, $template);
     }
 
     /**
@@ -42,12 +41,12 @@ final class ComponentParserTest extends KernelTestCase
      */
     public function testAcceptHtmlComponentTagWithValidComponentName(string $name): void
     {
-        $environment = $this->createEnvironment();
+        $environment = self::createEnvironment();
         $source = \sprintf('<twig:%s></twig:%s>', $name, $name);
 
         $template = $environment->createTemplate($source);
 
-        $this->assertInstanceOf(TemplateWrapper::class, $template);
+        self::assertInstanceOf(TemplateWrapper::class, $template);
     }
 
     /**
@@ -55,24 +54,12 @@ final class ComponentParserTest extends KernelTestCase
      */
     public function testAcceptHtmlSelfClosingComponentTagWithValidComponentName(string $name): void
     {
-        $environment = $this->createEnvironment();
+        $environment = self::createEnvironment();
         $source = \sprintf('<twig:%s />', $name);
 
         $template = $environment->createTemplate($source);
 
-        $this->assertInstanceOf(TemplateWrapper::class, $template);
-    }
-
-    public function testItThrowsWhenComponentNameCannotBeParsed(): void
-    {
-        $environment = $this->createEnvironment();
-        $source = '{% component [] %}{% endcomponent %}';
-
-        $this->expectException(SyntaxError::class);
-        $this->expectExceptionMessage('Could not parse component name in "foo.html.twig');
-        $this->expectExceptionMessage(')" at line 1.');
-
-        $environment->createTemplate($source, 'foo.html.twig');
+        self::assertInstanceOf(TemplateWrapper::class, $template);
     }
 
     public static function provideValidComponentNames(): iterable