Issue #2486019 by mpdonadio, pfrenssen, webflo, michielnugter, maris.abols, ivanjaros, hauruck, MickeA, Jo Fitzgerald, mian3010, iMiksu: Wrong validation messages in Datelist::validateDatelist()

8.4.x
Nathaniel Catchpole 2017-06-07 15:25:41 +01:00
parent 1105324af3
commit fde3369b6c
5 changed files with 69 additions and 13 deletions

View File

@ -2,6 +2,7 @@
namespace Drupal\Core\Datetime\Element;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\Render\Element\FormElement;
@ -69,4 +70,36 @@ abstract class DateElementBase extends FormElement {
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;
}
}

View File

@ -302,6 +302,8 @@ class Datelist extends DateElementBase {
public static function validateDatelist(&$element, FormStateInterface $form_state, &$complete_form) {
$input_exists = FALSE;
$input = NestedArray::getValue($form_state->getValues(), $element['#parents'], $input_exists);
$title = static::getElementTitle($element, $complete_form);
if ($input_exists) {
$all_empty = static::checkEmptyInputs($input, $element['#date_part_order']);
@ -311,10 +313,11 @@ class Datelist extends DateElementBase {
}
// 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']) {
$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)) {
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]));
}
}
@ -326,7 +329,7 @@ class Datelist extends DateElementBase {
}
// If the input is invalid and an error doesn't exist, set one.
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]));
}
}
}

View File

