Commit fde3369b authored by catch's avatar catch

Issue #2486019 by mpdonadio, pfrenssen, webflo, michielnugter, maris.abols,...

Issue #2486019 by mpdonadio, pfrenssen, webflo, michielnugter, maris.abols, ivanjaros, hauruck, MickeA, Jo Fitzgerald, mian3010, iMiksu: Wrong validation messages in Datelist::validateDatelist()
parent 1105324a
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
namespace Drupal\Core\Datetime\Element; namespace Drupal\Core\Datetime\Element;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Datetime\DrupalDateTime; use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\Render\Element\FormElement; use Drupal\Core\Render\Element\FormElement;
...@@ -69,4 +70,36 @@ protected static function datetimeRangeYears($string, $date = NULL) { ...@@ -69,4 +70,36 @@ protected static function datetimeRangeYears($string, $date = NULL) {
return [$min_year, $max_year]; return [$min_year, $max_year];
} }
/**
* Returns the most relevant title of a datetime element.
*
* Since datetime form elements often consist of combined date and time fields
* the element title might not be located on the element itself but on the
* parent container element.
*
* @param array $element
* The element being processed.
* @param array $complete_form
* The complete form structure.
*
* @return string
* The title.
*/
protected static function getElementTitle($element, $complete_form) {
$title = '';
if (!empty($element['#title'])) {
$title = $element['#title'];
}
else {
$parents = $element['#array_parents'];
array_pop($parents);
$parent_element = NestedArray::getValue($complete_form, $parents);
if (!empty($parent_element['#title'])) {
$title = $parent_element['#title'];
}
}
return $title;
}
} }
...@@ -302,6 +302,8 @@ public static function processDatelist(&$element, FormStateInterface $form_state ...@@ -302,6 +302,8 @@ public static function processDatelist(&$element, FormStateInterface $form_state
public static function validateDatelist(&$element, FormStateInterface $form_state, &$complete_form) { public static function validateDatelist(&$element, FormStateInterface $form_state, &$complete_form) {
$input_exists = FALSE; $input_exists = FALSE;
$input = NestedArray::getValue($form_state->getValues(), $element['#parents'], $input_exists); $input = NestedArray::getValue($form_state->getValues(), $element['#parents'], $input_exists);
$title = static::getElementTitle($element, $complete_form);
if ($input_exists) { if ($input_exists) {
$all_empty = static::checkEmptyInputs($input, $element['#date_part_order']); $all_empty = static::checkEmptyInputs($input, $element['#date_part_order']);
...@@ -311,10 +313,11 @@ public static function validateDatelist(&$element, FormStateInterface $form_stat ...@@ -311,10 +313,11 @@ public static function validateDatelist(&$element, FormStateInterface $form_stat
} }
// If there's empty input and the field is required, set an error. // If there's empty input and the field is required, set an error.
elseif (empty($input['year']) && empty($input['month']) && empty($input['day']) && $element['#required']) { elseif (empty($input['year']) && empty($input['month']) && empty($input['day']) && $element['#required']) {
$form_state->setError($element, t('The %field date is required.')); $form_state->setError($element, t('The %field date is required.', ['%field' => $title]));
} }
elseif (!empty($all_empty)) { elseif (!empty($all_empty)) {
foreach ($all_empty as $value) { foreach ($all_empty as $value) {
$form_state->setError($element, t('The %field date is incomplete.', ['%field' => $title]));
$form_state->setError($element[$value], t('A value must be selected for %part.', ['%part' => $value])); $form_state->setError($element[$value], t('A value must be selected for %part.', ['%part' => $value]));
} }
} }
...@@ -326,7 +329,7 @@ public static function validateDatelist(&$element, FormStateInterface $form_stat ...@@ -326,7 +329,7 @@ public static function validateDatelist(&$element, FormStateInterface $form_stat
} }
// If the input is invalid and an error doesn't exist, set one. // If the input is invalid and an error doesn't exist, set one.
elseif ($form_state->getError($element) === NULL) { elseif ($form_state->getError($element) === NULL) {
$form_state->setError($element, t('The %field date is invalid.', ['%field' => !empty($element['#title']) ? $element['#title'] : ''])); $form_state->setError($element, t('The %field date is invalid.', ['%field' => $title]));
} }
} }
} }
......
...@@ -107,6 +107,7 @@ protected function setUp() { ...@@ -107,6 +107,7 @@ protected function setUp() {
*/ */
protected function createField() { protected function createField() {
$field_name = Unicode::strtolower($this->randomMachineName()); $field_name = Unicode::strtolower($this->randomMachineName());
$field_label = Unicode::ucfirst(Unicode::strtolower($this->randomMachineName()));
$type = $this->getTestFieldType(); $type = $this->getTestFieldType();
$widget_type = $formatter_type = $type . '_default'; $widget_type = $formatter_type = $type . '_default';
...@@ -119,8 +120,9 @@ protected function createField() { ...@@ -119,8 +120,9 @@ protected function createField() {
$this->fieldStorage->save(); $this->fieldStorage->save();
$this->field = FieldConfig::create([ $this->field = FieldConfig::create([
'field_storage' => $this->fieldStorage, 'field_storage' => $this->fieldStorage,
'label' => $field_label,
'bundle' => 'entity_test', 'bundle' => 'entity_test',
'description' => 'Description for ' . $field_name, 'description' => 'Description for ' . $field_label,
'required' => TRUE, 'required' => TRUE,
]); ]);
$this->field->save(); $this->field->save();
......
...@@ -208,6 +208,7 @@ public function testDateField() { ...@@ -208,6 +208,7 @@ public function testDateField() {
*/ */
public function testDatetimeField() { public function testDatetimeField() {
$field_name = $this->fieldStorage->getName(); $field_name = $this->fieldStorage->getName();
$field_label = $this->field->label();
// Change the field to a datetime field. // Change the field to a datetime field.
$this->fieldStorage->setSetting('datetime_type', 'datetime'); $this->fieldStorage->setSetting('datetime_type', 'datetime');
$this->fieldStorage->save(); $this->fieldStorage->save();
...@@ -216,7 +217,7 @@ public function testDatetimeField() { ...@@ -216,7 +217,7 @@ public function testDatetimeField() {
$this->drupalGet('entity_test/add'); $this->drupalGet('entity_test/add');
$this->assertFieldByName("{$field_name}[0][value][date]", '', 'Date element found.'); $this->assertFieldByName("{$field_name}[0][value][date]", '', 'Date element found.');
$this->assertFieldByName("{$field_name}[0][value][time]", '', 'Time element found.'); $this->assertFieldByName("{$field_name}[0][value][time]", '', 'Time element found.');
$this->assertFieldByXPath('//fieldset[@id="edit-' . $field_name . '-0"]/legend', $field_name, 'Fieldset and label found'); $this->assertFieldByXPath('//fieldset[@id="edit-' . $field_name . '-0"]/legend', $field_label, 'Fieldset and label found');
$this->assertFieldByXPath('//fieldset[@aria-describedby="edit-' . $field_name . '-0--description"]', NULL, 'ARIA described-by found'); $this->assertFieldByXPath('//fieldset[@aria-describedby="edit-' . $field_name . '-0--description"]', NULL, 'ARIA described-by found');
$this->assertFieldByXPath('//div[@id="edit-' . $field_name . '-0--description"]', NULL, 'ARIA description found'); $this->assertFieldByXPath('//div[@id="edit-' . $field_name . '-0--description"]', NULL, 'ARIA description found');
...@@ -352,6 +353,7 @@ public function testDatetimeField() { ...@@ -352,6 +353,7 @@ public function testDatetimeField() {
*/ */
public function testDatelistWidget() { public function testDatelistWidget() {
$field_name = $this->fieldStorage->getName(); $field_name = $this->fieldStorage->getName();
$field_label = $this->field->label();
// Ensure field is set to a date only field. // Ensure field is set to a date only field.
$this->fieldStorage->setSetting('datetime_type', 'date'); $this->fieldStorage->setSetting('datetime_type', 'date');
...@@ -370,7 +372,7 @@ public function testDatelistWidget() { ...@@ -370,7 +372,7 @@ public function testDatelistWidget() {
// Display creation form. // Display creation form.
$this->drupalGet('entity_test/add'); $this->drupalGet('entity_test/add');
$this->assertFieldByXPath('//fieldset[@id="edit-' . $field_name . '-0"]/legend', $field_name, 'Fieldset and label found'); $this->assertFieldByXPath('//fieldset[@id="edit-' . $field_name . '-0"]/legend', $field_label, 'Fieldset and label found');
$this->assertFieldByXPath('//fieldset[@aria-describedby="edit-' . $field_name . '-0--description"]', NULL, 'ARIA described-by found'); $this->assertFieldByXPath('//fieldset[@aria-describedby="edit-' . $field_name . '-0--description"]', NULL, 'ARIA described-by found');
$this->assertFieldByXPath('//div[@id="edit-' . $field_name . '-0--description"]', NULL, 'ARIA description found'); $this->assertFieldByXPath('//div[@id="edit-' . $field_name . '-0--description"]', NULL, 'ARIA description found');
...@@ -511,7 +513,7 @@ public function testDatelistWidget() { ...@@ -511,7 +513,7 @@ public function testDatelistWidget() {
\Drupal::entityManager()->clearCachedFieldDefinitions(); \Drupal::entityManager()->clearCachedFieldDefinitions();
// Test the widget for validation notifications. // Test the widget for validation notifications.
foreach ($this->datelistDataProvider() as $data) { foreach ($this->datelistDataProvider($field_label) as $data) {
list($date_value, $expected) = $data; list($date_value, $expected) = $data;
// Display creation form. // Display creation form.
...@@ -562,13 +564,21 @@ public function testDatelistWidget() { ...@@ -562,13 +564,21 @@ public function testDatelistWidget() {
/** /**
* The data provider for testing the validation of the datelist widget. * The data provider for testing the validation of the datelist widget.
* *
* @param string $field_label
* The label of the field being tested.
*
* @return array * @return array
* An array of datelist input permutations to test. * An array of datelist input permutations to test.
*/ */
protected function datelistDataProvider() { protected function datelistDataProvider($field_label) {
return [ return [
// Nothing selected.
[['year' => '', 'month' => '', 'day' => '', 'hour' => '', 'minute' => ''], [
"The $field_label date is required.",
]],
// Year only selected, validation error on Month, Day, Hour, Minute. // Year only selected, validation error on Month, Day, Hour, Minute.
[['year' => 2012, 'month' => '', 'day' => '', 'hour' => '', 'minute' => ''], [ [['year' => 2012, 'month' => '', 'day' => '', 'hour' => '', 'minute' => ''], [
"The $field_label date is incomplete.",
'A value must be selected for month.', 'A value must be selected for month.',
'A value must be selected for day.', 'A value must be selected for day.',
'A value must be selected for hour.', 'A value must be selected for hour.',
...@@ -576,17 +586,20 @@ protected function datelistDataProvider() { ...@@ -576,17 +586,20 @@ protected function datelistDataProvider() {
]], ]],
// Year and Month selected, validation error on Day, Hour, Minute. // Year and Month selected, validation error on Day, Hour, Minute.
[['year' => 2012, 'month' => '12', 'day' => '', 'hour' => '', 'minute' => ''], [ [['year' => 2012, 'month' => '12', 'day' => '', 'hour' => '', 'minute' => ''], [
"The $field_label date is incomplete.",
'A value must be selected for day.', 'A value must be selected for day.',
'A value must be selected for hour.', 'A value must be selected for hour.',
'A value must be selected for minute.', 'A value must be selected for minute.',
]], ]],
// Year, Month and Day selected, validation error on Hour, Minute. // Year, Month and Day selected, validation error on Hour, Minute.
[['year' => 2012, 'month' => '12', 'day' => '31', 'hour' => '', 'minute' => ''], [ [['year' => 2012, 'month' => '12', 'day' => '31', 'hour' => '', 'minute' => ''], [
"The $field_label date is incomplete.",
'A value must be selected for hour.', 'A value must be selected for hour.',
'A value must be selected for minute.', 'A value must be selected for minute.',
]], ]],
// Year, Month, Day and Hour selected, validation error on Minute only. // Year, Month, Day and Hour selected, validation error on Minute only.
[['year' => 2012, 'month' => '12', 'day' => '31', 'hour' => '0', 'minute' => ''], [ [['year' => 2012, 'month' => '12', 'day' => '31', 'hour' => '0', 'minute' => ''], [
"The $field_label date is incomplete.",
'A value must be selected for minute.', 'A value must be selected for minute.',
]], ]],
]; ];
......
...@@ -46,6 +46,7 @@ protected function getTestFieldType() { ...@@ -46,6 +46,7 @@ protected function getTestFieldType() {
*/ */
public function testDateRangeField() { public function testDateRangeField() {
$field_name = $this->fieldStorage->getName(); $field_name = $this->fieldStorage->getName();
$field_label = $this->field->label();
// Loop through defined timezones to test that date-only fields work at the // Loop through defined timezones to test that date-only fields work at the
// extremes. // extremes.
...@@ -64,7 +65,7 @@ public function testDateRangeField() { ...@@ -64,7 +65,7 @@ public function testDateRangeField() {
$this->assertFieldByXPath('//*[@id="edit-' . $field_name . '-wrapper"]//label[contains(@class, "js-form-required")]', TRUE, 'Required markup found'); $this->assertFieldByXPath('//*[@id="edit-' . $field_name . '-wrapper"]//label[contains(@class, "js-form-required")]', TRUE, 'Required markup found');
$this->assertNoFieldByName("{$field_name}[0][value][time]", '', 'Start time element not found.'); $this->assertNoFieldByName("{$field_name}[0][value][time]", '', 'Start time element not found.');
$this->assertNoFieldByName("{$field_name}[0][end_value][time]", '', 'End time element not found.'); $this->assertNoFieldByName("{$field_name}[0][end_value][time]", '', 'End time element not found.');
$this->assertFieldByXPath('//fieldset[@id="edit-' . $field_name . '-0"]/legend', $field_name, 'Fieldset and label found'); $this->assertFieldByXPath('//fieldset[@id="edit-' . $field_name . '-0"]/legend', $field_label, 'Fieldset and label found');
$this->assertFieldByXPath('//fieldset[@aria-describedby="edit-' . $field_name . '-0--description"]', NULL, 'ARIA described-by found'); $this->assertFieldByXPath('//fieldset[@aria-describedby="edit-' . $field_name . '-0--description"]', NULL, 'ARIA described-by found');
$this->assertFieldByXPath('//div[@id="edit-' . $field_name . '-0--description"]', NULL, 'ARIA description found'); $this->assertFieldByXPath('//div[@id="edit-' . $field_name . '-0--description"]', NULL, 'ARIA description found');
...@@ -256,6 +257,7 @@ public function testDateRangeField() { ...@@ -256,6 +257,7 @@ public function testDateRangeField() {
*/ */
public function testDatetimeRangeField() { public function testDatetimeRangeField() {
$field_name = $this->fieldStorage->getName(); $field_name = $this->fieldStorage->getName();
$field_label = $this->field->label();
// Ensure the field to a datetime field. // Ensure the field to a datetime field.
$this->fieldStorage->setSetting('datetime_type', DateRangeItem::DATETIME_TYPE_DATETIME); $this->fieldStorage->setSetting('datetime_type', DateRangeItem::DATETIME_TYPE_DATETIME);
...@@ -267,7 +269,7 @@ public function testDatetimeRangeField() { ...@@ -267,7 +269,7 @@ public function testDatetimeRangeField() {
$this->assertFieldByName("{$field_name}[0][value][time]", '', 'Start time element found.'); $this->assertFieldByName("{$field_name}[0][value][time]", '', 'Start time element found.');
$this->assertFieldByName("{$field_name}[0][end_value][date]", '', 'End date element found.'); $this->assertFieldByName("{$field_name}[0][end_value][date]", '', 'End date element found.');
$this->assertFieldByName("{$field_name}[0][end_value][time]", '', 'End time element found.'); $this->assertFieldByName("{$field_name}[0][end_value][time]", '', 'End time element found.');
$this->assertFieldByXPath('//fieldset[@id="edit-' . $field_name . '-0"]/legend', $field_name, 'Fieldset and label found'); $this->assertFieldByXPath('//fieldset[@id="edit-' . $field_name . '-0"]/legend', $field_label, 'Fieldset and label found');
$this->assertFieldByXPath('//fieldset[@aria-describedby="edit-' . $field_name . '-0--description"]', NULL, 'ARIA described-by found'); $this->assertFieldByXPath('//fieldset[@aria-describedby="edit-' . $field_name . '-0--description"]', NULL, 'ARIA described-by found');
$this->assertFieldByXPath('//div[@id="edit-' . $field_name . '-0--description"]', NULL, 'ARIA description found'); $this->assertFieldByXPath('//div[@id="edit-' . $field_name . '-0--description"]', NULL, 'ARIA description found');
...@@ -428,6 +430,7 @@ public function testDatetimeRangeField() { ...@@ -428,6 +430,7 @@ public function testDatetimeRangeField() {
*/ */
public function testAlldayRangeField() { public function testAlldayRangeField() {
$field_name = $this->fieldStorage->getName(); $field_name = $this->fieldStorage->getName();
$field_label = $this->field->label();
// Ensure field is set to a all-day field. // Ensure field is set to a all-day field.
$this->fieldStorage->setSetting('datetime_type', DateRangeItem::DATETIME_TYPE_ALLDAY); $this->fieldStorage->setSetting('datetime_type', DateRangeItem::DATETIME_TYPE_ALLDAY);
...@@ -440,7 +443,7 @@ public function testAlldayRangeField() { ...@@ -440,7 +443,7 @@ public function testAlldayRangeField() {
$this->assertFieldByXPath('//*[@id="edit-' . $field_name . '-wrapper"]//label[contains(@class, "js-form-required")]', TRUE, 'Required markup found'); $this->assertFieldByXPath('//*[@id="edit-' . $field_name . '-wrapper"]//label[contains(@class, "js-form-required")]', TRUE, 'Required markup found');
$this->assertNoFieldByName("{$field_name}[0][value][time]", '', 'Start time element not found.'); $this->assertNoFieldByName("{$field_name}[0][value][time]", '', 'Start time element not found.');
$this->assertNoFieldByName("{$field_name}[0][end_value][time]", '', 'End time element not found.'); $this->assertNoFieldByName("{$field_name}[0][end_value][time]", '', 'End time element not found.');
$this->assertFieldByXPath('//fieldset[@id="edit-' . $field_name . '-0"]/legend', $field_name, 'Fieldset and label found'); $this->assertFieldByXPath('//fieldset[@id="edit-' . $field_name . '-0"]/legend', $field_label, 'Fieldset and label found');
$this->assertFieldByXPath('//fieldset[@aria-describedby="edit-' . $field_name . '-0--description"]', NULL, 'ARIA described-by found'); $this->assertFieldByXPath('//fieldset[@aria-describedby="edit-' . $field_name . '-0--description"]', NULL, 'ARIA described-by found');
$this->assertFieldByXPath('//div[@id="edit-' . $field_name . '-0--description"]', NULL, 'ARIA description found'); $this->assertFieldByXPath('//div[@id="edit-' . $field_name . '-0--description"]', NULL, 'ARIA description found');
...@@ -598,6 +601,7 @@ public function testAlldayRangeField() { ...@@ -598,6 +601,7 @@ public function testAlldayRangeField() {
*/ */
public function testDatelistWidget() { public function testDatelistWidget() {
$field_name = $this->fieldStorage->getName(); $field_name = $this->fieldStorage->getName();
$field_label = $this->field->label();
// Ensure field is set to a date only field. // Ensure field is set to a date only field.
$this->fieldStorage->setSetting('datetime_type', DateRangeItem::DATETIME_TYPE_DATE); $this->fieldStorage->setSetting('datetime_type', DateRangeItem::DATETIME_TYPE_DATE);
...@@ -616,7 +620,7 @@ public function testDatelistWidget() { ...@@ -616,7 +620,7 @@ public function testDatelistWidget() {
// Display creation form. // Display creation form.
$this->drupalGet('entity_test/add'); $this->drupalGet('entity_test/add');
$this->assertFieldByXPath('//fieldset[@id="edit-' . $field_name . '-0"]/legend', $field_name, 'Fieldset and label found'); $this->assertFieldByXPath('//fieldset[@id="edit-' . $field_name . '-0"]/legend', $field_label, 'Fieldset and label found');
$this->assertFieldByXPath('//fieldset[@aria-describedby="edit-' . $field_name . '-0--description"]', NULL, 'ARIA described-by found'); $this->assertFieldByXPath('//fieldset[@aria-describedby="edit-' . $field_name . '-0--description"]', NULL, 'ARIA described-by found');
$this->assertFieldByXPath('//div[@id="edit-' . $field_name . '-0--description"]', NULL, 'ARIA description found'); $this->assertFieldByXPath('//div[@id="edit-' . $field_name . '-0--description"]', NULL, 'ARIA description found');
...@@ -1121,6 +1125,7 @@ public function testInvalidField() { ...@@ -1121,6 +1125,7 @@ public function testInvalidField() {
$this->fieldStorage->setSetting('datetime_type', DateRangeItem::DATETIME_TYPE_DATETIME); $this->fieldStorage->setSetting('datetime_type', DateRangeItem::DATETIME_TYPE_DATETIME);
$this->fieldStorage->save(); $this->fieldStorage->save();
$field_name = $this->fieldStorage->getName(); $field_name = $this->fieldStorage->getName();
$field_label = $this->field->label();
$this->drupalGet('entity_test/add'); $this->drupalGet('entity_test/add');
$this->assertFieldByName("{$field_name}[0][value][date]", '', 'Start date element found.'); $this->assertFieldByName("{$field_name}[0][value][date]", '', 'Start date element found.');
...@@ -1299,7 +1304,7 @@ public function testInvalidField() { ...@@ -1299,7 +1304,7 @@ public function testInvalidField() {
"{$field_name}[0][end_value][time]" => '12:00:00', "{$field_name}[0][end_value][time]" => '12:00:00',
]; ];
$this->drupalPostForm(NULL, $edit, t('Save')); $this->drupalPostForm(NULL, $edit, t('Save'));
$this->assertText(new FormattableMarkup('The @title end date cannot be before the start date', ['@title' => $field_name]), 'End date before start date has been caught.'); $this->assertText(new FormattableMarkup('The @title end date cannot be before the start date', ['@title' => $field_label]), 'End date before start date has been caught.');
$edit = [ $edit = [
"{$field_name}[0][value][date]" => '2012-12-01', "{$field_name}[0][value][date]" => '2012-12-01',
...@@ -1308,7 +1313,7 @@ public function testInvalidField() { ...@@ -1308,7 +1313,7 @@ public function testInvalidField() {
"{$field_name}[0][end_value][time]" => '11:00:00', "{$field_name}[0][end_value][time]" => '11:00:00',
]; ];
$this->drupalPostForm(NULL, $edit, t('Save')); $this->drupalPostForm(NULL, $edit, t('Save'));
$this->assertText(new FormattableMarkup('The @title end date cannot be before the start date', ['@title' => $field_name]), 'End time before start time has been caught.'); $this->assertText(new FormattableMarkup('The @title end date cannot be before the start date', ['@title' => $field_label]), 'End time before start time has been caught.');
} }
/** /**
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment