Repository: symfony/ux (php)

Issue:
[Stimulus][TwigComponent] Syntax error escaping objects since 2.25.0
Since 2.25.x using the documented way to merge a stimulus_controller with a twig component attributes results in a javascript syntax error when an object is passed as parameter.

I reproduced this on a new test project, using a simple component with a dummy object like this:
```
<div{{ attributes.defaults(stimulus_controller('test', { myObject: {someProperty: 'test'} })) }}>
    KO: javascript error, unable to parse the myObject attribute
</div>
```

With the stimulus controller waiting an object for "myObject", it gives the following syntax error:
`Uncaught (in promise) SyntaxError: Expected property name or '}' in JSON at position 1 (line 1 column 2)`

It seems that using this syntax to merge the attributes & stimulus_controller escape the stimulus controller attributes in a "bad" way that makes the controller unable to parse it by default.

As a workaround, using toArray on the stimulus_controller result like this seems to work:
```
<div{{ attributes.defaults(stimulus_controller('test', { myObject: {someProperty: 'test'} }).toArray()) }}>
    OK: attributes parsed
</div>
```





Pull request description:
[TwigComponent] Fix `ComponentAttributes` rendering when using `StimulusAttributes` as default attributes
| Q             | A
| ------------- | ---
| Bug fix?      | yes
| New feature?  | no <!-- please update src/**/CHANGELOG.md files -->
| Docs?         | yes <!-- required for new features -->
| Issues        | Fix #2756 <!-- prefix each issue number with "Fix #", no need to create an issue if none exist, explain below instead -->
| License       | MIT

<!--
Replace this notice by a description of your feature/bugfix.
This will help reviewers and should be a good start for the documentation.

Additionally (see https://symfony.com/releases):
 - Always add tests and ensure they pass.
 - For new features, provide some code snippets to help understand usage.
 - Features and deprecations must be submitted against branch main.
 - Update/add documentation as required (we can help!)
 - Changelog entry should follow https://symfony.com/doc/current/contributing/code/conventions.html#writing-a-changelog-entry
 - Never break backward compatibility (see https://symfony.com/bc).
-->

We missed the case where we can inject `StimulusAttributes` when using `attributes.defaults()`, as documented in https://symfony.com/bundles/ux-twig-component/current/index.html#component-attributes

It looks like our tests weren't solid enough, since we asserted on `all()` instead of the rendered string. 

Patch:
diff --git a/src/TwigComponent/CHANGELOG.md b/src/TwigComponent/CHANGELOG.md
index 770a5e15..8ddd89fb 100644
--- a/src/TwigComponent/CHANGELOG.md
+++ b/src/TwigComponent/CHANGELOG.md
@@ -1,5 +1,9 @@
 # CHANGELOG
 
+## 2.25.2
+
+-   Fix `ComponentAttributes` rendering when using `StimulusAttributes` as default attributes
+
 ## 2.25.1
 
 -   [SECURITY] `ComponentAttributes` now requires a `Twig\Runtime\EscaperRuntime`
diff --git a/src/TwigComponent/src/ComponentAttributes.php b/src/TwigComponent/src/ComponentAttributes.php
index 140e39f8..82f9a5cd 100644
--- a/src/TwigComponent/src/ComponentAttributes.php
+++ b/src/TwigComponent/src/ComponentAttributes.php
@@ -144,7 +144,7 @@ final class ComponentAttributes implements \Stringable, \IteratorAggregate, \Cou
     public function defaults(iterable $attributes): self
     {
         if ($attributes instanceof StimulusAttributes) {
-            $attributes = $attributes->toEscapedArray();
+            $attributes = $attributes->toArray();
         }
 
         if ($attributes instanceof \Traversable) {


Test patch:
diff --git a/src/TwigComponent/tests/Unit/ComponentAttributesTest.php b/src/TwigComponent/tests/Unit/ComponentAttributesTest.php
index 4e48ab85..377822c3 100644
--- a/src/TwigComponent/tests/Unit/ComponentAttributesTest.php
+++ b/src/TwigComponent/tests/Unit/ComponentAttributesTest.php
@@ -143,7 +143,7 @@ final class ComponentAttributesTest extends TestCase
         ], new EscaperRuntime());
 
         $stimulusAttributes = new StimulusAttributes(new Environment(new ArrayLoader()));
-        $stimulusAttributes->addController('foo', ['name' => 'ryan', 'some_array' => ['a', 'b']]);
+        $stimulusAttributes->addController('foo', ['name' => 'ryan', 'some_array' => ['a', 'b'], 'some_array_with_keys' => ['key1' => 'value1', 'key2' => 'value2']]);
         $attributes = $attributes->defaults($stimulusAttributes);
 
         $this->assertEquals([
@@ -151,8 +151,10 @@ final class ComponentAttributesTest extends TestCase
             'data-controller' => 'foo live',
             'data-live-data-value' => '{}',
             'data-foo-name-value' => 'ryan',
-            'data-foo-some-array-value' => '[&quot;a&quot;,&quot;b&quot;]',
+            'data-foo-some-array-value' => '["a","b"]',
+            'data-foo-some-array-with-keys-value' => '{"key1":"value1","key2":"value2"}',
         ], $attributes->all());
+        $this->assertSame(' data-controller="foo live" data-foo-name-value="ryan" data-foo-some-array-value="[&quot;a&quot;,&quot;b&quot;]" data-foo-some-array-with-keys-value="{&quot;key1&quot;:&quot;value1&quot;,&quot;key2&quot;:&quot;value2&quot;}" class="foo" data-live-data-value="{}"', (string) $attributes);
     }
 
     public function testCanAddStimulusActionViaStimulusAttributes(): void
@@ -175,6 +177,7 @@ final class ComponentAttributesTest extends TestCase
             'class' => 'foo',
             'data-action' => 'foo#barMethod live#foo',
         ], $attributes->all());
+        $this->assertSame(' data-action="foo#barMethod live#foo" class="foo"', (string) $attributes);
     }
 
     public function testBooleanBehaviour(): void