summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorwebchick2015-10-06 05:32:45 (GMT)
committerwebchick2015-10-06 05:32:45 (GMT)
commite55a7d8dc32aae6029863ea8a55e48515354053e (patch)
treed8ae277296f37fa452777e9c54bb7b514687b6b6
parent40fa14ba9a51b09fd4d99d5a2710bae023db75fd (diff)
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
-rw-r--r--core/core.services.yml1
-rw-r--r--core/includes/form.inc18
-rw-r--r--core/includes/theme.inc5
-rw-r--r--core/lib/Drupal/Core/Form/FormErrorHandler.php73
-rw-r--r--core/lib/Drupal/Core/Form/FormStateInterface.php3
-rw-r--r--core/modules/content_translation/src/Tests/ContentTranslationUITestBase.php2
-rw-r--r--core/modules/datetime/src/Tests/DateTimeFieldTest.php26
-rw-r--r--core/modules/entity_reference/src/Tests/EntityReferenceAdminTest.php2
-rw-r--r--core/modules/file/src/Element/ManagedFile.php2
-rw-r--r--core/modules/file/src/Tests/FileFieldValidateTest.php6
-rw-r--r--core/modules/inline_form_errors/inline_form_errors.info.yml6
-rw-r--r--core/modules/inline_form_errors/inline_form_errors.module59
-rw-r--r--core/modules/inline_form_errors/src/FormErrorHandler.php108
-rw-r--r--core/modules/inline_form_errors/src/InlineFormErrorsServiceProvider.php28
-rw-r--r--core/modules/inline_form_errors/tests/src/Unit/FormErrorHandlerTest.php135
-rw-r--r--core/modules/shortcut/src/Tests/ShortcutSetsTest.php3
-rw-r--r--core/modules/system/src/Tests/Form/FormTest.php2
-rw-r--r--core/modules/system/src/Tests/Form/TriggeringElementProgrammedUnitTest.php12
-rw-r--r--core/modules/system/src/Tests/Form/ValidationTest.php68
-rw-r--r--core/modules/user/src/Tests/UserBlocksTest.php9
-rw-r--r--core/tests/Drupal/Tests/Core/Form/FormErrorHandlerTest.php35
21 files changed, 384 insertions, 219 deletions
diff --git a/core/core.services.yml b/core/core.services.yml
index f867f88..0d6eb2e 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -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']
diff --git a/core/includes/form.inc b/core/includes/form.inc
index 48fa93f..678a640 100644
--- a/core/includes/form.inc
+++ b/core/includes/form.inc
@@ -10,7 +10,6 @@ use Drupal\Component\Utility\UrlHelper;
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'])) {
diff --git a/core/includes/theme.inc b/core/includes/theme.inc
index 9d98489..733fc9f 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -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'];
diff --git a/core/lib/Drupal/Core/Form/FormErrorHandler.php b/core/lib/Drupal/Core/Form/FormErrorHandler.php
index 870fbf0..6781bbf 100644
--- a/core/lib/Drupal/Core/Form/FormErrorHandler.php
+++ b/core/lib/Drupal/Core/Form/FormErrorHandler.php
@@ -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 @@ class FormErrorHandler implements FormErrorHandlerInterface {
* 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');
- }
}
/**
diff --git a/core/lib/Drupal/Core/Form/FormStateInterface.php b/core/lib/Drupal/Core/Form/FormStateInterface.php
index 11b089b..798d12c 100644
--- a/core/lib/Drupal/Core/Form/FormStateInterface.php
+++ b/core/lib/Drupal/Core/Form/FormStateInterface.php
@@ -417,8 +417,7 @@ interface FormStateInterface {
* 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
diff --git a/core/modules/content_translation/src/Tests/ContentTranslationUITestBase.php b/core/modules/content_translation/src/Tests/ContentTranslationUITestBase.php
index de8c349..9ed2e46 100644
--- a/core/modules/content_translation/src/Tests/ContentTranslationUITestBase.php
+++ b/core/modules/content_translation/src/Tests/ContentTranslationUITestBase.php
@@ -317,7 +317,7 @@ abstract class ContentTranslationUITestBase extends ContentTranslationTestBase {
'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.');
diff --git a/core/modules/datetime/src/Tests/DateTimeFieldTest.php b/core/modules/datetime/src/Tests/DateTimeFieldTest.php
index a2ceebd..14ad190 100644
--- a/core/modules/datetime/src/Tests/DateTimeFieldTest.php
+++ b/core/modules/datetime/src/Tests/DateTimeFieldTest.php
@@ -548,7 +548,9 @@ class DateTimeFieldTest extends WebTestBase {
$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 @@ class DateTimeFieldTest extends WebTestBase {
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.',
+ ]],
];
}
diff --git a/core/modules/entity_reference/src/Tests/EntityReferenceAdminTest.php b/core/modules/entity_reference/src/Tests/EntityReferenceAdminTest.php
index 8d5e15b..ea070e3 100644
--- a/core/modules/entity_reference/src/Tests/EntityReferenceAdminTest.php
+++ b/core/modules/entity_reference/src/Tests/EntityReferenceAdminTest.php
@@ -275,7 +275,6 @@ class EntityReferenceAdminTest extends WebTestBase {
$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 @@ class EntityReferenceAdminTest extends WebTestBase {
// 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() . ')']));
diff --git a/core/modules/file/src/Element/ManagedFile.php b/core/modules/file/src/Element/ManagedFile.php
index 1378ff3..1d9a742 100644
--- a/core/modules/file/src/Element/ManagedFile.php
+++ b/core/modules/file/src/Element/ManagedFile.php
@@ -390,7 +390,7 @@ class ManagedFile extends FormElement {
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.
diff --git a/core/modules/file/src/Tests/FileFieldValidateTest.php b/core/modules/file/src/Tests/FileFieldValidateTest.php
index 89bac43..291c432 100644
--- a/core/modules/file/src/Tests/FileFieldValidateTest.php
+++ b/core/modules/file/src/Tests/FileFieldValidateTest.php
@@ -35,8 +35,7 @@ class FileFieldValidateTest extends FileFieldTestBase {
$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 @@ class FileFieldValidateTest extends FileFieldTestBase {
$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);
diff --git a/core/modules/inline_form_errors/inline_form_errors.info.yml b/core/modules/inline_form_errors/inline_form_errors.info.yml
new file mode 100644
index 0000000..a5949fa
--- /dev/null
+++ b/core/modules/inline_form_errors/inline_form_errors.info.yml
@@ -0,0 +1,6 @@
+type: module
+name: Inline Form Errors
+description: 'Enables inline form errors.'
+version: VERSION
+core: 8.x
+package: Core (Experimental)
diff --git a/core/modules/inline_form_errors/inline_form_errors.module b/core/modules/inline_form_errors/inline_form_errors.module
new file mode 100644
index 0000000..52e572d
--- /dev/null
+++ b/core/modules/inline_form_errors/inline_form_errors.module
@@ -0,0 +1,59 @@
+<?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'];
+ }
+}
diff --git a/core/modules/inline_form_errors/src/FormErrorHandler.php b/core/modules/inline_form_errors/src/FormErrorHandler.php
new file mode 100644
index 0000000..3650667
--- /dev/null
+++ b/core/modules/inline_form_errors/src/FormErrorHandler.php
@@ -0,0 +1,108 @@
+<?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');
+ }
+ }
+
+}
diff --git a/core/modules/inline_form_errors/src/InlineFormErrorsServiceProvider.php b/core/modules/inline_form_errors/src/InlineFormErrorsServiceProvider.php
new file mode 100644
index 0000000..a03e5e9
--- /dev/null
+++ b/core/modules/inline_form_errors/src/InlineFormErrorsServiceProvider.php
@@ -0,0 +1,28 @@
+<?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')]);
+ }
+
+}
diff --git a/core/modules/inline_form_errors/tests/src/Unit/FormErrorHandlerTest.php b/core/modules/inline_form_errors/tests/src/Unit/FormErrorHandlerTest.php
new file mode 100644
index 0000000..0598173
--- /dev/null
+++ b/core/modules/inline_form_errors/tests/src/Unit/FormErrorHandlerTest.php
@@ -0,0 +1,135 @@
+<?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']);
+ }
+
+}
diff --git a/core/modules/shortcut/src/Tests/ShortcutSetsTest.php b/core/modules/shortcut/src/Tests/ShortcutSetsTest.php
index 47efc0f..e9dc559 100644
--- a/core/modules/shortcut/src/Tests/ShortcutSetsTest.php
+++ b/core/modules/shortcut/src/Tests/ShortcutSetsTest.php
@@ -148,8 +148,7 @@ class ShortcutSetsTest extends ShortcutTestBase {
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');
diff --git a/core/modules/system/src/Tests/Form/FormTest.php b/core/modules/system/src/Tests/Form/FormTest.php
index 3ce775d..f0e5af0 100644
--- a/core/modules/system/src/Tests/Form/FormTest.php
+++ b/core/modules/system/src/Tests/Form/FormTest.php
@@ -190,7 +190,7 @@ class FormTest extends WebTestBase {
}
// 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.
diff --git a/core/modules/system/src/Tests/Form/TriggeringElementProgrammedUnitTest.php b/core/modules/system/src/Tests/Form/TriggeringElementProgrammedUnitTest.php
index f7cffa1..ae6b82c 100644
--- a/core/modules/system/src/Tests/Form/TriggeringElementProgrammedUnitTest.php
+++ b/core/modules/system/src/Tests/Form/TriggeringElementProgrammedUnitTest.php
@@ -19,23 +19,11 @@ use Drupal\simpletest\KernelTestBase;
*/
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}
- */
public function getFormId() {
return 'triggering_element_programmed_form';
}
diff --git a/core/modules/system/src/Tests/Form/ValidationTest.php b/core/modules/system/src/Tests/Form/ValidationTest.php
index 2bc4867..c73e674 100644
--- a/core/modules/system/src/Tests/Form/ValidationTest.php
+++ b/core/modules/system/src/Tests/Form/ValidationTest.php
@@ -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 @@ class ValidationTest extends WebTestBase {
$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'])));
- $messages[] = [
- 'title' => $form[$key]['#title'],
- 'message' => $form[$key]['#form_test_required_error'],
- 'key' => $key,
- ];
- }
- elseif (!empty($form[$key]['#required'])) {
- $messages[] = [
- 'title' => $form[$key]['#title'],
- 'message' => t('@name field is required.', ['@name' => $form[$key]['#title']]),
- 'key' => $key,
- ];
+ $this->assertText($form[$key]['#form_test_required_error']);
}
}
- $this->assertErrorMessages($messages);
+ $this->assertNoText(t('An illegal choice has been detected. Please contact the site administrator.'));
// Verify that no custom validation error appears with valid values.
$edit = array(
@@ -251,7 +234,6 @@ class ValidationTest extends WebTestBase {
);
$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'])));
@@ -261,50 +243,8 @@ class ValidationTest extends WebTestBase {
$this->assertNoText(t('@name field is required.', array('@name' => $form[$key]['#title'])));
$this->assertNoText($form[$key]['#form_test_required_error']);
}
- elseif (!empty($form[$key]['#required'])) {
- $messages[] = [
- 'title' => $form[$key]['#title'],
- 'message' => t('@name field is required.', ['@name' => $form[$key]['#title']]),
- 'key' => $key,
- ];
- }
- }
- $this->assertErrorMessages($messages);
- }
-
- /**
- * Asserts that the given error messages are displayed.
- *
- * @param array $messages
- * An associative array of error messages keyed by the order they appear on
- * the page, with the following key-value pairs:
- * - title: The human readable form element title.
- * - message: The error message for this form element.
- * - key: The key used for the form element.
- */
- protected function assertErrorMessages($messages) {
- $element = $this->xpath('//div[@class = "form-item--error-message"]/strong');
- $this->assertIdentical(count($messages), count($element));
-
- $error_links = [];
- foreach ($messages as $delta => $message) {
- // Ensure the message appears in the correct place.
- if (!isset($element[$delta])) {
- $this->fail(format_string('The error message for the "@title" element with key "@key" was not found.', ['@title' => $message['title'], '@key' => $message['key']]));
- }
- else {
- $this->assertEqual($message['message'], (string) $element[$delta]);
- }
-
- // Gather the element for checking the jump link section.
- $error_links[] = \Drupal::l($message['title'], Url::fromRoute('<none>', [], ['fragment' => 'edit-' . str_replace('_', '-', $message['key']), 'external' => TRUE]));
- }
- $top_message = \Drupal::translation()->formatPlural(count($error_links), '1 error has been found:', '@count errors have been found:');
- $this->assertRaw($top_message);
- foreach ($error_links as $error_link) {
- $this->assertRaw($error_link);
}
- $this->assertNoText('An illegal choice has been detected. Please contact the site administrator.');
+ $this->assertNoText(t('An illegal choice has been detected. Please contact the site administrator.'));
}
}
diff --git a/core/modules/user/src/Tests/UserBlocksTest.php b/core/modules/user/src/Tests/UserBlocksTest.php
index 9241ac6..3da2d46 100644
--- a/core/modules/user/src/Tests/UserBlocksTest.php
+++ b/core/modules/user/src/Tests/UserBlocksTest.php
@@ -67,15 +67,6 @@ class UserBlocksTest extends WebTestBase {
* Test the user login block.
*/
function testUserLoginBlock() {
- // Make sure the validation error is displayed when try to login with
- // invalid username/password.
- $edit['name'] = $this->randomMachineName();
- $edit['pass'] = $this->randomMachineName();
- $this->drupalPostForm('node', $edit, t('Log in'));
- $this->assertRaw('1 error has been found:');
- $this->assertRaw('<a href="#edit-name">Username</a>');
- $this->assertText(t('Unrecognized username or password.'));
-
// Create a user with some permission that anonymous users lack.
$user = $this->drupalCreateUser(array('administer permissions'));
diff --git a/core/tests/Drupal/Tests/Core/Form/FormErrorHandlerTest.php b/core/tests/Drupal/Tests/Core/Form/FormErrorHandlerTest.php
index cd8e3b2..c947934 100644
--- a/core/tests/Drupal/Tests/Core/Form/FormErrorHandlerTest.php
+++ b/core/tests/Drupal/Tests/Core/Form/FormErrorHandlerTest.php
@@ -21,34 +21,28 @@ class FormErrorHandlerTest extends UnitTestCase {
* @covers ::displayErrorMessages
*/
public function testDisplayErrorMessages() {
- $link_generator = $this->getMock('Drupal\Core\Utility\LinkGeneratorInterface');
- $link_generator->expects($this->any())
- ->method('generate')
- ->willReturnArgument(0);
- $renderer = $this->getMock('\Drupal\Core\Render\RendererInterface');
$form_error_handler = $this->getMockBuilder('Drupal\Core\Form\FormErrorHandler')
- ->setConstructorArgs([$this->getStringTranslationStub(), $link_generator, $renderer])
->setMethods(['drupalSetMessage'])
->getMock();
$form_error_handler->expects($this->at(0))
->method('drupalSetMessage')
- ->with('no title given', 'error');
+ ->with('invalid', 'error');
$form_error_handler->expects($this->at(1))
->method('drupalSetMessage')
- ->with('element is invisible', 'error');
+ ->with('invalid', 'error');
$form_error_handler->expects($this->at(2))
->method('drupalSetMessage')
- ->with('this missing element is invalid', 'error');
+ ->with('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>';
- }));
+ ->with('no title given', 'error');
+ $form_error_handler->expects($this->at(4))
+ ->method('drupalSetMessage')
+ ->with('element is invisible', 'error');
+ $form_error_handler->expects($this->at(5))
+ ->method('drupalSetMessage')
+ ->with('this missing element is invalid', 'error');
$form = [
'#parents' => [],
@@ -74,13 +68,6 @@ class FormErrorHandlerTest extends UnitTestCase {
'#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'],
@@ -96,7 +83,6 @@ class FormErrorHandlerTest extends UnitTestCase {
$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');
@@ -110,7 +96,6 @@ class FormErrorHandlerTest extends UnitTestCase {
*/
public function testSetElementErrorsFromFormState() {
$form_error_handler = $this->getMockBuilder('Drupal\Core\Form\FormErrorHandler')
- ->setConstructorArgs([$this->getStringTranslationStub(), $this->getMock('Drupal\Core\Utility\LinkGeneratorInterface'), $this->getMock('\Drupal\Core\Render\RendererInterface')])
->setMethods(['drupalSetMessage'])
->getMock();