@ -107,6 +107,7 @@ abstract class DateTestBase extends BrowserTestBase {
*/
protected function createField() {
$field_name = Unicode::strtolower($this->randomMachineName());
$field_label = Unicode::ucfirst(Unicode::strtolower($this->randomMachineName()));
$type = $this->getTestFieldType();
$widget_type = $formatter_type = $type . '_default';
@ -119,8 +120,9 @@ abstract class DateTestBase extends BrowserTestBase {
$this->fieldStorage->save();
$this->field = FieldConfig::create([
'field_storage' => $this->fieldStorage,
'label' => $field_label,
'bundle' => 'entity_test',
'description' => 'Description for ' . $field_name,
'description' => 'Description for ' . $field_label,
'required' => TRUE,
]);
$this->field->save();

View File

@ -208,6 +208,7 @@ class DateTimeFieldTest extends DateTestBase {
*/
public function testDatetimeField() {
$field_name = $this->fieldStorage->getName();
$field_label = $this->field->label();
// Change the field to a datetime field.
$this->fieldStorage->setSetting('datetime_type', 'datetime');
$this->fieldStorage->save();
@ -216,7 +217,7 @@ class DateTimeFieldTest extends DateTestBase {
$this->drupalGet('entity_test/add');
$this->assertFieldByName("{$field_name}[0][value][date]", '', 'Date 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('//div[@id="edit-' . $field_name . '-0--description"]', NULL, 'ARIA description found');
@ -352,6 +353,7 @@ class DateTimeFieldTest extends DateTestBase {
*/
public function testDatelistWidget() {
$field_name = $this->fieldStorage->getName();
$field_label = $this->field->label();
// Ensure field is set to a date only field.
$this->fieldStorage->setSetting('datetime_type', 'date');
@ -370,7 +372,7 @@ class DateTimeFieldTest extends DateTestBase {
// Display creation form.
$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('//div[@id="edit-' . $field_name . '-0--description"]', NULL, 'ARIA description found');
@ -511,7 +513,7 @@ class DateTimeFieldTest extends DateTestBase {
\Drupal::entityManager()->clearCachedFieldDefinitions();
// Test the widget for validation notifications.
foreach ($this->datelistDataProvider() as $data) {
foreach ($this->datelistDataProvider($field_label) as $data) {
list($date_value, $expected) = $data;
// Display creation form.
@ -562,13 +564,21 @@ class DateTimeFieldTest extends DateTestBase {
/**
* The data provider for testing the validation of the datelist widget.
*
* @param string $field_label
* The label of the field being tested.
*
* @return array
* An array of datelist input permutations to test.
*/
protected function datelistDataProvider() {
protected function datelistDataProvider($field_label) {
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' => 2012, 'month' => '', 'day' => '', 'hour' => '', 'minute' => ''], [
"The $field_label date is incomplete.",
'A value must be selected for month.',
'A value must be selected for day.',
'A value must be selected for hour.',
@ -576,17 +586,20 @@ class DateTimeFieldTest extends DateTestBase {
]],
// Year and Month selected, validation error on 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 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' => ''], [
"The $field_label date is incomplete.",
'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' => ''], [
"The $field_label date is incomplete.",
'A value must be selected for minute.',
]],
];

View File

@ -46,6 +46,7 @@ class DateRangeFieldTest extends DateTestBase {
*/
public function testDateRangeField() {
$field_name = $this->fieldStorage->getName();
$field_label = $this->field->label();
// Loop through defined timezones to test that date-only fields work at the
// extremes.
@ -64,7 +65,7 @@ class DateRangeFieldTest extends DateTestBase {
$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][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('//div[@id="edit-' . $field_name . '-0--description"]', NULL, 'ARIA description found');
@ -256,6 +257,7 @@ class DateRangeFieldTest extends DateTestBase {
*/
public function testDatetimeRangeField() {
$field_name = $this->fieldStorage->getName();
$field_label = $this->field->label();
// Ensure the field to a datetime field.
$this->fieldStorage->setSetting('datetime_type', DateRangeItem::DATETIME_TYPE_DATETIME);
@ -267,7 +269,7 @@ class DateRangeFieldTest extends DateTestBase {
$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][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('//div[@id="edit-' . $field_name . '-0--description"]', NULL, 'ARIA description found');
@ -428,6 +430,7 @@ class DateRangeFieldTest extends DateTestBase {
*/
public function testAlldayRangeField() {
$field_name = $this->fieldStorage->getName();
$field_label = $this->field->label();
// Ensure field is set to a all-day field.
$this->fieldStorage->setSetting('datetime_type', DateRangeItem::DATETIME_TYPE_ALLDAY);
@ -440,7 +443,7 @@ class DateRangeFieldTest extends DateTestBase {
$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][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('//div[@id="edit-' . $field_name . '-0--description"]', NULL, 'ARIA description found');
@ -598,6 +601,7 @@ class DateRangeFieldTest extends DateTestBase {
*/
public function testDatelistWidget() {
$field_name = $this->fieldStorage->getName();
$field_label = $this->field->label();
// Ensure field is set to a date only field.
$this->fieldStorage->setSetting('datetime_type', DateRangeItem::DATETIME_TYPE_DATE);
@ -616,7 +620,7 @@ class DateRangeFieldTest extends DateTestBase {
// Display creation form.
$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('//div[@id="edit-' . $field_name . '-0--description"]', NULL, 'ARIA description found');
@ -1121,6 +1125,7 @@ class DateRangeFieldTest extends DateTestBase {
$this->fieldStorage->setSetting('datetime_type', DateRangeItem::DATETIME_TYPE_DATETIME);
$this->fieldStorage->save();
$field_name = $this->fieldStorage->getName();
$field_label = $this->field->label();
$this->drupalGet('entity_test/add');
$this->assertFieldByName("{$field_name}[0][value][date]", '', 'Start date element found.');
@ -1299,7 +1304,7 @@ class DateRangeFieldTest extends DateTestBase {
"{$field_name}[0][end_value][time]" => '12:00:00',
];
$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 = [
"{$field_name}[0][value][date]" => '2012-12-01',
@ -1308,7 +1313,7 @@ class DateRangeFieldTest extends DateTestBase {
"{$field_name}[0][end_value][time]" => '11:00:00',
];
$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.');
}
/**