[Admin] Add global alert for form errors (#16634)

| Q               | A
|-----------------|-----
| Branch?         | 2.0
| Bug fix?        | no
| New feature?    | no
| BC breaks?      | no
| Deprecations?   | no
| Related tickets | n/a
| License         | MIT


![image](https://github.com/user-attachments/assets/099ce8fc-9c1d-462a-925f-cbacf3a0479d)
This commit is contained in:
Kamil Grygierzec 2024-07-29 10:19:17 +02:00 committed by GitHub
commit 7d9c78a1cf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 84 additions and 103 deletions

View file

@ -12,7 +12,7 @@ Feature: Customer group validation
When I want to create a new customer group
And I try to add it
Then I should be notified that name is required
# And I should be informed that this form contains errors #TODO: Uncomment when flash messages are brought back
And I should be informed that this form contains errors
@api @ui
Scenario: Trying to remove name from an existing customer group
@ -21,7 +21,7 @@ Feature: Customer group validation
And I remove its name
And I try to save my changes
Then I should be notified that name is required
# And I should be informed that this form contains errors #TODO: Uncomment when flash messages are brought back
And I should be informed that this form contains errors
And this customer group should still be named "Retail"
@api @ui

View file

@ -14,24 +14,20 @@ declare(strict_types=1);
namespace Sylius\Behat\Context\Ui\Admin;
use Behat\Behat\Context\Context;
use FriendsOfBehat\PageObjectExtension\Page\SymfonyPageInterface;
use Sylius\Behat\Context\Ui\Admin\Helper\ValidationTrait;
use Sylius\Behat\Element\Admin\CustomerGroup\FormElementInterface;
use Sylius\Behat\Page\Admin\Crud\IndexPageInterface;
use Sylius\Behat\Page\Admin\CustomerGroup\CreatePageInterface;
use Sylius\Behat\Page\Admin\CustomerGroup\UpdatePageInterface;
use Sylius\Behat\Service\Resolver\CurrentPageResolverInterface;
use Sylius\Behat\Page\Admin\Crud\CreatePageInterface;
use Sylius\Behat\Page\Admin\Crud\UpdatePageInterface;
use Sylius\Component\Customer\Model\CustomerGroupInterface;
use Webmozart\Assert\Assert;
final readonly class ManagingCustomerGroupsContext implements Context
{
use ValidationTrait;
public function __construct(
private CreatePageInterface $createPage,
private IndexPageInterface $indexPage,
private CurrentPageResolverInterface $currentPageResolver,
private UpdatePageInterface $updatePage,
private FormElementInterface $formElement,
) {
}
@ -49,7 +45,15 @@ final readonly class ManagingCustomerGroupsContext implements Context
*/
public function iSpecifyItsCodeAs(?string $code = null): void
{
$this->createPage->specifyCode($code ?? '');
$this->formElement->fillElement($code ?? '', 'code');
}
/**
* @When I specify a too long code
*/
public function iSpecifyATooLongCode(): void
{
$this->formElement->fillElement(str_repeat('a', 256), 'code');
}
/**
@ -58,7 +62,7 @@ final readonly class ManagingCustomerGroupsContext implements Context
*/
public function iSpecifyItsNameAs(?string $name = null): void
{
$this->createPage->nameIt($name ?? '');
$this->formElement->fillElement($name ?? '', 'name');
}
/**
@ -198,7 +202,7 @@ final readonly class ManagingCustomerGroupsContext implements Context
public function iShouldBeNotifiedThatNameIsRequired(): void
{
Assert::same(
$this->updatePage->getValidationMessage('name'),
$this->formElement->getValidationMessage('name'),
'Please enter a customer group name.',
);
}
@ -208,7 +212,18 @@ final readonly class ManagingCustomerGroupsContext implements Context
*/
public function iShouldBeNotifiedThatCustomerGroupWithThisCodeAlreadyExists(): void
{
Assert::same($this->createPage->getValidationMessage('code'), 'Customer group code has to be unique.');
Assert::same($this->formElement->getValidationMessage('code'), 'Customer group code has to be unique.');
}
/**
* @Then I should be notified that code is too long
*/
public function iShouldBeNotifiedThatCodeIsTooLong(): void
{
Assert::contains(
$this->formElement->getValidationMessage('code'),
'must not be longer than 255 characters.',
);
}
/**
@ -216,10 +231,7 @@ final readonly class ManagingCustomerGroupsContext implements Context
*/
public function iShouldBeInformedThatThisFormContainsErrors(): void
{
/** @var CreatePageInterface|UpdatePageInterface $currentPage */
$currentPage = $this->currentPageResolver->getCurrentPageWithForm([$this->createPage, $this->updatePage]);
Assert::contains($currentPage->getMessageInvalidForm(), 'This form contains errors');
Assert::true($this->formElement->hasFormErrorAlert());
}
/**
@ -227,7 +239,7 @@ final readonly class ManagingCustomerGroupsContext implements Context
*/
public function iShouldNotBeAbleToEditItsCode(): void
{
Assert::true($this->updatePage->isCodeDisabled());
Assert::true($this->formElement->isCodeDisabled());
}
/**
@ -253,9 +265,4 @@ final readonly class ManagingCustomerGroupsContext implements Context
),
);
}
protected function resolveCurrentPage(): SymfonyPageInterface
{
return $this->currentPageResolver->getCurrentPageWithForm([$this->createPage, $this->updatePage]);
}
}

View file

@ -44,14 +44,19 @@ class FormElement extends Element implements FormElementInterface
public function getValidationErrors(): string
{
$validationMessage = $this->getDocument()->find('css', '.sylius-validation-error, .alert.alert-danger');
$validationMessage = $this->getDocument()->find('css', 'form .alert.alert-danger');
if (null === $validationMessage) {
throw new ElementNotFoundException($this->getSession(), 'Validation message', 'css', '.sylius-validation-error, .alert.alert-danger');
throw new ElementNotFoundException($this->getSession(), 'Validation message', 'css', 'form .alert.alert-danger');
}
return $validationMessage->getText();
}
public function hasFormErrorAlert(): bool
{
return $this->hasElement('form_error_alert');
}
/**
* @return array<string, string>
*/
@ -59,6 +64,7 @@ class FormElement extends Element implements FormElementInterface
{
return array_merge(parent::getDefinedElements(), [
'form' => 'form',
'form_error_alert' => '[data-test-form-error-alert]',
]);
}

View file

@ -31,4 +31,6 @@ interface FormElementInterface
* @throws ElementNotFoundException
*/
public function getValidationErrors(): string;
public function hasFormErrorAlert(): bool;
}

View file

@ -11,31 +11,26 @@
declare(strict_types=1);
namespace Sylius\Behat\Page\Admin\CustomerGroup;
namespace Sylius\Behat\Element\Admin\CustomerGroup;
use Behat\Mink\Element\NodeElement;
use Sylius\Behat\Behaviour\ChecksCodeImmutability;
use Sylius\Behat\Page\Admin\Crud\UpdatePage as BaseUpdatePage;
use Sylius\Behat\Element\Admin\Crud\FormElement as BaseFormElement;
class UpdatePage extends BaseUpdatePage implements UpdatePageInterface
final class FormElement extends BaseFormElement implements FormElementInterface
{
use ChecksCodeImmutability;
public function nameIt(string $name): void
protected function getDefinedElements(): array
{
$this->getElement('name')->setValue($name);
return array_merge(parent::getDefinedElements(), [
'code' => '[data-test-code]',
'name' => '[data-test-name]',
]);
}
protected function getCodeElement(): NodeElement
{
return $this->getElement('code');
}
protected function getDefinedElements(): array
{
return array_merge(parent::getDefinedElements(), [
'code' => '#sylius_admin_customer_group_code',
'name' => '#sylius_admin_customer_group_name',
]);
}
}

View file

@ -11,13 +11,11 @@
declare(strict_types=1);
namespace Sylius\Behat\Page\Admin\CustomerGroup;
namespace Sylius\Behat\Element\Admin\CustomerGroup;
use Sylius\Behat\Page\Admin\Crud\UpdatePageInterface as BaseUpdatePageInterface;
use Sylius\Behat\Element\Admin\Crud\FormElementInterface as BaseFormElementInterface;
interface UpdatePageInterface extends BaseUpdatePageInterface
interface FormElementInterface extends BaseFormElementInterface
{
public function nameIt(string $name): void;
public function isCodeDisabled(): bool;
}

View file

@ -1,32 +0,0 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Sylius Sp. z o.o.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Sylius\Behat\Page\Admin\CustomerGroup;
use Sylius\Behat\Behaviour\NamesIt;
use Sylius\Behat\Behaviour\SpecifiesItsField;
use Sylius\Behat\Page\Admin\Crud\CreatePage as BaseCreatePage;
class CreatePage extends BaseCreatePage implements CreatePageInterface
{
use SpecifiesItsField;
use NamesIt;
protected function getDefinedElements(): array
{
return array_merge(parent::getDefinedElements(), [
'code' => '#sylius_admin_customer_group_code',
'name' => '#sylius_admin_customer_group_name',
]);
}
}

View file

@ -1,23 +0,0 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Sylius Sp. z o.o.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Sylius\Behat\Page\Admin\CustomerGroup;
use Sylius\Behat\Page\Admin\Crud\CreatePageInterface as BaseCreatePageInterface;
interface CreatePageInterface extends BaseCreatePageInterface
{
public function specifyCode(string $code): void;
public function nameIt(string $name): void;
}

View file

@ -108,8 +108,8 @@
<service id="sylius.behat.context.ui.admin.managing_customer_groups" class="Sylius\Behat\Context\Ui\Admin\ManagingCustomerGroupsContext">
<argument type="service" id="sylius.behat.page.admin.customer_group.create" />
<argument type="service" id="sylius.behat.page.admin.customer_group.index" />
<argument type="service" id="sylius.behat.current_page_resolver" />
<argument type="service" id="sylius.behat.page.admin.customer_group.update" />
<argument type="service" id="sylius.behat.element.admin.customer_group.form" />
</service>
<service id="sylius.behat.context.ui.admin.managing_exchange_rates" class="Sylius\Behat\Context\Ui\Admin\ManagingExchangeRatesContext">

View file

@ -144,5 +144,12 @@
parent="sylius.behat.element.admin.crud.form"
>
</service>
<service
id="sylius.behat.element.admin.customer_group.form"
class="Sylius\Behat\Element\Admin\CustomerGroup\FormElement"
parent="sylius.behat.element.admin.crud.form"
>
</service>
</services>
</container>

View file

@ -13,9 +13,9 @@
<container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<parameters>
<parameter key="sylius.behat.page.admin.customer_group.create.class">Sylius\Behat\Page\Admin\CustomerGroup\CreatePage</parameter>
<parameter key="sylius.behat.page.admin.customer_group.create.class">%sylius.behat.page.admin.crud.create.class%</parameter>
<parameter key="sylius.behat.page.admin.customer_group.index.class">%sylius.behat.page.admin.crud.index.class%</parameter>
<parameter key="sylius.behat.page.admin.customer_group.update.class">Sylius\Behat\Page\Admin\CustomerGroup\UpdatePage</parameter>
<parameter key="sylius.behat.page.admin.customer_group.update.class">%sylius.behat.page.admin.crud.update.class%</parameter>
</parameters>
<services>

View file

@ -13,6 +13,8 @@ twig_hooks:
template: '@SyliusAdmin/shared/crud/common/content/flashes.html.twig'
header:
template: '@SyliusAdmin/shared/crud/common/content/header.html.twig'
form_error_alert:
template: '@SyliusAdmin/shared/crud/common/content/form_error_alert.html.twig'
form:
template: '@SyliusAdmin/shared/crud/common/content/form.html.twig'
configuration:

View file

@ -13,6 +13,8 @@ twig_hooks:
template: '@SyliusAdmin/shared/crud/common/content/flashes.html.twig'
header:
template: '@SyliusAdmin/shared/crud/common/content/header.html.twig'
form_error_alert:
template: '@SyliusAdmin/shared/crud/common/content/form_error_alert.html.twig'
form:
template: '@SyliusAdmin/shared/crud/common/content/form.html.twig'
configuration:

View file

@ -4,11 +4,11 @@
<div class="container-xl" {{ attributes }}>
{{ form_start(form, {'attr': {'novalidate': 'novalidate', 'id': form.vars.id}}) }}
{{ form_errors(form) }}
<div class="grid gap-4">
{% if hookable_metadata.configuration.method is defined %}
<input type="hidden" name="_method" value="{{ hookable_metadata.configuration.method }}" />
{% endif %}
{{ form_errors(form) }}
{{ form_widget(form._token) }}
{% hook 'form' with { form } %}

View file

@ -0,0 +1,17 @@
{% from '@SyliusAdmin/shared/helper/icon.html.twig' import icon %}
{% set form = hookable_metadata.context.form|default(null) %}
{% if form is not null and form.vars.valid == false %}
<div class="container-xl">
<div class="sylius alert alert-danger" role="alert" {{ sylius_test_html_attribute('form-error-alert') }}>
<div class="d-flex">
<div>{{ icon({ icon: 'danger' }) }}</div>
<div class="mx-2">
<h4 class="alert-title">{{ 'sylius.ui.error'|trans }}</h4>
<div class="text-secondary">{{ 'sylius.ui.this_message_contains_errors'|trans }}</div>
</div>
</div>
</div>
</div>
{% endif %}