Skip to content

Commit ca54aec

Browse files
#160 - Test a field constraint given by its FQCN
Review of #168: documented but not covered. A project-specific constraint (tests/App/Validator/Even) given by its FQCN in form.context.<key>.options .constraints: - ProcessConfigurationsManagerTest: built by ConstraintLoader, with and without options; - Functional/LaunchActionTest (new test.form_custom_constraint process): the violation is displayed under the field and nothing is dispatched, a valid value is dispatched. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 686f812 commit ca54aec

5 files changed

Lines changed: 132 additions & 0 deletions

File tree

‎tests/App/Validator/Even.php‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the CleverAge/UiProcessBundle package.
7+
*
8+
* Copyright (c) Clever-Age
9+
*
10+
* For the full copyright and license information, please view the LICENSE
11+
* file that was distributed with this source code.
12+
*/
13+
14+
namespace CleverAge\UiProcessBundle\Tests\App\Validator;
15+
16+
use Symfony\Component\Validator\Attribute\HasNamedArguments;
17+
use Symfony\Component\Validator\Constraint;
18+
19+
/**
20+
* A project-specific constraint, given by its FQCN in the process configurations of the tests.
21+
*/
22+
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD)]
23+
class Even extends Constraint
24+
{
25+
public string $message = 'The value {{ value }} is not even.';
26+
27+
/**
28+
* @param string[]|null $groups
29+
*/
30+
#[HasNamedArguments]
31+
public function __construct(?string $message = null, ?array $groups = null, mixed $payload = null)
32+
{
33+
parent::__construct(groups: $groups, payload: $payload);
34+
$this->message = $message ?? $this->message;
35+
}
36+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the CleverAge/UiProcessBundle package.
7+
*
8+
* Copyright (c) Clever-Age
9+
*
10+
* For the full copyright and license information, please view the LICENSE
11+
* file that was distributed with this source code.
12+
*/
13+
14+
namespace CleverAge\UiProcessBundle\Tests\App\Validator;
15+
16+
use Symfony\Component\Validator\Constraint;
17+
use Symfony\Component\Validator\ConstraintValidator;
18+
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
19+
20+
class EvenValidator extends ConstraintValidator
21+
{
22+
public function validate(mixed $value, Constraint $constraint): void
23+
{
24+
if (!$constraint instanceof Even) {
25+
throw new UnexpectedTypeException($constraint, Even::class);
26+
}
27+
if (null === $value || (\is_int($value) && 0 === $value % 2)) {
28+
return;
29+
}
30+
31+
$this->context->buildViolation($constraint->message)
32+
->setParameter('{{ value }}', $this->formatValue($value))
33+
->addViolation();
34+
}
35+
}

‎tests/App/config/packages.yaml‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,21 @@ clever_age_process:
154154
tasks:
155155
data:
156156
service: '@CleverAge\ProcessBundle\Task\DummyTask'
157+
test.form_custom_constraint:
158+
description: 'Process launched with the UI form, with a field constraint given by its FQCN'
159+
options:
160+
ui:
161+
ui_launch_mode: form
162+
form:
163+
context:
164+
count:
165+
type: Symfony\Component\Form\Extension\Core\Type\IntegerType
166+
options:
167+
constraints:
168+
- CleverAge\UiProcessBundle\Tests\App\Validator\Even: ~
169+
tasks:
170+
data:
171+
service: '@CleverAge\ProcessBundle\Task\DummyTask'
157172
test.form_strict:
158173
description: 'Process launched with the UI form, without free context'
159174
entry_point: data

‎tests/Functional/LaunchActionTest.php‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,33 @@ public function testInvalidChoice(): void
268268
self::assertSame([], $this->getDispatchedMessages());
269269
}
270270

271+
/**
272+
* A project-specific constraint, given by its FQCN: its violation is displayed under the field.
273+
*/
274+
public function testFieldConstraintClass(): void
275+
{
276+
$this->login();
277+
278+
$crawler = $this->client->request('GET', '/process?routeName=process_launch&process=test.form_custom_constraint');
279+
$form = $crawler->selectButton('Launch')->form();
280+
/** @var array<string, array<string, mixed>> $values */
281+
$values = $form->getPhpValues();
282+
$values['launch']['context'] = ['count' => '3'];
283+
$crawler = $this->client->request($form->getMethod(), $form->getUri(), $values);
284+
285+
self::assertResponseIsSuccessful();
286+
self::assertSame('The value 3 is not even.', $this->getFieldError($crawler, 'launch_context_count'));
287+
self::assertSame([], $this->getDispatchedMessages());
288+
289+
$values['launch']['context'] = ['count' => '4'];
290+
$this->client->request($form->getMethod(), $form->getUri(), $values);
291+
292+
self::assertResponseRedirects();
293+
$messages = $this->getDispatchedMessages();
294+
self::assertCount(1, $messages);
295+
self::assertSame(['execution_user' => 'user@example.com', 'count' => 4], $messages[0]->context);
296+
}
297+
271298
public function testExtraContextRowWithADeclaredKey(): void
272299
{
273300
$this->login();

‎tests/Manager/ProcessConfigurationsManagerTest.php‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
use CleverAge\UiProcessBundle\Form\Type\ProcessContextType;
2020
use CleverAge\UiProcessBundle\Manager\ProcessConfigurationsManager;
2121
use CleverAge\UiProcessBundle\Notifier\NotificationTrigger;
22+
use CleverAge\UiProcessBundle\Tests\App\Validator\Even;
2223
use PHPUnit\Framework\Attributes\CoversClass;
2324
use PHPUnit\Framework\Attributes\DataProvider;
2425
use PHPUnit\Framework\Attributes\TestWith;
@@ -245,6 +246,24 @@ public function testFieldConstraints(): void
245246
}
246247
}
247248

249+
/**
250+
* A project-specific constraint, given by its FQCN.
251+
*/
252+
public function testFieldConstraintClass(): void
253+
{
254+
[$options] = $this->resolveUi(['form' => ['context' => ['count' => ['options' => ['constraints' => [
255+
[Even::class => null],
256+
[Even::class => ['message' => 'Odd value.']],
257+
]]]]]]);
258+
259+
$constraints = $options['form']['context']['count']['options']['constraints'];
260+
self::assertIsArray($constraints);
261+
self::assertCount(2, $constraints);
262+
self::assertInstanceOf(Even::class, $constraints[0]);
263+
self::assertInstanceOf(Even::class, $constraints[1]);
264+
self::assertSame('Odd value.', $constraints[1]->message);
265+
}
266+
248267
public function testFieldConstraintsMustBeAnArray(): void
249268
{
250269
$this->expectException(InvalidOptionsException::class);

0 commit comments

Comments
 (0)