Skip to content

Commit c3da95c

Browse files
committed
Adding a rich functional test
1 parent 2ac924c commit c3da95c

File tree

10 files changed

+323
-2
lines changed

10 files changed

+323
-2
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
composer.lock
33
.php-cs-fixer.cache
44
.phpunit.result.cache
5+
/tests/fixtures/var

composer.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
"require-dev": {
1818
"symfony/framework-bundle": "^6.3",
1919
"symfony/phpunit-bridge": "^6.3",
20-
"phpstan/phpstan": "1.11.x-dev"
20+
"phpstan/phpstan": "1.11.x-dev",
21+
"zenstruck/browser": "1.x-dev",
22+
"symfony/twig-bundle": "7.0.x-dev"
2123
},
2224
"minimum-stability": "dev",
2325
"autoload": {

src/DynamicFormBuilder.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,6 @@ public function clearDataOnTransformationError(FormEvent $event): void
124124
if ($form->get('__dynamic_error')->isValid()) {
125125
$form->get('__dynamic_error')->addError(new FormError('Some dynamic fields have errors.'));
126126
}
127-
dump($form);
128127
}
129128
}
130129

tests/FunctionalTest.php

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
<?php
2+
3+
namespace Symfonycasts\DynamicForms\Tests;
4+
5+
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
6+
use Symfonycasts\DynamicForms\Tests\fixtures\DynamicFormsTestKernel;
7+
use Zenstruck\Browser\Test\HasBrowser;
8+
9+
class FunctionalTest extends KernelTestCase
10+
{
11+
use HasBrowser;
12+
13+
public function testDynamicFields()
14+
{
15+
$browser = $this->browser();
16+
$browser->visit('/form')
17+
// check for the hidden field
18+
->assertSeeElement('#test_dynamic_form___dynamic_error')
19+
->assertSee('Is Form Valid: no')
20+
// Breakfast is the pre-selected meal
21+
->assertSee('What is for Breakfast?')
22+
->assertContains('<option value="bacon">')
23+
->assertNotContains('<option value="pizza">')
24+
->assertNotContains('What size pizza?')
25+
;
26+
27+
// change the meal to dinner
28+
$browser->selectFieldOption('Meal', 'Dinner')
29+
->click('Submit Form')
30+
// changing the field doesn't cause any issues
31+
->assertSee('Is Form Valid: yes')
32+
->assertNotContains('<option value="bacon">')
33+
->assertContains('<option value="pizza">')
34+
->assertNotContains('What size pizza?')
35+
;
36+
37+
// now select Pizza!
38+
$browser->selectFieldOption('Main food', 'Pizza')
39+
->click('Submit Form')
40+
->assertSee('Is Form Valid: yes')
41+
->assertContains('<option value="pizza" selected="selected">')
42+
->assertContains('What size pizza?')
43+
;
44+
45+
// select the size
46+
$browser->selectFieldOption('Pizza size', '14 inch')
47+
->click('Submit Form')
48+
->assertSee('Is Form Valid: yes')
49+
->assertContains('<option value="pizza" selected="selected">')
50+
->assertContains('<option value="14" selected="selected">')
51+
;
52+
53+
// now change the meal to breakfast
54+
$browser->selectFieldOption('Meal', 'Breakfast')
55+
->click('Submit Form')
56+
// form is not valid: the mainFood submitted an invalid value
57+
->assertSee('Is Form Valid: no')
58+
->assertContains('<option value="bacon">')
59+
->assertNotContains('<option value="pizza"')
60+
->assertNotContains('What size pizza?')
61+
;
62+
63+
// select a valid food for breakfast
64+
$browser->selectFieldOption('Main food', 'Bacon')
65+
->click('Submit Form')
66+
// form is valid again
67+
->assertSee('Is Form Valid: yes')
68+
;
69+
70+
// change the meal again
71+
$browser->selectFieldOption('Meal', 'Lunch')
72+
->click('Submit Form')
73+
// form is not valid: the mainFood=bacon is invalid for lunch
74+
->assertSee('Is Form Valid: no')
75+
;
76+
}
77+
78+
79+
protected static function getKernelClass(): string
80+
{
81+
return DynamicFormsTestKernel::class;
82+
}
83+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<?php
2+
3+
namespace Symfonycasts\DynamicForms\Tests\fixtures;
4+
5+
use Psr\Log\NullLogger;
6+
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
7+
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
8+
use Symfony\Bundle\TwigBundle\TwigBundle;
9+
use Symfony\Component\DependencyInjection\ContainerBuilder;
10+
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
11+
use Symfony\Component\Form\FormFactoryInterface;
12+
use Symfony\Component\HttpFoundation\Request;
13+
use Symfony\Component\HttpFoundation\Response;
14+
use Symfony\Component\HttpKernel\Kernel;
15+
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
16+
use Symfonycasts\DynamicForms\Tests\fixtures\Enum\DynamicTestMeal;
17+
use Twig\Environment;
18+
19+
class DynamicFormsTestKernel extends Kernel
20+
{
21+
use MicroKernelTrait;
22+
23+
public function form(Environment $twig, FormFactoryInterface $formFactory, Request $request): Response
24+
{
25+
$form = $formFactory->create(TestDynamicForm::class, [
26+
'meal' => DynamicTestMeal::Breakfast,
27+
]);
28+
$form->handleRequest($request);
29+
30+
return new Response($twig->render('form.html.twig', [
31+
'form' => $form->createView(),
32+
'isFormValid' => $form->isSubmitted() && $form->isValid(),
33+
]));
34+
}
35+
36+
public function registerBundles(): iterable
37+
{
38+
return [
39+
new FrameworkBundle(),
40+
new TwigBundle(),
41+
];
42+
}
43+
44+
public function getProjectDir(): string
45+
{
46+
return __DIR__;
47+
}
48+
49+
protected function configureContainer(ContainerConfigurator $container): void
50+
{
51+
$container->extension('framework', [
52+
'secret' => 'foo000',
53+
'http_method_override' => false,
54+
'test' => true,
55+
]);
56+
57+
$container->extension('twig', [
58+
'default_path' => '%kernel.project_dir%/templates',
59+
]);
60+
}
61+
62+
protected function build(ContainerBuilder $container): void
63+
{
64+
$container->register('logger', NullLogger::class);
65+
}
66+
67+
protected function configureRoutes(RoutingConfigurator $routes): void
68+
{
69+
$routes->add('form', '/form')->controller('kernel::form');
70+
}
71+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
namespace Symfonycasts\DynamicForms\Tests\fixtures\Enum;
4+
5+
enum DynamicTestFood: string
6+
{
7+
case Eggs = 'eggs';
8+
case Bacon = 'bacon';
9+
case Strawberries = 'strawberries';
10+
case Croissant = 'croissant';
11+
case Bagel = 'bagel';
12+
case Kiwi = 'kiwi';
13+
case Avocado = 'avocado';
14+
case Waffles = 'waffles';
15+
case Pancakes = 'pancakes';
16+
case Salad = 'salad';
17+
case Tea = 'tea️';
18+
case Sandwich = 'sandwich';
19+
case Cheese = 'cheese';
20+
case Sushi = 'sushi';
21+
case Pizza = 'pizza';
22+
case Pint = 'pint';
23+
case Pasta = 'pasta';
24+
25+
public function getReadable(): string
26+
{
27+
return match ($this) {
28+
self::Eggs => 'Eggs 🍳',
29+
self::Bacon => 'Bacon 🥓',
30+
self::Strawberries => 'Strawberries 🍓',
31+
self::Croissant => 'Croissant 🥐',
32+
self::Bagel => 'Bagel 🥯',
33+
self::Kiwi => 'Kiwi 🥝',
34+
self::Avocado => 'Avocado 🥑',
35+
self::Waffles => 'Waffles 🧇',
36+
self::Pancakes => 'Pancakes 🥞',
37+
self::Salad => 'Salad 🥙',
38+
self::Tea => 'Tea ☕️',
39+
self::Sandwich => 'Sandwich 🥪',
40+
self::Cheese => 'Cheese 🧀',
41+
self::Sushi => 'Sushi 🍱',
42+
self::Pizza => 'Pizza 🍕',
43+
self::Pint => 'A Pint 🍺',
44+
self::Pasta => 'Pasta 🍝',
45+
};
46+
}
47+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
namespace Symfonycasts\DynamicForms\Tests\fixtures\Enum;
4+
5+
enum DynamicTestMeal: string
6+
{
7+
case Breakfast = 'breakfast';
8+
case SecondBreakfast = 'second_breakfast';
9+
case Elevenses = 'elevenses';
10+
case Lunch = 'lunch';
11+
case Dinner = 'dinner';
12+
13+
public function getReadable(): string
14+
{
15+
return match ($this) {
16+
self::Breakfast => 'Breakfast',
17+
self::SecondBreakfast => 'Second Breakfast',
18+
self::Elevenses => 'Elevenses',
19+
self::Lunch => 'Lunch',
20+
self::Dinner => 'Dinner',
21+
};
22+
}
23+
24+
/**
25+
* @return list<DynamicTestFood>
26+
*/
27+
public function getFoodChoices(): array
28+
{
29+
return match ($this) {
30+
self::Breakfast => [DynamicTestFood::Eggs, DynamicTestFood::Bacon, DynamicTestFood::Strawberries, DynamicTestFood::Croissant],
31+
self::SecondBreakfast => [DynamicTestFood::Bagel, DynamicTestFood::Kiwi, DynamicTestFood::Avocado, DynamicTestFood::Waffles],
32+
self::Elevenses => [DynamicTestFood::Pancakes, DynamicTestFood::Strawberries, DynamicTestFood::Tea],
33+
self::Lunch => [DynamicTestFood::Sandwich, DynamicTestFood::Cheese, DynamicTestFood::Sushi],
34+
self::Dinner => [DynamicTestFood::Pizza, DynamicTestFood::Pint, DynamicTestFood::Pasta],
35+
};
36+
}
37+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
namespace Symfonycasts\DynamicForms\Tests\fixtures\Enum;
4+
5+
enum DynamicTestPizzaSize: int
6+
{
7+
case Small = 12;
8+
case Medium = 14;
9+
case Large = 16;
10+
11+
public function getReadable(): string
12+
{
13+
return match ($this) {
14+
self::Small => '12 inch',
15+
self::Medium => '14 inch',
16+
self::Large => '16 inch',
17+
};
18+
}
19+
}

tests/fixtures/TestDynamicForm.php

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php
2+
3+
namespace Symfonycasts\DynamicForms\Tests\fixtures;
4+
5+
use Symfony\Component\Form\AbstractType;
6+
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
7+
use Symfony\Component\Form\Extension\Core\Type\EnumType;
8+
use Symfony\Component\Form\FormBuilderInterface;
9+
use Symfonycasts\DynamicForms\DependentField;
10+
use Symfonycasts\DynamicForms\DynamicFormBuilder;
11+
use Symfonycasts\DynamicForms\Tests\fixtures\Enum\DynamicTestFood;
12+
use Symfonycasts\DynamicForms\Tests\fixtures\Enum\DynamicTestMeal;
13+
use Symfonycasts\DynamicForms\Tests\fixtures\Enum\DynamicTestPizzaSize;
14+
15+
class TestDynamicForm extends AbstractType
16+
{
17+
public function buildForm(FormBuilderInterface $builder, array $options): void
18+
{
19+
$builder = new DynamicFormBuilder($builder);
20+
21+
$builder->add('meal', EnumType::class, [
22+
'class' => DynamicTestMeal::class,
23+
'choice_label' => fn (DynamicTestMeal $meal): string => $meal->getReadable(),
24+
'placeholder' => 'Which meal is it?',
25+
]);
26+
27+
$builder->add('upperCasePizzaSizes', CheckboxType::class, [
28+
'mapped' => false,
29+
]);
30+
31+
// addDynamic(string $name, array $dependencies, callable $callback): self
32+
$builder->addDependent('mainFood', ['meal'], function(DependentField $field, ?DynamicTestMeal $meal) {
33+
$field->add(EnumType::class, [
34+
'class' => DynamicTestFood::class,
35+
'placeholder' => null === $meal ? 'Select a meal first' : sprintf('What is for %s?', $meal->getReadable()),
36+
'choices' => $meal?->getFoodChoices(),
37+
'choice_label' => fn (DynamicTestFood $food): string => $food->getReadable(),
38+
'disabled' => null === $meal,
39+
]);
40+
});
41+
42+
$builder->addDependent('pizzaSize', ['mainFood', 'upperCasePizzaSizes'], function(DependentField $field, ?DynamicTestFood $food, bool $upperCasePizzaSizes) {
43+
if (DynamicTestFood::Pizza !== $food) {
44+
return;
45+
}
46+
47+
$field->add(EnumType::class, [
48+
'class' => DynamicTestPizzaSize::class,
49+
'placeholder' => $upperCasePizzaSizes ? strtoupper('What size pizza?') : 'What size pizza?',
50+
'choice_label' => fn (DynamicTestPizzaSize $pizzaSize): string => $upperCasePizzaSizes ? strtoupper($pizzaSize->getReadable()) : $pizzaSize->getReadable(),
51+
'required' => true,
52+
]);
53+
});
54+
}
55+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Is Form Valid: {{ isFormValid ? 'yes' : 'no' }}
2+
3+
{{ form_start(form) }}
4+
{{ form_widget(form) }}
5+
6+
<button type="submit">Submit Form</button>
7+
{{ form_end(form) }}

0 commit comments

Comments
 (0)