Commit e55a7d8d authored by webchick's avatar webchick

Issue #2578561 by tim.plunkett, joelpittet, Bojhan, Fabianx, xjm, cilefen,...

Issue #2578561 by tim.plunkett, joelpittet, Bojhan, Fabianx, xjm, cilefen, David_Rothstein, DamienMcKenna: Move "Inline Form Errors" functionality to optional module and restore D7-style form errors by default
parent 40fa14ba
......@@ -341,7 +341,6 @@ services:
arguments: ['@request_stack', '@url_generator']
form_error_handler:
class: Drupal\Core\Form\FormErrorHandler
arguments: ['@string_translation', '@link_generator', '@renderer']
form_cache:
class: Drupal\Core\Form\FormCache
arguments: ['@app.root', '@keyvalue.expirable', '@module_handler', '@current_user', '@csrf_token', '@logger.channel.form', '@request_stack', '@page_cache_request_policy']
......
......@@ -10,7 +10,6 @@
use Drupal\Component\Utility\Xss;
use Drupal\Core\Database\Database;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Form\FormElementHelper;
use Drupal\Core\Form\OptGroup;
use Drupal\Core\Render\Element;
use Drupal\Core\Template\Attribute;
......@@ -222,11 +221,8 @@ function template_preprocess_fieldset(&$variables) {
$variables['attributes']['aria-describedby'] = $description_id;
}
// Display any error messages.
// Suppress error messages.
$variables['errors'] = NULL;
if (!empty($element['#errors']) && empty($element['#error_no_message'])) {
$variables['errors'] = $element['#errors'];
}
}
/**
......@@ -257,11 +253,8 @@ function template_preprocess_details(&$variables) {
$variables['children'] = (isset($element['#children'])) ? $element['#children'] : '';
$variables['value'] = (isset($element['#value'])) ? $element['#value'] : '';
// Display any error messages.
// Suppress error messages.
$variables['errors'] = NULL;
if (!empty($element['#errors']) && empty($element['#error_no_message'])) {
$variables['errors'] = $element['#errors'];
}
}
/**
......@@ -437,7 +430,7 @@ function template_preprocess_form_element(&$variables) {
);
$variables['attributes'] = $element['#wrapper_attributes'];
// Add element #id for #type 'item' and 'password_confirm'.
// Add element #id for #type 'item'.
if (isset($element['#markup']) && !empty($element['#id'])) {
$variables['attributes']['id'] = $element['#id'];
}
......@@ -453,11 +446,8 @@ function template_preprocess_form_element(&$variables) {
// Pass elements disabled status to template.
$variables['disabled'] = !empty($element['#attributes']['disabled']) ? $element['#attributes']['disabled'] : NULL;
// Display any error messages.
// Suppress error messages.
$variables['errors'] = NULL;
if (!empty($element['#errors']) && empty($element['#error_no_message'])) {
$variables['errors'] = $element['#errors'];
}
// If #title is not set, we don't display any label.
if (!isset($element['#title'])) {
......
......@@ -546,11 +546,8 @@ function template_preprocess_datetime_wrapper(&$variables) {
$variables['title'] = $element['#title'];
}
// Display any error messages.
// Suppress error messages.
$variables['errors'] = NULL;
if (!empty($element['#errors']) && empty($element['#error_no_message'])) {
$variables['errors'] = $element['#errors'];
}
if (!empty($element['#description'])) {
$variables['description'] = $element['#description'];
......
......@@ -8,44 +8,12 @@
namespace Drupal\Core\Form;
use Drupal\Core\Render\Element;
use Drupal\Core\Routing\LinkGeneratorTrait;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslationInterface;
use Drupal\Core\Url;
use Drupal\Core\Utility\LinkGeneratorInterface;
/**
* Handles form errors.
*/
class FormErrorHandler implements FormErrorHandlerInterface {
use StringTranslationTrait;
use LinkGeneratorTrait;
/**
* The renderer service.
*
* @var \Drupal\Core\Render\RendererInterface
*/
protected $renderer;
/**
* Constructs a new FormErrorHandler.
*
* @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
* The string translation service.
* @param \Drupal\Core\Utility\LinkGeneratorInterface $link_generator
* The link generation service.
* @param \Drupal\Core\Render\RendererInterface $renderer
* The renderer service.
*/
public function __construct(TranslationInterface $string_translation, LinkGeneratorInterface $link_generator, RendererInterface $renderer) {
$this->stringTranslation = $string_translation;
$this->linkGenerator = $link_generator;
$this->renderer = $renderer;
}
/**
* {@inheritdoc}
*/
......@@ -71,51 +39,12 @@ public function handleFormErrors(array &$form, FormStateInterface $form_state) {
* The current state of the form.
*/
protected function displayErrorMessages(array $form, FormStateInterface $form_state) {
$error_links = [];
$errors = $form_state->getErrors();
// Loop through all form errors and check if we need to display a link.
foreach ($errors as $name => $error) {
$form_element = FormElementHelper::getElementByName($name, $form);
$title = FormElementHelper::getElementTitle($form_element);
// Only show links to erroneous elements that are visible.
$is_visible_element = Element::isVisibleElement($form_element);
// Only show links for elements that have a title themselves or have
// children with a title.
$has_title = !empty($title);
// Only show links for elements with an ID.
$has_id = !empty($form_element['#id']);
// Do not show links to elements with suppressed messages. Most often
// their parent element is used for inline errors.
if (!empty($form_element['#error_no_message'])) {
unset($errors[$name]);
}
elseif ($is_visible_element && $has_title && $has_id) {
$error_links[] = $this->l($title, Url::fromRoute('<none>', [], ['fragment' => $form_element['#id'], 'external' => TRUE]));
unset($errors[$name]);
}
}
// Set normal error messages for all remaining errors.
// Loop through all form errors and set an error message.
foreach ($errors as $error) {
$this->drupalSetMessage($error, 'error');
}
if (!empty($error_links)) {
$render_array = [
[
'#markup' => $this->formatPlural(count($error_links), '1 error has been found: ', '@count errors have been found: '),
],
[
'#theme' => 'item_list',
'#items' => $error_links,
'#context' => ['list_style' => 'comma-list'],
],
];
$message = $this->renderer->renderPlain($render_array);
$this->drupalSetMessage($message, 'error');
}
}
/**
......
......@@ -417,8 +417,7 @@ public static function hasAnyErrors();
* indicate which element needs to be changed and provide an error message.
* This causes the Form API to not execute the form submit handlers, and
* instead to re-display the form to the user with the corresponding elements
* rendered with an 'error' CSS class (shown as red by default) and the error
* message near the element.
* rendered with an 'error' CSS class (shown as red by default).
*
* The standard behavior of this method can be changed if a button provides
* the #limit_validation_errors property. Multistep forms not wanting to
......
......@@ -317,7 +317,7 @@ protected function doTestAuthoringInfo() {
'content_translation[created]' => '19/11/1978',
);
$this->drupalPostForm($entity->urlInfo('edit-form'), $edit, $this->getFormSubmitAction($entity, $langcode));
$this->assertTrue($this->xpath('//div[contains(concat(" ", normalize-space(@class), " "), :class)]', array(':class' => ' messages--error ')), 'Invalid values generate a form error message.');
$this->assertTrue($this->xpath('//div[contains(@class, "error")]//ul'), 'Invalid values generate a list of form errors.');
$metadata = $this->manager->getTranslationMetadata($entity->getTranslation($langcode));
$this->assertEqual($metadata->getAuthor()->id(), $values[$langcode]['uid'], 'Translation author correctly kept.');
$this->assertEqual($metadata->getCreatedTime(), $values[$langcode]['created'], 'Translation date correctly kept.');
......
......@@ -548,7 +548,9 @@ function testDatelistWidget() {
$this->drupalPostForm(NULL, $edit, t('Save'));
$this->assertResponse(200);
$this->assertText(t($expected));
foreach ($expected as $expected_text) {
$this->assertText(t($expected_text));
}
}
// Test the widget for complete input with zeros as part of selections.
......@@ -589,13 +591,27 @@ function testDatelistWidget() {
protected function datelistDataProvider() {
return [
// Year only selected, validation error on Month, Day, Hour, Minute.
[['year' => 2012, 'month' => '', 'day' => '', 'hour' => '', 'minute' => ''], '4 errors have been found: MonthDayHourMinute'],
[['year' => 2012, 'month' => '', 'day' => '', 'hour' => '', 'minute' => ''], [
'A value must be selected for month.',
'A value must be selected for day.',
'A value must be selected for hour.',
'A value must be selected for minute.',
]],
// Year and Month selected, validation error on Day, Hour, Minute.
[['year' => 2012, 'month' => '12', 'day' => '', 'hour' => '', 'minute' => ''], '3 errors have been found: DayHourMinute'],
[['year' => 2012, 'month' => '12', 'day' => '', 'hour' => '', 'minute' => ''], [
'A value must be selected for day.',
'A value must be selected for hour.',
'A value must be selected for minute.',
]],
// Year, Month and Day selected, validation error on Hour, Minute.
[['year' => 2012, 'month' => '12', 'day' => '31', 'hour' => '', 'minute' => ''], '2 errors have been found: HourMinute'],
[['year' => 2012, 'month' => '12', 'day' => '31', 'hour' => '', 'minute' => ''], [
'A value must be selected for hour.',
'A value must be selected for minute.',
]],
// Year, Month, Day and Hour selected, validation error on Minute only.
[['year' => 2012, 'month' => '12', 'day' => '31', 'hour' => '0', 'minute' => ''], '1 error has been found: Minute'],
[['year' => 2012, 'month' => '12', 'day' => '31', 'hour' => '0', 'minute' => ''], [
'A value must be selected for minute.',
]],
];
}
......
......@@ -275,7 +275,6 @@ public function testFieldAdminHandler() {
$this->drupalPostForm('node/add/' . $this->type, $edit, t('Save'));
// Assert that entity reference autocomplete field is validated.
$this->assertText(t('1 error has been found: Test Entity Reference Field'), 'Node save failed when required entity reference field was not correctly filled.');
$this->assertText(t('There are no entities matching "@entity"', ['@entity' => 'Test']));
$edit = array(
......@@ -286,7 +285,6 @@ public function testFieldAdminHandler() {
// Assert the results multiple times to avoid sorting problem of nodes with
// the same title.
$this->assertText(t('1 error has been found: Test Entity Reference Field'));
$this->assertText(t('Multiple entities match this reference;'));
$this->assertText(t("@node1", ['@node1' => $node1->getTitle() . ' (' . $node1->id() . ')']));
$this->assertText(t("@node2", ['@node2' => $node2->getTitle() . ' (' . $node2->id() . ')']));
......
......@@ -390,7 +390,7 @@ public static function validateManagedFile(&$element, FormStateInterface $form_s
if ($element['#required'] && empty($element['fids']['#value']) && !in_array($clicked_button, ['upload_button', 'remove_button'])) {
// We expect the field name placeholder value to be wrapped in t()
// here, so it won't be escaped again as it's already marked safe.
$form_state->setError($element, t('@name is required.', ['@name' => $element['#title']]));
$form_state->setError($element, t('@name field is required.', ['@name' => $element['#title']]));
}
// Consolidate the array value of this field to array of FIDs.
......
......@@ -35,8 +35,7 @@ function testRequired() {
$edit = array();
$edit['title[0][value]'] = $this->randomMachineName();
$this->drupalPostForm('node/add/' . $type_name, $edit, t('Save and publish'));
$this->assertText('1 error has been found: ' . $field->label(), 'Node save failed when required file field was empty.');
$this->assertIdentical(1, count($this->xpath('//div[contains(concat(" ", normalize-space(@class), " "), :class)]//a', [':class' => ' messages--error '])), 'There is one link in the error message.');
$this->assertRaw(t('@title field is required.', array('@title' => $field->getLabel())), 'Node save failed when required file field was empty.');
// Create a new node with the uploaded file.
$nid = $this->uploadNodeFile($test_file, $field_name, $type_name);
......@@ -57,8 +56,7 @@ function testRequired() {
$edit = array();
$edit['title[0][value]'] = $this->randomMachineName();
$this->drupalPostForm('node/add/' . $type_name, $edit, t('Save and publish'));
$this->assertText('1 error has been found: ' . $field->label(), 'Node save failed when required multiple value file field was empty.');
$this->assertIdentical(1, count($this->xpath('//div[contains(concat(" ", normalize-space(@class), " "), :class)]//a', [':class' => ' messages--error '])), 'There is one link in the error message.');
$this->assertRaw(t('@title field is required.', array('@title' => $field->getLabel())), 'Node save failed when required multiple value file field was empty.');
// Create a new node with the uploaded file into the multivalue field.
$nid = $this->uploadNodeFile($test_file, $field_name, $type_name);
......
type: module
name: Inline Form Errors
description: 'Enables inline form errors.'
version: VERSION
core: 8.x
package: Core (Experimental)
<?php
/**
* @file
* Enables inline form errors.
*/
use Drupal\Core\Routing\RouteMatchInterface;
/**
* Implements hook_help().
*/
function inline_form_errors_help($route_name, RouteMatchInterface $route_match) {
switch ($route_name) {
case 'help.page.inline_form_errors':
$output = '';
$output .= '<h3>' . t('About') . '</h3>';
$output .= '<p>' . t('The Inline Form Errors module provides an experimental approach to form errors, placing the error messages next to the elements themselves. For more information, see the <a href=":inline_form_error">online documentation for the Inline Form Errors module</a>.', [':inline_form_error' => 'https://www.drupal.org/documentation/modules/inline_form_error']) . '</p>';
return $output;
}
}
/**
* Implements hook_preprocess_HOOK() for form element templates.
*/
function inline_form_errors_preprocess_form_element(&$variables) {
_inline_form_errors_set_errors($variables);
}
/**
* Implements hook_preprocess_HOOK() for details element templates.
*/
function inline_form_errors_preprocess_details(&$variables) {
_inline_form_errors_set_errors($variables);
}
/**
* Implements hook_preprocess_HOOK() for fieldset element templates.
*/
function inline_form_errors_preprocess_fieldset(&$variables) {
_inline_form_errors_set_errors($variables);
}
/**
* Implements hook_preprocess_HOOK() for datetime form wrapper templates.
*/
function inline_form_errors_preprocess_datetime_wrapper(&$variables) {
_inline_form_errors_set_errors($variables);
}
/**
* Populates form errors in the template.
*/
function _inline_form_errors_set_errors(&$variables) {
$element = $variables['element'];
if (!empty($element['#errors']) && empty($element['#error_no_message'])) {
$variables['errors'] = $element['#errors'];
}
}
<?php
/**
* @file
* Contains \Drupal\inline_form_errors\FormErrorHandler.
*/
namespace Drupal\inline_form_errors;
use Drupal\Core\Form\FormElementHelper;
use Drupal\Core\Form\FormErrorHandler as CoreFormErrorHandler;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Routing\LinkGeneratorTrait;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslationInterface;
use Drupal\Core\Url;
use Drupal\Core\Utility\LinkGeneratorInterface;
/**
* Produces inline form errors.
*/
class FormErrorHandler extends CoreFormErrorHandler {
use StringTranslationTrait;
use LinkGeneratorTrait;
/**
* The renderer service.
*
* @var \Drupal\Core\Render\RendererInterface
*/
protected $renderer;
/**
* Constructs a new FormErrorHandler.
*
* @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
* The string translation service.
* @param \Drupal\Core\Utility\LinkGeneratorInterface $link_generator
* The link generation service.
* @param \Drupal\Core\Render\RendererInterface $renderer
* The renderer service.
*/
public function __construct(TranslationInterface $string_translation, LinkGeneratorInterface $link_generator, RendererInterface $renderer) {
$this->stringTranslation = $string_translation;
$this->linkGenerator = $link_generator;
$this->renderer = $renderer;
}
/**
* Loops through and displays all form errors.
*
* @param array $form
* An associative array containing the structure of the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*/
protected function displayErrorMessages(array $form, FormStateInterface $form_state) {
$error_links = [];
$errors = $form_state->getErrors();
// Loop through all form errors and check if we need to display a link.
foreach ($errors as $name => $error) {
$form_element = FormElementHelper::getElementByName($name, $form);
$title = FormElementHelper::getElementTitle($form_element);
// Only show links to erroneous elements that are visible.
$is_visible_element = Element::isVisibleElement($form_element);
// Only show links for elements that have a title themselves or have
// children with a title.
$has_title = !empty($title);
// Only show links for elements with an ID.
$has_id = !empty($form_element['#id']);
// Do not show links to elements with suppressed messages. Most often
// their parent element is used for inline errors.
if (!empty($form_element['#error_no_message'])) {
unset($errors[$name]);
}
elseif ($is_visible_element && $has_title && $has_id) {
$error_links[] = $this->l($title, Url::fromRoute('<none>', [], ['fragment' => $form_element['#id'], 'external' => TRUE]));
unset($errors[$name]);
}
}
// Set normal error messages for all remaining errors.
foreach ($errors as $error) {
$this->drupalSetMessage($error, 'error');
}
if (!empty($error_links)) {
$render_array = [
[
'#markup' => $this->formatPlural(count($error_links), '1 error has been found: ', '@count errors have been found: '),
],
[
'#theme' => 'item_list',
'#items' => $error_links,
'#context' => ['list_style' => 'comma-list'],
],
];
$message = $this->renderer->renderPlain($render_array);
$this->drupalSetMessage($message, 'error');
}
}
}
<?php
/**
* @file
* Contains \Drupal\inline_form_errors\InlineFormErrorsServiceProvider.
*/
namespace Drupal\inline_form_errors;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\DependencyInjection\ServiceProviderBase;
use Symfony\Component\DependencyInjection\Reference;
/**
* Overrides the form_error_handler service to enable inline form errors.
*/
class InlineFormErrorsServiceProvider extends ServiceProviderBase {
/**
* {@inheritdoc}
*/
public function alter(ContainerBuilder $container) {
$container->getDefinition('form_error_handler')
->setClass(FormErrorHandler::class)
->setArguments([new Reference('string_translation'), new Reference('link_generator'), new Reference('renderer')]);
}
}
<?php
/**
* @file
* Contains \Drupal\Tests\inline_form_errors\Unit\FormErrorHandlerTest.
*/
namespace Drupal\Tests\inline_form_errors\Unit;
use Drupal\Core\Form\FormState;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\Utility\LinkGeneratorInterface;
use Drupal\inline_form_errors\FormErrorHandler;
use Drupal\Tests\UnitTestCase;
/**
* @coversDefaultClass \Drupal\inline_form_errors\FormErrorHandler
* @group InlineFormErrors
*/
class FormErrorHandlerTest extends UnitTestCase {
/**
* @covers ::handleFormErrors
* @covers ::displayErrorMessages
*/
public function testDisplayErrorMessagesInline() {
$link_generator = $this->getMock(LinkGeneratorInterface::class);
$link_generator->expects($this->any())
->method('generate')
->willReturnArgument(0);
$renderer = $this->getMock(RendererInterface::class);
$form_error_handler = $this->getMockBuilder(FormErrorHandler::class)
->setConstructorArgs([$this->getStringTranslationStub(), $link_generator, $renderer])
->setMethods(['drupalSetMessage'])
->getMock();
$form_error_handler->expects($this->at(0))
->method('drupalSetMessage')
->with('no title given', 'error');
$form_error_handler->expects($this->at(1))
->method('drupalSetMessage')
->with('element is invisible', 'error');
$form_error_handler->expects($this->at(2))
->method('drupalSetMessage')
->with('this missing element is invalid', 'error');
$form_error_handler->expects($this->at(3))
->method('drupalSetMessage')
->with('3 errors have been found: <ul-comma-list-mock><li-mock>Test 1</li-mock><li-mock>Test 2 &amp; a half</li-mock><li-mock>Test 3</li-mock></ul-comma-list-mock>', 'error');
$renderer->expects($this->any())
->method('renderPlain')
->will($this->returnCallback(function ($render_array) {
return $render_array[0]['#markup'] . '<ul-comma-list-mock><li-mock>' . implode(array_map('htmlspecialchars', $render_array[1]['#items']), '</li-mock><li-mock>') . '</li-mock></ul-comma-list-mock>';
}));
$form = [
'#parents' => [],
];
$form['test1'] = [
'#type' => 'textfield',
'#title' => 'Test 1',
'#parents' => ['test1'],
'#id' => 'edit-test1',
];
$form['test2'] = [
'#type' => 'textfield',
'#title' => 'Test 2 & a half',
'#parents' => ['test2'],
'#id' => 'edit-test2',
];
$form['fieldset'] = [
'#parents' => ['fieldset'],
'test3' => [
'#type' => 'textfield',
'#title' => 'Test 3',
'#parents' => ['fieldset', 'test3'],
'#id' => 'edit-test3',
],
];
$form['test4'] = [
'#type' => 'textfield',
'#title' => 'Test 4',
'#parents' => ['test4'],
'#id' => 'edit-test4',
'#error_no_message' => TRUE,
];
$form['test5'] = [
'#type' => 'textfield',
'#parents' => ['test5'],
'#id' => 'edit-test5',
];
$form['test6'] = [
'#type' => 'value',
'#title' => 'Test 6',
'#parents' => ['test6'],
'#id' => 'edit-test6',
];
$form_state = new FormState();
$form_state->setErrorByName('test1', 'invalid');
$form_state->setErrorByName('test2', 'invalid');
$form_state->setErrorByName('fieldset][test3', 'invalid');
$form_state->setErrorByName('test4', 'no error message');
$form_state->setErrorByName('test5', 'no title given');
$form_state->setErrorByName('test6', 'element is invisible');
$form_state->setErrorByName('missing_element', 'this missing element is invalid');
$form_error_handler->handleFormErrors($form, $form_state);
$this->assertSame('invalid', $form['test1']['#errors']);
}
/**
* @covers ::handleFormErrors
* @covers ::setElementErrorsFromFormState
*/
public function testSetElementErrorsFromFormState() {
$form_error_handler = $this->getMockBuilder(FormErrorHandler::class)
->setConstructorArgs([$this->getStringTranslationStub(), $this->getMock(LinkGeneratorInterface::class), $this->getMock(RendererInterface::class)])
->setMethods(['drupalSetMessage'])
->getMock();
$form = [
'#parents' => [],
];
$form['test'] = [
'#type' => 'textfield',
'#title' => 'Test',
'#parents' => ['test'],
'#id' => 'edit-test',
];
$form_state = new FormState();
$form_state->setErrorByName('test', 'invalid');
$form_error_handler->handleFormErrors($form, $form_state);
$this->assertSame('invalid', $form['test']['#errors']);
}
}
......@@ -148,8 +148,7 @@ function testShortcutSetSwitchCreate() {
function testShortcutSetSwitchNoSetName() {
$edit = array('set' => 'new');
$this->drupalPostForm('user/' . $this->adminUser->id() . '/shortcuts', $edit, t('Change set'));
$this->assertRaw('1 error has been found:');
$this->assertRaw('<a href="#edit-label">Label</a>');
$this->assertText(t('The new set label is required.'));
$current_set = shortcut_current_displayed_set($this->adminUser);
$this->assertEqual($current_set->id(), $this->set->id(), 'Attempting to switch to a new shortcut set without providing a set name does not succeed.');
$this->assertFieldByXPath("//input[@name='label' and contains(concat(' ', normalize-space(@class), ' '), ' error ')]", NULL, 'The new set label field has the error class');
......
......@@ -190,7 +190,7 @@ function testRequiredCheckboxesRadio() {
}
// Check the page for error messages.
$errors = $this->xpath('//div[contains(@class, "form-item--error-message")]//strong');
$errors = $this->xpath('//div[contains(@class, "error")]//li');
foreach ($errors as $error) {
$expected_key = array_search($error[0], $expected);
// If the error message is not one of the expected messages, fail.
......
......@@ -19,20 +19,8 @@
*/
class TriggeringElementProgrammedUnitTest extends KernelTestBase implements FormInterface {
/**
* {@inheritdoc}
*/
public static $modules = array('system');
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installSchema('system', ['router']);
\Drupal::service('router.builder')->rebuild();
}
/**
* {@inheritdoc}
*/
......
......@@ -8,7 +8,6 @@
namespace Drupal\system\Tests\Form;
use Drupal\Core\Render\Element;
use Drupal\Core\Url;
use Drupal\simpletest\WebTestBase;
/**
......@@ -215,33 +214,17 @@ function testCustomRequiredError() {
$edit = array();
$this->drupalPostForm('form-test/validate-required', $edit, 'Submit');
$messages = [];
foreach (Element::children($form) as $key) {
if (isset($form[$key]['#required_error'])) {
$this->assertNoText(t('@name field is required.', array('@name' => $form[$key]['#title'])));
$messages[] = [
'title' => $form[$key]['#title'],
'message' => $form[$key]['#required_error'],
'key' => $key,
];
$this->assertText($form[$key]['#required_error']);
}
elseif (isset($form[$key]['#form_test_required_error'])) {
$this->assertNoText(t('@name field is required.', array('@name' => $form[$key]['#title'])));