Skip to content

Commit 0c42b52

Browse files
committed
Add tests
1 parent a67fbb0 commit 0c42b52

File tree

4 files changed

+287
-0
lines changed

4 files changed

+287
-0
lines changed
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\UX\LiveComponent\Tests\Fixtures\Component;
13+
14+
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
15+
use Symfony\Component\Form\FormInterface;
16+
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
17+
use Symfony\UX\LiveComponent\DefaultActionTrait;
18+
use Symfony\UX\LiveComponent\LiveCollectionTrait;
19+
use Symfony\UX\LiveComponent\Tests\Fixtures\Dto\BlogPost;
20+
use Symfony\UX\LiveComponent\Tests\Fixtures\Dto\Comment;
21+
use Symfony\UX\LiveComponent\Tests\Fixtures\Form\BlogPostFormLiveCollectionType;
22+
23+
#[AsLiveComponent('form_with_live_collection_type', template: 'components/form_with_collection_type.html.twig')]
24+
class FormWithLiveCollectionTypeComponent extends AbstractController
25+
{
26+
use LiveCollectionTrait;
27+
use DefaultActionTrait;
28+
29+
public BlogPost $post;
30+
31+
public function __construct()
32+
{
33+
$this->post = new BlogPost();
34+
// start with 1 comment
35+
$this->post->comments[] = new Comment();
36+
}
37+
38+
protected function instantiateForm(): FormInterface
39+
{
40+
return $this->createForm(BlogPostFormLiveCollectionType::class, $this->post);
41+
}
42+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace Symfony\UX\LiveComponent\Tests\Fixtures\Form;
15+
16+
use Symfony\Component\Form\AbstractType;
17+
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
18+
use Symfony\Component\Form\Extension\Core\Type\TextType;
19+
use Symfony\Component\Form\FormBuilderInterface;
20+
use Symfony\Component\OptionsResolver\OptionsResolver;
21+
use Symfony\UX\LiveComponent\Form\Type\LiveCollectionType;
22+
use Symfony\UX\LiveComponent\Tests\Fixtures\Dto\BlogPost;
23+
24+
class BlogPostFormLiveCollectionType extends AbstractType
25+
{
26+
public function buildForm(FormBuilderInterface $builder, array $options)
27+
{
28+
$builder
29+
->add('title', TextType::class)
30+
->add('content', TextareaType::class)
31+
->add('comments', LiveCollectionType::class, [
32+
'entry_type' => CommentFormType::class,
33+
'allow_add' => true,
34+
'allow_delete' => true,
35+
])
36+
;
37+
}
38+
39+
public function configureOptions(OptionsResolver $resolver)
40+
{
41+
$resolver->setDefaults([
42+
'csrf_protection' => false,
43+
'data_class' => BlogPost::class,
44+
]);
45+
}
46+
47+
public function getBlockPrefix()
48+
{
49+
return 'blog_post_form';
50+
}
51+
}

src/LiveComponent/tests/Functional/Form/ComponentWithFormTest.php

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,4 +233,92 @@ public function testHandleCheckboxChanges(): void
233233
})
234234
;
235235
}
236+
237+
public function testLiveCollectionTypeAddButtonsByDefault(): void
238+
{
239+
$dehydrated = $this->dehydrateComponent($this->mountComponent('form_with_live_collection_type'));
240+
241+
$this->browser()
242+
->get('/_components/form_with_live_collection_type?data='.urlencode(json_encode($dehydrated)))
243+
->use(function (HtmlResponse $response) use (&$dehydrated, &$token) {
244+
// mimic user typing
245+
$dehydrated['blog_post_form']['content'] = 'changed description by user';
246+
$dehydrated['validatedFields'] = ['blog_post_form.content'];
247+
$token = $response->crawler()->filter('div')->first()->attr('data-live-csrf-value');
248+
})
249+
->assertContains('<button type="button" id="blog_post_form_comments_add"')
250+
->assertContains('<button type="button" id="blog_post_form_comments_0_delete"')
251+
;
252+
}
253+
254+
public function testLiveCollectionTypeFieldsAddedAndRemoved(): void
255+
{
256+
$dehydrated = $this->dehydrateComponent($this->mountComponent('form_with_live_collection_type'));
257+
$token = null;
258+
259+
$this->browser()
260+
->get('/_components/form_with_live_collection_type?data='.urlencode(json_encode($dehydrated)))
261+
->use(function (HtmlResponse $response) use (&$dehydrated, &$token) {
262+
// mimic user typing
263+
$dehydrated['blog_post_form']['content'] = 'changed description by user';
264+
$dehydrated['validatedFields'] = ['blog_post_form.content'];
265+
$token = $response->crawler()->filter('div')->first()->attr('data-live-csrf-value');
266+
})
267+
// post to action, which will add a new embedded comment
268+
->post('/_components/form_with_live_collection_type/addCollectionItem?'.http_build_query(['args' => 'name=blog_post_form[comments]']), [
269+
'body' => json_encode($dehydrated),
270+
'headers' => ['X-CSRF-TOKEN' => $token],
271+
])
272+
->assertStatus(422)
273+
// look for original embedded form
274+
->assertContains('<textarea id="blog_post_form_comments_0_content"')
275+
// look for new embedded form
276+
->assertContains('<textarea id="blog_post_form_comments_1_content"')
277+
// changed text is still present
278+
->assertContains('changed description by user</textarea>')
279+
// check that validation happened and stuck
280+
->assertContains('The content field is too short')
281+
// make sure the title field did not suddenly become validated
282+
->assertNotContains('The title field should not be blank')
283+
->use(function (Crawler $crawler) use (&$dehydrated, &$token) {
284+
$div = $crawler->filter('[data-controller="live"]');
285+
$liveData = json_decode($div->attr('data-live-data-value'), true);
286+
// make sure the 2nd collection type was initialized, that it didn't
287+
// just "keep" the empty array that we set it to in the component
288+
$this->assertEquals(
289+
[
290+
['content' => ''],
291+
['content' => ''],
292+
],
293+
$liveData['blog_post_form']['comments']
294+
);
295+
296+
// grab the latest live data
297+
$dehydrated = $liveData;
298+
// fake that this field was being validated
299+
$dehydrated['validatedFields'][] = 'blog_post_form.0.comments.content';
300+
$token = $div->attr('data-live-csrf-value');
301+
})
302+
303+
// post to action, which will remove the original embedded comment
304+
->post('/_components/form_with_live_collection_type/removeCollectionItem?'.http_build_query(['args' => 'name=blog_post_form[comments]&index=0']), [
305+
'body' => json_encode($dehydrated),
306+
'headers' => ['X-CSRF-TOKEN' => $token],
307+
])
308+
->assertStatus(422)
309+
// the original embedded form should be gone
310+
->assertNotContains('<textarea id="blog_post_form_comments_0_content"')
311+
// the added one should still be present
312+
->assertContains('<textarea id="blog_post_form_comments_1_content"')
313+
->use(function (Crawler $crawler) {
314+
$div = $crawler->filter('[data-controller="live"]');
315+
$liveData = json_decode($div->attr('data-live-data-value'), true);
316+
// the embedded validated field should be gone, since its data is gone
317+
$this->assertEquals(
318+
['blog_post_form.content'],
319+
$liveData['validatedFields']
320+
);
321+
})
322+
;
323+
}
236324
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\UX\LiveComponent\Tests\Unit\Form\Type;
13+
14+
use Symfony\Component\Form\Extension\Core\Type\TextType;
15+
use Symfony\Component\Form\Test\TypeTestCase;
16+
use Symfony\UX\LiveComponent\Form\Type\LiveCollectionType;
17+
18+
/**
19+
* @author Gábor Egyed <[email protected]>
20+
*
21+
* @experimental
22+
*/
23+
final class LiveCollectionTypeTest extends TypeTestCase
24+
{
25+
public function testAddButtonPrototypeDefaultBlockPrefixes()
26+
{
27+
$collectionView = $this->factory->createNamed('fields', LiveCollectionType::class, [], [
28+
'allow_add' => true,
29+
])
30+
->createView()
31+
;
32+
33+
$expectedBlockPrefixes = [
34+
'button',
35+
'live_collection_button_add',
36+
'_fields_add',
37+
];
38+
39+
$this->assertCount(0, $collectionView);
40+
$this->assertSame($expectedBlockPrefixes, $collectionView->vars['button_add_prototype']->vars['block_prefixes']);
41+
}
42+
43+
public function testAddButtonPrototypeBlockPrefixesWithCustomBlockPrefix()
44+
{
45+
$collectionView = $this->factory->createNamed('fields', LiveCollectionType::class, [], [
46+
'allow_add' => true,
47+
'button_add_options' => ['block_prefix' => 'custom_prefix'],
48+
])
49+
->createView()
50+
;
51+
52+
$expectedBlockPrefixes = [
53+
'button',
54+
'live_collection_button_add',
55+
'custom_prefix',
56+
'_fields_add',
57+
];
58+
59+
$this->assertCount(0, $collectionView);
60+
$this->assertSame($expectedBlockPrefixes, $collectionView->vars['button_add_prototype']->vars['block_prefixes']);
61+
}
62+
63+
public function testDeleteButtonPrototypeDefaultBlockPrefixes()
64+
{
65+
$collectionView = $this->factory->createNamed('tags', LiveCollectionType::class, [
66+
'tags' => ['tag01'],
67+
], [
68+
'entry_type' => TextType::class,
69+
'allow_delete' => true,
70+
])
71+
->createView()
72+
;
73+
74+
$expectedBlockPrefixes = [
75+
'button',
76+
'live_collection_button_delete',
77+
'_tags_entry_delete',
78+
];
79+
80+
$this->assertCount(1, $collectionView);
81+
$this->assertSame($expectedBlockPrefixes, $collectionView['tags']->vars['button_delete_prototype']->vars['block_prefixes']);
82+
}
83+
84+
public function testDeleteButtonPrototypeBlockPrefixesWithCustomBlockPrefix()
85+
{
86+
$collectionView = $this->factory->createNamed('tags', LiveCollectionType::class, [
87+
'tags' => ['tag01'],
88+
], [
89+
'entry_type' => TextType::class,
90+
'allow_delete' => true,
91+
'button_delete_options' => ['block_prefix' => 'custom_prefix'],
92+
])
93+
->createView()
94+
;
95+
96+
$expectedBlockPrefixes = [
97+
'button',
98+
'live_collection_button_delete',
99+
'custom_prefix',
100+
'_tags_entry_delete',
101+
];
102+
103+
$this->assertCount(1, $collectionView);
104+
$this->assertSame($expectedBlockPrefixes, $collectionView['tags']->vars['button_delete_prototype']->vars['block_prefixes']);
105+
}
106+
}

0 commit comments

Comments
 (0)